fixed merge conflict
1
.gitignore
vendored
|
@ -1,5 +1,6 @@
|
|||
*.swp
|
||||
*.bat
|
||||
*.json
|
||||
sets/
|
||||
|
||||
.idea/
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
</div>
|
||||
<div class = "col navbar navbar-fixed-bottom vh-75 min-vh-50 text-break ml-5" id = "bodydiv" style = "min-height: 75vh; display: flex; flex-direction: column;" >
|
||||
</div>
|
||||
|
||||
<audio id="bruh_sound_effect" src="../media/audio/bruh_sound_effect.mp3" preload="auto"></audio>
|
||||
</div>
|
||||
<!-- sidebar -->
|
||||
|
||||
|
|
1438
builder/doc.html
Normal file
|
@ -272,7 +272,7 @@
|
|||
<div class="col-auto order-xl-0 order-1">
|
||||
<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">
|
||||
<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 class="col-3">
|
||||
<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"/>
|
||||
</div>
|
||||
<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 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">
|
||||
<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 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>
|
||||
|
@ -404,6 +406,8 @@
|
|||
</div>
|
||||
<div class="col text-center">
|
||||
<div id="summary-box"></div>
|
||||
<div id="err-box"></div>
|
||||
<div id="stack-box"></div>
|
||||
<div id="str-warnings"></div>
|
||||
<div id="dex-warnings"></div>
|
||||
<div id="int-warnings"></div>
|
||||
|
@ -425,7 +429,7 @@
|
|||
</div>
|
||||
<div class = "col-3 py-2">
|
||||
<button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-atree" onclick = "toggle_tab('atree-dropdown'); toggleButton('toggle-atree')">
|
||||
Show Ability Tree
|
||||
Edit Abilities
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-3 py-2">
|
||||
|
@ -434,7 +438,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
|
@ -612,13 +616,16 @@
|
|||
</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="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 class="col" id="atree-active">
|
||||
Active:
|
||||
<div class="col mx-auto" style="height: 90vh; overflow-y: auto;" id="atree-rhs">
|
||||
<div class="col mx-auto" style="height: 2em; overflow-y: auto;" id="atree-header">
|
||||
</div>
|
||||
<div class="col mx-auto" style="overflow-y: auto;" id="atree-active">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -632,7 +639,7 @@
|
|||
Spell Damage %:
|
||||
</div>
|
||||
<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 class = "row" id = "sdPct-base">
|
||||
Original Value: 0
|
||||
|
@ -643,7 +650,7 @@
|
|||
Spell Damage Raw:
|
||||
</div>
|
||||
<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 class = "row" id = "sdRaw-base">
|
||||
Original Value: 0
|
||||
|
@ -654,7 +661,7 @@
|
|||
Melee Damage %:
|
||||
</div>
|
||||
<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 class = "row" id = "mdPct-base">
|
||||
Original Value: 0
|
||||
|
@ -665,7 +672,7 @@
|
|||
Melee Damage Raw:
|
||||
</div>
|
||||
<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 class = "row" id = "mdRaw-base">
|
||||
Original Value: 0
|
||||
|
@ -678,7 +685,7 @@
|
|||
Poison:
|
||||
</div>
|
||||
<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 class = "row" id = "poison-base">
|
||||
Original Value: 0
|
||||
|
@ -689,7 +696,7 @@
|
|||
Damage %:
|
||||
</div>
|
||||
<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 class = "row" id = "eDamPct-base">
|
||||
Original Value: 0
|
||||
|
@ -700,7 +707,7 @@
|
|||
Damage %:
|
||||
</div>
|
||||
<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 class = "row" id = "tDamPct-base">
|
||||
Original Value: 0
|
||||
|
@ -711,7 +718,7 @@
|
|||
Damage %:
|
||||
</div>
|
||||
<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 class = "row" id = "wDamPct-base">
|
||||
Original Value: 0
|
||||
|
@ -724,7 +731,7 @@
|
|||
Damage %:
|
||||
</div>
|
||||
<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 class = "row" id = "fDamPct-base">
|
||||
Original Value: 0
|
||||
|
@ -735,7 +742,7 @@
|
|||
Damage %:
|
||||
</div>
|
||||
<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 class = "row" id = "aDamPct-base">
|
||||
Original Value: 0
|
||||
|
@ -746,7 +753,7 @@
|
|||
+ Tier:
|
||||
</div>
|
||||
<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 class = "row" id = "atkTier-base">
|
||||
Original Value: 0
|
||||
|
@ -765,7 +772,7 @@
|
|||
Defense %:
|
||||
</div>
|
||||
<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 class = "row" id = "eDefPct-base">
|
||||
Original Value: 0
|
||||
|
@ -776,7 +783,7 @@
|
|||
Defense %:
|
||||
</div>
|
||||
<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 class = "row" id = "tDefPct-base">
|
||||
Original Value: 0
|
||||
|
@ -787,7 +794,7 @@
|
|||
Defense %:
|
||||
</div>
|
||||
<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 class = "row" id = "wDefPct-base">
|
||||
Original Value: 0
|
||||
|
@ -798,7 +805,7 @@
|
|||
Defense %:
|
||||
</div>
|
||||
<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 class = "row" id = "fDefPct-base">
|
||||
Original Value: 0
|
||||
|
@ -811,7 +818,7 @@
|
|||
Defense %:
|
||||
</div>
|
||||
<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 class = "row" id = "aDefPct-base">
|
||||
Original Value: 0
|
||||
|
@ -822,7 +829,7 @@
|
|||
Health Regen Raw:
|
||||
</div>
|
||||
<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 class = "row" id = "hprRaw-base">
|
||||
Original Value: 0
|
||||
|
@ -833,7 +840,7 @@
|
|||
Health Regen %:
|
||||
</div>
|
||||
<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 class = "row" id = "hprPct-base">
|
||||
Original Value: 0
|
||||
|
@ -844,7 +851,7 @@
|
|||
Health Bonus:
|
||||
</div>
|
||||
<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 class = "row" id = "hpBonus-base">
|
||||
Original Value: 0
|
||||
|
@ -860,7 +867,7 @@
|
|||
1st Spell Cost %:
|
||||
</div>
|
||||
<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 class = "row" id = "spPct1-base">
|
||||
Original Value: 0
|
||||
|
@ -871,7 +878,7 @@
|
|||
2nd Spell Cost %:
|
||||
</div>
|
||||
<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 class = "row" id = "spPct2-base">
|
||||
Original Value: 0
|
||||
|
@ -882,7 +889,7 @@
|
|||
3rd Spell Cost %:
|
||||
</div>
|
||||
<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 class = "row" id = "spPct3-base">
|
||||
Original Value: 0
|
||||
|
@ -893,7 +900,7 @@
|
|||
4th Spell Cost %:
|
||||
</div>
|
||||
<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 class = "row" id = "spPct4-base">
|
||||
Original Value: 0
|
||||
|
@ -906,7 +913,7 @@
|
|||
1st Spell Cost Raw:
|
||||
</div>
|
||||
<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 class = "row" id = "spRaw1-base">
|
||||
Original Value: 0
|
||||
|
@ -917,7 +924,7 @@
|
|||
2nd Spell Cost Raw:
|
||||
</div>
|
||||
<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 class = "row" id = "spRaw2-base">
|
||||
Original Value: 0
|
||||
|
@ -928,7 +935,7 @@
|
|||
3rd Spell Cost Raw:
|
||||
</div>
|
||||
<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 class = "row" id = "spRaw3-base">
|
||||
Original Value: 0
|
||||
|
@ -939,7 +946,7 @@
|
|||
4th Spell Cost Raw:
|
||||
</div>
|
||||
<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 class = "row" id = "spRaw4-base">
|
||||
Original Value: 0
|
||||
|
@ -957,27 +964,27 @@
|
|||
<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="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%)
|
||||
</button>
|
||||
</div>
|
||||
<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%)
|
||||
</button>
|
||||
</div>
|
||||
<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%)
|
||||
</button>
|
||||
</div>
|
||||
<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%)
|
||||
</button>
|
||||
</div>
|
||||
<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%)
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1014,27 +1021,27 @@
|
|||
<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="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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1044,7 +1051,7 @@
|
|||
Rage (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "e_slider" id = "str_boost_armor" name = "str-boost-armor" autocomplete = "off" min = '0' max = '400' value = '0' step = '1' onchange = "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;">
|
||||
<label id = "str_boost_armor_label" for="str-boost-armor">% Earth Dmg Boost: 0</label>
|
||||
</div>
|
||||
|
@ -1058,27 +1065,27 @@
|
|||
<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="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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1088,7 +1095,7 @@
|
|||
Kill Streak (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "t_slider" id = "dex_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "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;">
|
||||
<label id = "dex_boost_armor_label" for="dex-boost-armor">% Thunder Dmg Boost: 0</label>
|
||||
</div>
|
||||
|
@ -1102,27 +1109,27 @@
|
|||
<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="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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1132,7 +1139,7 @@
|
|||
Concentration (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "w_slider" id = "int_boost_armor" name = "dex-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "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;">
|
||||
<label id = "int_boost_armor_label" for="dex-boost-armor">% Water Dmg Boost: 0</label>
|
||||
</div>
|
||||
|
@ -1146,27 +1153,27 @@
|
|||
<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="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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1176,7 +1183,7 @@
|
|||
Endurance (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "f_slider" id = "def_boost_armor" name = "def-boost-armor" autocomplete = "off" min = '0' max = '200' value = '0' step = '1' onchange = "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;">
|
||||
<label id = "def_boost_armor_label" for="def-boost-armor">% Fire Dmg Boost: 0</label>
|
||||
</div>
|
||||
|
@ -1190,27 +1197,27 @@
|
|||
<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="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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
<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]
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1220,7 +1227,7 @@
|
|||
Dodge (Passive)
|
||||
</div>
|
||||
<div class="col">
|
||||
<input type = "range" class = "a_slider" id = "agi_boost_armor" name = "agi-boost-armor" autocomplete = "off" min = '0' max = '150' value = '0' step = '1' onchange = "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;">
|
||||
<label id = "agi_boost_armor_label" for="agi-boost-armor">% Air Dmg Boost: 0</label>
|
||||
</div>
|
||||
|
@ -1255,28 +1262,30 @@
|
|||
<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 = "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>
|
||||
<div class = "col">
|
||||
<div class = "col spell-display dark-5 rounded dark-shadow py-2 border border-dark" id="build-poison-stats">poison</div>
|
||||
</div>
|
||||
<div class = "col">
|
||||
<div 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 dark-5 rounded dark-shadow py-2" id = "spell0-info" style="display: none;">Spell 1</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 dark-5 rounded dark-shadow py-2" id = "spell1-info" style="display: none;">Spell 2</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 dark-5 rounded dark-shadow py-2" id = "spell2-info" style="display: none;">Spell 3</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 dark-5 rounded dark-shadow py-2" id = "spell3-info" style="display: none;">Spell 4</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class = "col">
|
||||
<div class = "spell-display dark-5 rounded dark-shadow py-2 border border-dark" id = "powder-special-stats"></div>
|
||||
</div>
|
||||
|
@ -1304,7 +1313,7 @@
|
|||
</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>
|
||||
<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 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>
|
||||
|
@ -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>
|
||||
<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>
|
||||
<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">
|
||||
<footer class="text-center">
|
||||
<div id="header2">
|
||||
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||
<p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||
</div>
|
||||
<div id="credits">
|
||||
|
@ -1396,19 +1405,18 @@
|
|||
<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/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/sq2icons.js"></script>
|
||||
<script type="text/javascript" src="../js/powders.js"></script>
|
||||
<script type="text/javascript" src="../js/skillpoints.js"></script>
|
||||
<script type="text/javascript" src="../js/damage_calc.js"></script>
|
||||
|
||||
<script type="text/javascript" src="../js/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/sq2display_constants.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_2.js"></script>
|
||||
|
@ -1417,12 +1425,16 @@
|
|||
<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/craft.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2build.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2builder.js"></script>
|
||||
<script type="text/javascript" src="../js/build.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/items.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>
|
||||
</html>
|
||||
|
|
|
@ -211,7 +211,7 @@
|
|||
</button>
|
||||
</div>
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
|
@ -282,7 +282,7 @@
|
|||
<div class="col dark-5 scaled-font">
|
||||
<footer class="text-center">
|
||||
<div id="header2">
|
||||
<p>Made by <b class = "hppeng">hppeng</b> and <b class = "ferricles">ferricles</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||
<p>Made by <b class = "hppeng">hppeng</b>, <b class = "ferricles">ferricles</b>, and <b>reschan</b> with <a href = "../atlas" target = "_blank" class = "atlas link">Atlas Inc</a> (JavaScript required to function, nothing works without js)</p>
|
||||
<p>Hard refresh the page (Ctrl+Shift+R on windows/chrome) if it isn't updating correctly.</p>
|
||||
</div>
|
||||
<div id="credits">
|
||||
|
@ -301,8 +301,6 @@
|
|||
<script type="text/javascript" src="../js/damage_calc.js"></script>
|
||||
<script type="text/javascript" src="../js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/display.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="../js/load_ing.js"></script>
|
||||
<script type="text/javascript" src="../js/load.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);
|
||||
}
|
||||
|
||||
/* atree hover */
|
||||
.atree-node {
|
||||
opacity: 75%;
|
||||
.rotate-flip {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.atree-node:hover {
|
||||
opacity: 100%;
|
||||
}
|
||||
|
||||
|
||||
.hide-scroll {
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
|
@ -490,3 +488,13 @@ a:hover {
|
|||
.hide-scroll::-webkit-scrollbar {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
|
||||
.atree-selected {
|
||||
outline: 5px solid rgba(95, 214, 223, 0.8);
|
||||
}
|
||||
|
||||
.atree-circle {
|
||||
border-radius:50%;
|
||||
-moz-border-radius:50%;
|
||||
-webkit-border-radius:50%;
|
||||
}
|
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
|
||||
</p>
|
||||
</div>
|
||||
<div class="row section" title="Wynnbuilder Internals (compute graph)">
|
||||
<p>
|
||||
This section is about how Wynnbuilder's main builder page processes user input and calculates results.
|
||||
Might be useful if you want to script wynnbuilder or extend it! Or for wynnbuilder developers (internal docs).
|
||||
</p>
|
||||
<div class="row section" title="Why?">
|
||||
<p>
|
||||
Modeling wynnbuilder's internal computations as a directed graph has a few advantages:
|
||||
</p>
|
||||
<ul class = "indent">
|
||||
<li>Each compute "node" is small(er); easier to debug.</li>
|
||||
<li>Information flow is specified explicitly (easier to debug).</li>
|
||||
<li>Easy to build caching for arbitrary computations (only calculate what u need)</li>
|
||||
<li>Stateless builder! Abstract the entire builder as a chain of function calls</li>
|
||||
<li>Makes for pretty pictures</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row section" title="TODO ComputeNode details">
|
||||
TODO
|
||||
</div>
|
||||
<p>
|
||||
An overview of wynnbuilder's internal structure can be seen <a href = "./compute_graph.svg" target = "_blank">here</a>. Arrows indicate flow of information.
|
||||
Colors correspond roughly as follows:
|
||||
</p>
|
||||
<img src="./builder_colorcode.png"/>
|
||||
<p>
|
||||
The overall logic flow is as follows:
|
||||
<ul class = "indent">
|
||||
<li>Item and Powder inputs are parsed. Powders are applied to items.</li>
|
||||
<li>Items and level information are combined to make a build.</li>
|
||||
<li>Information from input fields for skill points and edit IDs is collected into an ID bonuses table.</li>
|
||||
<li>Information about active powder specials, strength boosts, etc. are collected into their own ID tables.</li>
|
||||
<li>All of the above tables are merged with the build's stats table to produce the "Final" ID bonus table.</li>
|
||||
<li>Which spell variant (think: major id) to use for each of the 4 spells is computed based on the build.</li>
|
||||
<li>Spell damage is calculated, using the merged stat table, spell info, and weapon info.</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>
|
||||
Outputs are computed as follows:
|
||||
<ul class = "indent">
|
||||
<li>Input box highlights are computed from the items produced by item input box nodes.</li>
|
||||
<li>Item display is computed from item input boxes.</li>
|
||||
<li>Build hash/URL is computed from the build, and skillpoint assignment.</li>
|
||||
<li>Spell damage is displayed based on calculated spell damage results.</li>
|
||||
<li>Build stats are displayed by builder-stats-display (this same node also displays a bunch of stuff at the bottom of the screen...)</li>
|
||||
</ul>
|
||||
</p>
|
||||
<div class="row section" title="Gotchas">
|
||||
<p>
|
||||
The build sets default skillpoints and edited IDs automatically, whenever a build item/level is updated.
|
||||
This is done using "soft links" by two nodes shown in red (builder-skillpoint-setter and builder-id-setter).
|
||||
</p>
|
||||
<p>
|
||||
A soft link is where something goes and manually marks nodes dirty and calls their update methods.
|
||||
This is useful for these cases because the skillpoints and editable ID fields usually take their value from
|
||||
user input, but in some cases we want to programatically set them.
|
||||
</p>
|
||||
<p>
|
||||
For example another soft link (not shown) is used to implement the reset button.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="row section" title="Test Section">
|
||||
</div> -->
|
||||
|
||||
</div>
|
||||
<script type="text/javascript" src="../js/dev.js"></script>
|
||||
<script type="text/javascript" src="../js/sq2icons.js"></script>
|
||||
|
|
|
@ -62,8 +62,6 @@
|
|||
<script type="text/javascript" src="/js/load_ing.js"></script>
|
||||
<script type="text/javascript" src="/js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/display.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="/js/item.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -79,8 +79,6 @@
|
|||
<script type="text/javascript" src="/js/damage_calc.js"></script>
|
||||
<script type="text/javascript" src="/js/display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/display.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display_constants.js"></script>
|
||||
<script type="text/javascript" src="/js/sq2display.js"></script>
|
||||
<script type="text/javascript" src="/js/query_2.js"></script>
|
||||
<script type="text/javascript" src="/js/expr_parser.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 ];
|
||||
|
||||
if (Math.sqrt(((at2[1]+atlas2.vy) - (at1[1]+atlas1.vy))**2 + ((at2[0]+atlas2.vx) - (at1[0]+atlas1.vx))**2) < 2*r) {
|
||||
//Play bruh sound effect
|
||||
document.getElementById('bruh_sound_effect').play();
|
||||
document.getElementById('bruh_sound_effect').currentTime = 0;
|
||||
|
||||
if(Math.sqrt( (at2[1]-at1[1])**2 + (at2[0]-at1[0])**2 ) < 2*r ) {//check for collision
|
||||
//Move both away slightly - correct alg this time :)
|
||||
atlas1.style.left = parseFloat(atlas1.style.left.replace("px","")) + (at1[0]-center[0]) * 2 * r / Math.sqrt(dx**2 + dy**2) + "px";
|
||||
|
|
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).
|
||||
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
||||
* @param {Object[]} inputerrors : List of instances of error-like classes.
|
||||
*
|
||||
* @param {Object[]} tomes: List of tomes.
|
||||
* In order: 2x Weapon Mastery Tome, 4x Armor Mastery Tome, 1x Guild Tome.
|
||||
* 2x Slaying Mastery Tome, 2x Dungeoneering Mastery Tome, 2x Gathering Mastery Tome are in game, but do not have "useful" stats (those that affect damage calculations or building)
|
||||
*/
|
||||
constructor(level,equipment, powders, externalStats, inputerrors=[]){
|
||||
|
||||
let errors = inputerrors;
|
||||
//this contains the Craft objects, if there are any crafted items. this.boots, etc. will contain the statMap of the Craft (which is built to be an expandedItem).
|
||||
this.craftedItems = [];
|
||||
this.customItems = [];
|
||||
// NOTE: powders is just an array of arrays of powder IDs. Not powder objects.
|
||||
this.powders = powders;
|
||||
if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") {
|
||||
const helmet = itemMap.get(equipment[0]);
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
} else {
|
||||
try {
|
||||
let helmet = getCustomFromHash(equipment[0]) ? getCustomFromHash(equipment[0]) : (getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : undefined);
|
||||
if (helmet.statMap.get("type") !== "helmet") {
|
||||
throw new Error("Not a helmet");
|
||||
}
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.statMap.get("slots"));
|
||||
helmet.statMap.set("powders",this.powders[0].slice());
|
||||
this.helmet = helmet.statMap;
|
||||
applyArmorPowders(this.helmet, this.powders[0]);
|
||||
if (this.helmet.get("custom")) {
|
||||
this.customItems.push(helmet);
|
||||
} else if (this.helmet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(helmet);
|
||||
}
|
||||
|
||||
} catch (Error) {
|
||||
const helmet = itemMap.get("No Helmet");
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
errors.push(new ItemNotFound(equipment[0], "helmet", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[1]) && itemMap.get(equipment[1]).type === "chestplate") {
|
||||
const chestplate = itemMap.get(equipment[1]);
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
} else {
|
||||
try {
|
||||
let chestplate = getCustomFromHash(equipment[1]) ? getCustomFromHash(equipment[1]) : (getCraftFromHash(equipment[1]) ? getCraftFromHash(equipment[1]) : undefined);
|
||||
if (chestplate.statMap.get("type") !== "chestplate") {
|
||||
throw new Error("Not a chestplate");
|
||||
}
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.statMap.get("slots"));
|
||||
chestplate.statMap.set("powders",this.powders[1].slice());
|
||||
this.chestplate = chestplate.statMap;
|
||||
applyArmorPowders(this.chestplate, this.powders[1]);
|
||||
if (this.chestplate.get("custom")) {
|
||||
this.customItems.push(chestplate);
|
||||
} else if (this.chestplate.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(chestplate);
|
||||
}
|
||||
} catch (Error) {
|
||||
console.log(Error);
|
||||
const chestplate = itemMap.get("No Chestplate");
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
errors.push(new ItemNotFound(equipment[1], "chestplate", true));
|
||||
}
|
||||
}
|
||||
if (itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") {
|
||||
const leggings = itemMap.get(equipment[2]);
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
} else {
|
||||
try {
|
||||
let leggings = getCustomFromHash(equipment[2]) ? getCustomFromHash(equipment[2]) : (getCraftFromHash(equipment[2]) ? getCraftFromHash(equipment[2]) : undefined);
|
||||
if (leggings.statMap.get("type") !== "leggings") {
|
||||
throw new Error("Not a leggings");
|
||||
}
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.statMap.get("slots"));
|
||||
leggings.statMap.set("powders",this.powders[2].slice());
|
||||
this.leggings = leggings.statMap;
|
||||
applyArmorPowders(this.leggings, this.powders[2]);
|
||||
if (this.leggings.get("custom")) {
|
||||
this.customItems.push(leggings);
|
||||
} else if (this.leggings.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(leggings);
|
||||
}
|
||||
} catch (Error) {
|
||||
const leggings = itemMap.get("No Leggings");
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
errors.push(new ItemNotFound(equipment[2], "leggings", true));
|
||||
}
|
||||
}
|
||||
if (itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") {
|
||||
const boots = itemMap.get(equipment[3]);
|
||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
||||
this.boots = expandItem(boots, this.powders[3]);
|
||||
} else {
|
||||
try {
|
||||
let boots = getCustomFromHash(equipment[3]) ? getCustomFromHash(equipment[3]) : (getCraftFromHash(equipment[3]) ? getCraftFromHash(equipment[3]) : undefined);
|
||||
if (boots.statMap.get("type") !== "boots") {
|
||||
throw new Error("Not a boots");
|
||||
}
|
||||
this.powders[3] = this.powders[3].slice(0,boots.statMap.get("slots"));
|
||||
boots.statMap.set("powders",this.powders[3].slice());
|
||||
this.boots = boots.statMap;
|
||||
applyArmorPowders(this.boots, this.powders[3]);
|
||||
if (this.boots.get("custom")) {
|
||||
this.customItems.push(boots);
|
||||
} else if (this.boots.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(boots);
|
||||
}
|
||||
} catch (Error) {
|
||||
const boots = itemMap.get("No Boots");
|
||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
||||
this.boots = expandItem(boots, this.powders[3]);
|
||||
errors.push(new ItemNotFound(equipment[3], "boots", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[4]) && itemMap.get(equipment[4]).type === "ring") {
|
||||
const ring = itemMap.get(equipment[4]);
|
||||
this.ring1 = expandItem(ring, []);
|
||||
}else{
|
||||
try {
|
||||
let ring = getCustomFromHash(equipment[4]) ? getCustomFromHash(equipment[4]) : (getCraftFromHash(equipment[4]) ? getCraftFromHash(equipment[4]) : undefined);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring1 = ring.statMap;
|
||||
if (this.ring1.get("custom")) {
|
||||
this.customItems.push(ring);
|
||||
} else if (this.ring1.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(ring);
|
||||
}
|
||||
} catch (Error) {
|
||||
const ring = itemMap.get("No Ring 1");
|
||||
this.ring1 = expandItem(ring, []);
|
||||
errors.push(new ItemNotFound(equipment[4], "ring1", true, "ring"));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[5]) && itemMap.get(equipment[5]).type === "ring") {
|
||||
const ring = itemMap.get(equipment[5]);
|
||||
this.ring2 = expandItem(ring, []);
|
||||
}else{
|
||||
try {
|
||||
let ring = getCustomFromHash(equipment[5]) ? getCustomFromHash(equipment[5]) : (getCraftFromHash(equipment[5]) ? getCraftFromHash(equipment[5]) : undefined);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring2 = ring.statMap;
|
||||
if (this.ring2.get("custom")) {
|
||||
this.customItems.push(ring);
|
||||
} else if (this.ring2.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(ring);
|
||||
}
|
||||
} catch (Error) {
|
||||
const ring = itemMap.get("No Ring 2");
|
||||
this.ring2 = expandItem(ring, []);
|
||||
errors.push(new ItemNotFound(equipment[5], "ring2", true, "ring"));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[6]) && itemMap.get(equipment[6]).type === "bracelet") {
|
||||
const bracelet = itemMap.get(equipment[6]);
|
||||
this.bracelet = expandItem(bracelet, []);
|
||||
}else{
|
||||
try {
|
||||
let bracelet = getCustomFromHash(equipment[6]) ? getCustomFromHash(equipment[6]) : (getCraftFromHash(equipment[6]) ? getCraftFromHash(equipment[6]) : undefined);
|
||||
if (bracelet.statMap.get("type") !== "bracelet") {
|
||||
throw new Error("Not a bracelet");
|
||||
}
|
||||
this.bracelet = bracelet.statMap;
|
||||
if (this.bracelet.get("custom")) {
|
||||
this.customItems.push(bracelet);
|
||||
} else if (this.bracelet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(bracelet);
|
||||
}
|
||||
} catch (Error) {
|
||||
const bracelet = itemMap.get("No Bracelet");
|
||||
this.bracelet = expandItem(bracelet, []);
|
||||
errors.push(new ItemNotFound(equipment[6], "bracelet", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[7]) && itemMap.get(equipment[7]).type === "necklace") {
|
||||
const necklace = itemMap.get(equipment[7]);
|
||||
this.necklace = expandItem(necklace, []);
|
||||
}else{
|
||||
try {
|
||||
let necklace = getCustomFromHash(equipment[7]) ? getCustomFromHash(equipment[7]) : (getCraftFromHash(equipment[7]) ? getCraftFromHash(equipment[7]) : undefined);
|
||||
if (necklace.statMap.get("type") !== "necklace") {
|
||||
throw new Error("Not a necklace");
|
||||
}
|
||||
this.necklace = necklace.statMap;
|
||||
if (this.necklace.get("custom")) {
|
||||
this.customItems.push(necklace);
|
||||
} else if (this.necklace.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(necklace);
|
||||
}
|
||||
} catch (Error) {
|
||||
const necklace = itemMap.get("No Necklace");
|
||||
this.necklace = expandItem(necklace, []);
|
||||
errors.push(new ItemNotFound(equipment[7], "necklace", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[8]) && itemMap.get(equipment[8]).category === "weapon") {
|
||||
const weapon = itemMap.get(equipment[8]);
|
||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
||||
this.weapon = expandItem(weapon, this.powders[4]);
|
||||
if (equipment[8] !== "No Weapon") {
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
||||
} else {
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
||||
}
|
||||
}else{
|
||||
try {
|
||||
let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined);
|
||||
if (weapon.statMap.get("category") !== "weapon") {
|
||||
throw new Error("Not a weapon");
|
||||
}
|
||||
this.weapon = weapon.statMap;
|
||||
if (this.weapon.get("custom")) {
|
||||
this.customItems.push(weapon);
|
||||
} else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(weapon);
|
||||
}
|
||||
this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots"));
|
||||
this.weapon.set("powders",this.powders[4].slice());
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
||||
} catch (Error) {
|
||||
const weapon = itemMap.get("No Weapon");
|
||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
||||
this.weapon = expandItem(weapon, this.powders[4]);
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
||||
errors.push(new ItemNotFound(equipment[8], "weapon", true));
|
||||
}
|
||||
}
|
||||
//console.log(this.craftedItems)
|
||||
constructor(level, items, weapon){
|
||||
|
||||
if (level < 1) { //Should these be constants?
|
||||
this.level = 1;
|
||||
|
@ -348,11 +122,13 @@ class Build{
|
|||
document.getElementById("level-choice").value = this.level;
|
||||
|
||||
this.availableSkillpoints = levelToSkillPoints(this.level);
|
||||
this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ];
|
||||
this.equipment = items;
|
||||
this.weapon = weapon;
|
||||
this.items = this.equipment.concat([this.weapon]);
|
||||
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
||||
let result = calculate_skillpoints(this.equipment, this.weapon);
|
||||
console.log(result);
|
||||
|
||||
// calc skillpoints requires statmaps only
|
||||
let result = calculate_skillpoints(this.equipment.map((x) => x.statMap), this.weapon.statMap);
|
||||
this.equip_order = result[0];
|
||||
// How many skillpoints the player had to assign (5 number)
|
||||
this.base_skillpoints = result[1];
|
||||
|
@ -362,21 +138,7 @@ class Build{
|
|||
this.assigned_skillpoints = result[3];
|
||||
this.activeSetCounts = result[4];
|
||||
|
||||
// For strength boosts like warscream, vanish, etc.
|
||||
this.damageMultiplier = 1.0;
|
||||
this.defenseMultiplier = 1.0;
|
||||
|
||||
// For other external boosts ;-;
|
||||
this.externalStats = externalStats;
|
||||
|
||||
this.initBuildStats();
|
||||
|
||||
// Remove every error before adding specific ones
|
||||
for (let i of document.getElementsByClassName("error")) {
|
||||
i.textContent = "";
|
||||
}
|
||||
this.errors = errors;
|
||||
if (errors.length > 0) this.errored = true;
|
||||
}
|
||||
|
||||
/*Returns build in string format
|
||||
|
@ -385,138 +147,62 @@ class Build{
|
|||
return [this.equipment,this.weapon].flat();
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
|
||||
getSpellCost(spellIdx, cost) {
|
||||
return Math.max(1, this.getBaseSpellCost(spellIdx, cost));
|
||||
}
|
||||
|
||||
getBaseSpellCost(spellIdx, cost) {
|
||||
cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
|
||||
cost += this.statMap.get("spRaw"+spellIdx);
|
||||
return Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100));
|
||||
}
|
||||
|
||||
|
||||
/* Get melee stats for build.
|
||||
Returns an array in the order:
|
||||
*/
|
||||
getMeleeStats(){
|
||||
const stats = this.statMap;
|
||||
if (this.weapon.get("tier") === "Crafted") {
|
||||
stats.set("damageBases", [this.weapon.get("nDamBaseHigh"),this.weapon.get("eDamBaseHigh"),this.weapon.get("tDamBaseHigh"),this.weapon.get("wDamBaseHigh"),this.weapon.get("fDamBaseHigh"),this.weapon.get("aDamBaseHigh")]);
|
||||
}
|
||||
let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier");
|
||||
if(adjAtkSpd > 6){
|
||||
adjAtkSpd = 6;
|
||||
}else if(adjAtkSpd < 0){
|
||||
adjAtkSpd = 0;
|
||||
}
|
||||
|
||||
let damage_mult = 1;
|
||||
if (this.weapon.get("type") === "relik") {
|
||||
damage_mult = 0.99; // CURSE YOU WYNNCRAFT
|
||||
//One day we will create WynnWynn and no longer have shaman 99% melee injustice.
|
||||
//In all seriousness 99% is because wynn uses 0.33 to estimate dividing the damage by 3 to split damage between 3 beams.
|
||||
}
|
||||
// 0spellmult for melee damage.
|
||||
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct") + this.externalStats.get("mdPct"), 0, this.weapon, this.total_skillpoints, damage_mult * this.damageMultiplier, this.externalStats);
|
||||
|
||||
let dex = this.total_skillpoints[1];
|
||||
|
||||
let totalDamNorm = results[0];
|
||||
let totalDamCrit = results[1];
|
||||
totalDamNorm.push(1-skillPointsToPercentage(dex));
|
||||
totalDamCrit.push(skillPointsToPercentage(dex));
|
||||
let damages_results = results[2];
|
||||
|
||||
let singleHitTotal = ((totalDamNorm[0]+totalDamNorm[1])*(totalDamNorm[2])
|
||||
+(totalDamCrit[0]+totalDamCrit[1])*(totalDamCrit[2]))/2;
|
||||
|
||||
//Now do math
|
||||
let normDPS = (totalDamNorm[0]+totalDamNorm[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
||||
let critDPS = (totalDamCrit[0]+totalDamCrit[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
||||
let avgDPS = (normDPS * (1 - skillPointsToPercentage(dex))) + (critDPS * (skillPointsToPercentage(dex)));
|
||||
//[[n n n n] [e e e e] [t t t t] [w w w w] [f f f f] [a a a a] [lowtotal hightotal normalChance] [critlowtotal crithightotal critChance] normalDPS critCPS averageDPS adjAttackSpeed, singleHit]
|
||||
return damages_results.concat([totalDamNorm,totalDamCrit,normDPS,critDPS,avgDPS,adjAtkSpd, singleHitTotal]).concat(results[3]);
|
||||
}
|
||||
|
||||
/*
|
||||
Get all defensive stats for this build.
|
||||
*/
|
||||
getDefenseStats(){
|
||||
const stats = this.statMap;
|
||||
let defenseStats = [];
|
||||
let def_pct = skillPointsToPercentage(this.total_skillpoints[3]);
|
||||
let agi_pct = skillPointsToPercentage(this.total_skillpoints[4]);
|
||||
//total hp
|
||||
let totalHp = stats.get("hp") + stats.get("hpBonus");
|
||||
if (totalHp < 5) totalHp = 5;
|
||||
defenseStats.push(totalHp);
|
||||
//EHP
|
||||
let ehp = [totalHp, totalHp];
|
||||
let defMult = classDefenseMultipliers.get(this.weapon.get("type"));
|
||||
ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
defenseStats.push(ehp);
|
||||
//HPR
|
||||
let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.);
|
||||
defenseStats.push(totalHpr);
|
||||
//EHPR
|
||||
let ehpr = [totalHpr, totalHpr];
|
||||
ehpr[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
ehpr[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
defenseStats.push(ehpr);
|
||||
//skp stats
|
||||
defenseStats.push([ (1 - ((1-def_pct) * (2 - this.defenseMultiplier)))*100, agi_pct*100]);
|
||||
//eledefs - TODO POWDERS
|
||||
let eledefs = [0, 0, 0, 0, 0];
|
||||
for(const i in skp_elements){ //kinda jank but ok
|
||||
eledefs[i] = rawToPct(stats.get(skp_elements[i] + "Def"), stats.get(skp_elements[i] + "DefPct")/100.);
|
||||
}
|
||||
defenseStats.push(eledefs);
|
||||
|
||||
//[total hp, [ehp w/ agi, ehp w/o agi], total hpr, [ehpr w/ agi, ehpr w/o agi], [def%, agi%], [edef,tdef,wdef,fdef,adef]]
|
||||
return defenseStats;
|
||||
}
|
||||
|
||||
/* Get all stats for this build. Stores in this.statMap.
|
||||
@pre The build itself should be valid. No checking of validity of pieces is done here.
|
||||
*/
|
||||
initBuildStats(){
|
||||
|
||||
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi"];
|
||||
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef", "str", "dex", "int", "def", "agi", "damMobs", "defMobs"];
|
||||
|
||||
let must_ids = [
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw","eDamPct","eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw","tDamPct","tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw","wDamPct","wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw","fDamPct","fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw","aDamPct","aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
"mdPct","mdRaw","sdPct","sdRaw","damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct","rSdRaw","rDamPct","rDamRaw","rDamAddMin","rDamAddMax" // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
]
|
||||
|
||||
//Create a map of this build's stats
|
||||
let statMap = new Map();
|
||||
statMap.set("defMultiplier", 1);
|
||||
|
||||
for (const staticID of staticIDs) {
|
||||
statMap.set(staticID, 0);
|
||||
}
|
||||
for (const staticID of must_ids) {
|
||||
statMap.set(staticID, 0);
|
||||
}
|
||||
statMap.set("hp", levelToHPBase(this.level));
|
||||
|
||||
let major_ids = new Set();
|
||||
for (const item of this.items){
|
||||
for (let [id, value] of item.get("maxRolls")) {
|
||||
const item_stats = item.statMap;
|
||||
for (let [id, value] of item_stats.get("maxRolls")) {
|
||||
if (staticIDs.includes(id)) {
|
||||
continue;
|
||||
}
|
||||
statMap.set(id,(statMap.get(id) || 0)+value);
|
||||
}
|
||||
for (const staticID of staticIDs) {
|
||||
if (item.get(staticID)) {
|
||||
statMap.set(staticID, statMap.get(staticID) + item.get(staticID));
|
||||
if (item_stats.get(staticID)) {
|
||||
statMap.set(staticID, statMap.get(staticID) + item_stats.get(staticID));
|
||||
}
|
||||
}
|
||||
if (item.get("majorIds")) {
|
||||
for (const major_id of item.get("majorIds")) {
|
||||
if (item_stats.get("majorIds")) {
|
||||
for (const major_id of item_stats.get("majorIds")) {
|
||||
major_ids.add(major_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
statMap.set('damageMultiplier', 1 + (statMap.get('damMobs') / 100));
|
||||
statMap.set('defMultiplier', 1 - (statMap.get('defMobs') / 100));
|
||||
statMap.set("activeMajorIDs", major_ids);
|
||||
for (const [setName, count] of this.activeSetCounts) {
|
||||
const bonus = sets[setName].bonuses[count-1];
|
||||
const bonus = sets.get(setName).bonuses[count-1];
|
||||
for (const id in bonus) {
|
||||
if (skp_order.includes(id)) {
|
||||
// pass. Don't include skillpoints in ids
|
||||
|
@ -529,27 +215,8 @@ class Build{
|
|||
statMap.set("poisonPct", 100);
|
||||
|
||||
// The stuff relevant for damage calculation!!! @ferricles
|
||||
statMap.set("atkSpd", this.weapon.get("atkSpd"));
|
||||
statMap.set("atkSpd", this.weapon.statMap.get("atkSpd"));
|
||||
|
||||
for (const x of skp_elements) {
|
||||
this.externalStats.set(x + "DamPct", 0);
|
||||
}
|
||||
this.externalStats.set("mdPct", 0);
|
||||
this.externalStats.set("sdPct", 0);
|
||||
this.externalStats.set("damageBonus", [0, 0, 0, 0, 0]);
|
||||
this.externalStats.set("defBonus",[0, 0, 0, 0, 0]);
|
||||
this.externalStats.set("poisonPct", 0);
|
||||
this.statMap = statMap;
|
||||
|
||||
this.aggregateStats();
|
||||
}
|
||||
|
||||
aggregateStats() {
|
||||
let statMap = this.statMap;
|
||||
statMap.set("damageRaw", [this.weapon.get("nDam"), this.weapon.get("eDam"), this.weapon.get("tDam"), this.weapon.get("wDam"), this.weapon.get("fDam"), this.weapon.get("aDam")]);
|
||||
statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
|
||||
statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]);
|
||||
statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]);
|
||||
statMap.set("defMult", classDefenseMultipliers.get(this.weapon.get("type")));
|
||||
}
|
||||
}
|
||||
|
|
550
js/build2.js
|
@ -1,550 +0,0 @@
|
|||
|
||||
|
||||
const classDefenseMultipliers = new Map([ ["relik",0.50], ["bow",0.60], ["wand", 0.80], ["dagger", 1.0], ["spear",1.20] ]);
|
||||
|
||||
/**
|
||||
* @description Error to catch items that don't exist.
|
||||
* @module ItemNotFound
|
||||
*/
|
||||
class ItemNotFound {
|
||||
/**
|
||||
* @class
|
||||
* @param {String} item the item name entered
|
||||
* @param {String} type the type of item
|
||||
* @param {Boolean} genElement whether to generate an element from inputs
|
||||
* @param {String} override override for item type
|
||||
*/
|
||||
constructor(item, type, genElement, override) {
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.message = `Cannot find ${override||type} named ${item}`;
|
||||
if (genElement)
|
||||
/**
|
||||
* @public
|
||||
* @type {Element}
|
||||
*/
|
||||
this.element = document.getElementById(`${type}-choice`).parentElement.querySelectorAll("p.error")[0];
|
||||
else
|
||||
this.element = document.createElement("div");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Error to catch incorrect input.
|
||||
* @module IncorrectInput
|
||||
*/
|
||||
class IncorrectInput {
|
||||
/**
|
||||
* @class
|
||||
* @param {String} input the inputted text
|
||||
* @param {String} format the correct format
|
||||
* @param {String} sibling the id of the error node's sibling
|
||||
*/
|
||||
constructor(input, format, sibling) {
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.message = `${input} is incorrect. Example: ${format}`;
|
||||
/**
|
||||
* @public
|
||||
* @type {String}
|
||||
*/
|
||||
this.id = sibling;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Error that inputs an array of items to generate errors of.
|
||||
* @module ListError
|
||||
* @extends Error
|
||||
*/
|
||||
class ListError extends Error {
|
||||
/**
|
||||
* @class
|
||||
* @param {Array} errors array of errors
|
||||
*/
|
||||
constructor(errors) {
|
||||
let ret = [];
|
||||
if (typeof errors[0] == "string") {
|
||||
super(errors[0]);
|
||||
} else {
|
||||
super(errors[0].message);
|
||||
}
|
||||
for (let i of errors) {
|
||||
if (typeof i == "string") {
|
||||
ret.push(new Error(i));
|
||||
} else {
|
||||
ret.push(i);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
* @type {Object[]}
|
||||
*/
|
||||
this.errors = ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*Class that represents a wynn player's build.
|
||||
*/
|
||||
class Build{
|
||||
|
||||
/**
|
||||
* @description Construct a build.
|
||||
* @param {Number} level : Level of the player.
|
||||
* @param {String[]} equipment : List of equipment names that make up the build.
|
||||
* In order: boots, Chestplate, Leggings, Boots, Ring1, Ring2, Brace, Neck, Weapon.
|
||||
* @param {Number[]} powders : Powder application. List of lists of integers (powder IDs).
|
||||
* In order: boots, Chestplate, Leggings, Boots, Weapon.
|
||||
* @param {Object[]} inputerrors : List of instances of error-like classes.
|
||||
*/
|
||||
constructor(level,equipment, powders, externalStats, inputerrors=[]){
|
||||
|
||||
let errors = inputerrors;
|
||||
//this contains the Craft objects, if there are any crafted items. this.boots, etc. will contain the statMap of the Craft (which is built to be an expandedItem).
|
||||
this.craftedItems = [];
|
||||
this.customItems = [];
|
||||
// NOTE: powders is just an array of arrays of powder IDs. Not powder objects.
|
||||
this.powders = powders;
|
||||
if(itemMap.get(equipment[0]) && itemMap.get(equipment[0]).type === "helmet") {
|
||||
const helmet = itemMap.get(equipment[0]);
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
} else {
|
||||
try {
|
||||
//let boots = getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : (getCustomFromHash(equipment[0])? getCustomFromHash(equipment[0]) : undefined);
|
||||
let helmet = getCustomFromHash(equipment[0]) ? getCustomFromHash(equipment[0]) : (getCraftFromHash(equipment[0]) ? getCraftFromHash(equipment[0]) : undefined);
|
||||
if (helmet.statMap.get("type") !== "helmet") {
|
||||
throw new Error("Not a helmet");
|
||||
}
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.statMap.get("slots"));
|
||||
helmet.statMap.set("powders",this.powders[0].slice());
|
||||
helmet.applyPowders();
|
||||
this.helmet = helmet.statMap;
|
||||
if (this.helmet.get("custom")) {
|
||||
this.customItems.push(helmet);
|
||||
} else if (this.helmet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(helmet);
|
||||
}
|
||||
|
||||
} catch (Error) {
|
||||
//console.log(Error); //fix
|
||||
const helmet = itemMap.get("No Helmet");
|
||||
this.powders[0] = this.powders[0].slice(0,helmet.slots);
|
||||
this.helmet = expandItem(helmet, this.powders[0]);
|
||||
errors.push(new ItemNotFound(equipment[0], "helmet", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[1]) && itemMap.get(equipment[1]).type === "chestplate") {
|
||||
const chestplate = itemMap.get(equipment[1]);
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
} else {
|
||||
try {
|
||||
let chestplate = getCustomFromHash(equipment[1]) ? getCustomFromHash(equipment[1]) : (getCraftFromHash(equipment[1]) ? getCraftFromHash(equipment[1]) : undefined);
|
||||
if (chestplate.statMap.get("type") !== "chestplate") {
|
||||
throw new Error("Not a chestplate");
|
||||
}
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.statMap.get("slots"));
|
||||
chestplate.statMap.set("powders",this.powders[1].slice());
|
||||
chestplate.applyPowders();
|
||||
this.chestplate = chestplate.statMap;
|
||||
if (this.chestplate.get("custom")) {
|
||||
this.customItems.push(chestplate);
|
||||
} else if (this.chestplate.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(chestplate);
|
||||
}
|
||||
} catch (Error) {
|
||||
const chestplate = itemMap.get("No Chestplate");
|
||||
this.powders[1] = this.powders[1].slice(0,chestplate.slots);
|
||||
this.chestplate = expandItem(chestplate, this.powders[1]);
|
||||
errors.push(new ItemNotFound(equipment[1], "chestplate", true));
|
||||
}
|
||||
}
|
||||
if (itemMap.get(equipment[2]) && itemMap.get(equipment[2]).type === "leggings") {
|
||||
const leggings = itemMap.get(equipment[2]);
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
} else {
|
||||
try {
|
||||
let leggings = getCustomFromHash(equipment[2]) ? getCustomFromHash(equipment[2]) : (getCraftFromHash(equipment[2]) ? getCraftFromHash(equipment[2]) : undefined);
|
||||
if (leggings.statMap.get("type") !== "leggings") {
|
||||
throw new Error("Not a leggings");
|
||||
}
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.statMap.get("slots"));
|
||||
leggings.statMap.set("powders",this.powders[2].slice());
|
||||
leggings.applyPowders();
|
||||
this.leggings = leggings.statMap;
|
||||
if (this.leggings.get("custom")) {
|
||||
this.customItems.push(leggings);
|
||||
} else if (this.leggings.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(leggings);
|
||||
}
|
||||
} catch (Error) {
|
||||
const leggings = itemMap.get("No Leggings");
|
||||
this.powders[2] = this.powders[2].slice(0,leggings.slots);
|
||||
this.leggings = expandItem(leggings, this.powders[2]);
|
||||
errors.push(new ItemNotFound(equipment[2], "leggings", true));
|
||||
}
|
||||
}
|
||||
if (itemMap.get(equipment[3]) && itemMap.get(equipment[3]).type === "boots") {
|
||||
const boots = itemMap.get(equipment[3]);
|
||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
||||
this.boots = expandItem(boots, this.powders[3]);
|
||||
} else {
|
||||
try {
|
||||
let boots = getCustomFromHash(equipment[3]) ? getCustomFromHash(equipment[3]) : (getCraftFromHash(equipment[3]) ? getCraftFromHash(equipment[3]) : undefined);
|
||||
if (boots.statMap.get("type") !== "boots") {
|
||||
throw new Error("Not a boots");
|
||||
}
|
||||
this.powders[3] = this.powders[3].slice(0,boots.statMap.get("slots"));
|
||||
boots.statMap.set("powders",this.powders[3].slice());
|
||||
boots.applyPowders();
|
||||
this.boots = boots.statMap;
|
||||
console.log(boots);
|
||||
if (this.boots.get("custom")) {
|
||||
this.customItems.push(boots);
|
||||
} else if (this.boots.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(boots);
|
||||
}
|
||||
} catch (Error) {
|
||||
const boots = itemMap.get("No Boots");
|
||||
this.powders[3] = this.powders[3].slice(0,boots.slots);
|
||||
this.boots = expandItem(boots, this.powders[3]);
|
||||
errors.push(new ItemNotFound(equipment[3], "boots", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[4]) && itemMap.get(equipment[4]).type === "ring") {
|
||||
const ring = itemMap.get(equipment[4]);
|
||||
this.ring1 = expandItem(ring, []);
|
||||
}else{
|
||||
try {
|
||||
let ring = getCustomFromHash(equipment[4]) ? getCustomFromHash(equipment[4]) : (getCraftFromHash(equipment[4]) ? getCraftFromHash(equipment[4]) : undefined);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring1 = ring.statMap;
|
||||
if (this.ring1.get("custom")) {
|
||||
this.customItems.push(ring);
|
||||
} else if (this.ring1.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(ring);
|
||||
}
|
||||
} catch (Error) {
|
||||
const ring = itemMap.get("No Ring 1");
|
||||
this.ring1 = expandItem(ring, []);
|
||||
errors.push(new ItemNotFound(equipment[4], "ring1", true, "ring"));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[5]) && itemMap.get(equipment[5]).type === "ring") {
|
||||
const ring = itemMap.get(equipment[5]);
|
||||
this.ring2 = expandItem(ring, []);
|
||||
}else{
|
||||
try {
|
||||
let ring = getCustomFromHash(equipment[5]) ? getCustomFromHash(equipment[5]) : (getCraftFromHash(equipment[5]) ? getCraftFromHash(equipment[5]) : undefined);
|
||||
if (ring.statMap.get("type") !== "ring") {
|
||||
throw new Error("Not a ring");
|
||||
}
|
||||
this.ring2 = ring.statMap;
|
||||
if (this.ring2.get("custom")) {
|
||||
this.customItems.push(ring);
|
||||
} else if (this.ring2.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(ring);
|
||||
}
|
||||
} catch (Error) {
|
||||
const ring = itemMap.get("No Ring 2");
|
||||
this.ring2 = expandItem(ring, []);
|
||||
errors.push(new ItemNotFound(equipment[5], "ring2", true, "ring"));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[6]) && itemMap.get(equipment[6]).type === "bracelet") {
|
||||
const bracelet = itemMap.get(equipment[6]);
|
||||
this.bracelet = expandItem(bracelet, []);
|
||||
}else{
|
||||
try {
|
||||
let bracelet = getCustomFromHash(equipment[6]) ? getCustomFromHash(equipment[6]) : (getCraftFromHash(equipment[6]) ? getCraftFromHash(equipment[6]) : undefined);
|
||||
if (bracelet.statMap.get("type") !== "bracelet") {
|
||||
throw new Error("Not a bracelet");
|
||||
}
|
||||
this.bracelet = bracelet.statMap;
|
||||
if (this.bracelet.get("custom")) {
|
||||
this.customItems.push(bracelet);
|
||||
} else if (this.bracelet.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(bracelet);
|
||||
}
|
||||
} catch (Error) {
|
||||
const bracelet = itemMap.get("No Bracelet");
|
||||
this.bracelet = expandItem(bracelet, []);
|
||||
errors.push(new ItemNotFound(equipment[6], "bracelet", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[7]) && itemMap.get(equipment[7]).type === "necklace") {
|
||||
const necklace = itemMap.get(equipment[7]);
|
||||
this.necklace = expandItem(necklace, []);
|
||||
}else{
|
||||
try {
|
||||
let necklace = getCustomFromHash(equipment[7]) ? getCustomFromHash(equipment[7]) : (getCraftFromHash(equipment[7]) ? getCraftFromHash(equipment[7]) : undefined);
|
||||
if (necklace.statMap.get("type") !== "necklace") {
|
||||
throw new Error("Not a necklace");
|
||||
}
|
||||
this.necklace = necklace.statMap;
|
||||
if (this.necklace.get("custom")) {
|
||||
this.customItems.push(necklace);
|
||||
} else if (this.necklace.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(necklace);
|
||||
}
|
||||
} catch (Error) {
|
||||
const necklace = itemMap.get("No Necklace");
|
||||
this.necklace = expandItem(necklace, []);
|
||||
errors.push(new ItemNotFound(equipment[7], "necklace", true));
|
||||
}
|
||||
}
|
||||
if(itemMap.get(equipment[8]) && itemMap.get(equipment[8]).category === "weapon") {
|
||||
const weapon = itemMap.get(equipment[8]);
|
||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
||||
this.weapon = expandItem(weapon, this.powders[4]);
|
||||
if (equipment[8] !== "No Weapon") {
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
||||
} else {
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
||||
}
|
||||
}else{
|
||||
try {
|
||||
let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined);
|
||||
if (weapon.statMap.get("category") !== "weapon") {
|
||||
throw new Error("Not a weapon");
|
||||
}
|
||||
this.weapon = weapon.statMap;
|
||||
if (this.weapon.get("custom")) {
|
||||
this.customItems.push(weapon);
|
||||
} else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority.
|
||||
this.craftedItems.push(weapon);
|
||||
}
|
||||
this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots"));
|
||||
this.weapon.set("powders",this.powders[4].slice());
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "grid";
|
||||
} catch (Error) {
|
||||
const weapon = itemMap.get("No Weapon");
|
||||
this.powders[4] = this.powders[4].slice(0,weapon.slots);
|
||||
this.weapon = expandItem(weapon, this.powders[4]);
|
||||
document.getElementsByClassName("powder-specials")[0].style.display = "none";
|
||||
errors.push(new ItemNotFound(equipment[8], "weapon", true));
|
||||
}
|
||||
}
|
||||
//console.log(this.craftedItems)
|
||||
|
||||
if (level < 1) { //Should these be constants?
|
||||
this.level = 1;
|
||||
} else if (level > 106) {
|
||||
this.level = 106;
|
||||
} else if (level <= 106 && level >= 1) {
|
||||
this.level = level;
|
||||
} else if (typeof level === "string") {
|
||||
this.level = level;
|
||||
errors.push(new IncorrectInput(level, "a number", "level-choice"));
|
||||
} else {
|
||||
errors.push("Level is not a string or number.");
|
||||
}
|
||||
document.getElementById("level-choice").value = this.level;
|
||||
|
||||
this.availableSkillpoints = levelToSkillPoints(this.level);
|
||||
this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ];
|
||||
this.items = this.equipment.concat([this.weapon]);
|
||||
// return [equip_order, best_skillpoints, final_skillpoints, best_total];
|
||||
let result = calculate_skillpoints(this.equipment, this.weapon);
|
||||
console.log(result);
|
||||
this.equip_order = result[0];
|
||||
this.base_skillpoints = result[1];
|
||||
this.total_skillpoints = result[2];
|
||||
this.assigned_skillpoints = result[3];
|
||||
this.activeSetCounts = result[4];
|
||||
|
||||
// For strength boosts like warscream, vanish, etc.
|
||||
this.damageMultiplier = 1.0;
|
||||
this.defenseMultiplier = 1.0;
|
||||
|
||||
// For other external boosts ;-;
|
||||
this.externalStats = externalStats;
|
||||
|
||||
this.initBuildStats();
|
||||
|
||||
// Remove every error before adding specific ones
|
||||
for (let i of document.getElementsByClassName("error")) {
|
||||
i.textContent = "";
|
||||
}
|
||||
this.errors = errors;
|
||||
if (errors.length > 0) this.errored = true;
|
||||
}
|
||||
|
||||
/*Returns build in string format
|
||||
*/
|
||||
toString(){
|
||||
return [this.equipment,this.weapon].flat();
|
||||
}
|
||||
|
||||
/* Getters */
|
||||
|
||||
/* Get total health for build.
|
||||
*/
|
||||
|
||||
getSpellCost(spellIdx, cost) {
|
||||
cost = Math.ceil(cost * (1 - skillPointsToPercentage(this.total_skillpoints[2])));
|
||||
cost = Math.max(0, Math.floor(cost * (1 + this.statMap.get("spPct"+spellIdx) / 100)));
|
||||
return Math.max(1, cost + this.statMap.get("spRaw"+spellIdx));
|
||||
}
|
||||
|
||||
|
||||
/* Get melee stats for build.
|
||||
Returns an array in the order:
|
||||
*/
|
||||
getMeleeStats(){
|
||||
const stats = this.statMap;
|
||||
if (this.weapon.get("tier") === "Crafted") {
|
||||
stats.set("damageBases", [this.weapon.get("nDamBaseHigh"),this.weapon.get("eDamBaseHigh"),this.weapon.get("tDamBaseHigh"),this.weapon.get("wDamBaseHigh"),this.weapon.get("fDamBaseHigh"),this.weapon.get("aDamBaseHigh")]);
|
||||
}
|
||||
let adjAtkSpd = attackSpeeds.indexOf(stats.get("atkSpd")) + stats.get("atkTier");
|
||||
if(adjAtkSpd > 6){
|
||||
adjAtkSpd = 6;
|
||||
}else if(adjAtkSpd < 0){
|
||||
adjAtkSpd = 0;
|
||||
}
|
||||
|
||||
let damage_mult = 1;
|
||||
if (this.weapon.get("type") === "relik") {
|
||||
damage_mult = 0.99; // CURSE YOU WYNNCRAFT
|
||||
//One day we will create WynnWynn and no longer have shaman 99% melee injustice.
|
||||
//In all seriousness 99% is because wynn uses 0.33 to estimate dividing the damage by 3 to split damage between 3 beams.
|
||||
}
|
||||
// 0spellmult for melee damage.
|
||||
let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], stats.get("mdRaw"), stats.get("mdPct") + this.externalStats.get("mdPct"), 0, this.weapon, this.total_skillpoints, damage_mult * this.damageMultiplier, this.externalStats);
|
||||
|
||||
let dex = this.total_skillpoints[1];
|
||||
|
||||
let totalDamNorm = results[0];
|
||||
let totalDamCrit = results[1];
|
||||
totalDamNorm.push(1-skillPointsToPercentage(dex));
|
||||
totalDamCrit.push(skillPointsToPercentage(dex));
|
||||
let damages_results = results[2];
|
||||
|
||||
let singleHitTotal = ((totalDamNorm[0]+totalDamNorm[1])*(totalDamNorm[2])
|
||||
+(totalDamCrit[0]+totalDamCrit[1])*(totalDamCrit[2]))/2;
|
||||
|
||||
//Now do math
|
||||
let normDPS = (totalDamNorm[0]+totalDamNorm[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
||||
let critDPS = (totalDamCrit[0]+totalDamCrit[1])/2 * baseDamageMultiplier[adjAtkSpd];
|
||||
let avgDPS = (normDPS * (1 - skillPointsToPercentage(dex))) + (critDPS * (skillPointsToPercentage(dex)));
|
||||
//[[n n n n] [e e e e] [t t t t] [w w w w] [f f f f] [a a a a] [lowtotal hightotal normalChance] [critlowtotal crithightotal critChance] normalDPS critCPS averageDPS adjAttackSpeed, singleHit]
|
||||
return damages_results.concat([totalDamNorm,totalDamCrit,normDPS,critDPS,avgDPS,adjAtkSpd, singleHitTotal]).concat(results[3]);
|
||||
}
|
||||
|
||||
/*
|
||||
Get all defensive stats for this build.
|
||||
*/
|
||||
getDefenseStats(){
|
||||
const stats = this.statMap;
|
||||
let defenseStats = [];
|
||||
let def_pct = skillPointsToPercentage(this.total_skillpoints[3]);
|
||||
let agi_pct = skillPointsToPercentage(this.total_skillpoints[4]);
|
||||
//total hp
|
||||
let totalHp = stats.get("hp") + stats.get("hpBonus");
|
||||
if (totalHp < 5) totalHp = 5;
|
||||
defenseStats.push(totalHp);
|
||||
//EHP
|
||||
let ehp = [totalHp, totalHp];
|
||||
let defMult = classDefenseMultipliers.get(this.weapon.get("type"));
|
||||
ehp[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
ehp[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
defenseStats.push(ehp);
|
||||
//HPR
|
||||
let totalHpr = rawToPct(stats.get("hprRaw"), stats.get("hprPct")/100.);
|
||||
defenseStats.push(totalHpr);
|
||||
//EHPR
|
||||
let ehpr = [totalHpr, totalHpr];
|
||||
ehpr[0] /= ((1-def_pct)*(1-agi_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
ehpr[1] /= ((1-def_pct)*(2-defMult)*(2-this.defenseMultiplier));
|
||||
defenseStats.push(ehpr);
|
||||
//skp stats
|
||||
defenseStats.push([ (1 - ((1-def_pct) * (2 - this.defenseMultiplier)))*100, agi_pct*100]);
|
||||
//eledefs - TODO POWDERS
|
||||
let eledefs = [0, 0, 0, 0, 0];
|
||||
for(const i in skp_elements){ //kinda jank but ok
|
||||
eledefs[i] = rawToPct(stats.get(skp_elements[i] + "Def"), stats.get(skp_elements[i] + "DefPct")/100.);
|
||||
}
|
||||
defenseStats.push(eledefs);
|
||||
|
||||
//[total hp, [ehp w/ agi, ehp w/o agi], total hpr, [ehpr w/ agi, ehpr w/o agi], [def%, agi%], [edef,tdef,wdef,fdef,adef]]
|
||||
return defenseStats;
|
||||
}
|
||||
|
||||
/* Get all stats for this build. Stores in this.statMap.
|
||||
@pre The build itself should be valid. No checking of validity of pieces is done here.
|
||||
*/
|
||||
initBuildStats(){
|
||||
|
||||
let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef"];
|
||||
|
||||
//Create a map of this build's stats
|
||||
let statMap = new Map();
|
||||
|
||||
for (const staticID of staticIDs) {
|
||||
statMap.set(staticID, 0);
|
||||
}
|
||||
statMap.set("hp", levelToHPBase(this.level));
|
||||
|
||||
let major_ids = new Set();
|
||||
for (const item of this.items){
|
||||
for (let [id, value] of item.get("maxRolls")) {
|
||||
statMap.set(id,(statMap.get(id) || 0)+value);
|
||||
}
|
||||
for (const staticID of staticIDs) {
|
||||
if (item.get(staticID)) {
|
||||
statMap.set(staticID, statMap.get(staticID) + item.get(staticID));
|
||||
}
|
||||
}
|
||||
if (item.get("majorIds")) {
|
||||
for (const majorID of item.get("majorIds")) {
|
||||
major_ids.add(majorID);
|
||||
}
|
||||
}
|
||||
}
|
||||
statMap.set("activeMajorIDs", major_ids);
|
||||
for (const [setName, count] of this.activeSetCounts) {
|
||||
const bonus = sets[setName].bonuses[count-1];
|
||||
for (const id in bonus) {
|
||||
if (skp_order.includes(id)) {
|
||||
// pass. Don't include skillpoints in ids
|
||||
}
|
||||
else {
|
||||
statMap.set(id,(statMap.get(id) || 0)+bonus[id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
statMap.set("poisonPct", 100);
|
||||
|
||||
// The stuff relevant for damage calculation!!! @ferricles
|
||||
statMap.set("atkSpd", this.weapon.get("atkSpd"));
|
||||
|
||||
for (const x of skp_elements) {
|
||||
this.externalStats.set(x + "DamPct", 0);
|
||||
}
|
||||
this.externalStats.set("mdPct", 0);
|
||||
this.externalStats.set("sdPct", 0);
|
||||
this.externalStats.set("damageBonus", [0, 0, 0, 0, 0]);
|
||||
this.externalStats.set("defBonus",[0, 0, 0, 0, 0]);
|
||||
this.externalStats.set("poisonPct", 0);
|
||||
this.statMap = statMap;
|
||||
|
||||
this.aggregateStats();
|
||||
}
|
||||
|
||||
aggregateStats() {
|
||||
let statMap = this.statMap;
|
||||
statMap.set("damageRaw", [this.weapon.get("nDam"), this.weapon.get("eDam"), this.weapon.get("tDam"), this.weapon.get("wDam"), this.weapon.get("fDam"), this.weapon.get("aDam")]);
|
||||
statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
|
||||
statMap.set("defRaw", [statMap.get("eDef"), statMap.get("tDef"), statMap.get("wDef"), statMap.get("fDef"), statMap.get("aDef")]);
|
||||
statMap.set("defBonus", [statMap.get("eDefPct"), statMap.get("tDefPct"), statMap.get("wDefPct"), statMap.get("fDefPct"), statMap.get("aDefPct")]);
|
||||
statMap.set("defMult", classDefenseMultipliers.get(this.weapon.get("type")));
|
||||
}
|
||||
}
|
107
js/build_constants.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* I kinda lied. Theres some listener stuff in here
|
||||
* but its mostly constants for builder page specifically.
|
||||
*/
|
||||
|
||||
const url_tag = location.hash.slice(1);
|
||||
|
||||
const BUILD_VERSION = "7.0.19";
|
||||
|
||||
let player_build;
|
||||
|
||||
|
||||
// THIS IS SUPER DANGEROUS, WE SHOULD NOT BE KEEPING THIS IN SO MANY PLACES
|
||||
let editable_item_fields = [ "sdPct", "sdRaw", "mdPct", "mdRaw", "poison",
|
||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
||||
"fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct",
|
||||
"hprRaw", "hprPct", "hpBonus", "atkTier",
|
||||
"spPct1", "spRaw1", "spPct2", "spRaw2",
|
||||
"spPct3", "spRaw3", "spPct4", "spRaw4" ];
|
||||
|
||||
let editable_elems = [];
|
||||
|
||||
for (let i of editable_item_fields) {
|
||||
let elem = document.getElementById(i);
|
||||
elem.addEventListener("change", (event) => {
|
||||
elem.classList.add("highlight");
|
||||
});
|
||||
editable_elems.push(elem);
|
||||
}
|
||||
|
||||
for (let i of skp_order) {
|
||||
let elem = document.getElementById(i+"-skp");
|
||||
elem.addEventListener("change", (event) => {
|
||||
elem.classList.add("highlight");
|
||||
});
|
||||
editable_elems.push(elem);
|
||||
}
|
||||
|
||||
function clear_highlights() {
|
||||
for (let i of editable_elems) {
|
||||
i.classList.remove("highlight");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let equipment_fields = [
|
||||
"helmet",
|
||||
"chestplate",
|
||||
"leggings",
|
||||
"boots",
|
||||
"ring1",
|
||||
"ring2",
|
||||
"bracelet",
|
||||
"necklace",
|
||||
"weapon"
|
||||
];
|
||||
let tome_fields = [
|
||||
"weaponTome1",
|
||||
"weaponTome2",
|
||||
"armorTome1",
|
||||
"armorTome2",
|
||||
"armorTome3",
|
||||
"armorTome4",
|
||||
"guildTome1",
|
||||
]
|
||||
let equipment_names = [
|
||||
"Helmet",
|
||||
"Chestplate",
|
||||
"Leggings",
|
||||
"Boots",
|
||||
"Ring 1",
|
||||
"Ring 2",
|
||||
"Bracelet",
|
||||
"Necklace",
|
||||
"Weapon"
|
||||
];
|
||||
|
||||
let tome_names = [
|
||||
"Weapon Tome",
|
||||
"Weapon Tome",
|
||||
"Armor Tome",
|
||||
"Armor Tome",
|
||||
"Armor Tome",
|
||||
"Armor Tome",
|
||||
"Guild Tome",
|
||||
]
|
||||
let equipment_inputs = equipment_fields.map(x => x + "-choice");
|
||||
let build_fields = equipment_fields.map(x => x+"-tooltip");
|
||||
let tomeInputs = tome_fields.map(x => x + "-choice");
|
||||
|
||||
let powder_inputs = [
|
||||
"helmet-powder",
|
||||
"chestplate-powder",
|
||||
"leggings-powder",
|
||||
"boots-powder",
|
||||
"weapon-powder",
|
||||
];
|
||||
|
||||
let weapon_keys = ['dagger', 'wand', 'bow', 'relik', 'spear'];
|
||||
let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots'];
|
||||
let accessory_keys= ['ring1', 'ring2', 'bracelet', 'necklace'];
|
||||
let powderable_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'weapon'];
|
||||
let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon'];
|
||||
let tome_keys = ['weaponTome1', 'weaponTome2', 'armorTome1', 'armorTome2', 'armorTome3', 'armorTome4', 'guildTome1'];
|
||||
|
||||
let spell_disp = ['build-melee-stats', 'spell0-info', 'spell1-info', 'spell2-info', 'spell3-info'];
|
||||
let other_disp = ['build-order', 'set-info', 'int-info'];
|
290
js/build_encode_decode.js
Normal file
|
@ -0,0 +1,290 @@
|
|||
function parsePowdering(powder_info) {
|
||||
// TODO: Make this run in linear instead of quadratic time... ew
|
||||
let powdering = [];
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let powders = "";
|
||||
let n_blocks = Base64.toInt(powder_info.charAt(0));
|
||||
// console.log(n_blocks + " blocks");
|
||||
powder_info = powder_info.slice(1);
|
||||
for (let j = 0; j < n_blocks; ++j) {
|
||||
let block = powder_info.slice(0,5);
|
||||
let six_powders = Base64.toInt(block);
|
||||
for (let k = 0; k < 6 && six_powders != 0; ++k) {
|
||||
powders += powderNames.get((six_powders & 0x1f) - 1);
|
||||
six_powders >>>= 5;
|
||||
}
|
||||
powder_info = powder_info.slice(5);
|
||||
}
|
||||
powdering[i] = powders;
|
||||
}
|
||||
return [powdering, powder_info];
|
||||
}
|
||||
|
||||
let atree_data = null;
|
||||
|
||||
/*
|
||||
* Populate fields based on url, and calculate build.
|
||||
*/
|
||||
function decodeBuild(url_tag) {
|
||||
if (url_tag) {
|
||||
//default values
|
||||
let equipment = [null, null, null, null, null, null, null, null, null];
|
||||
let tomes = [null, null, null, null, null, null, null];
|
||||
let powdering = ["", "", "", "", ""];
|
||||
let info = url_tag.split("_");
|
||||
let version = info[0];
|
||||
let save_skp = false;
|
||||
let skillpoints = [0, 0, 0, 0, 0];
|
||||
let level = 106;
|
||||
|
||||
let version_number = parseInt(version)
|
||||
//equipment (items)
|
||||
// TODO: use filters
|
||||
if (version_number < 4) {
|
||||
let equipments = info[1];
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
let equipment_str = equipments.slice(i*3,i*3+3);
|
||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||
}
|
||||
info[1] = equipments.slice(27);
|
||||
}
|
||||
else if (version_number == 4) {
|
||||
let info_str = info[1];
|
||||
let start_idx = 0;
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
if (info_str.charAt(start_idx) === "-") {
|
||||
equipment[i] = "CR-"+info_str.slice(start_idx+1, start_idx+18);
|
||||
start_idx += 18;
|
||||
}
|
||||
else {
|
||||
let equipment_str = info_str.slice(start_idx, start_idx+3);
|
||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||
start_idx += 3;
|
||||
}
|
||||
}
|
||||
info[1] = info_str.slice(start_idx);
|
||||
}
|
||||
else if (version_number <= 7) {
|
||||
let info_str = info[1];
|
||||
let start_idx = 0;
|
||||
for (let i = 0; i < 9; ++i ) {
|
||||
if (info_str.slice(start_idx,start_idx+3) === "CR-") {
|
||||
equipment[i] = info_str.slice(start_idx, start_idx+20);
|
||||
start_idx += 20;
|
||||
} else if (info_str.slice(start_idx+3,start_idx+6) === "CI-") {
|
||||
let len = Base64.toInt(info_str.slice(start_idx,start_idx+3));
|
||||
equipment[i] = info_str.slice(start_idx+3,start_idx+3+len);
|
||||
start_idx += (3+len);
|
||||
} else {
|
||||
let equipment_str = info_str.slice(start_idx, start_idx+3);
|
||||
equipment[i] = getItemNameFromID(Base64.toInt(equipment_str));
|
||||
start_idx += 3;
|
||||
}
|
||||
}
|
||||
info[1] = info_str.slice(start_idx);
|
||||
}
|
||||
//constant in all versions
|
||||
for (let i in equipment) {
|
||||
setValue(equipment_inputs[i], equipment[i]);
|
||||
}
|
||||
|
||||
//level, skill point assignments, and powdering
|
||||
if (version_number == 1) {
|
||||
let powder_info = info[1];
|
||||
let res = parsePowdering(powder_info);
|
||||
powdering = res[0];
|
||||
} else if (version_number == 2) {
|
||||
save_skp = true;
|
||||
let skillpoint_info = info[1].slice(0, 10);
|
||||
for (let i = 0; i < 5; ++i ) {
|
||||
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
|
||||
}
|
||||
|
||||
let powder_info = info[1].slice(10);
|
||||
let res = parsePowdering(powder_info);
|
||||
powdering = res[0];
|
||||
} else if (version_number <= 7){
|
||||
level = Base64.toInt(info[1].slice(10,12));
|
||||
setValue("level-choice",level);
|
||||
save_skp = true;
|
||||
let skillpoint_info = info[1].slice(0, 10);
|
||||
for (let i = 0; i < 5; ++i ) {
|
||||
skillpoints[i] = Base64.toIntSigned(skillpoint_info.slice(i*2,i*2+2));
|
||||
}
|
||||
|
||||
let powder_info = info[1].slice(12);
|
||||
|
||||
let res = parsePowdering(powder_info);
|
||||
powdering = res[0];
|
||||
info[1] = res[1];
|
||||
}
|
||||
// Tomes.
|
||||
if (version >= 6) {
|
||||
//tome values do not appear in anything before v6.
|
||||
for (let i in tomes) {
|
||||
let tome_str = info[1].charAt(i);
|
||||
let tome_name = getTomeNameFromID(Base64.toInt(tome_str));
|
||||
console.log(tome_name);
|
||||
setValue(tomeInputs[i], tome_name);
|
||||
}
|
||||
info[1] = info[1].slice(7);
|
||||
}
|
||||
|
||||
if (version >= 7) {
|
||||
// ugly af. only works since its the last thing. will be fixed with binary decode
|
||||
atree_data = new BitVector(info[1]);
|
||||
}
|
||||
else {
|
||||
atree_data = null;
|
||||
}
|
||||
|
||||
for (let i in powder_inputs) {
|
||||
setValue(powder_inputs[i], powdering[i]);
|
||||
}
|
||||
for (let i in skillpoints) {
|
||||
setValue(skp_order[i] + "-skp", skillpoints[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Stores the entire build in a string using B64 encoding and adds it to the URL.
|
||||
*/
|
||||
function encodeBuild(build, powders, skillpoints, atree, atree_state) {
|
||||
|
||||
if (build) {
|
||||
let build_string;
|
||||
|
||||
//V6 encoding - Tomes
|
||||
//V7 encoding - ATree
|
||||
build_version = 5;
|
||||
build_string = "";
|
||||
tome_string = "";
|
||||
|
||||
for (const item of build.items) {
|
||||
if (item.statMap.get("custom")) {
|
||||
let custom = "CI-"+encodeCustom(item, true);
|
||||
build_string += Base64.fromIntN(custom.length, 3) + custom;
|
||||
build_version = Math.max(build_version, 5);
|
||||
} else if (item.statMap.get("crafted")) {
|
||||
build_string += "CR-"+encodeCraft(item);
|
||||
} else if (item.statMap.get("category") === "tome") {
|
||||
let tome_id = item.statMap.get("id");
|
||||
if (tome_id <= 60) {
|
||||
// valid normal tome. ID 61-63 is for NONE tomes.
|
||||
build_version = Math.max(build_version, 6);
|
||||
}
|
||||
tome_string += Base64.fromIntN(tome_id, 1);
|
||||
} else {
|
||||
build_string += Base64.fromIntN(item.statMap.get("id"), 3);
|
||||
}
|
||||
}
|
||||
|
||||
for (const skp of skillpoints) {
|
||||
build_string += Base64.fromIntN(skp, 2); // Maximum skillpoints: 2048
|
||||
}
|
||||
build_string += Base64.fromIntN(build.level, 2);
|
||||
for (const _powderset of powders) {
|
||||
let n_bits = Math.ceil(_powderset.length / 6);
|
||||
build_string += Base64.fromIntN(n_bits, 1); // Hard cap of 378 powders.
|
||||
// Slice copy.
|
||||
let powderset = _powderset.slice();
|
||||
while (powderset.length != 0) {
|
||||
let firstSix = powderset.slice(0,6).reverse();
|
||||
let powder_hash = 0;
|
||||
for (const powder of firstSix) {
|
||||
powder_hash = (powder_hash << 5) + 1 + powder; // LSB will be extracted first.
|
||||
}
|
||||
build_string += Base64.fromIntN(powder_hash, 5);
|
||||
powderset = powderset.slice(6);
|
||||
}
|
||||
}
|
||||
build_string += tome_string;
|
||||
|
||||
if (atree_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 weaponTypes = [ "wand", "spear", "bow", "dagger", "relik" ];
|
||||
const consumableTypes = [ "potion", "scroll", "food"];
|
||||
const tomeTypes = ["armorTome", "weaponTome", "guildTome"]; //"dungeonTome", "gatheringTome", "slayingTome"
|
||||
const tome_types = ['weaponTome', 'armorTome', 'guildTome'];
|
||||
const attackSpeeds = ["SUPER_SLOW", "VERY_SLOW", "SLOW", "NORMAL", "FAST", "VERY_FAST", "SUPER_FAST"];
|
||||
const baseDamageMultiplier = [ 0.51, 0.83, 1.5, 2.05, 2.5, 3.1, 4.3 ];
|
||||
//0.51, 0.82, 1.50, 2.05, 2.50, 3.11, 4.27
|
||||
const classes = ["Warrior", "Assassin", "Mage", "Archer", "Shaman"];
|
||||
const wep_to_class = new Map([["dagger", "Assassin"], ["spear", "Warrior"], ["wand", "Mage"], ["bow", "Archer"], ["relik", "Shaman"]])
|
||||
const tiers = ["Normal", "Unique", "Rare", "Legendary", "Fabled", "Mythic", "Set", "Crafted"] //I'm not sure why you would make a custom crafted but if you do you should be able to use it w/ the correct powder formula
|
||||
const types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tomeTypes).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
|
||||
const all_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(consumableTypes).concat(tome_types).map(x => x.substring(0,1).toUpperCase() + x.substring(1));
|
||||
//weaponTypes.push("sword");
|
||||
//console.log(types)
|
||||
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tomeTypes);
|
||||
let itemTypes = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
|
||||
|
||||
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
|
||||
let skpReqs = skp_order.map(x => x + "Req");
|
||||
|
||||
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rainbowRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "dmgMobs", "defMobs"];
|
||||
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs",
|
||||
|
||||
// wynn2 damages.
|
||||
"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct"*/,"eDamRaw","eDamAddMin","eDamAddMax",
|
||||
"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct"*/,"tDamRaw","tDamAddMin","tDamAddMax",
|
||||
"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct"*/,"wDamRaw","wDamAddMin","wDamAddMax",
|
||||
"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct"*/,"fDamRaw","fDamAddMin","fDamAddMax",
|
||||
"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct"*/,"aDamRaw","aDamAddMin","aDamAddMax",
|
||||
"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
|
||||
/*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
|
||||
"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
|
||||
"critDamPct"
|
||||
];
|
||||
// Extra fake IDs (reserved for use in spell damage calculation) : damageMultiplier, defMultiplier, poisonPct, activeMajorIDs
|
||||
let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ]
|
||||
|
||||
//File reading for ID translations for JSON purposes
|
||||
let reversetranslations = new Map();
|
||||
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rainbowRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
||||
//does not include dmgMobs (wep tomes) and defMobs (armor tomes)
|
||||
let translations = new Map([["name", "name"], ["displayName", "displayName"], ["tier", "tier"], ["set", "set"], ["sockets", "slots"], ["type", "type"], ["dropType", "drop"], ["quest", "quest"], ["restrictions", "restrict"], ["damage", "nDam"], ["fireDamage", "fDam"], ["waterDamage", "wDam"], ["airDamage", "aDam"], ["thunderDamage", "tDam"], ["earthDamage", "eDam"], ["attackSpeed", "atkSpd"], ["health", "hp"], ["fireDefense", "fDef"], ["waterDefense", "wDef"], ["airDefense", "aDef"], ["thunderDefense", "tDef"], ["earthDefense", "eDef"], ["level", "lvl"], ["classRequirement", "classReq"], ["strength", "strReq"], ["dexterity", "dexReq"], ["intelligence", "intReq"], ["agility", "agiReq"], ["defense", "defReq"], ["healthRegen", "hprPct"], ["manaRegen", "mr"], ["spellDamage", "sdPct"], ["damageBonus", "mdPct"], ["lifeSteal", "ls"], ["manaSteal", "ms"], ["xpBonus", "xpb"], ["lootBonus", "lb"], ["reflection", "ref"], ["strengthPoints", "str"], ["dexterityPoints", "dex"], ["intelligencePoints", "int"], ["agilityPoints", "agi"], ["defensePoints", "def"], ["thorns", "thorns"], ["exploding", "expd"], ["speed", "spd"], ["attackSpeedBonus", "atkTier"], ["poison", "poison"], ["healthBonus", "hpBonus"], ["soulPoints", "spRegen"], ["emeraldStealing", "eSteal"], ["healthRegenRaw", "hprRaw"], ["spellDamageRaw", "sdRaw"], ["damageBonusRaw", "mdRaw"], ["bonusFireDamage", "fDamPct"], ["bonusWaterDamage", "wDamPct"], ["bonusAirDamage", "aDamPct"], ["bonusThunderDamage", "tDamPct"], ["bonusEarthDamage", "eDamPct"], ["bonusFireDefense", "fDefPct"], ["bonusWaterDefense", "wDefPct"], ["bonusAirDefense", "aDefPct"], ["bonusThunderDefense", "tDefPct"], ["bonusEarthDefense", "eDefPct"], ["type", "type"], ["identified", "fixID"], ["skin", "skin"], ["category", "category"], ["spellCostPct1", "spPct1"], ["spellCostRaw1", "spRaw1"], ["spellCostPct2", "spPct2"], ["spellCostRaw2", "spRaw2"], ["spellCostPct3", "spPct3"], ["spellCostRaw3", "spRaw3"], ["spellCostPct4", "spPct4"], ["spellCostRaw4", "spRaw4"], ["rainbowSpellDamageRaw", "rSdRaw"], ["sprint", "sprint"], ["sprintRegen", "sprintReg"], ["jumpHeight", "jh"], ["lootQuality", "lq"], ["gatherXpBonus", "gXp"], ["gatherSpeed", "gSpd"]]);
|
||||
//does not include damMobs (wep tomes) and defMobs (armor tomes)
|
||||
for (const [k, v] of translations) {
|
||||
reversetranslations.set(v, k);
|
||||
}
|
||||
|
||||
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 {
|
||||
/***
|
||||
/**
|
||||
* Make a generic compute node.
|
||||
* Adds the node to the global map of nodenames to nodes (for calling from html listeners).
|
||||
*
|
||||
* @param name : Name of the node (string). Must be unique. Must "fit in" a JS string (terminated by single quotes).
|
||||
*/
|
||||
constructor(name) {
|
||||
if (_ALL_NODES.has(name)) {
|
||||
throw 'Duplicate node name: ' + name;
|
||||
}
|
||||
_ALL_NODES.set(name, this)
|
||||
this.inputs = [];
|
||||
this.inputs = []; // parent nodes
|
||||
this.input_translation = new Map();
|
||||
this.children = [];
|
||||
this.value = 0;
|
||||
this.value = null;
|
||||
this.name = name;
|
||||
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.
|
||||
*/
|
||||
update(timestamp) {
|
||||
if (timestamp <= this.update_time) {
|
||||
update() {
|
||||
if (this.inputs_dirty_count != 0) {
|
||||
return;
|
||||
}
|
||||
this.update_time = timestamp;
|
||||
|
||||
let value_map = Map();
|
||||
if (this.dirty === 0) {
|
||||
return;
|
||||
}
|
||||
if (this.dirty == 2) {
|
||||
let calc_inputs = new Map();
|
||||
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) {
|
||||
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() {
|
||||
return 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";
|
||||
}
|
||||
|
||||
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)
|
||||
link_name = (link_name !== undefined) ? link_name : parent_node.name;
|
||||
this.input_translation.set(parent_node.name, link_name);
|
||||
if (parent_node.dirty || (parent_node.value === null && !this.fail_cb)) {
|
||||
this.inputs_dirty_count += 1;
|
||||
this.inputs_dirty.set(parent_node.name, true);
|
||||
}
|
||||
parent_node.children.push(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a link to a parent node.
|
||||
* TODO: time complexity of list deletion (not super relevant but it hurts my soul)
|
||||
*/
|
||||
remove_link(parent_node) {
|
||||
const idx = this.inputs.indexOf(parent_node); // Get idx
|
||||
this.inputs.splice(idx, 1); // remove element
|
||||
|
||||
this.input_translation.delete(parent_node.name);
|
||||
const was_dirty = this.inputs_dirty.get(parent_node.name);
|
||||
this.inputs_dirty.delete(parent_node.name);
|
||||
if (was_dirty) {
|
||||
this.inputs_dirty_count -= 1;
|
||||
}
|
||||
|
||||
const idx2 = parent_node.children.indexOf(this);
|
||||
parent_node.children.splice(idx2, 1);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
class ValueCheckComputeNode extends ComputeNode {
|
||||
constructor(name) { super(name); }
|
||||
|
||||
/**
|
||||
* Request update of this compute node. Pushes updates to children,
|
||||
* but only if this node's value changed.
|
||||
*/
|
||||
update() {
|
||||
if (this.inputs_dirty_count != 0) {
|
||||
return;
|
||||
}
|
||||
if (this.dirty === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let calc_inputs = new Map();
|
||||
for (const input of this.inputs) {
|
||||
calc_inputs.set(this.input_translation.get(input.name), input.value);
|
||||
}
|
||||
let val = this.compute_func(calc_inputs);
|
||||
if (val !== this.value) {
|
||||
super.mark_dirty(2);
|
||||
}
|
||||
else {
|
||||
console.log("soft update");
|
||||
}
|
||||
this.value = val;
|
||||
|
||||
this.dirty = 0;
|
||||
for (const child of this.children) {
|
||||
child.mark_input_clean(this.name, this.value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaulting to "dusty" state.
|
||||
*/
|
||||
mark_dirty(dirty_state="unused") {
|
||||
return super.mark_dirty(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a ComputeNode to be updated.
|
||||
*
|
||||
* @param node_name : ComputeNode name to schedule an update for.
|
||||
* @param node : ComputeNode to schedule an update for.
|
||||
*/
|
||||
function calcSchedule(node_name) {
|
||||
node = _ALL_NODES.get(node_name);
|
||||
function calcSchedule(node, timeout) {
|
||||
if (node.update_task !== null) {
|
||||
clearTimeout(node.update_task);
|
||||
}
|
||||
node.mark_dirty();
|
||||
node.update_task = setTimeout(function() {
|
||||
const timestamp = Date.now();
|
||||
node.update(timestamp);
|
||||
node.update();
|
||||
node.update_task = null;
|
||||
}, 500);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
/***
|
||||
* Node for getting an item's stats from an item input field.
|
||||
*/
|
||||
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) {
|
||||
class PrintNode extends ComputeNode {
|
||||
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.input_field.setAttribute("onInput", "calcSchedule('"+name+"');");
|
||||
this.input_field = item_input_field;
|
||||
this.none_item = none_item;
|
||||
this.fail_cb = true;
|
||||
}
|
||||
|
||||
compute_func() {
|
||||
// built on the assumption of no one will type in CI/CR letter by letter
|
||||
|
||||
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;
|
||||
compute_func(input_map) {
|
||||
console.log([this.name, input_map]);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node for getting an input from an input field.
|
||||
* Fires updates whenever the input field is updated.
|
||||
*
|
||||
* Signature: InputNode() => str
|
||||
*/
|
||||
class InputNode extends ComputeNode {
|
||||
constructor(name, input_field) {
|
||||
super(name);
|
||||
this.input_field = input_field;
|
||||
this.input_field.addEventListener("input", () => calcSchedule(this, 500));
|
||||
this.input_field.addEventListener("change", () => calcSchedule(this, 5));
|
||||
//calcSchedule(this); Manually fire first update for better control
|
||||
}
|
||||
|
||||
compute_func(input_map) {
|
||||
return this.input_field.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passthrough node for simple aggregation.
|
||||
* Unfortunately if you use this too much you get layers and layers of maps...
|
||||
*
|
||||
* Signature: PassThroughNode(**kwargs) => Map[...]
|
||||
*/
|
||||
class PassThroughNode extends ComputeNode {
|
||||
constructor(name) {
|
||||
super(name);
|
||||
this.breakout_nodes = new Map();
|
||||
}
|
||||
|
||||
compute_func(input_map) {
|
||||
return input_map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a ComputeNode that will "break out" one part of this aggregation input.
|
||||
* There is some overhead to this operation because ComputeNode is not exactly a free abstraction... oof
|
||||
* Also you will recv updates whenever any input that is part of the aggregation changes even
|
||||
* if the specific sub-input didn't change.
|
||||
*
|
||||
* Parameters:
|
||||
* sub-input: The key to listen to
|
||||
*/
|
||||
get_node(sub_input) {
|
||||
if (this.breakout_nodes.has(sub_input)) {
|
||||
return this.breakout_nodes.get(sub_input);
|
||||
}
|
||||
const _name = this.name;
|
||||
const ret = new (class extends ComputeNode {
|
||||
constructor() { super('passthrough-'+_name+'-'+sub_input); }
|
||||
compute_func(input_map) { return input_map.get(_name).get(sub_input); }
|
||||
})().link_to(this);
|
||||
this.breakout_nodes.set(sub_input, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,7 +170,6 @@ class Craft{
|
|||
statMap.set(e + "Dam", "0-0");
|
||||
statMap.set(e + "DamLow", "0-0");
|
||||
}
|
||||
//statMap.set("damageBonus", [statMap.get("eDamPct"), statMap.get("tDamPct"), statMap.get("wDamPct"), statMap.get("fDamPct"), statMap.get("aDamPct")]);
|
||||
statMap.set("category","weapon");
|
||||
statMap.set("atkSpd",this.atkSpd);
|
||||
}
|
||||
|
@ -190,7 +189,6 @@ class Craft{
|
|||
let amounts = this.recipe.get("materials").map(x=> x.get("amount"));
|
||||
//Mat Multipliers - should work!
|
||||
matmult = (tierToMult[tiers[0]]*amounts[0] + tierToMult[tiers[1]]*amounts[1]) / (amounts[0]+amounts[1]);
|
||||
console.log(matmult);
|
||||
|
||||
let low = this.recipe.get("healthOrDamage")[0];
|
||||
let high = this.recipe.get("healthOrDamage")[1];
|
||||
|
@ -382,12 +380,10 @@ class Craft{
|
|||
|
||||
statMap.set("reqs",[0,0,0,0,0]);
|
||||
statMap.set("skillpoints", [0,0,0,0,0]);
|
||||
statMap.set("damageBonus",[0,0,0,0,0]);
|
||||
for (const e in skp_order) {
|
||||
statMap.set(skp_order[e], statMap.get("maxRolls").has(skp_order[e]) ? statMap.get("maxRolls").get(skp_order[e]) : 0);
|
||||
statMap.get("skillpoints")[e] = statMap.get("maxRolls").has(skp_order[e]) ? statMap.get("maxRolls").get(skp_order[e]) : 0;
|
||||
statMap.get("reqs")[e] = statMap.has(skp_order[e]+"Req") && !consumableTypes.includes(statMap.get("type"))? statMap.get(skp_order[e]+"Req") : 0;
|
||||
statMap.get("damageBonus")[e] = statMap.has(skp_order[e]+"DamPct") ? statMap.get(skp_order[e]+"DamPct") : 0;
|
||||
}
|
||||
for (const id of rolledIDs) {
|
||||
if (statMap.get("minRolls").has(id)) {
|
||||
|
|
|
@ -172,16 +172,17 @@ function calculateCraft() {
|
|||
document.getElementById("mat-2").textContent = recipe.get("materials")[1].get("item").split(" ").slice(1).join(" ") + " Tier:";
|
||||
|
||||
//Display Recipe Stats
|
||||
displaysq2RecipeStats(player_craft, "recipe-stats");
|
||||
displayRecipeStats(player_craft, "recipe-stats");
|
||||
|
||||
//Display Craft Stats
|
||||
// displayCraftStats(player_craft, "craft-stats");
|
||||
let mock_item = player_craft.statMap;
|
||||
displaysq2ExpandedItem(mock_item, "craft-stats");
|
||||
apply_weapon_powders(mock_item);
|
||||
displayExpandedItem(mock_item, "craft-stats");
|
||||
|
||||
//Display Ingredients' Stats
|
||||
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
|
||||
let warning_elem = document.getElementById("craft-warnings");
|
||||
|
@ -264,7 +265,7 @@ function populateFields() {
|
|||
*/
|
||||
function copyRecipeHash() {
|
||||
if (player_craft) {
|
||||
copyTextToClipboard("CR-"+location.hash);
|
||||
copyTextToClipboard("CR-"+location.hash.slice(1));
|
||||
document.getElementById("copy-hash-button").textContent = "Copied!";
|
||||
}
|
||||
}
|
||||
|
@ -341,7 +342,7 @@ function toggleMaterial(buttonId) {
|
|||
*/
|
||||
function updateCraftedImage() {
|
||||
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";
|
||||
}
|
||||
|
||||
|
@ -364,4 +365,8 @@ function resetFields() {
|
|||
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("custom", true);
|
||||
return new Custom(statMap);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -370,7 +370,7 @@ function useBaseItem(elem) {
|
|||
//Check items db.
|
||||
for (const [name,itemObj] of itemMap) {
|
||||
if (itemName === name) {
|
||||
baseItem = expandItem(itemObj, []);
|
||||
baseItem = expandItem(itemObj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,190 +1,297 @@
|
|||
const damageMultipliers = new Map([ ["allytotem", .15], ["yourtotem", .35], ["vanish", 0.80], ["warscream", 0.10], ["bash", 0.50] ]);
|
||||
// Calculate spell damage given a spell elemental conversion table, and a spell multiplier.
|
||||
// If spell mult is 0, its melee damage and we don't multiply by attack speed.
|
||||
// externalStats should be a map
|
||||
function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier, externalStats) {
|
||||
let buildStats = new Map(stats);
|
||||
let tooltipinfo = new Map();
|
||||
//6x for damages, normal min normal max crit min crit max
|
||||
let damageformulas = [["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "]];
|
||||
|
||||
if(externalStats) { //if nothing is passed in, then this hopefully won't trigger
|
||||
for (const entry of externalStats) {
|
||||
const key = entry[0];
|
||||
const value = entry[1];
|
||||
if (typeof value === "number") {
|
||||
buildStats.set(key, buildStats.get(key) + value);
|
||||
} else if (Array.isArray(value)) {
|
||||
arr = [];
|
||||
for (let j = 0; j < value.length; j++) {
|
||||
arr[j] = buildStats.get(key)[j] + value[j];
|
||||
function get_base_dps(item) {
|
||||
const attack_speed_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
|
||||
//SUPER JANK @HPP PLS FIX
|
||||
if (item.get("tier") !== "Crafted") {
|
||||
let total_damage = 0;
|
||||
for (const damage_k of damage_keys) {
|
||||
damages = item.get(damage_k);
|
||||
total_damage += damages[0] + damages[1];
|
||||
}
|
||||
buildStats.set(key, arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let powders = weapon.get("powders").slice();
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
let damages = [];
|
||||
const rawDamages = buildStats.get("damageRaw");
|
||||
for (let i = 0; i < rawDamages.length; i++) {
|
||||
const damage_vals = rawDamages[i].split("-").map(Number);
|
||||
damages.push(damage_vals);
|
||||
}
|
||||
|
||||
// Applying spell conversions
|
||||
let neutralBase = damages[0].slice();
|
||||
let neutralRemainingRaw = damages[0].slice();
|
||||
|
||||
|
||||
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
|
||||
|
||||
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
|
||||
//1st round - apply each as ingred, 2nd round - apply as normal
|
||||
if (weapon.get("tier") === "Crafted") {
|
||||
let damageBases = buildStats.get("damageBases").slice();
|
||||
for (const p of powders.concat(weapon.get("ingredPowders"))) {
|
||||
let powder = powderStats[p]; //use min, max, and convert
|
||||
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
|
||||
let diff = Math.floor(damageBases[0] * powder.convert/100);
|
||||
damageBases[0] -= diff;
|
||||
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
|
||||
}
|
||||
//update all damages
|
||||
if(!weapon.get("custom")) {
|
||||
for (let i = 0; i < damages.length; i++) {
|
||||
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
|
||||
}
|
||||
}
|
||||
|
||||
neutralRemainingRaw = damages[0].slice();
|
||||
neutralBase = damages[0].slice();
|
||||
}
|
||||
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
let conversionRatio = spellConversions[i+1]/100;
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
||||
damages[i+1][0] = Math.floor(round_near(damages[i+1][0] + min_diff));
|
||||
damages[i+1][1] = Math.floor(round_near(damages[i+1][1] + max_diff));
|
||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
||||
}
|
||||
|
||||
//apply powders to weapon
|
||||
for (const powderID of powders) {
|
||||
const powder = powderStats[powderID];
|
||||
// Bitwise to force conversion to integer (integer division).
|
||||
const element = (powderID/6) | 0;
|
||||
let conversionRatio = powder.convert/100;
|
||||
if (neutralRemainingRaw[1] > 0) {
|
||||
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
|
||||
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
|
||||
damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff));
|
||||
damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff));
|
||||
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
|
||||
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
|
||||
}
|
||||
damages[element+1][0] += powder.min;
|
||||
damages[element+1][1] += powder.max;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//console.log(tooltipinfo);
|
||||
|
||||
damages[0] = neutralRemainingRaw;
|
||||
tooltipinfo.set("damageBases", damages);
|
||||
|
||||
let damageMult = damageMultiplier;
|
||||
let melee = false;
|
||||
// If we are doing melee calculations:
|
||||
tooltipinfo.set("dmgMult", damageMult);
|
||||
if (spellMultiplier == 0) {
|
||||
spellMultiplier = 1;
|
||||
melee = true;
|
||||
return total_damage * attack_speed_mult / 2;
|
||||
}
|
||||
else {
|
||||
tooltipinfo.set("dmgMult", `(${tooltipinfo.get("dmgMult")} * ${spellMultiplier} * ${baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))]})`)
|
||||
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))];
|
||||
let total_damage_min = 0;
|
||||
let total_damage_max = 0;
|
||||
for (const damage_k of damage_keys) {
|
||||
damages = item.get(damage_k);
|
||||
total_damage_min += damages[0][0] + damages[0][1];
|
||||
total_damage_max += damages[1][0] + damages[1][1];
|
||||
}
|
||||
//console.log(damages);
|
||||
//console.log(damageMult);
|
||||
tooltipinfo.set("rawModifier", `(${rawModifier} * ${spellMultiplier} * ${damageMultiplier})`);
|
||||
rawModifier *= spellMultiplier * damageMultiplier;
|
||||
let totalDamNorm = [0, 0];
|
||||
let totalDamCrit = [0, 0];
|
||||
let damages_results = [];
|
||||
// 0th skillpoint is strength, 1st is dex.
|
||||
let str = total_skillpoints[0];
|
||||
let strBoost = 1 + skillPointsToPercentage(str);
|
||||
if(!melee){
|
||||
let baseDam = rawModifier * strBoost;
|
||||
let baseDamCrit = rawModifier * (1 + strBoost);
|
||||
totalDamNorm = [baseDam, baseDam];
|
||||
totalDamCrit = [baseDamCrit, baseDamCrit];
|
||||
for (let arr of damageformulas) {
|
||||
arr = arr.map(x => x + " + " +tooltipinfo.get("rawModifier"));
|
||||
total_damage_min = attack_speed_mult * total_damage_min / 2;
|
||||
total_damage_max = attack_speed_mult * total_damage_max / 2;
|
||||
return [total_damage_min, total_damage_max];
|
||||
}
|
||||
}
|
||||
let staticBoost = (pctModifier / 100.);
|
||||
tooltipinfo.set("staticBoost", `${(pctModifier/ 100.).toFixed(2)}`);
|
||||
tooltipinfo.set("skillBoost",["","","","","",""]);
|
||||
let skillBoost = [0];
|
||||
for (let i in total_skillpoints) {
|
||||
skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get("damageBonus")[i] / 100.);
|
||||
tooltipinfo.get("skillBoost")[parseInt(i,10)+1] = `(${skillPointsToPercentage(total_skillpoints[i]).toFixed(2)} + ${(buildStats.get("damageBonus")[i]/100.).toFixed(2)})`
|
||||
}
|
||||
tooltipinfo.get("skillBoost")[0] = undefined;
|
||||
|
||||
for (let i in damages) {
|
||||
let damageBoost = 1 + skillBoost[i] + staticBoost;
|
||||
tooltipinfo.set("damageBoost", `(1 + ${(tooltipinfo.get("skillBoost")[i] ? tooltipinfo.get("skillBoost")[i] + " + " : "")} ${tooltipinfo.get("staticBoost")})`)
|
||||
damages_results.push([
|
||||
Math.max(damages[i][0] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal min
|
||||
Math.max(damages[i][1] * strBoost * Math.max(damageBoost,0) * damageMult, 0), // Normal max
|
||||
Math.max(damages[i][0] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit min
|
||||
Math.max(damages[i][1] * (strBoost + 1) * Math.max(damageBoost,0) * damageMult, 0), // Crit max
|
||||
]);
|
||||
damageformulas[i][0] += `(max((${tooltipinfo.get("damageBases")[i][0]} * ${strBoost} * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))`
|
||||
damageformulas[i][1] += `(max((${tooltipinfo.get("damageBases")[i][1]} * ${strBoost} * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))`
|
||||
damageformulas[i][2] += `(max((${tooltipinfo.get("damageBases")[i][0]} * ${strBoost} * 2 * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))`
|
||||
damageformulas[i][3] += `(max((${tooltipinfo.get("damageBases")[i][1]} * ${strBoost} * 2 * max(${tooltipinfo.get("damageBoost")}, 0) * ${tooltipinfo.get("dmgMult")}), 0))`
|
||||
totalDamNorm[0] += damages_results[i][0];
|
||||
totalDamNorm[1] += damages_results[i][1];
|
||||
totalDamCrit[0] += damages_results[i][2];
|
||||
totalDamCrit[1] += damages_results[i][3];
|
||||
}
|
||||
if (melee) {
|
||||
totalDamNorm[0] += Math.max(strBoost*rawModifier, -damages_results[0][0]);
|
||||
totalDamNorm[1] += Math.max(strBoost*rawModifier, -damages_results[0][1]);
|
||||
totalDamCrit[0] += Math.max((strBoost+1)*rawModifier, -damages_results[0][2]);
|
||||
totalDamCrit[1] += Math.max((strBoost+1)*rawModifier, -damages_results[0][3]);
|
||||
}
|
||||
damages_results[0][0] += strBoost*rawModifier;
|
||||
damages_results[0][1] += strBoost*rawModifier;
|
||||
damages_results[0][2] += (strBoost + 1)*rawModifier;
|
||||
damages_results[0][3] += (strBoost + 1)*rawModifier;
|
||||
for (let i = 0; i < 2; i++) {
|
||||
damageformulas[0][i] += ` + (${strBoost} * ${tooltipinfo.get("rawModifier")})`
|
||||
}
|
||||
for (let i = 2; i < 4; i++) {
|
||||
damageformulas[0][i] += ` + (2 * ${strBoost} * ${tooltipinfo.get("rawModifier")})`
|
||||
}
|
||||
|
||||
if (totalDamNorm[0] < 0) totalDamNorm[0] = 0;
|
||||
if (totalDamNorm[1] < 0) totalDamNorm[1] = 0;
|
||||
if (totalDamCrit[0] < 0) totalDamCrit[0] = 0;
|
||||
if (totalDamCrit[1] < 0) totalDamCrit[1] = 0;
|
||||
|
||||
tooltipinfo.set("damageformulas", damageformulas);
|
||||
return [totalDamNorm, totalDamCrit, damages_results, tooltipinfo];
|
||||
}
|
||||
|
||||
|
||||
function calculateSpellDamage(stats, weapon, conversions, use_spell_damage, ignore_speed=false) {
|
||||
// TODO: Roll all the loops together maybe
|
||||
|
||||
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
|
||||
// 1. Get weapon damage (with powders).
|
||||
let weapon_damages;
|
||||
if (weapon.get('tier') === 'Crafted') {
|
||||
weapon_damages = damage_keys.map(x => weapon.get(x)[1]);
|
||||
}
|
||||
else {
|
||||
weapon_damages = damage_keys.map(x => weapon.get(x));
|
||||
}
|
||||
let present = weapon.get(damage_present_key);
|
||||
|
||||
// 2. Conversions.
|
||||
// 2.1. First, apply neutral conversion (scale weapon damage). Keep track of total weapon damage here.
|
||||
let damages = [];
|
||||
const neutral_convert = conversions[0] / 100;
|
||||
let weapon_min = 0;
|
||||
let weapon_max = 0;
|
||||
for (const damage of weapon_damages) {
|
||||
let min_dmg = damage[0] * neutral_convert;
|
||||
let max_dmg = damage[1] * neutral_convert;
|
||||
damages.push([min_dmg, max_dmg]);
|
||||
weapon_min += damage[0];
|
||||
weapon_max += damage[1];
|
||||
}
|
||||
|
||||
// 2.2. Next, apply elemental conversions using damage computed in step 1.1.
|
||||
// Also, track which elements are present. (Add onto those present in the weapon itself.)
|
||||
let total_convert = 0; //TODO get confirmation that this is how raw works.
|
||||
for (let i = 1; i <= 5; ++i) {
|
||||
if (conversions[i] > 0) {
|
||||
const conv_frac = conversions[i]/100;
|
||||
damages[i][0] += conv_frac * weapon_min;
|
||||
damages[i][1] += conv_frac * weapon_max;
|
||||
present[i] = true;
|
||||
total_convert += conv_frac
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
let damage_prefix = damage_elements[i] + specific_boost_str;
|
||||
let damageBoost = 1 + skill_boost[i] + static_boost
|
||||
+ ((stats.get(damage_prefix+'Pct') + stats.get(damage_elements[i]+'DamPct')) /100);
|
||||
damages[i][0] *= Math.max(damageBoost, 0);
|
||||
damages[i][1] *= Math.max(damageBoost, 0);
|
||||
// Collect total damage post %boost
|
||||
total_min += damages[i][0];
|
||||
total_max += damages[i][1];
|
||||
}
|
||||
|
||||
let total_elem_min = total_min - damages[0][0];
|
||||
let total_elem_max = total_max - damages[0][1];
|
||||
|
||||
// 5.2: Raw application.
|
||||
let prop_raw = stats.get(specific_boost_str.toLowerCase()+'Raw') + stats.get('damRaw');
|
||||
let rainbow_raw = stats.get('r'+specific_boost_str+'Raw') + stats.get('rDamRaw');
|
||||
for (let i in damages) {
|
||||
let damages_obj = damages[i];
|
||||
let damage_prefix = damage_elements[i] + specific_boost_str;
|
||||
// Normie raw
|
||||
let raw_boost = 0;
|
||||
if (present[i]) {
|
||||
raw_boost += stats.get(damage_prefix+'Raw') + stats.get(damage_elements[i]+'DamRaw');
|
||||
}
|
||||
// Next, rainraw and propRaw
|
||||
let min_boost = raw_boost;
|
||||
let max_boost = raw_boost;
|
||||
if (total_max > 0) { // TODO: what about total negative all raw?
|
||||
if (total_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 = {
|
||||
"wand": [
|
||||
|
|
1619
js/display.js
|
@ -1,99 +1,3 @@
|
|||
let nonRolledIDs = [
|
||||
"name",
|
||||
"lore",
|
||||
"displayName",
|
||||
"tier",
|
||||
"set",
|
||||
"slots",
|
||||
"type",
|
||||
"material",
|
||||
"drop",
|
||||
"quest",
|
||||
"restrict",
|
||||
"nDam",
|
||||
"fDam",
|
||||
"wDam",
|
||||
"aDam",
|
||||
"tDam",
|
||||
"eDam",
|
||||
"atkSpd",
|
||||
"hp",
|
||||
"fDef",
|
||||
"wDef",
|
||||
"aDef",
|
||||
"tDef",
|
||||
"eDef",
|
||||
"lvl",
|
||||
"classReq",
|
||||
"strReq",
|
||||
"dexReq",
|
||||
"intReq",
|
||||
"defReq",
|
||||
"agiReq","str",
|
||||
"dex",
|
||||
"int",
|
||||
"agi",
|
||||
"def",
|
||||
"fixID",
|
||||
"category",
|
||||
"id",
|
||||
"skillpoints",
|
||||
"reqs",
|
||||
"nDam_",
|
||||
"fDam_",
|
||||
"wDam_",
|
||||
"aDam_",
|
||||
"tDam_",
|
||||
"eDam_",
|
||||
"majorIds"];
|
||||
let rolledIDs = [
|
||||
"hprPct",
|
||||
"mr",
|
||||
"sdPct",
|
||||
"mdPct",
|
||||
"ls",
|
||||
"ms",
|
||||
"xpb",
|
||||
"lb",
|
||||
"ref",
|
||||
"thorns",
|
||||
"expd",
|
||||
"spd",
|
||||
"atkTier",
|
||||
"poison",
|
||||
"hpBonus",
|
||||
"spRegen",
|
||||
"eSteal",
|
||||
"hprRaw",
|
||||
"sdRaw",
|
||||
"mdRaw",
|
||||
"fDamPct",
|
||||
"wDamPct",
|
||||
"aDamPct",
|
||||
"tDamPct",
|
||||
"eDamPct",
|
||||
"fDefPct",
|
||||
"wDefPct",
|
||||
"aDefPct",
|
||||
"tDefPct",
|
||||
"eDefPct",
|
||||
"spPct1",
|
||||
"spRaw1",
|
||||
"spPct2",
|
||||
"spRaw2",
|
||||
"spPct3",
|
||||
"spRaw3",
|
||||
"spPct4",
|
||||
"spRaw4",
|
||||
"rainbowRaw",
|
||||
"sprint",
|
||||
"sprintReg",
|
||||
"jh",
|
||||
"lq",
|
||||
"gXp",
|
||||
"gSpd"
|
||||
];
|
||||
let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ];
|
||||
let colorMap = new Map(
|
||||
[
|
||||
["Normal", "#fff"],
|
||||
|
@ -305,23 +209,24 @@ let posModSuffixes = {
|
|||
/*
|
||||
* Display commands
|
||||
*/
|
||||
let build_overall_display_commands = [
|
||||
"#table",
|
||||
let build_all_display_commands = [
|
||||
"#defense-stats",
|
||||
"str", "dex", "int", "def", "agi",
|
||||
"!spacer",
|
||||
"mr", "ms",
|
||||
"hprRaw", "hprPct",
|
||||
"ls",
|
||||
"sdRaw", "sdPct",
|
||||
"mdRaw", "mdPct",
|
||||
"ref", "thorns",
|
||||
"ls",
|
||||
"poison",
|
||||
"expd",
|
||||
"spd",
|
||||
"atkTier",
|
||||
"!elemental",
|
||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
||||
"!elemental",
|
||||
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
|
||||
"atkTier",
|
||||
"poison",
|
||||
"ref", "thorns",
|
||||
"expd",
|
||||
"spd",
|
||||
"rainbowRaw",
|
||||
"sprint", "sprintReg",
|
||||
"jh",
|
||||
|
@ -331,26 +236,51 @@ let build_overall_display_commands = [
|
|||
"gXp", "gSpd",
|
||||
];
|
||||
|
||||
let item_display_commands = [
|
||||
"#cdiv",
|
||||
let build_offensive_display_commands = [
|
||||
"str", "dex", "int", "def", "agi",
|
||||
"mr", "ms",
|
||||
"sdRaw", "sdPct",
|
||||
"mdRaw", "mdPct",
|
||||
"ref", "thorns",
|
||||
"ls",
|
||||
"poison",
|
||||
"expd",
|
||||
"spd",
|
||||
"atkTier",
|
||||
"rainbowRaw",
|
||||
"!elemental",
|
||||
"fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct",
|
||||
"!elemental",
|
||||
"spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4",
|
||||
];
|
||||
|
||||
let build_basic_display_commands = [
|
||||
'#defense-stats',
|
||||
// defense stats [hp, ehp, hpr, ]
|
||||
// "sPot", // base * atkspd + spell raws
|
||||
// melee potential
|
||||
// "mPot", // melee% * (base * atkspd) + melee raws
|
||||
"mr", "ms",
|
||||
"ls",
|
||||
"poison",
|
||||
"spd",
|
||||
"atkTier",
|
||||
]
|
||||
|
||||
let sq2_item_display_commands = [
|
||||
"displayName",
|
||||
//"type", //REPLACE THIS WITH SKIN
|
||||
"#ldiv",
|
||||
"atkSpd",
|
||||
"#ldiv",
|
||||
"!elemental",
|
||||
"hp",
|
||||
"nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_",
|
||||
"!spacer",
|
||||
"fDef", "wDef", "aDef", "tDef", "eDef",
|
||||
"!elemental",
|
||||
"#ldiv",
|
||||
"classReq",
|
||||
"lvl",
|
||||
"strReq", "dexReq", "intReq", "defReq","agiReq",
|
||||
"#ldiv",
|
||||
"!spacer",
|
||||
"str", "dex", "int", "def", "agi",
|
||||
"#table",
|
||||
"str", "dex", "int", "def", "agi", //jank lmao
|
||||
"hpBonus",
|
||||
"hprRaw", "hprPct",
|
||||
"sdRaw", "sdPct",
|
||||
|
@ -374,11 +304,33 @@ let item_display_commands = [
|
|||
"spRegen",
|
||||
"eSteal",
|
||||
"gXp", "gSpd",
|
||||
"#ldiv",
|
||||
"majorIds",
|
||||
"!spacer",
|
||||
"slots",
|
||||
"!spacer",
|
||||
"set",
|
||||
"lore",
|
||||
"quest",
|
||||
"restrict"
|
||||
];
|
||||
|
||||
let sq2_ing_display_order = [
|
||||
"displayName", //tier will be displayed w/ name
|
||||
"!spacer",
|
||||
"ids",
|
||||
"!spacer",
|
||||
"posMods",
|
||||
"itemIDs",
|
||||
"consumableIDs",
|
||||
"!spacer",
|
||||
"lvl",
|
||||
"skills",
|
||||
]
|
||||
|
||||
let elem_colors = [
|
||||
"#00AA00",
|
||||
"#FFFF55",
|
||||
"#55FFFF",
|
||||
"#FF5555",
|
||||
"#FFFFFF"
|
||||
]
|
||||
|
|
|
@ -213,14 +213,14 @@ function redraw(data) {
|
|||
let tier_mod = tiers_mod.get(tier);
|
||||
let y_max = baseline_y.map(x => 2.1*x*tier_mod*type_mod);
|
||||
let y_min = baseline_y.map(x => 2.0*x*tier_mod*type_mod);
|
||||
line_top.datum(zip(baseline_x, y_max))
|
||||
line_top.datum(zip2(baseline_x, y_max))
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", d => colorMap.get(tier))
|
||||
.attr("d", d3.line()
|
||||
.x(function(d) { return x(d[0]) })
|
||||
.y(function(d) { return y(d[1]) })
|
||||
)
|
||||
line_bot.datum(zip(baseline_x, y_min))
|
||||
line_bot.datum(zip2(baseline_x, y_min))
|
||||
.attr("fill", "none")
|
||||
.attr("stroke", d => colorMap.get(tier))
|
||||
.attr("d", d3.line()
|
||||
|
|
|
@ -239,7 +239,7 @@ function init_items2() {
|
|||
const itemListFooter = document.getElementById('item-list-footer');
|
||||
|
||||
// compile the search db from the item db
|
||||
const searchDb = items.filter(i => !i.remapID).map(i => [i, expandItem(i, [])]);
|
||||
const searchDb = items.filter(i => !i.remapID).map(i => [i, expandItem(i)]);
|
||||
|
||||
// init item list elements
|
||||
const ITEM_LIST_SIZE = 64;
|
||||
|
|
136
js/load.js
|
@ -6,7 +6,7 @@ let reload = false;
|
|||
let load_complete = false;
|
||||
let load_in_progress = false;
|
||||
let items;
|
||||
let sets;
|
||||
let sets = new Map();
|
||||
let itemMap;
|
||||
let idMap;
|
||||
let redirectMap;
|
||||
|
@ -14,42 +14,46 @@ let itemLists = new Map();
|
|||
/*
|
||||
* Load item set from local DB. Calls init() on success.
|
||||
*/
|
||||
async function load_local(init_func) {
|
||||
async function load_local() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let get_tx = db.transaction(['item_db', 'set_db'], 'readonly');
|
||||
let sets_store = get_tx.objectStore('set_db');
|
||||
let get_store = get_tx.objectStore('item_db');
|
||||
let request = get_store.getAll();
|
||||
request.onerror = function(event) {
|
||||
console.log("Could not read local item db...");
|
||||
reject("Could not read local item db...");
|
||||
}
|
||||
request.onsuccess = function(event) {
|
||||
console.log("Successfully read local item db.");
|
||||
items = request.result;
|
||||
//console.log(items);
|
||||
let request2 = sets_store.openCursor();
|
||||
|
||||
sets = {};
|
||||
request2.onerror = function(event) {
|
||||
console.log("Could not read local set db...");
|
||||
}
|
||||
|
||||
// key-value iteration (hpp don't break this again)
|
||||
// https://stackoverflow.com/questions/47931595/indexeddb-getting-all-data-with-keys
|
||||
let request2 = sets_store.openCursor();
|
||||
request2.onerror = function(event) {
|
||||
reject("Could not read local set db...");
|
||||
}
|
||||
request2.onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (cursor) {
|
||||
sets[cursor.primaryKey] = cursor.value;
|
||||
let key = cursor.primaryKey;
|
||||
let value = cursor.value;
|
||||
sets.set(key, value);
|
||||
cursor.continue();
|
||||
}
|
||||
else {
|
||||
// no more results
|
||||
console.log("Successfully read local set db.");
|
||||
//console.log(sets);
|
||||
}
|
||||
};
|
||||
get_tx.oncomplete = function(event) {
|
||||
items = request.result;
|
||||
init_maps();
|
||||
init_func();
|
||||
load_complete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
await get_tx.complete;
|
||||
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.
|
||||
*/
|
||||
async function load(init_func) {
|
||||
async function load() {
|
||||
|
||||
let getUrl = window.location;
|
||||
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 result = await (await fetch(url)).json();
|
||||
items = result.items;
|
||||
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 sets_ = result.sets;
|
||||
|
||||
let add_tx = db.transaction(['item_db', 'set_db'], 'readwrite');
|
||||
add_tx.onabort = function(e) {
|
||||
|
@ -127,54 +121,47 @@ async function load(init_func) {
|
|||
add_promises.push(req);
|
||||
}
|
||||
let sets_store = add_tx.objectStore('set_db');
|
||||
for (const set in sets) {
|
||||
add_promises.push(sets_store.add(sets[set], set));
|
||||
for (const set in sets_) {
|
||||
add_promises.push(sets_store.add(sets_[set], set));
|
||||
sets.set(set, sets_[set]);
|
||||
}
|
||||
add_promises.push(add_tx.complete);
|
||||
Promise.all(add_promises).then((values) => {
|
||||
|
||||
await Promise.all(add_promises);
|
||||
init_maps();
|
||||
init_func();
|
||||
load_complete = true;
|
||||
});
|
||||
// DB not closed? idfk man
|
||||
db.close();
|
||||
}
|
||||
|
||||
function load_init(init_func) {
|
||||
if (load_complete) {
|
||||
console.log("Item db already loaded, skipping load sequence");
|
||||
init_func();
|
||||
return;
|
||||
}
|
||||
async function load_init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = window.indexedDB.open('item_db', DB_VERSION);
|
||||
|
||||
request.onerror = function() {
|
||||
console.log("DB failed to open...");
|
||||
reject("DB failed to open...");
|
||||
};
|
||||
|
||||
request.onsuccess = function() {
|
||||
(async function() {
|
||||
request.onsuccess = async function() {
|
||||
db = request.result;
|
||||
if (!reload) {
|
||||
console.log("Using stored data...")
|
||||
load_local(init_func);
|
||||
}
|
||||
else {
|
||||
if (load_in_progress) {
|
||||
while (!load_complete) {
|
||||
await sleep(100);
|
||||
}
|
||||
console.log("Skipping load...")
|
||||
init_func();
|
||||
}
|
||||
else {
|
||||
// Not 100% safe... whatever!
|
||||
load_in_progress = true
|
||||
if (reload) {
|
||||
console.log("Using new data...")
|
||||
load(init_func);
|
||||
await load();
|
||||
}
|
||||
else {
|
||||
console.log("Using stored data...")
|
||||
await load_local();
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
resolve();
|
||||
};
|
||||
|
||||
request.onupgradeneeded = function(e) {
|
||||
reload = true;
|
||||
|
@ -198,20 +185,16 @@ function load_init(init_func) {
|
|||
db.createObjectStore('set_db');
|
||||
|
||||
console.log("DB setup complete...");
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function init_maps() {
|
||||
//warp
|
||||
itemMap = new Map();
|
||||
/* Mapping from item names to set names. */
|
||||
idMap = new Map();
|
||||
redirectMap = new Map();
|
||||
for (const it of itemTypes) {
|
||||
// List of 'raw' "none" items (No Helmet, etc), in order helmet, chestplate... ring1, ring2, brace, neck, weapon.
|
||||
for (const it of itemTypes) {
|
||||
itemLists.set(it, []);
|
||||
}
|
||||
}
|
||||
|
||||
let noneItems = [
|
||||
let none_items = [
|
||||
["armor", "helmet", "No Helmet"],
|
||||
["armor", "chestplate", "No Chestplate"],
|
||||
["armor", "leggings", "No Leggings"],
|
||||
|
@ -221,13 +204,13 @@ function init_maps() {
|
|||
["accessory", "bracelet", "No Bracelet"],
|
||||
["accessory", "necklace", "No Necklace"],
|
||||
["weapon", "dagger", "No Weapon"],
|
||||
];
|
||||
for (let i = 0; i < noneItems.length; i++) {
|
||||
];
|
||||
for (let i = 0; i < none_items.length; i++) {
|
||||
let item = Object();
|
||||
item.slots = 0;
|
||||
item.category = noneItems[i][0];
|
||||
item.type = noneItems[i][1];
|
||||
item.name = noneItems[i][2];
|
||||
item.category = none_items[i][0];
|
||||
item.type = none_items[i][1];
|
||||
item.name = none_items[i][2];
|
||||
item.displayName = item.name;
|
||||
item.set = null;
|
||||
item.quest = null;
|
||||
|
@ -245,15 +228,22 @@ function init_maps() {
|
|||
item.aDam = "0-0";
|
||||
clean_item(item);
|
||||
|
||||
noneItems[i] = item;
|
||||
}
|
||||
items = items.concat(noneItems);
|
||||
none_items[i] = item;
|
||||
}
|
||||
|
||||
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);
|
||||
for (const item of items) {
|
||||
if (item.remapID === undefined) {
|
||||
itemLists.get(item.type).push(item.displayName);
|
||||
itemMap.set(item.displayName, item);
|
||||
if (noneItems.includes(item)) {
|
||||
if (none_items.includes(item)) {
|
||||
idMap.set(item.id, "");
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -4,6 +4,7 @@ const ING_DB_VERSION = 13;
|
|||
|
||||
let idb;
|
||||
let ireload = false;
|
||||
let iload_in_progress = false;
|
||||
let iload_complete = false;
|
||||
let ings;
|
||||
let recipes;
|
||||
|
@ -20,32 +21,34 @@ let recipeIDMap;
|
|||
/*
|
||||
* Load item set from local DB. Calls init() on success.
|
||||
*/
|
||||
async function ing_load_local(init_func) {
|
||||
console.log("IngMap is: \n " + ingMap);
|
||||
async function ing_load_local() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
let get_tx = idb.transaction(['ing_db', 'recipe_db'], 'readonly');
|
||||
let ings_store = get_tx.objectStore('ing_db');
|
||||
let recipes_store = get_tx.objectStore('recipe_db');
|
||||
let request3 = ings_store.getAll();
|
||||
request3.onerror = function(event) {
|
||||
console.log("Could not read local ingredient db...");
|
||||
reject("Could not read local ingredient db...");
|
||||
}
|
||||
request3.onsuccess = function(event) {
|
||||
console.log("Successfully read local ingredient db.");
|
||||
ings = request3.result;
|
||||
}
|
||||
let request4 = recipes_store.getAll();
|
||||
request4.onerror = function(event) {
|
||||
console.log("Could not read local recipe db...");
|
||||
reject("Could not read local recipe db...");
|
||||
}
|
||||
request4.onsuccess = function(event) {
|
||||
console.log("Successfully read local recipe db.");
|
||||
}
|
||||
get_tx.oncomplete = function(event) {
|
||||
ings = request3.result;
|
||||
recipes = request4.result;
|
||||
init_ing_maps();
|
||||
init_func();
|
||||
iload_complete = true;
|
||||
}
|
||||
}
|
||||
await get_tx.complete;
|
||||
idb.close();
|
||||
resolve()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clean_ing(ing) {
|
||||
|
@ -59,11 +62,12 @@ function clean_ing(ing) {
|
|||
/*
|
||||
* Load item set from remote DB (aka a big json file). Calls init() on success.
|
||||
*/
|
||||
async function load_ings(init_func) {
|
||||
async function load_ings() {
|
||||
|
||||
let getUrl = window.location;
|
||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
|
||||
let url = baseUrl + "/ingreds_compress.json";
|
||||
let baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
||||
// "Random" string to prevent caching!
|
||||
let url = baseUrl + "/ingreds_compress.json?"+new Date();
|
||||
url = url.replace(/\w+.html/, "") ;
|
||||
let result = await (await fetch(url)).json();
|
||||
|
||||
|
@ -97,35 +101,40 @@ async function load_ings(init_func) {
|
|||
}
|
||||
add_promises.push(add_tx2.complete);
|
||||
add_promises.push(add_tx3.complete);
|
||||
Promise.all(add_promises).then((values) => {
|
||||
|
||||
await Promise.all(add_promises);
|
||||
init_ing_maps();
|
||||
init_func();
|
||||
iload_complete = true;
|
||||
});
|
||||
// DB not closed? idfk man
|
||||
idb.close();
|
||||
}
|
||||
|
||||
function load_ing_init(init_func) {
|
||||
if (iload_complete) {
|
||||
console.log("Ingredient db already loaded, skipping load sequence");
|
||||
init_func();
|
||||
return;
|
||||
}
|
||||
async function load_ing_init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = window.indexedDB.open("ing_db", ING_DB_VERSION)
|
||||
request.onerror = function() {
|
||||
console.log("DB failed to open...");
|
||||
reject("DB failed to open...");
|
||||
}
|
||||
|
||||
request.onsuccess = function() {
|
||||
request.onsuccess = async function() {
|
||||
idb = request.result;
|
||||
if (!ireload) {
|
||||
console.log("Using stored data...")
|
||||
ing_load_local(init_func);
|
||||
if (iload_in_progress) {
|
||||
while (!iload_complete) {
|
||||
await sleep(100);
|
||||
}
|
||||
console.log("Skipping load...")
|
||||
}
|
||||
else {
|
||||
iload_in_progress = true
|
||||
if (ireload) {
|
||||
console.log("Using new data...")
|
||||
load_ings(init_func);
|
||||
await load_ings();
|
||||
}
|
||||
else {
|
||||
console.log("Using stored data...")
|
||||
await ing_load_local();
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
|
||||
request.onupgradeneeded = function(e) {
|
||||
|
@ -150,6 +159,7 @@ function load_ing_init(init_func) {
|
|||
|
||||
console.log("DB setup complete...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init_ing_maps() {
|
||||
|
@ -222,4 +232,5 @@ function init_ing_maps() {
|
|||
recipeList.push(recipe["name"]);
|
||||
recipeIDMap.set(recipe["id"],recipe["name"]);
|
||||
}
|
||||
console.log(ingMap);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
let tdb;
|
||||
|
@ -13,29 +13,31 @@ let tomeLists = new Map();
|
|||
/*
|
||||
* 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_store = get_tx.objectStore('tome_db');
|
||||
let request = get_store.getAll();
|
||||
request.onerror = function(event) {
|
||||
console.log("Could not read local tome db...");
|
||||
reject("Could not read local tome db...");
|
||||
}
|
||||
request.onsuccess = function(event) {
|
||||
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();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 baseUrl = getUrl.protocol + "//" + getUrl.host + "/";// + getUrl.pathname.split('/')[1];
|
||||
|
@ -60,49 +62,42 @@ async function load_tome(init_func) {
|
|||
};
|
||||
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_func();
|
||||
tload_complete = true;
|
||||
});
|
||||
// DB not closed? idfk man
|
||||
tdb.close();
|
||||
}
|
||||
|
||||
function load_tome_init(init_func) {
|
||||
if (tload_complete) {
|
||||
console.log("Tome db already loaded, skipping load sequence");
|
||||
init_func();
|
||||
return;
|
||||
}
|
||||
async function load_tome_init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = window.indexedDB.open('tome_db', TOME_DB_VERSION);
|
||||
|
||||
request.onerror = function() {
|
||||
console.log("DB failed to open...");
|
||||
reject("DB failed to open...");
|
||||
};
|
||||
|
||||
request.onsuccess = function() {
|
||||
(async function() {
|
||||
request.onsuccess = async function() {
|
||||
tdb = request.result;
|
||||
if (!treload) {
|
||||
console.log("Using stored data...")
|
||||
load_tome_local(init_func);
|
||||
}
|
||||
else {
|
||||
if (tload_in_progress) {
|
||||
while (!tload_complete) {
|
||||
await sleep(100);
|
||||
}
|
||||
console.log("Skipping load...")
|
||||
init_func();
|
||||
}
|
||||
else {
|
||||
// Not 100% safe... whatever!
|
||||
tload_in_progress = true
|
||||
if (treload) {
|
||||
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) {
|
||||
|
@ -121,8 +116,14 @@ function load_tome_init(init_func) {
|
|||
|
||||
console.log("DB setup complete...");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let none_tomes = [
|
||||
["tome", "weaponTome", "No Weapon Tome"],
|
||||
["tome", "armorTome", "No Armor Tome"],
|
||||
["tome", "guildTome", "No Guild Tome"]
|
||||
];
|
||||
function init_tome_maps() {
|
||||
//warp
|
||||
tomeMap = new Map();
|
||||
|
@ -130,21 +131,16 @@ function init_tome_maps() {
|
|||
tomeIDMap = new Map();
|
||||
|
||||
tomeRedirectMap = new Map();
|
||||
for (const it of tomeTypes) {
|
||||
for (const it of tome_types) {
|
||||
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++) {
|
||||
let tome = Object();
|
||||
tome.slots = 0;
|
||||
tome.category = noneTomes[i][0];
|
||||
tome.type = noneTomes[i][1];
|
||||
tome.name = noneTomes[i][2];
|
||||
tome.category = none_tomes[i][0];
|
||||
tome.type = none_tomes[i][1];
|
||||
tome.name = none_tomes[i][2];
|
||||
tome.displayName = tome.name;
|
||||
tome.set = null;
|
||||
tome.quest = null;
|
||||
|
@ -163,14 +159,14 @@ function init_tome_maps() {
|
|||
//dependency - load.js
|
||||
clean_item(tome);
|
||||
|
||||
noneTomes[i] = tome;
|
||||
none_tomes[i] = tome;
|
||||
}
|
||||
tomes = tomes.concat(noneTomes);
|
||||
tomes = tomes.concat(none_tomes);
|
||||
for (const tome of tomes) {
|
||||
if (tome.remapID === undefined) {
|
||||
tomeLists.get(tome.type).push(tome.displayName);
|
||||
tomeMap.set(tome.displayName, tome);
|
||||
if (noneTomes.includes(tome)) {
|
||||
if (none_tomes.includes(tome)) {
|
||||
tomeIDMap.set(tome.id, "");
|
||||
}
|
||||
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("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 old_bonus = {};
|
||||
if (setCount) {
|
||||
old_bonus = sets[setName].bonuses[setCount-1];
|
||||
old_bonus = sets.get(setName).bonuses[setCount-1];
|
||||
activeSetCounts.set(setName, setCount + 1);
|
||||
}
|
||||
else {
|
||||
setCount = 0;
|
||||
activeSetCounts.set(setName, 1);
|
||||
}
|
||||
const new_bonus = sets[setName].bonuses[setCount];
|
||||
const new_bonus = sets.get(setName).bonuses[setCount];
|
||||
//let skp_order = ["str","dex","int","def","agi"];
|
||||
for (const i in skp_order) {
|
||||
const delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
||||
|
@ -74,8 +74,8 @@ function calculate_skillpoints(equipment, weapon) {
|
|||
if (setName) { // undefined/null means no set.
|
||||
const setCount = activeSetCounts.get(setName);
|
||||
if (setCount) {
|
||||
const old_bonus = sets[setName].bonuses[setCount-1];
|
||||
const new_bonus = sets[setName].bonuses[setCount];
|
||||
const old_bonus = sets.get(setName).bonuses[setCount-1];
|
||||
const new_bonus = sets.get(setName).bonuses[setCount];
|
||||
//let skp_order = ["str","dex","int","def","agi"];
|
||||
for (const i in skp_order) {
|
||||
const set_delta = (new_bonus[skp_order[i]] || 0) - (old_bonus[skp_order[i]] || 0);
|
||||
|
|
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() {
|
||||
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);
|
||||
|
|
156
js/utils.js
|
@ -1,32 +1,8 @@
|
|||
let getUrl = window.location;
|
||||
const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
|
||||
|
||||
const zip = (a, b) => a.map((k, i) => [k, b[i]]);
|
||||
|
||||
//updates all the OGP tags for a webpage. Should be called when build changes
|
||||
function updateOGP() {
|
||||
//update the embed URL
|
||||
let url_elem = document.getElementById("ogp-url");
|
||||
if (url_elem) {
|
||||
url_elem.content = url_base+location.hash;
|
||||
}
|
||||
|
||||
//update the embed text content
|
||||
let build_elem = document.getElementById("ogp-build-list");
|
||||
if (build_elem && player_build) {
|
||||
let text = "WynnBuilder build:\n"+
|
||||
"> "+player_build.helmet.get("displayName")+"\n"+
|
||||
"> "+player_build.chestplate.get("displayName")+"\n"+
|
||||
"> "+player_build.leggings.get("displayName")+"\n"+
|
||||
"> "+player_build.boots.get("displayName")+"\n"+
|
||||
"> "+player_build.ring1.get("displayName")+"\n"+
|
||||
"> "+player_build.ring2.get("displayName")+"\n"+
|
||||
"> "+player_build.bracelet.get("displayName")+"\n"+
|
||||
"> "+player_build.necklace.get("displayName")+"\n"+
|
||||
"> "+player_build.weapon.get("displayName")+" ["+player_build.weapon.get("powders").map(x => powderNames.get(x)).join("")+"]";
|
||||
build_elem.content = text;
|
||||
}
|
||||
}
|
||||
const zip2 = (a, b) => a.map((k, i) => [k, b[i]]);
|
||||
const zip3 = (a, b, c) => a.map((k, i) => [k, b[i], c[i]]);
|
||||
|
||||
function clamp(num, low, high){
|
||||
return Math.min(Math.max(num, low), high);
|
||||
|
@ -217,9 +193,9 @@ Base64 = (function () {
|
|||
*/
|
||||
read_bit(idx) {
|
||||
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.
|
||||
|
@ -657,3 +633,127 @@ async function hardReload() {
|
|||
function capitalizeFirst(str) {
|
||||
return str[0].toUpperCase() + str.substring(1);
|
||||
}
|
||||
|
||||
/** https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript
|
||||
* If we ever want to write something that needs to import other js files
|
||||
*/
|
||||
const getScript = url => new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = url;
|
||||
script.async = true;
|
||||
|
||||
script.onerror = reject;
|
||||
|
||||
script.onload = script.onreadystatechange = function () {
|
||||
const loadState = this.readyState;
|
||||
|
||||
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
|
||||
|
||||
script.onload = script.onreadystatechange = null;
|
||||
|
||||
resolve();
|
||||
}
|
||||
|
||||
document.head.appendChild(script);
|
||||
})
|
||||
|
||||
/*
|
||||
GENERIC TEST FUNCTIONS
|
||||
*/
|
||||
/** The generic assert function. Fails on all "false-y" values. Useful for non-object equality checks, boolean value checks, and existence checks.
|
||||
*
|
||||
* @param {*} arg - argument to assert.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert(arg, msg) {
|
||||
if (!arg) {
|
||||
throw new Error(msg ? msg : "Assert failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts object equality of the 2 parameters. For loose and strict asserts, use assert().
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_equals(arg1, arg2, msg) {
|
||||
if (!Object.is(arg1, arg2)) {
|
||||
throw new Error(msg ? msg : "Assert Equals failed. " + arg1 + " is not " + arg2 + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts object inequality of the 2 parameters. For loose and strict asserts, use assert().
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_not_equals(arg1, arg2, msg) {
|
||||
if (Object.is(arg1, arg2)) {
|
||||
throw new Error(msg ? msg : "Assert Not Equals failed. " + arg1 + " is " + arg2 + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts proximity between 2 arguments. Should be used for any floating point datatype.
|
||||
*
|
||||
* @param {*} arg1 - first argument to compare.
|
||||
* @param {*} arg2 - second argument to compare.
|
||||
* @param {Number} epsilon - the margin of error (<= del difference is ok). Defaults to -1E5.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_near(arg1, arg2, epsilon = 1E-5, msg) {
|
||||
if (Math.abs(arg1 - arg2) > epsilon) {
|
||||
throw new Error(msg ? msg : "Assert Near failed. " + arg1 + " is not within " + epsilon + " of " + arg2 + ".");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts that the input argument is null.
|
||||
*
|
||||
* @param {*} arg - the argument to test for null.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_null(arg, msg) {
|
||||
if (arg !== null) {
|
||||
throw new Error(msg ? msg : "Assert Near failed. " + arg + " is not null.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts that the input argument is undefined.
|
||||
*
|
||||
* @param {*} arg - the argument to test for undefined.
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_undefined(arg, msg) {
|
||||
if (arg !== undefined) {
|
||||
throw new Error(msg ? msg : "Assert Near failed. " + arg + " is not undefined.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Asserts that there is an error when a callback function is run.
|
||||
*
|
||||
* @param {Function} func_binding - a function binding to run. Can be passed in with func.bind(null, arg1, ..., argn)
|
||||
* @param {String} msg - the error message to throw.
|
||||
*/
|
||||
function assert_error(func_binding, msg) {
|
||||
try {
|
||||
func_binding();
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
throw new Error(msg ? msg : "Function didn't throw an error.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy object/array of basic types.
|
||||
*/
|
||||
function deepcopy(obj) {
|
||||
if (typeof(obj) !== 'object' || obj === null) { // null or value type
|
||||
return obj;
|
||||
}
|
||||
let ret = Array.isArray(obj) ? [] : {};
|
||||
for (let key in obj) {
|
||||
ret[key] = deepcopy(obj[key]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
|
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:
|
||||
|
||||
1. run `python3 dump.py`. This will overwrite `dump.json` and `../ingreds.json`
|
||||
2. Copy `../old clean.json` or `../compress.json` into `updated.json`
|
||||
3. Run `python3 transform_merge.py`
|
||||
4. Run `python3 ing_transform_combine.py`
|
||||
5. Check validity (json differ or whatever)
|
||||
6. Copy `clean.json` and `compress.json` into toplevel for usage
|
||||
1. Get new data from API with `get.py`
|
||||
2. Clean the data (may have to do manually) with the `process` related py files
|
||||
3. Check validity (json differ or whatever)
|
||||
4. Create clean and compress versions and copy them into toplevel for usage (can use `clean_json.py` and `compress_json.py` for this).
|
||||
|
|
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 re
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#looks like something that hpp does with curl
|
||||
|
||||
import os
|
||||
|
||||
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
|
||||
import json
|
||||
infile = sys.argv[1]
|
||||
outfile = sys.argv[2]
|
||||
if len(sys.argv) > 3 and sys.argv[3] == "decompress":
|
||||
json.dump(json.load(open(infile)), open(outfile, "w"), indent=4)
|
||||
else:
|
||||
'''
|
||||
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 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"))
|
||||
|
|
|
@ -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,
|
||||
"Mask of the Spirits": 3649,
|
||||
"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 json
|
||||
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
"""Json diff checker for manual testing."""
|
||||
"""
|
||||
Json diff checker for manual testing - mainly debug
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
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 difflib
|
||||
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
"""
|
||||
Parses a set from a single file.
|
||||
|
||||
Usage: python parse_set_individual.py [infile]
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
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 json
|
||||
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
|
||||
|
||||
with open("../ingreds.json", "r") as infile:
|
||||
ing_data = json.loads(infile.read())
|
||||
ings = ing_data
|
||||
#this data does not have request :)
|
||||
|
||||
import sys
|
||||
import os
|
||||
if os.path.exists("../ing_map.json"):
|
||||
with open("../ing_map.json","r") as ing_mapfile:
|
||||
import base64
|
||||
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)
|
||||
else:
|
||||
ing_map = {ing["name"]: i for i, ing in enumerate(ings)}
|
||||
|
@ -146,8 +161,6 @@ ing_delete_keys = [
|
|||
"skin"
|
||||
]
|
||||
|
||||
print("loaded all files.")
|
||||
|
||||
for ing in ings:
|
||||
for key in ing_delete_keys:
|
||||
if key in ing:
|
||||
|
@ -202,13 +215,10 @@ for ing in ings:
|
|||
print(f'New Ingred: {ing["name"]}')
|
||||
ing["id"] = ing_map[ing["name"]]
|
||||
|
||||
|
||||
with open("../ingreds_clean.json", "w") as outfile:
|
||||
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:
|
||||
#save ing ids
|
||||
with open("ing_map.json", "w+") as ing_mapfile:
|
||||
json.dump(ing_map, ing_mapfile, indent = 2)
|
||||
|
||||
|
||||
print('All ing jsons updated.')
|
||||
#save ings
|
||||
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 sys
|
||||
import os
|
||||
import base64
|
||||
import argparse
|
||||
|
||||
with open("dump.json", "r") as infile:
|
||||
data = json.load(infile)
|
||||
parser = argparse.ArgumentParser(description="Process raw pulled item 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:
|
||||
data = json.loads(in_file.read())
|
||||
|
||||
with open("updated.json", "r") as oldfile:
|
||||
old_data = json.load(oldfile)
|
||||
|
||||
items = data["items"]
|
||||
old_items = old_data["items"]
|
||||
if "request" in data:
|
||||
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 = {
|
||||
#"name": "name",
|
||||
|
@ -141,7 +132,12 @@ delete_keys = [
|
|||
#"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}
|
||||
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()])
|
||||
max_id = 0
|
||||
|
||||
|
@ -150,8 +146,8 @@ known_item_names = set()
|
|||
for item in items:
|
||||
known_item_names.add(item["name"])
|
||||
|
||||
old_items_map = dict()
|
||||
remap_items = []
|
||||
old_items_map = dict()
|
||||
for item in old_items:
|
||||
if "remapID" in item:
|
||||
remap_items.append(item)
|
||||
|
@ -186,16 +182,18 @@ for item in items:
|
|||
item_name = item["displayName"]
|
||||
else:
|
||||
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)
|
||||
|
||||
with open("clean.json", "w") as outfile:
|
||||
json.dump(data, outfile, indent=2)
|
||||
with open("compress.json", "w") as outfile:
|
||||
json.dump(data, outfile)
|
||||
#write items back into data
|
||||
data["items"] = items
|
||||
|
||||
#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)
|