fixed merge conflict
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
*.swp
|
*.swp
|
||||||
*.bat
|
*.bat
|
||||||
|
*.json
|
||||||
sets/
|
sets/
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
</div>
|
</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 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>
|
</div>
|
||||||
|
<audio id="bruh_sound_effect" src="../media/audio/bruh_sound_effect.mp3" preload="auto"></audio>
|
||||||
</div>
|
</div>
|
||||||
<!-- sidebar -->
|
<!-- sidebar -->
|
||||||
|
|
||||||
|
|
1438
builder/doc.html
Normal file
|
@ -272,7 +272,7 @@
|
||||||
<div class="col-auto order-xl-0 order-1">
|
<div class="col-auto order-xl-0 order-1">
|
||||||
<div class="row h-100 dark-shadow dark-6 rounded" id='weapon-dropdown'>
|
<div class="row h-100 dark-shadow dark-6 rounded" id='weapon-dropdown'>
|
||||||
<div class="col-auto px-lg-1 g-0 dark-7 rounded-end my-auto text-center scaled-item-icon" id="weapon-img-loc">
|
<div class="col-auto px-lg-1 g-0 dark-7 rounded-end my-auto text-center scaled-item-icon" id="weapon-img-loc">
|
||||||
<img id="weapon-img" class="img-fluid rounded" src="../media/items/new/generic-wand.png">
|
<img id="weapon-img" class="img-fluid rounded" src="../media/items/new/generic-dagger.png">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
<div class="row row-cols-1 h-100 align-items-center">
|
<div class="row row-cols-1 h-100 align-items-center">
|
||||||
|
@ -307,15 +307,17 @@
|
||||||
<input class="equipment-input text-light form-control form-control-sm" id="level-choice" name="level-choice" value="106" placeholder="Build level" value="" tabindex="2"/>
|
<input class="equipment-input text-light form-control form-control-sm" id="level-choice" name="level-choice" value="106" placeholder="Build level" value="" tabindex="2"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto px-1 text-nowrap scaled-font">
|
<div class="col-auto px-1 text-nowrap scaled-font">
|
||||||
<button class="button fw-bold text-light dark-5 scaled-font rounded" id="reset-button" onclick="sq2ResetFields()">Reset</button>
|
<button class="button fw-bold text-light dark-5 scaled-font rounded" id="reset-button" onclick="resetFields()">Reset</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row align-items-center justify-content-center my-1">
|
<div class="row align-items-center justify-content-center my-1">
|
||||||
|
<div class="row align-items-center">
|
||||||
<div class="col-auto px-1 text-nowrap scaled-font">
|
<div class="col-auto px-1 text-nowrap scaled-font">
|
||||||
<button class="button fw-bold text-light dark-5 scaled-font rounded" id="copy-button" onclick="copyBuild()">Copy short</button>
|
<button class="border-dark text-light dark-5 scaled-font rounded" id=copy-button onclick="copyBuild()">Copy short</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto px-1 text-nowrap scaled-font">
|
<div class="col-auto px-1 text-nowrap scaled-font">
|
||||||
<button class="button fw-bold text-light dark-5 scaled-font rounded" id="share-button" onclick="shareBuild()">Copy for sharing</button>
|
<button class="border-dark text-light dark-5 scaled-font rounded" id=share-button onclick="shareBuild(player_build)">Copy for sharing</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -404,6 +406,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-center">
|
<div class="col text-center">
|
||||||
<div id="summary-box"></div>
|
<div id="summary-box"></div>
|
||||||
|
<div id="err-box"></div>
|
||||||
|
<div id="stack-box"></div>
|
||||||
<div id="str-warnings"></div>
|
<div id="str-warnings"></div>
|
||||||
<div id="dex-warnings"></div>
|
<div id="dex-warnings"></div>
|
||||||
<div id="int-warnings"></div>
|
<div id="int-warnings"></div>
|
||||||
|
@ -425,7 +429,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class = "col-3 py-2">
|
<div class = "col-3 py-2">
|
||||||
<button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-atree" onclick = "toggle_tab('atree-dropdown'); toggleButton('toggle-atree')">
|
<button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-atree" onclick = "toggle_tab('atree-dropdown'); toggleButton('toggle-atree')">
|
||||||
Show Ability Tree
|
Edit Abilities
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col-3 py-2">
|
<div class = "col-3 py-2">
|
||||||
|
@ -434,7 +438,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col-3 py-2">
|
<div class = "col-3 py-2">
|
||||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "edit-ID-button" onclick = "resetEditableIDs(); updateStats();">
|
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "edit-ID-button" onclick = "resetEditableIDs();">
|
||||||
Reset Edited IDs
|
Reset Edited IDs
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -612,13 +616,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown"">
|
<div class = "col dark-6 rounded-bottom my-3 my-xl-1" id = "atree-dropdown" style = "display:none;">
|
||||||
<div class="row row-cols-1 row-cols-xl-2">
|
<div class="row row-cols-1 row-cols-xl-2">
|
||||||
<div class="col border rounded dark-9 hide-scroll" id="atree-ui" style="height: 500px; overflow-y: auto;">
|
<div class="col border border-semi-light rounded dark-9 hide-scroll" id="atree-ui" style="height: 90vh; overflow-y: auto;">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col" id="atree-active">
|
<div class="col mx-auto" style="height: 90vh; overflow-y: auto;" id="atree-rhs">
|
||||||
Active:
|
<div class="col mx-auto" style="height: 2em; overflow-y: auto;" id="atree-header">
|
||||||
|
</div>
|
||||||
|
<div class="col mx-auto" style="overflow-y: auto;" id="atree-active">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -632,7 +639,7 @@
|
||||||
Spell Damage %:
|
Spell Damage %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="sdPct" name="sdPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="sdPct" name="sdPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "sdPct-base">
|
<div class = "row" id = "sdPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -643,7 +650,7 @@
|
||||||
Spell Damage Raw:
|
Spell Damage Raw:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="sdRaw" name="sdRaw" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="sdRaw" name="sdRaw" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "sdRaw-base">
|
<div class = "row" id = "sdRaw-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -654,7 +661,7 @@
|
||||||
Melee Damage %:
|
Melee Damage %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="mdPct" name="mdPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="mdPct" name="mdPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "mdPct-base">
|
<div class = "row" id = "mdPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -665,7 +672,7 @@
|
||||||
Melee Damage Raw:
|
Melee Damage Raw:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="mdRaw" name="mdRaw" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="mdRaw" name="mdRaw" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "mdRaw-base">
|
<div class = "row" id = "mdRaw-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -678,7 +685,7 @@
|
||||||
Poison:
|
Poison:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="poison" name="poison" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="poison" name="poison" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "poison-base">
|
<div class = "row" id = "poison-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -689,7 +696,7 @@
|
||||||
Damage %:
|
Damage %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="eDamPct" name="eDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="eDamPct" name="eDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "eDamPct-base">
|
<div class = "row" id = "eDamPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -700,7 +707,7 @@
|
||||||
Damage %:
|
Damage %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="tDamPct" name="tDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="tDamPct" name="tDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "tDamPct-base">
|
<div class = "row" id = "tDamPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -711,7 +718,7 @@
|
||||||
Damage %:
|
Damage %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="wDamPct" name="wDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="wDamPct" name="wDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "wDamPct-base">
|
<div class = "row" id = "wDamPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -724,7 +731,7 @@
|
||||||
Damage %:
|
Damage %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="fDamPct" name="fDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="fDamPct" name="fDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "fDamPct-base">
|
<div class = "row" id = "fDamPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -735,7 +742,7 @@
|
||||||
Damage %:
|
Damage %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="aDamPct" name="aDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="aDamPct" name="aDamPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "aDamPct-base">
|
<div class = "row" id = "aDamPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -746,7 +753,7 @@
|
||||||
+ Tier:
|
+ Tier:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="atkTier" name="atkTier" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="atkTier" name="atkTier" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "atkTier-base">
|
<div class = "row" id = "atkTier-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -765,7 +772,7 @@
|
||||||
Defense %:
|
Defense %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="eDefPct" name="eDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="eDefPct" name="eDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "eDefPct-base">
|
<div class = "row" id = "eDefPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -776,7 +783,7 @@
|
||||||
Defense %:
|
Defense %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="tDefPct" name="tDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="tDefPct" name="tDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "tDefPct-base">
|
<div class = "row" id = "tDefPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -787,7 +794,7 @@
|
||||||
Defense %:
|
Defense %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="wDefPct" name="wDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="wDefPct" name="wDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "wDefPct-base">
|
<div class = "row" id = "wDefPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -798,7 +805,7 @@
|
||||||
Defense %:
|
Defense %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="fDefPct" name="fDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="fDefPct" name="fDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "fDefPct-base">
|
<div class = "row" id = "fDefPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -811,7 +818,7 @@
|
||||||
Defense %:
|
Defense %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="aDefPct" name="aDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="aDefPct" name="aDefPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "aDefPct-base">
|
<div class = "row" id = "aDefPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -822,7 +829,7 @@
|
||||||
Health Regen Raw:
|
Health Regen Raw:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="hprRaw" name="hprRaw" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="hprRaw" name="hprRaw" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "hprRaw-base">
|
<div class = "row" id = "hprRaw-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -833,7 +840,7 @@
|
||||||
Health Regen %:
|
Health Regen %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="hprPct" name="hprPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="hprPct" name="hprPct" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "hprPct-base">
|
<div class = "row" id = "hprPct-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -844,7 +851,7 @@
|
||||||
Health Bonus:
|
Health Bonus:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="hpBonus" name="hpBonus" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="hpBonus" name="hpBonus" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "hpBonus-base">
|
<div class = "row" id = "hpBonus-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -860,7 +867,7 @@
|
||||||
1st Spell Cost %:
|
1st Spell Cost %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="spPct1" name="spPct1" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="spPct1" name="spPct1" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "spPct1-base">
|
<div class = "row" id = "spPct1-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -871,7 +878,7 @@
|
||||||
2nd Spell Cost %:
|
2nd Spell Cost %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="spPct2" name="spPct2" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="spPct2" name="spPct2" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "spPct2-base">
|
<div class = "row" id = "spPct2-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -882,7 +889,7 @@
|
||||||
3rd Spell Cost %:
|
3rd Spell Cost %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="spPct3" name="spPct3" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="spPct3" name="spPct3" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "spPct3-base">
|
<div class = "row" id = "spPct3-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -893,7 +900,7 @@
|
||||||
4th Spell Cost %:
|
4th Spell Cost %:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="spPct4" name="spPct4" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="spPct4" name="spPct4" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "spPct4-base">
|
<div class = "row" id = "spPct4-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -906,7 +913,7 @@
|
||||||
1st Spell Cost Raw:
|
1st Spell Cost Raw:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="spRaw1" name="spRaw1" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="spRaw1" name="spRaw1" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "spRaw1-base">
|
<div class = "row" id = "spRaw1-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -917,7 +924,7 @@
|
||||||
2nd Spell Cost Raw:
|
2nd Spell Cost Raw:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="spRaw2" name="spRaw2" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="spRaw2" name="spRaw2" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "spRaw2-base">
|
<div class = "row" id = "spRaw2-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -928,7 +935,7 @@
|
||||||
3rd Spell Cost Raw:
|
3rd Spell Cost Raw:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="spRaw3" name="spRaw3" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="spRaw3" name="spRaw3" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "spRaw3-base">
|
<div class = "row" id = "spRaw3-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -939,7 +946,7 @@
|
||||||
4th Spell Cost Raw:
|
4th Spell Cost Raw:
|
||||||
</div>
|
</div>
|
||||||
<div class = "row">
|
<div class = "row">
|
||||||
<input type = "number" placeholder = "0" id="spRaw4" name="spRaw4" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm" oninput = "updateStats()"/>
|
<input type = "number" placeholder = "0" id="spRaw4" name="spRaw4" value="0" class="border-dark text-light dark-10 rounded scaled-font form-control form-control-sm"/>
|
||||||
</div>
|
</div>
|
||||||
<div class = "row" id = "spRaw4-base">
|
<div class = "row" id = "spRaw4-base">
|
||||||
Original Value: 0
|
Original Value: 0
|
||||||
|
@ -957,27 +964,27 @@
|
||||||
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
||||||
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="vanish-boost" onclick="updateBoosts('vanish-boost', true)">
|
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="vanish-boost" onclick="update_boosts('vanish-boost')">
|
||||||
Vanish (+80%)
|
Vanish (+80%)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="warscream-boost" onclick="updateBoosts('warscream-boost', true)">
|
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="warscream-boost" onclick="update_boosts('warscream-boost')">
|
||||||
War Scream (+10%)
|
War Scream (+10%)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="yourtotem-boost" onclick="updateBoosts('yourtotem-boost', true)">
|
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="yourtotem-boost" onclick="update_boosts('yourtotem-boost')">
|
||||||
Your Totem (+35%)
|
Your Totem (+35%)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="allytotem-boost" onclick="updateBoosts('allytotem-boost', true)">
|
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="allytotem-boost" onclick="update_boosts('allytotem-boost')">
|
||||||
Ally Totem (+15%)
|
Ally Totem (+15%)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="bash-boost" onclick="updateBoosts('bash-boost', true)">
|
<button class="button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id="bash-boost" onclick="update_boosts('bash-boost')">
|
||||||
Bash (+50%)
|
Bash (+50%)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1014,27 +1021,27 @@
|
||||||
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
||||||
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-1" onclick = "updatePowderSpecials('Quake-1', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-1" onclick = "updatePowderSpecials('Quake-1')">
|
||||||
Lv.4 [e4e4]
|
Lv.4 [e4e4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-2" onclick = "updatePowderSpecials('Quake-2', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-2" onclick = "updatePowderSpecials('Quake-2')">
|
||||||
Lv.4.5 [e5e4]
|
Lv.4.5 [e5e4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-3" onclick = "updatePowderSpecials('Quake-3', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-3" onclick = "updatePowderSpecials('Quake-3')">
|
||||||
Lv.5 [e5e5]
|
Lv.5 [e5e5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-4" onclick = "updatePowderSpecials('Quake-4', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-4" onclick = "updatePowderSpecials('Quake-4')">
|
||||||
Lv.5.5 [e6e5]
|
Lv.5.5 [e6e5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-5" onclick = "updatePowderSpecials('Quake-5', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Quake-5" onclick = "updatePowderSpecials('Quake-5')">
|
||||||
Lv.6 [e6e6]
|
Lv.6 [e6e6]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1044,7 +1051,7 @@
|
||||||
Rage (Passive)
|
Rage (Passive)
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input type = "range" class = "e_slider" id = "str_boost_armor" name = "str-boost-armor" autocomplete = "off" min = '0' max = '400' value = '0' step = '1' onchange = "updateArmorPowderSpecials('str_boost_armor', true)">
|
<input type = "range" class = "e_slider" id = "str_boost_armor" name = "str-boost-armor" autocomplete = "off" min = '0' max = '400' value = '0' step = '1' onchange = "update_armor_powder_specials('str_boost_armor')">
|
||||||
<input type="text" id="str_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
<input type="text" id="str_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||||
<label id = "str_boost_armor_label" for="str-boost-armor">% Earth Dmg Boost: 0</label>
|
<label id = "str_boost_armor_label" for="str-boost-armor">% Earth Dmg Boost: 0</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1058,27 +1065,27 @@
|
||||||
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
||||||
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-1" onclick = "updatePowderSpecials('Chain_Lightning-1', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-1" onclick = "updatePowderSpecials('Chain_Lightning-1')">
|
||||||
Lv.4 [t4t4]
|
Lv.4 [t4t4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-2" onclick = "updatePowderSpecials('Chain_Lightning-2', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-2" onclick = "updatePowderSpecials('Chain_Lightning-2')">
|
||||||
Lv.4.5 [t5t4]
|
Lv.4.5 [t5t4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-3" onclick = "updatePowderSpecials('Chain_Lightning-3', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-3" onclick = "updatePowderSpecials('Chain_Lightning-3')">
|
||||||
Lv.5 [t5t5]
|
Lv.5 [t5t5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-4" onclick = "updatePowderSpecials('Chain_Lightning-4', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-4" onclick = "updatePowderSpecials('Chain_Lightning-4')">
|
||||||
Lv.5.5 [t6t5]
|
Lv.5.5 [t6t5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-5" onclick = "updatePowderSpecials('Chain_Lightning-5', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Chain_Lightning-5" onclick = "updatePowderSpecials('Chain_Lightning-5')">
|
||||||
Lv.6 [t6t6]
|
Lv.6 [t6t6]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1088,7 +1095,7 @@
|
||||||
Kill Streak (Passive)
|
Kill Streak (Passive)
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input type = "range" class = "t_slider" id = "dex_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "updateArmorPowderSpecials('dex_boost_armor', true)">
|
<input type = "range" class = "t_slider" id = "dex_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "update_armor_powder_specials('dex_boost_armor')">
|
||||||
<input type="text" id="dex_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
<input type="text" id="dex_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||||
<label id = "dex_boost_armor_label" for="dex-boost-armor">% Thunder Dmg Boost: 0</label>
|
<label id = "dex_boost_armor_label" for="dex-boost-armor">% Thunder Dmg Boost: 0</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1102,27 +1109,27 @@
|
||||||
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
||||||
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-1" onclick = "updatePowderSpecials('Curse-1', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-1" onclick = "updatePowderSpecials('Curse-1')">
|
||||||
Lv.4 [w4w4]
|
Lv.4 [w4w4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-2" onclick = "updatePowderSpecials('Curse-2', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-2" onclick = "updatePowderSpecials('Curse-2')">
|
||||||
Lv.4.5 [w5w4]
|
Lv.4.5 [w5w4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-3" onclick = "updatePowderSpecials('Curse-3', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-3" onclick = "updatePowderSpecials('Curse-3')">
|
||||||
Lv.5 [w5w5]
|
Lv.5 [w5w5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-4" onclick = "updatePowderSpecials('Curse-4', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-4" onclick = "updatePowderSpecials('Curse-4')">
|
||||||
Lv.5.5 [w6w5]
|
Lv.5.5 [w6w5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-5" onclick = "updatePowderSpecials('Curse-5', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Curse-5" onclick = "updatePowderSpecials('Curse-5')">
|
||||||
Lv.6 [w6w6]
|
Lv.6 [w6w6]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1132,7 +1139,7 @@
|
||||||
Concentration (Passive)
|
Concentration (Passive)
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input type = "range" class = "w_slider" id = "int_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "updateArmorPowderSpecials('int_boost_armor', true)">
|
<input type = "range" class = "w_slider" id = "int_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "update_armor_powder_specials('int_boost_armor')">
|
||||||
<input type="text" id="int_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
<input type="text" id="int_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||||
<label id = "int_boost_armor_label" for="dex-boost-armor">% Water Dmg Boost: 0</label>
|
<label id = "int_boost_armor_label" for="dex-boost-armor">% Water Dmg Boost: 0</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1146,27 +1153,27 @@
|
||||||
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
||||||
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-1" onclick = "updatePowderSpecials('Courage-1', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-1" onclick = "updatePowderSpecials('Courage-1')">
|
||||||
Lv.4 [f4f4]
|
Lv.4 [f4f4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-2" onclick = "updatePowderSpecials('Courage-2', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-2" onclick = "updatePowderSpecials('Courage-2')">
|
||||||
Lv.4.5 [f5f4]
|
Lv.4.5 [f5f4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-3" onclick = "updatePowderSpecials('Courage-3', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-3" onclick = "updatePowderSpecials('Courage-3')">
|
||||||
Lv.5 [f5f5]
|
Lv.5 [f5f5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-4" onclick = "updatePowderSpecials('Courage-4', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-4" onclick = "updatePowderSpecials('Courage-4')">
|
||||||
Lv.5.5 [f6f5]
|
Lv.5.5 [f6f5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-5" onclick = "updatePowderSpecials('Courage-5', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Courage-5" onclick = "updatePowderSpecials('Courage-5')">
|
||||||
Lv.6 [f6f6]
|
Lv.6 [f6f6]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1176,7 +1183,7 @@
|
||||||
Endurance (Passive)
|
Endurance (Passive)
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input type = "range" class = "f_slider" id = "def_boost_armor" name = "def-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "updateArmorPowderSpecials('def_boost_armor', true)">
|
<input type = "range" class = "f_slider" id = "def_boost_armor" name = "def-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "update_armor_powder_specials('def_boost_armor')">
|
||||||
<input type="text" id="def_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
<input type="text" id="def_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||||
<label id = "def_boost_armor_label" for="def-boost-armor">% Fire Dmg Boost: 0</label>
|
<label id = "def_boost_armor_label" for="def-boost-armor">% Fire Dmg Boost: 0</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1190,27 +1197,27 @@
|
||||||
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
<div class="col skp-tooltip dark-6 rounded-bottom my-3 my-xl-1">
|
||||||
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
<div class="row row-cols-2 row-cols-xl-0 text-nowrap justify-content-center">
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-1" onclick = "updatePowderSpecials('Wind_Prison-1', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-1" onclick = "updatePowderSpecials('Wind_Prison-1')">
|
||||||
Lv.4 [a4a4]
|
Lv.4 [a4a4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-2" onclick = "updatePowderSpecials('Wind_Prison-2', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-2" onclick = "updatePowderSpecials('Wind_Prison-2')">
|
||||||
Lv.4.5 [a5a4]
|
Lv.4.5 [a5a4]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-3" onclick = "updatePowderSpecials('Wind_Prison-3', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-3" onclick = "updatePowderSpecials('Wind_Prison-3')">
|
||||||
Lv.5 [a5a5]
|
Lv.5 [a5a5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-4" onclick = "updatePowderSpecials('Wind_Prison-4', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-4" onclick = "updatePowderSpecials('Wind_Prison-4')">
|
||||||
Lv.5.5 [a6a5]
|
Lv.5.5 [a6a5]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto p-1">
|
<div class="col-auto p-1">
|
||||||
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-5" onclick = "updatePowderSpecials('Wind_Prison-5', true)">
|
<button class = "button-boost w-100 border-0 text-white dark-8u dark-shadow-sm" id = "Wind_Prison-5" onclick = "updatePowderSpecials('Wind_Prison-5')">
|
||||||
Lv.6 [a6a6]
|
Lv.6 [a6a6]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1220,7 +1227,7 @@
|
||||||
Dodge (Passive)
|
Dodge (Passive)
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<input type = "range" class = "a_slider" id = "agi_boost_armor" name = "agi-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "updateArmorPowderSpecials('agi_boost_armor', true)">
|
<input type = "range" class = "a_slider" id = "agi_boost_armor" name = "agi-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "update_armor_powder_specials('agi_boost_armor')">
|
||||||
<input type="text" id="agi_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
<input type="text" id="agi_boost_armor_prev" autocomplete = "off" value="0" style = "display:none;">
|
||||||
<label id = "agi_boost_armor_label" for="agi-boost-armor">% Air Dmg Boost: 0</label>
|
<label id = "agi_boost_armor_label" for="agi-boost-armor">% Air Dmg Boost: 0</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1255,28 +1262,30 @@
|
||||||
<div class="col-xl-3 mb-3 px-0">
|
<div class="col-xl-3 mb-3 px-0">
|
||||||
<div class="row row-cols-1 gy-3 mb-4 text-center scaled-font">
|
<div class="row row-cols-1 gy-3 mb-4 text-center scaled-font">
|
||||||
<div class = "col">
|
<div class = "col">
|
||||||
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id="build-melee-statsAvg">melee</div>
|
<div class = "spell-display spell-expand dark-5 rounded dark-shadow py-2 border border-dark" id="build-melee-statsAvg">melee</div>
|
||||||
<div class = "spell-display dark-5 rounded-bottom py-2 dark-shadow" id = "build-melee-stats" style="display: none;"></div>
|
<div class = "spell-display dark-5 rounded-bottom py-2 dark-shadow" id = "build-melee-stats" style="display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div class = "col">
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2 border border-dark" id="build-poison-stats">poison</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2 border border-dark" id="build-poison-stats">poison</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div id="all-spells-display" class="row row-cols-1 gy-3 text-center scaled-font pe-0">
|
||||||
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell0-infoAvg">spell1</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell0-infoAvg">spell1</div>
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell0-info" style="display: none;">Spell 1</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell0-info" style="display: none;">Spell 1</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell1-infoAvg">spell2</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell1-infoAvg">spell2</div>
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell1-info" style="display: none;">Spell 2</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell1-info" style="display: none;">Spell 2</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell2-infoAvg">spell3</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell2-infoAvg">spell3</div>
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell2-info" style="display: none;">Spell 3</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell2-info" style="display: none;">Spell 3</div>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col">
|
<div class = "col pe-0">
|
||||||
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell3-infoAvg">spell4</div>
|
<div class = "col spell-display spell-expand dark-5 rounded dark-shadow pt-2 border border-dark" id="spell3-infoAvg">spell4</div>
|
||||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell3-info" style="display: none;">Spell 4</div>
|
<div class = "col spell-display dark-5 rounded dark-shadow py-2" id = "spell3-info" style="display: none;">Spell 4</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class = "col">
|
<div class = "col">
|
||||||
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1304,7 +1313,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="weapon-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
<div id="weapon-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
||||||
</div>
|
</div>
|
||||||
<div id="weaponTome1-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
<!--div id="weaponTome1-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
||||||
</div>
|
</div>
|
||||||
<div id="weaponTome2-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
<div id="weaponTome2-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
||||||
</div>
|
</div>
|
||||||
|
@ -1317,7 +1326,7 @@
|
||||||
<div id="armorTome4-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
<div id="armorTome4-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
||||||
</div>
|
</div>
|
||||||
<div id="guildTome1-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
<div id="guildTome1-tooltip" class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3">
|
||||||
</div>
|
</div-->
|
||||||
<div class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3" id = "build-order">
|
<div class="rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark dark-shadow p-3" id = "build-order">
|
||||||
</div>
|
</div>
|
||||||
<div class = "rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark p-3" id = "set-info"></div>
|
<div class = "rounded row row-cols-1 g-0 scaled-font float-tooltip border border-3 border-dark p-3" id = "set-info"></div>
|
||||||
|
@ -1327,7 +1336,7 @@
|
||||||
<div class="col-12 dark-5 scaled-font">
|
<div class="col-12 dark-5 scaled-font">
|
||||||
<footer class="text-center">
|
<footer class="text-center">
|
||||||
<div id="header2">
|
<div id="header2">
|
||||||
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
<p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="credits">
|
<div id="credits">
|
||||||
|
@ -1396,19 +1405,18 @@
|
||||||
<script src="https://cdn.jsdelivr.net/npm/macy@2"></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/utils.js"></script>
|
||||||
<script type="text/javascript" src="../js/build_utils.js"></script>
|
<script type="text/javascript" src="../js/build_utils.js"></script>
|
||||||
|
<script type="text/javascript" src="../js/computation_graph.js"></script>
|
||||||
<!-- <script type="text/javascript" src="../js/icons.js"></script> -->
|
<!-- <script type="text/javascript" src="../js/icons.js"></script> -->
|
||||||
<script type="text/javascript" src="../js/sq2icons.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/powders.js"></script>
|
||||||
<script type="text/javascript" src="../js/skillpoints.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/damage_calc.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="../js/atree_constants.js"></script>
|
<script type="text/javascript" src="../js/atree_constants_min.js"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="../js/display_constants.js"></script>
|
<script type="text/javascript" src="../js/display_constants.js"></script>
|
||||||
<script type="text/javascript" src="../js/sq2display_constants.js"></script>
|
|
||||||
|
|
||||||
<script type="text/javascript" src="../js/display.js"></script>
|
<script type="text/javascript" src="../js/display.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.js"></script>
|
||||||
<script type="text/javascript" src="../js/query_2.js"></script>
|
<script type="text/javascript" src="../js/query_2.js"></script>
|
||||||
|
@ -1417,12 +1425,16 @@
|
||||||
<script type="text/javascript" src="../js/load_tome.js"></script>
|
<script type="text/javascript" src="../js/load_tome.js"></script>
|
||||||
<script type="text/javascript" src="../js/custom.js"></script>
|
<script type="text/javascript" src="../js/custom.js"></script>
|
||||||
<script type="text/javascript" src="../js/craft.js"></script>
|
<script type="text/javascript" src="../js/craft.js"></script>
|
||||||
<script type="text/javascript" src="../js/sq2build.js"></script>
|
<script type="text/javascript" src="../js/build.js"></script>
|
||||||
<script type="text/javascript" src="../js/sq2builder.js"></script>
|
<script type="text/javascript" src="../js/build_constants.js"></script>
|
||||||
|
<script type="text/javascript" src="../js/build_encode_decode.js"></script>
|
||||||
|
<script type="text/javascript" src="../js/atree.js"></script>
|
||||||
|
<script type="text/javascript" src="../js/builder.js"></script>
|
||||||
|
<script type="text/javascript" src="../js/builder_graph.js"></script>
|
||||||
<script type="text/javascript" src="../js/expr_parser.js"></script>
|
<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/items.js"></script>
|
||||||
<script type="text/javascript" src="../js/sq2items.js"></script>
|
<script type="text/javascript" src="../js/sq2items.js"></script>
|
||||||
<script type="text/javascript" src="../js/sq2bs.js"></script>
|
<script type="text/javascript" src="../js/optimize.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -211,7 +211,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class = "col-lg-3 col-sm-6">
|
<div class = "col-lg-3 col-sm-6">
|
||||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "craft-button" onclick = "copyRecipeHash()">
|
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "copy-hash-button" onclick = "copyRecipeHash()">
|
||||||
Copy Hash
|
Copy Hash
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -282,7 +282,7 @@
|
||||||
<div class="col dark-5 scaled-font">
|
<div class="col dark-5 scaled-font">
|
||||||
<footer class="text-center">
|
<footer class="text-center">
|
||||||
<div id="header2">
|
<div id="header2">
|
||||||
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
<p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="credits">
|
<div id="credits">
|
||||||
|
@ -301,8 +301,6 @@
|
||||||
<script type="text/javascript" src="../js/damage_calc.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_constants.js"></script>
|
||||||
<script type="text/javascript" src="../js/display.js"></script>
|
<script type="text/javascript" src="../js/display.js"></script>
|
||||||
<script type="text/javascript" src="../js/sq2display_constants.js"></script>
|
|
||||||
<script type="text/javascript" src="../js/sq2display.js"></script>
|
|
||||||
<script type="text/javascript" src="../js/load_ing.js"></script>
|
<script type="text/javascript" src="../js/load_ing.js"></script>
|
||||||
<script type="text/javascript" src="../js/load.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/craft.js"></script>
|
||||||
|
|
20
credits.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
Theme, formatting, and overall inspiration: Wynndata (Dukio)
|
||||||
|
- https://wynndata.tk
|
||||||
|
|
||||||
|
The game, of course
|
||||||
|
- wynncraft.com
|
||||||
|
|
||||||
|
Additional Contributors, in no particular order:
|
||||||
|
- Kiocifer (Icons!)
|
||||||
|
- IncinerateMe (helping transition to 1.20.3 / CI helper)
|
||||||
|
- puppy (wynn2 ability tree help)
|
||||||
|
- SockMower (ability tree encode/decode optimization)
|
||||||
|
- ITechnically (coding emotional support / misc)
|
||||||
|
- touhoku (best IM)
|
||||||
|
- HeyZeer0 (huge help in getting our damage formulas right)
|
||||||
|
- Lennon (Skill point formula reversing)
|
||||||
|
- Phanta (WynnAtlas custom expression parser / item search)
|
||||||
|
- nbcss (Crafted Item mechanics reverse engineering)
|
||||||
|
- dr_carlos (Hiding UI elements properly, fade animations, proper error handling)
|
||||||
|
- Atlas Inc discord (feedback, ideas, damage calc, etc)
|
|
@ -474,14 +474,12 @@ a:hover {
|
||||||
transform: rotate(270deg);
|
transform: rotate(270deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* atree hover */
|
.rotate-flip {
|
||||||
.atree-node {
|
-webkit-transform: scaleX(-1);
|
||||||
opacity: 75%;
|
transform: scaleX(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.atree-node:hover {
|
|
||||||
opacity: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hide-scroll {
|
.hide-scroll {
|
||||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||||
|
@ -490,3 +488,13 @@ a:hover {
|
||||||
.hide-scroll::-webkit-scrollbar {
|
.hide-scroll::-webkit-scrollbar {
|
||||||
display: none; /* Safari and Chrome */
|
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%;
|
||||||
|
}
|
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 |
|
@ -892,9 +892,70 @@
|
||||||
Last updated: 30 May 2022
|
Last updated: 30 May 2022
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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 class="row section" title="Test Section">
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="../js/dev.js"></script>
|
<script type="text/javascript" src="../js/dev.js"></script>
|
||||||
<script type="text/javascript" src="../js/sq2icons.js"></script>
|
<script type="text/javascript" src="../js/sq2icons.js"></script>
|
||||||
|
|
|
@ -62,8 +62,6 @@
|
||||||
<script type="text/javascript" src="/js/load_ing.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_constants.js"></script>
|
||||||
<script type="text/javascript" src="/js/display.js"></script>
|
<script type="text/javascript" src="/js/display.js"></script>
|
||||||
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
|
|
||||||
<script type="text/javascript" src="/js/sq2display.js"></script>
|
|
||||||
<script type="text/javascript" src="/js/item.js"></script>
|
<script type="text/javascript" src="/js/item.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -79,8 +79,6 @@
|
||||||
<script type="text/javascript" src="/js/damage_calc.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_constants.js"></script>
|
||||||
<script type="text/javascript" src="/js/display.js"></script>
|
<script type="text/javascript" src="/js/display.js"></script>
|
||||||
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
|
|
||||||
<script type="text/javascript" src="/js/sq2display.js"></script>
|
|
||||||
<script type="text/javascript" src="/js/query_2.js"></script>
|
<script type="text/javascript" src="/js/query_2.js"></script>
|
||||||
<script type="text/javascript" src="/js/expr_parser.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/load.js"></script>
|
||||||
|
|
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
|
|
@ -119,6 +119,10 @@ function runAtlas() {
|
||||||
let center = [(at1[0]+at2[0])/2, (at1[1]+at2[1])/2 ];
|
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) {
|
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
|
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 :)
|
//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";
|
atlas1.style.left = parseFloat(atlas1.style.left.replace("px","")) + (at1[0]-center[0]) * 2 * r / Math.sqrt(dx**2 + dy**2) + "px";
|
||||||
|
|
1029
js/atree.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).
|
* @param {Number[]} powders : Powder application. List of lists of integers (powder IDs).
|
||||||
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
||||||
* @param {Object[]} inputerrors : List of instances of error-like classes.
|
* @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=[]){
|
constructor(level, items, weapon){
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
if (level < 1) { //Should these be constants?
|
if (level < 1) { //Should these be constants?
|
||||||
this.level = 1;
|
this.level = 1;
|
||||||
|
@ -348,11 +122,13 @@ class Build{
|
||||||
document.getElementById("level-choice").value = this.level;
|
document.getElementById("level-choice").value = this.level;
|
||||||
|
|
||||||
this.availableSkillpoints = levelToSkillPoints(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]);
|
this.items = this.equipment.concat([this.weapon]);
|
||||||
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
// 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];
|
this.equip_order = result[0];
|
||||||
// How many skillpoints the player had to assign (5 number)
|
// How many skillpoints the player had to assign (5 number)
|
||||||
this.base_skillpoints = result[1];
|
this.base_skillpoints = result[1];
|
||||||
|
@ -362,21 +138,7 @@ class Build{
|
||||||
this.assigned_skillpoints = result[3];
|
this.assigned_skillpoints = result[3];
|
||||||
this.activeSetCounts = result[4];
|
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();
|
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
|
/*Returns build in string format
|
||||||
|
@ -385,138 +147,62 @@ class Build{
|
||||||
return [this.equipment,this.weapon].flat();
|
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.
|
/* 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.
|
@pre The build itself should be valid. No checking of validity of pieces is done here.
|
||||||
*/
|
*/
|
||||||
initBuildStats(){
|
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
|
//Create a map of this build's stats
|
||||||
let statMap = new Map();
|
let statMap = new Map();
|
||||||
|
statMap.set("defMultiplier", 1);
|
||||||
|
|
||||||
for (const staticID of staticIDs) {
|
for (const staticID of staticIDs) {
|
||||||
statMap.set(staticID, 0);
|
statMap.set(staticID, 0);
|
||||||
}
|
}
|
||||||
|
for (const staticID of must_ids) {
|
||||||
|
statMap.set(staticID, 0);
|
||||||
|
}
|
||||||
statMap.set("hp", levelToHPBase(this.level));
|
statMap.set("hp", levelToHPBase(this.level));
|
||||||
|
|
||||||
let major_ids = new Set();
|
let major_ids = new Set();
|
||||||
for (const item of this.items){
|
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)) {
|
if (staticIDs.includes(id)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
statMap.set(id,(statMap.get(id) || 0)+value);
|
statMap.set(id,(statMap.get(id) || 0)+value);
|
||||||
}
|
}
|
||||||
for (const staticID of staticIDs) {
|
for (const staticID of staticIDs) {
|
||||||
if (item.get(staticID)) {
|
if (item_stats.get(staticID)) {
|
||||||
statMap.set(staticID, statMap.get(staticID) + item.get(staticID));
|
statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (item.get("majorIds")) {
|
if (item_stats.get("majorIds")) {
|
||||||
for (const major_id of item.get("majorIds")) {
|
for (const major_id of item_stats.get("majorIds")) {
|
||||||
major_ids.add(major_id);
|
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);
|
statMap.set("activeMajorIDs", major_ids);
|
||||||
for (const [setName, count] of this.activeSetCounts) {
|
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) {
|
for (const id in bonus) {
|
||||||
if (skp_order.includes(id)) {
|
if (skp_order.includes(id)) {
|
||||||
// pass. Don't include skillpoints in ids
|
// pass. Don't include skillpoints in ids
|
||||||
|
@ -529,27 +215,8 @@ class Build{
|
||||||
statMap.set("poisonPct", 100);
|
statMap.set("poisonPct", 100);
|
||||||
|
|
||||||
// The stuff relevant for damage calculation!!! @ferricles
|
// 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.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_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;
|
||||||
|
}
|
|
@ -51,28 +51,247 @@ const armorTypes = [ "helmet", "chestplate", "leggings", "boots" ];
|
||||||
const accessoryTypes = [ "ring", "bracelet", "necklace" ];
|
const accessoryTypes = [ "ring", "bracelet", "necklace" ];
|
||||||
const weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ];
|
const weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ];
|
||||||
const consumableTypes = [ "potion", "scroll", "food"];
|
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 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 ];
|
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
|
//0.51, 0.82, 1.50, 2.05, 2.50, 3.11, 4.27
|
||||||
const classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"];
|
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 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");
|
//weaponTypes.push("sword");
|
||||||
//console.log(types)
|
//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 elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
|
||||||
let skpReqs = skp_order.map(x => x + "Req");
|
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" ]
|
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
|
//File reading for ID translations for JSON purposes
|
||||||
let reversetranslations = new Map();
|
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"]]);
|
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 dmgMobs (wep tomes) and defMobs (armor tomes)
|
//does not include damMobs (wep tomes) and defMobs (armor tomes)
|
||||||
for (const [k, v] of translations) {
|
for (const [k, v] of translations) {
|
||||||
reversetranslations.set(v, k);
|
reversetranslations.set(v, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
1329
js/builder.js
1160
js/builder_graph.js
Normal file
|
@ -1,122 +1,263 @@
|
||||||
let _ALL_NODES = new Map();
|
let all_nodes = [];
|
||||||
|
|
||||||
class ComputeNode {
|
class ComputeNode {
|
||||||
/***
|
/**
|
||||||
* Make a generic compute node.
|
* Make a generic compute node.
|
||||||
* Adds the node to the global map of nodenames to nodes (for calling from html listeners).
|
* 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).
|
* @param name : Name of the node (string). Must be unique. Must "fit in" a JS string (terminated by single quotes).
|
||||||
*/
|
*/
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
if (_ALL_NODES.has(name)) {
|
this.inputs = []; // parent nodes
|
||||||
throw 'Duplicate node name: ' + name;
|
this.input_translation = new Map();
|
||||||
}
|
|
||||||
_ALL_NODES.set(name, this)
|
|
||||||
this.inputs = [];
|
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.value = 0;
|
this.value = null;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.update_task = null;
|
this.update_task = null;
|
||||||
this.update_time = Date.now();
|
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.
|
* Request update of this compute node. Pushes updates to children.
|
||||||
*/
|
*/
|
||||||
update(timestamp) {
|
update() {
|
||||||
if (timestamp <= this.update_time) {
|
if (this.inputs_dirty_count != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.update_time = timestamp;
|
if (this.dirty === 0) {
|
||||||
|
return;
|
||||||
let value_map = Map();
|
}
|
||||||
|
if (this.dirty == 2) {
|
||||||
|
let calc_inputs = new Map();
|
||||||
for (const input of this.inputs) {
|
for (const input of this.inputs) {
|
||||||
value_map.set(input.name, input.get_value());
|
calc_inputs.set(this.input_translation.get(input.name), input.value);
|
||||||
}
|
}
|
||||||
this.value = this.compute_func();
|
this.value = this.compute_func(calc_inputs);
|
||||||
|
}
|
||||||
|
this.dirty = 0;
|
||||||
for (const child of this.children) {
|
for (const child of this.children) {
|
||||||
child.update();
|
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 of this compute node. Can't trigger update cascades (push based update, not pull based.)
|
||||||
*/
|
*/
|
||||||
get_value() {
|
get_value() {
|
||||||
return this.value
|
return this.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/**
|
||||||
* Abstract method for computing something. Return value is set into this.value
|
* Abstract method for computing something. Return value is set into this.value
|
||||||
*/
|
*/
|
||||||
compute_func() {
|
compute_func(input_map) {
|
||||||
throw "no compute func specified";
|
throw "no compute func specified";
|
||||||
}
|
}
|
||||||
|
|
||||||
link_to(parent_node) {
|
/**
|
||||||
|
* Add link to a parent compute node, optionally with an alias.
|
||||||
|
*/
|
||||||
|
link_to(parent_node, link_name) {
|
||||||
this.inputs.push(parent_node)
|
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);
|
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.
|
* Schedule a ComputeNode to be updated.
|
||||||
*
|
*
|
||||||
* @param node_name : ComputeNode name to schedule an update for.
|
* @param node : ComputeNode to schedule an update for.
|
||||||
*/
|
*/
|
||||||
function calcSchedule(node_name) {
|
function calcSchedule(node, timeout) {
|
||||||
node = _ALL_NODES.get(node_name);
|
|
||||||
if (node.update_task !== null) {
|
if (node.update_task !== null) {
|
||||||
clearTimeout(node.update_task);
|
clearTimeout(node.update_task);
|
||||||
}
|
}
|
||||||
|
node.mark_dirty();
|
||||||
node.update_task = setTimeout(function() {
|
node.update_task = setTimeout(function() {
|
||||||
const timestamp = Date.now();
|
node.update();
|
||||||
node.update(timestamp);
|
|
||||||
node.update_task = null;
|
node.update_task = null;
|
||||||
}, 500);
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
class PrintNode extends ComputeNode {
|
||||||
* Node for getting an item's stats from an item input field.
|
|
||||||
*/
|
constructor(name) {
|
||||||
class ItemStats extends ComputeNode {
|
|
||||||
/***
|
|
||||||
* Make an item stat pulling compute node.
|
|
||||||
*
|
|
||||||
* @param name: Name of this node.
|
|
||||||
* @param item_input_field: Input field (html element) to listen for item names from.
|
|
||||||
* @param none_item: Item object to use as the "none" for this field.
|
|
||||||
*/
|
|
||||||
constructor(name, item_input_field, none_item) {
|
|
||||||
super(name);
|
super(name);
|
||||||
this.input_field.setAttribute("onInput", "calcSchedule('"+name+"');");
|
this.fail_cb = true;
|
||||||
this.input_field = item_input_field;
|
|
||||||
this.none_item = none_item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compute_func() {
|
compute_func(input_map) {
|
||||||
// built on the assumption of no one will type in CI/CR letter by letter
|
console.log([this.name, input_map]);
|
||||||
|
return null;
|
||||||
let item_text = this.input_field.value;
|
|
||||||
let item;
|
|
||||||
|
|
||||||
if (item_text.slice(0, 3) == "CI-") {
|
|
||||||
item = getCustomFromHash(item_text);
|
|
||||||
}
|
|
||||||
else if (item_text.slice(0, 3) == "CR-") {
|
|
||||||
item = getCraftFromHash(item_text);
|
|
||||||
}
|
|
||||||
else if (itemMap.has(item_text)) {
|
|
||||||
item = itemMap.get(item_text);
|
|
||||||
}
|
|
||||||
else if (tomeMap.has(item_text)) {
|
|
||||||
item = tomeMap.get(item_text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return this.none_item;
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 + "Dam", "0-0");
|
||||||
statMap.set(e + "DamLow", "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("category","weapon");
|
||||||
statMap.set("atkSpd",this.atkSpd);
|
statMap.set("atkSpd",this.atkSpd);
|
||||||
}
|
}
|
||||||
|
@ -190,7 +189,6 @@ class Craft{
|
||||||
let amounts = this.recipe.get("materials").map(x=> x.get("amount"));
|
let amounts = this.recipe.get("materials").map(x=> x.get("amount"));
|
||||||
//Mat Multipliers - should work!
|
//Mat Multipliers - should work!
|
||||||
matmult = (tierToMult[tiers[0]]*amounts[0] + tierToMult[tiers[1]]*amounts[1]) / (amounts[0]+amounts[1]);
|
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 low = this.recipe.get("healthOrDamage")[0];
|
||||||
let high = this.recipe.get("healthOrDamage")[1];
|
let high = this.recipe.get("healthOrDamage")[1];
|
||||||
|
@ -382,12 +380,10 @@ class Craft{
|
||||||
|
|
||||||
statMap.set("reqs",[0,0,0,0,0]);
|
statMap.set("reqs",[0,0,0,0,0]);
|
||||||
statMap.set("skillpoints", [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) {
|
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.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("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("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) {
|
for (const id of rolledIDs) {
|
||||||
if (statMap.get("minRolls").has(id)) {
|
if (statMap.get("minRolls").has(id)) {
|
||||||
|
|
|
@ -172,16 +172,17 @@ function calculateCraft() {
|
||||||
document.getElementById("mat-2").textContent = recipe.get("materials")[1].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
|
//Display Recipe Stats
|
||||||
displaysq2RecipeStats(player_craft, "recipe-stats");
|
displayRecipeStats(player_craft, "recipe-stats");
|
||||||
|
|
||||||
//Display Craft Stats
|
//Display Craft Stats
|
||||||
// displayCraftStats(player_craft, "craft-stats");
|
// displayCraftStats(player_craft, "craft-stats");
|
||||||
let mock_item = player_craft.statMap;
|
let mock_item = player_craft.statMap;
|
||||||
displaysq2ExpandedItem(mock_item, "craft-stats");
|
apply_weapon_powders(mock_item);
|
||||||
|
displayExpandedItem(mock_item, "craft-stats");
|
||||||
|
|
||||||
//Display Ingredients' Stats
|
//Display Ingredients' Stats
|
||||||
for (let i = 1; i < 7; i++) {
|
for (let i = 1; i < 7; i++) {
|
||||||
displaysq2ExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats");
|
displayExpandedIngredient(player_craft.ingreds[i-1] , "ing-"+i+"-stats");
|
||||||
}
|
}
|
||||||
//Display Warnings - only ingred type warnings for now
|
//Display Warnings - only ingred type warnings for now
|
||||||
let warning_elem = document.getElementById("craft-warnings");
|
let warning_elem = document.getElementById("craft-warnings");
|
||||||
|
@ -264,7 +265,7 @@ function populateFields() {
|
||||||
*/
|
*/
|
||||||
function copyRecipeHash() {
|
function copyRecipeHash() {
|
||||||
if (player_craft) {
|
if (player_craft) {
|
||||||
copyTextToClipboard("CR-"+location.hash);
|
copyTextToClipboard("CR-"+location.hash.slice(1));
|
||||||
document.getElementById("copy-hash-button").textContent = "Copied!";
|
document.getElementById("copy-hash-button").textContent = "Copied!";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,7 +342,7 @@ function toggleMaterial(buttonId) {
|
||||||
*/
|
*/
|
||||||
function updateCraftedImage() {
|
function updateCraftedImage() {
|
||||||
let input = document.getElementById("recipe-choice");
|
let input = document.getElementById("recipe-choice");
|
||||||
if (item_types.includes(input.value)) {
|
if (all_types.includes(input.value)) {
|
||||||
document.getElementById("recipe-img").src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + input.value.toLowerCase() + ".png";
|
document.getElementById("recipe-img").src = "../media/items/" + (newIcons ? "new/":"old/") + "generic-" + input.value.toLowerCase() + ".png";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,4 +365,8 @@ function resetFields() {
|
||||||
calculateCraft();
|
calculateCraft();
|
||||||
}
|
}
|
||||||
|
|
||||||
load_ing_init(init_crafter);
|
(async function() {
|
||||||
|
let load_promises = [ load_ing_init() ];
|
||||||
|
await Promise.all(load_promises);
|
||||||
|
init_crafter();
|
||||||
|
})();
|
||||||
|
|
|
@ -175,6 +175,7 @@ function getCustomFromHash(hash) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statMap.set("hash", "CI-" + name);
|
statMap.set("hash", "CI-" + name);
|
||||||
|
statMap.set("custom", true);
|
||||||
return new Custom(statMap);
|
return new Custom(statMap);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -370,7 +370,7 @@ function useBaseItem(elem) {
|
||||||
//Check items db.
|
//Check items db.
|
||||||
for (const [name,itemObj] of itemMap) {
|
for (const [name,itemObj] of itemMap) {
|
||||||
if (itemName === name) {
|
if (itemName === name) {
|
||||||
baseItem = expandItem(itemObj, []);
|
baseItem = expandItem(itemObj);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,190 +1,297 @@
|
||||||
const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]);
|
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
|
function get_base_dps(item) {
|
||||||
for (const entry of externalStats) {
|
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
||||||
const key = entry[0];
|
//SUPER JANK @HPP PLS FIX
|
||||||
const value = entry[1];
|
if (item.get("tier") !== "Crafted") {
|
||||||
if (typeof value === "number") {
|
let total_damage = 0;
|
||||||
buildStats.set(key, buildStats.get(key) + value);
|
for (const damage_k of damage_keys) {
|
||||||
} else if (Array.isArray(value)) {
|
damages = item.get(damage_k);
|
||||||
arr = [];
|
total_damage += damages[0] + damages[1];
|
||||||
for (let j = 0; j < value.length; j++) {
|
|
||||||
arr[j] = buildStats.get(key)[j] + value[j];
|
|
||||||
}
|
}
|
||||||
buildStats.set(key, arr);
|
return total_damage * attack_speed_mult / 2;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
tooltipinfo.set("dmgMult", `(${tooltipinfo.get("dmgMult")} * ${spellMultiplier} * ${baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))]})`)
|
let total_damage_min = 0;
|
||||||
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))];
|
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];
|
||||||
}
|
}
|
||||||
//console.log(damages);
|
total_damage_min = attack_speed_mult * total_damage_min / 2;
|
||||||
//console.log(damageMult);
|
total_damage_max = attack_speed_mult * total_damage_max / 2;
|
||||||
tooltipinfo.set("rawModifier", `(${rawModifier} * ${spellMultiplier} * ${damageMultiplier})`);
|
return [total_damage_min, total_damage_max];
|
||||||
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 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;
|
|
||||||
|
|
||||||
|
|
||||||
|
function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) {
|
||||||
|
// TODO: Roll all the loops together maybe
|
||||||
|
|
||||||
|
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||||
|
// 1. Get weapon damage (with powders).
|
||||||
|
let weapon_damages;
|
||||||
|
if (weapon.get('tier') === 'Crafted') {
|
||||||
|
weapon_damages = damage_keys.map(x => weapon.get(x)[1]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
weapon_damages = damage_keys.map(x => weapon.get(x));
|
||||||
|
}
|
||||||
|
let present = weapon.get(damage_present_key);
|
||||||
|
|
||||||
|
// 2. Conversions.
|
||||||
|
// 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here.
|
||||||
|
let damages = [];
|
||||||
|
const neutral_convert = conversions[0] / 100;
|
||||||
|
let weapon_min = 0;
|
||||||
|
let weapon_max = 0;
|
||||||
|
for (const damage of weapon_damages) {
|
||||||
|
let min_dmg = damage[0] * neutral_convert;
|
||||||
|
let max_dmg = damage[1] * neutral_convert;
|
||||||
|
damages.push([min_dmg, max_dmg]);
|
||||||
|
weapon_min += damage[0];
|
||||||
|
weapon_max += damage[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.2. Next, apply elemental conversions using damage computed in step 1.1.
|
||||||
|
// Also, track which elements are present. (Add onto those present in the weapon itself.)
|
||||||
|
let total_convert = 0; //TODO get confirmation that this is how raw works.
|
||||||
|
for (let i = 1; i <= 5; ++i) {
|
||||||
|
if (conversions[i] > 0) {
|
||||||
|
const conv_frac = conversions[i]/100;
|
||||||
|
damages[i][0] += conv_frac * weapon_min;
|
||||||
|
damages[i][1] += conv_frac * weapon_max;
|
||||||
|
present[i] = true;
|
||||||
|
total_convert += conv_frac
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also theres prop and rainbow!!
|
||||||
|
const damage_elements = ['n'].concat(skp_elements); // netwfa
|
||||||
|
|
||||||
|
if (!ignore_speed) {
|
||||||
|
// 3. Apply attack speed multiplier. Ignored for melee single hit
|
||||||
|
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(weapon.get("atkSpd"))];
|
||||||
|
for (let i = 0; i < 6; ++i) {
|
||||||
|
damages[i][0] *= attack_speed_mult;
|
||||||
|
damages[i][1] *= attack_speed_mult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Add additive damage. TODO: Is there separate additive damage?
|
||||||
|
for (let i = 0; i < 6; ++i) {
|
||||||
|
if (present[i]) {
|
||||||
|
damages[i][0] += stats.get(damage_elements[i]+'DamAddMin');
|
||||||
|
damages[i][1] += stats.get(damage_elements[i]+'DamAddMax');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. ID bonus.
|
||||||
|
let specific_boost_str = 'Md';
|
||||||
|
if (use_spell_damage) {
|
||||||
|
specific_boost_str = 'Sd';
|
||||||
|
}
|
||||||
|
// 5.1: %boost application
|
||||||
|
let skill_boost = [0]; // no neutral skillpoint booster
|
||||||
|
for (const skp of skp_order) {
|
||||||
|
skill_boost.push(skillPointsToPercentage(stats.get(skp)));
|
||||||
|
}
|
||||||
|
let static_boost = (stats.get(specific_boost_str.toLowerCase()+'Pct') + stats.get('damPct')) / 100;
|
||||||
|
|
||||||
|
// These do not count raw damage. I think. Easy enough to change
|
||||||
|
let total_min = 0;
|
||||||
|
let total_max = 0;
|
||||||
for (let i in damages) {
|
for (let i in damages) {
|
||||||
let damageBoost = 1 + skillBoost[i] + staticBoost;
|
let damage_prefix = damage_elements[i] + specific_boost_str;
|
||||||
tooltipinfo.set("damageBoost", `(1 + ${(tooltipinfo.get("skillBoost")[i] ? tooltipinfo.get("skillBoost")[i] + " + " : "")} ${tooltipinfo.get("staticBoost")})`)
|
let damageBoost = 1 + skill_boost[i] + static_boost
|
||||||
damages_results.push([
|
+ ((stats.get(damage_prefix+'Pct') + stats.get(damage_elements[i]+'DamPct')) /100);
|
||||||
Math.max(damages[i][0] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal min
|
damages[i][0] *= Math.max(damageBoost, 0);
|
||||||
Math.max(damages[i][1] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal max
|
damages[i][1] *= Math.max(damageBoost, 0);
|
||||||
Math.max(damages[i][0] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit min
|
// Collect total damage post %boost
|
||||||
Math.max(damages[i][1] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit max
|
total_min += damages[i][0];
|
||||||
]);
|
total_max += damages[i][1];
|
||||||
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;
|
let total_elem_min = total_min - damages[0][0];
|
||||||
if (totalDamNorm[1] < 0) totalDamNorm[1] = 0;
|
let total_elem_max = total_max - damages[0][1];
|
||||||
if (totalDamCrit[0] < 0) totalDamCrit[0] = 0;
|
|
||||||
if (totalDamCrit[1] < 0) totalDamCrit[1] = 0;
|
|
||||||
|
|
||||||
tooltipinfo.set("damageformulas", damageformulas);
|
// 5.2: Raw application.
|
||||||
return [totalDamNorm, totalDamCrit, damages_results, tooltipinfo];
|
let prop_raw = stats.get(specific_boost_str.toLowerCase()+'Raw') + stats.get('damRaw');
|
||||||
|
let rainbow_raw = stats.get('r'+specific_boost_str+'Raw') + stats.get('rDamRaw');
|
||||||
|
for (let i in damages) {
|
||||||
|
let damages_obj = damages[i];
|
||||||
|
let damage_prefix = damage_elements[i] + specific_boost_str;
|
||||||
|
// Normie raw
|
||||||
|
let raw_boost = 0;
|
||||||
|
if (present[i]) {
|
||||||
|
raw_boost += stats.get(damage_prefix+'Raw') + stats.get(damage_elements[i]+'DamRaw');
|
||||||
|
}
|
||||||
|
// Next, rainraw and propRaw
|
||||||
|
let min_boost = raw_boost;
|
||||||
|
let max_boost = raw_boost;
|
||||||
|
if (total_max > 0) { // TODO: what about total negative all raw?
|
||||||
|
if (total_elem_min > 0) {
|
||||||
|
min_boost += (damages_obj[0] / total_min) * prop_raw;
|
||||||
|
}
|
||||||
|
max_boost += (damages_obj[1] / total_max) * prop_raw;
|
||||||
|
}
|
||||||
|
if (i != 0 && total_elem_max > 0) { // rainraw TODO above
|
||||||
|
if (total_elem_min > 0) {
|
||||||
|
min_boost += (damages_obj[0] / total_elem_min) * rainbow_raw;
|
||||||
|
}
|
||||||
|
max_boost += (damages_obj[1] / total_elem_max) * rainbow_raw;
|
||||||
|
}
|
||||||
|
damages_obj[0] += min_boost * total_convert;
|
||||||
|
damages_obj[1] += max_boost * total_convert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Strength boosters
|
||||||
|
// str/dex, as well as any other mutually multiplicative effects
|
||||||
|
let strBoost = 1 + skill_boost[1];
|
||||||
|
let total_dam_norm = [0, 0];
|
||||||
|
let total_dam_crit = [0, 0];
|
||||||
|
let damages_results = [];
|
||||||
|
const damage_mult = stats.get("damageMultiplier");
|
||||||
|
|
||||||
|
for (const damage of damages) {
|
||||||
|
const res = [
|
||||||
|
damage[0] * strBoost * damage_mult, // Normal min
|
||||||
|
damage[1] * strBoost * damage_mult, // Normal max
|
||||||
|
damage[0] * (strBoost + 1) * damage_mult, // Crit min
|
||||||
|
damage[1] * (strBoost + 1) * damage_mult, // Crit max
|
||||||
|
];
|
||||||
|
damages_results.push(res);
|
||||||
|
total_dam_norm[0] += res[0];
|
||||||
|
total_dam_norm[1] += res[1];
|
||||||
|
total_dam_crit[0] += res[2];
|
||||||
|
total_dam_crit[1] += res[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_dam_norm[0] < 0) total_dam_norm[0] = 0;
|
||||||
|
if (total_dam_norm[1] < 0) total_dam_norm[1] = 0;
|
||||||
|
if (total_dam_crit[0] < 0) total_dam_crit[0] = 0;
|
||||||
|
if (total_dam_crit[1] < 0) total_dam_crit[1] = 0;
|
||||||
|
|
||||||
|
return [total_dam_norm, total_dam_crit, damages_results];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Spell schema:
|
||||||
|
|
||||||
|
spell: {
|
||||||
|
name: str internal string name for the spell. Unique identifier, 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 = {
|
const spell_table = {
|
||||||
"wand": [
|
"wand": [
|
||||||
|
|
1607
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(
|
let colorMap = new Map(
|
||||||
[
|
[
|
||||||
["Normal", "#fff"],
|
["Normal", "#fff"],
|
||||||
|
@ -305,23 +209,24 @@ let posModSuffixes = {
|
||||||
/*
|
/*
|
||||||
* Display commands
|
* Display commands
|
||||||
*/
|
*/
|
||||||
let build_overall_display_commands = [
|
let build_all_display_commands = [
|
||||||
"#table",
|
"#defense-stats",
|
||||||
"str", "dex", "int", "def", "agi",
|
"str", "dex", "int", "def", "agi",
|
||||||
|
"!spacer",
|
||||||
"mr", "ms",
|
"mr", "ms",
|
||||||
"hprRaw", "hprPct",
|
"hprRaw", "hprPct",
|
||||||
|
"ls",
|
||||||
"sdRaw", "sdPct",
|
"sdRaw", "sdPct",
|
||||||
"mdRaw", "mdPct",
|
"mdRaw", "mdPct",
|
||||||
"ref", "thorns",
|
|
||||||
"ls",
|
|
||||||
"poison",
|
|
||||||
"expd",
|
|
||||||
"spd",
|
|
||||||
"atkTier",
|
|
||||||
"!elemental",
|
"!elemental",
|
||||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
||||||
"!elemental",
|
"!elemental",
|
||||||
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
|
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
|
||||||
|
"atkTier",
|
||||||
|
"poison",
|
||||||
|
"ref", "thorns",
|
||||||
|
"expd",
|
||||||
|
"spd",
|
||||||
"rainbowRaw",
|
"rainbowRaw",
|
||||||
"sprint", "sprintReg",
|
"sprint", "sprintReg",
|
||||||
"jh",
|
"jh",
|
||||||
|
@ -331,26 +236,51 @@ let build_overall_display_commands = [
|
||||||
"gXp", "gSpd",
|
"gXp", "gSpd",
|
||||||
];
|
];
|
||||||
|
|
||||||
let item_display_commands = [
|
let build_offensive_display_commands = [
|
||||||
"#cdiv",
|
"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",
|
"displayName",
|
||||||
//"type", //REPLACE THIS WITH SKIN
|
|
||||||
"#ldiv",
|
|
||||||
"atkSpd",
|
"atkSpd",
|
||||||
"#ldiv",
|
|
||||||
"!elemental",
|
"!elemental",
|
||||||
"hp",
|
"hp",
|
||||||
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
||||||
|
"!spacer",
|
||||||
"fDef", "wDef", "aDef", "tDef", "eDef",
|
"fDef", "wDef", "aDef", "tDef", "eDef",
|
||||||
"!elemental",
|
"!elemental",
|
||||||
"#ldiv",
|
|
||||||
"classReq",
|
"classReq",
|
||||||
"lvl",
|
"lvl",
|
||||||
"strReq", "dexReq", "intReq", "defReq","agiReq",
|
"strReq", "dexReq", "intReq", "defReq","agiReq",
|
||||||
"#ldiv",
|
"!spacer",
|
||||||
"str", "dex", "int", "def", "agi",
|
"str", "dex", "int", "def", "agi",
|
||||||
"#table",
|
|
||||||
"str", "dex", "int", "def", "agi", //jank lmao
|
|
||||||
"hpBonus",
|
"hpBonus",
|
||||||
"hprRaw", "hprPct",
|
"hprRaw", "hprPct",
|
||||||
"sdRaw", "sdPct",
|
"sdRaw", "sdPct",
|
||||||
|
@ -374,11 +304,33 @@ let item_display_commands = [
|
||||||
"spRegen",
|
"spRegen",
|
||||||
"eSteal",
|
"eSteal",
|
||||||
"gXp", "gSpd",
|
"gXp", "gSpd",
|
||||||
"#ldiv",
|
|
||||||
"majorIds",
|
"majorIds",
|
||||||
|
"!spacer",
|
||||||
"slots",
|
"slots",
|
||||||
|
"!spacer",
|
||||||
"set",
|
"set",
|
||||||
"lore",
|
"lore",
|
||||||
"quest",
|
"quest",
|
||||||
"restrict"
|
"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 tier_mod = tiers_mod.get(tier);
|
||||||
let y_max = baseline_y.map(x => 2.1*x*tier_mod*type_mod);
|
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);
|
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("fill", "none")
|
||||||
.attr("stroke", d => colorMap.get(tier))
|
.attr("stroke", d => colorMap.get(tier))
|
||||||
.attr("d", d3.line()
|
.attr("d", d3.line()
|
||||||
.x(function(d) { return x(d[0]) })
|
.x(function(d) { return x(d[0]) })
|
||||||
.y(function(d) { return y(d[1]) })
|
.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("fill", "none")
|
||||||
.attr("stroke", d => colorMap.get(tier))
|
.attr("stroke", d => colorMap.get(tier))
|
||||||
.attr("d", d3.line()
|
.attr("d", d3.line()
|
||||||
|
|
|
@ -239,7 +239,7 @@ function init_items2() {
|
||||||
const itemListFooter = document.getElementById('item-list-footer');
|
const itemListFooter = document.getElementById('item-list-footer');
|
||||||
|
|
||||||
// compile the search db from the item db
|
// 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
|
// init item list elements
|
||||||
const ITEM_LIST_SIZE = 64;
|
const ITEM_LIST_SIZE = 64;
|
||||||
|
|
128
js/load.js
|
@ -6,7 +6,7 @@ let reload = false;
|
||||||
let load_complete = false;
|
let load_complete = false;
|
||||||
let load_in_progress = false;
|
let load_in_progress = false;
|
||||||
let items;
|
let items;
|
||||||
let sets;
|
let sets = new Map();
|
||||||
let itemMap;
|
let itemMap;
|
||||||
let idMap;
|
let idMap;
|
||||||
let redirectMap;
|
let redirectMap;
|
||||||
|
@ -14,42 +14,46 @@ let itemLists = new Map();
|
||||||
/*
|
/*
|
||||||
* Load item set from local DB. Calls init() on success.
|
* Load item set from local DB. Calls init() on success.
|
||||||
*/
|
*/
|
||||||
async function load_local(init_func) {
|
async function load_local() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
let get_tx = db.transaction(['item_db', 'set_db'], 'readonly');
|
let get_tx = db.transaction(['item_db', 'set_db'], 'readonly');
|
||||||
let sets_store = get_tx.objectStore('set_db');
|
let sets_store = get_tx.objectStore('set_db');
|
||||||
let get_store = get_tx.objectStore('item_db');
|
let get_store = get_tx.objectStore('item_db');
|
||||||
let request = get_store.getAll();
|
let request = get_store.getAll();
|
||||||
request.onerror = function(event) {
|
request.onerror = function(event) {
|
||||||
console.log("Could not read local item db...");
|
reject("Could not read local item db...");
|
||||||
}
|
}
|
||||||
request.onsuccess = function(event) {
|
request.onsuccess = function(event) {
|
||||||
console.log("Successfully read local item db.");
|
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...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
request2.onsuccess = function(event) {
|
||||||
let cursor = event.target.result;
|
let cursor = event.target.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
sets[cursor.primaryKey] = cursor.value;
|
let key = cursor.primaryKey;
|
||||||
|
let value = cursor.value;
|
||||||
|
sets.set(key, value);
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// no more results
|
||||||
console.log("Successfully read local set db.");
|
console.log("Successfully read local set db.");
|
||||||
//console.log(sets);
|
}
|
||||||
|
};
|
||||||
|
get_tx.oncomplete = function(event) {
|
||||||
|
items = request.result;
|
||||||
init_maps();
|
init_maps();
|
||||||
init_func();
|
|
||||||
load_complete = true;
|
load_complete = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await get_tx.complete;
|
|
||||||
db.close();
|
db.close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -91,7 +95,7 @@ function clean_item(item) {
|
||||||
/*
|
/*
|
||||||
* Load item set from remote DB (aka a big json file). Calls init() on success.
|
* 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 getUrl = window.location;
|
||||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
||||||
|
@ -99,17 +103,7 @@ async function load(init_func) {
|
||||||
let url = baseUrl + "/compress.json?"+new Date();
|
let url = baseUrl + "/compress.json?"+new Date();
|
||||||
let result = await (await fetch(url)).json();
|
let result = await (await fetch(url)).json();
|
||||||
items = result.items;
|
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');
|
let add_tx = db.transaction(['item_db', 'set_db'], 'readwrite');
|
||||||
add_tx.onabort = function(e) {
|
add_tx.onabort = function(e) {
|
||||||
|
@ -127,54 +121,47 @@ async function load(init_func) {
|
||||||
add_promises.push(req);
|
add_promises.push(req);
|
||||||
}
|
}
|
||||||
let sets_store = add_tx.objectStore('set_db');
|
let sets_store = add_tx.objectStore('set_db');
|
||||||
for (const set in sets) {
|
for (const set in sets_) {
|
||||||
add_promises.push(sets_store.add(sets[set], set));
|
add_promises.push(sets_store.add(sets_[set], set));
|
||||||
|
sets.set(set, sets_[set]);
|
||||||
}
|
}
|
||||||
add_promises.push(add_tx.complete);
|
add_promises.push(add_tx.complete);
|
||||||
Promise.all(add_promises).then((values) => {
|
|
||||||
|
await Promise.all(add_promises);
|
||||||
init_maps();
|
init_maps();
|
||||||
init_func();
|
|
||||||
load_complete = true;
|
load_complete = true;
|
||||||
});
|
db.close();
|
||||||
// DB not closed? idfk man
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_init(init_func) {
|
async function load_init() {
|
||||||
if (load_complete) {
|
return new Promise((resolve, reject) => {
|
||||||
console.log("Item db already loaded, skipping load sequence");
|
|
||||||
init_func();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let request = window.indexedDB.open('item_db', DB_VERSION);
|
let request = window.indexedDB.open('item_db', DB_VERSION);
|
||||||
|
|
||||||
request.onerror = function() {
|
request.onerror = function() {
|
||||||
console.log("DB failed to open...");
|
reject("DB failed to open...");
|
||||||
};
|
};
|
||||||
|
|
||||||
request.onsuccess = function() {
|
request.onsuccess = async function() {
|
||||||
(async function() {
|
|
||||||
db = request.result;
|
db = request.result;
|
||||||
if (!reload) {
|
|
||||||
console.log("Using stored data...")
|
|
||||||
load_local(init_func);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (load_in_progress) {
|
if (load_in_progress) {
|
||||||
while (!load_complete) {
|
while (!load_complete) {
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
console.log("Skipping load...")
|
console.log("Skipping load...")
|
||||||
init_func();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Not 100% safe... whatever!
|
|
||||||
load_in_progress = true
|
load_in_progress = true
|
||||||
|
if (reload) {
|
||||||
console.log("Using new data...")
|
console.log("Using new data...")
|
||||||
load(init_func);
|
await load();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Using stored data...")
|
||||||
|
await load_local();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
resolve();
|
||||||
}
|
};
|
||||||
|
|
||||||
request.onupgradeneeded = function(e) {
|
request.onupgradeneeded = function(e) {
|
||||||
reload = true;
|
reload = true;
|
||||||
|
@ -198,20 +185,16 @@ function load_init(init_func) {
|
||||||
db.createObjectStore('set_db');
|
db.createObjectStore('set_db');
|
||||||
|
|
||||||
console.log("DB setup complete...");
|
console.log("DB setup complete...");
|
||||||
}
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_maps() {
|
// List of 'raw' "none" items (No Helmet, etc), in order helmet, chestplate... ring1, ring2, brace, neck, weapon.
|
||||||
//warp
|
|
||||||
itemMap = new Map();
|
|
||||||
/* Mapping from item names to set names. */
|
|
||||||
idMap = new Map();
|
|
||||||
redirectMap = new Map();
|
|
||||||
for (const it of itemTypes) {
|
for (const it of itemTypes) {
|
||||||
itemLists.set(it, []);
|
itemLists.set(it, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
let noneItems = [
|
let none_items = [
|
||||||
["armor", "helmet", "No Helmet"],
|
["armor", "helmet", "No Helmet"],
|
||||||
["armor", "chestplate", "No Chestplate"],
|
["armor", "chestplate", "No Chestplate"],
|
||||||
["armor", "leggings", "No Leggings"],
|
["armor", "leggings", "No Leggings"],
|
||||||
|
@ -222,12 +205,12 @@ function init_maps() {
|
||||||
["accessory", "necklace", "No Necklace"],
|
["accessory", "necklace", "No Necklace"],
|
||||||
["weapon", "dagger", "No Weapon"],
|
["weapon", "dagger", "No Weapon"],
|
||||||
];
|
];
|
||||||
for (let i = 0; i < noneItems.length; i++) {
|
for (let i = 0; i < none_items.length; i++) {
|
||||||
let item = Object();
|
let item = Object();
|
||||||
item.slots = 0;
|
item.slots = 0;
|
||||||
item.category = noneItems[i][0];
|
item.category = none_items[i][0];
|
||||||
item.type = noneItems[i][1];
|
item.type = none_items[i][1];
|
||||||
item.name = noneItems[i][2];
|
item.name = none_items[i][2];
|
||||||
item.displayName = item.name;
|
item.displayName = item.name;
|
||||||
item.set = null;
|
item.set = null;
|
||||||
item.quest = null;
|
item.quest = null;
|
||||||
|
@ -245,15 +228,22 @@ function init_maps() {
|
||||||
item.aDam = "0-0";
|
item.aDam = "0-0";
|
||||||
clean_item(item);
|
clean_item(item);
|
||||||
|
|
||||||
noneItems[i] = item;
|
none_items[i] = item;
|
||||||
}
|
}
|
||||||
items = items.concat(noneItems);
|
|
||||||
|
function init_maps() {
|
||||||
|
//warp
|
||||||
|
itemMap = new Map();
|
||||||
|
/* Mapping from item names to set names. */
|
||||||
|
idMap = new Map();
|
||||||
|
redirectMap = new Map();
|
||||||
|
items = items.concat(none_items);
|
||||||
//console.log(items);
|
//console.log(items);
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.remapID === undefined) {
|
if (item.remapID === undefined) {
|
||||||
itemLists.get(item.type).push(item.displayName);
|
itemLists.get(item.type).push(item.displayName);
|
||||||
itemMap.set(item.displayName, item);
|
itemMap.set(item.displayName, item);
|
||||||
if (noneItems.includes(item)) {
|
if (none_items.includes(item)) {
|
||||||
idMap.set(item.id, "");
|
idMap.set(item.id, "");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -4,6 +4,7 @@ const ING_DB_VERSION = 13;
|
||||||
|
|
||||||
let idb;
|
let idb;
|
||||||
let ireload = false;
|
let ireload = false;
|
||||||
|
let iload_in_progress = false;
|
||||||
let iload_complete = false;
|
let iload_complete = false;
|
||||||
let ings;
|
let ings;
|
||||||
let recipes;
|
let recipes;
|
||||||
|
@ -20,32 +21,34 @@ let recipeIDMap;
|
||||||
/*
|
/*
|
||||||
* Load item set from local DB. Calls init() on success.
|
* Load item set from local DB. Calls init() on success.
|
||||||
*/
|
*/
|
||||||
async function ing_load_local(init_func) {
|
async function ing_load_local() {
|
||||||
console.log("IngMap is: \n " + ingMap);
|
return new Promise(function(resolve, reject) {
|
||||||
let get_tx = idb.transaction(['ing_db', 'recipe_db'], 'readonly');
|
let get_tx = idb.transaction(['ing_db', 'recipe_db'], 'readonly');
|
||||||
let ings_store = get_tx.objectStore('ing_db');
|
let ings_store = get_tx.objectStore('ing_db');
|
||||||
let recipes_store = get_tx.objectStore('recipe_db');
|
let recipes_store = get_tx.objectStore('recipe_db');
|
||||||
let request3 = ings_store.getAll();
|
let request3 = ings_store.getAll();
|
||||||
request3.onerror = function(event) {
|
request3.onerror = function(event) {
|
||||||
console.log("Could not read local ingredient db...");
|
reject("Could not read local ingredient db...");
|
||||||
}
|
}
|
||||||
request3.onsuccess = function(event) {
|
request3.onsuccess = function(event) {
|
||||||
console.log("Successfully read local ingredient db.");
|
console.log("Successfully read local ingredient db.");
|
||||||
ings = request3.result;
|
}
|
||||||
let request4 = recipes_store.getAll();
|
let request4 = recipes_store.getAll();
|
||||||
request4.onerror = function(event) {
|
request4.onerror = function(event) {
|
||||||
console.log("Could not read local recipe db...");
|
reject("Could not read local recipe db...");
|
||||||
}
|
}
|
||||||
request4.onsuccess = function(event) {
|
request4.onsuccess = function(event) {
|
||||||
console.log("Successfully read local recipe db.");
|
console.log("Successfully read local recipe db.");
|
||||||
|
}
|
||||||
|
get_tx.oncomplete = function(event) {
|
||||||
|
ings = request3.result;
|
||||||
recipes = request4.result;
|
recipes = request4.result;
|
||||||
init_ing_maps();
|
init_ing_maps();
|
||||||
init_func();
|
|
||||||
iload_complete = true;
|
iload_complete = true;
|
||||||
}
|
|
||||||
}
|
|
||||||
await get_tx.complete;
|
|
||||||
idb.close();
|
idb.close();
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function clean_ing(ing) {
|
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.
|
* 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 getUrl = window.location;
|
||||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
|
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
||||||
let url = baseUrl + "/ingreds_compress.json";
|
// "Random" string to prevent caching!
|
||||||
|
let url = baseUrl + "/ingreds_compress.json?"+new Date();
|
||||||
url = url.replace(/\w+.html/, "") ;
|
url = url.replace(/\w+.html/, "") ;
|
||||||
let result = await (await fetch(url)).json();
|
let result = await (await fetch(url)).json();
|
||||||
|
|
||||||
|
@ -97,35 +101,40 @@ async function load_ings(init_func) {
|
||||||
}
|
}
|
||||||
add_promises.push(add_tx2.complete);
|
add_promises.push(add_tx2.complete);
|
||||||
add_promises.push(add_tx3.complete);
|
add_promises.push(add_tx3.complete);
|
||||||
Promise.all(add_promises).then((values) => {
|
|
||||||
|
await Promise.all(add_promises);
|
||||||
init_ing_maps();
|
init_ing_maps();
|
||||||
init_func();
|
|
||||||
iload_complete = true;
|
iload_complete = true;
|
||||||
});
|
idb.close();
|
||||||
// DB not closed? idfk man
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_ing_init(init_func) {
|
async function load_ing_init() {
|
||||||
if (iload_complete) {
|
return new Promise((resolve, reject) => {
|
||||||
console.log("Ingredient db already loaded, skipping load sequence");
|
|
||||||
init_func();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let request = window.indexedDB.open("ing_db", ING_DB_VERSION)
|
let request = window.indexedDB.open("ing_db", ING_DB_VERSION)
|
||||||
request.onerror = function() {
|
request.onerror = function() {
|
||||||
console.log("DB failed to open...");
|
reject("DB failed to open...");
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onsuccess = function() {
|
request.onsuccess = async function() {
|
||||||
idb = request.result;
|
idb = request.result;
|
||||||
if (!ireload) {
|
if (iload_in_progress) {
|
||||||
console.log("Using stored data...")
|
while (!iload_complete) {
|
||||||
ing_load_local(init_func);
|
await sleep(100);
|
||||||
|
}
|
||||||
|
console.log("Skipping load...")
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
iload_in_progress = true
|
||||||
|
if (ireload) {
|
||||||
console.log("Using new data...")
|
console.log("Using new data...")
|
||||||
load_ings(init_func);
|
await load_ings();
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
console.log("Using stored data...")
|
||||||
|
await ing_load_local();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onupgradeneeded = function(e) {
|
request.onupgradeneeded = function(e) {
|
||||||
|
@ -150,6 +159,7 @@ function load_ing_init(init_func) {
|
||||||
|
|
||||||
console.log("DB setup complete...");
|
console.log("DB setup complete...");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_ing_maps() {
|
function init_ing_maps() {
|
||||||
|
@ -222,4 +232,5 @@ function init_ing_maps() {
|
||||||
recipeList.push(recipe["name"]);
|
recipeList.push(recipe["name"]);
|
||||||
recipeIDMap.set(recipe["id"],recipe["name"]);
|
recipeIDMap.set(recipe["id"],recipe["name"]);
|
||||||
}
|
}
|
||||||
|
console.log(ingMap);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const TOME_DB_VERSION = 1;
|
const TOME_DB_VERSION = 3;
|
||||||
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
|
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
|
||||||
|
|
||||||
let tdb;
|
let tdb;
|
||||||
|
@ -13,29 +13,31 @@ let tomeLists = new Map();
|
||||||
/*
|
/*
|
||||||
* Load tome set from local DB. Calls init() on success.
|
* Load tome set from local DB. Calls init() on success.
|
||||||
*/
|
*/
|
||||||
async function load_tome_local(init_func) {
|
async function load_tome_local() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
let get_tx = tdb.transaction(['tome_db'], 'readonly');
|
let get_tx = tdb.transaction(['tome_db'], 'readonly');
|
||||||
let get_store = get_tx.objectStore('tome_db');
|
let get_store = get_tx.objectStore('tome_db');
|
||||||
let request = get_store.getAll();
|
let request = get_store.getAll();
|
||||||
request.onerror = function(event) {
|
request.onerror = function(event) {
|
||||||
console.log("Could not read local tome db...");
|
reject("Could not read local tome db...");
|
||||||
}
|
}
|
||||||
request.onsuccess = function(event) {
|
request.onsuccess = function(event) {
|
||||||
console.log("Successfully read local tome db.");
|
console.log("Successfully read local tome db.");
|
||||||
tomes = request.result;
|
|
||||||
|
|
||||||
init_tome_maps();
|
|
||||||
init_func();
|
|
||||||
tload_complete = true;
|
|
||||||
}
|
}
|
||||||
await get_tx.complete;
|
get_tx.oncomplete = function(event) {
|
||||||
|
tomes = request.result;
|
||||||
|
init_tome_maps();
|
||||||
|
tload_complete = true;
|
||||||
tdb.close();
|
tdb.close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Load tome set from remote DB (json). Calls init() on success.
|
* Load tome set from remote DB (json). Calls init() on success.
|
||||||
*/
|
*/
|
||||||
async function load_tome(init_func) {
|
async function load_tome() {
|
||||||
|
|
||||||
let getUrl = window.location;
|
let getUrl = window.location;
|
||||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
||||||
|
@ -60,49 +62,42 @@ async function load_tome(init_func) {
|
||||||
};
|
};
|
||||||
add_promises.push(req);
|
add_promises.push(req);
|
||||||
}
|
}
|
||||||
Promise.all(add_promises).then((values) => {
|
add_promises.push(add_tx.complete);
|
||||||
|
|
||||||
|
await Promise.all(add_promises);
|
||||||
init_tome_maps();
|
init_tome_maps();
|
||||||
init_func();
|
|
||||||
tload_complete = true;
|
tload_complete = true;
|
||||||
});
|
tdb.close();
|
||||||
// DB not closed? idfk man
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_tome_init(init_func) {
|
async function load_tome_init() {
|
||||||
if (tload_complete) {
|
return new Promise((resolve, reject) => {
|
||||||
console.log("Tome db already loaded, skipping load sequence");
|
|
||||||
init_func();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let request = window.indexedDB.open('tome_db', TOME_DB_VERSION);
|
let request = window.indexedDB.open('tome_db', TOME_DB_VERSION);
|
||||||
|
|
||||||
request.onerror = function() {
|
request.onerror = function() {
|
||||||
console.log("DB failed to open...");
|
reject("DB failed to open...");
|
||||||
};
|
};
|
||||||
|
|
||||||
request.onsuccess = function() {
|
request.onsuccess = async function() {
|
||||||
(async function() {
|
|
||||||
tdb = request.result;
|
tdb = request.result;
|
||||||
if (!treload) {
|
|
||||||
console.log("Using stored data...")
|
|
||||||
load_tome_local(init_func);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (tload_in_progress) {
|
if (tload_in_progress) {
|
||||||
while (!tload_complete) {
|
while (!tload_complete) {
|
||||||
await sleep(100);
|
await sleep(100);
|
||||||
}
|
}
|
||||||
console.log("Skipping load...")
|
console.log("Skipping load...")
|
||||||
init_func();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Not 100% safe... whatever!
|
|
||||||
tload_in_progress = true
|
tload_in_progress = true
|
||||||
|
if (treload) {
|
||||||
console.log("Using new data...")
|
console.log("Using new data...")
|
||||||
load_tome(init_func);
|
await load_tome();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Using stored data...")
|
||||||
|
await load_tome_local();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onupgradeneeded = function(e) {
|
request.onupgradeneeded = function(e) {
|
||||||
|
@ -121,8 +116,14 @@ function load_tome_init(init_func) {
|
||||||
|
|
||||||
console.log("DB setup complete...");
|
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() {
|
function init_tome_maps() {
|
||||||
//warp
|
//warp
|
||||||
tomeMap = new Map();
|
tomeMap = new Map();
|
||||||
|
@ -130,21 +131,16 @@ function init_tome_maps() {
|
||||||
tomeIDMap = new Map();
|
tomeIDMap = new Map();
|
||||||
|
|
||||||
tomeRedirectMap = new Map();
|
tomeRedirectMap = new Map();
|
||||||
for (const it of tomeTypes) {
|
for (const it of tome_types) {
|
||||||
tomeLists.set(it, []);
|
tomeLists.set(it, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
let noneTomes = [
|
|
||||||
["tome", "weaponTome", "No Weapon Tome"],
|
|
||||||
["tome", "armorTome", "No Armor Tome"],
|
|
||||||
["tome", "guildTome", "No Guild Tome"]
|
|
||||||
];
|
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
let tome = Object();
|
let tome = Object();
|
||||||
tome.slots = 0;
|
tome.slots = 0;
|
||||||
tome.category = noneTomes[i][0];
|
tome.category = none_tomes[i][0];
|
||||||
tome.type = noneTomes[i][1];
|
tome.type = none_tomes[i][1];
|
||||||
tome.name = noneTomes[i][2];
|
tome.name = none_tomes[i][2];
|
||||||
tome.displayName = tome.name;
|
tome.displayName = tome.name;
|
||||||
tome.set = null;
|
tome.set = null;
|
||||||
tome.quest = null;
|
tome.quest = null;
|
||||||
|
@ -163,14 +159,14 @@ function init_tome_maps() {
|
||||||
//dependency - load.js
|
//dependency - load.js
|
||||||
clean_item(tome);
|
clean_item(tome);
|
||||||
|
|
||||||
noneTomes[i] = tome;
|
none_tomes[i] = tome;
|
||||||
}
|
}
|
||||||
tomes = tomes.concat(noneTomes);
|
tomes = tomes.concat(none_tomes);
|
||||||
for (const tome of tomes) {
|
for (const tome of tomes) {
|
||||||
if (tome.remapID === undefined) {
|
if (tome.remapID === undefined) {
|
||||||
tomeLists.get(tome.type).push(tome.displayName);
|
tomeLists.get(tome.type).push(tome.displayName);
|
||||||
tomeMap.set(tome.displayName, tome);
|
tomeMap.set(tome.displayName, tome);
|
||||||
if (noneTomes.includes(tome)) {
|
if (none_tomes.includes(tome)) {
|
||||||
tomeIDMap.set(tome.id, "");
|
tomeIDMap.set(tome.id, "");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
131
js/powders.js
|
@ -61,3 +61,134 @@ let powderSpecialStats = [
|
||||||
_ps("Courage",new Map([ ["Duration", [6,6.5,7,7.5,8]],["Damage", [75,87.5,100,112.5,125]],["Damage Boost", [70,90,110,130,150]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),200), //f
|
_ps("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
|
_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");
|
||||||
|
|
||||||
|
})();
|
|
@ -31,14 +31,14 @@ function calculate_skillpoints(equipment, weapon) {
|
||||||
let setCount = activeSetCounts.get(setName);
|
let setCount = activeSetCounts.get(setName);
|
||||||
let old_bonus = {};
|
let old_bonus = {};
|
||||||
if (setCount) {
|
if (setCount) {
|
||||||
old_bonus = sets[setName].bonuses[setCount-1];
|
old_bonus = sets.get(setName).bonuses[setCount-1];
|
||||||
activeSetCounts.set(setName, setCount + 1);
|
activeSetCounts.set(setName, setCount + 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setCount = 0;
|
setCount = 0;
|
||||||
activeSetCounts.set(setName, 1);
|
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"];
|
//let skp_order = ["str","dex","int","def","agi"];
|
||||||
for (const i in skp_order) {
|
for (const i in skp_order) {
|
||||||
const delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
const delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
||||||
|
@ -74,8 +74,8 @@ function calculate_skillpoints(equipment, weapon) {
|
||||||
if (setName) { // undefined/null means no set.
|
if (setName) { // undefined/null means no set.
|
||||||
const setCount = activeSetCounts.get(setName);
|
const setCount = activeSetCounts.get(setName);
|
||||||
if (setCount) {
|
if (setCount) {
|
||||||
const old_bonus = sets[setName].bonuses[setCount-1];
|
const old_bonus = sets.get(setName).bonuses[setCount-1];
|
||||||
const new_bonus = sets[setName].bonuses[setCount];
|
const new_bonus = sets.get(setName).bonuses[setCount];
|
||||||
//let skp_order = ["str","dex","int","def","agi"];
|
//let skp_order = ["str","dex","int","def","agi"];
|
||||||
for (const i in skp_order) {
|
for (const i in skp_order) {
|
||||||
const set_delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
const set_delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
||||||
|
|
632
js/sq2bs.js
|
@ -1,632 +0,0 @@
|
||||||
let weapon_keys = ['dagger', 'wand', 'bow', 'relik', 'spear'];
|
|
||||||
let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots'];
|
|
||||||
let skp_keys = ['str', 'dex', 'int', 'def', 'agi'];
|
|
||||||
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'].concat(tome_keys);
|
|
||||||
let powder_keys = ['e', 't', 'w', 'f', 'a'];
|
|
||||||
|
|
||||||
let spell_disp = ['spell0-info', 'spell1-info', 'spell2-info', 'spell3-info'];
|
|
||||||
let other_disp = ['build-order', 'set-info', 'int-info'];
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
|
|
||||||
for (const eq of equipment_keys) {
|
|
||||||
document.querySelector("#"+eq+"-choice").setAttribute("oninput", "update_field('"+ eq +"'); calcBuildSchedule();");
|
|
||||||
document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');"); //toggle_plus_minus('" + eq + "-pm');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const eq of powderable_keys) {
|
|
||||||
document.querySelector("#"+eq+"-powder").setAttribute("oninput", "calcBuildSchedule(); update_field('"+ eq +"');");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const eq of tome_keys) {
|
|
||||||
document.querySelector("#" + eq + "-choice").setAttribute("oninput", "update_field('" + eq + "'); calcBuildSchedule();");
|
|
||||||
document.querySelector("#"+eq+"-tooltip").setAttribute("onclick", "collapse_element('#"+ eq +"-tooltip');");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const i of spell_disp) {
|
|
||||||
document.querySelector("#"+i+"Avg").setAttribute("onclick", "toggle_spell_tab('"+i+"')");
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelector("#level-choice").setAttribute("oninput", "calcBuildSchedule()")
|
|
||||||
document.querySelector("#weapon-choice").setAttribute("oninput", document.querySelector("#weapon-choice").getAttribute("oninput") + "resetArmorPowderSpecials();");
|
|
||||||
// document.querySelector("#edit-IDs-button").setAttribute("onclick", "toggle_edit_id_tab()");
|
|
||||||
|
|
||||||
let skp_fields = document.getElementsByClassName("skp-update");
|
|
||||||
|
|
||||||
for (i = 0; i < skp_fields.length; i++) {
|
|
||||||
skp_fields[i].setAttribute("oninput", "updateStatSchedule()");
|
|
||||||
}
|
|
||||||
|
|
||||||
let masonry = Macy({
|
|
||||||
container: "#masonry-container",
|
|
||||||
columns: 1,
|
|
||||||
mobileFirst: true,
|
|
||||||
breakAt: {
|
|
||||||
1200: 4,
|
|
||||||
},
|
|
||||||
margin: {
|
|
||||||
x: 20,
|
|
||||||
y: 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
let search_masonry = Macy({
|
|
||||||
container: "#search-results",
|
|
||||||
columns: 1,
|
|
||||||
mobileFirst: true,
|
|
||||||
breakAt: {
|
|
||||||
1200: 4,
|
|
||||||
},
|
|
||||||
margin: {
|
|
||||||
x: 20,
|
|
||||||
y: 20,
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector("#search-container").addEventListener("keyup", function(event) {
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
document.querySelector("#search-container").style.display = "none";
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
construct_AT(document.getElementById("atree-ui"), atree_example);
|
|
||||||
document.getElementById("atree-dropdown").style.display = "none";
|
|
||||||
});
|
|
||||||
|
|
||||||
// phanta scheduler
|
|
||||||
let calcBuildTask = null;
|
|
||||||
let updateStatTask = null;
|
|
||||||
let doSearchTask = null;
|
|
||||||
|
|
||||||
function calcBuildSchedule(){
|
|
||||||
if (calcBuildTask !== null) {
|
|
||||||
clearTimeout(calcBuildTask);
|
|
||||||
}
|
|
||||||
calcBuildTask = setTimeout(function(){
|
|
||||||
calcBuildTask = null;
|
|
||||||
resetEditableIDs();
|
|
||||||
calculateBuild();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStatSchedule(){
|
|
||||||
if (updateStatTask !== null) {
|
|
||||||
clearTimeout(updateStatTask);
|
|
||||||
}
|
|
||||||
updateStatTask = setTimeout(function(){
|
|
||||||
updateStatTask = null;
|
|
||||||
updateStats();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
function doSearchSchedule(){
|
|
||||||
console.log("Search Schedule called");
|
|
||||||
if (doSearchTask !== null) {
|
|
||||||
clearTimeout(doSearchTask);
|
|
||||||
}
|
|
||||||
doSearchTask = setTimeout(function(){
|
|
||||||
doSearchTask = null;
|
|
||||||
doItemSearch();
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
function sq2ResetFields(){
|
|
||||||
for (let i in powderInputs) {
|
|
||||||
setValue(powderInputs[i], "");
|
|
||||||
}
|
|
||||||
for (let i in equipmentInputs) {
|
|
||||||
setValue(equipmentInputs[i], "");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i in tomeInputs) {
|
|
||||||
setValue(tomeInputs[i], "");
|
|
||||||
}
|
|
||||||
setValue("str-skp", "0");
|
|
||||||
setValue("dex-skp", "0");
|
|
||||||
setValue("int-skp", "0");
|
|
||||||
setValue("def-skp", "0");
|
|
||||||
setValue("agi-skp", "0");
|
|
||||||
setValue("level-choice", "106");
|
|
||||||
location.hash = "";
|
|
||||||
calculateBuild();
|
|
||||||
}
|
|
||||||
|
|
||||||
// equipment field dynamic styling
|
|
||||||
function update_field(field) {
|
|
||||||
// built on the assumption of no one will type in CI/CR letter by letter
|
|
||||||
// resets
|
|
||||||
document.querySelector("#"+field+"-choice").classList.remove("text-light", "is-invalid", 'Normal', 'Unique', 'Rare', 'Legendary', 'Fabled', 'Mythic', 'Set', 'Crafted', 'Custom');
|
|
||||||
document.querySelector("#"+field+"-choice").classList.add("text-light");
|
|
||||||
document.querySelector("#" + field + "-img").classList.remove('Normal-shadow', 'Unique-shadow', 'Rare-shadow', 'Legendary-shadow', 'Fabled-shadow', 'Mythic-shadow', 'Set-shadow', 'Crafted-shadow', 'Custom-shadow');
|
|
||||||
|
|
||||||
item = document.querySelector("#"+field+"-choice").value
|
|
||||||
let powder_slots;
|
|
||||||
let tier;
|
|
||||||
let category;
|
|
||||||
let type;
|
|
||||||
|
|
||||||
// get item info
|
|
||||||
if (item.slice(0, 3) == "CI-") {
|
|
||||||
item = getCustomFromHash(item);
|
|
||||||
powder_slots = item.statMap.get("slots");
|
|
||||||
tier = item.statMap.get("tier");
|
|
||||||
category = item.statMap.get("category");
|
|
||||||
type = item.statMap.get("type");
|
|
||||||
}
|
|
||||||
else if (item.slice(0, 3) == "CR-") {
|
|
||||||
item = getCraftFromHash(item);
|
|
||||||
powder_slots = item.statMap.get("slots");
|
|
||||||
tier = item.statMap.get("tier");
|
|
||||||
category = item.statMap.get("category");
|
|
||||||
type = item.statMap.get("type");
|
|
||||||
}
|
|
||||||
else if (itemMap.get(item)) {
|
|
||||||
item = itemMap.get(item);
|
|
||||||
if (!item) {return false;}
|
|
||||||
powder_slots = item.slots;
|
|
||||||
tier = item.tier;
|
|
||||||
category = item.category;
|
|
||||||
type = item.type;
|
|
||||||
}
|
|
||||||
else if (tomeMap.get(item)) {
|
|
||||||
tome = tomeMap.get(item);
|
|
||||||
if (!tome) {return false;}
|
|
||||||
powder_slots = 0;
|
|
||||||
tier = tome.tier;
|
|
||||||
category = tome.category;
|
|
||||||
type = tome.type;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// item not found
|
|
||||||
document.querySelector("#"+field+"-choice").classList.add("text-light");
|
|
||||||
if (item) { document.querySelector("#"+field+"-choice").classList.add("is-invalid"); }
|
|
||||||
|
|
||||||
/*if (!accessory_keys.contains(type.toLowerCase())) {
|
|
||||||
document.querySelector("#"+type+"-powder").disabled = true;
|
|
||||||
}*/
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if ((type != field.replace(/[0-9]/g, '')) && (category != field.replace(/[0-9]/g, ''))) {
|
|
||||||
document.querySelector("#"+field+"-choice").classList.add("text-light");
|
|
||||||
if (item) { document.querySelector("#"+field+"-choice").classList.add("is-invalid"); }
|
|
||||||
|
|
||||||
//document.querySelector("#"+equipment_keys[i]+"-powder").disabled = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set item color
|
|
||||||
document.querySelector("#"+field+"-choice").classList.add(tier);
|
|
||||||
document.querySelector("#"+field+"-img").classList.add(tier + "-shadow");
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (powderable_keys.includes(field)) {
|
|
||||||
// set powder slots
|
|
||||||
document.querySelector("#"+field+"-powder").setAttribute("placeholder", powder_slots+" slots");
|
|
||||||
|
|
||||||
if (powder_slots == 0) {
|
|
||||||
document.querySelector("#"+field+"-powder").disabled = true;
|
|
||||||
} else {
|
|
||||||
document.querySelector("#"+field+"-powder").disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// powder error handling
|
|
||||||
document.querySelector("#" + field + "-powder").classList.remove("is-invalid");
|
|
||||||
let powder_string = document.querySelector("#"+field+"-powder").value;
|
|
||||||
|
|
||||||
if (powder_string.length % 2 != 0 || powder_string.length / 2 > powder_slots) {
|
|
||||||
document.querySelector("#"+field+"-powder").classList.add("is-invalid");
|
|
||||||
} else {
|
|
||||||
for (i = 0; i < powder_string.length / 2; i++) {
|
|
||||||
if (powder_keys.includes(powder_string.substring(i*2, i*2+2).split("")[0]) == false || isNaN(powder_string.substring(i*2, i*2+2).split("")[1]) || parseInt(powder_string.substring(i*2, i*2+2).split("")[1]) < 1 || parseInt(powder_string.substring(i*2, i*2+2).split("")[1]) > 6) {
|
|
||||||
document.querySelector("#"+field+"-powder").classList.add("is-invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// set weapon img
|
|
||||||
if (category == 'weapon') {
|
|
||||||
document.querySelector("#weapon-img").setAttribute('src', '../media/items/new/generic-'+type+'.png');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* tabulars | man i hate this code but too lazy to fix /shrug */
|
|
||||||
|
|
||||||
let tabs = ['overall-stats', 'offensive-stats', 'defensive-stats'];
|
|
||||||
|
|
||||||
function show_tab(tab) {
|
|
||||||
//console.log(itemFilters)
|
|
||||||
|
|
||||||
//hide all tabs, then show the tab of the div clicked and highlight the correct button
|
|
||||||
for (const i in tabs) {
|
|
||||||
document.querySelector("#" + tabs[i]).style.display = "none";
|
|
||||||
document.getElementById("tab-" + tabs[i].split("-")[0] + "-btn").classList.remove("selected-btn");
|
|
||||||
}
|
|
||||||
document.querySelector("#" + tab).style.display = "";
|
|
||||||
document.getElementById("tab-" + tab.split("-")[0] + "-btn").classList.add("selected-btn");
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_spell_tab(tab) {
|
|
||||||
let arrow_img = document.querySelector("#" + "arrow_" + tab + "Avg");
|
|
||||||
if (document.querySelector("#"+tab).style.display == "none") {
|
|
||||||
document.querySelector("#"+tab).style.display = "";
|
|
||||||
arrow_img.src = arrow_img.src.replace("down", "up");
|
|
||||||
} else {
|
|
||||||
document.querySelector("#"+tab).style.display = "none";
|
|
||||||
arrow_img.src = arrow_img.src.replace("up", "down");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_boost_tab(tab) {
|
|
||||||
for (const i of skp_keys) {
|
|
||||||
document.querySelector("#"+i+"-boost").style.display = "none";
|
|
||||||
document.getElementById(i + "-boost-tab").classList.remove("selected-btn");
|
|
||||||
}
|
|
||||||
document.querySelector("#"+tab+"-boost").style.display = "";
|
|
||||||
document.getElementById(tab + "-boost-tab").classList.add("selected-btn");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// toggle tab
|
|
||||||
function toggle_tab(tab) {
|
|
||||||
if (document.querySelector("#"+tab).style.display == "none") {
|
|
||||||
document.querySelector("#"+tab).style.display = "";
|
|
||||||
} else {
|
|
||||||
document.querySelector("#"+tab).style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function collapse_element(elmnt) {
|
|
||||||
elem_list = document.querySelector(elmnt).children;
|
|
||||||
if (elem_list) {
|
|
||||||
for (elem of elem_list) {
|
|
||||||
if (elem.classList.contains("no-collapse")) { continue; }
|
|
||||||
if (elem.style.display == "none") {
|
|
||||||
elem.style.display = "";
|
|
||||||
} else {
|
|
||||||
elem.style.display = "none";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// macy quirk
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
|
||||||
// weird bug where display: none overrides??
|
|
||||||
document.querySelector(elmnt).style.removeProperty('display');
|
|
||||||
}
|
|
||||||
|
|
||||||
// search misc
|
|
||||||
function set_item(item) {
|
|
||||||
document.querySelector("#search-container").style.display = "none";
|
|
||||||
let type;
|
|
||||||
// if (!player_build) {return false;}
|
|
||||||
if (item.get("category") === "weapon") {
|
|
||||||
type = "weapon";
|
|
||||||
} else if (item.get("type") === "ring") {
|
|
||||||
if (!document.querySelector("#ring1-choice").value) {
|
|
||||||
type = "ring1";
|
|
||||||
} else {
|
|
||||||
type = "ring2";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
type = item.get("type");
|
|
||||||
}
|
|
||||||
document.querySelector("#"+type+"-choice").value = item.get("displayName");
|
|
||||||
calcBuildSchedule();
|
|
||||||
update_field(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable boosts
|
|
||||||
|
|
||||||
function reset_powder_specials() {
|
|
||||||
let specials = ["Quake", "Chain_Lightning", "Curse", "Courage", "Wind_Prison"]
|
|
||||||
for (const special of specials) {
|
|
||||||
for (i = 1; i < 6; i++) {
|
|
||||||
if (document.querySelector("#"+special+"-"+i).classList.contains("toggleOn")) {
|
|
||||||
document.querySelector("#"+special+"-"+i).classList.remove("toggleOn");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// autocomplete initialize
|
|
||||||
function init_autocomplete() {
|
|
||||||
let dropdowns = new Map()
|
|
||||||
for (const eq of equipment_keys) {
|
|
||||||
if (tome_keys.includes(eq)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// build dropdown
|
|
||||||
let item_arr = [];
|
|
||||||
if (eq == 'weapon') {
|
|
||||||
for (const weaponType of weapon_keys) {
|
|
||||||
for (const weapon of itemLists.get(weaponType)) {
|
|
||||||
let item_obj = itemMap.get(weapon);
|
|
||||||
if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
item_arr.push(weapon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (const item of itemLists.get(eq.replace(/[0-9]/g, ''))) {
|
|
||||||
let item_obj = itemMap.get(item);
|
|
||||||
if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (item_obj["name"] == 'No '+ eq.charAt(0).toUpperCase() + eq.slice(1)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
item_arr.push(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create dropdown
|
|
||||||
dropdowns.set(eq, new autoComplete({
|
|
||||||
data: {
|
|
||||||
src: item_arr
|
|
||||||
},
|
|
||||||
selector: "#"+ eq +"-choice",
|
|
||||||
wrapper: false,
|
|
||||||
resultsList: {
|
|
||||||
maxResults: 1000,
|
|
||||||
tabSelect: true,
|
|
||||||
noResults: true,
|
|
||||||
class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm",
|
|
||||||
element: (list, data) => {
|
|
||||||
// dynamic result loc
|
|
||||||
let position = document.getElementById(eq+'-dropdown').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 * 2 +"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",
|
|
||||||
element: (item, data) => {
|
|
||||||
item.classList.add(itemMap.get(data.value).tier);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
input: {
|
|
||||||
selection: (event) => {
|
|
||||||
if (event.detail.selection.value) {
|
|
||||||
event.target.value = event.detail.selection.value;
|
|
||||||
}
|
|
||||||
update_field(eq);
|
|
||||||
calcBuildSchedule();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const eq of tome_keys) {
|
|
||||||
// build dropdown
|
|
||||||
let tome_arr = [];
|
|
||||||
for (const tome of tomeLists.get(eq.replace(/[0-9]/g, ''))) {
|
|
||||||
let tome_obj = tomeMap.get(tome);
|
|
||||||
if (tome_obj["restrict"] && tome_obj["restrict"] === "DEPRECATED") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//this should suffice for tomes - jank
|
|
||||||
if (tome_obj["name"].includes('No ' + eq.charAt(0).toUpperCase())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let tome_name = tome;
|
|
||||||
tome_arr.push(tome_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// create dropdown
|
|
||||||
dropdowns.set(eq, new autoComplete({
|
|
||||||
data: {
|
|
||||||
src: tome_arr
|
|
||||||
},
|
|
||||||
selector: "#"+ eq +"-choice",
|
|
||||||
wrapper: false,
|
|
||||||
resultsList: {
|
|
||||||
maxResults: 1000,
|
|
||||||
tabSelect: true,
|
|
||||||
noResults: true,
|
|
||||||
class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm",
|
|
||||||
element: (list, data) => {
|
|
||||||
// dynamic result loc
|
|
||||||
let position = document.getElementById(eq+'-dropdown').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 * 2 +"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",
|
|
||||||
element: (tome, data) => {
|
|
||||||
tome.classList.add(tomeMap.get(data.value).tier);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
events: {
|
|
||||||
input: {
|
|
||||||
selection: (event) => {
|
|
||||||
if (event.detail.selection.value) {
|
|
||||||
event.target.value = event.detail.selection.value;
|
|
||||||
}
|
|
||||||
update_field(eq);
|
|
||||||
calcBuildSchedule();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let filter_loc = ["filter1", "filter2", "filter3", "filter4"];
|
|
||||||
for (const i of filter_loc) {
|
|
||||||
dropdowns.set(i+"-choice", new autoComplete({
|
|
||||||
data: {
|
|
||||||
src: sq2ItemFilters,
|
|
||||||
},
|
|
||||||
selector: "#"+i+"-choice",
|
|
||||||
wrapper: false,
|
|
||||||
resultsList: {
|
|
||||||
tabSelect: true,
|
|
||||||
noResults: true,
|
|
||||||
class: "search-box dark-7 rounded-bottom px-2 fw-bold dark-shadow-sm",
|
|
||||||
element: (list, data) => {
|
|
||||||
// dynamic result loc
|
|
||||||
console.log(i);
|
|
||||||
list.style.zIndex = "100";
|
|
||||||
let position = document.getElementById(i+"-dropdown").getBoundingClientRect();
|
|
||||||
window_pos = document.getElementById("search-container").getBoundingClientRect();
|
|
||||||
list.style.top = position.bottom - window_pos.top + 5 +"px";
|
|
||||||
list.style.left = position.x - window_pos.x +"px";
|
|
||||||
list.style.width = position.width+"px";
|
|
||||||
|
|
||||||
if (!data.results.length) {
|
|
||||||
message = document.createElement('li');
|
|
||||||
message.classList.add('scaled-font');
|
|
||||||
message.textContent = "No filters 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;
|
|
||||||
}
|
|
||||||
doSearchSchedule();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// atree parsing
|
|
||||||
function construct_AT(elem, tree) {
|
|
||||||
for (let i = 0; i < tree.length; i++) {
|
|
||||||
let node = tree[i];
|
|
||||||
|
|
||||||
// create rows if not exist
|
|
||||||
if (document.getElementById("atree-row-" + node.row) == null) {
|
|
||||||
for (let j = 0; j <= node.row; j++) {
|
|
||||||
if (document.getElementById("atree-row-" + j) == null) {
|
|
||||||
let row = document.createElement('div');
|
|
||||||
row.classList.add("row");
|
|
||||||
row.id = "atree-row-" + j;
|
|
||||||
row.style.height = elem.getBoundingClientRect().width / 5 + "px";
|
|
||||||
|
|
||||||
|
|
||||||
for (let k = 0; k < 5; k++) {
|
|
||||||
col = document.createElement('div');
|
|
||||||
col.classList.add('col', 'px-0');
|
|
||||||
row.appendChild(col);
|
|
||||||
};
|
|
||||||
elem.appendChild(row);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// create node
|
|
||||||
let node_elem = document.createElement('div')
|
|
||||||
node_elem.style = "background-image: url('" + node.image + "'); background-size: cover; width: 100%; height: 100%;";
|
|
||||||
|
|
||||||
if (node.connector && node.rotate != 0) {
|
|
||||||
node_elem.classList.add("rotate-" + node.rotate);
|
|
||||||
};
|
|
||||||
|
|
||||||
// add tooltip
|
|
||||||
if (!node.connector) {
|
|
||||||
node_elem.addEventListener('mouseover', function(e) {
|
|
||||||
if (e.target !== this) {return;}
|
|
||||||
let tooltip = this.children[0];
|
|
||||||
tooltip.style.top = this.getBoundingClientRect().bottom + window.scrollY * 1.02 + "px";
|
|
||||||
tooltip.style.left = this.parentElement.parentElement.getBoundingClientRect().left + (elem.getBoundingClientRect().width * .05 / 2) + "px";
|
|
||||||
tooltip.style.display = "block";
|
|
||||||
});
|
|
||||||
|
|
||||||
node_elem.addEventListener('mouseout', function(e) {
|
|
||||||
if (e.target !== this) {return;}
|
|
||||||
let tooltip = this.children[0];
|
|
||||||
tooltip.style.display = "none";
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
node_elem.classList.add("atree-node");
|
|
||||||
|
|
||||||
let active_tooltip = document.createElement('div');
|
|
||||||
active_tooltip.classList.add("rounded-bottom", "dark-7", "border");
|
|
||||||
active_tooltip.style.width = elem.getBoundingClientRect().width * .95 + "px";
|
|
||||||
active_tooltip.style.display = "none";
|
|
||||||
|
|
||||||
// tooltip text formatting
|
|
||||||
|
|
||||||
let active_tooltip_title = document.createElement('b');
|
|
||||||
active_tooltip_title.classList.add("scaled-font");
|
|
||||||
active_tooltip_title.textContent = node.title;
|
|
||||||
|
|
||||||
let active_tooltip_text = document.createElement('p');
|
|
||||||
active_tooltip_text.classList.add("scaled-font-sm");
|
|
||||||
active_tooltip_text.textContent = node.desc;
|
|
||||||
|
|
||||||
active_tooltip.appendChild(active_tooltip_title);
|
|
||||||
active_tooltip.appendChild(active_tooltip_text);
|
|
||||||
|
|
||||||
node_tooltip = active_tooltip.cloneNode(true);
|
|
||||||
|
|
||||||
active_tooltip.id = "atree-ab-" + node.title.replaceAll(" ", "");
|
|
||||||
|
|
||||||
node_tooltip.style.position = "absolute";
|
|
||||||
node_tooltip.style.zIndex = "100";
|
|
||||||
|
|
||||||
node_elem.appendChild(node_tooltip);
|
|
||||||
document.getElementById("atree-active").appendChild(active_tooltip);
|
|
||||||
|
|
||||||
node_elem.addEventListener('click', function(e) {
|
|
||||||
if (e.target !== this) {return;}
|
|
||||||
let tooltip = document.getElementById("atree-ab-" + node.title.replaceAll(" ", ""));
|
|
||||||
if (tooltip.style.display == "block") {
|
|
||||||
tooltip.style.display = "none";
|
|
||||||
this.classList.add("atree-node");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tooltip.style.display = "block";
|
|
||||||
this.classList.remove("atree-node");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById("atree-row-" + node.row).children[node.col].appendChild(node_elem);
|
|
||||||
};
|
|
||||||
};
|
|
692
js/sq2build.js
|
@ -1,692 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.20], ["sword", 1.10]]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Error to catch items that don't exist.
|
|
||||||
* @module ItemNotFound
|
|
||||||
*/
|
|
||||||
class ItemNotFound {
|
|
||||||
/**
|
|
||||||
* @class
|
|
||||||
* @param {String} item the item name entered
|
|
||||||
* @param {String} type the type of item
|
|
||||||
* @param {Boolean} genElement whether to generate an element from inputs
|
|
||||||
* @param {String} override override for item type
|
|
||||||
*/
|
|
||||||
constructor(item, type, genElement, override) {
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
this.message = `Cannot find ${override||type} named ${item}`;
|
|
||||||
if (genElement)
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
* @type {Element}
|
|
||||||
*/
|
|
||||||
this.element = document.getElementById(`${type}-choice`).parentElement.querySelectorAll("p.error")[0];
|
|
||||||
else
|
|
||||||
this.element = document.createElement("div");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Error to catch incorrect input.
|
|
||||||
* @module IncorrectInput
|
|
||||||
*/
|
|
||||||
class IncorrectInput {
|
|
||||||
/**
|
|
||||||
* @class
|
|
||||||
* @param {String} input the inputted text
|
|
||||||
* @param {String} format the correct format
|
|
||||||
* @param {String} sibling the id of the error node's sibling
|
|
||||||
*/
|
|
||||||
constructor(input, format, sibling) {
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
this.message = `${input} is incorrect. Example: ${format}`;
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
* @type {String}
|
|
||||||
*/
|
|
||||||
this.id = sibling;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Error that inputs an array of items to generate errors of.
|
|
||||||
* @module ListError
|
|
||||||
* @extends Error
|
|
||||||
*/
|
|
||||||
class ListError extends Error {
|
|
||||||
/**
|
|
||||||
* @class
|
|
||||||
* @param {Array} errors array of errors
|
|
||||||
*/
|
|
||||||
constructor(errors) {
|
|
||||||
let ret = [];
|
|
||||||
if (typeof errors[0] == "string") {
|
|
||||||
super(errors[0]);
|
|
||||||
} else {
|
|
||||||
super(errors[0].message);
|
|
||||||
}
|
|
||||||
for (let i of errors) {
|
|
||||||
if (typeof i == "string") {
|
|
||||||
ret.push(new Error(i));
|
|
||||||
} else {
|
|
||||||
ret.push(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @public
|
|
||||||
* @type {Object[]}
|
|
||||||
*/
|
|
||||||
this.errors = ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Class that represents a wynn player's build.
|
|
||||||
*/
|
|
||||||
class Build{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Construct a build.
|
|
||||||
* @param {Number} level : Level of the player.
|
|
||||||
* @param {String[]} equipment : List of equipment names that make up the build.
|
|
||||||
* In order: boots, Chestplate, Leggings, Boots, Ring1, Ring2, Brace, Neck, Weapon.
|
|
||||||
* @param {Number[]} powders : Powder application. List of lists of integers (powder IDs).
|
|
||||||
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
|
||||||
* @param {Object[]} inputerrors : List of instances of error-like classes.
|
|
||||||
*
|
|
||||||
* @param {Object[]} tomes: List of tomes.
|
|
||||||
* In order: 2x Weapon Mastery Tome, 4x Armor Mastery Tome, 1x Guild Tome.
|
|
||||||
* 2x Slaying Mastery Tome, 2x Dungeoneering Mastery Tome, 2x Gathering Mastery Tome are in game, but do not have "useful" stats (those that affect damage calculations or building)
|
|
||||||
*/
|
|
||||||
constructor(level,equipment, powders, externalStats, inputerrors=[], tomes){
|
|
||||||
|
|
||||||
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.chesplate, 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]);
|
|
||||||
}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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//cannot craft tomes
|
|
||||||
|
|
||||||
if(tomeMap.get(tomes[0]) && tomeMap.get(tomes[0]).type === "weaponTome") {
|
|
||||||
const weaponTome1 = tomeMap.get(tomes[0]);
|
|
||||||
this.weaponTome1 = expandItem(weaponTome1, []);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let weaponTome1 = getCustomFromHash(tomes[0]) ? getCustomFromHash(tomes[0]) : undefined;
|
|
||||||
if (weaponTome1.statMap.get("type") !== "weaponTome") {
|
|
||||||
throw new Error("Not a Weapon Tome");
|
|
||||||
}
|
|
||||||
if (this.weaponTome1.get("custom")) {
|
|
||||||
this.customItems.push(weaponTome1);
|
|
||||||
} //can't craft tomes
|
|
||||||
|
|
||||||
} catch (Error) {
|
|
||||||
const weaponTome1 = tomeMap.get("No Weapon Tome");
|
|
||||||
this.weaponTome1 = expandItem(weaponTome1, []);
|
|
||||||
errors.push(new ItemNotFound(tomes[0], "weaponTome1", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(tomeMap.get(tomes[1]) && tomeMap.get(tomes[1]).type === "weaponTome") {
|
|
||||||
const weaponTome2 = tomeMap.get(tomes[1]);
|
|
||||||
this.weaponTome2 = expandItem(weaponTome2, []);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let weaponTome2 = getCustomFromHash(tomes[1]) ? getCustomFromHash(tomes[1]) : undefined;
|
|
||||||
if (weaponTome2.statMap.get("type") !== "weaponTome") {
|
|
||||||
throw new Error("Not a Weapon Tome");
|
|
||||||
}
|
|
||||||
if (this.weaponTome2.get("custom")) {
|
|
||||||
this.customItems.push(weaponTome2);
|
|
||||||
} //can't craft tomes
|
|
||||||
|
|
||||||
} catch (Error) {
|
|
||||||
const weaponTome2 = tomeMap.get("No Weapon Tome");
|
|
||||||
this.weaponTome2 = expandItem(weaponTome2, []);
|
|
||||||
errors.push(new ItemNotFound(tomes[1], "weaponTome2", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(tomeMap.get(tomes[2]) && tomeMap.get(tomes[2]).type === "armorTome") {
|
|
||||||
const armorTome1 = tomeMap.get(tomes[2]);
|
|
||||||
this.armorTome1 = expandItem(armorTome1, []);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let armorTome1 = getCustomFromHash(tomes[2]) ? getCustomFromHash(tomes[2]) : undefined;
|
|
||||||
if (armorTome1.statMap.get("type") !== "armorTome") {
|
|
||||||
throw new Error("Not an Armor Tome");
|
|
||||||
}
|
|
||||||
if (this.armorTome1.get("custom")) {
|
|
||||||
this.customItems.push(armorTome1);
|
|
||||||
} //can't craft tomes
|
|
||||||
|
|
||||||
} catch (Error) {
|
|
||||||
const armorTome1 = tomeMap.get("No Armor Tome");
|
|
||||||
this.armorTome1 = expandItem(armorTome1, []);
|
|
||||||
errors.push(new ItemNotFound(tomes[2], "armorTome1", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(tomeMap.get(tomes[3]) && tomeMap.get(tomes[3]).type === "armorTome") {
|
|
||||||
const armorTome2 = tomeMap.get(tomes[3]);
|
|
||||||
this.armorTome2 = expandItem(armorTome2, []);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let armorTome2 = getCustomFromHash(tomes[3]) ? getCustomFromHash(tomes[3]) : undefined;
|
|
||||||
if (armorTome2.statMap.get("type") !== "armorTome") {
|
|
||||||
throw new Error("Not an Armor Tome");
|
|
||||||
}
|
|
||||||
if (this.armorTome2.get("custom")) {
|
|
||||||
this.customItems.push(armorTome2);
|
|
||||||
} //can't craft tomes
|
|
||||||
|
|
||||||
} catch (Error) {
|
|
||||||
const armorTome2 = tomeMap.get("No Armor Tome");
|
|
||||||
this.armorTome2 = expandItem(armorTome2, []);
|
|
||||||
errors.push(new ItemNotFound(tomes[3], "armorTome2", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(tomeMap.get(tomes[4]) && tomeMap.get(tomes[4]).type === "armorTome") {
|
|
||||||
const armorTome3 = tomeMap.get(tomes[4]);
|
|
||||||
this.armorTome3 = expandItem(armorTome3, []);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let armorTome3 = getCustomFromHash(tomes[4]) ? getCustomFromHash(tomes[4]) : undefined;
|
|
||||||
if (armorTome3.statMap.get("type") !== "armorTome") {
|
|
||||||
throw new Error("Not an Armor Tome");
|
|
||||||
}
|
|
||||||
if (this.armorTome3.get("custom")) {
|
|
||||||
this.customItems.push(armorTome3);
|
|
||||||
} //can't craft tomes
|
|
||||||
|
|
||||||
} catch (Error) {
|
|
||||||
const armorTome3 = tomeMap.get("No Armor Tome");
|
|
||||||
this.armorTome3 = expandItem(armorTome3, []);
|
|
||||||
errors.push(new ItemNotFound(tomes[4], "armorTome3", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(tomeMap.get(tomes[5]) && tomeMap.get(tomes[5]).type === "armorTome") {
|
|
||||||
const armorTome4 = tomeMap.get(tomes[5]);
|
|
||||||
this.armorTome4 = expandItem(armorTome4, []);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let armorTome4 = getCustomFromHash(tomes[5]) ? getCustomFromHash(tomes[5]) : undefined;
|
|
||||||
if (armorTome4.statMap.get("type") !== "armorTome") {
|
|
||||||
throw new Error("Not an Armor Tome");
|
|
||||||
}
|
|
||||||
if (this.armorTome4.get("custom")) {
|
|
||||||
this.customItems.push(armorTome4);
|
|
||||||
} //can't craft tomes
|
|
||||||
|
|
||||||
} catch (Error) {
|
|
||||||
const armorTome4 = tomeMap.get("No Armor Tome");
|
|
||||||
this.armorTome4 = expandItem(armorTome4, []);
|
|
||||||
errors.push(new ItemNotFound(tomes[5], "armorTome4", true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(tomeMap.get(tomes[6]) && tomeMap.get(tomes[6]).type === "guildTome") {
|
|
||||||
const guildTome1 = tomeMap.get(tomes[6]);
|
|
||||||
this.guildTome1 = expandItem(guildTome1, []);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
let guildTome1 = getCustomFromHash(tomes[6]) ? getCustomFromHash(tomes[6]) : undefined;
|
|
||||||
if (guildTome1.statMap.get("type") !== "guildTome1") {
|
|
||||||
throw new Error("Not an Guild Tome");
|
|
||||||
}
|
|
||||||
if (this.guildTome1.get("custom")) {
|
|
||||||
this.customItems.push(guildTome1);
|
|
||||||
} //can't craft tomes
|
|
||||||
|
|
||||||
} catch (Error) {
|
|
||||||
const guildTome1 = tomeMap.get("No Guild Tome");
|
|
||||||
this.guildTome1 = expandItem(guildTome1, []);
|
|
||||||
errors.push(new ItemNotFound(tomes[6], "guildTome1", 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.tomes = [this.weaponTome1, this.weaponTome2, this.armorTome1, this.armorTome2, this.armorTome3, this.armorTome4, this.guildTome1];
|
|
||||||
this.items = this.equipment.concat([this.weapon]).concat(this.tomes);
|
|
||||||
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
|
||||||
let result = calculate_skillpoints(this.equipment.concat(this.tomes), this.weapon);
|
|
||||||
console.log(result);
|
|
||||||
this.equip_order = result[0];
|
|
||||||
// How many skillpoints the player had to assign (5 number)
|
|
||||||
this.base_skillpoints = result[1];
|
|
||||||
// How many skillpoints the build ended up with (5 number)
|
|
||||||
this.total_skillpoints = result[2];
|
|
||||||
// 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
|
|
||||||
*/
|
|
||||||
toString(){
|
|
||||||
return [this.equipment,this.weapon,this.tomes].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"];
|
|
||||||
|
|
||||||
//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")) {
|
|
||||||
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.get("majorIds")) {
|
|
||||||
for (const major_id of item.get("majorIds")) {
|
|
||||||
major_ids.add(major_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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")));
|
|
||||||
}
|
|
||||||
}
|
|
1137
js/sq2builder.js
2411
js/sq2display.js
|
@ -1,191 +0,0 @@
|
||||||
let powder_chars = [
|
|
||||||
'\u2724',
|
|
||||||
'\u2726',
|
|
||||||
'\u2749',
|
|
||||||
'\u2739',
|
|
||||||
'\u274b'
|
|
||||||
]
|
|
||||||
let subscript_nums = [
|
|
||||||
'\u2081',
|
|
||||||
'\u2082',
|
|
||||||
'\u2083',
|
|
||||||
'\u2084',
|
|
||||||
'\u2085',
|
|
||||||
'\u2086',
|
|
||||||
]
|
|
||||||
|
|
||||||
let skp_names = [
|
|
||||||
'str',
|
|
||||||
'dex',
|
|
||||||
'int',
|
|
||||||
'def',
|
|
||||||
'agi'
|
|
||||||
]
|
|
||||||
|
|
||||||
let elem_chars = [
|
|
||||||
'e',
|
|
||||||
't',
|
|
||||||
'w',
|
|
||||||
'f',
|
|
||||||
'a'
|
|
||||||
]
|
|
||||||
|
|
||||||
let elem_names = [
|
|
||||||
'earth',
|
|
||||||
'thunder',
|
|
||||||
'water',
|
|
||||||
'fire',
|
|
||||||
'air'
|
|
||||||
]
|
|
||||||
|
|
||||||
let elem_colors = [
|
|
||||||
"#00AA00",
|
|
||||||
"#FFFF55",
|
|
||||||
"#55FFFF",
|
|
||||||
"#FF5555",
|
|
||||||
"#FFFFFF"
|
|
||||||
]
|
|
||||||
|
|
||||||
let item_types = [
|
|
||||||
"Helmet",
|
|
||||||
"Chestplate",
|
|
||||||
"Leggings",
|
|
||||||
"Boots",
|
|
||||||
"Ring",
|
|
||||||
"Bracelet",
|
|
||||||
"Necklace",
|
|
||||||
"Dagger",
|
|
||||||
"Spear",
|
|
||||||
"Wand",
|
|
||||||
"Relik",
|
|
||||||
"Bow",
|
|
||||||
"Potion",
|
|
||||||
"Scroll",
|
|
||||||
"Food",
|
|
||||||
"Weapon Tome",
|
|
||||||
"Armor Tome",
|
|
||||||
"Guild Tome"
|
|
||||||
]
|
|
||||||
|
|
||||||
let tome_types = ['weaponTome', 'armorTome', 'guildTome'];
|
|
||||||
let tome_keys = ['weaponTome1', 'weaponTome2', 'armorTome1', 'armorTome2', 'armorTome3', 'armorTome4', 'guildTome1'];
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Display commands
|
|
||||||
*/
|
|
||||||
let build_all_display_commands = [
|
|
||||||
"#defense-stats",
|
|
||||||
"str", "dex", "int", "def", "agi",
|
|
||||||
"mr", "ms",
|
|
||||||
"hprRaw", "hprPct",
|
|
||||||
"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",
|
|
||||||
"rainbowRaw",
|
|
||||||
"sprint", "sprintReg",
|
|
||||||
"jh",
|
|
||||||
"xpb", "lb", "lq",
|
|
||||||
"spRegen",
|
|
||||||
"eSteal",
|
|
||||||
"gXp", "gSpd",
|
|
||||||
];
|
|
||||||
|
|
||||||
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",
|
|
||||||
"atkSpd",
|
|
||||||
"!elemental",
|
|
||||||
"hp",
|
|
||||||
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
|
||||||
"!spacer",
|
|
||||||
"fDef", "wDef", "aDef", "tDef", "eDef",
|
|
||||||
"!elemental",
|
|
||||||
"classReq",
|
|
||||||
"lvl",
|
|
||||||
"strReq", "dexReq", "intReq", "defReq","agiReq",
|
|
||||||
"!spacer",
|
|
||||||
"str", "dex", "int", "def", "agi",
|
|
||||||
"hpBonus",
|
|
||||||
"hprRaw", "hprPct",
|
|
||||||
"sdRaw", "sdPct",
|
|
||||||
"mdRaw", "mdPct",
|
|
||||||
"mr", "ms",
|
|
||||||
"ref", "thorns",
|
|
||||||
"ls",
|
|
||||||
"poison",
|
|
||||||
"expd",
|
|
||||||
"spd",
|
|
||||||
"atkTier",
|
|
||||||
"!elemental",
|
|
||||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
|
||||||
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
|
|
||||||
"!elemental",
|
|
||||||
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
|
|
||||||
"rainbowRaw",
|
|
||||||
"sprint", "sprintReg",
|
|
||||||
"jh",
|
|
||||||
"xpb", "lb", "lq",
|
|
||||||
"spRegen",
|
|
||||||
"eSteal",
|
|
||||||
"gXp", "gSpd",
|
|
||||||
"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",
|
|
||||||
]
|
|
|
@ -250,7 +250,7 @@ function resetItemSearch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function init_items() {
|
function init_items() {
|
||||||
items_expanded = items.filter( (i) => !("remapID" in i) ).map( (i) => expandItem(i, []) );
|
items_expanded = items.filter( (i) => !("remapID" in i) ).map( (i) => expandItem(i) );
|
||||||
}
|
}
|
||||||
|
|
||||||
load_init(init_items);
|
load_init(init_items);
|
||||||
|
|
156
js/utils.js
|
@ -1,32 +1,8 @@
|
||||||
let getUrl = window.location;
|
let getUrl = window.location;
|
||||||
const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
|
const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
|
||||||
|
|
||||||
const zip = (a, b) => a.map((k, i) => [k, b[i]]);
|
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]]);
|
||||||
//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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(num, low, high){
|
function clamp(num, low, high){
|
||||||
return Math.min(Math.max(num, low), high);
|
return Math.min(Math.max(num, low), high);
|
||||||
|
@ -217,9 +193,9 @@ Base64 = (function () {
|
||||||
*/
|
*/
|
||||||
read_bit(idx) {
|
read_bit(idx) {
|
||||||
if (idx < 0 || idx >= this.length) {
|
if (idx < 0 || idx >= this.length) {
|
||||||
throw new RangeError("Cannot read bit outside the range of the BitVector.");
|
throw new RangeError("Cannot read bit outside the range of the BitVector. ("+idx+" > "+this.length+")");
|
||||||
}
|
}
|
||||||
return ((this.bits[Math.floor(idx / 32)] & (1 << (idx % 32))) == 0 ? 0 : 1);
|
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.
|
/** Returns an integer value (if possible) made from the range of bits [start, end). Undefined behavior if the range to read is too big.
|
||||||
|
@ -657,3 +633,127 @@ async function hardReload() {
|
||||||
function capitalizeFirst(str) {
|
function capitalizeFirst(str) {
|
||||||
return str[0].toUpperCase() + str.substring(1);
|
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;
|
||||||
|
}
|
||||||
|
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/connect_c.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB 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 |
Before Width: | Height: | Size: 5.5 KiB 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 |
BIN
media/atree/node_4_blocked.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
media/atree/node_highlight.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
media/audio/bruh_sound_effect.mp3
Normal file
|
@ -1,8 +1,6 @@
|
||||||
Process for getting new data:
|
Process for getting new data:
|
||||||
|
|
||||||
1. run `python3 dump.py`. This will overwrite `dump.json` and `../ingreds.json`
|
1. Get new data from API with `get.py`
|
||||||
2. Copy `../old clean.json` or `../compress.json` into `updated.json`
|
2. Clean the data (may have to do manually) with the `process` related py files
|
||||||
3. Run `python3 transform_merge.py`
|
3. Check validity (json differ or whatever)
|
||||||
4. Run `python3 ing_transform_combine.py`
|
4. Create clean and compress versions and copy them into toplevel for usage (can use `clean_json.py` and `compress_json.py` for this).
|
||||||
5. Check validity (json differ or whatever)
|
|
||||||
6. Copy `clean.json` and `compress.json` into toplevel for usage
|
|
||||||
|
|
66
py_script/atree-convertID.py
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
"""
|
||||||
|
Generate a JSON Ability Tree [atree_constants_idfied.json] with:
|
||||||
|
- All references replaced by numerical IDs
|
||||||
|
given a JSON Ability Tree with reference as string AND a JSON Ability Names to IDs.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Ability names to IDs data
|
||||||
|
with open("atree_ids.json") as f:
|
||||||
|
id_data = json.loads(f.read())
|
||||||
|
|
||||||
|
# Ability tree data with reference as string
|
||||||
|
with open("atree_constants.json") as f:
|
||||||
|
atree_data = json.loads(f.read())
|
||||||
|
|
||||||
|
def translate_id(id_data, atree_data):
|
||||||
|
for _class, info in atree_data.items():
|
||||||
|
def translate(path, ref):
|
||||||
|
ref_dict = info
|
||||||
|
for x in path:
|
||||||
|
ref_dict = ref_dict[x]
|
||||||
|
ref_dict[ref] = id_data[_class][ref_dict[ref]]
|
||||||
|
|
||||||
|
for abil in range(len(info)):
|
||||||
|
info[abil]["id"] = id_data[_class][info[abil]["display_name"]]
|
||||||
|
for ref in range(len(info[abil]["parents"])):
|
||||||
|
translate([abil, "parents"], ref)
|
||||||
|
|
||||||
|
for ref in range(len(info[abil]["dependencies"])):
|
||||||
|
translate([abil, "dependencies"], ref)
|
||||||
|
|
||||||
|
for ref in range(len(info[abil]["blockers"])):
|
||||||
|
translate([abil, "blockers"], ref)
|
||||||
|
|
||||||
|
if "base_abil" in info[abil]:
|
||||||
|
base_abil_name = info[abil]["base_abil"]
|
||||||
|
if base_abil_name in id_data[_class]:
|
||||||
|
translate([abil], "base_abil")
|
||||||
|
|
||||||
|
if "effects" not in info[abil]:
|
||||||
|
print("WARNING: abil missing 'effects' tag")
|
||||||
|
print(info[abil])
|
||||||
|
info[abil]["effects"] = []
|
||||||
|
for effect in info[abil]["effects"]:
|
||||||
|
if effect["type"] == "raw_stat":
|
||||||
|
for bonus in effect["bonuses"]:
|
||||||
|
if "abil" in bonus and bonus["abil"] in id_data[_class]:
|
||||||
|
bonus["abil"] = id_data[_class][bonus["abil"]]
|
||||||
|
|
||||||
|
elif effect["type"] == "stat_scaling":
|
||||||
|
if "inputs" in effect: # Might not exist for sliders
|
||||||
|
for _input in effect["inputs"]:
|
||||||
|
if "abil" in _input and _input["abil"] in id_data[_class]:
|
||||||
|
_input["abil"] = id_data[_class][_input["abil"]]
|
||||||
|
if isinstance(effect["output"], list):
|
||||||
|
for output in effect["output"]:
|
||||||
|
if "abil" in output and output["abil"] in id_data[_class]:
|
||||||
|
output["abil"] = id_data[_class][output["abil"]]
|
||||||
|
else:
|
||||||
|
if "abil" in effect["output"] and effect["output"]["abil"] in id_data[_class]:
|
||||||
|
effect["output"]["abil"] = id_data[_class][effect["output"]["abil"]]
|
||||||
|
|
||||||
|
translate_id(id_data, atree_data)
|
||||||
|
|
||||||
|
with open('atree_constants_idfied.json', 'w', encoding='utf-8') as abil_dest:
|
||||||
|
json.dump(atree_data, abil_dest, ensure_ascii=False, indent=4)
|
79
py_script/atree-generateID.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
"""
|
||||||
|
Generate a minified JSON Ability Tree [atree_constants_min.json] AND a minified .js form [atree_constants_min.js] of the Ability Tree with:
|
||||||
|
- All references replaced by numerical IDs
|
||||||
|
- Extra JSON File with Class: [Original name as key and Assigned IDs as value].
|
||||||
|
given [atree_constants.js] .js form of the Ability Tree with reference as string.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
def translate_id(id_data, atree_data):
|
||||||
|
for _class, info in atree_data.items():
|
||||||
|
def translate(path, ref):
|
||||||
|
ref_dict = info
|
||||||
|
for x in path:
|
||||||
|
ref_dict = ref_dict[x]
|
||||||
|
ref_dict[ref] = id_data[_class][ref_dict[ref]]
|
||||||
|
|
||||||
|
for abil in range(len(info)):
|
||||||
|
info[abil]["id"] = id_data[_class][info[abil]["display_name"]]
|
||||||
|
for ref in range(len(info[abil]["parents"])):
|
||||||
|
translate([abil, "parents"], ref)
|
||||||
|
|
||||||
|
for ref in range(len(info[abil]["dependencies"])):
|
||||||
|
translate([abil, "dependencies"], ref)
|
||||||
|
|
||||||
|
for ref in range(len(info[abil]["blockers"])):
|
||||||
|
translate([abil, "blockers"], ref)
|
||||||
|
|
||||||
|
if "base_abil" in info[abil]:
|
||||||
|
base_abil_name = info[abil]["base_abil"]
|
||||||
|
if base_abil_name in id_data[_class]:
|
||||||
|
translate([abil], "base_abil")
|
||||||
|
|
||||||
|
if "effects" not in info[abil]:
|
||||||
|
print("WARNING: abil missing 'effects' tag")
|
||||||
|
print(info[abil])
|
||||||
|
info[abil]["effects"] = []
|
||||||
|
for effect in info[abil]["effects"]:
|
||||||
|
if effect["type"] == "raw_stat":
|
||||||
|
for bonus in effect["bonuses"]:
|
||||||
|
if "abil" in bonus and bonus["abil"] in id_data[_class]:
|
||||||
|
bonus["abil"] = id_data[_class][bonus["abil"]]
|
||||||
|
|
||||||
|
elif effect["type"] == "stat_scaling":
|
||||||
|
if "inputs" in effect: # Might not exist for sliders
|
||||||
|
for _input in effect["inputs"]:
|
||||||
|
if "abil" in _input and _input["abil"] in id_data[_class]:
|
||||||
|
_input["abil"] = id_data[_class][_input["abil"]]
|
||||||
|
if isinstance(effect["output"], list):
|
||||||
|
for output in effect["output"]:
|
||||||
|
if "abil" in output and output["abil"] in id_data[_class]:
|
||||||
|
output["abil"] = id_data[_class][output["abil"]]
|
||||||
|
else:
|
||||||
|
if "abil" in effect["output"] and effect["output"]["abil"] in id_data[_class]:
|
||||||
|
effect["output"]["abil"] = id_data[_class][effect["output"]["abil"]]
|
||||||
|
|
||||||
|
abilDict = {}
|
||||||
|
with open("atree_constants.js") as f:
|
||||||
|
data = f.read()
|
||||||
|
data = data.replace("const atrees = ", "")
|
||||||
|
data = json.loads(data)
|
||||||
|
for classType, info in data.items():
|
||||||
|
_id = 0
|
||||||
|
abilDict[classType] = {}
|
||||||
|
for abil in info:
|
||||||
|
abilDict[classType][abil["display_name"]] = _id
|
||||||
|
_id += 1
|
||||||
|
|
||||||
|
with open("atree_ids.json", "w", encoding='utf-8') as id_dest:
|
||||||
|
json.dump(abilDict, id_dest, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
translate_id(abilDict, data)
|
||||||
|
|
||||||
|
data_str = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
|
||||||
|
data_str = "const atrees=" + data_str
|
||||||
|
with open('atree_constants_min.js', 'w', encoding='utf-8') as abil_dest:
|
||||||
|
abil_dest.write(data_str)
|
||||||
|
|
||||||
|
with open('atree_constants_min.json', 'w', encoding='utf-8') as json_dest:
|
||||||
|
json.dump(data, json_dest, ensure_ascii=False, separators=(',', ':'))
|
146
py_script/atree-ids.json
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
{
|
||||||
|
"Archer": {
|
||||||
|
"Arrow Shield": 1,
|
||||||
|
"Escape": 2,
|
||||||
|
"Arrow Bomb": 3,
|
||||||
|
"Heart Shatter": 4,
|
||||||
|
"Fire Creep": 5,
|
||||||
|
"Bryophyte Roots": 6,
|
||||||
|
"Nimble String": 7,
|
||||||
|
"Arrow Storm": 8,
|
||||||
|
"Guardian Angels": 9,
|
||||||
|
"Windy Feet": 10,
|
||||||
|
"Basaltic Trap": 11,
|
||||||
|
"Windstorm": 12,
|
||||||
|
"Grappling Hook": 13,
|
||||||
|
"Implosion": 14,
|
||||||
|
"Twain's Arc": 15,
|
||||||
|
"Fierce Stomp": 16,
|
||||||
|
"Scorched Earth": 17,
|
||||||
|
"Leap": 18,
|
||||||
|
"Shocking Bomb": 19,
|
||||||
|
"Mana Trap": 20,
|
||||||
|
"Escape Artist": 21,
|
||||||
|
"Initiator": 22,
|
||||||
|
"Call of the Hound": 23,
|
||||||
|
"Arrow Hurricane": 24,
|
||||||
|
"Geyser Stomp": 25,
|
||||||
|
"Crepuscular Ray": 26,
|
||||||
|
"Grape Bomb": 27,
|
||||||
|
"Tangled Traps": 28,
|
||||||
|
"Snow Storm": 29,
|
||||||
|
"All-Seeing Panoptes": 30,
|
||||||
|
"Minefield": 31,
|
||||||
|
"Bow Proficiency I": 32,
|
||||||
|
"Cheaper Arrow Bomb": 33,
|
||||||
|
"Cheaper Arrow Storm": 34,
|
||||||
|
"Cheaper Escape": 35,
|
||||||
|
"Earth Mastery": 36,
|
||||||
|
"Thunder Mastery": 37,
|
||||||
|
"Water Mastery": 38,
|
||||||
|
"Air Mastery": 39,
|
||||||
|
"Fire Mastery": 40,
|
||||||
|
"More Shields": 41,
|
||||||
|
"Stormy Feet": 42,
|
||||||
|
"Refined Gunpowder": 43,
|
||||||
|
"More Traps": 44,
|
||||||
|
"Better Arrow Shield": 45,
|
||||||
|
"Better Leap": 46,
|
||||||
|
"Better Guardian Angels": 47,
|
||||||
|
"Cheaper Arrow Storm (2)": 48,
|
||||||
|
"Precise Shot": 49,
|
||||||
|
"Cheaper Arrow Shield": 50,
|
||||||
|
"Rocket Jump": 51,
|
||||||
|
"Cheaper Escape (2)": 52,
|
||||||
|
"Stronger Hook": 53,
|
||||||
|
"Cheaper Arrow Bomb (2)": 54,
|
||||||
|
"Bouncing Bomb": 55,
|
||||||
|
"Homing Shots": 56,
|
||||||
|
"Shrapnel Bomb": 57,
|
||||||
|
"Elusive": 58,
|
||||||
|
"Double Shots": 59,
|
||||||
|
"Triple Shots": 60,
|
||||||
|
"Power Shots": 61,
|
||||||
|
"Focus": 62,
|
||||||
|
"More Focus": 63,
|
||||||
|
"More Focus (2)": 64,
|
||||||
|
"Traveler": 65,
|
||||||
|
"Patient Hunter": 66,
|
||||||
|
"Stronger Patient Hunter": 67,
|
||||||
|
"Frenzy": 68,
|
||||||
|
"Phantom Ray": 69,
|
||||||
|
"Arrow Rain": 70,
|
||||||
|
"Decimator": 71
|
||||||
|
},
|
||||||
|
"Warrior": {
|
||||||
|
"Bash": 1,
|
||||||
|
"Spear Proficiency 1": 2,
|
||||||
|
"Cheaper Bash": 3,
|
||||||
|
"Double Bash": 4,
|
||||||
|
"Charge": 5,
|
||||||
|
"Heavy Impact": 6,
|
||||||
|
"Vehement": 7,
|
||||||
|
"Tougher Skin": 8,
|
||||||
|
"Uppercut": 9,
|
||||||
|
"Cheaper Charge": 10,
|
||||||
|
"War Scream": 11,
|
||||||
|
"Earth Mastery": 12,
|
||||||
|
"Thunder Mastery": 13,
|
||||||
|
"Water Mastery": 14,
|
||||||
|
"Air Mastery": 15,
|
||||||
|
"Fire Mastery": 16,
|
||||||
|
"Quadruple Bash": 17,
|
||||||
|
"Fireworks": 18,
|
||||||
|
"Half-Moon Swipe": 19,
|
||||||
|
"Flyby Jab": 20,
|
||||||
|
"Flaming Uppercut": 21,
|
||||||
|
"Iron Lungs": 22,
|
||||||
|
"Generalist": 23,
|
||||||
|
"Counter": 24,
|
||||||
|
"Mantle of the Bovemists": 25,
|
||||||
|
"Bak'al's Grasp": 26,
|
||||||
|
"Spear Proficiency 2": 27,
|
||||||
|
"Cheaper Uppercut": 28,
|
||||||
|
"Aerodynamics": 29,
|
||||||
|
"Provoke": 30,
|
||||||
|
"Precise Strikes": 31,
|
||||||
|
"Air Shout": 32,
|
||||||
|
"Enraged Blow": 33,
|
||||||
|
"Flying Kick": 34,
|
||||||
|
"Stronger Mantle": 35,
|
||||||
|
"Manachism": 36,
|
||||||
|
"Boiling Blood": 37,
|
||||||
|
"Ragnarokkr": 38,
|
||||||
|
"Ambidextrous": 39,
|
||||||
|
"Burning Heart": 40,
|
||||||
|
"Stronger Bash": 41,
|
||||||
|
"Intoxicating Blood": 42,
|
||||||
|
"Comet": 43,
|
||||||
|
"Collide": 44,
|
||||||
|
"Rejuvenating Skin": 45,
|
||||||
|
"Uncontainable Corruption": 46,
|
||||||
|
"Radiant Devotee": 47,
|
||||||
|
"Whirlwind Strike": 48,
|
||||||
|
"Mythril Skin": 49,
|
||||||
|
"Armour Breaker": 50,
|
||||||
|
"Shield Strike": 51,
|
||||||
|
"Sparkling Hope": 52,
|
||||||
|
"Massive Bash": 53,
|
||||||
|
"Tempest": 54,
|
||||||
|
"Spirit of the Rabbit": 55,
|
||||||
|
"Massacre": 56,
|
||||||
|
"Axe Kick": 57,
|
||||||
|
"Radiance": 58,
|
||||||
|
"Cheaper Bash 2": 59,
|
||||||
|
"Cheaper War Scream": 60,
|
||||||
|
"Discombobulate": 61,
|
||||||
|
"Thunderclap": 62,
|
||||||
|
"Cyclone": 63,
|
||||||
|
"Second Chance": 64,
|
||||||
|
"Blood Pact": 65,
|
||||||
|
"Haemorrhage": 66,
|
||||||
|
"Brink of Madness": 67,
|
||||||
|
"Cheaper Uppercut 2": 68,
|
||||||
|
"Martyr": 69
|
||||||
|
}
|
||||||
|
}
|
4967
py_script/atree-parse.json
Normal file
|
@ -1,3 +1,5 @@
|
||||||
|
#parses all CI and creates a json file with all of them
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#looks like something that hpp does with curl
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
with open("ci.txt.2") as infile:
|
with open("ci.txt.2") as infile:
|
||||||
|
|
19
py_script/clean_json.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
'''
|
||||||
|
A generic file used for turning a json into a "clean" version of itself (human-friendly whitespace).
|
||||||
|
Clean files are useful for human reading and dev debugging.
|
||||||
|
|
||||||
|
|
||||||
|
Usage: python clean_json.py [infile rel path] [outfile rel path]
|
||||||
|
'''
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Pull data from wynn API.")
|
||||||
|
parser.add_argument('infile', help='input file to read data from')
|
||||||
|
parser.add_argument('outfile', help='output file to dump clean data into')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
infile, outfile = args.infile, args.outfile
|
||||||
|
json.dump(json.load(open(infile)), open(outfile, "w"), indent = 2)
|
|
@ -1,8 +1,18 @@
|
||||||
import sys
|
'''
|
||||||
|
A generic file used for turning a json into a compressed version of itself (minimal whitespaces).
|
||||||
|
Compressed files are useful for lowering the amount of data sent.
|
||||||
|
|
||||||
|
Usage: python compress_json.py [infile rel path] [outfile rel path]
|
||||||
|
'''
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
import json
|
import json
|
||||||
infile = sys.argv[1]
|
import argparse
|
||||||
outfile = sys.argv[2]
|
|
||||||
if len(sys.argv) > 3 and sys.argv[3] == "decompress":
|
parser = argparse.ArgumentParser(description="Pull data from wynn API.")
|
||||||
json.dump(json.load(open(infile)), open(outfile, "w"), indent=4)
|
parser.add_argument('infile', help='input file to read data from')
|
||||||
else:
|
parser.add_argument('outfile', help='output file to dump clean data into')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
infile, outfile = args.infile, args.outfile
|
||||||
json.dump(json.load(open(infile)), open(outfile, "w"))
|
json.dump(json.load(open(infile)), open(outfile, "w"))
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
response = requests.get("https://api.wynncraft.com/public_api.php?action=itemDB&category=all")
|
|
||||||
|
|
||||||
with open("dump.json", "w") as outfile:
|
|
||||||
outfile.write(json.dumps(response.json()))
|
|
||||||
|
|
||||||
arr = np.array([])
|
|
||||||
for i in range(4):
|
|
||||||
response = requests.get("https://api.wynncraft.com/v2/ingredient/search/tier/" + str(i))
|
|
||||||
arr = np.append(arr, np.array(response.json()['data']))
|
|
||||||
|
|
||||||
with open("../ingreds.json", "w") as outfile:
|
|
||||||
outfile.write(json.dumps(list(arr)))
|
|
||||||
|
|
||||||
with open("../ingreds_compress.json", "w") as outfile:
|
|
||||||
outfile.write(json.dumps(list(arr)))
|
|
||||||
|
|
||||||
with open("../ingreds_clean.json", "w") as outfile:
|
|
||||||
json.dump(list(arr), outfile, indent = 2) #needs further cleaning
|
|
65
py_script/get.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
"""
|
||||||
|
Used to GET data from the Wynncraft API. Has shorthand options and allows
|
||||||
|
for requesting from a specific url.
|
||||||
|
|
||||||
|
Usage: python get.py [url or command] [outfile rel path]
|
||||||
|
|
||||||
|
Relevant page: https://docs.wynncraft.com/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import requests
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Pull data from wynn API.")
|
||||||
|
parser.add_argument('target', help='an API page, or preset [items, ings, recipes, terrs, maploc]')
|
||||||
|
parser.add_argument('outfile', help='output file to dump results into')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
req, outfile = args.target, args.outfile
|
||||||
|
|
||||||
|
CURR_WYNN_VERS = 2.0
|
||||||
|
|
||||||
|
#default to empty file output
|
||||||
|
response = {}
|
||||||
|
|
||||||
|
if req.lower() == "items":
|
||||||
|
response = requests.get("https://api.wynncraft.com/public_api.php?action=itemDB&category=all")
|
||||||
|
elif req.lower() == "ings":
|
||||||
|
response = {"ings":[]}
|
||||||
|
for i in range(4):
|
||||||
|
response['ings'].extend(requests.get("https://api.wynncraft.com/v2/ingredient/search/tier/" + str(i)).json()['data'])
|
||||||
|
elif req.lower() == "recipes":
|
||||||
|
temp = requests.get("https://api.wynncraft.com/v2/recipe/list")
|
||||||
|
response = {"recipes":[]}
|
||||||
|
for i in range(len(temp['data'])):
|
||||||
|
response["recipes"].extend(requests.get("https://api.wynncraft.com/v2/recipe/get/" + temp['data'][i]).json()['data'])
|
||||||
|
print("" + str(i) + " / " + str(len(temp['data'])))
|
||||||
|
elif req.lower() == "terrs":
|
||||||
|
response = requests.get("https://api.wynncraft.com/public_api.php?action=territoryList").json()['territories']
|
||||||
|
delkeys = ["territory","acquired","attacker"]
|
||||||
|
for t in response:
|
||||||
|
for key in delkeys:
|
||||||
|
del response[t][key]
|
||||||
|
response[t]["neighbors"] = []
|
||||||
|
|
||||||
|
#Dependency on a third-party manually-collected data source. May not update in sync with API.
|
||||||
|
terr_data = requests.get("https://gist.githubusercontent.com/kristofbolyai/87ae828ecc740424c0f4b3749b2287ed/raw/0735f2e8bb2d2177ba0e7e96ade421621070a236/territories.json").json()
|
||||||
|
for t in data:
|
||||||
|
response[t]["neighbors"] = data[t]["Routes"]
|
||||||
|
response[t]["resources"] = data[t]["Resources"]
|
||||||
|
response[t]["storage"] = data[t]["Storage"]
|
||||||
|
response[t]["emeralds"] = data[t]["Emeralds"]
|
||||||
|
response[t]["doubleemeralds"] = data[t]["DoubleEmerald"]
|
||||||
|
response[t]["doubleresource"] = data[t]["DoubleResource"]
|
||||||
|
|
||||||
|
elif req.lower() == "maploc":
|
||||||
|
response = requests.get('https://api.wynncraft.com/public_api.php?action=mapLocations')
|
||||||
|
else:
|
||||||
|
response = requests.get(req)
|
||||||
|
|
||||||
|
response['version'] = CURR_WYNN_VERS
|
||||||
|
|
||||||
|
json.dump(response, open(outfile, "w+"))
|
|
@ -3646,5 +3646,11 @@
|
||||||
"Narcissist": 3648,
|
"Narcissist": 3648,
|
||||||
"Mask of the Spirits": 3649,
|
"Mask of the Spirits": 3649,
|
||||||
"Inhibitor": 3650,
|
"Inhibitor": 3650,
|
||||||
"Spear of Testiness": 3651
|
"Spear of Testiness": 3651,
|
||||||
|
"Blue Wynnter Sweater": 3648,
|
||||||
|
"Green Wynnter Sweater": 3649,
|
||||||
|
"Purple Wynnter Sweater": 3650,
|
||||||
|
"Red Wynnter Sweater": 3651,
|
||||||
|
"Snowtread Boots": 3652,
|
||||||
|
"White Wynnter Sweater": 3653
|
||||||
}
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
Used for grabbing image files at some point. Not used recently.
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
"""Json diff checker for manual testing."""
|
"""
|
||||||
|
Json diff checker for manual testing - mainly debug
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
"""
|
||||||
|
Used to parse a changelog at some point in the past. Could be used in the future.
|
||||||
|
|
||||||
|
Not a typically used file
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import difflib
|
import difflib
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
"""
|
||||||
|
Parses a set from a single file.
|
||||||
|
|
||||||
|
Usage: python parse_set_individual.py [infile]
|
||||||
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
set_infile = sys.argv[1]
|
set_infile = sys.argv[1]
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
with open("sets.txt", "r") as setsFile:
|
|
||||||
sets_split = (x.split("'", 2)[1][2:] for x in setsFile.read().split("a href=")[1:])
|
|
||||||
with open("sets_list.txt", "w") as outFile:
|
|
||||||
outFile.write("\n".join(sets_split))
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
Generates data for dps_vis
|
||||||
|
"""
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import json
|
import json
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
|
@ -1,15 +1,30 @@
|
||||||
|
"""
|
||||||
|
Used to process the raw data about ingredients pulled from the API.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
- python process_ings.py [infile] [outfile]
|
||||||
|
OR
|
||||||
|
- python process_ings.py [infile and outfile]
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
with open("../ingreds.json", "r") as infile:
|
|
||||||
ing_data = json.loads(infile.read())
|
|
||||||
ings = ing_data
|
|
||||||
#this data does not have request :)
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
if os.path.exists("../ing_map.json"):
|
import base64
|
||||||
with open("../ing_map.json","r") as ing_mapfile:
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Process raw pulled ingredient data.")
|
||||||
|
parser.add_argument('infile', help='input file to read data from')
|
||||||
|
parser.add_argument('outfile', help='output file to dump clean data into')
|
||||||
|
args = parser.parse_args()
|
||||||
|
infile, outfile = args.infile, args.outfile
|
||||||
|
|
||||||
|
with open(infile, "r") as in_file:
|
||||||
|
ing_data = json.loads(in_file.read())
|
||||||
|
ings = ing_data['ings']
|
||||||
|
|
||||||
|
if os.path.exists("ing_map.json"):
|
||||||
|
with open("ing_map.json","r") as ing_mapfile:
|
||||||
ing_map = json.load(ing_mapfile)
|
ing_map = json.load(ing_mapfile)
|
||||||
else:
|
else:
|
||||||
ing_map = {ing["name"]: i for i, ing in enumerate(ings)}
|
ing_map = {ing["name"]: i for i, ing in enumerate(ings)}
|
||||||
|
@ -146,8 +161,6 @@ ing_delete_keys = [
|
||||||
"skin"
|
"skin"
|
||||||
]
|
]
|
||||||
|
|
||||||
print("loaded all files.")
|
|
||||||
|
|
||||||
for ing in ings:
|
for ing in ings:
|
||||||
for key in ing_delete_keys:
|
for key in ing_delete_keys:
|
||||||
if key in ing:
|
if key in ing:
|
||||||
|
@ -202,13 +215,10 @@ for ing in ings:
|
||||||
print(f'New Ingred: {ing["name"]}')
|
print(f'New Ingred: {ing["name"]}')
|
||||||
ing["id"] = ing_map[ing["name"]]
|
ing["id"] = ing_map[ing["name"]]
|
||||||
|
|
||||||
|
#save ing ids
|
||||||
with open("../ingreds_clean.json", "w") as outfile:
|
with open("ing_map.json", "w+") as ing_mapfile:
|
||||||
json.dump(ing_data, outfile, indent = 2)
|
|
||||||
with open("../ingreds_compress.json", "w") as outfile:
|
|
||||||
json.dump(ing_data, outfile)
|
|
||||||
with open("../ing_map.json", "w") as ing_mapfile:
|
|
||||||
json.dump(ing_map, ing_mapfile, indent = 2)
|
json.dump(ing_map, ing_mapfile, indent = 2)
|
||||||
|
|
||||||
|
#save ings
|
||||||
print('All ing jsons updated.')
|
with open(outfile, "w+") as out_file:
|
||||||
|
json.dump(ing_data, out_file)
|
|
@ -1,44 +1,35 @@
|
||||||
"""
|
"""
|
||||||
|
Used to process the raw item data pulled from the API.
|
||||||
|
|
||||||
NOTE!!!!!!!
|
Usage:
|
||||||
|
- python process_items.py [infile] [outfile]
|
||||||
|
OR
|
||||||
|
- python process_items.py [infile and outfile]
|
||||||
|
|
||||||
DEMON TIDE 1.20 IS HARD CODED!
|
|
||||||
|
|
||||||
AMBIVALENCE IS REMOVED!
|
|
||||||
|
|
||||||
|
NOTE: id_map.json is due for change. Should be updated manually when Wynn2.0/corresponding WB version drops.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import argparse
|
||||||
|
|
||||||
with open("dump.json", "r") as infile:
|
parser = argparse.ArgumentParser(description="Process raw pulled item data.")
|
||||||
data = json.load(infile)
|
parser.add_argument('infile', help='input file to read data from')
|
||||||
|
parser.add_argument('outfile', help='output file to dump clean data into')
|
||||||
|
args = parser.parse_args()
|
||||||
|
infile, outfile = args.infile, args.outfile
|
||||||
|
|
||||||
|
with open(infile, "r") as in_file:
|
||||||
|
data = json.loads(in_file.read())
|
||||||
|
|
||||||
with open("updated.json", "r") as oldfile:
|
|
||||||
old_data = json.load(oldfile)
|
|
||||||
|
|
||||||
items = data["items"]
|
items = data["items"]
|
||||||
old_items = old_data["items"]
|
|
||||||
if "request" in data:
|
if "request" in data:
|
||||||
del data["request"]
|
del data["request"]
|
||||||
|
|
||||||
# import os
|
|
||||||
# sets = dict()
|
|
||||||
# for filename in os.listdir('sets'):
|
|
||||||
# if "json" not in filename:
|
|
||||||
# continue
|
|
||||||
# set_name = filename[1:].split(".")[0].replace("+", " ").replace("%27", "'")
|
|
||||||
# with open("sets/"+filename) as set_info:
|
|
||||||
# set_obj = json.load(set_info)
|
|
||||||
# for item in set_obj["items"]:
|
|
||||||
# item_set_map[item] = set_name
|
|
||||||
# sets[set_name] = set_obj
|
|
||||||
#
|
|
||||||
# data["sets"] = sets
|
|
||||||
data["sets"] = old_data["sets"]
|
|
||||||
item_set_map = dict()
|
|
||||||
for set_name, set_data in data["sets"].items():
|
|
||||||
for item_name in set_data["items"]:
|
|
||||||
item_set_map[item_name] = set_name
|
|
||||||
|
|
||||||
translate_mappings = {
|
translate_mappings = {
|
||||||
#"name": "name",
|
#"name": "name",
|
||||||
|
@ -141,7 +132,12 @@ delete_keys = [
|
||||||
#"material"
|
#"material"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
with open("../clean.json", "r") as oldfile:
|
||||||
|
old_data = json.load(oldfile)
|
||||||
|
old_items = old_data['items']
|
||||||
id_map = {item["name"]: item["id"] for item in old_items}
|
id_map = {item["name"]: item["id"] for item in old_items}
|
||||||
|
with open("id_map.json", "r") as idmap_file:
|
||||||
|
id_map = json.load(idmap_file)
|
||||||
used_ids = set([v for k, v in id_map.items()])
|
used_ids = set([v for k, v in id_map.items()])
|
||||||
max_id = 0
|
max_id = 0
|
||||||
|
|
||||||
|
@ -150,8 +146,8 @@ known_item_names = set()
|
||||||
for item in items:
|
for item in items:
|
||||||
known_item_names.add(item["name"])
|
known_item_names.add(item["name"])
|
||||||
|
|
||||||
old_items_map = dict()
|
|
||||||
remap_items = []
|
remap_items = []
|
||||||
|
old_items_map = dict()
|
||||||
for item in old_items:
|
for item in old_items:
|
||||||
if "remapID" in item:
|
if "remapID" in item:
|
||||||
remap_items.append(item)
|
remap_items.append(item)
|
||||||
|
@ -186,16 +182,18 @@ for item in items:
|
||||||
item_name = item["displayName"]
|
item_name = item["displayName"]
|
||||||
else:
|
else:
|
||||||
item_name = item["name"]
|
item_name = item["name"]
|
||||||
if item_name in item_set_map:
|
|
||||||
item["set"] = item_set_map[item_name]
|
|
||||||
if item["name"] in old_items_map:
|
|
||||||
old_item = old_items_map[item["name"]]
|
|
||||||
if "hideSet" in old_item:
|
|
||||||
item["hideSet"] = old_item["hideSet"]
|
|
||||||
|
|
||||||
items.extend(remap_items)
|
items.extend(remap_items)
|
||||||
|
|
||||||
with open("clean.json", "w") as outfile:
|
#write items back into data
|
||||||
json.dump(data, outfile, indent=2)
|
data["items"] = items
|
||||||
with open("compress.json", "w") as outfile:
|
|
||||||
json.dump(data, outfile)
|
#save id map
|
||||||
|
with open("id_map.json","w") as id_mapfile:
|
||||||
|
json.dump(id_map, id_mapfile, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
#write the data back to the outfile
|
||||||
|
with open(outfile, "w+") as out_file:
|
||||||
|
json.dump(data, out_file)
|
||||||
|
|
59
py_script/process_recipes.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
"""
|
||||||
|
Used to process the raw data about crafting recipes pulled from the API.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
- python process_recipes.py [infile] [outfile]
|
||||||
|
OR
|
||||||
|
- python process_recipes.py [infile and outfile]
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="Process raw pulled recipe data.")
|
||||||
|
parser.add_argument('infile', help='input file to read data from')
|
||||||
|
parser.add_argument('outfile', help='output file to dump clean data into')
|
||||||
|
args = parser.parse_args()
|
||||||
|
infile, outfile = args.infile, args.outfile
|
||||||
|
|
||||||
|
with open(infile, "r") as in_file:
|
||||||
|
recipe_data = json.loads(in_file.read())
|
||||||
|
recipes = recipe_data["recipes"]
|
||||||
|
|
||||||
|
if os.path.exists("recipe_map.json"):
|
||||||
|
with open("recipe_map.json","r") as recipe_mapfile:
|
||||||
|
recipe_map = json.load(recipe_mapfile)
|
||||||
|
else:
|
||||||
|
recipe_map = {recipe["name"]: i for i, recipe in enumerate(recipes)}
|
||||||
|
|
||||||
|
recipe_translate_mappings = {
|
||||||
|
"level" : "lvl",
|
||||||
|
"id" : "name",
|
||||||
|
}
|
||||||
|
recipe_delete_keys = [ #lol
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
for recipe in recipes:
|
||||||
|
for key in recipe_delete_keys:
|
||||||
|
if key in recipe:
|
||||||
|
del recipe[key]
|
||||||
|
for k, v in recipe_translate_mappings.items():
|
||||||
|
if k in recipe:
|
||||||
|
recipe[v] = recipe[k]
|
||||||
|
del recipe[k]
|
||||||
|
if not (recipe["name"] in recipe_map):
|
||||||
|
recipe_map[recipe["name"]] = len(recipe_map)
|
||||||
|
print(f'New Recipe: {recipe["name"]}')
|
||||||
|
recipe["id"] = recipe_map[recipe["name"]]
|
||||||
|
|
||||||
|
#save recipe id map
|
||||||
|
with open("recipe_map.json", "w") as recipe_mapfile:
|
||||||
|
json.dump(recipe_map, recipe_mapfile, indent = 2)
|
||||||
|
|
||||||
|
#save recipe data
|
||||||
|
with open(outfile, "w+") as out_file:
|
||||||
|
json.dump(recipe_data, out_file)
|