Merge
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
*.swp
|
||||
*.bat
|
||||
*.json
|
||||
sets/
|
||||
|
||||
.idea/
|
||||
|
|
44
atlas.html
|
@ -1,44 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<link rel="icon" href="favicon.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<title>ATLAS???</title>
|
||||
</head>
|
||||
<body class="all">
|
||||
<div class="center">
|
||||
<header class = "header nomarginp">
|
||||
<div class = "headerleft" id = "headerleft">
|
||||
</div>
|
||||
<div class = "headercenter" id = "headercenter">
|
||||
<div >
|
||||
<p class = "itemp" id = "header">Atlas???</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "headerright" id = "headerright">
|
||||
<div>
|
||||
<button class = "atlas" onclick = "atlasClick()" style = "padding:0">
|
||||
<img src = "/favicon.png" style = "left:0;top:0;width:48px;height:48px"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
<div class = "center" id = "flavortext">
|
||||
...
|
||||
</div>
|
||||
<div class = "center bodydiv" id = "bodydiv">
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/js/loadheader.js"></script>
|
||||
<script type="text/javascript" src="/js/icons.js"></script>
|
||||
<script type="text/javascript" src="/js/atlas.js"></script>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
71
atlas/index.html
Normal file
|
@ -0,0 +1,71 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ATLAS???</title>
|
||||
|
||||
<link rel="icon" href="favicon.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
|
||||
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
|
||||
|
||||
<link rel="stylesheet" href="../css/sidebar.css">
|
||||
<link rel="stylesheet" href="../css/sq2bs.css">
|
||||
</head>
|
||||
<body class="text-light d-flex justify-content-center" id = "body">
|
||||
<div id="main-sidebar" class="sidebar dark-7 dark-shadow col">
|
||||
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
|
||||
<a href = "../crafter/"><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href = "../items/"><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
|
||||
<a href = "/customizer.html"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
|
||||
<a href = "/map.html"><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
|
||||
<a href = "/wynnfo/index.html"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
|
||||
<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 py-1 vh-100">
|
||||
<div class = "row">
|
||||
<div class = "col-4" id = "headerleft">
|
||||
</div>
|
||||
<div class = "col-4 d-flex justify-content-center" id = "headercenter" style = "color:#fbcd49; font-size: 200%;">
|
||||
<div>
|
||||
<p class = "itemp box-title" id = "header">Atlas???</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-4 d-flex justify-content-end" id = "headerright">
|
||||
<div>
|
||||
<button class = "rounded atlas" onclick = "atlasClick()" style = "padding:0; text-align: right">
|
||||
<img src = "favicon.png" class = "rounded" style = "left:0;top:0;width:48px;height:48px"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row d-flex text-center justify-content-center scaled-font" id = "flavortext">
|
||||
...
|
||||
</div>
|
||||
<div class = "col navbar navbar-fixed-bottom vh-75 min-vh-50 text-break ml-5" id = "bodydiv" style = "min-height: 75vh; display: flex; flex-direction: column;" >
|
||||
</div>
|
||||
<audio id="bruh_sound_effect" src="../media/audio/bruh_sound_effect.mp3" preload="auto"></audio>
|
||||
</div>
|
||||
<!-- sidebar -->
|
||||
|
||||
<!-- main body -->
|
||||
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/autoComplete.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/macy@2"></script>
|
||||
|
||||
|
||||
<script type="text/javascript" src="../js/utils.js"></script>
|
||||
<script type="text/javascript" src="../js/icons.js"></script>
|
||||
<script type="text/javascript" src="../js/atlas.js"></script>
|
||||
</body>
|
||||
</html>
|
1439
builder/doc.html
Normal file
1432
builder/index.html
Normal file
3107
clean.json
229
crafter.html
|
@ -1,229 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<link rel="stylesheet" media="screen and (min-width: 1100px)" href="/css/crafter-wide.css"/>
|
||||
<link rel="stylesheet" media="screen and (max-width: 1099px)" href="/css/crafter-narrow.css"/>
|
||||
<link rel="icon" href="./media/icons/new/crafter.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<title>WynnCrafter</title>
|
||||
</head>
|
||||
<body class="all">
|
||||
<div class="center">
|
||||
<header class = "header nomarginp">
|
||||
<div class = "headerleft" id = "headerleft">
|
||||
</div>
|
||||
<div class = "headercenter" id = "headercenter">
|
||||
<div >
|
||||
<p class = "itemp" id = "header">WynnCrafter</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "headerright" id = "headerright">
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
<div class = "container" display = "grid">
|
||||
<div class = "center" style = "grid-column:1;grid-row:1" >
|
||||
<div class = "crafter">
|
||||
<table class = "center">
|
||||
<tr>
|
||||
<td>
|
||||
<label for="recipe-choice">Recipe Type:</label>
|
||||
<br/>
|
||||
<input class="recipeinput" list="recipe-choices" id="recipe-choice" name="recipe-choice" placeholder="Potion"/>
|
||||
<datalist id="recipe-choices">
|
||||
</datalist>
|
||||
</td>
|
||||
<td >
|
||||
<label for="level-choice">Recipe Level:</label>
|
||||
<br/>
|
||||
<input class="levelinput" list="level-choices" id="level-choice" name="level-choice" placeholder="103-105" />
|
||||
<datalist id="level-choices">
|
||||
</datalist>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">
|
||||
<label id = "mat-1" for="mat-1-tier-choice">Material 1 Tier:</label>
|
||||
<br/>
|
||||
<button class = "button Star" id = "mat-1-1" onclick = "toggleMaterial('mat-1-1')">1</button>
|
||||
<button class = "button Star" id = "mat-1-2" onclick = "toggleMaterial('mat-1-2')">2</button>
|
||||
<button class = "button Star" id = "mat-1-3" onclick = "toggleMaterial('mat-1-3')">3</button>
|
||||
</td>
|
||||
<td class="center">
|
||||
<label id = "mat-2" for="mat-2-tier-choice">Material 2 Tier:</label>
|
||||
<br/>
|
||||
<button class = "button Star" id = "mat-2-1" onclick = "toggleMaterial('mat-2-1')">1</button>
|
||||
<button class = "button Star" id = "mat-2-2" onclick = "toggleMaterial('mat-2-2')">2</button>
|
||||
<button class = "button Star" id = "mat-2-3" onclick = "toggleMaterial('mat-2-3')">3</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">
|
||||
<label for="ing-choice-1">Ingredient 1:</label>
|
||||
<br/>
|
||||
<input class="inginput" list="ing-choices-1" id="ing-choice-1" name="ing-choice-1" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-1">
|
||||
</datalist>
|
||||
</td>
|
||||
<td class="center">
|
||||
<label for="ing-choice-2">Ingredient 2:</label>
|
||||
<br/>
|
||||
<input class="inginput" list="ing-choices-2" id="ing-choice-2" name="ing-choice-2" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-2">
|
||||
</datalist>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">
|
||||
<label for="ing-choice-3">Ingredient 3:</label>
|
||||
<br/>
|
||||
<input class="inginput" list="ing-choices-3" id="ing-choice-3" name="ing-choice-3" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-3">
|
||||
</datalist>
|
||||
</td>
|
||||
<td class="center">
|
||||
<label for="ing-choice-4">Ingredient 4:</label>
|
||||
<br/>
|
||||
<input class="inginput" list="ing-choices-4" id="ing-choice-4" name="ing-choice-4" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-4">
|
||||
</datalist>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="center">
|
||||
<label for="ing-choice-5">Ingredient 5:</label>
|
||||
<br/>
|
||||
<input class="inginput" list="ing-choices-5" id="ing-choice-5" name="ing-choice-5" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-5">
|
||||
</datalist>
|
||||
</td>
|
||||
<td class="center">
|
||||
<label for="ing-choice-6">Ingredient 6:</label>
|
||||
<br/>
|
||||
<input class="inginput" list="ing-choices-6" id="ing-choice-6" name="ing-choice-6" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-6">
|
||||
</datalist>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<label for = "atkSpdChoices"> Attack Speed (weapons only):</label>
|
||||
<table class = "center" id = "atkSpdChoices">
|
||||
<tr>
|
||||
<td class = "center">
|
||||
<button class = "button" id = "slow-atk-button" onclick = "toggleAtkSpd('slow-atk-button')">
|
||||
Slow
|
||||
</button>
|
||||
</td>
|
||||
<td class = "center">
|
||||
<button class = "button" id = "normal-atk-button" onclick = "toggleAtkSpd('normal-atk-button')">
|
||||
Normal
|
||||
</button>
|
||||
</td>
|
||||
<td class = "center">
|
||||
<button class = "button" id = "fast-atk-button" onclick = "toggleAtkSpd('fast-atk-button')">
|
||||
Fast
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class = "center">
|
||||
<tr>
|
||||
<td class = "center">
|
||||
<button class = "button" id = "craft-button" onclick = "calculateCraft()">
|
||||
Craft Item
|
||||
</button>
|
||||
</td>
|
||||
<td class = "center">
|
||||
<button class = "button" id = "reset-button" onclick = "resetFields()">
|
||||
Reset
|
||||
</button>
|
||||
</td>
|
||||
<td class = "center">
|
||||
<button class = "button" id = "copy-button" onclick = "copyRecipe()">
|
||||
Copy Short
|
||||
</button>
|
||||
</td>
|
||||
<td class = "center">
|
||||
<button class = "button" id = "share-button" onclick = "shareRecipe()">
|
||||
Copy Long
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "recipe center hide-container-block" style = "display:none">
|
||||
<div class = "recipe-stats">
|
||||
<div class = "center" id = "recipe-stats"></div>
|
||||
</div>
|
||||
<div class = "craft-warnings">
|
||||
<div class = "center" id = "craft-warnings"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "crafted center hide-container-block" style = "display:none">
|
||||
<div class = "craft-stats">
|
||||
<div class = "center" id = "craft-stats"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--This is also incredibly janky.-->
|
||||
<p></p>
|
||||
<p></p>
|
||||
<p></p>
|
||||
<div class="ingredients-container hide-container-grid" id = "ingreds" style = "display:none">
|
||||
<p class="center title hide-container-block" style = "display:block">
|
||||
Ingredients
|
||||
</p>
|
||||
<div class = "ingredients hide-container-grid" id = "ingreds">
|
||||
<div class="ing-stats" id = "ing-1" style = "grid-item-1">
|
||||
<div class = "center" id = "ing-1-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats" id = "ing-2" style = "grid-item-2">
|
||||
<div class = "center" id = "ing-2-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats" id = "ing-3" style = "grid-item-3">
|
||||
<div class = "center" id = "ing-3-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats" id = "ing-4" style = "grid-item-4">
|
||||
<div class = "center" id = "ing-4-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats" id = "ing-5" style = "grid-item-5">
|
||||
<div class = "center" id = "ing-5-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats" id = "ing-6" style = "grid-item-6">
|
||||
<div class = "center" id = "ing-6-stats"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "center">
|
||||
<footer>
|
||||
<div class="center" id="header2">
|
||||
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "./atlas.html" target = "_blank" class = "link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||
</div>
|
||||
<div class="center" id="credits">
|
||||
<a href="https://hppeng-wynn.github.io/credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<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/loadheader.js"></script>
|
||||
<script type="text/javascript" src="/js/icons.js"></script>
|
||||
<script type="text/javascript" src="/js/powders.js"></script>
|
||||
<script type="text/javascript" src="/js/skillpoints.js"></script>
|
||||
<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/load_ing.js"></script>
|
||||
<script type="text/javascript" src="/js/craft.js"></script>
|
||||
<script type="text/javascript" src="/js/crafter.js"></script>
|
||||
</body>
|
||||
</html>
|
313
crafter/index.html
Normal file
|
@ -0,0 +1,313 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WynnCrafter</title>
|
||||
<link rel="icon" href="../media/icons/new/crafter.png" type="image/icon type">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
|
||||
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../css/sq2bs.css">
|
||||
<link rel="stylesheet" href="../css/sidebar.css">
|
||||
<link rel="stylesheet" href="../css/wynnstyles.css">
|
||||
</head>
|
||||
<body id = "body" class = "text-light d-flex justify-content-center">
|
||||
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
|
||||
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
|
||||
<a href = ""><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href = "../items/"><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
|
||||
<a href = "../custom/"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
|
||||
<a href = "../map/"><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
|
||||
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
|
||||
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
|
||||
<hr/>
|
||||
<a href = "https://discord.gg/CGavnAnerv" target = "_blank"><img src = "../media/icons/discord.png" alt = "WB Discord" title = "WB Discord"><b>WB Discord</b></a>
|
||||
</div>
|
||||
<div class = "container py-5 vh-100 mx-0 mx-lg-auto">
|
||||
<div class = "col">
|
||||
<div class = "row g-3">
|
||||
<div class = "col-lg-5 col-sm-12 text-center">
|
||||
<div class = "row gx-5 mb-2">
|
||||
<div class = "col-lg-6 col-sm-12">
|
||||
<div id = "recipe-dropdown" class = "row h-100 dark-shadow dark-6 rounded">
|
||||
<div id = "recipe-img-loc" class = "col-auto px-lg-1 g-0 dark-7 rounded-end my-auto text-center scaled-item-icon">
|
||||
<img id = "recipe-img" class = "img-fluid rounded Crafted-shadow" src = "../media/items/new/generic-potion.png">
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<div class = "row align-items-center">
|
||||
<div class = "col-4 px-0">
|
||||
<p class = "text-right mb-0 scaled-font fw-bold">Type:</p>
|
||||
</div>
|
||||
<div class = "col-7 px-0">
|
||||
<input class="recipeinput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="recipe-choices" id="recipe-choice" name="recipe-choice" placeholder="Potion"/>
|
||||
<datalist id="recipe-choices">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row align-items-center">
|
||||
<div class = "col-4 px-0">
|
||||
<p class = "text-right mb-0 scaled-font fw-bold">Lv:</p>
|
||||
</div>
|
||||
<div class = "col-7 px-0">
|
||||
<input class="levelinput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="level-choices" id="level-choice" name="level-choice" placeholder="103-105" />
|
||||
<datalist id="level-choices">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-6 col-sm-12">
|
||||
<div id = "atkSpdChoices" class = "row h-100 dark-shadow dark-6 rounded">
|
||||
<div class = "col py-2">
|
||||
<div class = "row h-50 align-items-center">
|
||||
<p class = "text-right mb-0 scaled-font fw-bold">Attack Speed</p>
|
||||
</div>
|
||||
<div class = "row h-50">
|
||||
<div class = "col-4 pl-1">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "slow-atk-button" onclick = "toggleAtkSpd('slow-atk-button')">
|
||||
Slow
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-4 px-0">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "normal-atk-button" onclick = "toggleAtkSpd('normal-atk-button')">
|
||||
Normal
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-4 pr-1">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "fast-atk-button" onclick = "toggleAtkSpd('fast-atk-button')">
|
||||
Fast
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row gx-5 mb-2">
|
||||
<div class = "col-lg-6 col-sm-12 justify-content-center">
|
||||
<div class = "row dark-shadow dark-6 rounded py-1 align-items-center">
|
||||
<div class = "col-6 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold" id = "mat-1">Mat 1 Tier:</p>
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<button class = "button Star rounded scaled-font fw-bold text-light dark-5" id = "mat-1-1" onclick = "toggleMaterial('mat-1-1')">1</button>
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<button class = "button Star rounded scaled-font fw-bold text-light dark-5" id = "mat-1-2" onclick = "toggleMaterial('mat-1-2')">2</button>
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<button class = "button Star rounded scaled-font fw-bold text-light dark-5" id = "mat-1-3" onclick = "toggleMaterial('mat-1-3')">3</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-6 col-sm-12 justify-content-center">
|
||||
<div class = "row dark-shadow dark-6 rounded py-1 align-items-center">
|
||||
<div class = "col-6 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold" id = "mat-2">Mat 2 Tier:</p>
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<button class = "button Star rounded scaled-font fw-bold text-light dark-5" id = "mat-2-1" onclick = "toggleMaterial('mat-2-1')">1</button>
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<button class = "button Star rounded scaled-font fw-bold text-light dark-5" id = "mat-2-2" onclick = "toggleMaterial('mat-2-2')">2</button>
|
||||
</div>
|
||||
<div class = "col px-0">
|
||||
<button class = "button Star rounded scaled-font fw-bold text-light dark-5" id = "mat-2-3" onclick = "toggleMaterial('mat-2-3')">3</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row gx-5 mb-1">
|
||||
<div class = "col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 1:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
<input class="inginput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="ing-choices-1" id="ing-choice-1" name="ing-choice-1" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-1">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 2:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
<input class="inginput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="ing-choices-2" id="ing-choice-2" name="ing-choice-2" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-2">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row gx-5 mb-1">
|
||||
<div class = "col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 3:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
<input class="inginput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="ing-choices-3" id="ing-choice-3" name="ing-choice-3" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-3">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 4:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
<input class="inginput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="ing-choices-4" id="ing-choice-4" name="ing-choice-4" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-4">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row gx-5 mb-1">
|
||||
<div class = "col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 5:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
<input class="inginput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="ing-choices-5" id="ing-choice-5" name="ing-choice-5" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-5">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col">
|
||||
<div class = "row h-100 dark-shadow dark-6 rounded align-items-center">
|
||||
<div class = "col-3 px-0">
|
||||
<p class = "mb-0 scaled-font fw-bold">Ing 6:</p>
|
||||
</div>
|
||||
<div class = "col-9 px-0">
|
||||
<input class="inginput border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" list="ing-choices-6" id="ing-choice-6" name="ing-choice-6" placeholder="No Ingredient" />
|
||||
<datalist id="ing-choices-6">
|
||||
</datalist>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class = "row rounded dark-shadow dark-6 py-2 gy-3">
|
||||
<div class = "col-lg-2 col-sm-6">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "reset-button" onclick = "resetFields()">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-6">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "copy-hash-button" onclick = "copyRecipeHash()">
|
||||
Copy Hash
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-lg-4 col-sm-6">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "copy-button" onclick = "copyRecipe()">
|
||||
Copy Short
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-6">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "share-button" onclick = "shareRecipe()">
|
||||
Copy Long
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-4">
|
||||
<div class = "recipe hide-container-block px-3 col rounded dark-6 text-light scaled-font p-3 border-dark dark-shadow g-0" style = "display:none">
|
||||
<div class = "row recipe-stats">
|
||||
<div class = "col" id = "recipe-stats"></div>
|
||||
</div>
|
||||
<div class = "row craft-warnings">
|
||||
<div class = "" id = "craft-warnings"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-3">
|
||||
<div class = "crafted row hide-container-block" style = "display:none">
|
||||
<div class = "craft-stats">
|
||||
<div class = "col rounded dark-6 text-light scaled-font p-3 border-dark dark-shadow g-0" id = "craft-stats"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col ingredients-container hide-container-grid" id = "ingreds" style = "display:none">
|
||||
<div class = "col-lg-6 col-sm-12 hide-container-grid" id = "ingreds">
|
||||
<div class = "row mb-3">
|
||||
<p class="box-title hide-container-block">
|
||||
Ingredients
|
||||
</p>
|
||||
</div>
|
||||
<div class = "row mb-3">
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-1">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-1-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-2">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-2-stats"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row mb-3">
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-3">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-3-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-4">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-4-stats"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row mb-3">
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-5">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-5-stats"></div>
|
||||
</div>
|
||||
<div class="ing-stats col-lg-6 col-sm scaled-font" id = "ing-6">
|
||||
<div class = "rounded col g-0 dark-6 border border-3 border-dark dark-shadow p-3" id = "ing-6-stats"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col dark-5 scaled-font">
|
||||
<footer class="text-center">
|
||||
<div id="header2">
|
||||
<p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||
</div>
|
||||
<div id="credits">
|
||||
<a href="credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
</footer>
|
||||
</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/sq2icons.js"></script>
|
||||
<script type="text/javascript" src="../js/powders.js"></script>
|
||||
<script type="text/javascript" src="../js/skillpoints.js"></script>
|
||||
<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/load_ing.js"></script>
|
||||
<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>
|
||||
<script type="text/javascript" src="../js/items.js"></script>
|
||||
<!-- <script type="text/javascript" src="../js/sq2items.js"></script> -->
|
||||
</body>
|
||||
</html>
|
10
credits.txt
|
@ -1,14 +1,20 @@
|
|||
|
||||
Theme, formatting, and overall inspiration: Wynndata (Dukio)
|
||||
- https://wynndata.tk
|
||||
|
||||
The game, of course
|
||||
- wynncraft.com
|
||||
|
||||
Additional Contributors:
|
||||
Additional Contributors, in no particular order:
|
||||
- Kiocifer (Icons!)
|
||||
- IncinerateMe (helping transition to 1.20.3 / CI helper)
|
||||
- puppy (wynn2 ability tree help)
|
||||
- SockMower (ability tree encode/decode optimization)
|
||||
- ITechnically (coding emotional support / misc)
|
||||
- touhoku (best IM)
|
||||
- HeyZeer0 (huge help in getting our damage formulas right)
|
||||
- Lennon (Skill point formula reversing)
|
||||
- Phanta (WynnAtlas custom expression parser / item search)
|
||||
- QuantumNep (Layout code/layout ideas)
|
||||
- nbcss (Crafted Item mechanics reverse engineering)
|
||||
- dr_carlos (Hiding UI elements properly, fade animations, proper error handling)
|
||||
- Atlas Inc discord (feedback, ideas, damage calc, etc)
|
||||
|
|
|
@ -102,7 +102,7 @@ main .heart {
|
|||
}
|
||||
|
||||
.full-width > img {
|
||||
width: 100%;
|
||||
width: 80%;
|
||||
background-color: #000;
|
||||
filter: brightness(0.7) contrast(1.2) grayscale(0.5);
|
||||
}
|
||||
|
|
85
css/dev.css
Normal file
|
@ -0,0 +1,85 @@
|
|||
/* General Styling - used for all elements on /dev/ */
|
||||
.row > * {
|
||||
margin-bottom: 0.75rem;
|
||||
margin-left: 0.5rem;
|
||||
padding-left: 0 !important; /*Override grid.scss (bs) */
|
||||
}
|
||||
|
||||
.row {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
border-left: 3px solid white;
|
||||
border-radius: 0.1rem;
|
||||
/* padding-right: 0rem !important;
|
||||
padding-left: 0rem !important; */
|
||||
}
|
||||
|
||||
.section-title:hover{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 1.5rem;;
|
||||
}
|
||||
|
||||
ul {
|
||||
/*Needed to override <ul> style in sq2bs.css*/
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
.indent {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
/* Math */
|
||||
math, number {
|
||||
font-family: 'CMU Serif', 'Cambria Math', 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
number {
|
||||
color: rgb(17, 234, 9);
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Variable Sizing (specific to dev page) */
|
||||
@media screen and (max-width: 600px) {
|
||||
.section-title > * {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) and (max-width: 1400px) {
|
||||
.section-title > * {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1400px) {
|
||||
.section-title > *{
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
}
|
|
@ -6,12 +6,6 @@
|
|||
.overall-container{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.container{
|
||||
width: 95%;
|
||||
border: 3px solid #BCBCBC;
|
||||
border-radius: 3px;
|
||||
padding: 2% 4% 4%;
|
||||
}
|
||||
.coord-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
|
|
|
@ -15,11 +15,6 @@
|
|||
grid-column-gap: 5px;
|
||||
grid-auto-rows: minmax(32px, auto);
|
||||
}
|
||||
.container{
|
||||
width: 95%;
|
||||
border: 3px solid #BCBCBC;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.leaflet-tooltip.labelp {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
font-size: 1.1em;
|
||||
|
|
48
css/sidebar.css
Normal file
|
@ -0,0 +1,48 @@
|
|||
:root {
|
||||
--sidebar-width: 3.5vw;
|
||||
}
|
||||
|
||||
/* sidebar stuff */
|
||||
.sidebar {
|
||||
height: 100%; /* 100% Full-height */
|
||||
width: var(--sidebar-width); /* 0 width - change this with JavaScript */
|
||||
position: fixed; /* Stay in place */
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow-x: hidden; /* Disable horizontal scroll */
|
||||
transition: 0.5s; /* 0.5 second transition effect to slide in the sidebar */
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.sidebar:hover {
|
||||
width: 11vw;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
padding: .4vw .4vw .4vw .4vw;
|
||||
display: block;
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebar a img {
|
||||
margin-right: .6vw;
|
||||
width: 2.5vw;
|
||||
}
|
||||
|
||||
.sidebar a b {
|
||||
font-size: .85vw;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background-color: hsl(0, 0%, 8%);
|
||||
color: rgb(210, 210, 210);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
:root {
|
||||
--sidebar-width: 0px;
|
||||
}
|
||||
.sidebar {display: none;}
|
||||
}
|
507
css/sq2bs.css
Normal file
|
@ -0,0 +1,507 @@
|
|||
* {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
}
|
||||
|
||||
#body {
|
||||
background-color: #121212;
|
||||
}
|
||||
/* builder containers */
|
||||
|
||||
|
||||
.e_slider, .t_slider, .w_slider, .f_slider, .a_slider {
|
||||
-webkit-appearance: none;
|
||||
background: #AAAAAA;
|
||||
border-radius: 30px;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
/***** Chrome, Safari, Opera, and Edge Chromium *****/
|
||||
.e_slider::-webkit-slider-runnable-track, .t_slider::-webkit-slider-runnable-track, .w_slider::-webkit-slider-runnable-track, .f_slider::-webkit-slider-runnable-track, .a_slider::-webkit-slider-runnable-track {
|
||||
-webkit-appeareance: none;
|
||||
background:transparent;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
/******** Firefox **** **/
|
||||
.e_slider::-moz-range-track, .t_slider::-moz-range-track, .w_slider::-moz-range-track, .f_slider::-moz-range-track, .a_slider::-moz-range-track {
|
||||
-webkit-appearance: none;
|
||||
background-color: transparent;
|
||||
border-radius: 30px;
|
||||
height: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
.e_slider::-webkit-slider-thumb, .t_slider::-webkit-slider-thumb, .w_slider::-webkit-slider-thumb, .f_slider::-webkit-slider-thumb, .a_slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
height: 0.75rem;
|
||||
width: 0.75rem;
|
||||
border-radius: 0.375rem;
|
||||
margin-top: -0.125rem;
|
||||
background-color: #FFFFFF;
|
||||
border: solid 2px #82CFD0;
|
||||
}
|
||||
|
||||
input[type=range]:focus {
|
||||
outline: none; /* Removes the border. */
|
||||
}
|
||||
|
||||
|
||||
/* equipment field specifics */
|
||||
/* inputs and dropdowns */
|
||||
.form-control {
|
||||
transition: none !important;
|
||||
box-shadow: none !important;
|
||||
width: 95% !important;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul.search-box {
|
||||
position: absolute;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
li.search-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li.search-item:hover {
|
||||
background-color: hsl(0, 0%, 11%) !important;
|
||||
}
|
||||
|
||||
/* boosts styles */
|
||||
.button-boost:hover {
|
||||
background-color: rgba(255, 255, 255, .1);
|
||||
}
|
||||
|
||||
button.toggleOn {
|
||||
background-color: #0a0 !important;
|
||||
}
|
||||
|
||||
|
||||
/* floating tooltip styles */
|
||||
.float-tooltip {
|
||||
background-color: hsl(0, 0%, 16%);
|
||||
position: absolute;
|
||||
transition: .3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* generic */
|
||||
|
||||
input {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input.equipment-input {
|
||||
font-weight: bold;
|
||||
background-color: hsl(0, 0%, 21%) !important;
|
||||
border-radius: 0.375rem !important;
|
||||
border-color: rgba(33, 37, 41, 1) !important;
|
||||
min-height: calc(1.2 * var(--scaled-fontsize) + 2px);
|
||||
padding: 0rem 0.5rem;
|
||||
font-size: var(--scaled-fontsize);
|
||||
}
|
||||
|
||||
.my-container {
|
||||
position: fixed; /* Stay in place */
|
||||
left: var(--sidebar-width);
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.spell-display p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.spell-display b {
|
||||
font-size: 3rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.spell-expand {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:root {
|
||||
--scaled-fontsize: 2.5rem;
|
||||
}
|
||||
.scaled-font {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.box-title {
|
||||
text-align: center;
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
text-align: center;
|
||||
overflow-wrap: break-word;
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
|
||||
.big-title {
|
||||
text-align: center;
|
||||
overflow-wrap: break-word;
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
|
||||
.skp-tooltip {
|
||||
font-size: 2.1875rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.spellcost-tooltip b {
|
||||
font-size: 2.1875rem !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #ff8180;
|
||||
font-size: 1.875rem;
|
||||
margin-bottom: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.scaled-item-icon {
|
||||
width: 8rem;
|
||||
}
|
||||
|
||||
.scaled-item-icon img {
|
||||
width: 6.5rem;
|
||||
}
|
||||
|
||||
.scaled-bckgrd {
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.scaled-bckgrd img {
|
||||
width: 6.5rem;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1200px) and (max-width: 1400px) {
|
||||
: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: .625rem;
|
||||
}
|
||||
|
||||
.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: 1400px) {
|
||||
:root {
|
||||
--scaled-fontsize: 1rem;
|
||||
}
|
||||
.scaled-font {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.box-title {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.big-title {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.skp-tooltip {
|
||||
font-size: .78rem;
|
||||
}
|
||||
|
||||
.spellcost-tooltip b {
|
||||
font-size: .78rem !important;
|
||||
}
|
||||
|
||||
.scaled-item-icon {
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.scaled-item-icon img {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
||||
.scaled-bckgrd {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
|
||||
.scaled-bckgrd img {
|
||||
width: 3.5rem;
|
||||
}
|
||||
|
||||
|
||||
.warning {
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.spell-display b {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
/* WynnAtlas Mini */
|
||||
.search-field {
|
||||
background-color: hsl(0, 0%, 14%) !important;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-color: hsl(0, 0%, 8%);
|
||||
}
|
||||
|
||||
/* Fake button for build stats */
|
||||
.fake-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fake-button:hover, .selected-btn{
|
||||
background-color: hsl(0, 0%, 14%) !important;
|
||||
}
|
||||
|
||||
|
||||
/* material design dark mode */
|
||||
.dark-1 {
|
||||
background-color: hsl(0, 0%, 5%) !important;
|
||||
}
|
||||
|
||||
.dark-2 {
|
||||
background-color: hsl(0, 0%, 7%) !important;
|
||||
}
|
||||
|
||||
.dark-3 {
|
||||
background-color: hsl(0, 0%, 8%) !important;
|
||||
}
|
||||
|
||||
.dark-4 {
|
||||
background-color: hsl(0, 0%, 9%) !important;
|
||||
}
|
||||
|
||||
.dark-5 {
|
||||
background-color: hsl(0, 0%, 11%) !important;
|
||||
}
|
||||
|
||||
.dark-6 {
|
||||
background-color: hsl(0, 0%, 12%) !important;
|
||||
}
|
||||
|
||||
.dark-7 {
|
||||
background-color: hsl(0, 0%, 14%) !important;
|
||||
}
|
||||
|
||||
.dark-8 {
|
||||
background-color: hsl(0, 0%, 15%) !important;
|
||||
}
|
||||
|
||||
.dark-9 {
|
||||
background-color: hsl(0, 0%, 16%) !important;
|
||||
}
|
||||
|
||||
.dark-10 {
|
||||
background-color: hsl(0, 0%, 20%) !important;
|
||||
}
|
||||
|
||||
.dark-1u {
|
||||
background-color: hsl(0, 0%, 5%);
|
||||
}
|
||||
|
||||
.dark-2u {
|
||||
background-color: hsl(0, 0%, 7%);
|
||||
}
|
||||
|
||||
.dark-3u {
|
||||
background-color: hsl(0, 0%, 8%);
|
||||
}
|
||||
|
||||
.dark-4u {
|
||||
background-color: hsl(0, 0%, 9%);
|
||||
}
|
||||
|
||||
.dark-5u {
|
||||
background-color: hsl(0, 0%, 11%);
|
||||
}
|
||||
|
||||
.dark-6u {
|
||||
background-color: hsl(0, 0%, 12%);
|
||||
}
|
||||
|
||||
.dark-7u {
|
||||
background-color: hsl(0, 0%, 14%);
|
||||
}
|
||||
|
||||
.dark-8u {
|
||||
background-color: hsl(0, 0%, 15%);
|
||||
}
|
||||
|
||||
.dark-9u {
|
||||
background-color: hsl(0, 0%, 16%);
|
||||
}
|
||||
|
||||
.border-dark-7 {
|
||||
border-color:hsl(0, 0%, 14%) !important;
|
||||
}
|
||||
|
||||
/* BOX SHADOW STYLES */
|
||||
.dark-shadow {
|
||||
box-shadow: 0rem 0rem 0.5rem 0.075rem black;
|
||||
}
|
||||
|
||||
.dark-shadow-sm {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem black;
|
||||
}
|
||||
|
||||
.atlas {
|
||||
color: #fbcd49;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #88FFFF;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #ff88ff;
|
||||
}
|
||||
|
||||
.border-semi-light {
|
||||
border-color: #aaa;
|
||||
}
|
||||
|
||||
/* supposed to work in firefox but doesn't */
|
||||
input[type=number]::-webkit-inner-spin-button,
|
||||
input[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
/* Make links bold when hovered over */
|
||||
|
||||
.clickable:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.button {
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
/* atree connector rotations */
|
||||
.rotate-90 {
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.rotate-180 {
|
||||
-webkit-transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
-o-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.rotate-270 {
|
||||
-webkit-transform: rotate(270deg);
|
||||
-moz-transform: rotate(270deg);
|
||||
-ms-transform: rotate(270deg);
|
||||
-o-transform: rotate(270deg);
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
.rotate-flip {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.hide-scroll {
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.hide-scroll::-webkit-scrollbar {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
|
||||
.atree-selected {
|
||||
outline: 5px solid rgba(95, 214, 223, 0.8);
|
||||
}
|
||||
|
||||
.atree-circle {
|
||||
border-radius:50%;
|
||||
-moz-border-radius:50%;
|
||||
-webkit-border-radius:50%;
|
||||
}
|
||||
|
||||
.hppeng{
|
||||
color: #20c2b6;
|
||||
}
|
||||
.ferricles{
|
||||
color: #5be553;
|
||||
}
|
|
@ -356,43 +356,6 @@ input {
|
|||
::placeholder{
|
||||
color: #aaa;
|
||||
}
|
||||
/* Tier colors tier colors */
|
||||
.none {
|
||||
color: #aaa;
|
||||
}
|
||||
.lore {
|
||||
color: #555;
|
||||
}
|
||||
.Normal{
|
||||
color: #fff;
|
||||
}
|
||||
.Unique{
|
||||
color:#ff5;
|
||||
}
|
||||
.Rare{
|
||||
color:#f5f;
|
||||
}
|
||||
.Legendary{
|
||||
color:#5ff;
|
||||
}
|
||||
.Fabled{
|
||||
color:#f55;
|
||||
}
|
||||
.Mythic{
|
||||
color:#a0a;
|
||||
}
|
||||
.Crafted {
|
||||
color: #0aa;
|
||||
}
|
||||
.Custom {
|
||||
color: #0aa;
|
||||
}
|
||||
.Set{
|
||||
color:#5f5;
|
||||
}
|
||||
.restrict, .warning {
|
||||
color: #ff8180;
|
||||
}
|
||||
|
||||
button.toggleOn{
|
||||
background-color:#0a0;
|
||||
|
@ -411,40 +374,11 @@ button.toggleOn:hover {
|
|||
|
||||
}
|
||||
|
||||
.Star {
|
||||
color:rgb(255, 198, 85);
|
||||
}
|
||||
.Star:after {
|
||||
content: "\272B";
|
||||
}
|
||||
.externalBuffs {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.T0 {
|
||||
color: #555;
|
||||
}
|
||||
.T1 {
|
||||
color: #ff5;
|
||||
}
|
||||
.T2 {
|
||||
color: #f5f;
|
||||
}
|
||||
.T3 {
|
||||
color: #5ff;
|
||||
}
|
||||
.T0-bracket {
|
||||
color: #555;
|
||||
}
|
||||
.T1-bracket {
|
||||
color: #fa0;
|
||||
}
|
||||
.T2-bracket {
|
||||
color: #a0a;
|
||||
}
|
||||
.T3-bracket {
|
||||
color: #0aa;
|
||||
}
|
||||
|
||||
.hide-container-block, .hide-container-grid, .set-info-div, .fade-in {
|
||||
animation-duration: 0.5s;
|
||||
animation-name: fadeInFromNone;
|
||||
|
@ -548,7 +482,3 @@ button.toggleOn:hover {
|
|||
height: 100vh;
|
||||
}
|
||||
|
||||
.atlas{
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
|
184
css/wynnstyles.css
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
Wynn-Related CSS
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/* Misc/Util Colors */
|
||||
.positive {
|
||||
color: #5f5;
|
||||
}
|
||||
|
||||
.negative {
|
||||
color: #f55;
|
||||
}
|
||||
|
||||
.Health {
|
||||
color: #AA0000
|
||||
}
|
||||
|
||||
.Health:before {
|
||||
content: "\2764" ' ';
|
||||
}
|
||||
|
||||
.Mana {
|
||||
color: #5ff;
|
||||
}
|
||||
|
||||
.Mana:after {
|
||||
content: "\273A"
|
||||
}
|
||||
|
||||
.lvl:before {
|
||||
content: 'Lv. '
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Tier Colors */
|
||||
|
||||
.Normal {
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
.Unique {
|
||||
color: #FFFF55 !important;
|
||||
}
|
||||
|
||||
.Rare {
|
||||
color: #FF55FF !important;
|
||||
}
|
||||
|
||||
.Legendary {
|
||||
color: #55FFFF !important;
|
||||
}
|
||||
|
||||
.Fabled {
|
||||
color: #FF5555 !important;
|
||||
}
|
||||
|
||||
.Mythic {
|
||||
color: #AA00AA !important;
|
||||
}
|
||||
|
||||
.Set {
|
||||
color: #5f5 !important;
|
||||
}
|
||||
.Crafted, .Custom {
|
||||
color: #0aa !important;
|
||||
}
|
||||
|
||||
/* Tier Shadows */
|
||||
.Normal-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #fff;
|
||||
}
|
||||
|
||||
.Unique-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #ff5;
|
||||
}
|
||||
.Rare-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #f5f;
|
||||
}
|
||||
.Legendary-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #5ff;
|
||||
}
|
||||
.Fabled-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #f55;
|
||||
}
|
||||
.Mythic-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #a0a;
|
||||
}
|
||||
.Crafted-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #0aa;
|
||||
}
|
||||
.Custom-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #0aa;
|
||||
}
|
||||
.Set-shadow {
|
||||
box-shadow: 0rem 0rem 0.25rem 0.05rem #5f5;
|
||||
}
|
||||
|
||||
/* CR Styles */
|
||||
|
||||
.Star {
|
||||
color:rgb(255, 198, 85);
|
||||
}
|
||||
.Star:after {
|
||||
content: "\272B";
|
||||
}
|
||||
.T0 {
|
||||
color: #555;
|
||||
}
|
||||
.T1 {
|
||||
color: #ff5;
|
||||
}
|
||||
.T2 {
|
||||
color: #f5f;
|
||||
}
|
||||
.T3 {
|
||||
color: #5ff;
|
||||
}
|
||||
.T0-bracket {
|
||||
color: #555;
|
||||
}
|
||||
.T1-bracket {
|
||||
color: #fa0;
|
||||
}
|
||||
.T2-bracket {
|
||||
color: #a0a;
|
||||
}
|
||||
.T3-bracket {
|
||||
color: #0aa;
|
||||
}
|
||||
|
||||
/* Damages */
|
||||
|
||||
.base_dps:before { /* Little Dagger icon */
|
||||
content: "\1F5E1";
|
||||
}
|
||||
|
||||
.Damage {
|
||||
color: rgb(255, 198, 85)
|
||||
}
|
||||
|
||||
.nDam, .Neutral {
|
||||
color: #FFAA00;
|
||||
}
|
||||
|
||||
.nDam:before, .Neutral:before {
|
||||
content: "\2724" ' ';
|
||||
}
|
||||
|
||||
.eDam, .Earth, .Earth_powder {
|
||||
color: #00AA00;
|
||||
}
|
||||
|
||||
.eDam:before, .Earth:before, .Earth_powder:before { content: "\2724" ' '; }
|
||||
|
||||
.tDam, .Thunder, .Thunder_powder {
|
||||
color: #FFFF55;
|
||||
}
|
||||
|
||||
.tDam:before, .Thunder:before, .Thunder_powder:before { content: "\2726" ' '; }
|
||||
|
||||
.wDam, .Water, .Water_powder {
|
||||
color: #55FFFF
|
||||
}
|
||||
|
||||
.wDam:before, .Water:before, .Water_powder:before { content: "\2749" ' '; }
|
||||
|
||||
.fDam, .Fire, .Fire_powder {
|
||||
color: #FF5555;
|
||||
}
|
||||
|
||||
.fDam:before, .Fire:before, .Fire_powder:before { content: "\2739" ' '; }
|
||||
|
||||
.aDam, .Air, .Air_powder {
|
||||
color: #FFFFFF
|
||||
}
|
||||
|
||||
.aDam:before, .Air:before, .Air_powder:before { content: "\274b" ' '; }
|
||||
|
||||
.restrict {
|
||||
color: #ff8180;
|
||||
}
|
1914
custom/index.html
Normal file
1893
customizer.html
BIN
dev/builder_colorcode.png
Executable file
After Width: | Height: | Size: 178 KiB |
12
dev/compute_graph.svg
Executable file
After Width: | Height: | Size: 63 KiB |
965
dev/index.html
Normal file
|
@ -0,0 +1,965 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
|
||||
<head>
|
||||
<title>WynnBuilder Dev</title>
|
||||
<link rel="icon" href="../media/icons/new/atlas64.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
|
||||
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../css/sq2bs.css">
|
||||
<link rel="stylesheet" href="../css/sidebar.css">
|
||||
<link rel="stylesheet" href="../css/wynnstyles.css">
|
||||
<link rel="stylesheet" href="../css/dev.css">
|
||||
</head>
|
||||
|
||||
<body id="body" class="all" style="overflow-y: scroll">
|
||||
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
|
||||
<a href="../builder/"><img src="../media/icons/new/builder.png" alt="WynnBuilder"
|
||||
title="WynnBuilder"><b>WynnBuilder</b></a>
|
||||
<a href="../crafter/"><img src="../media/icons/new/crafter.png" alt="WynnCrafter"
|
||||
title="WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href="../items/"><img src="../media/icons/new/searcher.png" alt="WynnAtlas"
|
||||
title="WynnAtlas"><b>WynnAtlas</b></a>
|
||||
<a href="../custom/"><img src="../media/icons/new/custom.png" alt="WynnCustom"
|
||||
title="WynnCustom"><b>WynnCustom</b></a>
|
||||
<a href="../map/"><img src="../media/icons/new/compass.png" alt="WynnGPS" title="WynnGPS"><b>WynnGPS</b></a>
|
||||
<a href="../wynnfo/"><img src="../media/icons/new/book.png" alt="Wynnfo"
|
||||
title="WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href="" onclick="toggleIcons()"><img src="../media/icons/new/reload.png" alt=""
|
||||
title="Swap items on page"><b>Swap Icon Style</b></a>
|
||||
<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 text-light px-5 scaled-font">
|
||||
<div class="row justify-content-center page-title">
|
||||
Wynnbuilder Developer Page
|
||||
</div>
|
||||
<div class="row">
|
||||
Welcome to the Wynnbuilder page for developers! Here we provide documentation and specifications for our
|
||||
website. Read through these sections to learn more about how WynnBuilder works!
|
||||
</div>
|
||||
<div class="row section" title="Decoding WynnBuilder links">
|
||||
<p>
|
||||
This section is about the encoding schemes Wynnbuilder uses for its various saveable items (builds,
|
||||
crafted items, and custom items).
|
||||
</p>
|
||||
<p>
|
||||
We use a Base 64 (B64) encode/decode system in most shareable links. It would be quite clunky to put a
|
||||
bunch of numbers (the data we save and read) into one link. To save some space, we compress the
|
||||
base 10 numerical alphabet into a custom B64 alphabet.
|
||||
</p>
|
||||
<div class="row section" title="WB Base 64 (B64)">
|
||||
<p>
|
||||
The Wynnbuilder B64 character table:
|
||||
</p>
|
||||
<pre class="full-width">
|
||||
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-
|
||||
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
|
||||
| | | | | | | | | | | | |
|
||||
0 5 10 15 20 25 30 35 40 45 50 55 60 </pre>
|
||||
<p>
|
||||
The B64 encoding of a number (in the 0 to 63 range) is equal to the character at the index
|
||||
within the above string.
|
||||
</p>
|
||||
<p>
|
||||
For example, if we have a set of items with id numbers in the range [<number>0</number>,
|
||||
<number>10000</number>], we need at most 3 B64 characters to encode any of these items
|
||||
in the link! The item of id <number>1337</number> corresponds to the B64 hash <code>0Kv</code>:
|
||||
<math>1337 = 0 * 4096 + 20 * 64 + 57</math>, <number>0</number> maps to <code>0</code>,
|
||||
<number>20</number> maps to <code>K</code>, and <number>57</number> maps to <code>v</code>.
|
||||
</p>
|
||||
<p>
|
||||
Decoding is a little different. We can either interpret the B64 string as a <b>signed</b> or <b>unsigned</b> number (signed: using 2s complement binary).
|
||||
</p>
|
||||
<p>
|
||||
Things that should be interpreted as <b>signed</b> are:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>Skill Points</li>
|
||||
<li>Any numerical identification value for custom items</li>
|
||||
</ul>
|
||||
<p>
|
||||
Things that should be interpreted as <b>unsigned</b> are:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>Item ID numbers</li>
|
||||
<li>Tome ID numbers</li>
|
||||
<li>Build Level</li>
|
||||
<li>Ingredient ID numbers</li>
|
||||
<li>Recipe ID numbers</li>
|
||||
<li>Powder numbers</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
Now that we understand the B64 system, we can move on to the way builds, crafted items, and custom items are stored in links.
|
||||
</p>
|
||||
<div class="row section" title="Builds">
|
||||
<p>
|
||||
First, what do we need in order to encode an entire build?
|
||||
</p>
|
||||
<p>
|
||||
Wynnbuilder mainly runs calculations for damages and defense. Therefore, we need:
|
||||
</p>
|
||||
<ul class="indent">
|
||||
<li>The build's items (equipment, tomes)</li>
|
||||
<li>The skill points distributed by the user (and user level)</li>
|
||||
<li>Item powderings</li>
|
||||
</ul>
|
||||
<p>
|
||||
Wynnbuilder assigns each item in the Wynncraft item pool to a unique ID number.
|
||||
<!-- For example, the bracelet <b class="atlas">Atlas</b>
|
||||
has an id number of <number>167</number>. We can then store all of a build's item pool items in a link with the items' id numbers. A
|
||||
similar idea is used for skill points and powders. However, we know how many different skills there are already (5), so we can encode
|
||||
the user's assignment of skill points in 5 numbers. With powders, it's a little different. There are 31 "states" of powder: 1 for no
|
||||
powder and then 5 elements with 6 tiers of powder for each element. We will know how many available powder slots we have based on our
|
||||
equipment. We can then put all of these numbers in a specific order (after running B64 encoding) to get our build link. -->
|
||||
</p>
|
||||
<div class = "row section" title = "ID number specifics">
|
||||
<p>
|
||||
For items, you can download the item DB here: <a href = "../clean.json" target = "_blank">clean.json</a>. Each item has an id value that can be put in a map. The NoneItem ID numbers start at 10000 in the canonical order: [helmet, chestplate, leggings, boots, ring 1, ring 2, bracelet, necklace, weapon] (No Weapon has an id of 10008).
|
||||
</p>
|
||||
<p>
|
||||
For tomes, you can download the tome DB here: <a href = "../tome_map.json" target = "_blank">tome_map.json</a>. The NoneTome ID numbers start at 61 in the order [no weapon tome, no armor tome, no guild tome] so that we can store tome IDs in 1 B64 character.
|
||||
</p>
|
||||
<p>
|
||||
For powders: id numbers <number>1</number> through <number>30</number> map to Earth I, Earth II, ..., Earth VI,
|
||||
etc. in the order Earth, Thunder, Water, Fire, Air. 0 is the id number for no powder.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<p>
|
||||
All build links will end in "#[version number]_[build hash]".
|
||||
</p>
|
||||
<div class="row section" title="Version 6">
|
||||
<p>
|
||||
Version 6 was made to account for the desire to save tomes in a build. As of the last version of this documentation, version 6 is used for encoding whenever there are tomes in the build.
|
||||
</p>
|
||||
<div class = "row section" title = "Example 1: With Tomes">
|
||||
<code class="full-width">
|
||||
https://hppeng-wynn.github.io/builder/#6_06W2SH0D40Qq2SK2SL02d0og0Qi191V-E0i2C1g0000100nZ6ZU6FCDo
|
||||
</code>
|
||||
<p>
|
||||
Build hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>
|
||||
<number>9</number> items from <code>idMap</code> (<number>3</number> B64 characters each):
|
||||
<code>06W</code>,
|
||||
<code>2SH</code>,
|
||||
<code>0D4</code>,
|
||||
<code>0Qq</code>,
|
||||
<code>2SK</code>,
|
||||
<code>2SL</code>,
|
||||
<code>02d</code>,
|
||||
<code>0og</code>,
|
||||
<code>0Qi</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>5</number> skill point totals (<number>2</number> B64 characters each):
|
||||
<code>19</code>,
|
||||
<code>1V</code>,
|
||||
<code>-E</code>,
|
||||
<code>0i</code>,
|
||||
<code>2C</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>1</number> player level (<number>2</number> B64 characters):
|
||||
<code>1g</code>
|
||||
</li>
|
||||
<li>
|
||||
A <b>variable</b> number of powder "blocks" (<number>5</number> B64 characters which give us <number>6</number> powders per block).
|
||||
<ul class = "indent">
|
||||
<li>For each of the 5 powderable equipment fields [helmet, chestplate, leggings, boots, weapon], we will have the following:</li>
|
||||
<li><number>1</number> B64 character that says that we need <number>n</number> blocks for this item.</li>
|
||||
<li><number>n</number> blocks of <number>5</number> B64 characters.</li>
|
||||
<li>
|
||||
Since there are 4 <code>0</code>s (B64 0 = 0 unsigned) in this example, we have no powders on any of the armor piece (no blocks).
|
||||
</li>
|
||||
<li>
|
||||
Then, we have <code>1</code> (B64 1 = 1 unsigned). There is 1 block of powders to decode for the weapon item. That is <code>00nZ6</code>.
|
||||
</li>
|
||||
<ul class = "indent">
|
||||
<li>The unsigned equivalent of <code>00nZ6</code> in binary is 30 <b>binary</b> bits long (omitted). Each section of 5 bits directly corresponds to an powder ID.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<number>7</number> tomes (<number>1</number> character each):
|
||||
<code>ZU6FCDo</code>
|
||||
<ul class = "indent">
|
||||
<li>The order of tomes listed is [2x weapon tome, 4x armor tome, 1x guild tome].</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row section" title="Version 5">
|
||||
<p>
|
||||
Version 5 was made to allow for the ability to save custom items. To learn the specifics about custom item encoding, refer to the Custom Items section.
|
||||
</p>
|
||||
<p>
|
||||
As of the last version of this documentation, version 5 is only used for encoding when there are custom items (and no tomes) in the build.
|
||||
</p>
|
||||
<div class = "row section" title = "Example 1: With Custom Item">
|
||||
<code class = "full-width">
|
||||
https://hppeng-wynn.github.io/builder/#5_06W00mCI-10000JCustom%20Chestplate0220510G020Fe0M0201a0D40Qq2SK2SL02d0og0Qi191V-E0i2C1g0000100nZ6zz++++-
|
||||
</code>
|
||||
<p>
|
||||
Build Hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>
|
||||
<number>9</number> items from <code>idMap</code> (<number>3</number> B64 characters each):
|
||||
<code>06W</code>,
|
||||
<code>00m</code> (with the custom item <code>CI-10000JCustom%20Chestplate0220510G020Fe0M0201a</code>),
|
||||
<code>0D4</code>,
|
||||
<code>0Qq</code>,
|
||||
<code>2SK</code>,
|
||||
<code>2SL</code>,
|
||||
<code>02d</code>,
|
||||
<code>0og</code>,
|
||||
<code>0Qi</code>
|
||||
<ul class = "indent">
|
||||
<li>Starting in this version, to encode a custom item we substitute in the length of the full hash of a custom item ("CI-[gibberish]") in <number>3</number> B64 characters for the item ID, followed by the full hash.</li>
|
||||
<li>When decoding build links of this version or higher, you must check whether or not the 3 characters after the current item are "CI-". If they are, the current 3 characters are the B64 representation of the unsigned length of the custom item hash (<math>n</math>), in characters. Then the next <math>n</math> characters make up the full custom item hash.</li>
|
||||
<li>No existing item has an item ID of "CI-" in B64, so we can define a special case check for this "id number".</li>
|
||||
<li>Further details on parsing and loading this custom item are in the Custom Item section.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<number>5</number> skill point totals (<number>2</number> B64 characters each):
|
||||
<code>19</code>,
|
||||
<code>1V</code>,
|
||||
<code>-E</code>,
|
||||
<code>0i</code>,
|
||||
<code>2C</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>1</number> player level (<number>2</number> B64 characters):
|
||||
<code>1g</code>
|
||||
</li>
|
||||
<li>
|
||||
A <b>variable</b> number of powder "blocks" (<number>5</number> B64 characters which give us <number>6</number> powders per block).
|
||||
<ul class = "indent">
|
||||
<li>For each of the 5 powderable equipment fields [helmet, chestplate, leggings, boots, weapon], we will have the following:</li>
|
||||
<li><number>1</number> B64 character that says that we need <number>n</number> blocks for this item.</li>
|
||||
<li><number>n</number> blocks of <number>5</number> B64 characters.</li>
|
||||
<li>
|
||||
Since there are 4 <code>0</code>s (B64 0 = 0 unsigned) in this example, we have no powders on any of the armor piece (no blocks).
|
||||
</li>
|
||||
<li>
|
||||
Then, we have <code>1</code> (B64 1 = 1 unsigned). There is 1 block of powders to decode for the weapon item. That is <code>00nZ6</code>.
|
||||
</li>
|
||||
<ul class = "indent">
|
||||
<li>The unsigned equivalent of <code>00nZ6</code> in binary is 30 <b>binary</b> bits long (omitted). Each section of 5 bits directly corresponds to an powder ID.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
It is possible that version 5 links will have an extra tome section at the end like above (see: Version 6 section). We ignore this in decoding.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row section" title="Version 4">
|
||||
<p>
|
||||
Version 4 was made to allow for the ability to save crafted items. To learn the specifics about crafted item encoding, refer to the Crafted Items section.
|
||||
</p>
|
||||
<p>
|
||||
As of the last version of this documentation, version 4 is the default version and is used when there are no custom items or tomes in the build.
|
||||
</p>
|
||||
<div class = "row section" title = "Example 1: No Crafted Items">
|
||||
<code class = "full-width">
|
||||
https://hppeng-wynn.github.io/builder/#4_06W2SH0D40Qq2SK2SL02d0og0Qi191V-E0i2C1g0000100nZ6zz++++-
|
||||
</code>
|
||||
<p>
|
||||
Build Hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>
|
||||
<number>9</number> items from <code>idMap</code> (<number>3</number> N64 characters each):
|
||||
<code>06W</code>,
|
||||
<code>2SH</code>,
|
||||
<code>0D4</code>,
|
||||
<code>0Qq</code>,
|
||||
<code>2SK</code>,
|
||||
<code>2SL</code>,
|
||||
<code>02d</code>,
|
||||
<code>0og</code>,
|
||||
<code>0Qi</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>5</number> skill point totals (<number>2</number> B64 characters each):
|
||||
<code>19</code>,
|
||||
<code>1V</code>,
|
||||
<code>-E</code>,
|
||||
<code>0i</code>,
|
||||
<code>2C</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>1</number> player level (<number>2</number> B64 characters):
|
||||
<code>1g</code>
|
||||
</li>
|
||||
<li>
|
||||
A <b>variable</b> number of powder "blocks" (<number>5</number> B64 characters which give us <number>6</number> powders per block).
|
||||
<ul class = "indent">
|
||||
<li>For each of the 5 powderable equipment fields [helmet, chestplate, leggings, boots, weapon], we will have the following:</li>
|
||||
<li><number>1</number> B64 character that says that we need <number>n</number> blocks for this item.</li>
|
||||
<li><number>n</number> blocks of <number>5</number> B64 characters.</li>
|
||||
<li>
|
||||
Since there are 4 <code>0</code>s (B64 0 = 0 unsigned) in this example, we have no powders on any of the armor piece (no blocks).
|
||||
</li>
|
||||
<li>
|
||||
Then, we have <code>1</code> (B64 1 = 1 unsigned). There is 1 block of powders to decode for the weapon item. That is <code>00nZ6</code>.
|
||||
</li>
|
||||
<ul class = "indent">
|
||||
<li>The unsigned equivalent of <code>00nZ6</code> in binary is 30 <b>binary</b> bits long (omitted). Each section of 5 bits directly corresponds to an powder ID.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class = "row section" title = "Example 2: With Crafted Items">
|
||||
<code class = "full-width">
|
||||
https://hppeng-wynn.github.io/builder/#4_06WCR-1628i8v8v94948f210D40Qq2SK2SL02d0og0Qi1Q1V-E0l2C1g0000100nZ6zz++++-
|
||||
</code>
|
||||
<p>
|
||||
Build Hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>
|
||||
<number>9</number> items from <code>idMap</code> (<number>3</number> B64 characters each):
|
||||
<code>06W</code>,
|
||||
<code>CR-1628i8v8v94948f21</code>,
|
||||
<code>0D4</code>,
|
||||
<code>0Qq</code>,
|
||||
<code>2SK</code>,
|
||||
<code>2SL</code>,
|
||||
<code>02d</code>,
|
||||
<code>0og</code>,
|
||||
<code>0Qi</code>
|
||||
<ul class = "indent">
|
||||
<li>Starting in this version, you can substitute in the full hash of a crafted item ("CR-[gibberish]") for the 3-character hash of an item pool item.</li>
|
||||
<li>The way we can tell that an item is a crafted item is when the 3-character hash of the 'item' is "CR-". No existing item has an item ID of "CR-" in B64, so we can define a special case check for this "id number".</li>
|
||||
<li>Further details on parsing and loading this custom item are in the Crafted Item section.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<number>5</number> skill point totals (<number>2</number> B64 characters each):
|
||||
<code>19</code>,
|
||||
<code>1V</code>,
|
||||
<code>-E</code>,
|
||||
<code>0i</code>,
|
||||
<code>2C</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>1</number> player level (<number>2</number> B64 characters):
|
||||
<code>1g</code>
|
||||
</li>
|
||||
<li>
|
||||
A <b>variable</b> number of powder "blocks" (<number>5</number> B64 characters which give us <number>6</number> powders per block).
|
||||
<ul class = "indent">
|
||||
<li>For each of the 5 powderable equipment fields [helmet, chestplate, leggings, boots, weapon], we will have the following:</li>
|
||||
<li><number>1</number> B64 character that says that we need <number>n</number> blocks for this item.</li>
|
||||
<li><number>n</number> blocks of <number>5</number> B64 characters.</li>
|
||||
<li>
|
||||
Since there are 4 <code>0</code>s (B64 0 = 0 unsigned) in this example, we have no powders on any of the armor piece (no blocks).
|
||||
</li>
|
||||
<li>
|
||||
Then, we have <code>1</code> (B64 1 = 1 unsigned). There is 1 block of powders to decode for the weapon item. That is <code>00nZ6</code>.
|
||||
</li>
|
||||
<ul class = "indent">
|
||||
<li>The unsigned equivalent of <code>00nZ6</code> in binary is 30 <b>binary</b> bits long (omitted). Each section of 5 bits directly corresponds to an powder ID.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
It is possible that version 4 links will have an extra tome string like above (see: Version 6 section) after the powders. You can ignore this in decoding.
|
||||
</p>
|
||||
</div>
|
||||
<div class="row section" title="Version 3">
|
||||
<p>
|
||||
Version 3 encoding added the ability to save build level.
|
||||
</p>
|
||||
<div class = "row section" title = "Example">
|
||||
<code class="full-width">
|
||||
https://hppeng-wynn.github.io/builder/#3_06W2SH0D40Qq2SK2SL02d0og0Qi191V-E0i2C1g0000100nZ6
|
||||
</code>
|
||||
<p>
|
||||
Build hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>
|
||||
<number>9</number> items from <code>idMap</code> (<number>3</number> B64 characters each):
|
||||
<code>06W</code>,
|
||||
<code>2SH</code>,
|
||||
<code>0D4</code>,
|
||||
<code>0Qq</code>,
|
||||
<code>2SK</code>,
|
||||
<code>2SL</code>,
|
||||
<code>02d</code>,
|
||||
<code>0og</code>,
|
||||
<code>0Qi</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>5</number> skill point totals (<number>2</number> B64 characters each):
|
||||
<code>19</code>,
|
||||
<code>1V</code>,
|
||||
<code>-E</code>,
|
||||
<code>0i</code>,
|
||||
<code>2C</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>1</number> player level (<number>2</number> B64 characters):
|
||||
<code>1g</code>
|
||||
</li>
|
||||
<li>
|
||||
A <b>variable</b> number of powder "blocks" (<number>5</number> B64 characters which give us <number>6</number> powders per block).
|
||||
<ul class = "indent">
|
||||
<li>For each of the 5 powderable equipment fields [helmet, chestplate, leggings, boots, weapon], we will have the following:</li>
|
||||
<li><number>1</number> B64 character that says that we need <number>n</number> blocks for this item.</li>
|
||||
<li><number>n</number> blocks of <number>5</number> B64 characters.</li>
|
||||
<li>
|
||||
Since there are 4 <code>0</code>s (B64 0 = 0 unsigned) in this example, we have no powders on any of the armor piece (no blocks).
|
||||
</li>
|
||||
<li>
|
||||
Then, we have <code>1</code> (B64 1 = 1 unsigned). There is 1 block of powders to decode for the weapon item. That is <code>00nZ6</code>.
|
||||
</li>
|
||||
<ul class = "indent">
|
||||
<li>The unsigned equivalent of <code>00nZ6</code> in binary is 30 <b>binary</b> bits long (omitted). Each section of 5 bits directly corresponds to an powder ID.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row section" title="Version 2">
|
||||
<p>
|
||||
Version 2 encoding added the ability to save skill point info.
|
||||
</p>
|
||||
<div class = "row section" title = "Example">
|
||||
<code class="full-width">
|
||||
https://hppeng-wynn.github.io/builder/#2_06W2SH0D40Qq2SK2SL02d0og0Qi191V-E0i2C0000100nZ6
|
||||
</code>
|
||||
<p>
|
||||
Build hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>
|
||||
<number>9</number> items from <code>idMap</code> (<number>3</number> B64 characters each):
|
||||
<code>06W</code>,
|
||||
<code>2SH</code>,
|
||||
<code>0D4</code>,
|
||||
<code>0Qq</code>,
|
||||
<code>2SK</code>,
|
||||
<code>2SL</code>,
|
||||
<code>02d</code>,
|
||||
<code>0og</code>,
|
||||
<code>0Qi</code>
|
||||
</li>
|
||||
<li>
|
||||
<number>5</number> skill point totals (<number>2</number> B64 characters each):
|
||||
<code>19</code>,
|
||||
<code>1V</code>,
|
||||
<code>-E</code>,
|
||||
<code>0i</code>,
|
||||
<code>2C</code>
|
||||
</li>
|
||||
<li>
|
||||
A <b>variable</b> number of powder "blocks" (<number>5</number> B64 characters which give us <number>6</number> powders per block).
|
||||
<ul class = "indent">
|
||||
<li>For each of the 5 powderable equipment fields [helmet, chestplate, leggings, boots, weapon], we will have the following:</li>
|
||||
<li><number>1</number> B64 character that says that we need <number>n</number> blocks for this item.</li>
|
||||
<li><number>n</number> blocks of <number>5</number> B64 characters.</li>
|
||||
<li>
|
||||
Since there are 4 <code>0</code>s (B64 0 = 0 unsigned) in this example, we have no powders on any of the armor piece (no blocks).
|
||||
</li>
|
||||
<li>
|
||||
Then, we have <code>1</code> (B64 1 = 1 unsigned). There is 1 block of powders to decode for the weapon item. That is <code>00nZ6</code>.
|
||||
</li>
|
||||
<ul class = "indent">
|
||||
<li>The unsigned equivalent of <code>00nZ6</code> in binary is 30 <b>binary</b> bits long (omitted). Each section of 5 bits directly corresponds to an powder ID.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row section" title="Version 1">
|
||||
<p>
|
||||
Version 1 is the very first encoding version by Wynnbuilder. It allows for saving all equipment (armors, accessories, weapon) and powders put on that equipment.
|
||||
</p>
|
||||
<div class = "row section" title = "Example">
|
||||
<code class="full-width">
|
||||
https://hppeng-wynn.github.io/builder/#1_06W2SH0D40Qq2SK2SL02d0og0Qi0000100nZ6
|
||||
</code>
|
||||
<p>
|
||||
Build hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>
|
||||
<number>9</number> items from <code>idMap</code> (<number>3</number> B64 characters each):
|
||||
<code>06W</code>,
|
||||
<code>2SH</code>,
|
||||
<code>0D4</code>,
|
||||
<code>0Qq</code>,
|
||||
<code>2SK</code>,
|
||||
<code>2SL</code>,
|
||||
<code>02d</code>,
|
||||
<code>0og</code>,
|
||||
<code>0Qi</code>
|
||||
</li>
|
||||
<li>
|
||||
A <b>variable</b> number of powder "blocks" (<number>5</number> B64 characters which give us <number>6</number> powders per block).
|
||||
<ul class = "indent">
|
||||
<li>For each of the 5 powderable equipment fields [helmet, chestplate, leggings, boots, weapon], we will have the following:</li>
|
||||
<li><number>1</number> B64 character that says that we need <number>n</number> blocks for this item.</li>
|
||||
<li><number>n</number> blocks of <number>5</number> B64 characters.</li>
|
||||
<li>
|
||||
Since there are 4 <code>0</code>s (B64 0 = 0 unsigned) in this example, we have no powders on any of the armor piece (no blocks).
|
||||
</li>
|
||||
<li>
|
||||
Then, we have <code>1</code> (B64 1 = 1 unsigned). There is 1 block of powders to decode for the weapon item. That is <code>00nZ6</code>.
|
||||
</li>
|
||||
<ul class = "indent">
|
||||
<li>The unsigned equivalent of <code>00nZ6</code> in binary is 30 <b>binary</b> bits long (omitted). Each section of 5 bits directly corresponds to an powder ID.</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row section" title="Crafted Items">
|
||||
<p>
|
||||
This section is about how to decode crafted items. To view an example of a crafted item in a build, check out <b>Builds > Version 4</b>.
|
||||
</p>
|
||||
<p>
|
||||
Crafted items always start with "CR-" so that they are, as an entire category, distinguishable from item pool items. The ingredients and materials that make up the crafted item are stored in the rest of the "hash".
|
||||
</p>
|
||||
<p>
|
||||
To encode all the info about a crafted item, we need:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>Ingredient Data</li>
|
||||
<li>Recipe Data</li>
|
||||
<li>Crafting Material Tiers</li>
|
||||
<li>Attack Speed (for weapons)</li>
|
||||
</ul>
|
||||
<p>
|
||||
Wynnbuilder assigns each ingredient and recipe to a unique ID number.
|
||||
</p>
|
||||
<div class = "row section" title = "ID number specifics">
|
||||
<p>
|
||||
For ingredients, you can download the ingredient id map here: <a href = "../ing_map.json" target = "_blank">ing_map.json</a>. The ID number for No Ingredient is 4000.
|
||||
</p>
|
||||
<p>
|
||||
For recipes, you can download the recipe id map here: <a href = "../recipe_map.json" target = "_blank">recipe_map.json</a> or the recipe DB here: <a href = "recipes_clean.json" target = "_blank">recipes_clean.json</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class = "row section" title = "Version 1">
|
||||
<p>
|
||||
This is the first version of crafted item encoding. Crafted Items are always stored in a constant number of B64 characters.
|
||||
</p>
|
||||
<div class = "row section" title = "Example - Crafted Item">
|
||||
<p>
|
||||
This example shows how to parse a crafted item hash.
|
||||
</p>
|
||||
<code class = "full-width">
|
||||
CR-1628i8v8v94948f21
|
||||
</code>
|
||||
<p>
|
||||
Crafted item hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li><number>3</number> characters to denote item type as crafted: <code>CR-</code> (always)</li>
|
||||
<li><number>1</number> character for encoding version: <code>1</code> </li>
|
||||
<li><number>6</number> ingredient IDs (<number>2</number> B64 characters each):
|
||||
<code>62</code>,
|
||||
<code>8i</code>,
|
||||
<code>8v</code>,
|
||||
<code>8v</code>,
|
||||
<code>94</code>,
|
||||
<code>94</code>
|
||||
</li>
|
||||
<li><number>2</number> B64 characters for recipe ID: <code>8f</code></li>
|
||||
<li><number>1</number> character to encode material tiers: <code>2</code>
|
||||
<ul class = "indent">
|
||||
<li>There are 2 material tiers to decode. The ordering of materials is determined by their order within the corresponding recipe object held in the db.</li>
|
||||
<li>The material tier character (from here on <math>t</math>) is in the range [<number>1</number>, <number>9</number>]. </li>
|
||||
<li>Mat 1's tier is equal to <math>t % 3</math> except when this yields 0, in which case it becomes 3.</li>
|
||||
<li>Mat 2's tier is equal to ceil(<math> (t - 0.5) / 3</math>).</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><number>1</number> character to encode attack speed: <code>1</code>
|
||||
<ul class = "indent">
|
||||
<li>The integer after doing unsigned decoding from the B64 character denotes the index within the following array: [SLOW, NORMAL, FAST]. B64 <code>1</code> maps to the unsigned integer <number>1</number>, meaning that the attack speed of this crafted item would be NORMAL if it were a weapon.</li>
|
||||
<li>Note: although only weapons will have attack speed, we decided to include the character in all crafted item hashes to keep a constant hash length.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
You may need to parse a crafted item from a wynnbuilder crafter link.
|
||||
</p>
|
||||
<code class = "full-width">
|
||||
https://hppeng-wynn.github.io/crafter/#1628i8v8v94948f21
|
||||
</code>
|
||||
<p>
|
||||
We can simply take the string after the octothorpe/hash tag (#), tack on "CR-" in front of this string, and arrive at the full hash for the crafted item in question. Decode using the same logic as the previous example.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row section" title="Custom Items">
|
||||
<p>
|
||||
This section is about how to decode custom items. To view an example of a custom item in a build, check out <b>Builds > Version 5</b>.
|
||||
</p>
|
||||
<p>
|
||||
Custom items always start with "CI-" so that they are, as an entire category, distinguishable from item pool items. The stats and values that make up the custom item are stored in the rest of the "hash".
|
||||
</p>
|
||||
<div class = "row section" title = "Version 1">
|
||||
<p>This is the first version of custom item encoding and decoding.</p>
|
||||
<p>You will need the full array of item identification saving order and all non-rolled identifications (ex: name). View them below.</p>
|
||||
<div class = "row section" title = "Important Arrays">
|
||||
<p> ID saving order: <code>ci_save_order = ["name", "lore", "tier", "set", "slots", "type", "material", "drop", "quest", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq","str", "dex", "int", "agi", "def", "id", "skillpoints", "reqs", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "majorIds", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd","durability","duration","charges"];</code> </p>
|
||||
<p> Non-rolled string IDs: <code>nonRolled_strings = ["name", "lore", "tier", "set", "type", "material", "drop", "quest", "majorIds", "classReq", "atkSpd", "displayName", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "durability", "duration"];</code></p>
|
||||
<p> Rolled IDs: <code>rolledIDs = ["hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd"];</code></p>
|
||||
<p> Non-rolled IDs: <code>nonRolledIDs = ["name", "lore", "displayName", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "str", "dex", "int", "agi", "def", "fixID", "category", "id", "skillpoints", "reqs", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "majorIds"];</code></p>
|
||||
<p> Tiers: <code>tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set", "Crafted"]</code></p>
|
||||
<p> Types: <code>types = [ "helmet", "chestplate", "leggings", "boots", "ring", "bracelet", "necklace", "wand", "spear", "bow", "dagger", "relik", "potion", "scroll", "food"];</code></p>
|
||||
<p> Attack Speeds: <code>attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY_FAST", "SUPER_FAST"];</code></p>
|
||||
<p> Class Requirements: <code>classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"]</code> </p>
|
||||
</div>
|
||||
|
||||
<div class = "row section" title = "Example">
|
||||
<p>
|
||||
Here's an example of a custom item hash.
|
||||
</p>
|
||||
<code class = "full-width">
|
||||
CI-10000HMeta%20Chestplate010Gbest%20in%20slot0240401030510G0302SG0H020Fe0I020Fe0J020Fe0K020Fe0L020Fe0M0201Y0i0200U220z0204iKK150200U22160200U22170200U22180200U22190200U22
|
||||
</code>
|
||||
<p>
|
||||
Given a custom item hash, we will in general continue to parse through many identifications and their values until we reach the end of the custom item hash.
|
||||
</p>
|
||||
<p>
|
||||
Custom item hash format:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li><number>1</number> B64 character denoting encoding/decoding version number: <code>1</code></li>
|
||||
<li><number>1</number> character denoting whether or not this item has fixed IDs: <code>0</code>. (0 for no fixed IDs, 1 for fixed IDs)</li>
|
||||
<li>A series of encoded identifications, each taking a variable number of characters. For every ID, we have to save:
|
||||
<ul class = "indent">
|
||||
<li><number>2</number> B64 characters that represent the identification ID (its index in the CI save order array). </li>
|
||||
<li><number>2</number> B64 characters that represent the length <math>len</math> of the value of the identification.</li>
|
||||
<li>A variable number characters to encode the value of the identification.
|
||||
<li>For string-valued identifications (in the non-rolled strings array), we do not use any encoding for the value. The next <number>len</number> characters of the custom item hash is the raw value of the identification (before substituting space for "%20").
|
||||
<ul class = "indent">
|
||||
<li>Exception: for the identifications <code>tier</code>, <code>type</code>, <code>atkSpd</code>, and <code>classReq</code>, there is no string used. They are encoded as a numerical value representing an index in a pre-defined array (check the Important Arrays section above). They also do not use the earlier-specified 2 characters to store length; instead, they each use only 1 B64 character to store their index in their corresponding arrays.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>For numerical-valued identifications, encoding depends on the fixed ID value from before.
|
||||
<ul class = "indent">
|
||||
<li>Rolled IDs (with non-fixed IDs):
|
||||
<ul class = "indent">
|
||||
<li><number>1</number> character to denote the sign of the min and max values: 0 for both positive, 1 for negative min and positive max, 2 for positive min and negative max, and 3 for both negative.</li>
|
||||
<li><number>len</number> B64 characters that represent the unsigned <b>minimum</b> value of the identification.</li>
|
||||
<li><number>len</number> B64 characters that represent the unsigned <b>maximum</b> value of the identification.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Rolled IDs (with fixed IDs) and Non-Rolled IDs:
|
||||
<ul class = "indent">
|
||||
<li><number>1</number> character (binary bit) to denote the sign of the value (0 positive, 1 negative).</li>
|
||||
<li><number>len</number> B64 characters that represent the unsigned value of the identification.</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>To finish the example, we'll go through all the actual identifications of the provided custom item.
|
||||
<ul class = "indent">
|
||||
<li>"CI-" constant portion</li>
|
||||
<li>Encoding version number: <code>1</code></li>
|
||||
<li>Fixed IDs: <code>0</code> (non-fixed IDs)</li>
|
||||
<li><code>000HMeta%20Chestplate</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>00</code> ("name")</li>
|
||||
<li>ID value length: <code>0H</code> (<number>17</number>)</li>
|
||||
<li>ID value: <code>Meta%20Chestplate</code> ("Meta Chestplate")</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>010Gbest%20in%20slot</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>01</code> ("lore")</li>
|
||||
<li>ID value length: <code>0G</code> (<number>16</number>)</li>
|
||||
<li>ID value: <code>best%20in%20slot</code> ("best in slot")</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>024</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>02</code> ("tier")</li>
|
||||
<li>ID value length: None (exception) </li>
|
||||
<li>ID value: <code>4</code> (tiers[4] = "Fabled")</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>040103</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>04</code> ("slots")</li>
|
||||
<li>ID value length: <code>01</code> (<number>1</number>)</li>
|
||||
<li>ID sign: <code>0</code> (positive)</li>
|
||||
<li>ID value: <code>3</code> (<number>3</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>051</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>05</code> ("type")</li>
|
||||
<li>ID value length: None (exception)</li>
|
||||
<li>ID value: <code>1</code> (types[1] = "chestplate")</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0G0302SG</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0G</code> ("hp")</li>
|
||||
<li>ID value length: <code>03</code> (<number>3</number>) </li>
|
||||
<li>ID sign: <code>0</code> (positive)</li>
|
||||
<li>ID value: <code>2SG</code> (<number>10000</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0H020Fe</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0H</code> ("fDef")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (positive)</li>
|
||||
<li>ID value: <code>Fe</code> (<number>1000</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0I020Fe</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0I</code> ("wDef")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (positive)</li>
|
||||
<li>ID value: <code>Fe</code> (<number>1000</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0J020Fe</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0J</code> ("aDef")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (positive)</li>
|
||||
<li>ID value: <code>Fe</code> (<number>1000</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0K020Fe</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0K</code> ("tDef")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (positive)</li>
|
||||
<li>ID value: <code>Fe</code> (<number>1000</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0L020Fe</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0L</code> ("eDef")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (positive)</li>
|
||||
<li>ID value: <code>Fe</code> (<number>1000</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0M0201Y</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0M</code> ("lvl")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (positive)</li>
|
||||
<li>ID value: <code>1Y</code> (<number>98</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0i0200U22</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0i</code> ("hprPct")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (both positive)</li>
|
||||
<li>ID value minimum: <code>0U</code> (<number>30</number>)</li>
|
||||
<li>ID value maximum: <code>22</code> (<number>130</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0z0204iKK</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0z</code> ("hprRaw")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (both positive)</li>
|
||||
<li>ID value minimum: <code>4i</code> (<number>300</number>)</li>
|
||||
<li>ID value maximum: <code>KK</code> (<number>1300</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>0z0204iKK</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>0z</code> ("hprRaw")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (both positive)</li>
|
||||
<li>ID value minimum: <code>4i</code> (<number>300</number>)</li>
|
||||
<li>ID value maximum: <code>KK</code> (<number>1300</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>150200U22</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>15</code> ("fDefPct")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (both positive)</li>
|
||||
<li>ID value minimum: <code>0U</code> (<number>30</number>)</li>
|
||||
<li>ID value maximum: <code>22</code> (<number>130</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>160200U22</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>16</code> ("wDefPct")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (both positive)</li>
|
||||
<li>ID value minimum: <code>0U</code> (<number>30</number>)</li>
|
||||
<li>ID value maximum: <code>22</code> (<number>130</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>170200U22</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>17</code> ("aDefPct")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (both positive)</li>
|
||||
<li>ID value minimum: <code>0U</code> (<number>30</number>)</li>
|
||||
<li>ID value maximum: <code>22</code> (<number>130</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>180200U22</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>18</code> ("tDefPct")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (both positive)</li>
|
||||
<li>ID value minimum: <code>0U</code> (<number>30</number>)</li>
|
||||
<li>ID value maximum: <code>22</code> (<number>130</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><code>190200U22</code>
|
||||
<ul class = "indent">
|
||||
<li>ID name: <code>19</code> ("eDefPct")</li>
|
||||
<li>ID value length: <code>02</code> (<number>2</number>) </li>
|
||||
<li>ID sign: <code>0</code> (both positive)</li>
|
||||
<li>ID value minimum: <code>0U</code> (<number>30</number>)</li>
|
||||
<li>ID value maximum: <code>22</code> (<number>130</number>)</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- TODO -->
|
||||
|
||||
<p>
|
||||
You may need to parse a custom item from a Wynnbuilder customizer link.
|
||||
</p>
|
||||
<code class = "full-width">
|
||||
hppeng-wynn.github.io/custom/#10000HMeta%20Chestplate010Gbest%20in%20slot0240401030510G0302SG0H020Fe0I020Fe0J020Fe0K020Fe0L020Fe0M0201Y0i0200U220z0204iKK150200U22160200U22170200U22180200U22190200U22
|
||||
</code>
|
||||
<p>
|
||||
Similar to crafted items, the part of the link after the "#" is the rest of the custom item after the "CI-" constant portion. You may need to convert all "%20" to spaces manually.
|
||||
</p>
|
||||
<p>
|
||||
Details on reading custom items in build links are provided in the Decoding WB links > Builds section.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Last updated: 30 May 2022
|
||||
</p>
|
||||
</div>
|
||||
<div class="row section" title="Wynnbuilder Internals (compute graph)">
|
||||
<p>
|
||||
This section is about how Wynnbuilder's main builder page processes user input and calculates results.
|
||||
Might be useful if you want to script wynnbuilder or extend it! Or for wynnbuilder developers (internal docs).
|
||||
</p>
|
||||
<div class="row section" title="Why?">
|
||||
<p>
|
||||
Modeling wynnbuilder's internal computations as a directed graph has a few advantages:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>Each compute "node" is small(er); easier to debug.</li>
|
||||
<li>Information flow is specified explicitly (easier to debug).</li>
|
||||
<li>Easy to build caching for arbitrary computations (only calculate what u need)</li>
|
||||
<li>Stateless builder! Abstract the entire builder as a chain of function calls</li>
|
||||
<li>Makes for pretty pictures</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row section" title="TODO ComputeNode details">
|
||||
TODO
|
||||
</div>
|
||||
<p>
|
||||
An overview of wynnbuilder's internal structure can be seen <a href = "./compute_graph.svg" target = "_blank">here</a>. Arrows indicate flow of information.
|
||||
Colors correspond roughly as follows:
|
||||
</p>
|
||||
<img src="./builder_colorcode.png"/>
|
||||
<p>
|
||||
The overall logic flow is as follows:
|
||||
<ul class = "indent">
|
||||
<li>Item and Powder inputs are parsed. Powders are applied to items.</li>
|
||||
<li>Items and level information are combined to make a build.</li>
|
||||
<li>Information from input fields for skill points and edit IDs is collected into an ID bonuses table.</li>
|
||||
<li>Information about active powder specials, strength boosts, etc. are collected into their own ID tables.</li>
|
||||
<li>All of the above tables are merged with the build's stats table to produce the "Final" ID bonus table.</li>
|
||||
<li>Which spell variant (think: major id) to use for each of the 4 spells is computed based on the build.</li>
|
||||
<li>Spell damage is calculated, using the merged stat table, spell info, and weapon info.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
Outputs are computed as follows:
|
||||
<ul class = "indent">
|
||||
<li>Input box highlights are computed from the items produced by item input box nodes.</li>
|
||||
<li>Item display is computed from item input boxes.</li>
|
||||
<li>Build hash/URL is computed from the build, and skillpoint assignment.</li>
|
||||
<li>Spell damage is displayed based on calculated spell damage results.</li>
|
||||
<li>Build stats are displayed by builder-stats-display (this same node also displays a bunch of stuff at the bottom of the screen...)</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="row section" title="Gotchas">
|
||||
<p>
|
||||
The build sets default skillpoints and edited IDs automatically, whenever a build item/level is updated.
|
||||
This is done using "soft links" by two nodes shown in red (builder-skillpoint-setter and builder-id-setter).
|
||||
</p>
|
||||
<p>
|
||||
A soft link is where something goes and manually marks nodes dirty and calls their update methods.
|
||||
This is useful for these cases because the skillpoints and editable ID fields usually take their value from
|
||||
user input, but in some cases we want to programatically set them.
|
||||
</p>
|
||||
<p>
|
||||
For example another soft link (not shown) is used to implement the reset button.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="row section" title="Test Section">
|
||||
</div> -->
|
||||
</div>
|
||||
<script type="text/javascript" src="../js/dev.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2icons.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
1044
index.html
71
item.html
|
@ -1,71 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<meta name="HandheldFriendly" content="true" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, width=device-width, user-scalable=no" />
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<link rel="stylesheet" href="/css/items.css">
|
||||
<link rel="stylesheet" media="screen and (min-width: 800px)" href="/css/item-wide.css"/>
|
||||
<link rel="stylesheet" media="screen and (max-width: 799px)" href="/css/item-narrow.css"/>
|
||||
<link rel="icon" href="./favicon.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<title>Wynn Clientside</title>
|
||||
</head>
|
||||
<body class="all">
|
||||
<div class="center">
|
||||
<header class = "header nomarginp">
|
||||
<div class = "headerleft" id = "headerleft">
|
||||
</div>
|
||||
<div class = "headercenter" id = "headercenter">
|
||||
<div >
|
||||
<p class = "itemp" id = "header">Item Info</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "headerright" id = "headerright">
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
<br>
|
||||
<div class = "overall center">
|
||||
<div class = "overall-container" style = "display:grid">
|
||||
<div class = "item-view-container hide-container-grid" display = "grid-item-1">
|
||||
<p class = "title"></p>
|
||||
<div class = "item-view container" id = "item-view">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class = "info" display = "grid-item-2">
|
||||
<p class = "title" >Additional Info</p>
|
||||
<div class = "additional-info" id = "additional-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "container set-bonus-info bigcontainer" id = "set-bonus-info" style = "display: none">
|
||||
</div>
|
||||
<div class = "container identification-costs bigcontainer" id = "identification-costs" style = "display: none">
|
||||
</div>
|
||||
<div class = "container identification-probabilities bigcontainer" id = "identification-probabilities" style = "display: none">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<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/loadheader.js"></script>
|
||||
<script type="text/javascript" src="/js/icons.js"></script>
|
||||
<script type="text/javascript" src="/js/damage_calc.js"></script>
|
||||
<script type="text/javascript" src="/js/powders.js"></script>
|
||||
<script type="text/javascript" src="/js/load.js"></script>
|
||||
<script type="text/javascript" src="/js/load_ing.js"></script>
|
||||
<script type="text/javascript" src="/js/crafter.js"></script>
|
||||
<script type="text/javascript" src="/js/craft.js"></script>
|
||||
<script type="text/javascript" src="/js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/display.js"></script>
|
||||
<script type="text/javascript" src="/js/custom.js"></script>
|
||||
<script type="text/javascript" src="/js/customizer.js"></script>
|
||||
<script type="text/javascript" src="/js/item.js"></script>
|
||||
</body>
|
||||
</html>
|
68
item/index.html
Normal file
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<title>WB Item Viewer</title>
|
||||
<link rel="icon" href="../media/icons/new/searcher.png" type="image/icon type">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
|
||||
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../css/sq2bs.css">
|
||||
<link rel="stylesheet" href="../css/sidebar.css">
|
||||
<link rel="stylesheet" href="../css/wynnstyles.css">
|
||||
</head>
|
||||
<body class = "text-light d-flex justify-content-center" id = "body">
|
||||
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
|
||||
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
|
||||
<a href = "../crafter/"><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href = "../items/"><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
|
||||
<a href = "../custom/"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
|
||||
<a href = "../map/"><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
|
||||
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
|
||||
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
|
||||
<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 class="col">
|
||||
<div class="row h-100 gx-lg-5 gy-3 mx-2 mx-lg-0 mb-3">
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "rounded col g-0 scaled-font border border-3 border-dark dark-shadow p-3 dark-7" id = "item-view">
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col-lg-6 col-sm-12">
|
||||
<div class = "rounded col scaled-font border border-3 border-dark dark-shadow p-3" id = "additional-info"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row mb-3 rounded border border-semi-dark border-3 p-3" id = "set-bonus-info" style = "display: none">
|
||||
</div>
|
||||
<div class = "row mb-3 rounded border border-semi-dark border-3 p-3" id = "identification-costs" style = "display: none">
|
||||
</div>
|
||||
<div class = "row mb-3 rounded border border-semi-dark border-3 p-3" id = "identification-probabilities" style = "display: none">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<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>
|
||||
<script type="text/javascript" src="/js/damage_calc.js"></script>
|
||||
<!-- <script type="text/javascript" src="/js/powders.js"></script> -->
|
||||
<script type="text/javascript" src="/js/load.js"></script>
|
||||
<script type="text/javascript" src="/js/load_ing.js"></script>
|
||||
<script type="text/javascript" src="/js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/display.js"></script>
|
||||
<script type="text/javascript" src="/js/item.js"></script>
|
||||
</body>
|
||||
</html>
|
137
items.html
|
@ -1,137 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="HandheldFriendly" content="true" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, width=device-width, user-scalable=no" />
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<link rel="stylesheet" href="/css/items.css">
|
||||
<link rel="stylesheet" media="screen and (min-width: 1100px)" href="/css/items-wide.css"/>
|
||||
<link rel="stylesheet" media="screen and (max-width: 1099px)" href="/css/items-narrow.css"/>
|
||||
<link rel="icon" href="./media/icons/new/searcher.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<title>Wynn Clientside</title>
|
||||
</head>
|
||||
<body class="all">
|
||||
<div class="center">
|
||||
<header class = "header nomarginp">
|
||||
<div class = "headerleft" id = "headerleft">
|
||||
</div>
|
||||
<div class = "headercenter" id = "headercenter">
|
||||
<div >
|
||||
<p class = "itemp" id = "header">WynnAtlas</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "headerright" id = "headerright">
|
||||
<div class="center" id="advanced">
|
||||
<a href="./items_2.html" class = "link">Advanced Search</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<div class="itemsearch">
|
||||
<div class="searchbox">
|
||||
<div class="left">
|
||||
<label for="name-choice">Name:</label><br>
|
||||
<input class="searchinput" type="text" id="name-choice" name="name-choice" placeholder="Item name (case insensitive)" tabindex="1" autofocus/>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
<div class="left">
|
||||
<label for="category-choice">Category:</label><br>
|
||||
<input class="searchinput" list="category-items" id="category-choice" name="category-choice" placeholder="ALL" tabindex="2"/>
|
||||
<datalist id="category-items">
|
||||
<option value="ALL">
|
||||
<option value="armor">
|
||||
<option value="helmet">
|
||||
<option value="chestplate">
|
||||
<option value="leggings">
|
||||
<option value="boots">
|
||||
<option value="accessory">
|
||||
<option value="ring">
|
||||
<option value="bracelet">
|
||||
<option value="necklace">
|
||||
<option value="weapon">
|
||||
<option value="wand">
|
||||
<option value="spear">
|
||||
<option value="bow">
|
||||
<option value="dagger">
|
||||
<option value="relik">
|
||||
</datalist>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
<div class="left">
|
||||
<label for="rarity-choice">Rarity:</label><br>
|
||||
<input class="searchinput" list="rarity-items" id="rarity-choice" name="rarity-choice" placeholder="ANY" tabindex="3"/>
|
||||
<datalist id="rarity-items">
|
||||
<option value="ANY">
|
||||
<option value="Normal">
|
||||
<option value="Unique">
|
||||
<option value="Set">
|
||||
<option value="Rare">
|
||||
<option value="Legendary">
|
||||
<option value="Fabled">
|
||||
<option value="Mythic">
|
||||
<option value="Sane">
|
||||
</datalist>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
<div class="left">
|
||||
<label for="level-choice">Level:</label><br>
|
||||
<input class="searchinput" type="text" id="level-choice" name="level-choice" value="1-106" tabindex="4"/>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
|
||||
<datalist id="filter-items">
|
||||
</datalist>
|
||||
<div class="left">
|
||||
<label for="filter1-choice">Filter 1:</label><br>
|
||||
<input class="searchinput" list="filter-items" id="filter1-choice" name="filter1-choice" placeholder="ANY" tabindex="5"/>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
<div class="left">
|
||||
<label for="filter2-choice">Filter 2:</label><br>
|
||||
<input class="searchinput" list="filter-items" id="filter2-choice" name="filter2-choice" placeholder="ANY" tabindex="6"/>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
<div class="left">
|
||||
<label for="filter3-choice">Filter 3:</label><br>
|
||||
<input class="searchinput" list="filter-items" id="filter3-choice" name="filter3-choice" placeholder="ANY" tabindex="7"/>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
<div class="left">
|
||||
<label for="filter4-choice">Filter 4:</label><br>
|
||||
<input class="searchinput" list="filter-items" id="filter4-choice" name="filter4-choice" placeholder="ANY" tabindex="8"/>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
<div class="right" style="grid-column:1/span 2">
|
||||
<button class = "button" id = "search-button" onclick = "doItemSearch()" tabindex="9">
|
||||
Search!
|
||||
</button>
|
||||
</div>
|
||||
<div id="summary" class="left" style="grid-column:3/span 2">
|
||||
Hello!
|
||||
</div>
|
||||
</div>
|
||||
<div class="center items" id="main">
|
||||
</div>
|
||||
</div>
|
||||
<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/loadheader.js"></script>
|
||||
<script type="text/javascript" src="/js/icons.js"></script>
|
||||
<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/expr_parser.js"></script>
|
||||
<script type="text/javascript" src="/js/load.js"></script>
|
||||
<script type="text/javascript" src="/js/items.js"></script>
|
||||
</body>
|
||||
</html>
|
152
items/index.html
Normal file
|
@ -0,0 +1,152 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<title>WynnAtlas</title>
|
||||
<link rel="icon" href="../media/icons/new/searcher.png" type="image/icon type">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
|
||||
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../css/sq2bs.css">
|
||||
<link rel="stylesheet" href="../css/sidebar.css">
|
||||
<link rel="stylesheet" href="../css/wynnstyles.css">
|
||||
</head>
|
||||
<body class = "text-light d-flex justify-content-center" id = "body">
|
||||
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
|
||||
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
|
||||
<a href = "../crafter/"><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href = ""><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
|
||||
<a href = "../custom/"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
|
||||
<a href = "../map/"><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
|
||||
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
|
||||
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
|
||||
<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 class = "col">
|
||||
<div class = "row">
|
||||
<div class = "col text-end">
|
||||
<a href = "../items_adv/">Advanced Item Search</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row">
|
||||
<div class = "row">
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col fw-bold">Name:</div>
|
||||
<input class = "col border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" type="text" id="item-name-choice" name="item-name-choice" placeholder="Item name (case insensitive)"/>
|
||||
<p class="error col"></p>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col fw-bold">Category:</div>
|
||||
<input class="col border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="item-category-choice" name="item-category-choice" placeholder="ALL"/>
|
||||
<datalist id="category-items">
|
||||
<option value="ALL">
|
||||
<option value="armor">
|
||||
<option value="helmet">
|
||||
<option value="chestplate">
|
||||
<option value="leggings">
|
||||
<option value="boots">
|
||||
<option value="accessory">
|
||||
<option value="ring">
|
||||
<option value="bracelet">
|
||||
<option value="necklace">
|
||||
<option value="weapon">
|
||||
<option value="wand">
|
||||
<option value="spear">
|
||||
<option value="bow">
|
||||
<option value="dagger">
|
||||
<option value="relik">
|
||||
</datalist>
|
||||
<p class="error"></p>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col fw-bold">Rarity:</div>
|
||||
<input class = "border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="item-rarity-choice" name="item-rarity-choice" placeholder="ANY"/>
|
||||
<datalist id = "rarity-items">
|
||||
<option value="ANY">
|
||||
<option value="Normal">
|
||||
<option value="Unique">
|
||||
<option value="Set">
|
||||
<option value="Rare">
|
||||
<option value="Legendary">
|
||||
<option value="Fabled">
|
||||
<option value="Mythic">
|
||||
<option value="Sane">
|
||||
</datalist>
|
||||
<p class="error col-auto"></p>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col fw-bold">Level Range:</div>
|
||||
<input class = "border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" type="text" id="item-level-choice" name="item-level-choice" placeholder = "1-106"/>
|
||||
<p class="error col-auto"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row">
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col fw-bold">Filter 1:</div>
|
||||
<input class = "border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="filter1-choice" name="filter1-choice" placeholder="ANY"/>
|
||||
<p class="error col-auto"></p>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col fw-bold">Filter 2:</div>
|
||||
<input class = "border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="filter2-choice" name="filter2-choice" placeholder="ANY"/>
|
||||
<p class="error col-auto"></p>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col fw-bold">Filter 3:</div>
|
||||
<input class = "border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="filter3-choice" name="filter3-choice" placeholder="ANY"/>
|
||||
<p class="error col-auto"></p>
|
||||
</div>
|
||||
<div class = "col-lg-3 col-sm-12">
|
||||
<div class = "col fw-bold">Filter 4:</div>
|
||||
<input class = "border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="filter4-choice" name="filter4-choice" placeholder="ANY"/>
|
||||
<p class="error col-auto"></p>
|
||||
</div>
|
||||
<datalist id = "filter-items"></datalist>
|
||||
</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()">
|
||||
Search!
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-auto">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "reset-button" onclick = "resetItemSearch()">
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row box-title justify-content-center" id = "summary">
|
||||
</div>
|
||||
<div class = "row" id = "search-results">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/autoComplete.min.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>
|
||||
<script type="text/javascript" src="../js/damage_calc.js"></script>
|
||||
<script type="text/javascript" src="../js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/display.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="../js/query.js"></script>
|
||||
<script type="text/javascript" src="../js/query_2.js"></script>
|
||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
||||
<script type="text/javascript" src="../js/load.js"></script>
|
||||
<script type="text/javascript" src="../js/items.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2items.js"></script>
|
||||
</body>
|
||||
</html>
|
74
items_2.html
|
@ -1,74 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="HandheldFriendly" content="true" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, width=device-width, user-scalable=no" />
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<link rel="stylesheet" href="/css/items_2.css">
|
||||
<link rel="icon" href="./media/icons/new/searcher.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<title>WynnAtlas</title>
|
||||
</head>
|
||||
<body class="all">
|
||||
<header class="header nomarginp">
|
||||
<div class="headerleft" id = "headerleft">
|
||||
</div>
|
||||
<div class="headercenter" id = "headercenter">
|
||||
<div>
|
||||
<p class="itemp" id="header">WynnAtlas</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="headerright" id = "headerright">
|
||||
<div class="center" id="basic">
|
||||
<a href="./items.html" class="link">Basic Search</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="center" id="credits">
|
||||
<a href="https://hppeng-wynn.github.io/credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
<div class="center" id="help">
|
||||
<a href="items_2_help.html" class="link" target="_blank">Search Guide</a>
|
||||
</div>
|
||||
<div class="center" id="main">
|
||||
<div id="search-container">
|
||||
<div class="search-field-container left" id="search-filter">
|
||||
<label class="search-field-label" for="search-filter-field">Filter By:</label>
|
||||
<input class="search-field" id="search-filter-field" type="text" autofocus="true"
|
||||
placeholder="name ?= "blue" & str >= 15 & dex >= 10">
|
||||
<div class="search-field-error" id="search-filter-error"></div>
|
||||
<div class="search-field-compl" id="search-filter-compl"></div>
|
||||
</div>
|
||||
<div class="search-field-container left" id="search-sort">
|
||||
<label class="search-field-label" for="search-sort-field">Sort By:</label>
|
||||
<input class="search-field" id="search-sort-field" type="text"
|
||||
placeholder="str + dex; meleerawdmg + spellrawdmg">
|
||||
<div class="search-field-error" id="search-sort-error"></div>
|
||||
<div class="search-field-compl" id="search-sort-compl"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="item-list-container">
|
||||
<div class="left" id="item-list"></div>
|
||||
<div class="center" id="item-list-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="scroll-up">↑</div>
|
||||
<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/loadheader.js"></script>
|
||||
<script type="text/javascript" src="/js/icons.js"></script>
|
||||
<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/expr_parser.js"></script>
|
||||
<script type="text/javascript" src="/js/load.js"></script>
|
||||
<script type="text/javascript" src="/js/items_2.js"></script>
|
||||
</body>
|
||||
</html>
|
14
items_adv/credits.txt
Normal file
|
@ -0,0 +1,14 @@
|
|||
Theme, formatting, and overall inspiration: Wynndata (Dukio)
|
||||
- https://wynndata.tk
|
||||
|
||||
The game, of course
|
||||
- wynncraft.com
|
||||
|
||||
Additional Contributors:
|
||||
- Kiocifer (Icons!)
|
||||
- Lennon (Skill point formula reversing)
|
||||
- Phanta (WynnAtlas custom expression parser / item search)
|
||||
- QuantumNep (Layout code/layout ideas)
|
||||
- nbcss (Crafted Item mechanics reverse engineering)
|
||||
- dr_carlos (Hiding UI elements properly, fade animations, proper error handling)
|
||||
- Atlas Inc discord (feedback, ideas, damage calc, etc)
|
BIN
items_adv/help_photo.png
Normal file
After Width: | Height: | Size: 65 KiB |
88
items_adv/index.html
Normal file
|
@ -0,0 +1,88 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<title>WynnAtlas</title>
|
||||
<link rel="icon" href="../media/icons/new/searcher.png" type="image/icon type">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
|
||||
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../css/sq2bs.css">
|
||||
<link rel="stylesheet" href="../css/items_2.css">
|
||||
<link rel="stylesheet" href="../css/sidebar.css">
|
||||
<link rel="stylesheet" href="../css/wynnstyles.css">
|
||||
</head>
|
||||
<body class = "text-light d-flex justify-content-center" id = "body">
|
||||
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
|
||||
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
|
||||
<a href = "../crafter/"><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href = "../items/"><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
|
||||
<a href = "/customizer.html"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
|
||||
<a href = "/map.html"><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
|
||||
<a href = "/wynnfo/index.html"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
|
||||
<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 class = "col">
|
||||
<div class = "row">
|
||||
<div class = "col text-start" id = "credits">
|
||||
<a href="credits.txt" class="link">Additional credits</a>
|
||||
</div>
|
||||
<div class = "col text-center" id = "help">
|
||||
<a href="items_2_help.html" class="link" target="_blank">Search Guide</a>
|
||||
</div>
|
||||
<div class = "col text-end">
|
||||
<a href = "../items/">Basic Item Search</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row">
|
||||
<div class="col" id="main">
|
||||
<div class = "row" id="search-container">
|
||||
<div class="col search-field-container" id="search-filter">
|
||||
<div class="col fw-bold">Filter By:</div>
|
||||
<input class="col border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="search-filter-field" type="text" autofocus="true"
|
||||
placeholder="name ?= "blue" & str >= 15 & dex >= 10">
|
||||
<div class="search-field-error" id="search-filter-error"></div>
|
||||
<div class="search-field-compl" id="search-filter-compl"></div>
|
||||
</div>
|
||||
<div class="col search-field-container" id="search-sort">
|
||||
<div class="col fw-bold">Sort By:</div>
|
||||
<input class="col border-dark text-light dark-5 rounded scaled-font form-control form-control-sm" id="search-sort-field" type="text"
|
||||
placeholder="str + dex; meleerawdmg + spellrawdmg">
|
||||
<div class="search-field-error" id="search-sort-error"></div>
|
||||
<div class="search-field-compl" id="search-sort-compl"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row" id="item-list-container">
|
||||
<div class="row" id="item-list"></div>
|
||||
<div class="row" id="item-list-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row" id="scroll-up">↑</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<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>
|
||||
<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/expr_parser.js"></script>
|
||||
<script type="text/javascript" src="/js/load.js"></script>
|
||||
<script type="text/javascript" src="/js/items_2.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,32 +1,45 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<title>WynnAtlas Help</title>
|
||||
<link rel="icon" href="../media/icons/new/searcher.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
|
||||
<!-- nunito font, copying wynnbuilder, which is copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<link rel="stylesheet" href="/css/article.css">
|
||||
<link rel="icon" href="./media/icons/new/searcher.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<title>WynnAtlas</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../css/article.css">
|
||||
<link rel="stylesheet" href="../css/styles.css">
|
||||
<link rel="stylesheet" href="../css/sidebar.css">
|
||||
<link rel="stylesheet" href="../css/wynnstyles.css">
|
||||
</head>
|
||||
<body class="all" style="overflow-y: scroll">
|
||||
<header class="header nomarginp">
|
||||
<div class="headerleft" id = "headerleft">
|
||||
</div>
|
||||
<div class="headercenter" id = "headercenter">
|
||||
<div>
|
||||
<p class="itemp" id="header">WynnAtlas</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="headerright" id = "headerright">
|
||||
</div>
|
||||
</header>
|
||||
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
|
||||
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
|
||||
<a href = "../crafter/"><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href = "../items/"><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
|
||||
<a href = "../custom/"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
|
||||
<a href = "../map/"><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
|
||||
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
|
||||
<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>
|
||||
<main>
|
||||
<div class="full-width">
|
||||
<img src="https://i.imgur.com/Ap6Zd3Q.png"/>
|
||||
<div class="full-width text-center">
|
||||
<img src="./help_photo.png"/>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>What the heck is “Advanced Item Search”?</h2>
|
||||
<p>The WynnBuilder team has been hard at work giving you the latest and greatest tools for optimizing your most complex Wynncraft builds. Now, we're introducing <strong class="rb-text">WynnAtlas</strong>, the new, bigger, better, smarter, powerful-er item guide! Featuring an extremely flexible expression language for filtering and sorting items, WynnAtlas' advanced item search system gives build engineers the power to select items with a high degree of granularity. Picking components for your brand-new Divzer WFA build has never been easier!</p>
|
||||
|
@ -208,7 +221,6 @@
|
|||
docsFns.append(genDocEntry(entry[0], entry[1], null, entry[2]));
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="/js/loadheader.js"></script>
|
||||
<script type="text/javascript" src="/js/icons.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2icons.js"></script>
|
||||
</body>
|
||||
</html>
|
5
js/README_atree_constants.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
How to convert:
|
||||
|
||||
1. edit `atree_constants.js`
|
||||
2. run `python3 ../py_script/atree-generateID.py
|
||||
3. check that the site still works
|
61
js/atlas.js
|
@ -6,25 +6,33 @@ function setTitle() {
|
|||
}
|
||||
|
||||
setTitle();
|
||||
const flavortexts = ["JALA?? \n ATLAS?? \n ANYONE??",
|
||||
"this really do be a bruh moment.",
|
||||
"OH, LOOK AT YOU. YOU FOUND THE FUNNY BUILDER GUILDER MEME PAGE. AREN'T YOU PROUD OF YOURSELF?",
|
||||
"Downloading Atlas Inc Virus 2.0...",
|
||||
"Any WynnBuilders in the chat?",
|
||||
":sunglaso:",
|
||||
"This says a lot about our society.",
|
||||
"WynnCraft is overrated. Stay on this page forever!",
|
||||
"Now trading Smash invite letters for Atlas Inc invites!",
|
||||
"You have reached the customer support page of Wynnbuilder. Please call [REDACTED] to get your problems solved!",
|
||||
"",
|
||||
"Isn't this like that one game Amogus?",
|
||||
"Mom, what does 'hppeng' mean?",
|
||||
"hpgbegg",
|
||||
"| |I || |_",
|
||||
"",
|
||||
];
|
||||
const flavortexts = [
|
||||
"JALA?? \n ATLAS?? \n ANYONE??",
|
||||
"this really do be a bruh moment.",
|
||||
"OH, LOOK AT YOU. YOU FOUND THE FUNNY BUILDER GUILDER MEME PAGE. AREN'T YOU PROUD OF YOURSELF?",
|
||||
"Downloading Atlas Inc Virus 2.0...",
|
||||
"Any WynnBuilders in the chat?",
|
||||
":sunglaso:",
|
||||
"This says a lot about our society.",
|
||||
"WynnCraft is overrated. Stay on this page forever!",
|
||||
"Now trading Smash invite letters for Atlas Inc invites!",
|
||||
"You have reached the customer support page of Wynnbuilder. Please call [REDACTED] to get your problems solved!",
|
||||
"Mom, what does 'hppeng' mean?",
|
||||
"hpgbegg",
|
||||
"| |I || |_",
|
||||
"Wynn was so good they made Wynn 2",
|
||||
"Join Monumenta today!",
|
||||
"do NOT look up the 25th largest island of Greece",
|
||||
"whatever you do, don't search for lego piece 26047.",
|
||||
"guys what does perbromic acid look like",
|
||||
"Hello Chat",
|
||||
"Goodbye Chat",
|
||||
"Look up. Now look down. Now look up again. Spin your head 3 times clockwise. You look real silly.",
|
||||
"There\'s \'guillble\' written on the ceiling",
|
||||
"when the pretender is conspicuous...",
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH"];
|
||||
const dt = 10; //millis
|
||||
const PIX_PER_SEC = 1000;
|
||||
const PIX_PER_SEC = 1337;
|
||||
const EPSILON = 1E-7 * dt;
|
||||
let atli = [];
|
||||
|
||||
|
@ -32,11 +40,13 @@ let atli = [];
|
|||
function atlasClick() {
|
||||
let atlas = document.createElement("div");
|
||||
let atlas_img = document.createElement("img");
|
||||
atlas_img.src = "favicon.png";
|
||||
atlas.style.maxWidth = "64px";
|
||||
atlas.style.maxHeight = "64px";
|
||||
atlas_img.src = "../atlas/favicon.png";
|
||||
atlas_img.style.width = "100%";
|
||||
atlas_img.style.height = "100%";
|
||||
atlas_img.style.maxWidth = "48px";
|
||||
atlas_img.style.maxHeight = "48px";
|
||||
atlas_img.style.maxWidth = "64px";
|
||||
atlas_img.style.maxHeight = "64px";
|
||||
atlas_img.style.zIndex = 1;
|
||||
atlas.classList.add("atlas");
|
||||
let roll = Math.random();
|
||||
|
@ -44,7 +54,7 @@ function atlasClick() {
|
|||
let rollList = ["lmoa","doom","agony","enraged","sunglaso","thonk","unglaso"];
|
||||
for (let i = rollList.length-1; i > -1; i--) {
|
||||
if (roll < (i+1) * rollchance) {
|
||||
atlas_img.src = "./media/memes/" + rollList[i] + ".png";
|
||||
atlas_img.src = "../media/memes/" + rollList[i] + ".png";
|
||||
}
|
||||
}
|
||||
atlas.appendChild(atlas_img);
|
||||
|
@ -52,7 +62,8 @@ function atlasClick() {
|
|||
|
||||
atlas.style.position = "absolute";
|
||||
rect = document.getElementById("bodydiv").getBoundingClientRect(); //use rect.top, rect.left, rect.bottom, and rect.top
|
||||
|
||||
console.log(rect)
|
||||
|
||||
atlasrect = atlas.getBoundingClientRect();
|
||||
atlas.style.left = Math.floor((rect.right - rect.left - 2*(atlasrect.right - atlasrect.left) ) * Math.random() + rect.left + (atlasrect.right - atlasrect.left) )+ "px";
|
||||
atlas.style.top = Math.floor((rect.bottom - rect.top - 2*(atlasrect.bottom - atlasrect.top) ) * Math.random() + rect.top + (atlasrect.bottom - atlasrect.top) ) + "px";
|
||||
|
@ -108,6 +119,10 @@ function runAtlas() {
|
|||
let center = [(at1[0]+at2[0])/2, (at1[1]+at2[1])/2 ];
|
||||
|
||||
if (Math.sqrt(((at2[1]+atlas2.vy) - (at1[1]+atlas1.vy))**2 + ((at2[0]+atlas2.vx) - (at1[0]+atlas1.vx))**2) < 2*r) {
|
||||
//Play bruh sound effect
|
||||
document.getElementById('bruh_sound_effect').play();
|
||||
document.getElementById('bruh_sound_effect').currentTime = 0;
|
||||
|
||||
if(Math.sqrt( (at2[1]-at1[1])**2 + (at2[0]-at1[0])**2 ) < 2*r ) {//check for collision
|
||||
//Move both away slightly - correct alg this time :)
|
||||
atlas1.style.left = parseFloat(atlas1.style.left.replace("px","")) + (at1[0]-center[0]) * 2 * r / Math.sqrt(dx**2 + dy**2) + "px";
|
||||
|
|
1082
js/atree.js
Normal file
3978
js/atree_constants.js
Normal file
1
js/atree_constants_min.js
Normal file
146
js/atree_ids.json
Normal file
|
@ -0,0 +1,146 @@
|
|||
{
|
||||
"Archer": {
|
||||
"Arrow Shield": 0,
|
||||
"Escape": 1,
|
||||
"Arrow Bomb": 2,
|
||||
"Heart Shatter": 3,
|
||||
"Fire Creep": 4,
|
||||
"Bryophyte Roots": 5,
|
||||
"Nimble String": 6,
|
||||
"Arrow Storm": 7,
|
||||
"Guardian Angels": 8,
|
||||
"Windy Feet": 9,
|
||||
"Basaltic Trap": 10,
|
||||
"Windstorm": 11,
|
||||
"Grappling Hook": 12,
|
||||
"Implosion": 13,
|
||||
"Twain's Arc": 14,
|
||||
"Fierce Stomp": 15,
|
||||
"Scorched Earth": 16,
|
||||
"Leap": 17,
|
||||
"Shocking Bomb": 18,
|
||||
"Mana Trap": 19,
|
||||
"Escape Artist": 20,
|
||||
"Initiator": 21,
|
||||
"Call of the Hound": 22,
|
||||
"Arrow Hurricane": 23,
|
||||
"Geyser Stomp": 24,
|
||||
"Crepuscular Ray": 25,
|
||||
"Grape Bomb": 26,
|
||||
"Tangled Traps": 27,
|
||||
"Snow Storm": 28,
|
||||
"All-Seeing Panoptes": 29,
|
||||
"Minefield": 30,
|
||||
"Bow Proficiency I": 31,
|
||||
"Cheaper Arrow Bomb": 32,
|
||||
"Cheaper Arrow Storm": 33,
|
||||
"Cheaper Escape": 34,
|
||||
"Earth Mastery": 35,
|
||||
"Thunder Mastery": 36,
|
||||
"Water Mastery": 37,
|
||||
"Air Mastery": 38,
|
||||
"Fire Mastery": 39,
|
||||
"More Shields": 40,
|
||||
"Stormy Feet": 41,
|
||||
"Refined Gunpowder": 42,
|
||||
"More Traps": 43,
|
||||
"Better Arrow Shield": 44,
|
||||
"Better Leap": 45,
|
||||
"Better Guardian Angels": 46,
|
||||
"Cheaper Arrow Storm (2)": 47,
|
||||
"Precise Shot": 48,
|
||||
"Cheaper Arrow Shield": 49,
|
||||
"Rocket Jump": 50,
|
||||
"Cheaper Escape (2)": 51,
|
||||
"Stronger Hook": 52,
|
||||
"Cheaper Arrow Bomb (2)": 53,
|
||||
"Bouncing Bomb": 54,
|
||||
"Homing Shots": 55,
|
||||
"Shrapnel Bomb": 56,
|
||||
"Elusive": 57,
|
||||
"Double Shots": 58,
|
||||
"Triple Shots": 59,
|
||||
"Power Shots": 60,
|
||||
"Focus": 61,
|
||||
"More Focus": 62,
|
||||
"More Focus (2)": 63,
|
||||
"Traveler": 64,
|
||||
"Patient Hunter": 65,
|
||||
"Stronger Patient Hunter": 66,
|
||||
"Frenzy": 67,
|
||||
"Phantom Ray": 68,
|
||||
"Arrow Rain": 69,
|
||||
"Decimator": 70
|
||||
},
|
||||
"Warrior": {
|
||||
"Bash": 0,
|
||||
"Spear Proficiency 1": 1,
|
||||
"Cheaper Bash": 2,
|
||||
"Double Bash": 3,
|
||||
"Charge": 4,
|
||||
"Heavy Impact": 5,
|
||||
"Vehement": 6,
|
||||
"Tougher Skin": 7,
|
||||
"Uppercut": 8,
|
||||
"Cheaper Charge": 9,
|
||||
"War Scream": 10,
|
||||
"Earth Mastery": 11,
|
||||
"Thunder Mastery": 12,
|
||||
"Water Mastery": 13,
|
||||
"Air Mastery": 14,
|
||||
"Fire Mastery": 15,
|
||||
"Quadruple Bash": 16,
|
||||
"Fireworks": 17,
|
||||
"Half-Moon Swipe": 18,
|
||||
"Flyby Jab": 19,
|
||||
"Flaming Uppercut": 20,
|
||||
"Iron Lungs": 21,
|
||||
"Generalist": 22,
|
||||
"Counter": 23,
|
||||
"Mantle of the Bovemists": 24,
|
||||
"Bak'al's Grasp": 25,
|
||||
"Spear Proficiency 2": 26,
|
||||
"Cheaper Uppercut": 27,
|
||||
"Aerodynamics": 28,
|
||||
"Provoke": 29,
|
||||
"Precise Strikes": 30,
|
||||
"Air Shout": 31,
|
||||
"Enraged Blow": 32,
|
||||
"Flying Kick": 33,
|
||||
"Stronger Mantle": 34,
|
||||
"Manachism": 35,
|
||||
"Boiling Blood": 36,
|
||||
"Ragnarokkr": 37,
|
||||
"Ambidextrous": 38,
|
||||
"Burning Heart": 39,
|
||||
"Stronger Bash": 40,
|
||||
"Intoxicating Blood": 41,
|
||||
"Comet": 42,
|
||||
"Collide": 43,
|
||||
"Rejuvenating Skin": 44,
|
||||
"Uncontainable Corruption": 45,
|
||||
"Radiant Devotee": 46,
|
||||
"Whirlwind Strike": 47,
|
||||
"Mythril Skin": 48,
|
||||
"Armour Breaker": 49,
|
||||
"Shield Strike": 50,
|
||||
"Sparkling Hope": 51,
|
||||
"Massive Bash": 52,
|
||||
"Tempest": 53,
|
||||
"Spirit of the Rabbit": 54,
|
||||
"Massacre": 55,
|
||||
"Axe Kick": 56,
|
||||
"Radiance": 57,
|
||||
"Cheaper Bash 2": 58,
|
||||
"Cheaper War Scream": 59,
|
||||
"Discombobulate": 60,
|
||||
"Thunderclap": 61,
|
||||
"Cyclone": 62,
|
||||
"Second Chance": 63,
|
||||
"Blood Pact": 64,
|
||||
"Haemorrhage": 65,
|
||||
"Brink of Madness": 66,
|
||||
"Cheaper Uppercut 2": 67,
|
||||
"Martyr": 68
|
||||
}
|
||||
}
|
405
js/build.js
|
@ -100,238 +100,12 @@ class Build{
|
|||
* @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)
|
||||
*/
|
||||
constructor(level,equipment, powders, externalStats, inputerrors=[]){
|
||||
|
||||
let errors = inputerrors;
|
||||
//this contains the Craft objects, if there are any crafted items. this.boots, etc. will contain the statMap of the Craft (which is built to be an expandedItem).
|
||||
this.craftedItems = [];
|
||||
this.customItems = [];
|
||||
// NOTE: powders is just an array of arrays of powder IDs. Not powder objects.
|
||||
this.powders = powders;
|
||||
if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") {
|
||||
const helmet = itemMap.get(equipment[0]);
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
} else {
|
||||
try {
|
||||
let helmet = getCustomFromHash(equipment[0]) ? getCustomFromHash(equipment[0]) : (getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : undefined);
|
||||
if (helmet.statMap.get("type") !== "helmet") {
|
||||
throw new Error("Not a helmet");
|
||||
}
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.statMap.get("slots"));
|
||||
helmet.statMap.set("powders",this.powders[0].slice());
|
||||
this.helmet = helmet.statMap;
|
||||
applyArmorPowders(this.helmet, this.powders[0]);
|
||||
if (this.helmet.get("custom")) {
|
||||
this.customItems.push(helmet);
|
||||
} else if (this.helmet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(helmet);
|
||||
}
|
||||
|
||||
} catch (Error) {
|
||||
const helmet = itemMap.get("No Helmet");
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
errors.push(new ItemNotFound(equipment[0], "helmet", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[1]) && itemMap.get(equipment[1]).type === "chestplate") {
|
||||
const chestplate = itemMap.get(equipment[1]);
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
} else {
|
||||
try {
|
||||
let chestplate = getCustomFromHash(equipment[1]) ? getCustomFromHash(equipment[1]) : (getCraftFromHash(equipment[1]) ? getCraftFromHash(equipment[1]) : undefined);
|
||||
if (chestplate.statMap.get("type") !== "chestplate") {
|
||||
throw new Error("Not a chestplate");
|
||||
}
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.statMap.get("slots"));
|
||||
chestplate.statMap.set("powders",this.powders[1].slice());
|
||||
this.chestplate = chestplate.statMap;
|
||||
applyArmorPowders(this.chestplate, this.powders[1]);
|
||||
if (this.chestplate.get("custom")) {
|
||||
this.customItems.push(chestplate);
|
||||
} else if (this.chestplate.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(chestplate);
|
||||
}
|
||||
} catch (Error) {
|
||||
console.log(Error);
|
||||
const chestplate = itemMap.get("No Chestplate");
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
errors.push(new ItemNotFound(equipment[1], "chestplate", true));
|
||||
}
|
||||
}
|
||||
if (itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") {
|
||||
const leggings = itemMap.get(equipment[2]);
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
} else {
|
||||
try {
|
||||
let leggings = getCustomFromHash(equipment[2]) ? getCustomFromHash(equipment[2]) : (getCraftFromHash(equipment[2]) ? getCraftFromHash(equipment[2]) : undefined);
|
||||
if (leggings.statMap.get("type") !== "leggings") {
|
||||
throw new Error("Not a leggings");
|
||||
}
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.statMap.get("slots"));
|
||||
leggings.statMap.set("powders",this.powders[2].slice());
|
||||
this.leggings = leggings.statMap;
|
||||
applyArmorPowders(this.leggings, this.powders[2]);
|
||||
if (this.leggings.get("custom")) {
|
||||
this.customItems.push(leggings);
|
||||
} else if (this.leggings.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(leggings);
|
||||
}
|
||||
} catch (Error) {
|
||||
const leggings = itemMap.get("No Leggings");
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
errors.push(new ItemNotFound(equipment[2], "leggings", true));
|
||||
}
|
||||
}
|
||||
if (itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") {
|
||||
const boots = itemMap.get(equipment[3]);
|
||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
||||
this.boots = expandItem(boots, this.powders[3]);
|
||||
} else {
|
||||
try {
|
||||
let boots = getCustomFromHash(equipment[3]) ? getCustomFromHash(equipment[3]) : (getCraftFromHash(equipment[3]) ? getCraftFromHash(equipment[3]) : undefined);
|
||||
if (boots.statMap.get("type") !== "boots") {
|
||||
throw new Error("Not a boots");
|
||||
}
|
||||
this.powders[3] = this.powders[3].slice(0,boots.statMap.get("slots"));
|
||||
boots.statMap.set("powders",this.powders[3].slice());
|
||||
this.boots = boots.statMap;
|
||||
applyArmorPowders(this.boots, this.powders[3]);
|
||||
if (this.boots.get("custom")) {
|
||||
this.customItems.push(boots);
|
||||
} else if (this.boots.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(boots);
|
||||
}
|
||||
} catch (Error) {
|
||||
const boots = itemMap.get("No Boots");
|
||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
||||
this.boots = expandItem(boots, this.powders[3]);
|
||||
errors.push(new ItemNotFound(equipment[3], "boots", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[4]) && itemMap.get(equipment[4]).type === "ring") {
|
||||
const ring = itemMap.get(equipment[4]);
|
||||
this.ring1 = expandItem(ring, []);
|
||||
}else{
|
||||
try {
|
||||
let ring = getCustomFromHash(equipment[4]) ? getCustomFromHash(equipment[4]) : (getCraftFromHash(equipment[4]) ? getCraftFromHash(equipment[4]) : undefined);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring1 = ring.statMap;
|
||||
if (this.ring1.get("custom")) {
|
||||
this.customItems.push(ring);
|
||||
} else if (this.ring1.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(ring);
|
||||
}
|
||||
} catch (Error) {
|
||||
const ring = itemMap.get("No Ring 1");
|
||||
this.ring1 = expandItem(ring, []);
|
||||
errors.push(new ItemNotFound(equipment[4], "ring1", true, "ring"));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[5]) && itemMap.get(equipment[5]).type === "ring") {
|
||||
const ring = itemMap.get(equipment[5]);
|
||||
this.ring2 = expandItem(ring, []);
|
||||
}else{
|
||||
try {
|
||||
let ring = getCustomFromHash(equipment[5]) ? getCustomFromHash(equipment[5]) : (getCraftFromHash(equipment[5]) ? getCraftFromHash(equipment[5]) : undefined);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring2 = ring.statMap;
|
||||
if (this.ring2.get("custom")) {
|
||||
this.customItems.push(ring);
|
||||
} else if (this.ring2.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(ring);
|
||||
}
|
||||
} catch (Error) {
|
||||
const ring = itemMap.get("No Ring 2");
|
||||
this.ring2 = expandItem(ring, []);
|
||||
errors.push(new ItemNotFound(equipment[5], "ring2", true, "ring"));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[6]) && itemMap.get(equipment[6]).type === "bracelet") {
|
||||
const bracelet = itemMap.get(equipment[6]);
|
||||
this.bracelet = expandItem(bracelet, []);
|
||||
}else{
|
||||
try {
|
||||
let bracelet = getCustomFromHash(equipment[6]) ? getCustomFromHash(equipment[6]) : (getCraftFromHash(equipment[6]) ? getCraftFromHash(equipment[6]) : undefined);
|
||||
if (bracelet.statMap.get("type") !== "bracelet") {
|
||||
throw new Error("Not a bracelet");
|
||||
}
|
||||
this.bracelet = bracelet.statMap;
|
||||
if (this.bracelet.get("custom")) {
|
||||
this.customItems.push(bracelet);
|
||||
} else if (this.bracelet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(bracelet);
|
||||
}
|
||||
} catch (Error) {
|
||||
const bracelet = itemMap.get("No Bracelet");
|
||||
this.bracelet = expandItem(bracelet, []);
|
||||
errors.push(new ItemNotFound(equipment[6], "bracelet", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[7]) && itemMap.get(equipment[7]).type === "necklace") {
|
||||
const necklace = itemMap.get(equipment[7]);
|
||||
this.necklace = expandItem(necklace, []);
|
||||
}else{
|
||||
try {
|
||||
let necklace = getCustomFromHash(equipment[7]) ? getCustomFromHash(equipment[7]) : (getCraftFromHash(equipment[7]) ? getCraftFromHash(equipment[7]) : undefined);
|
||||
if (necklace.statMap.get("type") !== "necklace") {
|
||||
throw new Error("Not a necklace");
|
||||
}
|
||||
this.necklace = necklace.statMap;
|
||||
if (this.necklace.get("custom")) {
|
||||
this.customItems.push(necklace);
|
||||
} else if (this.necklace.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(necklace);
|
||||
}
|
||||
} catch (Error) {
|
||||
const necklace = itemMap.get("No Necklace");
|
||||
this.necklace = expandItem(necklace, []);
|
||||
errors.push(new ItemNotFound(equipment[7], "necklace", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[8]) && itemMap.get(equipment[8]).category === "weapon") {
|
||||
const weapon = itemMap.get(equipment[8]);
|
||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
||||
this.weapon = expandItem(weapon, this.powders[4]);
|
||||
if (equipment[8] !== "No Weapon") {
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
||||
} else {
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
||||
}
|
||||
}else{
|
||||
try {
|
||||
let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined);
|
||||
if (weapon.statMap.get("category") !== "weapon") {
|
||||
throw new Error("Not a weapon");
|
||||
}
|
||||
this.weapon = weapon.statMap;
|
||||
if (this.weapon.get("custom")) {
|
||||
this.customItems.push(weapon);
|
||||
} else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(weapon);
|
||||
}
|
||||
this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots"));
|
||||
this.weapon.set("powders",this.powders[4].slice());
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
||||
} catch (Error) {
|
||||
const weapon = itemMap.get("No Weapon");
|
||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
||||
this.weapon = expandItem(weapon, this.powders[4]);
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
||||
errors.push(new ItemNotFound(equipment[8], "weapon", true));
|
||||
}
|
||||
}
|
||||
//console.log(this.craftedItems)
|
||||
constructor(level, items, weapon){
|
||||
|
||||
if (level < 1) { //Should these be constants?
|
||||
this.level = 1;
|
||||
|
@ -348,11 +122,13 @@ class Build{
|
|||
document.getElementById("level-choice").value = this.level;
|
||||
|
||||
this.availableSkillpoints = levelToSkillPoints(this.level);
|
||||
this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ];
|
||||
this.equipment = items;
|
||||
this.weapon = weapon;
|
||||
this.items = this.equipment.concat([this.weapon]);
|
||||
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
||||
let result = calculate_skillpoints(this.equipment, this.weapon);
|
||||
console.log(result);
|
||||
|
||||
// calc skillpoints requires statmaps only
|
||||
let result = calculate_skillpoints(this.equipment.map((x) => x.statMap), this.weapon.statMap);
|
||||
this.equip_order = result[0];
|
||||
// How many skillpoints the player had to assign (5 number)
|
||||
this.base_skillpoints = result[1];
|
||||
|
@ -361,22 +137,8 @@ class Build{
|
|||
// How many skillpoints assigned (1 number, sum of base_skillpoints)
|
||||
this.assigned_skillpoints = result[3];
|
||||
this.activeSetCounts = result[4];
|
||||
|
||||
// For strength boosts like warscream, vanish, etc.
|
||||
this.damageMultiplier = 1.0;
|
||||
this.defenseMultiplier = 1.0;
|
||||
|
||||
// For other external boosts ;-;
|
||||
this.externalStats = externalStats;
|
||||
|
||||
this.initBuildStats();
|
||||
|
||||
// Remove every error before adding specific ones
|
||||
for (let i of document.getElementsByClassName("error")) {
|
||||
i.textContent = "";
|
||||
}
|
||||
this.errors = errors;
|
||||
if (errors.length > 0) this.errored = true;
|
||||
}
|
||||
|
||||
/*Returns build in string format
|
||||
|
@ -385,138 +147,62 @@ class Build{
|
|||
return [this.equipment,this.weapon].flat();
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
|
||||
getSpellCost(spellIdx, cost) {
|
||||
return Math.max(1, this.getBaseSpellCost(spellIdx, cost));
|
||||
}
|
||||
|
||||
getBaseSpellCost(spellIdx, cost) {
|
||||
cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
|
||||
cost += this.statMap.get("spRaw"+spellIdx);
|
||||
return Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100));
|
||||
}
|
||||
|
||||
|
||||
/* Get melee stats for build.
|
||||
Returns an array in the order:
|
||||
*/
|
||||
getMeleeStats(){
|
||||
const stats = this.statMap;
|
||||
if (this.weapon.get("tier") === "Crafted") {
|
||||
stats.set("damageBases", [this.weapon.get("nDamBaseHigh"),this.weapon.get("eDamBaseHigh"),this.weapon.get("tDamBaseHigh"),this.weapon.get("wDamBaseHigh"),this.weapon.get("fDamBaseHigh"),this.weapon.get("aDamBaseHigh")]);
|
||||
}
|
||||
let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier");
|
||||
if(adjAtkSpd > 6){
|
||||
adjAtkSpd = 6;
|
||||
}else if(adjAtkSpd < 0){
|
||||
adjAtkSpd = 0;
|
||||
}
|
||||
|
||||
let damage_mult = 1;
|
||||
if (this.weapon.get("type") === "relik") {
|
||||
damage_mult = 0.99; // CURSE YOU WYNNCRAFT
|
||||
//One day we will create WynnWynn and no longer have shaman 99% melee injustice.
|
||||
//In all seriousness 99% is because wynn uses 0.33 to estimate dividing the damage by 3 to split damage between 3 beams.
|
||||
}
|
||||
// 0spellmult for melee damage.
|
||||
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct") + this.externalStats.get("mdPct"), 0, this.weapon, this.total_skillpoints, damage_mult * this.damageMultiplier, this.externalStats);
|
||||
|
||||
let dex = this.total_skillpoints[1];
|
||||
|
||||
let totalDamNorm = results[0];
|
||||
let totalDamCrit = results[1];
|
||||
totalDamNorm.push(1-skillPointsToPercentage(dex));
|
||||
totalDamCrit.push(skillPointsToPercentage(dex));
|
||||
let damages_results = results[2];
|
||||
|
||||
let singleHitTotal = ((totalDamNorm[0]+totalDamNorm[1])*(totalDamNorm[2])
|
||||
+(totalDamCrit[0]+totalDamCrit[1])*(totalDamCrit[2]))/2;
|
||||
|
||||
//Now do math
|
||||
let normDPS = (totalDamNorm[0]+totalDamNorm[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
||||
let critDPS = (totalDamCrit[0]+totalDamCrit[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
||||
let avgDPS = (normDPS * (1 - skillPointsToPercentage(dex))) + (critDPS * (skillPointsToPercentage(dex)));
|
||||
//[[n n n n] [e e e e] [t t t t] [w w w w] [f f f f] [a a a a] [lowtotal hightotal normalChance] [critlowtotal crithightotal critChance] normalDPS critCPS averageDPS adjAttackSpeed, singleHit]
|
||||
return damages_results.concat([totalDamNorm,totalDamCrit,normDPS,critDPS,avgDPS,adjAtkSpd, singleHitTotal]).concat(results[3]);
|
||||
}
|
||||
|
||||
/*
|
||||
Get all defensive stats for this build.
|
||||
*/
|
||||
getDefenseStats(){
|
||||
const stats = this.statMap;
|
||||
let defenseStats = [];
|
||||
let def_pct = skillPointsToPercentage(this.total_skillpoints[3]);
|
||||
let agi_pct = skillPointsToPercentage(this.total_skillpoints[4]);
|
||||
//total hp
|
||||
let totalHp = stats.get("hp") + stats.get("hpBonus");
|
||||
if (totalHp < 5) totalHp = 5;
|
||||
defenseStats.push(totalHp);
|
||||
//EHP
|
||||
let ehp = [totalHp, totalHp];
|
||||
let defMult = classDefenseMultipliers.get(this.weapon.get("type"));
|
||||
ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
defenseStats.push(ehp);
|
||||
//HPR
|
||||
let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.);
|
||||
defenseStats.push(totalHpr);
|
||||
//EHPR
|
||||
let ehpr = [totalHpr, totalHpr];
|
||||
ehpr[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
ehpr[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
defenseStats.push(ehpr);
|
||||
//skp stats
|
||||
defenseStats.push([ (1 - ((1-def_pct) * (2 - this.defenseMultiplier)))*100, agi_pct*100]);
|
||||
//eledefs - TODO POWDERS
|
||||
let eledefs = [0, 0, 0, 0, 0];
|
||||
for(const i in skp_elements){ //kinda jank but ok
|
||||
eledefs[i] = rawToPct(stats.get(skp_elements[i] + "Def"), stats.get(skp_elements[i] + "DefPct")/100.);
|
||||
}
|
||||
defenseStats.push(eledefs);
|
||||
|
||||
//[total hp, [ehp w/ agi, ehp w/o agi], total hpr, [ehpr w/ agi, ehpr w/o agi], [def%, agi%], [edef,tdef,wdef,fdef,adef]]
|
||||
return defenseStats;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
initBuildStats(){
|
||||
|
||||
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi"];
|
||||
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "damMobs", "defMobs"];
|
||||
|
||||
let must_ids = [
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw","eDamPct","eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw","tDamPct","tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw","wDamPct","wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw","fDamPct","fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw","aDamPct","aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
"mdPct","mdRaw","sdPct","sdRaw","damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct","rSdRaw","rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
]
|
||||
|
||||
//Create a map of this build's stats
|
||||
let statMap = new Map();
|
||||
statMap.set("defMultiplier", 1);
|
||||
|
||||
for (const staticID of staticIDs) {
|
||||
statMap.set(staticID, 0);
|
||||
}
|
||||
for (const staticID of must_ids) {
|
||||
statMap.set(staticID, 0);
|
||||
}
|
||||
statMap.set("hp", levelToHPBase(this.level));
|
||||
|
||||
let major_ids = new Set();
|
||||
for (const item of this.items){
|
||||
for (let [id, value] of item.get("maxRolls")) {
|
||||
const item_stats = item.statMap;
|
||||
for (let [id, value] of item_stats.get("maxRolls")) {
|
||||
if (staticIDs.includes(id)) {
|
||||
continue;
|
||||
}
|
||||
statMap.set(id,(statMap.get(id) || 0)+value);
|
||||
}
|
||||
for (const staticID of staticIDs) {
|
||||
if (item.get(staticID)) {
|
||||
statMap.set(staticID, statMap.get(staticID) + item.get(staticID));
|
||||
if (item_stats.get(staticID)) {
|
||||
statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID));
|
||||
}
|
||||
}
|
||||
if (item.get("majorIds")) {
|
||||
for (const major_id of item.get("majorIds")) {
|
||||
if (item_stats.get("majorIds")) {
|
||||
for (const major_id of item_stats.get("majorIds")) {
|
||||
major_ids.add(major_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100));
|
||||
statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100));
|
||||
statMap.set("activeMajorIDs", major_ids);
|
||||
for (const [setName, count] of this.activeSetCounts) {
|
||||
const bonus = sets[setName].bonuses[count-1];
|
||||
const bonus = sets.get(setName).bonuses[count-1];
|
||||
for (const id in bonus) {
|
||||
if (skp_order.includes(id)) {
|
||||
// pass. Don't include skillpoints in ids
|
||||
|
@ -529,27 +215,8 @@ class Build{
|
|||
statMap.set("poisonPct", 100);
|
||||
|
||||
// The stuff relevant for damage calculation!!! @ferricles
|
||||
statMap.set("atkSpd", this.weapon.get("atkSpd"));
|
||||
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));
|
||||
|
||||
for (const x of skp_elements) {
|
||||
this.externalStats.set(x + "DamPct", 0);
|
||||
}
|
||||
this.externalStats.set("mdPct", 0);
|
||||
this.externalStats.set("sdPct", 0);
|
||||
this.externalStats.set("damageBonus", [0, 0, 0, 0, 0]);
|
||||
this.externalStats.set("defBonus",[0, 0, 0, 0, 0]);
|
||||
this.externalStats.set("poisonPct", 0);
|
||||
this.statMap = statMap;
|
||||
|
||||
this.aggregateStats();
|
||||
}
|
||||
|
||||
aggregateStats() {
|
||||
let statMap = this.statMap;
|
||||
statMap.set("damageRaw", [this.weapon.get("nDam"), this.weapon.get("eDam"), this.weapon.get("tDam"), this.weapon.get("wDam"), this.weapon.get("fDam"), this.weapon.get("aDam")]);
|
||||
statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
|
||||
statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]);
|
||||
statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]);
|
||||
statMap.set("defMult", classDefenseMultipliers.get(this.weapon.get("type")));
|
||||
}
|
||||
}
|
||||
|
|
550
js/build2.js
|
@ -1,550 +0,0 @@
|
|||
|
||||
|
||||
const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.20] ]);
|
||||
|
||||
/**
|
||||
* @description Error to catch items that don't exist.
|
||||
* @module ItemNotFound
|
||||
*/
|
||||
class ItemNotFound {
|
||||
/**
|
||||
* @class
|
||||
* @param {String} item the item name entered
|
||||
* @param {String} type the type of item
|
||||
* @param {Boolean} genElement whether to generate an element from inputs
|
||||
* @param {String} override override for item type
|
||||
*/
|
||||
constructor(item, type, genElement, override) {
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.message = `Cannot find ${override||type} named ${item}`;
|
||||
if (genElement)
|
||||
/**
|
||||
* @public
|
||||
* @type {Element}
|
||||
*/
|
||||
this.element = document.getElementById(`${type}-choice`).parentElement.querySelectorAll("p.error")[0];
|
||||
else
|
||||
this.element = document.createElement("div");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Error to catch incorrect input.
|
||||
* @module IncorrectInput
|
||||
*/
|
||||
class IncorrectInput {
|
||||
/**
|
||||
* @class
|
||||
* @param {String} input the inputted text
|
||||
* @param {String} format the correct format
|
||||
* @param {String} sibling the id of the error node's sibling
|
||||
*/
|
||||
constructor(input, format, sibling) {
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.message = `${input} is incorrect. Example: ${format}`;
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.id = sibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Error that inputs an array of items to generate errors of.
|
||||
* @module ListError
|
||||
* @extends Error
|
||||
*/
|
||||
class ListError extends Error {
|
||||
/**
|
||||
* @class
|
||||
* @param {Array} errors array of errors
|
||||
*/
|
||||
constructor(errors) {
|
||||
let ret = [];
|
||||
if (typeof errors[0] == "string") {
|
||||
super(errors[0]);
|
||||
} else {
|
||||
super(errors[0].message);
|
||||
}
|
||||
for (let i of errors) {
|
||||
if (typeof i == "string") {
|
||||
ret.push(new Error(i));
|
||||
} else {
|
||||
ret.push(i);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
* @type {Object[]}
|
||||
*/
|
||||
this.errors = ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*Class that represents a wynn player's build.
|
||||
*/
|
||||
class 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.
|
||||
*/
|
||||
constructor(level,equipment, powders, externalStats, inputerrors=[]){
|
||||
|
||||
let errors = inputerrors;
|
||||
//this contains the Craft objects, if there are any crafted items. this.boots, etc. will contain the statMap of the Craft (which is built to be an expandedItem).
|
||||
this.craftedItems = [];
|
||||
this.customItems = [];
|
||||
// NOTE: powders is just an array of arrays of powder IDs. Not powder objects.
|
||||
this.powders = powders;
|
||||
if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") {
|
||||
const helmet = itemMap.get(equipment[0]);
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
} else {
|
||||
try {
|
||||
//let boots = getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : (getCustomFromHash(equipment[0])? getCustomFromHash(equipment[0]) : undefined);
|
||||
let helmet = getCustomFromHash(equipment[0]) ? getCustomFromHash(equipment[0]) : (getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : undefined);
|
||||
if (helmet.statMap.get("type") !== "helmet") {
|
||||
throw new Error("Not a helmet");
|
||||
}
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.statMap.get("slots"));
|
||||
helmet.statMap.set("powders",this.powders[0].slice());
|
||||
helmet.applyPowders();
|
||||
this.helmet = helmet.statMap;
|
||||
if (this.helmet.get("custom")) {
|
||||
this.customItems.push(helmet);
|
||||
} else if (this.helmet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(helmet);
|
||||
}
|
||||
|
||||
} catch (Error) {
|
||||
//console.log(Error); //fix
|
||||
const helmet = itemMap.get("No Helmet");
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
errors.push(new ItemNotFound(equipment[0], "helmet", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[1]) && itemMap.get(equipment[1]).type === "chestplate") {
|
||||
const chestplate = itemMap.get(equipment[1]);
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
} else {
|
||||
try {
|
||||
let chestplate = getCustomFromHash(equipment[1]) ? getCustomFromHash(equipment[1]) : (getCraftFromHash(equipment[1]) ? getCraftFromHash(equipment[1]) : undefined);
|
||||
if (chestplate.statMap.get("type") !== "chestplate") {
|
||||
throw new Error("Not a chestplate");
|
||||
}
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.statMap.get("slots"));
|
||||
chestplate.statMap.set("powders",this.powders[1].slice());
|
||||
chestplate.applyPowders();
|
||||
this.chestplate = chestplate.statMap;
|
||||
if (this.chestplate.get("custom")) {
|
||||
this.customItems.push(chestplate);
|
||||
} else if (this.chestplate.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(chestplate);
|
||||
}
|
||||
} catch (Error) {
|
||||
const chestplate = itemMap.get("No Chestplate");
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
errors.push(new ItemNotFound(equipment[1], "chestplate", true));
|
||||
}
|
||||
}
|
||||
if (itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") {
|
||||
const leggings = itemMap.get(equipment[2]);
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
} else {
|
||||
try {
|
||||
let leggings = getCustomFromHash(equipment[2]) ? getCustomFromHash(equipment[2]) : (getCraftFromHash(equipment[2]) ? getCraftFromHash(equipment[2]) : undefined);
|
||||
if (leggings.statMap.get("type") !== "leggings") {
|
||||
throw new Error("Not a leggings");
|
||||
}
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.statMap.get("slots"));
|
||||
leggings.statMap.set("powders",this.powders[2].slice());
|
||||
leggings.applyPowders();
|
||||
this.leggings = leggings.statMap;
|
||||
if (this.leggings.get("custom")) {
|
||||
this.customItems.push(leggings);
|
||||
} else if (this.leggings.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(leggings);
|
||||
}
|
||||
} catch (Error) {
|
||||
const leggings = itemMap.get("No Leggings");
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
errors.push(new ItemNotFound(equipment[2], "leggings", true));
|
||||
}
|
||||
}
|
||||
if (itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") {
|
||||
const boots = itemMap.get(equipment[3]);
|
||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
||||
this.boots = expandItem(boots, this.powders[3]);
|
||||
} else {
|
||||
try {
|
||||
let boots = getCustomFromHash(equipment[3]) ? getCustomFromHash(equipment[3]) : (getCraftFromHash(equipment[3]) ? getCraftFromHash(equipment[3]) : undefined);
|
||||
if (boots.statMap.get("type") !== "boots") {
|
||||
throw new Error("Not a boots");
|
||||
}
|
||||
this.powders[3] = this.powders[3].slice(0,boots.statMap.get("slots"));
|
||||
boots.statMap.set("powders",this.powders[3].slice());
|
||||
boots.applyPowders();
|
||||
this.boots = boots.statMap;
|
||||
console.log(boots);
|
||||
if (this.boots.get("custom")) {
|
||||
this.customItems.push(boots);
|
||||
} else if (this.boots.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(boots);
|
||||
}
|
||||
} catch (Error) {
|
||||
const boots = itemMap.get("No Boots");
|
||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
||||
this.boots = expandItem(boots, this.powders[3]);
|
||||
errors.push(new ItemNotFound(equipment[3], "boots", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[4]) && itemMap.get(equipment[4]).type === "ring") {
|
||||
const ring = itemMap.get(equipment[4]);
|
||||
this.ring1 = expandItem(ring, []);
|
||||
}else{
|
||||
try {
|
||||
let ring = getCustomFromHash(equipment[4]) ? getCustomFromHash(equipment[4]) : (getCraftFromHash(equipment[4]) ? getCraftFromHash(equipment[4]) : undefined);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring1 = ring.statMap;
|
||||
if (this.ring1.get("custom")) {
|
||||
this.customItems.push(ring);
|
||||
} else if (this.ring1.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(ring);
|
||||
}
|
||||
} catch (Error) {
|
||||
const ring = itemMap.get("No Ring 1");
|
||||
this.ring1 = expandItem(ring, []);
|
||||
errors.push(new ItemNotFound(equipment[4], "ring1", true, "ring"));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[5]) && itemMap.get(equipment[5]).type === "ring") {
|
||||
const ring = itemMap.get(equipment[5]);
|
||||
this.ring2 = expandItem(ring, []);
|
||||
}else{
|
||||
try {
|
||||
let ring = getCustomFromHash(equipment[5]) ? getCustomFromHash(equipment[5]) : (getCraftFromHash(equipment[5]) ? getCraftFromHash(equipment[5]) : undefined);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring2 = ring.statMap;
|
||||
if (this.ring2.get("custom")) {
|
||||
this.customItems.push(ring);
|
||||
} else if (this.ring2.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(ring);
|
||||
}
|
||||
} catch (Error) {
|
||||
const ring = itemMap.get("No Ring 2");
|
||||
this.ring2 = expandItem(ring, []);
|
||||
errors.push(new ItemNotFound(equipment[5], "ring2", true, "ring"));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[6]) && itemMap.get(equipment[6]).type === "bracelet") {
|
||||
const bracelet = itemMap.get(equipment[6]);
|
||||
this.bracelet = expandItem(bracelet, []);
|
||||
}else{
|
||||
try {
|
||||
let bracelet = getCustomFromHash(equipment[6]) ? getCustomFromHash(equipment[6]) : (getCraftFromHash(equipment[6]) ? getCraftFromHash(equipment[6]) : undefined);
|
||||
if (bracelet.statMap.get("type") !== "bracelet") {
|
||||
throw new Error("Not a bracelet");
|
||||
}
|
||||
this.bracelet = bracelet.statMap;
|
||||
if (this.bracelet.get("custom")) {
|
||||
this.customItems.push(bracelet);
|
||||
} else if (this.bracelet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(bracelet);
|
||||
}
|
||||
} catch (Error) {
|
||||
const bracelet = itemMap.get("No Bracelet");
|
||||
this.bracelet = expandItem(bracelet, []);
|
||||
errors.push(new ItemNotFound(equipment[6], "bracelet", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[7]) && itemMap.get(equipment[7]).type === "necklace") {
|
||||
const necklace = itemMap.get(equipment[7]);
|
||||
this.necklace = expandItem(necklace, []);
|
||||
}else{
|
||||
try {
|
||||
let necklace = getCustomFromHash(equipment[7]) ? getCustomFromHash(equipment[7]) : (getCraftFromHash(equipment[7]) ? getCraftFromHash(equipment[7]) : undefined);
|
||||
if (necklace.statMap.get("type") !== "necklace") {
|
||||
throw new Error("Not a necklace");
|
||||
}
|
||||
this.necklace = necklace.statMap;
|
||||
if (this.necklace.get("custom")) {
|
||||
this.customItems.push(necklace);
|
||||
} else if (this.necklace.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(necklace);
|
||||
}
|
||||
} catch (Error) {
|
||||
const necklace = itemMap.get("No Necklace");
|
||||
this.necklace = expandItem(necklace, []);
|
||||
errors.push(new ItemNotFound(equipment[7], "necklace", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[8]) && itemMap.get(equipment[8]).category === "weapon") {
|
||||
const weapon = itemMap.get(equipment[8]);
|
||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
||||
this.weapon = expandItem(weapon, this.powders[4]);
|
||||
if (equipment[8] !== "No Weapon") {
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
||||
} else {
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
||||
}
|
||||
}else{
|
||||
try {
|
||||
let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined);
|
||||
if (weapon.statMap.get("category") !== "weapon") {
|
||||
throw new Error("Not a weapon");
|
||||
}
|
||||
this.weapon = weapon.statMap;
|
||||
if (this.weapon.get("custom")) {
|
||||
this.customItems.push(weapon);
|
||||
} else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(weapon);
|
||||
}
|
||||
this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots"));
|
||||
this.weapon.set("powders",this.powders[4].slice());
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
||||
} catch (Error) {
|
||||
const weapon = itemMap.get("No Weapon");
|
||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
||||
this.weapon = expandItem(weapon, this.powders[4]);
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
||||
errors.push(new ItemNotFound(equipment[8], "weapon", true));
|
||||
}
|
||||
}
|
||||
//console.log(this.craftedItems)
|
||||
|
||||
if (level < 1) { //Should these be constants?
|
||||
this.level = 1;
|
||||
} else if (level > 106) {
|
||||
this.level = 106;
|
||||
} else if (level <= 106 && level >= 1) {
|
||||
this.level = level;
|
||||
} else if (typeof level === "string") {
|
||||
this.level = level;
|
||||
errors.push(new IncorrectInput(level, "a number", "level-choice"));
|
||||
} else {
|
||||
errors.push("Level is not a string or number.");
|
||||
}
|
||||
document.getElementById("level-choice").value = this.level;
|
||||
|
||||
this.availableSkillpoints = levelToSkillPoints(this.level);
|
||||
this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ];
|
||||
this.items = this.equipment.concat([this.weapon]);
|
||||
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
||||
let result = calculate_skillpoints(this.equipment, this.weapon);
|
||||
console.log(result);
|
||||
this.equip_order = result[0];
|
||||
this.base_skillpoints = result[1];
|
||||
this.total_skillpoints = result[2];
|
||||
this.assigned_skillpoints = result[3];
|
||||
this.activeSetCounts = result[4];
|
||||
|
||||
// For strength boosts like warscream, vanish, etc.
|
||||
this.damageMultiplier = 1.0;
|
||||
this.defenseMultiplier = 1.0;
|
||||
|
||||
// For other external boosts ;-;
|
||||
this.externalStats = externalStats;
|
||||
|
||||
this.initBuildStats();
|
||||
|
||||
// Remove every error before adding specific ones
|
||||
for (let i of document.getElementsByClassName("error")) {
|
||||
i.textContent = "";
|
||||
}
|
||||
this.errors = errors;
|
||||
if (errors.length > 0) this.errored = true;
|
||||
}
|
||||
|
||||
/*Returns build in string format
|
||||
*/
|
||||
toString(){
|
||||
return [this.equipment,this.weapon].flat();
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
|
||||
/* Get total health for build.
|
||||
*/
|
||||
|
||||
getSpellCost(spellIdx, cost) {
|
||||
cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
|
||||
cost = Math.max(0, Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100)));
|
||||
return Math.max(1, cost + this.statMap.get("spRaw"+spellIdx));
|
||||
}
|
||||
|
||||
|
||||
/* Get melee stats for build.
|
||||
Returns an array in the order:
|
||||
*/
|
||||
getMeleeStats(){
|
||||
const stats = this.statMap;
|
||||
if (this.weapon.get("tier") === "Crafted") {
|
||||
stats.set("damageBases", [this.weapon.get("nDamBaseHigh"),this.weapon.get("eDamBaseHigh"),this.weapon.get("tDamBaseHigh"),this.weapon.get("wDamBaseHigh"),this.weapon.get("fDamBaseHigh"),this.weapon.get("aDamBaseHigh")]);
|
||||
}
|
||||
let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier");
|
||||
if(adjAtkSpd > 6){
|
||||
adjAtkSpd = 6;
|
||||
}else if(adjAtkSpd < 0){
|
||||
adjAtkSpd = 0;
|
||||
}
|
||||
|
||||
let damage_mult = 1;
|
||||
if (this.weapon.get("type") === "relik") {
|
||||
damage_mult = 0.99; // CURSE YOU WYNNCRAFT
|
||||
//One day we will create WynnWynn and no longer have shaman 99% melee injustice.
|
||||
//In all seriousness 99% is because wynn uses 0.33 to estimate dividing the damage by 3 to split damage between 3 beams.
|
||||
}
|
||||
// 0spellmult for melee damage.
|
||||
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct") + this.externalStats.get("mdPct"), 0, this.weapon, this.total_skillpoints, damage_mult * this.damageMultiplier, this.externalStats);
|
||||
|
||||
let dex = this.total_skillpoints[1];
|
||||
|
||||
let totalDamNorm = results[0];
|
||||
let totalDamCrit = results[1];
|
||||
totalDamNorm.push(1-skillPointsToPercentage(dex));
|
||||
totalDamCrit.push(skillPointsToPercentage(dex));
|
||||
let damages_results = results[2];
|
||||
|
||||
let singleHitTotal = ((totalDamNorm[0]+totalDamNorm[1])*(totalDamNorm[2])
|
||||
+(totalDamCrit[0]+totalDamCrit[1])*(totalDamCrit[2]))/2;
|
||||
|
||||
//Now do math
|
||||
let normDPS = (totalDamNorm[0]+totalDamNorm[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
||||
let critDPS = (totalDamCrit[0]+totalDamCrit[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
||||
let avgDPS = (normDPS * (1 - skillPointsToPercentage(dex))) + (critDPS * (skillPointsToPercentage(dex)));
|
||||
//[[n n n n] [e e e e] [t t t t] [w w w w] [f f f f] [a a a a] [lowtotal hightotal normalChance] [critlowtotal crithightotal critChance] normalDPS critCPS averageDPS adjAttackSpeed, singleHit]
|
||||
return damages_results.concat([totalDamNorm,totalDamCrit,normDPS,critDPS,avgDPS,adjAtkSpd, singleHitTotal]).concat(results[3]);
|
||||
}
|
||||
|
||||
/*
|
||||
Get all defensive stats for this build.
|
||||
*/
|
||||
getDefenseStats(){
|
||||
const stats = this.statMap;
|
||||
let defenseStats = [];
|
||||
let def_pct = skillPointsToPercentage(this.total_skillpoints[3]);
|
||||
let agi_pct = skillPointsToPercentage(this.total_skillpoints[4]);
|
||||
//total hp
|
||||
let totalHp = stats.get("hp") + stats.get("hpBonus");
|
||||
if (totalHp < 5) totalHp = 5;
|
||||
defenseStats.push(totalHp);
|
||||
//EHP
|
||||
let ehp = [totalHp, totalHp];
|
||||
let defMult = classDefenseMultipliers.get(this.weapon.get("type"));
|
||||
ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
defenseStats.push(ehp);
|
||||
//HPR
|
||||
let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.);
|
||||
defenseStats.push(totalHpr);
|
||||
//EHPR
|
||||
let ehpr = [totalHpr, totalHpr];
|
||||
ehpr[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
ehpr[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
defenseStats.push(ehpr);
|
||||
//skp stats
|
||||
defenseStats.push([ (1 - ((1-def_pct) * (2 - this.defenseMultiplier)))*100, agi_pct*100]);
|
||||
//eledefs - TODO POWDERS
|
||||
let eledefs = [0, 0, 0, 0, 0];
|
||||
for(const i in skp_elements){ //kinda jank but ok
|
||||
eledefs[i] = rawToPct(stats.get(skp_elements[i] + "Def"), stats.get(skp_elements[i] + "DefPct")/100.);
|
||||
}
|
||||
defenseStats.push(eledefs);
|
||||
|
||||
//[total hp, [ehp w/ agi, ehp w/o agi], total hpr, [ehpr w/ agi, ehpr w/o agi], [def%, agi%], [edef,tdef,wdef,fdef,adef]]
|
||||
return defenseStats;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
initBuildStats(){
|
||||
|
||||
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef"];
|
||||
|
||||
//Create a map of this build's stats
|
||||
let statMap = new Map();
|
||||
|
||||
for (const staticID of staticIDs) {
|
||||
statMap.set(staticID, 0);
|
||||
}
|
||||
statMap.set("hp", levelToHPBase(this.level));
|
||||
|
||||
let major_ids = new Set();
|
||||
for (const item of this.items){
|
||||
for (let [id, value] of item.get("maxRolls")) {
|
||||
statMap.set(id,(statMap.get(id) || 0)+value);
|
||||
}
|
||||
for (const staticID of staticIDs) {
|
||||
if (item.get(staticID)) {
|
||||
statMap.set(staticID, statMap.get(staticID) + item.get(staticID));
|
||||
}
|
||||
}
|
||||
if (item.get("majorIds")) {
|
||||
for (const majorID of item.get("majorIds")) {
|
||||
major_ids.add(majorID);
|
||||
}
|
||||
}
|
||||
}
|
||||
statMap.set("activeMajorIDs", major_ids);
|
||||
for (const [setName, count] of this.activeSetCounts) {
|
||||
const bonus = sets[setName].bonuses[count-1];
|
||||
for (const id in bonus) {
|
||||
if (skp_order.includes(id)) {
|
||||
// pass. Don't include skillpoints in ids
|
||||
}
|
||||
else {
|
||||
statMap.set(id,(statMap.get(id) || 0)+bonus[id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
statMap.set("poisonPct", 100);
|
||||
|
||||
// The stuff relevant for damage calculation!!! @ferricles
|
||||
statMap.set("atkSpd", this.weapon.get("atkSpd"));
|
||||
|
||||
for (const x of skp_elements) {
|
||||
this.externalStats.set(x + "DamPct", 0);
|
||||
}
|
||||
this.externalStats.set("mdPct", 0);
|
||||
this.externalStats.set("sdPct", 0);
|
||||
this.externalStats.set("damageBonus", [0, 0, 0, 0, 0]);
|
||||
this.externalStats.set("defBonus",[0, 0, 0, 0, 0]);
|
||||
this.externalStats.set("poisonPct", 0);
|
||||
this.statMap = statMap;
|
||||
|
||||
this.aggregateStats();
|
||||
}
|
||||
|
||||
aggregateStats() {
|
||||
let statMap = this.statMap;
|
||||
statMap.set("damageRaw", [this.weapon.get("nDam"), this.weapon.get("eDam"), this.weapon.get("tDam"), this.weapon.get("wDam"), this.weapon.get("fDam"), this.weapon.get("aDam")]);
|
||||
statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
|
||||
statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]);
|
||||
statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]);
|
||||
statMap.set("defMult", classDefenseMultipliers.get(this.weapon.get("type")));
|
||||
}
|
||||
}
|
107
js/build_constants.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* I kinda lied. Theres some listener stuff in here
|
||||
* but its mostly constants for builder page specifically.
|
||||
*/
|
||||
|
||||
const url_tag = location.hash.slice(1);
|
||||
|
||||
const BUILD_VERSION = "7.0.19";
|
||||
|
||||
let player_build;
|
||||
|
||||
|
||||
// THIS IS SUPER DANGEROUS, WE SHOULD NOT BE KEEPING THIS IN SO MANY PLACES
|
||||
let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison",
|
||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
||||
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
|
||||
"hprRaw", "hprPct", "hpBonus", "atkTier",
|
||||
"spPct1", "spRaw1", "spPct2", "spRaw2",
|
||||
"spPct3", "spRaw3", "spPct4", "spRaw4" ];
|
||||
|
||||
let editable_elems = [];
|
||||
|
||||
for (let i of editable_item_fields) {
|
||||
let elem = document.getElementById(i);
|
||||
elem.addEventListener("change", (event) => {
|
||||
elem.classList.add("highlight");
|
||||
});
|
||||
editable_elems.push(elem);
|
||||
}
|
||||
|
||||
for (let i of skp_order) {
|
||||
let elem = document.getElementById(i+"-skp");
|
||||
elem.addEventListener("change", (event) => {
|
||||
elem.classList.add("highlight");
|
||||
});
|
||||
editable_elems.push(elem);
|
||||
}
|
||||
|
||||
function clear_highlights() {
|
||||
for (let i of editable_elems) {
|
||||
i.classList.remove("highlight");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let equipment_fields = [
|
||||
"helmet",
|
||||
"chestplate",
|
||||
"leggings",
|
||||
"boots",
|
||||
"ring1",
|
||||
"ring2",
|
||||
"bracelet",
|
||||
"necklace",
|
||||
"weapon"
|
||||
];
|
||||
let tome_fields = [
|
||||
"weaponTome1",
|
||||
"weaponTome2",
|
||||
"armorTome1",
|
||||
"armorTome2",
|
||||
"armorTome3",
|
||||
"armorTome4",
|
||||
"guildTome1",
|
||||
]
|
||||
let equipment_names = [
|
||||
"Helmet",
|
||||
"Chestplate",
|
||||
"Leggings",
|
||||
"Boots",
|
||||
"Ring 1",
|
||||
"Ring 2",
|
||||
"Bracelet",
|
||||
"Necklace",
|
||||
"Weapon"
|
||||
];
|
||||
|
||||
let tome_names = [
|
||||
"Weapon Tome",
|
||||
"Weapon Tome",
|
||||
"Armor Tome",
|
||||
"Armor Tome",
|
||||
"Armor Tome",
|
||||
"Armor Tome",
|
||||
"Guild Tome",
|
||||
]
|
||||
let equipment_inputs = equipment_fields.map(x => x + "-choice");
|
||||
let build_fields = equipment_fields.map(x => x+"-tooltip");
|
||||
let tomeInputs = tome_fields.map(x => x + "-choice");
|
||||
|
||||
let powder_inputs = [
|
||||
"helmet-powder",
|
||||
"chestplate-powder",
|
||||
"leggings-powder",
|
||||
"boots-powder",
|
||||
"weapon-powder",
|
||||
];
|
||||
|
||||
let weapon_keys = ['dagger', 'wand', 'bow', 'relik', 'spear'];
|
||||
let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots'];
|
||||
let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace'];
|
||||
let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'weapon'];
|
||||
let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon'];
|
||||
let tome_keys = ['weaponTome1', 'weaponTome2', 'armorTome1', 'armorTome2', 'armorTome3', 'armorTome4', 'guildTome1'];
|
||||
|
||||
let spell_disp = ['build-melee-stats', 'spell0-info', 'spell1-info', 'spell2-info', 'spell3-info'];
|
||||
let other_disp = ['build-order', 'set-info', 'int-info'];
|
290
js/build_encode_decode.js
Normal file
|
@ -0,0 +1,290 @@
|
|||
function parsePowdering(powder_info) {
|
||||
// TODO: Make this run in linear instead of quadratic time... ew
|
||||
let powdering = [];
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let powders = "";
|
||||
let n_blocks = Base64.toInt(powder_info.charAt(0));
|
||||
// console.log(n_blocks + " blocks");
|
||||
powder_info = powder_info.slice(1);
|
||||
for (let j = 0; j < n_blocks; ++j) {
|
||||
let block = powder_info.slice(0,5);
|
||||
let six_powders = Base64.toInt(block);
|
||||
for (let k = 0; k < 6 && six_powders != 0; ++k) {
|
||||
powders += powderNames.get((six_powders & 0x1f) - 1);
|
||||
six_powders >>>= 5;
|
||||
}
|
||||
powder_info = powder_info.slice(5);
|
||||
}
|
||||
powdering[i] = powders;
|
||||
}
|
||||
return [powdering, powder_info];
|
||||
}
|
||||
|
||||
let atree_data = null;
|
||||
|
||||
/*
|
||||
* Populate fields based on url, and calculate build.
|
||||
*/
|
||||
function decodeBuild(url_tag) {
|
||||
if (url_tag) {
|
||||
//default values
|
||||
let equipment = [null, null, null, null, null, null, null, null, null];
|
||||
let tomes = [null, null, null, null, null, null, null];
|
||||
let powdering = ["", "", "", "", ""];
|
||||
let info = url_tag.split("_");
|
||||
let version = info[0];
|
||||
let save_skp = false;
|
||||
let skillpoints = [0, 0, 0, 0, 0];
|
||||
let level = 106;
|
||||
|
||||
let version_number = parseInt(version)
|
||||
//equipment (items)
|
||||
// TODO: use filters
|
||||
if (version_number < 4) {
|
||||
let equipments = info[1];
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
let equipment_str = equipments.slice(i*3,i*3+3);
|
||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||
}
|
||||
info[1] = equipments.slice(27);
|
||||
}
|
||||
else if (version_number == 4) {
|
||||
let info_str = info[1];
|
||||
let start_idx = 0;
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
if (info_str.charAt(start_idx) === "-") {
|
||||
equipment[i] = "CR-"+info_str.slice(start_idx+1, start_idx+18);
|
||||
start_idx += 18;
|
||||
}
|
||||
else {
|
||||
let equipment_str = info_str.slice(start_idx, start_idx+3);
|
||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||
start_idx += 3;
|
||||
}
|
||||
}
|
||||
info[1] = info_str.slice(start_idx);
|
||||
}
|
||||
else if (version_number <= 7) {
|
||||
let info_str = info[1];
|
||||
let start_idx = 0;
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
if (info_str.slice(start_idx,start_idx+3) === "CR-") {
|
||||
equipment[i] = info_str.slice(start_idx, start_idx+20);
|
||||
start_idx += 20;
|
||||
} else if (info_str.slice(start_idx+3,start_idx+6) === "CI-") {
|
||||
let len = Base64.toInt(info_str.slice(start_idx,start_idx+3));
|
||||
equipment[i] = info_str.slice(start_idx+3,start_idx+3+len);
|
||||
start_idx += (3+len);
|
||||
} else {
|
||||
let equipment_str = info_str.slice(start_idx, start_idx+3);
|
||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||
start_idx += 3;
|
||||
}
|
||||
}
|
||||
info[1] = info_str.slice(start_idx);
|
||||
}
|
||||
//constant in all versions
|
||||
for (let i in equipment) {
|
||||
setValue(equipment_inputs[i], equipment[i]);
|
||||
}
|
||||
|
||||
//level, skill point assignments, and powdering
|
||||
if (version_number == 1) {
|
||||
let powder_info = info[1];
|
||||
let res = parsePowdering(powder_info);
|
||||
powdering = res[0];
|
||||
} else if (version_number == 2) {
|
||||
save_skp = true;
|
||||
let skillpoint_info = info[1].slice(0, 10);
|
||||
for (let i = 0; i < 5; ++i ) {
|
||||
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
|
||||
}
|
||||
|
||||
let powder_info = info[1].slice(10);
|
||||
let res = parsePowdering(powder_info);
|
||||
powdering = res[0];
|
||||
} else if (version_number <= 7){
|
||||
level = Base64.toInt(info[1].slice(10,12));
|
||||
setValue("level-choice",level);
|
||||
save_skp = true;
|
||||
let skillpoint_info = info[1].slice(0, 10);
|
||||
for (let i = 0; i < 5; ++i ) {
|
||||
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
|
||||
}
|
||||
|
||||
let powder_info = info[1].slice(12);
|
||||
|
||||
let res = parsePowdering(powder_info);
|
||||
powdering = res[0];
|
||||
info[1] = res[1];
|
||||
}
|
||||
// Tomes.
|
||||
if (version >= 6) {
|
||||
//tome values do not appear in anything before v6.
|
||||
for (let i in tomes) {
|
||||
let tome_str = info[1].charAt(i);
|
||||
let tome_name = getTomeNameFromID(Base64.toInt(tome_str));
|
||||
console.log(tome_name);
|
||||
setValue(tomeInputs[i], tome_name);
|
||||
}
|
||||
info[1] = info[1].slice(7);
|
||||
}
|
||||
|
||||
if (version >= 7) {
|
||||
// ugly af. only works since its the last thing. will be fixed with binary decode
|
||||
atree_data = new BitVector(info[1]);
|
||||
}
|
||||
else {
|
||||
atree_data = null;
|
||||
}
|
||||
|
||||
for (let i in powder_inputs) {
|
||||
setValue(powder_inputs[i], powdering[i]);
|
||||
}
|
||||
for (let i in skillpoints) {
|
||||
setValue(skp_order[i] + "-skp", skillpoints[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Stores the entire build in a string using B64 encoding and adds it to the URL.
|
||||
*/
|
||||
function encodeBuild(build, powders, skillpoints, atree, atree_state) {
|
||||
|
||||
if (build) {
|
||||
let build_string;
|
||||
|
||||
//V6 encoding - Tomes
|
||||
//V7 encoding - ATree
|
||||
build_version = 5;
|
||||
build_string = "";
|
||||
tome_string = "";
|
||||
|
||||
for (const item of build.items) {
|
||||
if (item.statMap.get("custom")) {
|
||||
let custom = "CI-"+encodeCustom(item, true);
|
||||
build_string += Base64.fromIntN(custom.length, 3) + custom;
|
||||
build_version = Math.max(build_version, 5);
|
||||
} else if (item.statMap.get("crafted")) {
|
||||
build_string += "CR-"+encodeCraft(item);
|
||||
} else if (item.statMap.get("category") === "tome") {
|
||||
let tome_id = item.statMap.get("id");
|
||||
if (tome_id <= 60) {
|
||||
// valid normal tome. ID 61-63 is for NONE tomes.
|
||||
build_version = Math.max(build_version, 6);
|
||||
}
|
||||
tome_string += Base64.fromIntN(tome_id, 1);
|
||||
} else {
|
||||
build_string += Base64.fromIntN(item.statMap.get("id"), 3);
|
||||
}
|
||||
}
|
||||
|
||||
for (const skp of skillpoints) {
|
||||
build_string += Base64.fromIntN(skp, 2); // Maximum skillpoints: 2048
|
||||
}
|
||||
build_string += Base64.fromIntN(build.level, 2);
|
||||
for (const _powderset of powders) {
|
||||
let n_bits = Math.ceil(_powderset.length / 6);
|
||||
build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders.
|
||||
// Slice copy.
|
||||
let powderset = _powderset.slice();
|
||||
while (powderset.length != 0) {
|
||||
let firstSix = powderset.slice(0,6).reverse();
|
||||
let powder_hash = 0;
|
||||
for (const powder of firstSix) {
|
||||
powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first.
|
||||
}
|
||||
build_string += Base64.fromIntN(powder_hash, 5);
|
||||
powderset = powderset.slice(6);
|
||||
}
|
||||
}
|
||||
build_string += tome_string;
|
||||
|
||||
if (atree.length > 0 && atree_state.get(atree[0].ability.id).active) {
|
||||
build_version = Math.max(build_version, 7);
|
||||
const bitvec = encode_atree(atree, atree_state);
|
||||
build_string += bitvec.toB64();
|
||||
}
|
||||
|
||||
return build_version.toString() + "_" + build_string;
|
||||
}
|
||||
}
|
||||
|
||||
function copyBuild() {
|
||||
copyTextToClipboard(url_base+location.hash);
|
||||
document.getElementById("copy-button").textContent = "Copied!";
|
||||
}
|
||||
|
||||
function shareBuild(build) {
|
||||
if (build) {
|
||||
let text = url_base+location.hash+"\n"+
|
||||
"WynnBuilder build:\n"+
|
||||
"> "+build.items[0].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[1].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[2].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[3].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[4].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[5].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[6].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[7].statMap.get("displayName")+"\n"+
|
||||
"> "+build.items[15].statMap.get("displayName")+" ["+build_powders[4].map(x => powderNames.get(x)).join("")+"]";
|
||||
copyTextToClipboard(text);
|
||||
document.getElementById("share-button").textContent = "Copied!";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ability tree encode and decode functions
|
||||
*
|
||||
* Based on a traversal, basically only uses bits to represent the nodes that are on (and "dark" outgoing edges).
|
||||
* credit: SockMower
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return: BitVector
|
||||
*/
|
||||
function encode_atree(atree, atree_state) {
|
||||
let ret_vec = new BitVector(0, 0);
|
||||
|
||||
function traverse(head, atree_state, visited, ret) {
|
||||
for (const child of head.children) {
|
||||
if (visited.has(child.ability.id)) { continue; }
|
||||
visited.set(child.ability.id, true);
|
||||
if (atree_state.get(child.ability.id).active) {
|
||||
ret.append(1, 1);
|
||||
traverse(child, atree_state, visited, ret);
|
||||
}
|
||||
else {
|
||||
ret.append(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
traverse(atree[0], atree_state, new Map(), ret_vec);
|
||||
return ret_vec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return: List of active nodes
|
||||
*/
|
||||
function decode_atree(atree, bits) {
|
||||
let i = 0;
|
||||
let ret = [];
|
||||
ret.push(atree[0]);
|
||||
function traverse(head, visited, ret) {
|
||||
for (const child of head.children) {
|
||||
if (visited.has(child.ability.id)) { continue; }
|
||||
visited.set(child.ability.id, true);
|
||||
if (bits.read_bit(i)) {
|
||||
i += 1;
|
||||
ret.push(child);
|
||||
traverse(child, visited, ret);
|
||||
}
|
||||
else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
traverse(atree[0], new Map(), ret);
|
||||
return ret;
|
||||
}
|
|
@ -15,6 +15,11 @@ function skillPointsToPercentage(skp){
|
|||
//return clamp((-0.0000000066695* Math.pow(Math.E, -0.00924033 * skp + 18.9) + 1.0771), 0.00, 0.808);
|
||||
}
|
||||
|
||||
// WYNN2: Skillpoint max scaling. Intel is cost reduction
|
||||
const skillpoint_final_mult = [1, 1, 0.5, 0.867, 0.951];
|
||||
// intel damage and water%
|
||||
const skillpoint_damage_mult = [1, 1, 1, 0.867, 0.951];
|
||||
|
||||
/*Turns the input amount of levels into skillpoints available.
|
||||
*
|
||||
* @param level - the integer level count to be converted
|
||||
|
@ -51,29 +56,247 @@ const armorTypes = [ "helmet", "chestplate", "leggings", "boots" ];
|
|||
const accessoryTypes = [ "ring", "bracelet", "necklace" ];
|
||||
const weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ];
|
||||
const consumableTypes = [ "potion", "scroll", "food"];
|
||||
const tomeTypes = ["armorTome", "weaponTome", "guildTome", "dungeonTome", "gatheringTome", "slayingTome"]
|
||||
const tome_types = ['weaponTome', 'armorTome', 'guildTome'];
|
||||
const attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY_FAST", "SUPER_FAST"];
|
||||
const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ];
|
||||
//0.51, 0.82, 1.50, 2.05, 2.50, 3.11, 4.27
|
||||
const classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"];
|
||||
const wep_to_class = new Map([["dagger", "Assassin"], ["spear", "Warrior"], ["wand", "Mage"], ["bow", "Archer"], ["relik", "Shaman"]])
|
||||
const tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set", "Crafted"] //I'm not sure why you would make a custom crafted but if you do you should be able to use it w/ the correct powder formula
|
||||
const types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tomeTypes).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
|
||||
const all_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
|
||||
//weaponTypes.push("sword");
|
||||
//console.log(types)
|
||||
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tomeTypes);
|
||||
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
|
||||
|
||||
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
|
||||
let skpReqs = skp_order.map(x => x + "Req");
|
||||
|
||||
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "dmgMobs", "defMobs"];
|
||||
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs",
|
||||
|
||||
// wynn2 damages.
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
"critDamPct"
|
||||
];
|
||||
// Extra fake IDs (reserved for use in spell damage calculation) : damageMultiplier, defMultiplier, poisonPct, activeMajorIDs
|
||||
let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ]
|
||||
|
||||
//File reading for ID translations for JSON purposes
|
||||
let reversetranslations = new Map();
|
||||
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rainbowRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
||||
//does not include dmgMobs (wep tomes) and defMobs (armor tomes)
|
||||
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rSdRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
||||
//does not include damMobs (wep tomes) and defMobs (armor tomes)
|
||||
for (const [k, v] of translations) {
|
||||
reversetranslations.set(v, k);
|
||||
}
|
||||
|
||||
console.log(translations);
|
||||
let nonRolledIDs = [
|
||||
"name",
|
||||
"lore",
|
||||
"displayName",
|
||||
"tier",
|
||||
"set",
|
||||
"slots",
|
||||
"type",
|
||||
"material",
|
||||
"drop",
|
||||
"quest",
|
||||
"restrict",
|
||||
"nDam", "fDam", "wDam", "aDam", "tDam", "eDam",
|
||||
"atkSpd",
|
||||
"hp",
|
||||
"fDef", "wDef", "aDef", "tDef", "eDef",
|
||||
"lvl",
|
||||
"classReq",
|
||||
"strReq", "dexReq", "intReq", "defReq", "agiReq",
|
||||
"str", "dex", "int", "agi", "def",
|
||||
"fixID",
|
||||
"category",
|
||||
"id",
|
||||
"skillpoints",
|
||||
"reqs",
|
||||
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
||||
"majorIds",
|
||||
"damMobs",
|
||||
"defMobs",
|
||||
// wynn2 damages.
|
||||
"eDamAddMin","eDamAddMax",
|
||||
"tDamAddMin","tDamAddMax",
|
||||
"wDamAddMin","wDamAddMax",
|
||||
"fDamAddMin","fDamAddMax",
|
||||
"aDamAddMin","aDamAddMax",
|
||||
"nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
"damAddMin","damAddMax", // all
|
||||
"rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral).
|
||||
];
|
||||
let rolledIDs = [
|
||||
"hprPct",
|
||||
"mr",
|
||||
"sdPct",
|
||||
"mdPct",
|
||||
"ls",
|
||||
"ms",
|
||||
"xpb",
|
||||
"lb",
|
||||
"ref",
|
||||
"thorns",
|
||||
"expd",
|
||||
"spd",
|
||||
"atkTier",
|
||||
"poison",
|
||||
"hpBonus",
|
||||
"spRegen",
|
||||
"eSteal",
|
||||
"hprRaw",
|
||||
"sdRaw",
|
||||
"mdRaw",
|
||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
||||
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
|
||||
"spPct1", "spRaw1",
|
||||
"spPct2", "spRaw2",
|
||||
"spPct3", "spRaw3",
|
||||
"spPct4", "spRaw4",
|
||||
"pDamRaw",
|
||||
"sprint",
|
||||
"sprintReg",
|
||||
"jh",
|
||||
"lq",
|
||||
"gXp",
|
||||
"gSpd",
|
||||
// wynn2 damages.
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
];
|
||||
let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ];
|
||||
|
||||
/**
|
||||
* Take an item with id list and turn it into a set of minrolls and maxrolls.
|
||||
*/
|
||||
function expandItem(item) {
|
||||
let minRolls = new Map();
|
||||
let maxRolls = new Map();
|
||||
let expandedItem = new Map();
|
||||
if (item.fixID) { //The item has fixed IDs.
|
||||
expandedItem.set("fixID",true);
|
||||
for (const id of rolledIDs) { //all rolled IDs are numerical
|
||||
let val = (item[id] || 0);
|
||||
minRolls.set(id,val);
|
||||
maxRolls.set(id,val);
|
||||
}
|
||||
} else { //The item does not have fixed IDs.
|
||||
for (const id of rolledIDs) {
|
||||
let val = (item[id] || 0);
|
||||
if (val > 0) { // positive rolled IDs
|
||||
if (reversedIDs.includes(id)) {
|
||||
maxRolls.set(id,idRound(val*0.3));
|
||||
minRolls.set(id,idRound(val*1.3));
|
||||
} else {
|
||||
maxRolls.set(id,idRound(val*1.3));
|
||||
minRolls.set(id,idRound(val*0.3));
|
||||
}
|
||||
} else if (val < 0) { //negative rolled IDs
|
||||
if (reversedIDs.includes(id)) {
|
||||
maxRolls.set(id,idRound(val*1.3));
|
||||
minRolls.set(id,idRound(val*0.7));
|
||||
}
|
||||
else {
|
||||
maxRolls.set(id,idRound(val*0.7));
|
||||
minRolls.set(id,idRound(val*1.3));
|
||||
}
|
||||
}
|
||||
else { // if val == 0
|
||||
// NOTE: DO NOT remove this case! idRound behavior does not round to 0!
|
||||
maxRolls.set(id,0);
|
||||
minRolls.set(id,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const id of nonRolledIDs) {
|
||||
expandedItem.set(id,item[id]);
|
||||
}
|
||||
expandedItem.set("minRolls",minRolls);
|
||||
expandedItem.set("maxRolls",maxRolls);
|
||||
return expandedItem;
|
||||
}
|
||||
|
||||
class Item {
|
||||
constructor(item_obj) {
|
||||
this.statMap = expandItem(item_obj);
|
||||
}
|
||||
}
|
||||
|
||||
/* Takes in an ingredient object and returns an equivalent Map().
|
||||
*/
|
||||
function expandIngredient(ing) {
|
||||
let expandedIng = new Map();
|
||||
let mapIds = ['consumableIDs', 'itemIDs', 'posMods'];
|
||||
for (const id of mapIds) {
|
||||
let idMap = new Map();
|
||||
for (const key of Object.keys(ing[id])) {
|
||||
idMap.set(key, ing[id][key]);
|
||||
}
|
||||
expandedIng.set(id, idMap);
|
||||
}
|
||||
let normIds = ['lvl','name', 'displayName','tier','skills','id'];
|
||||
for (const id of normIds) {
|
||||
expandedIng.set(id, ing[id]);
|
||||
}
|
||||
if (ing['isPowder']) {
|
||||
expandedIng.set("isPowder",ing['isPowder']);
|
||||
expandedIng.set("pid",ing['pid']);
|
||||
}
|
||||
//now the actually hard one
|
||||
let idMap = new Map();
|
||||
idMap.set("minRolls", new Map());
|
||||
idMap.set("maxRolls", new Map());
|
||||
for (const field of ingFields) {
|
||||
let val = (ing['ids'][field] || 0);
|
||||
idMap.get("minRolls").set(field, val['minimum']);
|
||||
idMap.get("maxRolls").set(field, val['maximum']);
|
||||
}
|
||||
expandedIng.set("ids",idMap);
|
||||
return expandedIng;
|
||||
}
|
||||
|
||||
/* Takes in a recipe object and returns an equivalent Map().
|
||||
*/
|
||||
function expandRecipe(recipe) {
|
||||
let expandedRecipe = new Map();
|
||||
let normIDs = ["name", "skill", "type","id"];
|
||||
for (const id of normIDs) {
|
||||
expandedRecipe.set(id,recipe[id]);
|
||||
}
|
||||
let rangeIDs = ["durability","lvl", "healthOrDamage", "duration", "basicDuration"];
|
||||
for (const id of rangeIDs) {
|
||||
if(recipe[id]){
|
||||
expandedRecipe.set(id, [recipe[id]['minimum'], recipe[id]['maximum']]);
|
||||
} else {
|
||||
expandedRecipe.set(id, [0,0]);
|
||||
}
|
||||
}
|
||||
expandedRecipe.set("materials", [ new Map([ ["item", recipe['materials'][0]['item']], ["amount", recipe['materials'][0]['amount']] ]) , new Map([ ["item", recipe['materials'][1]['item']], ["amount",recipe['materials'][1]['amount'] ] ]) ]);
|
||||
return expandedRecipe;
|
||||
}
|
||||
|
||||
/*An independent helper function that rounds a rolled ID to the nearest integer OR brings the roll away from 0.
|
||||
* @param id
|
||||
*/
|
||||
function idRound(id){
|
||||
rounded = Math.round(id);
|
||||
if(rounded == 0){
|
||||
return 1; //this is a hack, will need changing along w/ rest of ID system if anything changes
|
||||
}else{
|
||||
return rounded;
|
||||
}
|
||||
}
|
||||
|
|
1315
js/builder.js
1166
js/builder_graph.js
Normal file
263
js/computation_graph.js
Normal file
|
@ -0,0 +1,263 @@
|
|||
let all_nodes = [];
|
||||
class ComputeNode {
|
||||
/**
|
||||
* Make a generic compute node.
|
||||
* Adds the node to the global map of nodenames to nodes (for calling from html listeners).
|
||||
*
|
||||
* @param name : Name of the node (string). Must be unique. Must "fit in" a JS string (terminated by single quotes).
|
||||
*/
|
||||
constructor(name) {
|
||||
this.inputs = []; // parent nodes
|
||||
this.input_translation = new Map();
|
||||
this.children = [];
|
||||
this.value = null;
|
||||
this.name = name;
|
||||
this.update_task = null;
|
||||
this.fail_cb = false; // Set to true to force updates even if parent failed.
|
||||
this.dirty = 2; // 3 states:
|
||||
// 2: dirty
|
||||
// 1: possibly dirty
|
||||
// 0: clean
|
||||
this.inputs_dirty = new Map();
|
||||
this.inputs_dirty_count = 0;
|
||||
all_nodes.push(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request update of this compute node. Pushes updates to children.
|
||||
*/
|
||||
update() {
|
||||
if (this.inputs_dirty_count != 0) {
|
||||
return;
|
||||
}
|
||||
if (this.dirty === 0) {
|
||||
return;
|
||||
}
|
||||
if (this.dirty == 2) {
|
||||
let calc_inputs = new Map();
|
||||
for (const input of this.inputs) {
|
||||
calc_inputs.set(this.input_translation.get(input.name), input.value);
|
||||
}
|
||||
this.value = this.compute_func(calc_inputs);
|
||||
}
|
||||
this.dirty = 0;
|
||||
for (const child of this.children) {
|
||||
child.mark_input_clean(this.name, this.value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark parent as not dirty. Propagates calculation if all inputs are present.
|
||||
*/
|
||||
mark_input_clean(input_name, value) {
|
||||
if (value !== null || this.fail_cb) {
|
||||
if (this.inputs_dirty.get(input_name)) {
|
||||
this.inputs_dirty.set(input_name, false);
|
||||
this.inputs_dirty_count -= 1;
|
||||
}
|
||||
if (this.inputs_dirty_count === 0) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mark_input_dirty(input_name) {
|
||||
if (!this.inputs_dirty.get(input_name)) {
|
||||
this.inputs_dirty.set(input_name, true);
|
||||
this.inputs_dirty_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
mark_dirty(dirty_state=2) {
|
||||
if (this.dirty < dirty_state) {
|
||||
this.dirty = dirty_state;
|
||||
for (const child of this.children) {
|
||||
child.mark_input_dirty(this.name);
|
||||
child.mark_dirty(dirty_state);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value of this compute node. Can't trigger update cascades (push based update, not pull based.)
|
||||
*/
|
||||
get_value() {
|
||||
return this.value
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method for computing something. Return value is set into this.value
|
||||
*/
|
||||
compute_func(input_map) {
|
||||
throw "no compute func specified";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add link to a parent compute node, optionally with an alias.
|
||||
*/
|
||||
link_to(parent_node, link_name) {
|
||||
this.inputs.push(parent_node)
|
||||
link_name = (link_name !== undefined) ? link_name : parent_node.name;
|
||||
this.input_translation.set(parent_node.name, link_name);
|
||||
if (parent_node.dirty || (parent_node.value === null && !this.fail_cb)) {
|
||||
this.inputs_dirty_count += 1;
|
||||
this.inputs_dirty.set(parent_node.name, true);
|
||||
}
|
||||
parent_node.children.push(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a link to a parent node.
|
||||
* TODO: time complexity of list deletion (not super relevant but it hurts my soul)
|
||||
*/
|
||||
remove_link(parent_node) {
|
||||
const idx = this.inputs.indexOf(parent_node); // Get idx
|
||||
this.inputs.splice(idx, 1); // remove element
|
||||
|
||||
this.input_translation.delete(parent_node.name);
|
||||
const was_dirty = this.inputs_dirty.get(parent_node.name);
|
||||
this.inputs_dirty.delete(parent_node.name);
|
||||
if (was_dirty) {
|
||||
this.inputs_dirty_count -= 1;
|
||||
}
|
||||
|
||||
const idx2 = parent_node.children.indexOf(this);
|
||||
parent_node.children.splice(idx2, 1);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
class ValueCheckComputeNode extends ComputeNode {
|
||||
constructor(name) { super(name); }
|
||||
|
||||
/**
|
||||
* Request update of this compute node. Pushes updates to children,
|
||||
* but only if this node's value changed.
|
||||
*/
|
||||
update() {
|
||||
if (this.inputs_dirty_count != 0) {
|
||||
return;
|
||||
}
|
||||
if (this.dirty === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let calc_inputs = new Map();
|
||||
for (const input of this.inputs) {
|
||||
calc_inputs.set(this.input_translation.get(input.name), input.value);
|
||||
}
|
||||
let val = this.compute_func(calc_inputs);
|
||||
if (val !== this.value) {
|
||||
super.mark_dirty(2);
|
||||
}
|
||||
else {
|
||||
console.log("soft update");
|
||||
}
|
||||
this.value = val;
|
||||
|
||||
this.dirty = 0;
|
||||
for (const child of this.children) {
|
||||
child.mark_input_clean(this.name, this.value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaulting to "dusty" state.
|
||||
*/
|
||||
mark_dirty(dirty_state="unused") {
|
||||
return super.mark_dirty(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a ComputeNode to be updated.
|
||||
*
|
||||
* @param node : ComputeNode to schedule an update for.
|
||||
*/
|
||||
function calcSchedule(node, timeout) {
|
||||
if (node.update_task !== null) {
|
||||
clearTimeout(node.update_task);
|
||||
}
|
||||
node.mark_dirty();
|
||||
node.update_task = setTimeout(function() {
|
||||
node.update();
|
||||
node.update_task = null;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
class PrintNode extends ComputeNode {
|
||||
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.fail_cb = true;
|
||||
}
|
||||
|
||||
compute_func(input_map) {
|
||||
console.log([this.name, input_map]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node for getting an input from an input field.
|
||||
* Fires updates whenever the input field is updated.
|
||||
*
|
||||
* Signature: InputNode() => str
|
||||
*/
|
||||
class InputNode extends ComputeNode {
|
||||
constructor(name, input_field) {
|
||||
super(name);
|
||||
this.input_field = input_field;
|
||||
this.input_field.addEventListener("input", () => calcSchedule(this, 500));
|
||||
this.input_field.addEventListener("change", () => calcSchedule(this, 5));
|
||||
//calcSchedule(this); Manually fire first update for better control
|
||||
}
|
||||
|
||||
compute_func(input_map) {
|
||||
return this.input_field.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passthrough node for simple aggregation.
|
||||
* Unfortunately if you use this too much you get layers and layers of maps...
|
||||
*
|
||||
* Signature: PassThroughNode(**kwargs) => Map[...]
|
||||
*/
|
||||
class PassThroughNode extends ComputeNode {
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.breakout_nodes = new Map();
|
||||
}
|
||||
|
||||
compute_func(input_map) {
|
||||
return input_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ComputeNode that will "break out" one part of this aggregation input.
|
||||
* There is some overhead to this operation because ComputeNode is not exactly a free abstraction... oof
|
||||
* Also you will recv updates whenever any input that is part of the aggregation changes even
|
||||
* if the specific sub-input didn't change.
|
||||
*
|
||||
* Parameters:
|
||||
* sub-input: The key to listen to
|
||||
*/
|
||||
get_node(sub_input) {
|
||||
if (this.breakout_nodes.has(sub_input)) {
|
||||
return this.breakout_nodes.get(sub_input);
|
||||
}
|
||||
const _name = this.name;
|
||||
const ret = new (class extends ComputeNode {
|
||||
constructor() { super('passthrough-'+_name+'-'+sub_input); }
|
||||
compute_func(input_map) { return input_map.get(_name).get(sub_input); }
|
||||
})().link_to(this);
|
||||
this.breakout_nodes.set(sub_input, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
|
@ -170,7 +170,6 @@ class Craft{
|
|||
statMap.set(e + "Dam", "0-0");
|
||||
statMap.set(e + "DamLow", "0-0");
|
||||
}
|
||||
//statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
|
||||
statMap.set("category","weapon");
|
||||
statMap.set("atkSpd",this.atkSpd);
|
||||
}
|
||||
|
@ -190,7 +189,6 @@ class Craft{
|
|||
let amounts = this.recipe.get("materials").map(x=> x.get("amount"));
|
||||
//Mat Multipliers - should work!
|
||||
matmult = (tierToMult[tiers[0]]*amounts[0] + tierToMult[tiers[1]]*amounts[1]) / (amounts[0]+amounts[1]);
|
||||
console.log(matmult);
|
||||
|
||||
let low = this.recipe.get("healthOrDamage")[0];
|
||||
let high = this.recipe.get("healthOrDamage")[1];
|
||||
|
@ -382,12 +380,10 @@ class Craft{
|
|||
|
||||
statMap.set("reqs",[0,0,0,0,0]);
|
||||
statMap.set("skillpoints", [0,0,0,0,0]);
|
||||
statMap.set("damageBonus",[0,0,0,0,0]);
|
||||
for (const e in skp_order) {
|
||||
statMap.set(skp_order[e], statMap.get("maxRolls").has(skp_order[e]) ? statMap.get("maxRolls").get(skp_order[e]) : 0);
|
||||
statMap.get("skillpoints")[e] = statMap.get("maxRolls").has(skp_order[e]) ? statMap.get("maxRolls").get(skp_order[e]) : 0;
|
||||
statMap.get("reqs")[e] = statMap.has(skp_order[e]+"Req") && !consumableTypes.includes(statMap.get("type"))? statMap.get(skp_order[e]+"Req") : 0;
|
||||
statMap.get("damageBonus")[e] = statMap.has(skp_order[e]+"DamPct") ? statMap.get(skp_order[e]+"DamPct") : 0;
|
||||
}
|
||||
for (const id of rolledIDs) {
|
||||
if (statMap.get("minRolls").has(id)) {
|
||||
|
|
|
@ -22,10 +22,10 @@ const ING_BUILD_VERSION = "7.0.1";
|
|||
*/
|
||||
let player_craft;
|
||||
|
||||
function setTitle() {
|
||||
document.getElementById("header").textContent = "WynnCrafter version "+ING_BUILD_VERSION+" (ingredient db version "+ING_DB_VERSION+")";
|
||||
document.getElementById("header").classList.add("funnynumber");
|
||||
}
|
||||
// function setTitle() {
|
||||
// document.getElementById("header").textContent = "WynnCrafter version "+ING_BUILD_VERSION+" (ingredient db version "+ING_DB_VERSION+")";
|
||||
// document.getElementById("header").classList.add("funnynumber");
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
@ -44,16 +44,32 @@ function init_crafter() {
|
|||
try {
|
||||
document.getElementById("recipe-choice").addEventListener("change", (event) => {
|
||||
updateMaterials();
|
||||
updateCraftedImage();
|
||||
calculateCraftSchedule();
|
||||
});
|
||||
document.getElementById("recipe-choice").addEventListener("oninput", (event) => {
|
||||
updateCraftedImage();
|
||||
});
|
||||
document.getElementById("level-choice").addEventListener("change", (event) => {
|
||||
updateMaterials();
|
||||
calculateCraftSchedule();
|
||||
});
|
||||
|
||||
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();");
|
||||
}
|
||||
for (let i = 1; i < 7; ++i) {
|
||||
document.getElementById("ing-choice-" + i ).setAttribute("oninput", "calculateCraftSchedule();");
|
||||
}
|
||||
for (const str of ["slow", "normal", "fast"]) {
|
||||
document.getElementById(str + "-atk-button").setAttribute("onclick", document.getElementById(str + "-atk-button").getAttribute("onclick") + "; calculateCraftSchedule();");
|
||||
}
|
||||
|
||||
|
||||
populateFields();
|
||||
decodeCraft(ing_url_tag);
|
||||
setTitle();
|
||||
} catch (error) {
|
||||
console.log("If you are seeing this while building, do not worry. Oherwise, panic! (jk contact ferricles)");
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
|
@ -89,6 +105,20 @@ function toggleAtkSpd(buttonId) {
|
|||
}
|
||||
}
|
||||
|
||||
let doCraftTask = null;
|
||||
|
||||
function calculateCraftSchedule(){
|
||||
console.log("Craft Schedule called");
|
||||
if (doCraftTask !== null) {
|
||||
clearTimeout(doCraftTask);
|
||||
}
|
||||
doCraftTask = setTimeout(function(){
|
||||
doCraftTask = null;
|
||||
calculateCraft();
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}, 250);
|
||||
}
|
||||
|
||||
function calculateCraft() {
|
||||
//Make things display.
|
||||
for (let i of document.getElementsByClassName("hide-container-block")) {
|
||||
|
@ -117,7 +147,8 @@ function calculateCraft() {
|
|||
}
|
||||
let ingreds = [];
|
||||
for (i = 1; i < 7; i++) {
|
||||
console.log(getValue("ing-choice-"+i));
|
||||
console.log("ing-choice-"+i);
|
||||
// console.log(getValue("ing-choice-"+i));
|
||||
getValue("ing-choice-" + i) === "" ? ingreds.push(expandIngredient(ingMap.get("No Ingredient"))) : ingreds.push(expandIngredient(ingMap.get(getValue("ing-choice-" + i))));
|
||||
}
|
||||
let atkSpd = "NORMAL"; //default attack speed will be normal.
|
||||
|
@ -138,16 +169,19 @@ function calculateCraft() {
|
|||
console.log(levelrange)
|
||||
console.log(mat_tiers)
|
||||
console.log(ingreds)*/
|
||||
|
||||
document.getElementById("mat-1").textContent = recipe.get("materials")[0].get("item").split(" ").slice(1).join(" ") + " Tier:";
|
||||
document.getElementById("mat-2").textContent = recipe.get("materials")[1].get("item").split(" ").slice(1).join(" ") + " Tier:";
|
||||
|
||||
//Display Recipe Stats
|
||||
displayRecipeStats(player_craft, "recipe-stats");
|
||||
for(let i = 0; i < 6; i++) {
|
||||
displayExpandedIngredient(player_craft["ingreds"][i],"tooltip-" + i);
|
||||
}
|
||||
|
||||
//Display Craft Stats
|
||||
displayCraftStats(player_craft, "craft-stats");
|
||||
// displayCraftStats(player_craft, "craft-stats");
|
||||
let mock_item = player_craft.statMap;
|
||||
if (mock_item.get('category') === 'weapon') { apply_weapon_powders(mock_item) };
|
||||
displayExpandedItem(mock_item, "craft-stats");
|
||||
|
||||
//Display Ingredients' Stats
|
||||
for (let i = 1; i < 7; i++) {
|
||||
displayExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats");
|
||||
|
@ -187,6 +221,7 @@ function decodeCraft(ing_url_tag) {
|
|||
//console.log(Base64.toInt(tag.substring(12,14)));
|
||||
recipesName = recipe.split("-");
|
||||
setValue("recipe-choice",recipesName[0]);
|
||||
updateCraftedImage();
|
||||
setValue("level-choice",recipesName[1]+"-"+recipesName[2]);
|
||||
tierNum = Base64.toInt(tag.substring(14,15));
|
||||
mat_tiers = [];
|
||||
|
@ -227,11 +262,20 @@ function populateFields() {
|
|||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Copy the link
|
||||
/*
|
||||
Copies the CR Hash (CR-blahblahblah)
|
||||
*/
|
||||
function copyRecipe(){
|
||||
function copyRecipeHash() {
|
||||
if (player_craft) {
|
||||
copyTextToClipboard("CR-"+location.hash.slice(1));
|
||||
document.getElementById("copy-hash-button").textContent = "Copied!";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Copies the link (hppeng-wynn.github.io/crafter/#blahblah)
|
||||
*/
|
||||
function copyRecipe() {
|
||||
if (player_craft) {
|
||||
copyTextToClipboard(ing_url_base+location.hash);
|
||||
document.getElementById("copy-button").textContent = "Copied!";
|
||||
|
@ -240,7 +284,7 @@ function copyRecipe(){
|
|||
|
||||
/* Copy the link AND a display of all ingredients
|
||||
*/
|
||||
function shareRecipe(){
|
||||
function shareRecipe() {
|
||||
if (player_craft) {
|
||||
let copyString = ing_url_base+location.hash + "\n";
|
||||
let name = player_craft.recipe.get("name").split("-");
|
||||
|
@ -296,6 +340,16 @@ function toggleMaterial(buttonId) {
|
|||
}
|
||||
}
|
||||
|
||||
/* Updates the crafted icon.
|
||||
*/
|
||||
function updateCraftedImage() {
|
||||
let input = document.getElementById("recipe-choice");
|
||||
if (all_types.includes(input.value)) {
|
||||
document.getElementById("recipe-img").src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + input.value.toLowerCase() + ".png";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Reset all fields
|
||||
*/
|
||||
function resetFields() {
|
||||
|
@ -313,4 +367,8 @@ function resetFields() {
|
|||
calculateCraft();
|
||||
}
|
||||
|
||||
load_ing_init(init_crafter);
|
||||
(async function() {
|
||||
let load_promises = [ load_ing_init() ];
|
||||
await Promise.all(load_promises);
|
||||
init_crafter();
|
||||
})();
|
||||
|
|
166
js/custom.js
|
@ -1,5 +1,6 @@
|
|||
const ci_save_order = ["name", "lore", "tier", "set", "slots", "type", "material", "drop", "quest", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq","str", "dex", "int", "agi", "def", "id", "skillpoints", "reqs", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "majorIds", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd","durability","duration","charges"];
|
||||
const nonRolled_strings = ["name","lore", "tier","set","type","material","drop","quest","majorIds","classReq","atkSpd","displayName", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "durability", "duration"];
|
||||
const ci_save_order = ["name", "lore", "tier", "set", "slots", "type", "material", "drop", "quest", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "str", "dex", "int", "agi", "def", "id", "skillpoints", "reqs", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "majorIds", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "durability", "duration", "charges"];
|
||||
const nonRolled_strings = ["name", "lore", "tier", "set", "type", "material", "drop", "quest", "majorIds", "classReq", "atkSpd", "displayName", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", "durability", "duration"];
|
||||
|
||||
//omitted restrict - it's always "Custom Item"
|
||||
//omitted displayName - either it's the same as name (repetitive) or it's "Custom Item"
|
||||
//omitted category - can always get this from type
|
||||
|
@ -16,7 +17,7 @@ const nonRolled_strings = ["name","lore", "tier","set","type","material","drop",
|
|||
function encodeCustom(custom, verbose) {
|
||||
if (custom) {
|
||||
if (custom.statMap) {
|
||||
custom = custom.statMap;
|
||||
custom = custom.statMap;
|
||||
}
|
||||
let hash = "1";
|
||||
//version 1
|
||||
|
@ -34,55 +35,53 @@ function encodeCustom(custom, verbose) {
|
|||
// 1 - min neg max pos
|
||||
// 2 - min pos max neg (how?)
|
||||
// 3 - min neg max neg
|
||||
let sign = (Boolean(val_min / Math.abs(val_min) < 0) | 0) + 2*(Boolean(val_max / Math.abs(val_max) < 0) | 0);
|
||||
let sign = (Boolean(val_min / Math.abs(val_min) < 0) | 0) + 2 * (Boolean(val_max / Math.abs(val_max) < 0) | 0);
|
||||
//console.log(id + ": " + sign);
|
||||
let min_len = Math.max(1,Math.ceil(log(64,Math.abs(val_min)+1)));
|
||||
let max_len = Math.max(1,Math.ceil(log(64,Math.abs(val_max)+1)));
|
||||
let len = Math.max(min_len,max_len);
|
||||
let min_len = Math.max(1, Math.ceil(log(64, Math.abs(val_min) + 1)));
|
||||
let max_len = Math.max(1, Math.ceil(log(64, Math.abs(val_max) + 1)));
|
||||
let len = Math.max(min_len, max_len);
|
||||
val_min = Math.abs(val_min);
|
||||
val_max = Math.abs(val_max);
|
||||
|
||||
if ( val_min != 0 || val_max != 0 ) {
|
||||
if (val_min != 0 || val_max != 0) {
|
||||
if (custom.get("fixID")) {
|
||||
hash += Base64.fromIntN(i,2) + Base64.fromIntN(len,2) + sign + Base64.fromIntN(val_min, len);
|
||||
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(len, 2) + sign + Base64.fromIntN(val_min, len);
|
||||
} else {
|
||||
hash += Base64.fromIntN(i,2) + Base64.fromIntN(len,2) + sign + Base64.fromIntN(val_min, len) + Base64.fromIntN(val_max,len);
|
||||
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(len, 2) + sign + Base64.fromIntN(val_min, len) + Base64.fromIntN(val_max, len);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let damages = ["nDam", "eDam", "tDam", "wDam", "fDam", "aDam"]; //"nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_"
|
||||
let val = custom.get(id);
|
||||
if (id == "majorIds") {
|
||||
console.log(val);
|
||||
if (val.length > 0) {
|
||||
val = val[0];
|
||||
}
|
||||
else {
|
||||
val = "";
|
||||
}
|
||||
console.log(val);
|
||||
}
|
||||
|
||||
if (typeof(val) === "string" && val !== "") {
|
||||
if ((damages.includes(id) && val === "0-0") || (!verbose && ["lore","majorIds","quest","materials","drop","set"].includes(id))) { continue; }
|
||||
if (typeof (val) === "string" && val !== "") {
|
||||
if ((damages.includes(id) && val === "0-0") || (!verbose && ["lore", "majorIds", "quest", "materials", "drop", "set"].includes(id))) { continue; }
|
||||
if (id === "type") {
|
||||
hash += Base64.fromIntN(i,2) + Base64.fromIntN(types.indexOf(val.substring(0,1).toUpperCase()+val.slice(1)),1);
|
||||
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(types.indexOf(val.substring(0, 1).toUpperCase() + val.slice(1)), 1);
|
||||
} else if (id === "tier") {
|
||||
hash += Base64.fromIntN(i,2) + Base64.fromIntN(tiers.indexOf(val),1);
|
||||
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(tiers.indexOf(val), 1);
|
||||
} else if (id === "atkSpd") {
|
||||
hash += Base64.fromIntN(i,2) + Base64.fromIntN(attackSpeeds.indexOf(val),1);
|
||||
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(attackSpeeds.indexOf(val), 1);
|
||||
} else if (id === "classReq") {
|
||||
hash += Base64.fromIntN(i,2) + Base64.fromIntN(classes.indexOf(val),1);
|
||||
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(classes.indexOf(val), 1);
|
||||
} else {
|
||||
hash += Base64.fromIntN(i,2) + Base64.fromIntN(val.replaceAll(" ", "%20").length,2) + val.replaceAll(" ", "%20"); //values cannot go above 4096 chars!!!! Is this ok?
|
||||
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(val.replaceAll(" ", "%20").length, 2) + val.replaceAll(" ", "%20"); //values cannot go above 4096 chars!!!! Is this ok?
|
||||
}
|
||||
} else if (typeof(val) === "number" && val != 0) {
|
||||
let len = Math.max(1,Math.ceil(log(64,Math.abs(val))));
|
||||
} else if (typeof (val) === "number" && val != 0) {
|
||||
let len = Math.max(1, Math.ceil(log(64, Math.abs(val))));
|
||||
let sign = Boolean(val / Math.abs(val) < 0) | 0;
|
||||
//console.log(sign);
|
||||
//hash += Base64.fromIntN(i,2) + Base64.fromIntN(val,Math.max(1,Math.ceil(log(64,Math.abs(val))))) + "_";
|
||||
hash += Base64.fromIntN(i,2) + Base64.fromIntN(len,2) + sign + Base64.fromIntN(Math.abs(val),len);
|
||||
}
|
||||
hash += Base64.fromIntN(i, 2) + Base64.fromIntN(len, 2) + sign + Base64.fromIntN(Math.abs(val), len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,49 +94,51 @@ function encodeCustom(custom, verbose) {
|
|||
function getCustomFromHash(hash) {
|
||||
let name = hash.slice();
|
||||
let statMap;
|
||||
console.log("decoding");
|
||||
try {
|
||||
if (name.slice(0,3) === "CI-") {
|
||||
if (name.slice(0, 3) === "CI-") {
|
||||
name = name.substring(3);
|
||||
} else {
|
||||
throw new Error("Not a custom item!");
|
||||
}
|
||||
|
||||
|
||||
//probably change vers and fixID to be encoded and decoded to/from B64 in the future
|
||||
let version = name.charAt(0);
|
||||
let fixID = Boolean(parseInt(name.charAt(1),10));
|
||||
let fixID = Boolean(parseInt(name.charAt(1), 10));
|
||||
let tag = name.substring(2);
|
||||
statMap = new Map();
|
||||
statMap.set("minRolls", new Map());
|
||||
statMap.set("maxRolls", new Map());
|
||||
|
||||
|
||||
if (version === "1") {
|
||||
//do the things
|
||||
if (fixID) {
|
||||
statMap.set("fixID", true);
|
||||
}
|
||||
}
|
||||
while (tag !== "") {
|
||||
let id = ci_save_order[Base64.toInt(tag.slice(0,2))];
|
||||
let len = Base64.toInt(tag.slice(2,4));
|
||||
let id = ci_save_order[Base64.toInt(tag.slice(0, 2))];
|
||||
let len = Base64.toInt(tag.slice(2, 4));
|
||||
if (rolledIDs.includes(id)) {
|
||||
let sign = parseInt(tag.slice(4,5),10);
|
||||
let minRoll = Base64.toInt(tag.slice(5,5+len));
|
||||
let sign = parseInt(tag.slice(4, 5), 10);
|
||||
let minRoll = Base64.toInt(tag.slice(5, 5 + len));
|
||||
if (!fixID) {
|
||||
let maxRoll = Base64.toInt(tag.slice(5+len,5+2*len));
|
||||
let maxRoll = Base64.toInt(tag.slice(5 + len, 5 + 2 * len));
|
||||
if (sign > 1) {
|
||||
maxRoll *= -1;
|
||||
}
|
||||
if (sign % 2 == 1) {
|
||||
minRoll *= -1;
|
||||
}
|
||||
statMap.get("minRolls").set(id,minRoll);
|
||||
statMap.get("maxRolls").set(id,maxRoll);
|
||||
tag = tag.slice(5+2*len);
|
||||
statMap.get("minRolls").set(id, minRoll);
|
||||
statMap.get("maxRolls").set(id, maxRoll);
|
||||
tag = tag.slice(5 + 2 * len);
|
||||
} else {
|
||||
if (sign != 0) {
|
||||
minRoll *= -1;
|
||||
}
|
||||
statMap.get("minRolls").set(id,minRoll);
|
||||
statMap.get("maxRolls").set(id,minRoll);
|
||||
tag = tag.slice(5+len);
|
||||
statMap.get("minRolls").set(id, minRoll);
|
||||
statMap.get("maxRolls").set(id, minRoll);
|
||||
tag = tag.slice(5 + len);
|
||||
}
|
||||
} else {
|
||||
let val;
|
||||
|
@ -155,17 +156,17 @@ function getCustomFromHash(hash) {
|
|||
val = classes[Base64.toInt(tag.charAt(2))];
|
||||
len = -1;
|
||||
} else { //general case
|
||||
val = tag.slice(4,4+len).replaceAll("%20"," ");
|
||||
val = tag.slice(4, 4 + len).replaceAll("%20", " ");
|
||||
}
|
||||
tag = tag.slice(4+len);
|
||||
tag = tag.slice(4 + len);
|
||||
} else {
|
||||
let sign = parseInt(tag.slice(4,5),10);
|
||||
val = Base64.toInt(tag.slice(5,5+len));
|
||||
let sign = parseInt(tag.slice(4, 5), 10);
|
||||
val = Base64.toInt(tag.slice(5, 5 + len));
|
||||
if (sign == 1) {
|
||||
val *= -1;
|
||||
}
|
||||
tag = tag.slice(5+len);
|
||||
}
|
||||
tag = tag.slice(5 + len);
|
||||
}
|
||||
if (id === "majorIds") {
|
||||
val = [val];
|
||||
console.log(val);
|
||||
|
@ -173,27 +174,28 @@ function getCustomFromHash(hash) {
|
|||
statMap.set(id, val);
|
||||
}
|
||||
}
|
||||
statMap.set("hash","CI-"+name);
|
||||
statMap.set("hash", "CI-" + name);
|
||||
statMap.set("custom", true);
|
||||
return new Custom(statMap);
|
||||
}
|
||||
} catch (error) {
|
||||
//console.log(statMap);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/** An object representing a Custom Item. Mostly for vanity purposes.
|
||||
* @dep Requires the use of nonRolledIDs and rolledIDs from display_constants.js.
|
||||
* @dep Requires the use of attackSpeeds from build.js.
|
||||
*/
|
||||
class Custom{
|
||||
class Custom {
|
||||
/**
|
||||
* @description Construct a custom item (CI) from a statMap.
|
||||
* @param {statMap}: A map with keys from rolledIDs or nonRolledIDs or minRolls/maxRolls and values befitting the keys. minRolls and maxRolls are their own maps and have the same keys, but with minimum and maximum values (for rolls).
|
||||
*
|
||||
*/
|
||||
constructor(statMap){
|
||||
constructor(statMap) {
|
||||
this.statMap = statMap;
|
||||
// TODO patch
|
||||
// this.statMap.set("majorIds", [this.statMap.get("majorIds")]);
|
||||
|
@ -202,17 +204,17 @@ class Custom{
|
|||
|
||||
setHash(hash) {
|
||||
let ihash = hash.slice();
|
||||
if (ihash.slice(0,3) !== "CI-") {
|
||||
if (ihash.slice(0, 3) !== "CI-") {
|
||||
ihash = "CI-" + hash;
|
||||
}
|
||||
|
||||
this.hash = ihash;
|
||||
this.statMap.set("hash",ihash);
|
||||
this.statMap.set("hash", ihash);
|
||||
}
|
||||
|
||||
updateName(name) {
|
||||
this.name = name;
|
||||
this.displayName = name;
|
||||
this.displayName = name;
|
||||
}
|
||||
|
||||
/* Get all stats for this CI.
|
||||
|
@ -220,26 +222,24 @@ class Custom{
|
|||
* Follows the expandedItem item structure, similar to a crafted item.
|
||||
* TODO: Check if this is even useful
|
||||
*/
|
||||
initCustomStats(){
|
||||
initCustomStats() {
|
||||
//this.setHashVerbose(); //do NOT move sethash from here please
|
||||
|
||||
this.statMap.set("custom", true);
|
||||
console.log(this.statMap);
|
||||
|
||||
for (const id of ci_save_order) {
|
||||
if (rolledIDs.includes(id)) {
|
||||
if (!(this.statMap.get("minRolls").has(id) && this.statMap.get("minRolls").get(id))) {
|
||||
this.statMap.get("minRolls").set(id,0);
|
||||
this.statMap.get("maxRolls").set(id,0);
|
||||
this.statMap.get("minRolls").set(id, 0);
|
||||
this.statMap.get("maxRolls").set(id, 0);
|
||||
}
|
||||
} else {
|
||||
if (nonRolled_strings.includes(id)) {
|
||||
if (!(this.statMap.has(id)&&this.statMap.get(id))) {
|
||||
this.statMap.set(id,"");
|
||||
if (!(this.statMap.has(id) && this.statMap.get(id))) {
|
||||
this.statMap.set(id, "");
|
||||
}
|
||||
} else {
|
||||
if (!(this.statMap.has(id)&&this.statMap.get(id))) {
|
||||
this.statMap.set(id,0);
|
||||
if (!(this.statMap.has(id) && this.statMap.get(id))) {
|
||||
this.statMap.set(id, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,30 +247,30 @@ class Custom{
|
|||
let type = this.statMap.get("type").toLowerCase();
|
||||
console.log(type);
|
||||
if (weaponTypes.includes(type)) {
|
||||
for (const n of ["nDam","eDam","tDam","wDam","fDam","aDam"]) {
|
||||
for (const n of ["nDam", "eDam", "tDam", "wDam", "fDam", "aDam"]) {
|
||||
if (!(this.statMap.has(n) && this.statMap.get(n))) {
|
||||
this.statMap.set(n,"0-0");
|
||||
this.statMap.set(n, "0-0");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const n of ["nDam","eDam","tDam","wDam","fDam","aDam"]) {
|
||||
for (const n of ["nDam", "eDam", "tDam", "wDam", "fDam", "aDam"]) {
|
||||
if (this.statMap.has(n)) {
|
||||
this.statMap.delete(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (this.statMap.get("type")) {
|
||||
this.statMap.set("type",this.statMap.get("type").toLowerCase());
|
||||
this.statMap.set("type", this.statMap.get("type").toLowerCase());
|
||||
if (armorTypes.includes(this.statMap.get("type"))) {
|
||||
this.statMap.set("category","armor");
|
||||
this.statMap.set("category", "armor");
|
||||
} else if (accessoryTypes.includes(this.statMap.get("type"))) {
|
||||
this.statMap.set("category","accessory");
|
||||
this.statMap.set("category", "accessory");
|
||||
} else if (weaponTypes.includes(this.statMap.get("type"))) {
|
||||
this.statMap.set("category","weapon");
|
||||
this.statMap.set("category", "weapon");
|
||||
} else if (consumableTypes.includes(this.statMap.get("type"))) {
|
||||
this.statMap.set("category","consumable");
|
||||
this.statMap.set("category", "consumable");
|
||||
} else if (tomeTypes.includes(this.statMap.get("type"))) {
|
||||
this.statMap.set("category", "tome");
|
||||
}
|
||||
|
@ -280,13 +280,13 @@ class Custom{
|
|||
this.statMap.set("crafted", true);
|
||||
|
||||
for (const e of skp_elements) {
|
||||
this.statMap.set(e+"DamLow", this.statMap.get(e+"Dam"));
|
||||
this.statMap.set(e + "DamLow", this.statMap.get(e + "Dam"));
|
||||
}
|
||||
this.statMap.set("nDamLow", this.statMap.get("nDam"));
|
||||
this.statMap.set("hpLow", this.statMap.get("hp"));
|
||||
for (const e of skp_order) {
|
||||
this.statMap.get("minRolls").set(e,this.statMap.get(e));
|
||||
this.statMap.get("maxRolls").set(e,this.statMap.get(e));
|
||||
this.statMap.get("minRolls").set(e, this.statMap.get(e));
|
||||
this.statMap.get("maxRolls").set(e, this.statMap.get(e));
|
||||
}
|
||||
// for (const e of ["durability", "duration"]) {
|
||||
// if (this.statMap.get(e) === "") {
|
||||
|
@ -296,15 +296,15 @@ class Custom{
|
|||
// }
|
||||
// }
|
||||
|
||||
this.statMap.set("lvlLow",this.statMap.get("lvl"));
|
||||
this.statMap.set("lvlLow", this.statMap.get("lvl"));
|
||||
if (this.statMap.get("category") === "weapon") {
|
||||
//this is for powder purposes.
|
||||
//users will likely not stick to the 0.9,1.1 rule because custom item. We will get around this by breaking everything and rewarding users for sticking to 0.9,1.1.
|
||||
this.statMap.set("nDamBaseLow", Math.floor((parseFloat(this.statMap.get("nDamLow")) + parseFloat(this.statMap.get("nDam"))) / 2) );
|
||||
this.statMap.set("nDamBaseHigh", Math.floor((parseFloat(this.statMap.get("nDamLow")) + parseFloat(this.statMap.get("nDam"))) / 2) );
|
||||
this.statMap.set("nDamBaseLow", Math.floor((parseFloat(this.statMap.get("nDamLow")) + parseFloat(this.statMap.get("nDam"))) / 2));
|
||||
this.statMap.set("nDamBaseHigh", Math.floor((parseFloat(this.statMap.get("nDamLow")) + parseFloat(this.statMap.get("nDam"))) / 2));
|
||||
for (const e in skp_elements) {
|
||||
this.statMap.set(skp_elements[e]+"DamBaseLow", Math.floor((parseFloat(this.statMap.get(skp_elements[e]+"DamLow")) + parseFloat(this.statMap.get(skp_elements[e]+"Dam"))) / 2));
|
||||
this.statMap.set(skp_elements[e]+"DamBaseHigh", Math.floor((parseFloat(this.statMap.get(skp_elements[e]+"DamLow")) + parseFloat(this.statMap.get(skp_elements[e]+"Dam"))) / 2));
|
||||
this.statMap.set(skp_elements[e] + "DamBaseLow", Math.floor((parseFloat(this.statMap.get(skp_elements[e] + "DamLow")) + parseFloat(this.statMap.get(skp_elements[e] + "Dam"))) / 2));
|
||||
this.statMap.set(skp_elements[e] + "DamBaseHigh", Math.floor((parseFloat(this.statMap.get(skp_elements[e] + "DamLow")) + parseFloat(this.statMap.get(skp_elements[e] + "Dam"))) / 2));
|
||||
}
|
||||
this.statMap.set("ingredPowders", []);
|
||||
}
|
||||
|
@ -312,7 +312,7 @@ class Custom{
|
|||
|
||||
if (this.statMap.get("category") !== "weapon") {
|
||||
this.statMap.set("atkSpd", "");
|
||||
for (const n in ["nDam","eDam","tDam","wDam","fDam","aDam"]) {
|
||||
for (const n in ["nDam", "eDam", "tDam", "wDam", "fDam", "aDam"]) {
|
||||
//this.statMap.set(n,"");
|
||||
}
|
||||
} else {
|
||||
|
@ -325,12 +325,12 @@ class Custom{
|
|||
} else {
|
||||
this.statMap.set("displayName", "Custom Item");
|
||||
}
|
||||
this.statMap.set("powders",[]);
|
||||
this.statMap.set("powders", []);
|
||||
|
||||
|
||||
this.statMap.set("reqs",[this.statMap.get("strReq"),this.statMap.get("dexReq"),this.statMap.get("intReq"),this.statMap.get("defReq"),this.statMap.get("agiReq")]);
|
||||
this.statMap.set("skillpoints", [this.statMap.get("str"),this.statMap.get("dex"),this.statMap.get("int"),this.statMap.get("def"),this.statMap.get("agi")]);
|
||||
|
||||
this.statMap.set("reqs", [this.statMap.get("strReq"), this.statMap.get("dexReq"), this.statMap.get("intReq"), this.statMap.get("defReq"), this.statMap.get("agiReq")]);
|
||||
this.statMap.set("skillpoints", [this.statMap.get("str"), this.statMap.get("dex"), this.statMap.get("int"), this.statMap.get("def"), this.statMap.get("agi")]);
|
||||
|
||||
|
||||
this.statMap.set("restrict", "Custom Item")
|
||||
}
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
const custom_url_base = location.href.split("#")[0];
|
||||
const custom_url_tag = location.hash.slice(1);
|
||||
|
||||
const CUSTOM_BUILD_VERSION = "7.0.1";
|
||||
|
||||
function setTitle() {
|
||||
let text = "WynnCustom version "+CUSTOM_BUILD_VERSION;
|
||||
document.getElementById("header").classList.add("funnynumber");
|
||||
document.getElementById("header").textContent = text;
|
||||
}
|
||||
|
||||
setTitle();
|
||||
|
||||
let player_custom_item;
|
||||
let player_custom_ing;
|
||||
let base_item; //the item that a user starts from, if any
|
||||
|
@ -51,7 +41,6 @@ function init_customizer() {
|
|||
|
||||
|
||||
} catch (error) {
|
||||
console.log("If you are seeing this while building, do not worry. Oherwise, panic! (jk contact ferricles)");
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +63,7 @@ function calculateCustom() {
|
|||
statMap.set("maxRolls", new Map());
|
||||
let inputs = document.getElementsByTagName("input");
|
||||
|
||||
if (document.getElementById("fixID-choice").textContent === "yes") {//Fixed IDs
|
||||
if (document.getElementById("fixID-choice").classList.contains("toggleOn")) {//Fixed IDs
|
||||
for (const input of inputs) {
|
||||
if (input.id.includes("-min") || input.id.includes("-max")) {
|
||||
continue;
|
||||
|
@ -187,33 +176,34 @@ function calculateCustom() {
|
|||
|
||||
player_custom_item = new Custom(statMap);
|
||||
|
||||
document.getElementById("right-container").classList.remove("sticky-box");
|
||||
let custom_str = encodeCustom(player_custom_item.statMap, true);
|
||||
location.hash = custom_str;
|
||||
player_custom_item.setHash(custom_str);
|
||||
//console.log(player_custom_item.statMap.get("hash"));
|
||||
|
||||
|
||||
displayExpandedItem(player_custom_item.statMap, "custom-stats");
|
||||
|
||||
//console.log(player_custom_item.statMap);
|
||||
displaysq2ExpandedItem(player_custom_item.statMap, "custom-stats");
|
||||
|
||||
}catch (error) {
|
||||
//USE THE ERROR <p>S!
|
||||
//The error elements no longer exist in the page. Add them back if needed.
|
||||
|
||||
// console.log(error.stack);
|
||||
|
||||
let msg = error.stack;
|
||||
let lines = msg.split("\n");
|
||||
let header = document.getElementById("header");
|
||||
header.textContent = "";
|
||||
for (const line of lines) {
|
||||
let p = document.createElement("p");
|
||||
p.classList.add("itemp");
|
||||
p.textContent = line;
|
||||
header.appendChild(p);
|
||||
for (line of lines) {
|
||||
console.log(line);
|
||||
}
|
||||
let p2 = document.createElement("p");
|
||||
p2.textContent = "If you believe this is an error, contact hppeng on forums or discord.";
|
||||
header.appendChild(p2);
|
||||
// let header = document.getElementById("header");
|
||||
// header.textContent = "";
|
||||
// for (const line of lines) {
|
||||
// let p = document.createElement("p");
|
||||
// p.classList.add("itemp");
|
||||
// p.textContent = line;
|
||||
// header.appendChild(p);
|
||||
// }
|
||||
// let p2 = document.createElement("p");
|
||||
// p2.textContent = "If you believe this is an error, contact hppeng on forums or discord.";
|
||||
// header.appendChild(p2);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -236,13 +226,13 @@ function decodeCustom(custom_url_tag) {
|
|||
if (fixID) {
|
||||
statMap.set("fixId", true);
|
||||
toggleButton("fixID-choice");
|
||||
toggleYN("fixID-choice");
|
||||
toggleFixed(document.getElementById("fixID-choice"));
|
||||
toggleFixed(document.getElementById("fixID-choice").classList.contains("toggleOn"));
|
||||
}
|
||||
while (tag !== "") {
|
||||
let id = ci_save_order[Base64.toInt(tag.slice(0,2))];
|
||||
//console.log(tag.slice(0, 2) + ": " + id);
|
||||
let len = Base64.toInt(tag.slice(2,4));
|
||||
|
||||
if (rolledIDs.includes(id)) {
|
||||
let sign = parseInt(tag.slice(4,5),10);
|
||||
let minRoll = Base64.toInt(tag.slice(5,5+len));
|
||||
|
@ -350,27 +340,15 @@ function populateFields() {
|
|||
|
||||
|
||||
|
||||
/* Changes an element's text content from yes to no or vice versa
|
||||
*/
|
||||
function toggleYN(elemId) {
|
||||
let elem = document.getElementById(elemId);
|
||||
if (elem.textContent && elem.textContent === "no") {
|
||||
elem.textContent = "yes";
|
||||
} else if (elem.textContent === "yes") {
|
||||
elem.textContent = "no";
|
||||
} else {
|
||||
elem.textContent = "no";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fixed : a boolean for the state of the fixID button.
|
||||
*/
|
||||
function toggleFixed(fixed) {
|
||||
function toggleFixed() {
|
||||
let fixedID_bool = document.getElementById("fixID-choice").classList.contains("toggleOn");
|
||||
for (const id of rolledIDs) {
|
||||
let elem = document.getElementById(id);
|
||||
if (elem) {
|
||||
if (fixed.textContent === "yes") { //now fixed IDs -> go to 1 input
|
||||
if (fixedID_bool) { //now fixed IDs -> go to 1 input
|
||||
document.getElementById(id+"-choice-fixed-container").style = "";
|
||||
document.getElementById(id+"-choice-container").style = "display:none";
|
||||
} else { //now rollable -> go to 2 inputs
|
||||
|
@ -392,7 +370,7 @@ function useBaseItem(elem) {
|
|||
//Check items db.
|
||||
for (const [name,itemObj] of itemMap) {
|
||||
if (itemName === name) {
|
||||
baseItem = expandItem(itemObj, []);
|
||||
baseItem = expandItem(itemObj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,190 +1,299 @@
|
|||
const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]);
|
||||
// Calculate spell damage given a spell elemental conversion table, and a spell multiplier.
|
||||
// If spell mult is 0, its melee damage and we don't multiply by attack speed.
|
||||
// externalStats should be a map
|
||||
function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier, externalStats) {
|
||||
let buildStats = new Map(stats);
|
||||
let tooltipinfo = new Map();
|
||||
//6x for damages, normal min normal max crit min crit max
|
||||
let damageformulas = [["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "]];
|
||||
|
||||
if(externalStats) { //if nothing is passed in, then this hopefully won't trigger
|
||||
for (const entry of externalStats) {
|
||||
const key = entry[0];
|
||||
const value = entry[1];
|
||||
if (typeof value === "number") {
|
||||
buildStats.set(key, buildStats.get(key) + value);
|
||||
} else if (Array.isArray(value)) {
|
||||
arr = [];
|
||||
for (let j = 0; j < value.length; j++) {
|
||||
arr[j] = buildStats.get(key)[j] + value[j];
|
||||
}
|
||||
buildStats.set(key, arr);
|
||||
}
|
||||
function get_base_dps(item) {
|
||||
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
||||
//SUPER JANK @HPP PLS FIX
|
||||
if (item.get("tier") !== "Crafted") {
|
||||
let total_damage = 0;
|
||||
for (const damage_k of damage_keys) {
|
||||
damages = item.get(damage_k);
|
||||
total_damage += damages[0] + damages[1];
|
||||
}
|
||||
}
|
||||
|
||||
let powders = weapon.get("powders").slice();
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
let damages = [];
|
||||
const rawDamages = buildStats.get("damageRaw");
|
||||
for (let i = 0; i < rawDamages.length; i++) {
|
||||
const damage_vals = rawDamages[i].split("-").map(Number);
|
||||
damages.push(damage_vals);
|
||||
}
|
||||
|
||||
// Applying spell conversions
|
||||
let neutralBase = damages[0].slice();
|
||||
let neutralRemainingRaw = damages[0].slice();
|
||||
|
||||
|
||||
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
|
||||
|
||||
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
|
||||
//1st round - apply each as ingred, 2nd round - apply as normal
|
||||
if (weapon.get("tier") === "Crafted") {
|
||||
let damageBases = buildStats.get("damageBases").slice();
|
||||
for (const p of powders.concat(weapon.get("ingredPowders"))) {
|
||||
let powder = powderStats[p]; //use min, max, and convert
|
||||
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
|
||||
let diff = Math.floor(damageBases[0] * powder.convert/100);
|
||||
damageBases[0] -= diff;
|
||||
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
|
||||
}
|
||||
//update all damages
|
||||
if(!weapon.get("custom")) {
|
||||
for (let i = 0; i < damages.length; i++) {
|
||||
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
|
||||
}
|
||||
}
|
||||
|
||||
neutralRemainingRaw = damages[0].slice();
|
||||
neutralBase = damages[0].slice();
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let conversionRatio = spellConversions[i+1]/100;
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
||||
damages[i+1][0] = Math.floor(round_near(damages[i+1][0] + min_diff));
|
||||
damages[i+1][1] = Math.floor(round_near(damages[i+1][1] + max_diff));
|
||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
||||
}
|
||||
|
||||
//apply powders to weapon
|
||||
for (const powderID of powders) {
|
||||
const powder = powderStats[powderID];
|
||||
// Bitwise to force conversion to integer (integer division).
|
||||
const element = (powderID/6) | 0;
|
||||
let conversionRatio = powder.convert/100;
|
||||
if (neutralRemainingRaw[1] > 0) {
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
||||
damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff));
|
||||
damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff));
|
||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
||||
}
|
||||
damages[element+1][0] += powder.min;
|
||||
damages[element+1][1] += powder.max;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//console.log(tooltipinfo);
|
||||
|
||||
damages[0] = neutralRemainingRaw;
|
||||
tooltipinfo.set("damageBases", damages);
|
||||
|
||||
let damageMult = damageMultiplier;
|
||||
let melee = false;
|
||||
// If we are doing melee calculations:
|
||||
tooltipinfo.set("dmgMult", damageMult);
|
||||
if (spellMultiplier == 0) {
|
||||
spellMultiplier = 1;
|
||||
melee = true;
|
||||
return total_damage * attack_speed_mult / 2;
|
||||
}
|
||||
else {
|
||||
tooltipinfo.set("dmgMult", `(${tooltipinfo.get("dmgMult")} * ${spellMultiplier} * ${baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))]})`)
|
||||
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))];
|
||||
}
|
||||
//console.log(damages);
|
||||
//console.log(damageMult);
|
||||
tooltipinfo.set("rawModifier", `(${rawModifier} * ${spellMultiplier} * ${damageMultiplier})`);
|
||||
rawModifier *= spellMultiplier * damageMultiplier;
|
||||
let totalDamNorm = [0, 0];
|
||||
let totalDamCrit = [0, 0];
|
||||
let damages_results = [];
|
||||
// 0th skillpoint is strength, 1st is dex.
|
||||
let str = total_skillpoints[0];
|
||||
let strBoost = 1 + skillPointsToPercentage(str);
|
||||
if(!melee){
|
||||
let baseDam = rawModifier * strBoost;
|
||||
let baseDamCrit = rawModifier * (1 + strBoost);
|
||||
totalDamNorm = [baseDam, baseDam];
|
||||
totalDamCrit = [baseDamCrit, baseDamCrit];
|
||||
for (let arr of damageformulas) {
|
||||
arr = arr.map(x => x + " + " +tooltipinfo.get("rawModifier"));
|
||||
let total_damage_min = 0;
|
||||
let total_damage_max = 0;
|
||||
for (const damage_k of damage_keys) {
|
||||
damages = item.get(damage_k);
|
||||
total_damage_min += damages[0][0] + damages[0][1];
|
||||
total_damage_max += damages[1][0] + damages[1][1];
|
||||
}
|
||||
total_damage_min = attack_speed_mult * total_damage_min / 2;
|
||||
total_damage_max = attack_speed_mult * total_damage_max / 2;
|
||||
return [total_damage_min, total_damage_max];
|
||||
}
|
||||
let staticBoost = (pctModifier / 100.);
|
||||
tooltipinfo.set("staticBoost", `${(pctModifier/ 100.).toFixed(2)}`);
|
||||
tooltipinfo.set("skillBoost",["","","","","",""]);
|
||||
let skillBoost = [0];
|
||||
for (let i in total_skillpoints) {
|
||||
skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get("damageBonus")[i] / 100.);
|
||||
tooltipinfo.get("skillBoost")[parseInt(i,10)+1] = `(${skillPointsToPercentage(total_skillpoints[i]).toFixed(2)} + ${(buildStats.get("damageBonus")[i]/100.).toFixed(2)})`
|
||||
}
|
||||
tooltipinfo.get("skillBoost")[0] = undefined;
|
||||
|
||||
for (let i in damages) {
|
||||
let damageBoost = 1 + skillBoost[i] + staticBoost;
|
||||
tooltipinfo.set("damageBoost", `(1 + ${(tooltipinfo.get("skillBoost")[i] ? tooltipinfo.get("skillBoost")[i] + " + " : "")} ${tooltipinfo.get("staticBoost")})`)
|
||||
damages_results.push([
|
||||
Math.max(damages[i][0] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal min
|
||||
Math.max(damages[i][1] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal max
|
||||
Math.max(damages[i][0] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit min
|
||||
Math.max(damages[i][1] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit max
|
||||
]);
|
||||
damageformulas[i][0] += `(max((${tooltipinfo.get("damageBases")[i][0]} * ${strBoost} * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))`
|
||||
damageformulas[i][1] += `(max((${tooltipinfo.get("damageBases")[i][1]} * ${strBoost} * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))`
|
||||
damageformulas[i][2] += `(max((${tooltipinfo.get("damageBases")[i][0]} * ${strBoost} * 2 * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))`
|
||||
damageformulas[i][3] += `(max((${tooltipinfo.get("damageBases")[i][1]} * ${strBoost} * 2 * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))`
|
||||
totalDamNorm[0] += damages_results[i][0];
|
||||
totalDamNorm[1] += damages_results[i][1];
|
||||
totalDamCrit[0] += damages_results[i][2];
|
||||
totalDamCrit[1] += damages_results[i][3];
|
||||
}
|
||||
if (melee) {
|
||||
totalDamNorm[0] += Math.max(strBoost*rawModifier, -damages_results[0][0]);
|
||||
totalDamNorm[1] += Math.max(strBoost*rawModifier, -damages_results[0][1]);
|
||||
totalDamCrit[0] += Math.max((strBoost+1)*rawModifier, -damages_results[0][2]);
|
||||
totalDamCrit[1] += Math.max((strBoost+1)*rawModifier, -damages_results[0][3]);
|
||||
}
|
||||
damages_results[0][0] += strBoost*rawModifier;
|
||||
damages_results[0][1] += strBoost*rawModifier;
|
||||
damages_results[0][2] += (strBoost + 1)*rawModifier;
|
||||
damages_results[0][3] += (strBoost + 1)*rawModifier;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
damageformulas[0][i] += ` + (${strBoost} * ${tooltipinfo.get("rawModifier")})`
|
||||
}
|
||||
for (let i = 2; i < 4; i++) {
|
||||
damageformulas[0][i] += ` + (2 * ${strBoost} * ${tooltipinfo.get("rawModifier")})`
|
||||
}
|
||||
|
||||
if (totalDamNorm[0] < 0) totalDamNorm[0] = 0;
|
||||
if (totalDamNorm[1] < 0) totalDamNorm[1] = 0;
|
||||
if (totalDamCrit[0] < 0) totalDamCrit[0] = 0;
|
||||
if (totalDamCrit[1] < 0) totalDamCrit[1] = 0;
|
||||
|
||||
tooltipinfo.set("damageformulas", damageformulas);
|
||||
return [totalDamNorm, totalDamCrit, damages_results, tooltipinfo];
|
||||
}
|
||||
|
||||
|
||||
function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) {
|
||||
// TODO: Roll all the loops together maybe
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
// 1. Get weapon damage (with powders).
|
||||
let weapon_damages;
|
||||
if (weapon.get('tier') === 'Crafted') {
|
||||
weapon_damages = damage_keys.map(x => weapon.get(x)[1]);
|
||||
}
|
||||
else {
|
||||
weapon_damages = damage_keys.map(x => weapon.get(x));
|
||||
}
|
||||
let present = weapon.get(damage_present_key);
|
||||
|
||||
// 2. Conversions.
|
||||
// 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here.
|
||||
let damages = [];
|
||||
const neutral_convert = conversions[0] / 100;
|
||||
let weapon_min = 0;
|
||||
let weapon_max = 0;
|
||||
for (const damage of weapon_damages) {
|
||||
let min_dmg = damage[0] * neutral_convert;
|
||||
let max_dmg = damage[1] * neutral_convert;
|
||||
damages.push([min_dmg, max_dmg]);
|
||||
weapon_min += damage[0];
|
||||
weapon_max += damage[1];
|
||||
}
|
||||
|
||||
// 2.2. Next, apply elemental conversions using damage computed in step 1.1.
|
||||
// Also, track which elements are present. (Add onto those present in the weapon itself.)
|
||||
let total_convert = 0; //TODO get confirmation that this is how raw works.
|
||||
for (let i = 1; i <= 5; ++i) {
|
||||
if (conversions[i] > 0) {
|
||||
const conv_frac = conversions[i]/100;
|
||||
damages[i][0] += conv_frac * weapon_min;
|
||||
damages[i][1] += conv_frac * weapon_max;
|
||||
present[i] = true;
|
||||
total_convert += conv_frac
|
||||
}
|
||||
}
|
||||
total_convert += conversions[0]/100;
|
||||
|
||||
// Also theres prop and rainbow!!
|
||||
const damage_elements = ['n'].concat(skp_elements); // netwfa
|
||||
|
||||
if (!ignore_speed) {
|
||||
// 3. Apply attack speed multiplier. Ignored for melee single hit
|
||||
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(weapon.get("atkSpd"))];
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
damages[i][0] *= attack_speed_mult;
|
||||
damages[i][1] *= attack_speed_mult;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Add additive damage. TODO: Is there separate additive damage?
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
if (present[i]) {
|
||||
damages[i][0] += stats.get(damage_elements[i]+'DamAddMin');
|
||||
damages[i][1] += stats.get(damage_elements[i]+'DamAddMax');
|
||||
}
|
||||
}
|
||||
|
||||
// 5. ID bonus.
|
||||
let specific_boost_str = 'Md';
|
||||
if (use_spell_damage) {
|
||||
specific_boost_str = 'Sd';
|
||||
}
|
||||
// 5.1: %boost application
|
||||
let skill_boost = [0]; // no neutral skillpoint booster
|
||||
for (let i in skp_order) {
|
||||
const skp = skp_order[i];
|
||||
skill_boost.push(skillPointsToPercentage(stats.get(skp)) * skillpoint_damage_mult[i]);
|
||||
}
|
||||
let static_boost = (stats.get(specific_boost_str.toLowerCase()+'Pct') + stats.get('damPct')) / 100;
|
||||
|
||||
// These do not count raw damage. I think. Easy enough to change
|
||||
let total_min = 0;
|
||||
let total_max = 0;
|
||||
for (let i in damages) {
|
||||
let damage_prefix = damage_elements[i] + specific_boost_str;
|
||||
let damageBoost = 1 + skill_boost[i] + static_boost
|
||||
+ ((stats.get(damage_prefix+'Pct') + stats.get(damage_elements[i]+'DamPct')) /100);
|
||||
damages[i][0] *= Math.max(damageBoost, 0);
|
||||
damages[i][1] *= Math.max(damageBoost, 0);
|
||||
// Collect total damage post %boost
|
||||
total_min += damages[i][0];
|
||||
total_max += damages[i][1];
|
||||
}
|
||||
|
||||
let total_elem_min = total_min - damages[0][0];
|
||||
let total_elem_max = total_max - damages[0][1];
|
||||
|
||||
// 5.2: Raw application.
|
||||
let prop_raw = stats.get(specific_boost_str.toLowerCase()+'Raw') + stats.get('damRaw');
|
||||
let rainbow_raw = stats.get('r'+specific_boost_str+'Raw') + stats.get('rDamRaw');
|
||||
for (let i in damages) {
|
||||
let damages_obj = damages[i];
|
||||
let damage_prefix = damage_elements[i] + specific_boost_str;
|
||||
// Normie raw
|
||||
let raw_boost = 0;
|
||||
if (present[i]) {
|
||||
raw_boost += stats.get(damage_prefix+'Raw') + stats.get(damage_elements[i]+'DamRaw');
|
||||
}
|
||||
// Next, rainraw and propRaw
|
||||
let min_boost = raw_boost;
|
||||
let max_boost = raw_boost;
|
||||
if (total_max > 0) { // TODO: what about total negative all raw?
|
||||
if (total_min > 0) {
|
||||
min_boost += (damages_obj[0] / total_min) * prop_raw;
|
||||
}
|
||||
max_boost += (damages_obj[1] / total_max) * prop_raw;
|
||||
}
|
||||
if (i != 0 && total_elem_max > 0) { // rainraw TODO above
|
||||
if (total_elem_min > 0) {
|
||||
min_boost += (damages_obj[0] / total_elem_min) * rainbow_raw;
|
||||
}
|
||||
max_boost += (damages_obj[1] / total_elem_max) * rainbow_raw;
|
||||
}
|
||||
damages_obj[0] += min_boost * total_convert;
|
||||
damages_obj[1] += max_boost * total_convert;
|
||||
}
|
||||
|
||||
// 6. Strength boosters
|
||||
// str/dex, as well as any other mutually multiplicative effects
|
||||
let strBoost = 1 + skill_boost[1];
|
||||
let total_dam_norm = [0, 0];
|
||||
let total_dam_crit = [0, 0];
|
||||
let damages_results = [];
|
||||
const damage_mult = stats.get("damageMultiplier");
|
||||
|
||||
for (const damage of damages) {
|
||||
const res = [
|
||||
damage[0] * strBoost * damage_mult, // Normal min
|
||||
damage[1] * strBoost * damage_mult, // Normal max
|
||||
damage[0] * (strBoost + 1) * damage_mult, // Crit min
|
||||
damage[1] * (strBoost + 1) * damage_mult, // Crit max
|
||||
];
|
||||
damages_results.push(res);
|
||||
total_dam_norm[0] += res[0];
|
||||
total_dam_norm[1] += res[1];
|
||||
total_dam_crit[0] += res[2];
|
||||
total_dam_crit[1] += res[3];
|
||||
}
|
||||
|
||||
if (total_dam_norm[0] < 0) total_dam_norm[0] = 0;
|
||||
if (total_dam_norm[1] < 0) total_dam_norm[1] = 0;
|
||||
if (total_dam_crit[0] < 0) total_dam_crit[0] = 0;
|
||||
if (total_dam_crit[1] < 0) total_dam_crit[1] = 0;
|
||||
|
||||
return [total_dam_norm, total_dam_crit, damages_results];
|
||||
}
|
||||
|
||||
/*
|
||||
Spell schema:
|
||||
|
||||
spell: {
|
||||
name: str internal string name for the spell. Unique identifier, also display
|
||||
cost: Optional[int] ignored for spells that are not id 1-4
|
||||
base_spell: int spell index. 0-4 are reserved (0 is melee, 1-4 is common 4 spells)
|
||||
spell_type: str [TODO: DEPRECATED/REMOVE] "healing" or "damage"
|
||||
scaling: Optional[str] [DEFAULT: "spell"] "melee" or "spell"
|
||||
use_atkspd: Optional[bool] [DEFAULT: true] true to factor attack speed, false otherwise.
|
||||
display: Optional[str] [DEFAULT: "total"] "total" to sum all parts. Or, the name of a spell part
|
||||
parts: List[part] Parts of this spell (different stuff the spell does basically)
|
||||
}
|
||||
|
||||
NOTE: when using `replace_spell` on an existing spell, all fields become optional.
|
||||
Specified fields overwrite existing fields; unspecified fields are left unchanged.
|
||||
|
||||
|
||||
There are three possible spell "part" types: damage, heal, and total.
|
||||
|
||||
part: spell_damage | spell_heal | spell_total
|
||||
|
||||
spell_damage: {
|
||||
name: str != "total" Name of the part.
|
||||
type: "damage" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||
multipliers: array[num, 6] floating point spellmults (though supposedly wynn only supports integer mults)
|
||||
}
|
||||
spell_heal: {
|
||||
name: str != "total" Name of the part.
|
||||
type: "heal" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||
power: num floating point healing power (1 is 100% of max hp).
|
||||
}
|
||||
spell_total: {
|
||||
name: str != "total" Name of the part.
|
||||
type: "total" [TODO: DEPRECATED/REMOVE] flag signaling what type of part it is. Can infer from fields
|
||||
hits: Map[str, num] Keys are other part names, numbers are the multipliers. Undefined behavior if subparts
|
||||
are not the same type of spell. Can only pull from spells defined before it.
|
||||
}
|
||||
|
||||
|
||||
Before passing to display, use the following structs.
|
||||
NOTE: total is collapsed into damage or healing.
|
||||
|
||||
spell_damage: {
|
||||
type: "damage" Internal use
|
||||
name: str Display name of part. Should be human readable
|
||||
normal_min: array[num, 6] floating point damages (no crit, min), can be less than zero. Order: NETWFA
|
||||
normal_max: array[num, 6] floating point damages (no crit, max)
|
||||
normal_total: array[num, 2] (min, max) noncrit total damage (not negative)
|
||||
crit_min: array[num, 6] floating point damages (crit, min), can be less than zero. Order: NETWFA
|
||||
crit_max: array[num, 6] floating point damages (crit, max)
|
||||
crit_total: array[num, 2] (min, max) crit total damage (not negative)
|
||||
}
|
||||
spell_heal: {
|
||||
type: "heal" Internal use
|
||||
name: str Display name of part. Should be human readable
|
||||
heal_amount: num floating point HP healed (self)
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
const default_spells = {
|
||||
wand: [{
|
||||
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||
name: "Wand Melee", // TODO: name for melee attacks?
|
||||
base_spell: 0,
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Melee",
|
||||
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||
}, {
|
||||
name: "Heal", // TODO: name for melee attacks? // JUST FOR TESTING...
|
||||
base_spell: 1,
|
||||
display: "Total Heal",
|
||||
parts: [
|
||||
{ name: "First Pulse", power: 0.12 },
|
||||
{ name: "Second and Third Pulses", power: 0.06 },
|
||||
{ name: "Total Heal", hits: { "First Pulse": 1, "Second and Third Pulses": 2 } }
|
||||
]
|
||||
}],
|
||||
spear: [{
|
||||
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||
name: "Melee", // TODO: name for melee attacks?
|
||||
base_spell: 0,
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Melee",
|
||||
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||
}],
|
||||
bow: [{
|
||||
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||
name: "Bow Shot", // TODO: name for melee attacks?
|
||||
base_spell: 0,
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Single Shot",
|
||||
parts: [{ name: "Single Shot", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||
}],
|
||||
dagger: [{
|
||||
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||
name: "Melee", // TODO: name for melee attacks?
|
||||
base_spell: 0,
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Melee",
|
||||
parts: [{ name: "Melee", multipliers: [100, 0, 0, 0, 0, 0] }]
|
||||
}],
|
||||
relik: [{
|
||||
type: "replace_spell", // not needed but makes this usable as an "abil part"
|
||||
name: "Relik Melee", // TODO: name for melee attacks?
|
||||
base_spell: 0,
|
||||
spell_type: "damage",
|
||||
scaling: "melee", use_atkspd: false,
|
||||
display: "Total",
|
||||
parts: [
|
||||
{ name: "Single Beam", multipliers: [33, 0, 0, 0, 0, 0] },
|
||||
{ name: "Total", hits: { "Single Beam": 3 } }
|
||||
]
|
||||
}]
|
||||
};
|
||||
|
||||
const spell_table = {
|
||||
"wand": [
|
||||
|
|
67
js/dev.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
function init_dev() {
|
||||
let sections = document.getElementsByClassName("section");
|
||||
|
||||
for (const section of sections) {
|
||||
//so clicking works
|
||||
section.classList.add("down");
|
||||
|
||||
//add title and toggle character
|
||||
let title_row = document.createElement("div");
|
||||
title_row.classList.add("row", "section-title");
|
||||
let title = document.createElement("div");
|
||||
title.classList.add("col");
|
||||
title.textContent = section.title ? section.title : "";
|
||||
title_row.appendChild(title);
|
||||
section.insertBefore(title_row, section.firstChild);
|
||||
|
||||
let toggle_char = document.createElement("div");
|
||||
toggle_char.classList.add("col-auto", "arrow");
|
||||
toggle_char.textContent = "V";
|
||||
title_row.appendChild(toggle_char);
|
||||
title_row.addEventListener("click", (event) =>
|
||||
{
|
||||
toggleSection(section);
|
||||
}, false);
|
||||
|
||||
for (const child of section.children) {
|
||||
if (!child.classList.contains("section-title")) {
|
||||
child.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Toggles section content as well as up and down arrow
|
||||
@params:
|
||||
*/
|
||||
function toggleSection(section) {
|
||||
//has down arrow (default state)
|
||||
let down = section.classList.contains("down");
|
||||
let arrow_elem = section.getElementsByClassName("arrow")[0];
|
||||
|
||||
if (down) {
|
||||
section.classList.remove("down");
|
||||
section.classList.add("up");
|
||||
arrow_elem.style.transform = 'rotate(180deg)';
|
||||
|
||||
for (const elem of section.children) {
|
||||
if (!elem.classList.contains("section-title")) {
|
||||
elem.style.display = "";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
section.classList.remove("up");
|
||||
section.classList.add("down");
|
||||
arrow_elem.style.transform = 'rotate(0deg)';
|
||||
for (const elem of section.children) {
|
||||
if (!elem.classList.contains("section-title")) {
|
||||
elem.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
init_dev();
|
1797
js/display.js
|
@ -1,99 +1,3 @@
|
|||
let nonRolledIDs = [
|
||||
"name",
|
||||
"lore",
|
||||
"displayName",
|
||||
"tier",
|
||||
"set",
|
||||
"slots",
|
||||
"type",
|
||||
"material",
|
||||
"drop",
|
||||
"quest",
|
||||
"restrict",
|
||||
"nDam",
|
||||
"fDam",
|
||||
"wDam",
|
||||
"aDam",
|
||||
"tDam",
|
||||
"eDam",
|
||||
"atkSpd",
|
||||
"hp",
|
||||
"fDef",
|
||||
"wDef",
|
||||
"aDef",
|
||||
"tDef",
|
||||
"eDef",
|
||||
"lvl",
|
||||
"classReq",
|
||||
"strReq",
|
||||
"dexReq",
|
||||
"intReq",
|
||||
"defReq",
|
||||
"agiReq","str",
|
||||
"dex",
|
||||
"int",
|
||||
"agi",
|
||||
"def",
|
||||
"fixID",
|
||||
"category",
|
||||
"id",
|
||||
"skillpoints",
|
||||
"reqs",
|
||||
"nDam_",
|
||||
"fDam_",
|
||||
"wDam_",
|
||||
"aDam_",
|
||||
"tDam_",
|
||||
"eDam_",
|
||||
"majorIds"];
|
||||
let rolledIDs = [
|
||||
"hprPct",
|
||||
"mr",
|
||||
"sdPct",
|
||||
"mdPct",
|
||||
"ls",
|
||||
"ms",
|
||||
"xpb",
|
||||
"lb",
|
||||
"ref",
|
||||
"thorns",
|
||||
"expd",
|
||||
"spd",
|
||||
"atkTier",
|
||||
"poison",
|
||||
"hpBonus",
|
||||
"spRegen",
|
||||
"eSteal",
|
||||
"hprRaw",
|
||||
"sdRaw",
|
||||
"mdRaw",
|
||||
"fDamPct",
|
||||
"wDamPct",
|
||||
"aDamPct",
|
||||
"tDamPct",
|
||||
"eDamPct",
|
||||
"fDefPct",
|
||||
"wDefPct",
|
||||
"aDefPct",
|
||||
"tDefPct",
|
||||
"eDefPct",
|
||||
"spPct1",
|
||||
"spRaw1",
|
||||
"spPct2",
|
||||
"spRaw2",
|
||||
"spPct3",
|
||||
"spRaw3",
|
||||
"spPct4",
|
||||
"spRaw4",
|
||||
"rainbowRaw",
|
||||
"sprint",
|
||||
"sprintReg",
|
||||
"jh",
|
||||
"lq",
|
||||
"gXp",
|
||||
"gSpd"
|
||||
];
|
||||
let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ];
|
||||
let colorMap = new Map(
|
||||
[
|
||||
["Normal", "#fff"],
|
||||
|
@ -305,23 +209,24 @@ let posModSuffixes = {
|
|||
/*
|
||||
* Display commands
|
||||
*/
|
||||
let build_overall_display_commands = [
|
||||
"#table",
|
||||
let build_all_display_commands = [
|
||||
"#defense-stats",
|
||||
"str", "dex", "int", "def", "agi",
|
||||
"!spacer",
|
||||
"mr", "ms",
|
||||
"hprRaw", "hprPct",
|
||||
"ls",
|
||||
"sdRaw", "sdPct",
|
||||
"mdRaw", "mdPct",
|
||||
"ref", "thorns",
|
||||
"ls",
|
||||
"poison",
|
||||
"expd",
|
||||
"spd",
|
||||
"atkTier",
|
||||
"!elemental",
|
||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
||||
"!elemental",
|
||||
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
|
||||
"atkTier",
|
||||
"poison",
|
||||
"ref", "thorns",
|
||||
"expd",
|
||||
"spd",
|
||||
"rainbowRaw",
|
||||
"sprint", "sprintReg",
|
||||
"jh",
|
||||
|
@ -331,26 +236,51 @@ let build_overall_display_commands = [
|
|||
"gXp", "gSpd",
|
||||
];
|
||||
|
||||
let item_display_commands = [
|
||||
"#cdiv",
|
||||
let build_offensive_display_commands = [
|
||||
"str", "dex", "int", "def", "agi",
|
||||
"mr", "ms",
|
||||
"sdRaw", "sdPct",
|
||||
"mdRaw", "mdPct",
|
||||
"ref", "thorns",
|
||||
"ls",
|
||||
"poison",
|
||||
"expd",
|
||||
"spd",
|
||||
"atkTier",
|
||||
"rainbowRaw",
|
||||
"!elemental",
|
||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
||||
"!elemental",
|
||||
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
|
||||
];
|
||||
|
||||
let build_basic_display_commands = [
|
||||
'#defense-stats',
|
||||
// defense stats [hp, ehp, hpr, ]
|
||||
// "sPot", // base * atkspd + spell raws
|
||||
// melee potential
|
||||
// "mPot", // melee% * (base * atkspd) + melee raws
|
||||
"mr", "ms",
|
||||
"ls",
|
||||
"poison",
|
||||
"spd",
|
||||
"atkTier",
|
||||
]
|
||||
|
||||
let sq2_item_display_commands = [
|
||||
"displayName",
|
||||
//"type", //REPLACE THIS WITH SKIN
|
||||
"#ldiv",
|
||||
"atkSpd",
|
||||
"#ldiv",
|
||||
"!elemental",
|
||||
"hp",
|
||||
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
||||
"!spacer",
|
||||
"fDef", "wDef", "aDef", "tDef", "eDef",
|
||||
"!elemental",
|
||||
"#ldiv",
|
||||
"classReq",
|
||||
"lvl",
|
||||
"strReq", "dexReq", "intReq", "defReq","agiReq",
|
||||
"#ldiv",
|
||||
"!spacer",
|
||||
"str", "dex", "int", "def", "agi",
|
||||
"#table",
|
||||
"str", "dex", "int", "def", "agi", //jank lmao
|
||||
"hpBonus",
|
||||
"hprRaw", "hprPct",
|
||||
"sdRaw", "sdPct",
|
||||
|
@ -374,11 +304,33 @@ let item_display_commands = [
|
|||
"spRegen",
|
||||
"eSteal",
|
||||
"gXp", "gSpd",
|
||||
"#ldiv",
|
||||
"majorIds",
|
||||
"!spacer",
|
||||
"slots",
|
||||
"!spacer",
|
||||
"set",
|
||||
"lore",
|
||||
"quest",
|
||||
"restrict"
|
||||
];
|
||||
|
||||
let sq2_ing_display_order = [
|
||||
"displayName", //tier will be displayed w/ name
|
||||
"!spacer",
|
||||
"ids",
|
||||
"!spacer",
|
||||
"posMods",
|
||||
"itemIDs",
|
||||
"consumableIDs",
|
||||
"!spacer",
|
||||
"lvl",
|
||||
"skills",
|
||||
]
|
||||
|
||||
let elem_colors = [
|
||||
"#00AA00",
|
||||
"#FFFF55",
|
||||
"#55FFFF",
|
||||
"#FF5555",
|
||||
"#FFFFFF"
|
||||
]
|
||||
|
|
|
@ -213,14 +213,14 @@ function redraw(data) {
|
|||
let tier_mod = tiers_mod.get(tier);
|
||||
let y_max = baseline_y.map(x => 2.1*x*tier_mod*type_mod);
|
||||
let y_min = baseline_y.map(x => 2.0*x*tier_mod*type_mod);
|
||||
line_top.datum(zip(baseline_x, y_max))
|
||||
line_top.datum(zip2(baseline_x, y_max))
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", d => colorMap.get(tier))
|
||||
.attr("d", d3.line()
|
||||
.x(function(d) { return x(d[0]) })
|
||||
.y(function(d) { return y(d[1]) })
|
||||
)
|
||||
line_bot.datum(zip(baseline_x, y_min))
|
||||
line_bot.datum(zip2(baseline_x, y_min))
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", d => colorMap.get(tier))
|
||||
.attr("d", d3.line()
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
//which icons to use
|
||||
let window_storage = window.localStorage;
|
||||
console.log(window_storage);
|
||||
icon_state_stored = window_storage.getItem("newicons");
|
||||
newIcons = true;
|
||||
if (icon_state_stored === "false") {toggleIcons()}
|
||||
|
||||
//REMOVE THIS IN THE REAL VERSION 7 OR SOMETHING
|
||||
window_storage.removeItem("rick");
|
||||
|
||||
/** Toggle icons on the ENTIRE page.
|
||||
*
|
||||
*/
|
||||
|
|
21
js/item.js
|
@ -10,13 +10,6 @@ const item_url_tag = location.hash.slice(1);
|
|||
|
||||
const ITEM_BUILD_VERSION = "7.0.1";
|
||||
|
||||
function setTitle() {
|
||||
let text = "WynnInfo version "+ITEM_BUILD_VERSION;
|
||||
document.getElementById("header").classList.add("funnynumber");
|
||||
document.getElementById("header").textContent = text;
|
||||
}
|
||||
|
||||
setTitle();
|
||||
|
||||
let item;
|
||||
let amp_state = 0; //the level of corkian map used for ID purposes. Default 0.
|
||||
|
@ -27,16 +20,17 @@ function init_itempage() {
|
|||
//displayExpandedItem(expandItem(itemMap.get(item_url_tag).statMap, []), "item-view");
|
||||
try{
|
||||
item = expandItem(itemMap.get(item_url_tag.replaceAll("%20"," ")), []);
|
||||
displayExpandedItem(item, "item-view");
|
||||
displayAdditionalInfo("additional-info", item);
|
||||
displayIDCosts("identification-costs", item);
|
||||
displaysq2ExpandedItem(item, "item-view");
|
||||
displaysq2AdditionalInfo("additional-info", item);
|
||||
displaysq2IDCosts("identification-costs", item);
|
||||
if (item.get("set") && sets[item.get("set")]) {
|
||||
displayAllSetBonuses("set-bonus-info",item.get("set"));
|
||||
displaysq2AllSetBonuses("set-bonus-info",item.get("set"));
|
||||
}
|
||||
console.log(item);
|
||||
displayIDProbabilities("identification-probabilities", item, amp_state);
|
||||
displaysq2IDProbabilities("identification-probabilities", item, amp_state);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.log(error.stack);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +39,6 @@ function init_itempage() {
|
|||
* @param {Number} button_id the ID of the button just pressed.
|
||||
*/
|
||||
function toggleAmps(button_id) {
|
||||
console.log(button_id);
|
||||
amp_state = 0;
|
||||
if (button_id == 0) {return;}
|
||||
else {
|
||||
|
@ -59,7 +52,7 @@ function toggleAmps(button_id) {
|
|||
amp_state = button_id;
|
||||
}
|
||||
}
|
||||
displayIDProbabilities("identification-probabilities", item, amp_state);
|
||||
displaysq2IDProbabilities("identification-probabilities", item, amp_state);
|
||||
}
|
||||
|
||||
|
||||
|
|
29
js/items.js
|
@ -103,15 +103,17 @@ const special_mappings = {
|
|||
};
|
||||
|
||||
let itemFilters = document.getElementById("filter-items");
|
||||
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);
|
||||
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 itemCategories = [ "armor", "accessory", "weapon" ];
|
||||
|
@ -137,9 +139,9 @@ let searchDb;
|
|||
function doItemSearch() {
|
||||
window.scrollTo(0, 0);
|
||||
let queries = [];
|
||||
queries.push('f:name?="'+document.getElementById("name-choice").value.trim()+'"');
|
||||
queries.push('f:name?="'+document.getElementById("item-name-choice").value.trim()+'"');
|
||||
|
||||
let categoryOrType = document.getElementById("category-choice").value;
|
||||
let categoryOrType = document.getElementById("item-category-choice").value;
|
||||
if (itemTypes.includes(categoryOrType)) {
|
||||
queries.push('f:type="'+categoryOrType+'"');
|
||||
}
|
||||
|
@ -147,7 +149,7 @@ function doItemSearch() {
|
|||
queries.push('f:cat="'+categoryOrType+'"');
|
||||
}
|
||||
|
||||
let rarity = document.getElementById("rarity-choice").value;
|
||||
let rarity = document.getElementById("item-rarity-choice").value;
|
||||
if (rarity) {
|
||||
if (rarity === "ANY") {
|
||||
|
||||
|
@ -160,7 +162,7 @@ function doItemSearch() {
|
|||
}
|
||||
}
|
||||
|
||||
let level_dat = document.getElementById("level-choice").value.split("-");
|
||||
let level_dat = document.getElementById("item-level-choice").value.split("-");
|
||||
queries.push('f:(lvl>='+parseInt(level_dat[0])+'&lvl<='+parseInt(level_dat[1])+')');
|
||||
|
||||
for (let i = 1; i <= 4; ++i) {
|
||||
|
@ -178,7 +180,6 @@ function doItemSearch() {
|
|||
}
|
||||
}
|
||||
|
||||
document.getElementById("main").textContent = "";
|
||||
let filterQuery = "true";
|
||||
let sortQueries = [];
|
||||
console.log(queries);
|
||||
|
|
|
@ -239,17 +239,22 @@ function init_items2() {
|
|||
const itemListFooter = document.getElementById('item-list-footer');
|
||||
|
||||
// compile the search db from the item db
|
||||
const searchDb = items.filter(i => !i.remapID).map(i => [i, expandItem(i, [])]);
|
||||
const searchDb = items.filter(i => !i.remapID).map(i => [i, expandItem(i)]);
|
||||
|
||||
// init item list elements
|
||||
const ITEM_LIST_SIZE = 64;
|
||||
const itemEntries = [];
|
||||
for (let i = 0; i < ITEM_LIST_SIZE; i++) {
|
||||
const itemElem = document.createElement('div');
|
||||
itemElem.classList.add('item-entry', 'box');
|
||||
itemElem.setAttribute('id', `item-entry-${i}`);
|
||||
itemElem.classList.add('col-lg-3', 'col-sm-auto', "p-2");
|
||||
// itemElem.setAttribute('id', `item-entry-${i}`);
|
||||
itemList.append(itemElem);
|
||||
itemEntries.push(itemElem);
|
||||
|
||||
const itemElemContained = document.createElement("div");
|
||||
itemElemContained.classList.add("dark-7", "rounded", "px-2", "col-auto");
|
||||
itemElemContained.setAttribute('id', `item-entry-${i}`);
|
||||
itemElem.appendChild(itemElemContained);
|
||||
}
|
||||
|
||||
// create the expression parser
|
||||
|
@ -323,10 +328,10 @@ function init_items2() {
|
|||
for (let i = 0; i < searchMax; i++) {
|
||||
const result = searchResults[i];
|
||||
itemEntries[i].classList.add('visible');
|
||||
displayExpandedItem(result.itemExp, `item-entry-${i}`);
|
||||
displaysq2ExpandedItem(result.itemExp, `item-entry-${i}`);
|
||||
if (result.sortKeys.length > 0) {
|
||||
const sortKeyListContainer = document.createElement('div');
|
||||
sortKeyListContainer.classList.add('itemleft');
|
||||
sortKeyListContainer.classList.add('row');
|
||||
const sortKeyList = document.createElement('ul');
|
||||
sortKeyList.classList.add('item-entry-sort-key', 'itemp', 'T0');
|
||||
sortKeyListContainer.append(sortKeyList);
|
||||
|
|
265
js/load.js
|
@ -1,4 +1,4 @@
|
|||
const DB_VERSION = 90;
|
||||
const DB_VERSION = 93;
|
||||
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
|
||||
|
||||
let db;
|
||||
|
@ -6,7 +6,7 @@ let reload = false;
|
|||
let load_complete = false;
|
||||
let load_in_progress = false;
|
||||
let items;
|
||||
let sets;
|
||||
let sets = new Map();
|
||||
let itemMap;
|
||||
let idMap;
|
||||
let redirectMap;
|
||||
|
@ -14,42 +14,46 @@ let itemLists = new Map();
|
|||
/*
|
||||
* Load item set from local DB. Calls init() on success.
|
||||
*/
|
||||
async function load_local(init_func) {
|
||||
let get_tx = db.transaction(['item_db', 'set_db'], 'readonly');
|
||||
let sets_store = get_tx.objectStore('set_db');
|
||||
let get_store = get_tx.objectStore('item_db');
|
||||
let request = get_store.getAll();
|
||||
request.onerror = function(event) {
|
||||
console.log("Could not read local item db...");
|
||||
}
|
||||
request.onsuccess = function(event) {
|
||||
console.log("Successfully read local item db.");
|
||||
items = request.result;
|
||||
//console.log(items);
|
||||
let request2 = sets_store.openCursor();
|
||||
|
||||
sets = {};
|
||||
request2.onerror = function(event) {
|
||||
console.log("Could not read local set db...");
|
||||
async function load_local() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let get_tx = db.transaction(['item_db', 'set_db'], 'readonly');
|
||||
let sets_store = get_tx.objectStore('set_db');
|
||||
let get_store = get_tx.objectStore('item_db');
|
||||
let request = get_store.getAll();
|
||||
request.onerror = function(event) {
|
||||
reject("Could not read local item db...");
|
||||
}
|
||||
request.onsuccess = function(event) {
|
||||
console.log("Successfully read local item db.");
|
||||
}
|
||||
|
||||
// key-value iteration (hpp don't break this again)
|
||||
// https://stackoverflow.com/questions/47931595/indexeddb-getting-all-data-with-keys
|
||||
let request2 = sets_store.openCursor();
|
||||
request2.onerror = function(event) {
|
||||
reject("Could not read local set db...");
|
||||
}
|
||||
request2.onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (cursor) {
|
||||
sets[cursor.primaryKey] = cursor.value;
|
||||
let key = cursor.primaryKey;
|
||||
let value = cursor.value;
|
||||
sets.set(key, value);
|
||||
cursor.continue();
|
||||
}
|
||||
else {
|
||||
// no more results
|
||||
console.log("Successfully read local set db.");
|
||||
//console.log(sets);
|
||||
init_maps();
|
||||
init_func();
|
||||
load_complete = true;
|
||||
}
|
||||
};
|
||||
get_tx.oncomplete = function(event) {
|
||||
items = request.result;
|
||||
init_maps();
|
||||
load_complete = true;
|
||||
db.close();
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
await get_tx.complete;
|
||||
db.close();
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -91,7 +95,7 @@ function clean_item(item) {
|
|||
/*
|
||||
* Load item set from remote DB (aka a big json file). Calls init() on success.
|
||||
*/
|
||||
async function load(init_func) {
|
||||
async function load() {
|
||||
|
||||
let getUrl = window.location;
|
||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
||||
|
@ -99,18 +103,8 @@ async function load(init_func) {
|
|||
let url = baseUrl + "/compress.json?"+new Date();
|
||||
let result = await (await fetch(url)).json();
|
||||
items = result.items;
|
||||
sets = result.sets;
|
||||
let sets_ = result.sets;
|
||||
|
||||
|
||||
|
||||
// let clear_tx = db.transaction(['item_db', 'set_db'], 'readwrite');
|
||||
// let clear_items = clear_tx.objectStore('item_db');
|
||||
// let clear_sets = clear_tx.objectStore('item_db');
|
||||
//
|
||||
// await clear_items.clear();
|
||||
// await clear_sets.clear();
|
||||
// await clear_tx.complete;
|
||||
|
||||
let add_tx = db.transaction(['item_db', 'set_db'], 'readwrite');
|
||||
add_tx.onabort = function(e) {
|
||||
console.log(e);
|
||||
|
@ -127,78 +121,114 @@ async function load(init_func) {
|
|||
add_promises.push(req);
|
||||
}
|
||||
let sets_store = add_tx.objectStore('set_db');
|
||||
for (const set in sets) {
|
||||
add_promises.push(sets_store.add(sets[set], set));
|
||||
for (const set in sets_) {
|
||||
add_promises.push(sets_store.add(sets_[set], set));
|
||||
sets.set(set, sets_[set]);
|
||||
}
|
||||
add_promises.push(add_tx.complete);
|
||||
Promise.all(add_promises).then((values) => {
|
||||
init_maps();
|
||||
init_func();
|
||||
load_complete = true;
|
||||
});
|
||||
// DB not closed? idfk man
|
||||
|
||||
await Promise.all(add_promises);
|
||||
init_maps();
|
||||
load_complete = true;
|
||||
db.close();
|
||||
}
|
||||
|
||||
function load_init(init_func) {
|
||||
if (load_complete) {
|
||||
console.log("Item db already loaded, skipping load sequence");
|
||||
init_func();
|
||||
return;
|
||||
}
|
||||
let request = window.indexedDB.open('item_db', DB_VERSION);
|
||||
async function load_init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = window.indexedDB.open('item_db', DB_VERSION);
|
||||
|
||||
request.onerror = function() {
|
||||
console.log("DB failed to open...");
|
||||
};
|
||||
request.onerror = function() {
|
||||
reject("DB failed to open...");
|
||||
};
|
||||
|
||||
request.onsuccess = function() {
|
||||
(async function() {
|
||||
request.onsuccess = async function() {
|
||||
db = request.result;
|
||||
if (!reload) {
|
||||
console.log("Using stored data...")
|
||||
load_local(init_func);
|
||||
if (load_in_progress) {
|
||||
while (!load_complete) {
|
||||
await sleep(100);
|
||||
}
|
||||
console.log("Skipping load...")
|
||||
}
|
||||
else {
|
||||
if (load_in_progress) {
|
||||
while (!load_complete) {
|
||||
await sleep(100);
|
||||
}
|
||||
console.log("Skipping load...")
|
||||
init_func();
|
||||
load_in_progress = true
|
||||
if (reload) {
|
||||
console.log("Using new data...")
|
||||
await load();
|
||||
}
|
||||
else {
|
||||
// Not 100% safe... whatever!
|
||||
load_in_progress = true
|
||||
console.log("Using new data...")
|
||||
load(init_func);
|
||||
console.log("Using stored data...")
|
||||
await load_local();
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onupgradeneeded = function(e) {
|
||||
reload = true;
|
||||
request.onupgradeneeded = function(e) {
|
||||
reload = true;
|
||||
|
||||
let db = e.target.result;
|
||||
|
||||
try {
|
||||
db.deleteObjectStore('item_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete item DB. This is probably fine");
|
||||
}
|
||||
try {
|
||||
db.deleteObjectStore('set_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete set DB. This is probably fine");
|
||||
}
|
||||
let db = e.target.result;
|
||||
|
||||
try {
|
||||
db.deleteObjectStore('item_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete item DB. This is probably fine");
|
||||
}
|
||||
try {
|
||||
db.deleteObjectStore('set_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete set DB. This is probably fine");
|
||||
}
|
||||
|
||||
db.createObjectStore('item_db');
|
||||
db.createObjectStore('set_db');
|
||||
db.createObjectStore('item_db');
|
||||
db.createObjectStore('set_db');
|
||||
|
||||
console.log("DB setup complete...");
|
||||
}
|
||||
console.log("DB setup complete...");
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// List of 'raw' "none" items (No Helmet, etc), in order helmet, chestplate... ring1, ring2, brace, neck, weapon.
|
||||
for (const it of itemTypes) {
|
||||
itemLists.set(it, []);
|
||||
}
|
||||
|
||||
let none_items = [
|
||||
["armor", "helmet", "No Helmet"],
|
||||
["armor", "chestplate", "No Chestplate"],
|
||||
["armor", "leggings", "No Leggings"],
|
||||
["armor", "boots", "No Boots"],
|
||||
["accessory", "ring", "No Ring 1"],
|
||||
["accessory", "ring", "No Ring 2"],
|
||||
["accessory", "bracelet", "No Bracelet"],
|
||||
["accessory", "necklace", "No Necklace"],
|
||||
["weapon", "dagger", "No Weapon"],
|
||||
];
|
||||
for (let i = 0; i < none_items.length; i++) {
|
||||
let item = Object();
|
||||
item.slots = 0;
|
||||
item.category = none_items[i][0];
|
||||
item.type = none_items[i][1];
|
||||
item.name = none_items[i][2];
|
||||
item.displayName = item.name;
|
||||
item.set = null;
|
||||
item.quest = null;
|
||||
item.skillpoints = [0, 0, 0, 0, 0];
|
||||
item.has_negstat = false;
|
||||
item.reqs = [0, 0, 0, 0, 0];
|
||||
item.fixID = true;
|
||||
item.tier = "Normal";
|
||||
item.id = 10000 + i;
|
||||
item.nDam = "0-0";
|
||||
item.eDam = "0-0";
|
||||
item.tDam = "0-0";
|
||||
item.wDam = "0-0";
|
||||
item.fDam = "0-0";
|
||||
item.aDam = "0-0";
|
||||
clean_item(item);
|
||||
|
||||
none_items[i] = item;
|
||||
}
|
||||
|
||||
function init_maps() {
|
||||
|
@ -207,56 +237,13 @@ function init_maps() {
|
|||
/* Mapping from item names to set names. */
|
||||
idMap = new Map();
|
||||
redirectMap = new Map();
|
||||
for (const it of itemTypes) {
|
||||
itemLists.set(it, []);
|
||||
}
|
||||
|
||||
let noneItems = [
|
||||
["armor", "helmet", "No Helmet"],
|
||||
["armor", "chestplate", "No Chestplate"],
|
||||
["armor", "leggings", "No Leggings"],
|
||||
["armor", "boots", "No Boots"],
|
||||
["accessory", "ring", "No Ring 1"],
|
||||
["accessory", "ring", "No Ring 2"],
|
||||
["accessory", "bracelet", "No Bracelet"],
|
||||
["accessory", "necklace", "No Necklace"],
|
||||
["weapon", "dagger", "No Weapon"],
|
||||
["tome", "weaponTome", "No Weapon Tome"],
|
||||
["tome", "armorTome", "No Armour Tome"],
|
||||
["tome", "guildTome", "No Guild Tome"]
|
||||
];
|
||||
for (let i = 0; i < 12; i++) {
|
||||
let item = Object();
|
||||
item.slots = 0;
|
||||
item.category = noneItems[i][0];
|
||||
item.type = noneItems[i][1];
|
||||
item.name = noneItems[i][2];
|
||||
item.displayName = item.name;
|
||||
item.set = null;
|
||||
item.quest = null;
|
||||
item.skillpoints = [0, 0, 0, 0, 0];
|
||||
item.has_negstat = false;
|
||||
item.reqs = [0, 0, 0, 0, 0];
|
||||
item.fixID = true;
|
||||
item.tier = "Normal";//do not get rid of this @hpp
|
||||
item.id = 10000 + i;
|
||||
item.nDam = "0-0";
|
||||
item.eDam = "0-0";
|
||||
item.tDam = "0-0";
|
||||
item.wDam = "0-0";
|
||||
item.fDam = "0-0";
|
||||
item.aDam = "0-0";
|
||||
clean_item(item);
|
||||
|
||||
noneItems[i] = item;
|
||||
}
|
||||
items = items.concat(noneItems);
|
||||
items = items.concat(none_items);
|
||||
//console.log(items);
|
||||
for (const item of items) {
|
||||
if (item.remapID === undefined) {
|
||||
itemLists.get(item.type).push(item.displayName);
|
||||
itemMap.set(item.displayName, item);
|
||||
if (noneItems.includes(item)) {
|
||||
if (none_items.includes(item)) {
|
||||
idMap.set(item.id, "");
|
||||
}
|
||||
else {
|
||||
|
|
143
js/load_ing.js
|
@ -4,6 +4,7 @@ const ING_DB_VERSION = 13;
|
|||
|
||||
let idb;
|
||||
let ireload = false;
|
||||
let iload_in_progress = false;
|
||||
let iload_complete = false;
|
||||
let ings;
|
||||
let recipes;
|
||||
|
@ -20,32 +21,34 @@ let recipeIDMap;
|
|||
/*
|
||||
* Load item set from local DB. Calls init() on success.
|
||||
*/
|
||||
async function ing_load_local(init_func) {
|
||||
console.log("IngMap is: \n " + ingMap);
|
||||
let get_tx = idb.transaction(['ing_db', 'recipe_db'], 'readonly');
|
||||
let ings_store = get_tx.objectStore('ing_db');
|
||||
let recipes_store = get_tx.objectStore('recipe_db');
|
||||
let request3 = ings_store.getAll();
|
||||
request3.onerror = function(event) {
|
||||
console.log("Could not read local ingredient db...");
|
||||
}
|
||||
request3.onsuccess = function(event) {
|
||||
console.log("Successfully read local ingredient db.");
|
||||
ings = request3.result;
|
||||
async function ing_load_local() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let get_tx = idb.transaction(['ing_db', 'recipe_db'], 'readonly');
|
||||
let ings_store = get_tx.objectStore('ing_db');
|
||||
let recipes_store = get_tx.objectStore('recipe_db');
|
||||
let request3 = ings_store.getAll();
|
||||
request3.onerror = function(event) {
|
||||
reject("Could not read local ingredient db...");
|
||||
}
|
||||
request3.onsuccess = function(event) {
|
||||
console.log("Successfully read local ingredient db.");
|
||||
}
|
||||
let request4 = recipes_store.getAll();
|
||||
request4.onerror = function(event) {
|
||||
console.log("Could not read local recipe db...");
|
||||
reject("Could not read local recipe db...");
|
||||
}
|
||||
request4.onsuccess = function(event) {
|
||||
console.log("Successfully read local recipe db.");
|
||||
}
|
||||
get_tx.oncomplete = function(event) {
|
||||
ings = request3.result;
|
||||
recipes = request4.result;
|
||||
init_ing_maps();
|
||||
init_func();
|
||||
iload_complete = true;
|
||||
idb.close();
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
await get_tx.complete;
|
||||
idb.close();
|
||||
});
|
||||
}
|
||||
|
||||
function clean_ing(ing) {
|
||||
|
@ -59,11 +62,12 @@ function clean_ing(ing) {
|
|||
/*
|
||||
* Load item set from remote DB (aka a big json file). Calls init() on success.
|
||||
*/
|
||||
async function load_ings(init_func) {
|
||||
async function load_ings() {
|
||||
|
||||
let getUrl = window.location;
|
||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
|
||||
let url = baseUrl + "/ingreds_compress.json";
|
||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
||||
// "Random" string to prevent caching!
|
||||
let url = baseUrl + "/ingreds_compress.json?"+new Date();
|
||||
url = url.replace(/\w+.html/, "") ;
|
||||
let result = await (await fetch(url)).json();
|
||||
|
||||
|
@ -97,59 +101,65 @@ async function load_ings(init_func) {
|
|||
}
|
||||
add_promises.push(add_tx2.complete);
|
||||
add_promises.push(add_tx3.complete);
|
||||
Promise.all(add_promises).then((values) => {
|
||||
init_ing_maps();
|
||||
init_func();
|
||||
iload_complete = true;
|
||||
});
|
||||
// DB not closed? idfk man
|
||||
|
||||
await Promise.all(add_promises);
|
||||
init_ing_maps();
|
||||
iload_complete = true;
|
||||
idb.close();
|
||||
}
|
||||
|
||||
function load_ing_init(init_func) {
|
||||
if (iload_complete) {
|
||||
console.log("Ingredient db already loaded, skipping load sequence");
|
||||
init_func();
|
||||
return;
|
||||
}
|
||||
let request = window.indexedDB.open("ing_db", ING_DB_VERSION)
|
||||
request.onerror = function() {
|
||||
console.log("DB failed to open...");
|
||||
}
|
||||
async function load_ing_init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = window.indexedDB.open("ing_db", ING_DB_VERSION)
|
||||
request.onerror = function() {
|
||||
reject("DB failed to open...");
|
||||
}
|
||||
|
||||
request.onsuccess = function() {
|
||||
idb = request.result;
|
||||
if (!ireload) {
|
||||
console.log("Using stored data...")
|
||||
ing_load_local(init_func);
|
||||
request.onsuccess = async function() {
|
||||
idb = request.result;
|
||||
if (iload_in_progress) {
|
||||
while (!iload_complete) {
|
||||
await sleep(100);
|
||||
}
|
||||
console.log("Skipping load...")
|
||||
}
|
||||
else {
|
||||
iload_in_progress = true
|
||||
if (ireload) {
|
||||
console.log("Using new data...")
|
||||
await load_ings();
|
||||
}
|
||||
else {
|
||||
console.log("Using stored data...")
|
||||
await ing_load_local();
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
console.log("Using new data...")
|
||||
load_ings(init_func);
|
||||
}
|
||||
}
|
||||
|
||||
request.onupgradeneeded = function(e) {
|
||||
ireload = true;
|
||||
request.onupgradeneeded = function(e) {
|
||||
ireload = true;
|
||||
|
||||
let idb = e.target.result;
|
||||
|
||||
try {
|
||||
idb.deleteObjectStore('ing_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete ingredient DB. This is probably fine");
|
||||
}
|
||||
try {
|
||||
idb.deleteObjectStore('recipe_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete recipe DB. This is probably fine");
|
||||
}
|
||||
idb.createObjectStore('ing_db');
|
||||
idb.createObjectStore('recipe_db');
|
||||
let idb = e.target.result;
|
||||
|
||||
try {
|
||||
idb.deleteObjectStore('ing_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete ingredient DB. This is probably fine");
|
||||
}
|
||||
try {
|
||||
idb.deleteObjectStore('recipe_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete recipe DB. This is probably fine");
|
||||
}
|
||||
idb.createObjectStore('ing_db');
|
||||
idb.createObjectStore('recipe_db');
|
||||
|
||||
console.log("DB setup complete...");
|
||||
}
|
||||
console.log("DB setup complete...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init_ing_maps() {
|
||||
|
@ -222,4 +232,5 @@ function init_ing_maps() {
|
|||
recipeList.push(recipe["name"]);
|
||||
recipeIDMap.set(recipe["id"],recipe["name"]);
|
||||
}
|
||||
console.log(ingMap);
|
||||
}
|
||||
|
|
180
js/load_tome.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
const TOME_DB_VERSION = 3;
|
||||
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
|
||||
|
||||
let tdb;
|
||||
let treload = false;
|
||||
let tload_complete = false;
|
||||
let tload_in_progress = false;
|
||||
let tomes;
|
||||
let tomeMap;
|
||||
let tometomeIDMap;
|
||||
let tomeRedirectMap;
|
||||
let tomeLists = new Map();
|
||||
/*
|
||||
* Load tome set from local DB. Calls init() on success.
|
||||
*/
|
||||
async function load_tome_local() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let get_tx = tdb.transaction(['tome_db'], 'readonly');
|
||||
let get_store = get_tx.objectStore('tome_db');
|
||||
let request = get_store.getAll();
|
||||
request.onerror = function(event) {
|
||||
reject("Could not read local tome db...");
|
||||
}
|
||||
request.onsuccess = function(event) {
|
||||
console.log("Successfully read local tome db.");
|
||||
}
|
||||
get_tx.oncomplete = function(event) {
|
||||
tomes = request.result;
|
||||
init_tome_maps();
|
||||
tload_complete = true;
|
||||
tdb.close();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Load tome set from remote DB (json). Calls init() on success.
|
||||
*/
|
||||
async function load_tome() {
|
||||
|
||||
let getUrl = window.location;
|
||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
||||
// "Random" string to prevent caching!
|
||||
let url = baseUrl + "/tomes.json?"+new Date();
|
||||
let result = await (await fetch(url)).json();
|
||||
tomes = result.tomes
|
||||
|
||||
let add_tx = tdb.transaction(['tome_db'], 'readwrite');
|
||||
add_tx.onabort = function(e) {
|
||||
console.log(e);
|
||||
console.log("Not enough space...");
|
||||
};
|
||||
let tomes_store = add_tx.objectStore('tome_db');
|
||||
let add_promises = [];
|
||||
for (const tome of tomes) {
|
||||
//dependency on clean_item in load.js
|
||||
clean_item(tome);
|
||||
let req = tomes_store.add(tome, tome.name);
|
||||
req.onerror = function() {
|
||||
console.log("ADD TOME ERROR? " + tome.name);
|
||||
};
|
||||
add_promises.push(req);
|
||||
}
|
||||
add_promises.push(add_tx.complete);
|
||||
|
||||
await Promise.all(add_promises);
|
||||
init_tome_maps();
|
||||
tload_complete = true;
|
||||
tdb.close();
|
||||
}
|
||||
|
||||
async function load_tome_init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = window.indexedDB.open('tome_db', TOME_DB_VERSION);
|
||||
|
||||
request.onerror = function() {
|
||||
reject("DB failed to open...");
|
||||
};
|
||||
|
||||
request.onsuccess = async function() {
|
||||
tdb = request.result;
|
||||
if (tload_in_progress) {
|
||||
while (!tload_complete) {
|
||||
await sleep(100);
|
||||
}
|
||||
console.log("Skipping load...")
|
||||
}
|
||||
else {
|
||||
tload_in_progress = true
|
||||
if (treload) {
|
||||
console.log("Using new data...")
|
||||
await load_tome();
|
||||
}
|
||||
else {
|
||||
console.log("Using stored data...")
|
||||
await load_tome_local();
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
|
||||
request.onupgradeneeded = function(e) {
|
||||
treload = true;
|
||||
|
||||
let tdb = e.target.result;
|
||||
|
||||
try {
|
||||
tdb.deleteObjectStore('tome_db');
|
||||
}
|
||||
catch (error) {
|
||||
console.log("Could not delete tome DB. This is probably fine");
|
||||
}
|
||||
|
||||
tdb.createObjectStore('tome_db');
|
||||
|
||||
console.log("DB setup complete...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let none_tomes = [
|
||||
["tome", "weaponTome", "No Weapon Tome"],
|
||||
["tome", "armorTome", "No Armor Tome"],
|
||||
["tome", "guildTome", "No Guild Tome"]
|
||||
];
|
||||
function init_tome_maps() {
|
||||
//warp
|
||||
tomeMap = new Map();
|
||||
/* Mapping from item names to set names. */
|
||||
tomeIDMap = new Map();
|
||||
|
||||
tomeRedirectMap = new Map();
|
||||
for (const it of tome_types) {
|
||||
tomeLists.set(it, []);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
let tome = Object();
|
||||
tome.slots = 0;
|
||||
tome.category = none_tomes[i][0];
|
||||
tome.type = none_tomes[i][1];
|
||||
tome.name = none_tomes[i][2];
|
||||
tome.displayName = tome.name;
|
||||
tome.set = null;
|
||||
tome.quest = null;
|
||||
tome.skillpoints = [0, 0, 0, 0, 0];
|
||||
tome.has_negstat = false;
|
||||
tome.reqs = [0, 0, 0, 0, 0];
|
||||
tome.fixID = true;
|
||||
tome.tier = "Normal";
|
||||
tome.id = 61 + i; //special case!
|
||||
tome.nDam = "0-0";
|
||||
tome.eDam = "0-0";
|
||||
tome.tDam = "0-0";
|
||||
tome.wDam = "0-0";
|
||||
tome.fDam = "0-0";
|
||||
tome.aDam = "0-0";
|
||||
//dependency - load.js
|
||||
clean_item(tome);
|
||||
|
||||
none_tomes[i] = tome;
|
||||
}
|
||||
tomes = tomes.concat(none_tomes);
|
||||
for (const tome of tomes) {
|
||||
if (tome.remapID === undefined) {
|
||||
tomeLists.get(tome.type).push(tome.displayName);
|
||||
tomeMap.set(tome.displayName, tome);
|
||||
if (none_tomes.includes(tome)) {
|
||||
tomeIDMap.set(tome.id, "");
|
||||
}
|
||||
else {
|
||||
tomeIDMap.set(tome.id, tome.displayName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
tomeRedirectMap.set(tome.id, tome.remapID);
|
||||
}
|
||||
}
|
||||
}
|
102
js/loadheader.js
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
<div class = "headerleft">
|
||||
<a href = "./index.html" class = "nomarginp iconlink tooltip">
|
||||
<img src = "/media/icons/new/builder.png" class = "left linkoptions headericon">
|
||||
<div class = "tooltiptext center">WynnBuilder</div>
|
||||
</img>
|
||||
</a>
|
||||
<a href = "./crafter.html" class = "nomarginp iconlink tooltip">
|
||||
<img src = "/media/icons/new/crafter.png" class = "left linkoptions headericon">
|
||||
<div class = "tooltiptext center">WynnCrafter</div>
|
||||
</img>
|
||||
</a>
|
||||
<a href = "./items.html" class = "nomarginp iconlink tooltip">
|
||||
<img src = "/media/icons/new/searcher.png" class = "left linkoptions headericon">
|
||||
<div class = "tooltiptext center">WynnAtlas</div>
|
||||
</img>
|
||||
</a>
|
||||
<a href = "./customizer.html" class = "nomarginp iconlink tooltip">
|
||||
<img src = "/media/icons/new/custom.png" class = "left linkoptions headericon">
|
||||
<div class = "tooltiptext center">WynnCustom</div>
|
||||
</img>
|
||||
</a> <a href = "./map.html" class = "nomarginp iconlink tooltip">
|
||||
<img src = "/media/icons/new/compass.png" class = "left linkoptions headericon">
|
||||
</img>
|
||||
<div class = "tooltiptext center">WynnGPS</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class = "headerright">
|
||||
<button class = "button" id = "toggle-icon-button" onclick="toggleIcons();">
|
||||
Use Old Icons
|
||||
</button>
|
||||
</div>
|
||||
*/
|
||||
let header_icon_map_left = new Map([
|
||||
["index",["builder","WynnBuilder"]],
|
||||
["crafter",["crafter","WynnCrafter"]],
|
||||
["items",["searcher","WynnAtlas"]],
|
||||
["customizer",["custom","WynnCustom"]],
|
||||
["map",["compass","WynnGPS"]],
|
||||
["wynnfo/index",["book","Wynnfo"]]
|
||||
]);
|
||||
|
||||
function setHeaders() {
|
||||
let headerleft = document.getElementById("headerleft");
|
||||
let headerright = document.getElementById("headerright");
|
||||
|
||||
for (const [name,data] of header_icon_map_left) {
|
||||
let a_elem = document.createElement("a");
|
||||
let img = document.createElement("img");
|
||||
let div = document.createElement("div");
|
||||
a_elem.classList.add("nomarginp");
|
||||
a_elem.classList.add("iconlink");
|
||||
a_elem.classList.add("tooltip");
|
||||
a_elem.href = "../" + name + ".html";
|
||||
img.classList.add("left");
|
||||
img.classList.add("linkoptions");
|
||||
img.classList.add("headericon");
|
||||
img.src = "/media/icons/new/" + data[0] + ".png";
|
||||
div.classList.add("tooltiptext");
|
||||
div.classList.add("header-tooltip");
|
||||
div.classList.add("center");
|
||||
div.textContent = data[1];
|
||||
a_elem.appendChild(img);
|
||||
a_elem.appendChild(div);
|
||||
headerleft.appendChild(a_elem);
|
||||
}
|
||||
|
||||
let toggle_icon_button = document.createElement("button");
|
||||
toggle_icon_button.classList.add("button");
|
||||
toggle_icon_button.id = "toggle-icon-button";
|
||||
toggle_icon_button.onclick = function() {toggleIcons()};
|
||||
toggle_icon_button.textContent = "Use Old Icons";
|
||||
headerright.appendChild(toggle_icon_button);
|
||||
|
||||
let reload_div = document.createElement("div");
|
||||
let reload_button = document.createElement("button");
|
||||
reload_button.classList.add("button");
|
||||
reload_button.style.left = '0px';
|
||||
reload_button.style.top = '0px';
|
||||
reload_button.style.width = '48px';
|
||||
reload_button.style.height = '48px';
|
||||
reload_button.style.padding = '0px';
|
||||
reload_button.onclick = hardReload;
|
||||
let reload_img = document.createElement("img");
|
||||
reload_img.src = "/media/icons/new/reload.png"
|
||||
reload_img.style.width = "100%";
|
||||
let reload_tooltip;
|
||||
reload_tooltip = createTooltip(reload_tooltip, "p", "Reload", reload_button, ["center","reloadtooltip"]);
|
||||
//reload_tooltip.style.position = "relative";
|
||||
reload_tooltip.style.left = "-50%";
|
||||
reload_tooltip.style.top = "70%";
|
||||
|
||||
reload_div.appendChild(reload_button);
|
||||
reload_button.appendChild(reload_img);
|
||||
headerright.appendChild(reload_div);
|
||||
|
||||
|
||||
console.log("Set Header");
|
||||
}
|
||||
|
||||
|
||||
setHeaders();
|
28
js/map.js
|
@ -9,16 +9,6 @@ const map_url_tag = location.hash.slice(1);
|
|||
// console.log(map_url_base);
|
||||
// console.log(map_url_tag);
|
||||
|
||||
const MAP_BUILD_VERSION = "7";
|
||||
|
||||
function setTitle() {
|
||||
let text = "WynnGPS version "+MAP_BUILD_VERSION;
|
||||
document.getElementById("header").classList.add("funnynumber");
|
||||
document.getElementById("header").textContent = text;
|
||||
}
|
||||
|
||||
setTitle();
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -73,7 +63,7 @@ function init_map(){ //async just in case we need async stuff
|
|||
zoomControl: false,
|
||||
zoom: 1
|
||||
}).setView([0,0], 1);
|
||||
L.imageOverlay("/media/maps/world-map.png", bounds).addTo(map);
|
||||
L.imageOverlay("../media/maps/world-map.png", bounds).addTo(map);
|
||||
|
||||
map.fitBounds(bounds);
|
||||
|
||||
|
@ -144,10 +134,10 @@ function placeMarker(lat, lng) {
|
|||
}
|
||||
|
||||
marker = L.marker([lat, lng], {icon: L.icon({
|
||||
iconUrl: '/media/icons/' + (newIcons ? "new/" : "old/" ) + 'marker.png',
|
||||
iconUrl: '../media/icons/' + (newIcons ? "new/" : "old/" ) + 'marker.png',
|
||||
iconSize: [32, 32],
|
||||
iconAnchor: [16, 32],
|
||||
shadowUrl: '/media/icons/' + (newIcons ? "new/" : "old/" ) + 'shadow.png',
|
||||
shadowUrl: '../media/icons/' + (newIcons ? "new/" : "old/" ) + 'shadow.png',
|
||||
shadowSize: [1,1],
|
||||
shadowAnchor: [16, 32],
|
||||
className: "marker"
|
||||
|
@ -243,7 +233,7 @@ function pullguilds() {
|
|||
.then(res => {
|
||||
guildTags.set(guild, res.prefix);
|
||||
guildColors.set(guild, randomColorHSL([0,1],[0,1],[0.4,1]));
|
||||
console.log("Succesfully pulled guild data for " + guild + ".");
|
||||
// console.log("Succesfully pulled guild data for " + guild + ".");
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
|
@ -286,7 +276,7 @@ function pullguilds() {
|
|||
let li = document.createElement("li");
|
||||
|
||||
let i = document.createElement("img");
|
||||
i.src = "./media/icons/locations/" + img;
|
||||
i.src = "../media/icons/locations/" + img;
|
||||
i.style.maxWidth = "32px";
|
||||
i.style.maxHeight = "32px";
|
||||
li.appendChild(i);
|
||||
|
@ -467,10 +457,10 @@ function toggleResources() {
|
|||
let imgBounds = [ [ TRcorner[0]-(16*n)-20-gap*n,TRcorner[1]+4], [ TRcorner[0]-(16*n)-4-gap*n,TRcorner[1]+20] ];
|
||||
imgBounds = [xytolatlng(imgBounds[0][0],imgBounds[0][1]), xytolatlng(imgBounds[1][0],imgBounds[1][1])];
|
||||
|
||||
let resourceObj = L.imageOverlay("/media/icons/" + (newIcons ? "new/" : "old/" ) +resource+".png", imgBounds, {className: `${resource} resourceimg`}).addTo(map);
|
||||
let resourceObj = L.imageOverlay("../media/icons/" + (newIcons ? "new/" : "old/" ) +resource+".png", imgBounds, {className: `${resource} resourceimg`}).addTo(map);
|
||||
resourceObjs.push(resourceObj);
|
||||
}
|
||||
let gearObj = L.imageOverlay("/media/icons/" + (newIcons ? "new/" : "old/" ) + "Gears.png", [xytolatlng(TRcorner[0]-(16*terr_resources.length)-20-gap*terr_resources.length,TRcorner[1]+4), xytolatlng(TRcorner[0]-(16*terr_resources.length)-4-gap*terr_resources.length,TRcorner[1]+20)], {className: `Ore resourceimg`}).addTo(map);
|
||||
let gearObj = L.imageOverlay("../media/icons/" + (newIcons ? "new/" : "old/" ) + "Gears.png", [xytolatlng(TRcorner[0]-(16*terr_resources.length)-20-gap*terr_resources.length,TRcorner[1]+4), xytolatlng(TRcorner[0]-(16*terr_resources.length)-4-gap*terr_resources.length,TRcorner[1]+20)], {className: `Ore resourceimg`}).addTo(map);
|
||||
resourceObjs.push(gearObj);
|
||||
//draw resource storage
|
||||
for (const n in terr_storage) {
|
||||
|
@ -479,10 +469,10 @@ function toggleResources() {
|
|||
let imgBounds = [ [ DRcorner[0]-(16*n)-20-gap*n,DRcorner[1]-20], [ DRcorner[0]-(16*n)-4-gap*n,DRcorner[1]-4] ];
|
||||
imgBounds = [xytolatlng(imgBounds[0][0],imgBounds[0][1]), xytolatlng(imgBounds[1][0],imgBounds[1][1])];
|
||||
|
||||
let resourceObj = L.imageOverlay("/media/icons/" + (newIcons ? "new/" : "old/" ) +storage+".png", imgBounds, {alt: `${storage}`, className: `${storage} resourceimg`}).addTo(map);
|
||||
let resourceObj = L.imageOverlay("../media/icons/" + (newIcons ? "new/" : "old/" ) +storage+".png", imgBounds, {alt: `${storage}`, className: `${storage} resourceimg`}).addTo(map);
|
||||
resourceObjs.push(resourceObj);
|
||||
}
|
||||
let chestObj = L.imageOverlay("/media/icons/" + (newIcons ? "new/" : "old/" ) + "Chest.png", [xytolatlng(DRcorner[0]-(16*terr_storage.length)-20-gap*terr_storage.length,DRcorner[1]-20), xytolatlng(DRcorner[0]-(16*terr_storage.length)-4-gap*terr_storage.length,DRcorner[1]-4)], {className: `Wood resourceimg`}).addTo(map);
|
||||
let chestObj = L.imageOverlay("../media/icons/" + (newIcons ? "new/" : "old/" ) + "Chest.png", [xytolatlng(DRcorner[0]-(16*terr_storage.length)-20-gap*terr_storage.length,DRcorner[1]-20), xytolatlng(DRcorner[0]-(16*terr_storage.length)-4-gap*terr_storage.length,DRcorner[1]-4)], {className: `Wood resourceimg`}).addTo(map);
|
||||
resourceObjs.push(chestObj);
|
||||
}
|
||||
|
||||
|
|
100
js/optimize.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
function optimizeStrDex() {
|
||||
if (!player_build) {
|
||||
return;
|
||||
}
|
||||
const skillpoints = skp_inputs.map(x => x.value); // JANK
|
||||
let total_assigned = 0;
|
||||
const min_assigned = player_build.base_skillpoints;
|
||||
const base_totals = player_build.total_skillpoints;
|
||||
let base_skillpoints = [];
|
||||
for (let i in skp_order){ //big bren
|
||||
const assigned = skillpoints[i] - base_totals[i] + min_assigned[i]
|
||||
base_skillpoints.push(assigned);
|
||||
total_assigned += assigned;
|
||||
}
|
||||
|
||||
const remaining = levelToSkillPoints(player_build.level) - total_assigned;
|
||||
const max_str_boost = 100 - base_skillpoints[0];
|
||||
const max_dex_boost = 100 - base_skillpoints[1];
|
||||
if (Math.min(remaining, max_str_boost, max_dex_boost) < 0) return; // Unwearable
|
||||
|
||||
let str_bonus = remaining;
|
||||
let dex_bonus = 0;
|
||||
let best_skillpoints = skillpoints;
|
||||
let best_damage = 0;
|
||||
for (let i = 0; i <= remaining; ++i) {
|
||||
let total_skillpoints = skillpoints.slice();
|
||||
total_skillpoints[0] += Math.min(max_str_boost, str_bonus);
|
||||
total_skillpoints[1] += Math.min(max_dex_boost, dex_bonus);
|
||||
|
||||
// Calculate total 3rd spell damage
|
||||
let spell = spell_table[player_build.weapon.statMap.get("type")][2];
|
||||
const stats = player_build.statMap;
|
||||
let critChance = skillPointsToPercentage(total_skillpoints[1]);
|
||||
let save_damages = [];
|
||||
let spell_parts;
|
||||
if (spell.parts) {
|
||||
spell_parts = spell.parts;
|
||||
}
|
||||
else {
|
||||
spell_parts = spell.variants.DEFAULT;
|
||||
for (const majorID of stats.get("activeMajorIDs")) {
|
||||
if (majorID in spell.variants) {
|
||||
spell_parts = spell.variants[majorID];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let total_damage = 0;
|
||||
for (const part of spell_parts) {
|
||||
if (part.type === "damage") {
|
||||
let tmp_conv = [];
|
||||
for (let i in part.conversion) {
|
||||
tmp_conv.push(part.conversion[i] * part.multiplier);
|
||||
}
|
||||
let _results = calculateSpellDamage(stats, player_build.weapon.statMap, tmp_conv, true);
|
||||
let totalDamNormal = _results[0];
|
||||
let totalDamCrit = _results[1];
|
||||
let results = _results[2];
|
||||
let tooltipinfo = _results[3];
|
||||
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
for (let j in results[i]) {
|
||||
results[i][j] = results[i][j].toFixed(2);
|
||||
}
|
||||
}
|
||||
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
|
||||
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
|
||||
let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0;
|
||||
|
||||
save_damages.push(averageDamage);
|
||||
if (part.summary == true) {
|
||||
total_damage = averageDamage;
|
||||
}
|
||||
} else if (part.type === "total") {
|
||||
total_damage = 0;
|
||||
for (let i in part.factors) {
|
||||
total_damage += save_damages[i] * part.factors[i];
|
||||
}
|
||||
}
|
||||
} // END Calculate total 3rd spell damage (total_damage)
|
||||
if (total_damage > best_damage) {
|
||||
best_damage = total_damage;
|
||||
best_skillpoints = total_skillpoints.slice();
|
||||
}
|
||||
|
||||
str_bonus -= 1;
|
||||
dex_bonus += 1;
|
||||
}
|
||||
console.log(best_skillpoints);
|
||||
|
||||
// TODO do not merge for performance reasons
|
||||
for (let i in skp_order) {
|
||||
skp_inputs[i].input_field.value = best_skillpoints[i];
|
||||
skp_inputs[i].mark_dirty();
|
||||
}
|
||||
for (let i in skp_order) {
|
||||
skp_inputs[i].update();
|
||||
}
|
||||
}
|
||||
|
133
js/powders.js
|
@ -55,9 +55,140 @@ class PowderSpecial{
|
|||
function _ps(a,b,c,d,e) { return new PowderSpecial(a,b,c,d,e); } //bruh moment
|
||||
|
||||
let powderSpecialStats = [
|
||||
_ps("Quake",new Map([["Radius",[4.4,4.9,5.4,5.9,6.4]], ["Damage",[155,220,285,350,415]] ]),"Rage",new Map([ ["Damage", [0.3,0.4,0.5,0.7,1.0]],["Description", "% " + "\u2764" + " Missing"] ]),400), //e
|
||||
_ps("Quake",new Map([["Radius",[4.4,4.9,5.4,5.9,6.4]], ["Damage",[155,220,285,350,415]] ]),"Rage",new Map([ ["Damage", [0.3,0.4,0.5,0.7,1.0]],["Description", "% " + "\u2764" + " Missing"] ]), 396), //e
|
||||
_ps("Chain Lightning",new Map([ ["Chains", [5,6,7,8,9]], ["Damage", [200,225,250,275,300]] ]),"Kill Streak",new Map([ ["Damage", [3,4.5,6,7.5,9]],["Duration", [5,5,5,5,5]],["Description", "Mob Killed"] ]),200), //t
|
||||
_ps("Curse",new Map([ ["Duration", [7,7.5,8,8.5,9]],["Damage Boost", [90,120,150,180,210]] ]),"Concentration",new Map([ ["Damage", [1,2,3,4,5]],["Duration",[1,1,1,1,1]],["Description", "Mana Used"] ]),150), //w
|
||||
_ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f
|
||||
_ps("Wind Prison",new Map([ ["Duration", [3,3.5,4,4.5,5]],["Damage Boost", [400,450,500,550,600]],["Knockback", [8,12,16,20,24]] ]),"Dodge",new Map([ ["Damage",[2,3,4,5,6]],["Duration",[2,3,4,5,6]],["Description","Near Mobs"] ]),150) //a
|
||||
];
|
||||
|
||||
/**
|
||||
* Apply armor powders.
|
||||
* Encoding shortcut assumes that all powders give +def to one element
|
||||
* and -def to the element "behind" it in cycle ETWFA, which is true
|
||||
* as of now and unlikely to change in the near future.
|
||||
*/
|
||||
function applyArmorPowders(expandedItem) {
|
||||
const powders = expandedItem.get('powders');
|
||||
for(const id of powders){
|
||||
let powder = powderStats[id];
|
||||
let name = powderNames.get(id).charAt(0);
|
||||
let prevName = skp_elements[(skp_elements.indexOf(name) + 4 )% 5];
|
||||
expandedItem.set(name+"Def", (expandedItem.get(name+"Def") || 0) + powder["defPlus"]);
|
||||
expandedItem.set(prevName+"Def", (expandedItem.get(prevName+"Def") || 0) - powder["defMinus"]);
|
||||
}
|
||||
}
|
||||
|
||||
const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
|
||||
const damage_present_key = 'damagePresent';
|
||||
/**
|
||||
* Apply weapon powders. MUTATES THE ITEM!
|
||||
* Adds entries for `damage_keys` and `damage_present_key`
|
||||
* For normal items, `damage_keys` is 6x2 list (elem: [min, max])
|
||||
* For crafted items, `damage_keys` is 6x2x2 list (elem: [minroll: [min, max], maxroll: [min, max]])
|
||||
*/
|
||||
function apply_weapon_powders(item) {
|
||||
let present;
|
||||
if (item.get("tier") !== "Crafted") {
|
||||
let weapon_result = calc_weapon_powder(item);
|
||||
let damages = weapon_result[0];
|
||||
present = weapon_result[1];
|
||||
for (const i in damage_keys) {
|
||||
item.set(damage_keys[i], damages[i]);
|
||||
}
|
||||
} else {
|
||||
let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")];
|
||||
let results_low = calc_weapon_powder(item, base_low);
|
||||
let damage_low = results_low[0];
|
||||
let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")];
|
||||
let results_high = calc_weapon_powder(item, base_high);
|
||||
let damage_high = results_high[0];
|
||||
present = results_high[1];
|
||||
|
||||
for (const i in damage_keys) {
|
||||
item.set(damage_keys[i], [damage_low[i], damage_high[i]]);
|
||||
}
|
||||
}
|
||||
item.set(damage_present_key, present);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate weapon damage from powder.
|
||||
*
|
||||
* Params:
|
||||
* weapon: Weapon to apply powder to
|
||||
* damageBases: used by crafted
|
||||
*
|
||||
* Return:
|
||||
* [damages, damage_present]
|
||||
*/
|
||||
function calc_weapon_powder(weapon, damageBases) {
|
||||
let powders = weapon.get("powders").slice();
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
let damages = [
|
||||
weapon.get('nDam').split('-').map(Number),
|
||||
weapon.get('eDam').split('-').map(Number),
|
||||
weapon.get('tDam').split('-').map(Number),
|
||||
weapon.get('wDam').split('-').map(Number),
|
||||
weapon.get('fDam').split('-').map(Number),
|
||||
weapon.get('aDam').split('-').map(Number)
|
||||
];
|
||||
|
||||
// Applying spell conversions
|
||||
let neutralBase = damages[0].slice();
|
||||
let neutralRemainingRaw = damages[0].slice();
|
||||
|
||||
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
|
||||
|
||||
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
|
||||
//1st round - apply each as ingred, 2nd round - apply as normal
|
||||
if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) {
|
||||
for (const p of powders.concat(weapon.get("ingredPowders"))) {
|
||||
let powder = powderStats[p]; //use min, max, and convert
|
||||
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
|
||||
let diff = Math.floor(damageBases[0] * powder.convert/100);
|
||||
damageBases[0] -= diff;
|
||||
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
|
||||
}
|
||||
//update all damages
|
||||
for (let i = 0; i < damages.length; i++) {
|
||||
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
|
||||
}
|
||||
neutralRemainingRaw = damages[0].slice();
|
||||
neutralBase = damages[0].slice();
|
||||
}
|
||||
|
||||
//apply powders to weapon
|
||||
for (const powderID of powders) {
|
||||
const powder = powderStats[powderID];
|
||||
// Bitwise to force conversion to integer (integer division).
|
||||
const element = (powderID/6) | 0;
|
||||
let conversionRatio = powder.convert/100;
|
||||
if (neutralRemainingRaw[1] > 0) {
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
||||
|
||||
damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff));
|
||||
damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff));
|
||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
||||
//damages[element+1][0] += min_diff;
|
||||
//damages[element+1][1] += max_diff;
|
||||
//neutralRemainingRaw[0] -= min_diff;
|
||||
//neutralRemainingRaw[1] -= max_diff;
|
||||
}
|
||||
damages[element+1][0] += powder.min;
|
||||
damages[element+1][1] += powder.max;
|
||||
}
|
||||
|
||||
// The ordering of these two blocks decides whether neutral is present when converted away or not.
|
||||
let present_elements = []
|
||||
for (const damage of damages) {
|
||||
present_elements.push(damage[1] > 0);
|
||||
}
|
||||
|
||||
// The ordering of these two blocks decides whether neutral is present when converted away or not.
|
||||
damages[0] = neutralRemainingRaw;
|
||||
return [damages, present_elements];
|
||||
}
|
||||
|
|
205
js/render_compute_graph.js
Normal file
|
@ -0,0 +1,205 @@
|
|||
// Set-up the export button
|
||||
function set_export_button(svg, button_id, output_id) {
|
||||
d3.select('#'+button_id).on('click', function(){
|
||||
//get svg source.
|
||||
var serializer = new XMLSerializer();
|
||||
var source = serializer.serializeToString(svg.node());
|
||||
console.log(source);
|
||||
|
||||
source = source.replace(/^<g/, '<svg');
|
||||
source = source.replace(/<\/g>$/, '</svg>');
|
||||
//add name spaces.
|
||||
if(!source.match(/^<svg[^>]+xmlns="http\:\/\/www\.w3\.org\/2000\/svg"/)){
|
||||
source = source.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
|
||||
}
|
||||
if(!source.match(/^<svg[^>]+"http\:\/\/www\.w3\.org\/1999\/xlink"/)){
|
||||
source = source.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
|
||||
}
|
||||
|
||||
//add xml declaration
|
||||
source = '<?xml version="1.0" standalone="no"?>\r\n' + source;
|
||||
|
||||
//convert svg source to URI data scheme.
|
||||
var url = "data:image/svg+xml;charset=utf-8,"+encodeURIComponent(source);
|
||||
|
||||
//set url value to a element's href attribute.
|
||||
document.getElementById(output_id).href = url;
|
||||
});
|
||||
}
|
||||
|
||||
d3.select("#graph_body")
|
||||
.append("div")
|
||||
.attr("style", "width: 100%; height: 100%; min-height: 0px; flex-grow: 1")
|
||||
.append("svg")
|
||||
.attr("preserveAspectRatio", "xMinYMin meet")
|
||||
.classed("svg-content-responsive", true);
|
||||
let graph = d3.select("svg");
|
||||
let svg = graph.append('g');
|
||||
let margin = {top: 20, right: 20, bottom: 35, left: 40};
|
||||
|
||||
function bbox() {
|
||||
let ret = graph.node().parentNode.getBoundingClientRect();
|
||||
return ret;
|
||||
}
|
||||
let _bbox = bbox();
|
||||
|
||||
const colors = ['aqua', 'yellow', 'fuchsia', 'white', 'teal', 'olive', 'purple', 'gray', 'blue', 'lime', 'red', 'silver', 'navy', 'green', 'maroon'];
|
||||
const n_colors = colors.length;
|
||||
|
||||
const view = svg.append("rect")
|
||||
.attr("class", "view")
|
||||
.attr("x", 0)
|
||||
.attr("y", 0);
|
||||
|
||||
|
||||
function convert_data(nodes_raw) {
|
||||
let edges = [];
|
||||
let node_id = new Map();
|
||||
nodes = [];
|
||||
for (let i in nodes_raw) {
|
||||
node_id.set(nodes_raw[i], i);
|
||||
nodes.push({id: i, color: 0, data: nodes_raw[i]});
|
||||
}
|
||||
for (const node of nodes_raw) {
|
||||
const to = node_id.get(node);
|
||||
for (const input of node.inputs) {
|
||||
const from = node_id.get(input);
|
||||
let name = input.name;
|
||||
let link_name = node.input_translation.get(name);
|
||||
edges.push({
|
||||
source: from,
|
||||
target: to,
|
||||
name: link_name
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
nodes: nodes,
|
||||
links: edges
|
||||
}
|
||||
}
|
||||
|
||||
function create_svg(data, redraw_func) {
|
||||
// Initialize the links
|
||||
var link = svg
|
||||
.selectAll("line")
|
||||
.data(data.links)
|
||||
.enter()
|
||||
.append("line")
|
||||
.style("stroke", "#aaa")
|
||||
|
||||
// Initialize the nodes
|
||||
let node = svg
|
||||
.selectAll("g")
|
||||
.data(data.nodes);
|
||||
|
||||
let node_enter = node.enter()
|
||||
.append('g')
|
||||
|
||||
let circles = node_enter.append("circle")
|
||||
.attr("r", 20)
|
||||
.style("fill", ({id, color, data}) => colors[color])
|
||||
|
||||
node_enter.append('text')
|
||||
.attr("dx", -20)
|
||||
.attr("dy", -22)
|
||||
.style('fill', 'white')
|
||||
.text(({id, color, data}) => data.name);
|
||||
|
||||
// Let's list the force we wanna apply on the network
|
||||
var simulation = d3.forceSimulation(data.nodes) // Force algorithm is applied to data.nodes
|
||||
.force("link", d3.forceLink().strength(0.1) // This force provides links between nodes
|
||||
.id(function(d) { return d.id; }) // This provide the id of a node
|
||||
.links(data.links) // and this the list of links
|
||||
)
|
||||
.force("charge", d3.forceManyBody().strength(-400)) // This adds repulsion between nodes. Play with the -400 for the repulsion strength
|
||||
//.force("center", d3.forceCenter(_bbox.width / 2, _bbox.height / 2).strength(0.1)) // This force attracts nodes to the center of the svg area
|
||||
.on("tick", ticked);
|
||||
// This function is run at each iteration of the force algorithm, updating the nodes position.
|
||||
let scale_transform = {k: 1, x: 0, y: 0}
|
||||
function ticked() {
|
||||
link
|
||||
.attr("x1", function(d) { return d.source.x; })
|
||||
.attr("y1", function(d) { return d.source.y; })
|
||||
.attr("x2", function(d) { return d.target.x; })
|
||||
.attr("y2", function(d) { return d.target.y; });
|
||||
|
||||
node_enter.attr("transform", function (d) { return 'translate('+scale_transform.x+','+scale_transform.y+') scale('+scale_transform.k+') translate('+d.x+','+d.y+')' })
|
||||
}
|
||||
|
||||
const drag = d3.drag()
|
||||
.on("start", dragstart)
|
||||
.on("drag", dragged);
|
||||
|
||||
node_enter.call(drag).on('click', click);
|
||||
function click(event, d) {
|
||||
if (event.ctrlKey) {
|
||||
// Color cycle.
|
||||
d.color = (d.color + 1) % n_colors;
|
||||
d3.select(this).selectAll('circle').style("fill", ({id, color, data}) => colors[color])
|
||||
}
|
||||
else {
|
||||
delete d.fx;
|
||||
delete d.fy;
|
||||
d3.select(this).classed("fixed", false);
|
||||
simulation.alpha(0.5).restart();
|
||||
}
|
||||
}
|
||||
|
||||
function dragstart() {
|
||||
d3.select(this).classed("fixed", true);
|
||||
}
|
||||
function dragged(event, d) {
|
||||
d.fx = event.x;
|
||||
d.fy = event.y;
|
||||
simulation.alpha(0.5).restart();
|
||||
}
|
||||
|
||||
const zoom = d3.zoom()
|
||||
.scaleExtent([0.01, 10])
|
||||
.translateExtent([[-10000, -10000], [10000, 10000]])
|
||||
.filter(filter)
|
||||
.on("zoom", zoomed);
|
||||
view.call(zoom);
|
||||
|
||||
function zoomed({ transform }) {
|
||||
link.attr('transform', transform);
|
||||
scale_transform = transform;
|
||||
node_enter.attr("transform", function (d) { return 'translate('+scale_transform.x+','+scale_transform.y+') scale('+scale_transform.k+') translate('+d.x+','+d.y+')' })
|
||||
redraw_func();
|
||||
}
|
||||
// prevent scrolling then apply the default filter
|
||||
function filter(event) {
|
||||
event.preventDefault();
|
||||
return (!event.ctrlKey || event.type === 'wheel') && !event.button;
|
||||
}
|
||||
}
|
||||
|
||||
set_export_button(svg, 'saveButton', 'saveLink');
|
||||
|
||||
(async function() {
|
||||
|
||||
// JANKY
|
||||
while (edit_id_output === undefined) {
|
||||
await sleep(500);
|
||||
}
|
||||
|
||||
function redraw() {
|
||||
_bbox = bbox();
|
||||
graph.attr("viewBox", [0, 0, _bbox.width, _bbox.height]);
|
||||
view.attr("width", _bbox.width - 1)
|
||||
.attr("height", _bbox.height - 1);
|
||||
}
|
||||
|
||||
d3.select(window)
|
||||
.on("resize", function() {
|
||||
redraw();
|
||||
});
|
||||
redraw();
|
||||
|
||||
const data = convert_data(all_nodes);
|
||||
create_svg(data, redraw);
|
||||
|
||||
console.log("render");
|
||||
|
||||
})();
|
|
@ -1,7 +1,6 @@
|
|||
function calculate_skillpoints(equipment, weapon) {
|
||||
// Calculate equipment equipping order and required skillpoints.
|
||||
// Return value: [equip_order, best_skillpoints, final_skillpoints, best_total];
|
||||
|
||||
let fixed = [];
|
||||
let consider = [];
|
||||
let noboost = [];
|
||||
|
@ -32,14 +31,14 @@ function calculate_skillpoints(equipment, weapon) {
|
|||
let setCount = activeSetCounts.get(setName);
|
||||
let old_bonus = {};
|
||||
if (setCount) {
|
||||
old_bonus = sets[setName].bonuses[setCount-1];
|
||||
old_bonus = sets.get(setName).bonuses[setCount-1];
|
||||
activeSetCounts.set(setName, setCount + 1);
|
||||
}
|
||||
else {
|
||||
setCount = 0;
|
||||
activeSetCounts.set(setName, 1);
|
||||
}
|
||||
const new_bonus = sets[setName].bonuses[setCount];
|
||||
const new_bonus = sets.get(setName).bonuses[setCount];
|
||||
//let skp_order = ["str","dex","int","def","agi"];
|
||||
for (const i in skp_order) {
|
||||
const delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
||||
|
@ -75,8 +74,8 @@ function calculate_skillpoints(equipment, weapon) {
|
|||
if (setName) { // undefined/null means no set.
|
||||
const setCount = activeSetCounts.get(setName);
|
||||
if (setCount) {
|
||||
const old_bonus = sets[setName].bonuses[setCount-1];
|
||||
const new_bonus = sets[setName].bonuses[setCount];
|
||||
const old_bonus = sets.get(setName).bonuses[setCount-1];
|
||||
const new_bonus = sets.get(setName).bonuses[setCount];
|
||||
//let skp_order = ["str","dex","int","def","agi"];
|
||||
for (const i in skp_order) {
|
||||
const set_delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
||||
|
|
34
js/sq2icons.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
//which icons to use
|
||||
let window_storage = window.localStorage;
|
||||
console.log(window_storage);
|
||||
icon_state_stored = window_storage.getItem("newicons");
|
||||
newIcons = true;
|
||||
if (icon_state_stored === "false") {toggleIcons()}
|
||||
|
||||
|
||||
/** Toggle icons on the ENTIRE page.
|
||||
*
|
||||
*/
|
||||
function toggleIcons() {
|
||||
newIcons = !newIcons;
|
||||
let imgs = document.getElementsByTagName("IMG");
|
||||
let favicon = document.querySelector("link[rel~='icon']");
|
||||
|
||||
if (newIcons) { //switch to new
|
||||
favicon.href = favicon.href.replace("media/icons/old","media/icons/new");
|
||||
for (const img of imgs) {
|
||||
if (img.src.includes("media/icons/old")) {img.src = img.src.replace("media/icons/old","media/icons/new");}
|
||||
if (img.src.includes("media/items/old")) {img.src = img.src.replace("media/items/old","media/items/new");}
|
||||
}
|
||||
//toggleiconbutton.textContent = "Use Old Icons";
|
||||
window_storage.setItem("newicons","true");
|
||||
} else { //switch to old
|
||||
favicon.href = favicon.href.replace("media/icons/new","media/icons/old");
|
||||
for (const img of imgs) {
|
||||
if (img.src.includes("media/icons/new")) {img.src = img.src.replace("media/icons/new","media/icons/old");}
|
||||
if (img.src.includes("media/items/new")) {img.src = img.src.replace("media/items/new","media/items/old");}
|
||||
}
|
||||
//toggleiconbutton.textContent = "Use New Icons";
|
||||
window_storage.setItem("newicons","false");
|
||||
}
|
||||
}
|
256
js/sq2items.js
Normal file
|
@ -0,0 +1,256 @@
|
|||
document.addEventListener("DOMContentLoaded", function() {
|
||||
let filterInputs = new Map([["item-category", ["ALL", "armor", "helmet", "chestplate", "leggings", "boots", "accessory", "ring", "bracelet", "necklace", "weapon", "wand", "spear", "bow", "dagger", "relik"]],
|
||||
["item-rarity", ["ANY", "Normal", "Unique", "Set", "Rare", "Legendary", "Fabled", "Mythic", "Sane"]],
|
||||
["filter1", sq2ItemFilters],
|
||||
["filter2", sq2ItemFilters],
|
||||
["filter3", sq2ItemFilters],
|
||||
["filter4", sq2ItemFilters]]);
|
||||
for (const [field, data] of filterInputs) {
|
||||
let field_choice = document.getElementById(field+"-choice");
|
||||
// show dropdown on click
|
||||
field_choice.onclick = function() {field_choice.dispatchEvent(new Event('input', {bubbles:true}));};
|
||||
filterInputs.set(field, new autoComplete({
|
||||
data: {
|
||||
src: data,
|
||||
},
|
||||
threshold: 0,
|
||||
selector: "#"+ field +"-choice",
|
||||
wrapper: false,
|
||||
resultsList: {
|
||||
maxResults: 100,
|
||||
tabSelect: true,
|
||||
noResults: true,
|
||||
class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm",
|
||||
element: (list, data) => {
|
||||
let position = document.getElementById(field+'-choice').getBoundingClientRect();
|
||||
list.style.top = position.bottom + window.scrollY +"px";
|
||||
list.style.left = position.x+"px";
|
||||
list.style.width = position.width+"px";
|
||||
list.style.maxHeight = position.height * 4 +"px";
|
||||
|
||||
if (!data.results.length) {
|
||||
message = document.createElement('li');
|
||||
message.classList.add('scaled-font');
|
||||
message.textContent = "No results found!";
|
||||
list.prepend(message);
|
||||
};
|
||||
},
|
||||
},
|
||||
resultItem: {
|
||||
class: "scaled-font search-item",
|
||||
selected: "dark-5",
|
||||
},
|
||||
events: {
|
||||
input: {
|
||||
selection: (event) => {
|
||||
if (event.detail.selection.value) {
|
||||
event.target.value = event.detail.selection.value;
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
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);
|
||||
displaysq2ExpandedItem(item, bckgrdbox.id);
|
||||
}
|
||||
}
|
||||
|
||||
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 = translate_mappings[raw_dat];
|
||||
if (filter_dat !== undefined) {
|
||||
queries.push(new IdQuery(filter_dat));
|
||||
continue;
|
||||
}
|
||||
filter_dat = 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) );
|
||||
}
|
||||
|
||||
load_init(init_items);
|
418
js/utils.js
|
@ -1,32 +1,8 @@
|
|||
let getUrl = window.location;
|
||||
const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
|
||||
|
||||
const zip = (a, b) => a.map((k, i) => [k, b[i]]);
|
||||
|
||||
//updates all the OGP tags for a webpage. Should be called when build changes
|
||||
function updateOGP() {
|
||||
//update the embed URL
|
||||
let url_elem = document.getElementById("ogp-url");
|
||||
if (url_elem) {
|
||||
url_elem.content = url_base+location.hash;
|
||||
}
|
||||
|
||||
//update the embed text content
|
||||
let build_elem = document.getElementById("ogp-build-list");
|
||||
if (build_elem && player_build) {
|
||||
let text = "WynnBuilder build:\n"+
|
||||
"> "+player_build.helmet.get("displayName")+"\n"+
|
||||
"> "+player_build.chestplate.get("displayName")+"\n"+
|
||||
"> "+player_build.leggings.get("displayName")+"\n"+
|
||||
"> "+player_build.boots.get("displayName")+"\n"+
|
||||
"> "+player_build.ring1.get("displayName")+"\n"+
|
||||
"> "+player_build.ring2.get("displayName")+"\n"+
|
||||
"> "+player_build.bracelet.get("displayName")+"\n"+
|
||||
"> "+player_build.necklace.get("displayName")+"\n"+
|
||||
"> "+player_build.weapon.get("displayName")+" ["+player_build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]";
|
||||
build_elem.content = text;
|
||||
}
|
||||
}
|
||||
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]]);
|
||||
|
||||
function clamp(num, low, high){
|
||||
return Math.min(Math.max(num, low), high);
|
||||
|
@ -98,8 +74,10 @@ function log(b, n) {
|
|||
// https://stackoverflow.com/a/27696695
|
||||
// Modified for fixed precision
|
||||
|
||||
// Base64.fromInt(-2147483648); // gives "200000"
|
||||
// Base64.toInt("200000"); // gives -2147483648
|
||||
Base64 = (function () {
|
||||
var digitsStr =
|
||||
var digitsStr =
|
||||
// 0 8 16 24 32 40 48 56 63
|
||||
// v v v v v v v v v
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-";
|
||||
|
@ -149,8 +127,246 @@ Base64 = (function () {
|
|||
};
|
||||
})();
|
||||
|
||||
// Base64.fromInt(-2147483648); // gives "200000"
|
||||
// Base64.toInt("200000"); // gives -2147483648
|
||||
|
||||
/** A class used to represent an arbitrary length bit vector. Very useful for encoding and decoding.
|
||||
*
|
||||
*/
|
||||
class BitVector {
|
||||
|
||||
/** Constructs an arbitrary-length bit vector.
|
||||
* @class
|
||||
* @param {String | Number} data - The data to append.
|
||||
* @param {Number} length - A set length for the data. Ignored if data is a string.
|
||||
*
|
||||
* The structure of the Uint32Array should be [[last, ..., first], ..., [last, ..., first], [empty space, last, ..., first]]
|
||||
*/
|
||||
constructor(data, length) {
|
||||
let bit_vec = [];
|
||||
|
||||
if (typeof data === "string") {
|
||||
let int = 0;
|
||||
let bv_idx = 0;
|
||||
length = data.length * 6;
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let char = Base64.toInt(data[i]);
|
||||
let pre_pos = bv_idx % 32;
|
||||
int |= (char << bv_idx);
|
||||
bv_idx += 6;
|
||||
let post_pos = bv_idx % 32;
|
||||
if (post_pos < pre_pos) { //we have to have filled up the integer
|
||||
bit_vec.push(int);
|
||||
int = (char >>> (6 - post_pos));
|
||||
}
|
||||
|
||||
if (i == data.length - 1 && post_pos != 0) {
|
||||
bit_vec.push(int);
|
||||
}
|
||||
}
|
||||
} else if (typeof data === "number") {
|
||||
if (typeof length === "undefined")
|
||||
if (length < 0) {
|
||||
throw new RangeError("BitVector must have nonnegative length.");
|
||||
}
|
||||
|
||||
//convert to int just in case
|
||||
data = Math.round(data);
|
||||
|
||||
//range of numbers that won't fit in a uint32
|
||||
if (data > 2**32 - 1 || data < -(2 ** 32 - 1)) {
|
||||
throw new RangeError("Numerical data has to fit within a 32-bit integer range to instantiate a BitVector.");
|
||||
}
|
||||
bit_vec.push(data);
|
||||
} else {
|
||||
throw new TypeError("BitVector must be instantiated with a Number or a B64 String");
|
||||
}
|
||||
|
||||
this.length = length;
|
||||
this.bits = new Uint32Array(bit_vec);
|
||||
}
|
||||
|
||||
/** Return value of bit at index idx.
|
||||
*
|
||||
* @param {Number} idx - The index to read
|
||||
*
|
||||
* @returns The bit value at position idx
|
||||
*/
|
||||
read_bit(idx) {
|
||||
if (idx < 0 || idx >= this.length) {
|
||||
throw new RangeError("Cannot read bit outside the range of the BitVector. ("+idx+" > "+this.length+")");
|
||||
}
|
||||
return ((this.bits[Math.floor(idx / 32)] & (1 << idx)) == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
/** Returns an integer value (if possible) made from the range of bits [start, end). Undefined behavior if the range to read is too big.
|
||||
*
|
||||
* @param {Number} start - The index to start slicing from. Inclusive.
|
||||
* @param {Number} end - The index to end slicing at. Exclusive.
|
||||
*
|
||||
* @returns An integer representation of the sliced bits.
|
||||
*/
|
||||
slice(start, end) {
|
||||
//TO NOTE: JS shifting is ALWAYS in mod 32. a << b will do a << (b mod 32) implicitly.
|
||||
|
||||
if (end < start) {
|
||||
throw new RangeError("Cannot slice a range where the end is before the start.");
|
||||
} else if (end == start) {
|
||||
return 0;
|
||||
} else if (end - start > 32) {
|
||||
//requesting a slice of longer than 32 bits (safe integer "length")
|
||||
throw new RangeError("Cannot slice a range of longer than 32 bits (unsafe to store in an integer).");
|
||||
}
|
||||
|
||||
let res = 0;
|
||||
if (Math.floor((end - 1) / 32) == Math.floor(start / 32)) {
|
||||
//the range is within 1 uint32 section - do some relatively fast bit twiddling
|
||||
res = (this.bits[Math.floor(start / 32)] & ~((((~0) << ((end - 1))) << 1) | ~((~0) << (start)))) >>> (start % 32);
|
||||
} else {
|
||||
//the number of bits in the uint32s
|
||||
let start_pos = (start % 32);
|
||||
let int_idx = Math.floor(start/32);
|
||||
res = (this.bits[int_idx] & ((~0) << (start))) >>> (start_pos);
|
||||
res |= (this.bits[int_idx + 1] & ~((~0) << (end))) << (32 - start_pos);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
// General code - slow
|
||||
// for (let i = start; i < end; i++) {
|
||||
// res |= (get_bit(i) << (i - start));
|
||||
// }
|
||||
}
|
||||
|
||||
/** Assign bit at index idx to 1.
|
||||
*
|
||||
* @param {Number} idx - The index to set.
|
||||
*/
|
||||
set_bit(idx) {
|
||||
if (idx < 0 || idx >= this.length) {
|
||||
throw new RangeError("Cannot set bit outside the range of the BitVector.");
|
||||
}
|
||||
this.bits[Math.floor(idx / 32)] |= (1 << idx % 32);
|
||||
}
|
||||
|
||||
/** Assign bit at index idx to 0.
|
||||
*
|
||||
* @param {Number} idx - The index to clear.
|
||||
*/
|
||||
clear_bit(idx) {
|
||||
if (idx < 0 || idx >= this.length) {
|
||||
throw new RangeError("Cannot clear bit outside the range of the BitVector.");
|
||||
}
|
||||
this.bits[Math.floor(idx / 32)] &= ~(1 << idx % 32);
|
||||
}
|
||||
|
||||
/** Creates a string version of the bit vector in B64. Does not keep the order of elements a sensible human readable format.
|
||||
*
|
||||
* @returns A b64 string representation of the BitVector.
|
||||
*/
|
||||
toB64() {
|
||||
if (this.length == 0) {
|
||||
return "";
|
||||
}
|
||||
let b64_str = "";
|
||||
let i = 0;
|
||||
while (i < this.length) {
|
||||
b64_str += Base64.fromIntV(this.slice(i, i + 6), 1);
|
||||
i += 6;
|
||||
}
|
||||
|
||||
return b64_str;
|
||||
}
|
||||
|
||||
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
|
||||
*
|
||||
* @returns A bit string representation of the BitVector. Goes from higher-indexed bits to lower-indexed bits. (n ... 0)
|
||||
*/
|
||||
toString() {
|
||||
let ret_str = "";
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
ret_str = (this.read_bit(i) == 0 ? "0": "1") + ret_str;
|
||||
}
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
|
||||
*
|
||||
* @returns A bit string representation of the BitVector. Goes from lower-indexed bits to higher-indexed bits. (0 ... n)
|
||||
*/
|
||||
toStringR() {
|
||||
let ret_str = "";
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
ret_str += (this.read_bit(i) == 0 ? "0": "1");
|
||||
}
|
||||
return ret_str;
|
||||
}
|
||||
|
||||
/** Appends data to the BitVector.
|
||||
*
|
||||
* @param {Number | String} data - The data to append.
|
||||
* @param {Number} length - The length, in bits, of the new data. This is ignored if data is a string.
|
||||
*/
|
||||
append(data, length) {
|
||||
if (length < 0) {
|
||||
throw new RangeError("BitVector length must increase by a nonnegative number.");
|
||||
}
|
||||
|
||||
let bit_vec = [];
|
||||
for (const uint of this.bits) {
|
||||
bit_vec.push(uint);
|
||||
}
|
||||
if (typeof data === "string") {
|
||||
let int = bit_vec[bit_vec.length - 1];
|
||||
let bv_idx = this.length;
|
||||
length = data.length * 6;
|
||||
let updated_curr = false;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let char = Base64.toInt(data[i]);
|
||||
let pre_pos = bv_idx % 32;
|
||||
int |= (char << bv_idx);
|
||||
bv_idx += 6;
|
||||
let post_pos = bv_idx % 32;
|
||||
if (post_pos < pre_pos) { //we have to have filled up the integer
|
||||
if (bit_vec.length == this.bits.length && !updated_curr) {
|
||||
bit_vec[bit_vec.length - 1] = int;
|
||||
updated_curr = true;
|
||||
} else {
|
||||
bit_vec.push(int);
|
||||
}
|
||||
int = (char >>> (6 - post_pos));
|
||||
}
|
||||
|
||||
if (i == data.length - 1) {
|
||||
if (bit_vec.length == this.bits.length && !updated_curr) {
|
||||
bit_vec[bit_vec.length - 1] = int;
|
||||
} else if (post_pos != 0) {
|
||||
bit_vec.push(int);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (typeof data === "number") {
|
||||
//convert to int just in case
|
||||
let int = Math.round(data);
|
||||
|
||||
//range of numbers that "could" fit in a uint32 -> [0, 2^32) U [-2^31, 2^31)
|
||||
if (data > 2**32 - 1 || data < -(2 ** 31)) {
|
||||
throw new RangeError("Numerical data has to fit within a 32-bit integer range to instantiate a BitVector.");
|
||||
}
|
||||
//could be split between multiple new ints
|
||||
//reminder that shifts implicitly mod 32
|
||||
bit_vec[bit_vec.length - 1] |= ((int & ~((~0) << length)) << (this.length));
|
||||
if (((this.length - 1) % 32 + 1) + length > 32) {
|
||||
bit_vec.push(int >>> (32 - this.length));
|
||||
}
|
||||
} else {
|
||||
throw new TypeError("BitVector must be appended with a Number or a B64 String");
|
||||
}
|
||||
|
||||
this.bits = new Uint32Array(bit_vec);
|
||||
this.length += length;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Turns a raw stat and a % stat into a final stat on the basis that - raw and >= 100% becomes 0 and + raw and <=-100% becomes negative.
|
||||
|
@ -262,7 +478,7 @@ function randomColor() {
|
|||
|
||||
/**
|
||||
* Generates a random color, but lightning must be relatively high (>0.5).
|
||||
*
|
||||
*
|
||||
* @returns a random color in RGB 6-bit form.
|
||||
*/
|
||||
function randomColorLight() {
|
||||
|
@ -270,7 +486,7 @@ function randomColorLight() {
|
|||
}
|
||||
|
||||
/** Generates a random color given HSL restrictions.
|
||||
*
|
||||
*
|
||||
* @returns a random color in RGB 6-bit form.
|
||||
*/
|
||||
function randomColorHSL(h,s,l) {
|
||||
|
@ -322,8 +538,8 @@ function randomColorHSL(h,s,l) {
|
|||
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||
}
|
||||
|
||||
/** Creates a tooltip.
|
||||
*
|
||||
/** Creates a tooltip.
|
||||
*
|
||||
* @param {DOM Element} elem - the element to make a tooltip
|
||||
* @param {String} element_type - the HTML element type that the tooltiptext should be.
|
||||
* @param {String} tooltiptext - the text to display in the tooltip.
|
||||
|
@ -354,7 +570,7 @@ function createTooltip(elem, element_type, tooltiptext, parent, classList) {
|
|||
}
|
||||
|
||||
/** A generic function that toggles the on and off state of a button.
|
||||
*
|
||||
*
|
||||
* @param {String} button_id - the id name of the button.
|
||||
*/
|
||||
function toggleButton(button_id) {
|
||||
|
@ -398,14 +614,142 @@ function addClasses(elem, classes) {
|
|||
return elem;
|
||||
}
|
||||
|
||||
/** A utility function that reloads the page forcefully.
|
||||
*
|
||||
/** A utility function that reloads the page forcefully.
|
||||
*
|
||||
*/
|
||||
async function hardReload() {
|
||||
//https://gist.github.com/rmehner/b9a41d9f659c9b1c3340
|
||||
const dbs = await window.indexedDB.databases();
|
||||
await dbs.forEach(db => { window.indexedDB.deleteDatabase(db.name) });
|
||||
|
||||
location.reload();
|
||||
location.reload(true);
|
||||
}
|
||||
|
||||
|
||||
function capitalizeFirst(str) {
|
||||
return str[0].toUpperCase() + str.substring(1);
|
||||
}
|
||||
|
||||
/** https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript
|
||||
* If we ever want to write something that needs to import other js files
|
||||
*/
|
||||
const getScript = url => new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = url;
|
||||
script.async = true;
|
||||
|
||||
script.onerror = reject;
|
||||
|
||||
script.onload = script.onreadystatechange = function () {
|
||||
const loadState = this.readyState;
|
||||
|
||||
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
|
||||
|
||||
script.onload = script.onreadystatechange = null;
|
||||
|
||||
resolve();
|
||||
}
|
||||
|
||||
document.head.appendChild(script);
|
||||
})
|
||||
|
||||
/*
|
||||
GENERIC TEST FUNCTIONS
|
||||
*/
|
||||
/** The generic assert function. Fails on all "false-y" values. Useful for non-object equality checks, boolean value checks, and existence checks.
|
||||
*
|
||||
* @param {*} arg - argument to assert.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert(arg, msg) {
|
||||
if (!arg) {
|
||||
throw new Error(msg ? msg : "Assert failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts object equality of the 2 parameters. For loose and strict asserts, use assert().
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_equals(arg1, arg2, msg) {
|
||||
if (!Object.is(arg1, arg2)) {
|
||||
throw new Error(msg ? msg : "Assert Equals failed. " + arg1 + " is not " + arg2 + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts object inequality of the 2 parameters. For loose and strict asserts, use assert().
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_not_equals(arg1, arg2, msg) {
|
||||
if (Object.is(arg1, arg2)) {
|
||||
throw new Error(msg ? msg : "Assert Not Equals failed. " + arg1 + " is " + arg2 + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts proximity between 2 arguments. Should be used for any floating point datatype.
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {Number} epsilon - the margin of error (<= del difference is ok). Defaults to -1E5.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_near(arg1, arg2, epsilon = 1E-5, msg) {
|
||||
if (Math.abs(arg1 - arg2) > epsilon) {
|
||||
throw new Error(msg ? msg : "Assert Near failed. " + arg1 + " is not within " + epsilon + " of " + arg2 + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts that the input argument is null.
|
||||
*
|
||||
* @param {*} arg - the argument to test for null.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_null(arg, msg) {
|
||||
if (arg !== null) {
|
||||
throw new Error(msg ? msg : "Assert Near failed. " + arg + " is not null.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts that the input argument is undefined.
|
||||
*
|
||||
* @param {*} arg - the argument to test for undefined.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_undefined(arg, msg) {
|
||||
if (arg !== undefined) {
|
||||
throw new Error(msg ? msg : "Assert Near failed. " + arg + " is not undefined.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts that there is an error when a callback function is run.
|
||||
*
|
||||
* @param {Function} func_binding - a function binding to run. Can be passed in with func.bind(null, arg1, ..., argn)
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_error(func_binding, msg) {
|
||||
try {
|
||||
func_binding();
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
throw new Error(msg ? msg : "Function didn't throw an error.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy object/array of basic types.
|
||||
*/
|
||||
function deepcopy(obj) {
|
||||
if (typeof(obj) !== 'object' || obj === null) { // null or value type
|
||||
return obj;
|
||||
}
|
||||
let ret = Array.isArray(obj) ? [] : {};
|
||||
for (let key in obj) {
|
||||
ret[key] = deepcopy(obj[key]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
132
map.html
|
@ -1,132 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<meta name="HandheldFriendly" content="true" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, width=device-width, user-scalable=no" />
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/css/styles.css">
|
||||
<link rel="stylesheet" media="screen and (min-width: 900px)" href="/css/map-wide.css"/>
|
||||
<link rel="stylesheet" media="screen and (max-width: 899px)" href="/css/map-narrow.css"/>
|
||||
<link rel="icon" href="./media/icons/new/compass2.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<!--Leaflet for map-->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
|
||||
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
|
||||
crossorigin="anonymous"/>
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
|
||||
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<title>WynnGPS</title>
|
||||
</head>
|
||||
<body class="all">
|
||||
<div class="center">
|
||||
<header class = "header nomarginp">
|
||||
<div class = "headerleft" id = "headerleft">
|
||||
</div>
|
||||
<div class = "headercenter" id = "headercenter">
|
||||
<div>
|
||||
<p class = "itemp" id = "header">WynnGPS</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "headerright" id = "headerright">
|
||||
<p class = "center">Right click to place marker.</p>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
<br>
|
||||
<div class = "overall-container" display = "grid">
|
||||
<div id = "mapdiv" class ="mapdiv container" display = "grid-item-1">
|
||||
|
||||
</div>
|
||||
<div id = "mapoptions-container" class = "container" display = "grid-item-2">
|
||||
<div id = "coord-container" class = "center coord-container" display = "grid">
|
||||
<p class = "nomargin"></p>
|
||||
<p class = "title center nomargin">Options</p>
|
||||
<p class = "nomargin"></p>
|
||||
<p class = "nomargin" display = "grid-item-1">X</p>
|
||||
<p class = "nomargin" display = "grid-item-2"></p>
|
||||
<p class = "nomargin" display = "grid-item-3">Z</p>
|
||||
<p class = "nomargin" id = "coord-x" display = "grid-item-4"></p>
|
||||
<p class = "nomargin" id = "coord-img" display = "grid-item-5">
|
||||
<img src = "/media/icons/new/compass2.png" alt style = "max-width:32px; max-height:32px"/>
|
||||
</p>
|
||||
<p class = "nomargin" id = "coord-z" display = "grid-item-6"></p>
|
||||
<p class = "nomargin" id = "marker-coord-x" display = "none"></p>
|
||||
<p class = "nomargin" id = "marker-coord-img" display = "none">
|
||||
<img src = "/media/icons/new/marker.png" alt style = "max-width:32px; max-height:32px"/>
|
||||
</p>
|
||||
<p class = "nomargin" id = "marker-coord-z" display = "none"></p>
|
||||
</div>
|
||||
<div id = "button-choices container">
|
||||
<button class = "left" id = "territories-button" onclick = "toggleButton('territories-button'); toggleTerritories()">Show Territories</button>
|
||||
<button class = "left" id = "claims-button" onclick = "toggleButton('claims-button'); toggleClaims()">Show Claims</button>
|
||||
<button class = "left" id = "routes-button" onclick = "toggleButton('routes-button'); toggleRoutes()">Show Routes</button>
|
||||
<button class = "left" id = "resources-button" onclick = "toggleButton('resources-button'); toggleResources()">Show Resources</button>
|
||||
<button class = "left" id = "locations-button" onclick = "toggleButton('locations-button'); toggleLocations()">Show Locations</button>
|
||||
<button class = "left" id = "pull-button" onclick = "refreshData()">Refresh Data</button>
|
||||
<p class = "left" style = "color:red">Do NOT refresh too often.</p>
|
||||
</div>
|
||||
<div id ="territory-stats">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id = "key-container" class = "container">
|
||||
<div id = "key-title" class = "center">
|
||||
<p class = "center title"> All Keys </p>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div id = "guild-key" style = "display:none">
|
||||
<p class = "left">Guild Key:</p>
|
||||
<ul id = "guildkeylist">
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id = "resources-key" style = "display:none">
|
||||
<p class = "left">Resource Key:</p>
|
||||
<ul id = "resourcelist">
|
||||
<li><img src= "media/icons/new/Emeralds.png" style ="max-width:16px;max-height:16px" class = "Emeralds"/> Emeralds</li>
|
||||
<li><img src= "media/icons/new/Ore.png" style ="max-width:16px;max-height:16px" class = "Ore"/> Ore</li>
|
||||
<li><img src= "media/icons/new/Wood.png" style ="max-width:16px;max-height:16px" class = "Wood"/> Wood</li>
|
||||
<li><img src= "media/icons/new/Crops.png" style ="max-width:16px;max-height:16px" class = "Crops"/> Crops</li>
|
||||
<li><img src= "media/icons/new/Fish.png" style ="max-width:16px;max-height:16px" class = "Fish"/> Fish</li>
|
||||
<li><img src= "media/icons/new/Chest.png" style ="max-width:16px;max-height:16px" class = "Wood"/> Storage</li>
|
||||
<li><img src= "media/icons/new/Gears.png" style ="max-width:16px;max-height:16px" class = "Ore"/> Production</li>
|
||||
<li>Double image means double generation</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id = "locations-key" style = "display:none">
|
||||
<p class = "left">Locations Key:</p>
|
||||
<ul id = "locationlist">
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/js/utils.js"></script>
|
||||
<script type="text/javascript" src="/js/loadheader.js"></script>
|
||||
<script type="text/javascript" src="/js/icons.js"></script>
|
||||
<script type="text/javascript" src="/js/load_map.js"></script>
|
||||
<script type="text/javascript" src="/js/map.js"></script>
|
||||
</body>
|
||||
</html>
|
164
map/index.html
Normal file
|
@ -0,0 +1,164 @@
|
|||
<!DOCTYPE html>
|
||||
<html scroll-behavior="smooth">
|
||||
<head>
|
||||
<meta name="HandheldFriendly" content="true" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, width=device-width, user-scalable=no" />
|
||||
<!-- nunito font, copying wynndata -->
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=.45, user-scalable=no">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Nunito&display=swap" rel="stylesheet">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tarekraafat/autocomplete.js@10.2.6/dist/css/autoComplete.min.css">
|
||||
|
||||
|
||||
<link rel="stylesheet" media="screen and (min-width: 900px)" href="/css/map-wide.css"/>
|
||||
<link rel="stylesheet" media="screen and (max-width: 899px)" href="/css/map-narrow.css"/>
|
||||
<link rel="stylesheet" href="../css/sq2bs.css">
|
||||
<link rel="stylesheet" href="../css/sidebar.css">
|
||||
<link rel="stylesheet" href="../css/wynnstyles.css">
|
||||
<link rel="icon" href="../media/icons/new/compass2.png">
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<!--Leaflet for map-->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
|
||||
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
|
||||
crossorigin="anonymous"/>
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
|
||||
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
<title>WynnGPS</title>
|
||||
</head>
|
||||
<body class = "text-light d-flex justify-content-center" id = "body">
|
||||
<div id="main-sidebar" class="sidebar dark-7 dark-shadow">
|
||||
<a href = "../builder/"><img src="../media/icons/new/builder.png" alt = "WynnBuilder" title = "WynnBuilder"><b>WynnBuilder</b></a>
|
||||
<a href = "../crafter/"><img src = "../media/icons/new/crafter.png" alt = "WynnCrafter" title = "WynnCrafter"><b>WynnCrafter</b></a>
|
||||
<a href = "../items/"><img src = "../media/icons/new/searcher.png" alt = "WynnAtlas" title = "WynnAtlas"><b>WynnAtlas</b></a>
|
||||
<a href = "../custom/"><img src = "../media/icons/new/custom.png" alt = "WynnCustom" title = "WynnCustom"><b>WynnCustom</b></a>
|
||||
<a href = ""><img src = "../media/icons/new/compass.png" alt = "WynnGPS" title = "WynnGPS"><b>WynnGPS</b></a>
|
||||
<a href = "../wynnfo/"><img src = "../media/icons/new/book.png" alt = "Wynnfo" title = "Wynnfo"><b>Wynnfo</b></a>
|
||||
<a onclick = "toggleIcons()"><img src = "../media/icons/new/reload.png" alt = "" title = "Swap items on page"><b>Swap Icon Style</b></a>
|
||||
<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 = "mapdiv" class = "col-lg-8 col-sm-12 rounded border border-light border-3">
|
||||
|
||||
</div>
|
||||
<div id = "mapoptions-container" class = "col-lg-3 col-sm-12 rounded border border-light border-3 mx-1">
|
||||
<div id = "coord-container" class = "row justify-content-center text-center">
|
||||
<div class = "row big-title justify-content-center">Options</div>
|
||||
<div class = "row justify-content-center">Right click to place marker.</div>
|
||||
<div class = "row">
|
||||
<div class = "col-4">X</div>
|
||||
<div class = "col-4"></div>
|
||||
<div class = "col-4">Z</div>
|
||||
</div>
|
||||
<div class = "row mb-1">
|
||||
<div class = "col-4" id = "coord-x"></div>
|
||||
<div class = "col-4">
|
||||
<p class = "nomargin" id = "coord-img">
|
||||
<img src = "../media/icons/new/compass2.png" alt style = "max-width:32px; max-height:32px"/>
|
||||
</p>
|
||||
</div>
|
||||
<div class = "col-4" id = "coord-z"></div>
|
||||
</div>
|
||||
<div class = "row mb-1">
|
||||
<div class = "col-4" id = "marker-coord-x"></div>
|
||||
<div class = "col-4">
|
||||
<p class = "nomargin" id = "marker-coord-img" display = "none">
|
||||
<img src = "../media/icons/new/marker.png" alt style = "max-width:32px; max-height:32px"/>
|
||||
</p>
|
||||
</div>
|
||||
<div class = "col-4" id = "marker-coord-z"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id = "button-choices row">
|
||||
<div class = "row">
|
||||
<div class = "col mb-1">
|
||||
<button class = "w-100 button rounded scaled-font fw-bold text-light dark-5" id = "territories-button" onclick = "toggleButton('territories-button'); toggleTerritories()">Show Territories</button>
|
||||
</div>
|
||||
<div class = "col mb-1">
|
||||
<button class = "w-100 button rounded scaled-font fw-bold text-light dark-5" id = "claims-button" onclick = "toggleButton('claims-button'); toggleClaims()">Show Claims</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row">
|
||||
<div class = "col mb-1">
|
||||
<button class = "w-100 button rounded scaled-font fw-bold text-light dark-5" id = "routes-button" onclick = "toggleButton('routes-button'); toggleRoutes()">Show Routes</button>
|
||||
</div>
|
||||
<div class = "col mb-1">
|
||||
<button class = "w-100 button rounded scaled-font fw-bold text-light dark-5" id = "resources-button" onclick = "toggleButton('resources-button'); toggleResources()">Show Resources</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row">
|
||||
<div class = "col mb-1">
|
||||
<button class = "w-100 button rounded scaled-font fw-bold text-light dark-5" id = "locations-button" onclick = "toggleButton('locations-button'); toggleLocations()">Show Locations</button>
|
||||
</div>
|
||||
<div class = "col mb-1">
|
||||
<button class = "w-100 button rounded scaled-font fw-bold text-light dark-5" id = "pull-button" onclick = "refreshData()">Refresh Data</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "row">
|
||||
<p class = "warning" style = "color:red">Do NOT refresh too often.</p>
|
||||
</div>
|
||||
|
||||
<div id ="territory-stats">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id = "key-container" class = "row rounded border border-light border-3 my-1">
|
||||
<div id = "key-title" class = "row">
|
||||
<p class = "item-title"> All Keys </p>
|
||||
</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<div id = "guild-key" style = "display:none">
|
||||
<p class = "left">Guild Key:</p>
|
||||
<ul id = "guildkeylist">
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id = "resources-key" style = "display:none">
|
||||
<p class = "left">Resource Key:</p>
|
||||
<ul id = "resourcelist">
|
||||
<li><img src= "../media/icons/new/Emeralds.png" style ="max-width:16px;max-height:16px" class = "Emeralds"/> Emeralds</li>
|
||||
<li><img src= "../media/icons/new/Ore.png" style ="max-width:16px;max-height:16px" class = "Ore"/> Ore</li>
|
||||
<li><img src= "../media/icons/new/Wood.png" style ="max-width:16px;max-height:16px" class = "Wood"/> Wood</li>
|
||||
<li><img src= "../media/icons/new/Crops.png" style ="max-width:16px;max-height:16px" class = "Crops"/> Crops</li>
|
||||
<li><img src= "../media/icons/new/Fish.png" style ="max-width:16px;max-height:16px" class = "Fish"/> Fish</li>
|
||||
<li><img src= "../media/icons/new/Chest.png" style ="max-width:16px;max-height:16px" class = "Wood"/> Storage</li>
|
||||
<li><img src= "../media/icons/new/Gears.png" style ="max-width:16px;max-height:16px" class = "Ore"/> Production</li>
|
||||
<li>Double image means double generation</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div id = "locations-key" style = "display:none">
|
||||
<p class = "left">Locations Key:</p>
|
||||
<ul id = "locationlist">
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script type="text/javascript" src="../js/utils.js"></script>
|
||||
<script type="text/javascript" src="../js/icons.js"></script>
|
||||
<script type="text/javascript" src="../js/load_map.js"></script>
|
||||
<script type="text/javascript" src="../js/map.js"></script>
|
||||
</body>
|
||||
</html>
|
BIN
media/atree/connect_angle.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/connect_c.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
media/atree/connect_line.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
media/atree/connect_t.png
Normal file
After Width: | Height: | Size: 962 B |
BIN
media/atree/highlight_angle.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
media/atree/highlight_c.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
media/atree/highlight_c_2_a.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/highlight_c_2_l.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/highlight_c_3.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/highlight_line.png
Normal file
After Width: | Height: | Size: 974 B |
BIN
media/atree/highlight_t_2_a.png
Normal file
After Width: | Height: | Size: 708 B |
BIN
media/atree/highlight_t_2_l.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
media/atree/highlight_t_3.png
Normal file
After Width: | Height: | Size: 654 B |
BIN
media/atree/node-blocked.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
media/atree/node-selected.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
media/atree/node.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
media/atree/node_0.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
media/atree/node_0_blocked.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
media/atree/node_1.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
media/atree/node_1_blocked.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
media/atree/node_2.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
media/atree/node_2_blocked.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
media/atree/node_3.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
media/atree/node_3_blocked.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
media/atree/node_4.png
Normal file
After Width: | Height: | Size: 5.2 KiB |