fixed merge conflict
|
@ -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 -->
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
@ -418,18 +420,23 @@
|
|||
<div class="row row-cols-1 row-cols-1 text-center scaled-font dark-5 rounded dark-shadow">
|
||||
<div class="col fw-bold dark-4 rounded-top">
|
||||
<div class = "row">
|
||||
<div class = "col-4 py-2">
|
||||
<div class = "col-3 py-2">
|
||||
<button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-tomes" onclick = "toggle_tab('tomes-dropdown'); toggleButton('toggle-tomes')">
|
||||
Show Tomes
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-4 py-2">
|
||||
<div class = "col-3 py-2">
|
||||
<button class = "col-auto button rounded scaled-font fw-bold text-light dark-5" id = "toggle-atree" onclick = "toggle_tab('atree-dropdown'); toggleButton('toggle-atree')">
|
||||
Show Ability Tree
|
||||
</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 = "toggle_tab('edit_id_tab'); toggleButton('edit-ID-button')">
|
||||
Edit IDs
|
||||
</button>
|
||||
</div>
|
||||
<div class = "col-4 py-2">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "edit-ID-button" onclick = "resetEditableIDs(); updateStats();">
|
||||
<div class = "col-3 py-2">
|
||||
<button class = "button rounded scaled-font fw-bold text-light dark-5" id = "edit-ID-button" onclick = "resetEditableIDs();">
|
||||
Reset Edited IDs
|
||||
</button>
|
||||
</div>
|
||||
|
@ -607,6 +614,15 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 border-semi-light rounded dark-9 hide-scroll" id="atree-ui" style="height: 500px; overflow-y: auto;">
|
||||
|
||||
</div>
|
||||
<div class="col mx-auto" id="atree-active">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col dark-6 rounded-bottom my-3 my-xl-1" id = "edit_id_tab" style = "display:none;">
|
||||
<div class = "row big-title justify-content-center">
|
||||
Damage Stats
|
||||
|
@ -617,7 +633,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
|
||||
|
@ -628,7 +644,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
|
||||
|
@ -639,7 +655,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
|
||||
|
@ -650,7 +666,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
|
||||
|
@ -663,7 +679,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
|
||||
|
@ -674,7 +690,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
|
||||
|
@ -685,7 +701,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
|
||||
|
@ -696,7 +712,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
|
||||
|
@ -709,7 +725,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
|
||||
|
@ -720,7 +736,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
|
||||
|
@ -731,7 +747,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
|
||||
|
@ -750,7 +766,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
|
||||
|
@ -761,7 +777,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
|
||||
|
@ -772,7 +788,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
|
||||
|
@ -783,7 +799,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
|
||||
|
@ -796,7 +812,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
|
||||
|
@ -807,7 +823,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
|
||||
|
@ -818,7 +834,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
|
||||
|
@ -829,7 +845,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
|
||||
|
@ -845,7 +861,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
|
||||
|
@ -856,7 +872,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
|
||||
|
@ -867,7 +883,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
|
||||
|
@ -878,7 +894,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
|
||||
|
@ -891,7 +907,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
|
||||
|
@ -902,7 +918,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
|
||||
|
@ -913,7 +929,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
|
||||
|
@ -924,7 +940,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
|
||||
|
@ -942,27 +958,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>
|
||||
|
@ -999,27 +1015,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>
|
||||
|
@ -1029,7 +1045,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>
|
||||
|
@ -1043,27 +1059,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>
|
||||
|
@ -1073,7 +1089,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>
|
||||
|
@ -1087,27 +1103,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>
|
||||
|
@ -1117,7 +1133,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>
|
||||
|
@ -1131,27 +1147,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>
|
||||
|
@ -1161,7 +1177,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>
|
||||
|
@ -1175,27 +1191,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>
|
||||
|
@ -1205,7 +1221,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>
|
||||
|
@ -1240,7 +1256,7 @@
|
|||
<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">
|
||||
|
@ -1289,7 +1305,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>
|
||||
|
@ -1302,7 +1318,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>
|
||||
|
@ -1381,17 +1397,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_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>
|
||||
|
@ -1400,12 +1417,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/display_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>
|
||||
|
|
|
@ -448,3 +448,38 @@ a:hover {
|
|||
.button {
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
/* atree connector rotations */
|
||||
.rotate-90 {
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.rotate-180 {
|
||||
-webkit-transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
-o-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.rotate-270 {
|
||||
-webkit-transform: rotate(270deg);
|
||||
-moz-transform: rotate(270deg);
|
||||
-ms-transform: rotate(270deg);
|
||||
-o-transform: rotate(270deg);
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.hide-scroll {
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.hide-scroll::-webkit-scrollbar {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
4099
js/atree_constants.js
Normal file
2
js/atree_constants_min.js
Normal file
171
js/atree_constants_old.js
Normal file
|
@ -0,0 +1,171 @@
|
|||
const atrees_old = {
|
||||
"Assassin": [
|
||||
{"title": "Spin Attack", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 0, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 1, "col": 4},
|
||||
{"title": "Dagger Proficiency I", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 2, "col": 3},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 2, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 3, "col": 4},
|
||||
{"title": "Double Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 4, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 5, "col": 4},
|
||||
{"title": "Dash", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 3},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 6, "col": 5},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 6, "col": 6},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 2},
|
||||
{"title": "Smoke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 7, "col": 6},
|
||||
{"title": "Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 6},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 3},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 5},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 8, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 1},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 8, "col": 0},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 0},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 0},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 2},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 6},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 6},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 8, "col": 7},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 8, "col": 8},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 8},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 10, "col": 8},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 1},
|
||||
{"title": "Backstab", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 1},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 9, "col": 4},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 90, "row": 10, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 10, "col": 5},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 4},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 10, "col": 7},
|
||||
{"title": "Fatality", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 11, "col": 7},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 11, "col": 0},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 0},
|
||||
{"title": "Violent Vortex", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 0},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 11, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 2},
|
||||
{"title": "Vanish", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 13, "col": 3},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 4},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 13, "col": 6},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 2},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 4},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 12, "col": 7},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 13, "col": 7},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 14, "col": 7},
|
||||
{"title": "Lacerate", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 15, "col": 7},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 15, "col": 1},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 1},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 5},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 5},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 15, "col": 8},
|
||||
{"title": "Wall of Smoke", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 16, "col": 8},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 16, "col": 0},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 0},
|
||||
{"title": "Silent Killer", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 0},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 16, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 2},
|
||||
{"title": "Shadow Travel", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 5},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 5},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 17, "col": 8},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 18, "col": 8},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 4},
|
||||
{"title": "Exploding Clones", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 19, "col": 4},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 3},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 0},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 0},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 3},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 3},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 18, "col": 6},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 18, "col": 7},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 19, "col": 7},
|
||||
{"title": "Weightless", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 20, "col": 7},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 20, "col": 1},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 20, "col": 2},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 1},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 4},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 4},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 20, "col": 6},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 21, "col": 5},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 6},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 20, "col": 8},
|
||||
{"title": "Dancing Blade", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 21, "col": 8},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 21, "col": 0},
|
||||
{"title": "Spin Attack Damage", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 0},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 21, "col": 3},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 22, "col": 3},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 22, "col": 1},
|
||||
{"title": "Marked", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 1},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 4},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 5},
|
||||
{"title": "Shurikens", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 6},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 23, "col": 7},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 22, "col": 8},
|
||||
{"title": "Far Reach", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 23, "col": 8},
|
||||
{"title": "Stronger Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 5},
|
||||
{"title": "Psithurism", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 24, "col": 7},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 1},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 1},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 3},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 24, "col": 4},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 4},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 5},
|
||||
{"title": "Choke Bomb", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 6},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 25, "col": 7},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 25, "col": 8},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 26, "col": 5},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 25, "col": 0},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 0},
|
||||
{"title": "Death Magnet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 0},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 25, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 2},
|
||||
{"title": "Cheaper Multihit", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 2},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 4},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 26, "col": 7},
|
||||
{"title": "Parry", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 27, "col": 7},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 1},
|
||||
{"title": "Fatal Spin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 1},
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 27, "col": 3},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 3},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 27, "col": 6},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 6},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 27, "col": 8},
|
||||
{"title": "Wall Jump", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 28, "col": 8},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 28, "col": 0},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 29, "col": 0},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 1},
|
||||
{"title": "Harvester", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 1},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 4},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 28, "col": 7},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 29, "col": 7},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 30, "col": 7 },
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 30, "col": 2},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 2 },
|
||||
{"image": "../media/atree/connect_t.png", "connector": true, "rotate": 180, "row": 30, "col": 5},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 90, "row": 30, "col": 6},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 5},
|
||||
{"title": "Ricochet", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 31, "col": 8},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 1},
|
||||
{"title": "Satsujin", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 1},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 4},
|
||||
{"title": "Forbidden Art", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 4},
|
||||
{"image": "../media/atree/connect_line.png", "connector": true, "rotate": 0, "row": 31, "col": 7},
|
||||
{"title": "Jasmine Bloom", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 32, "col": 7},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 180, "row": 32, "col": 0},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 0},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 2},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 2},
|
||||
{"image": "../media/atree/connect_angle.png", "connector": true, "rotate": 270, "row": 32, "col": 5},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 5},
|
||||
{"title": "Text", "desc": "desc", "image": "../media/atree/node.png", "connector": false, "row": 33, "col": 8},
|
||||
]
|
||||
}
|
394
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,110 +147,18 @@ 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", "dmgMobs", "defMobs"];
|
||||
|
||||
//Create a map of this build's stats
|
||||
let statMap = new Map();
|
||||
statMap.set("damageMultiplier", 1);
|
||||
statMap.set("defMultiplier", 1);
|
||||
|
||||
for (const staticID of staticIDs) {
|
||||
statMap.set(staticID, 0);
|
||||
|
@ -497,26 +167,35 @@ class Build{
|
|||
|
||||
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)) {
|
||||
if (staticID === "dmgMobs") {
|
||||
statMap.set('damageMultiplier', statMap.get('damageMultiplier') * item_stats.get(staticID));
|
||||
}
|
||||
else if (staticID === "defMobs") {
|
||||
statMap.set('defMultiplier', statMap.get('defMultiplier') * item_stats.get(staticID));
|
||||
}
|
||||
else {
|
||||
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("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,16 +208,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();
|
||||
|
@ -546,10 +217,7 @@ class Build{
|
|||
|
||||
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")));
|
||||
let weapon_stats = this.weapon.statMap;
|
||||
statMap.set("damageRaw", [weapon_stats.get("nDam"), weapon_stats.get("eDam"), weapon_stats.get("tDam"), weapon_stats.get("wDam"), weapon_stats.get("fDam"), weapon_stats.get("aDam")]);
|
||||
}
|
||||
}
|
||||
|
|
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'];
|
218
js/build_encode_decode.js
Normal file
|
@ -0,0 +1,218 @@
|
|||
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];
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 <= 6) {
|
||||
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 <= 6){
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
if (build) {
|
||||
let build_string;
|
||||
|
||||
//V6 encoding - Tomes
|
||||
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;
|
||||
|
||||
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!";
|
||||
}
|
||||
}
|
||||
|
|
@ -51,16 +51,17 @@ 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 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");
|
||||
|
@ -76,3 +77,188 @@ 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"];
|
||||
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" ];
|
||||
|
||||
/**
|
||||
* 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);
|
||||
expandedItem.set("powders", []);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
1324
js/builder.js
1105
js/builder_graph.js
|
@ -1,122 +1,150 @@
|
|||
let _ALL_NODES = new Map();
|
||||
|
||||
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 = true;
|
||||
this.inputs_dirty = new Map();
|
||||
this.inputs_dirty_count = 0;
|
||||
}
|
||||
|
||||
/***
|
||||
/**
|
||||
* 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) {
|
||||
return;
|
||||
}
|
||||
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 = false;
|
||||
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() {
|
||||
if (!this.dirty) {
|
||||
this.dirty = true;
|
||||
for (const child of this.children) {
|
||||
child.mark_input_dirty(this.name);
|
||||
child.mark_dirty();
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
this.inputs_dirty.set(parent_node.name, parent_node.dirty);
|
||||
if (parent_node.dirty) {
|
||||
this.inputs_dirty_count += 1;
|
||||
}
|
||||
parent_node.children.push(this);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -264,7 +264,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!";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,28 +1,9 @@
|
|||
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) {
|
||||
function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier, spellMultiplier, weapon, total_skillpoints, damageMultiplier) {
|
||||
let buildStats = new Map(stats);
|
||||
let tooltipinfo = new Map();
|
||||
//6x for damages, normal min normal max crit min crit max
|
||||
let damageformulas = [["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "],["Min: = ","Max: = ","Min: = ","Max: = "]];
|
||||
|
||||
if(externalStats) { //if nothing is passed in, then this hopefully won't trigger
|
||||
for (const entry of externalStats) {
|
||||
const key = entry[0];
|
||||
const value = entry[1];
|
||||
if (typeof value === "number") {
|
||||
buildStats.set(key, buildStats.get(key) + value);
|
||||
} else if (Array.isArray(value)) {
|
||||
arr = [];
|
||||
for (let j = 0; j < value.length; j++) {
|
||||
arr[j] = buildStats.get(key)[j] + value[j];
|
||||
}
|
||||
buildStats.set(key, arr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let powders = weapon.get("powders").slice();
|
||||
|
||||
|
@ -91,29 +72,20 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
|
|||
damages[element+1][1] += powder.max;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//console.log(tooltipinfo);
|
||||
|
||||
damages[0] = neutralRemainingRaw;
|
||||
tooltipinfo.set("damageBases", damages);
|
||||
|
||||
let damageMult = damageMultiplier;
|
||||
let melee = false;
|
||||
// If we are doing melee calculations:
|
||||
tooltipinfo.set("dmgMult", damageMult);
|
||||
if (spellMultiplier == 0) {
|
||||
spellMultiplier = 1;
|
||||
melee = true;
|
||||
}
|
||||
else {
|
||||
tooltipinfo.set("dmgMult", `(${tooltipinfo.get("dmgMult")} * ${spellMultiplier} * ${baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))]})`)
|
||||
damageMult *= spellMultiplier * baseDamageMultiplier[attackSpeeds.indexOf(buildStats.get("atkSpd"))];
|
||||
}
|
||||
//console.log(damages);
|
||||
//console.log(damageMult);
|
||||
tooltipinfo.set("rawModifier", `(${rawModifier} * ${spellMultiplier} * ${damageMultiplier})`);
|
||||
rawModifier *= spellMultiplier * damageMultiplier;
|
||||
let totalDamNorm = [0, 0];
|
||||
let totalDamCrit = [0, 0];
|
||||
|
@ -126,33 +98,21 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
|
|||
let baseDamCrit = rawModifier * (1 + strBoost);
|
||||
totalDamNorm = [baseDam, baseDam];
|
||||
totalDamCrit = [baseDamCrit, baseDamCrit];
|
||||
for (let arr of damageformulas) {
|
||||
arr = arr.map(x => x + " + " +tooltipinfo.get("rawModifier"));
|
||||
}
|
||||
}
|
||||
let staticBoost = (pctModifier / 100.);
|
||||
tooltipinfo.set("staticBoost", `${(pctModifier/ 100.).toFixed(2)}`);
|
||||
tooltipinfo.set("skillBoost",["","","","","",""]);
|
||||
let skillBoost = [0];
|
||||
for (let i in total_skillpoints) {
|
||||
skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get("damageBonus")[i] / 100.);
|
||||
tooltipinfo.get("skillBoost")[parseInt(i,10)+1] = `(${skillPointsToPercentage(total_skillpoints[i]).toFixed(2)} + ${(buildStats.get("damageBonus")[i]/100.).toFixed(2)})`
|
||||
skillBoost.push(skillPointsToPercentage(total_skillpoints[i]) + buildStats.get(skp_elements[i]+"DamPct") / 100.);
|
||||
}
|
||||
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];
|
||||
|
@ -168,20 +128,13 @@ function calculateSpellDamage(stats, spellConversions, rawModifier, pctModifier,
|
|||
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];
|
||||
return [totalDamNorm, totalDamCrit, damages_results];
|
||||
}
|
||||
|
||||
|
||||
|
|
1436
js/display.js
218
js/display_atree.js
Normal file
|
@ -0,0 +1,218 @@
|
|||
let atree_map;
|
||||
let atree_connectors_map;
|
||||
function construct_AT(elem, tree) {
|
||||
console.log("constructing ability tree UI");
|
||||
document.getElementById("atree-active").innerHTML = ""; //reset all atree actives - should be done in a more general way later
|
||||
elem.innerHTML = ""; //reset the atree in the DOM
|
||||
|
||||
if (tree === undefined) {return false;}
|
||||
|
||||
// add in the "Active" title to atree
|
||||
let active_row = document.createElement("div");
|
||||
active_row.classList.add("row", "item-title", "mx-auto", "justify-content-center");
|
||||
active_row.textContent = "Active:";
|
||||
document.getElementById("atree-active").appendChild(active_row);
|
||||
|
||||
atree_map = new Map();
|
||||
atree_connectors_map = new Map()
|
||||
for (let i of tree) {
|
||||
atree_map.set(i.display_name, {display: i.display, parents: i.parents, connectors: []});
|
||||
}
|
||||
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
let node = tree[i];
|
||||
|
||||
// create rows if not exist
|
||||
let missing_rows = [node.display.row];
|
||||
|
||||
for (let parent of node.parents) {
|
||||
missing_rows.push(tree.find(object => {return object.display_name === parent;}).display.row);
|
||||
}
|
||||
for (let missing_row of missing_rows) {
|
||||
if (document.getElementById("atree-row-" + missing_row) == null) {
|
||||
for (let j = 0; j <= missing_row; j++) {
|
||||
if (document.getElementById("atree-row-" + j) == null) {
|
||||
let row = document.createElement('div');
|
||||
row.classList.add("row");
|
||||
row.id = "atree-row-" + j;
|
||||
//was causing atree rows to be 0 height
|
||||
row.style.minHeight = elem.scrollWidth / 9 + "px";
|
||||
//row.style.minHeight = elem.getBoundingClientRect().width / 9 + "px";
|
||||
|
||||
for (let k = 0; k < 9; k++) {
|
||||
col = document.createElement('div');
|
||||
col.classList.add('col', 'px-0');
|
||||
col.style.minHeight = elem.scrollWidth / 9 + "px";
|
||||
row.appendChild(col);
|
||||
|
||||
atree_connectors_map.set(j + "," + k, [])
|
||||
};
|
||||
elem.appendChild(row);
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
let connector_list = [];
|
||||
// create connectors based on parent location
|
||||
for (let parent of node.parents) {
|
||||
let parent_node = atree_map.get(parent);
|
||||
|
||||
let connect_elem = document.createElement("div");
|
||||
connect_elem.style = "background-size: cover; width: 100%; height: 100%;";
|
||||
// connect up
|
||||
for (let i = node.display.row - 1; i > parent_node.display.row; i--) {
|
||||
let connector = connect_elem.cloneNode();
|
||||
connector.style.backgroundImage = "url('../media/atree/connect_line.png')";
|
||||
atree_map.get(node.display_name).connectors.push(i + "," + node.display.col);
|
||||
atree_connectors_map.get(i + "," + node.display.col).push({connector: connector, type: "line"});
|
||||
resolve_connector(i + "," + node.display.col, node);
|
||||
}
|
||||
// connect horizontally
|
||||
let min = Math.min(parent_node.display.col, node.display.col);
|
||||
let max = Math.max(parent_node.display.col, node.display.col);
|
||||
for (let i = min + 1; i < max; i++) {
|
||||
let connector = connect_elem.cloneNode();
|
||||
connector.style.backgroundImage = "url('../media/atree/connect_line.png')";
|
||||
connector.classList.add("rotate-90");
|
||||
atree_map.get(node.display_name).connectors.push(parent_node.display.row + "," + i);
|
||||
atree_connectors_map.get(parent_node.display.row + "," + i).push({connector: connector, type: "line"});
|
||||
resolve_connector(parent_node.display.row + "," + i, node);
|
||||
}
|
||||
|
||||
// connect corners
|
||||
|
||||
if (parent_node.display.row != node.display.row && parent_node.display.col != node.display.col) {
|
||||
let connector = connect_elem.cloneNode();
|
||||
connector.style.backgroundImage = "url('../media/atree/connect_angle.png')";
|
||||
atree_map.get(node.display_name).connectors.push(parent_node.display.row + "," + node.display.col);
|
||||
atree_connectors_map.get(parent_node.display.row + "," + node.display.col).push({connector: connector, type: "angle"});
|
||||
if (parent_node.display.col > node.display.col) {
|
||||
connector.classList.add("rotate-180");
|
||||
}
|
||||
else {// if (parent_node.display.col < node.display.col && (parent_node.display.row != node.display.row)) {
|
||||
connector.classList.add("rotate-270");
|
||||
}
|
||||
resolve_connector(parent_node.display.row + "," + node.display.col, node);
|
||||
}
|
||||
}
|
||||
|
||||
// create node
|
||||
let node_elem = document.createElement('div')
|
||||
node_elem.style = "background-image: url('../media/atree/node.png'); background-size: cover; width: 100%; height: 100%;";
|
||||
|
||||
// add tooltip
|
||||
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 * .2 / 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("fake-button");
|
||||
|
||||
let active_tooltip = document.createElement('div');
|
||||
active_tooltip.classList.add("rounded-bottom", "dark-7", "border", "mb-2", "mx-auto");
|
||||
//was causing active element boxes to be 0 width
|
||||
// active_tooltip.style.width = elem.getBoundingClientRect().width * .80 + "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.innerHTML = node.display_name;
|
||||
|
||||
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.display_name.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.display_name.replaceAll(" ", ""));
|
||||
if (tooltip.style.display == "block") {
|
||||
tooltip.style.display = "none";
|
||||
this.classList.remove("atree-selected");
|
||||
this.style.backgroundImage = 'url("../media/atree/node.png")';
|
||||
}
|
||||
else {
|
||||
tooltip.style.display = "block";
|
||||
this.classList.add("atree-selected");
|
||||
this.style.backgroundImage = 'url("../media/atree/node-selected.png")';
|
||||
}
|
||||
});
|
||||
document.getElementById("atree-row-" + node.display.row).children[node.display.col].appendChild(node_elem);
|
||||
};
|
||||
|
||||
atree_render_connection();
|
||||
};
|
||||
|
||||
// resolve connector conflict
|
||||
function resolve_connector(pos, node) {
|
||||
if (atree_connectors_map.get(pos).length < 2) {return false;}
|
||||
|
||||
let line = false;
|
||||
let angle = false;
|
||||
let t = false;
|
||||
for (let i of atree_connectors_map.get(pos)) {
|
||||
if (i.type == "line") {
|
||||
line += true;
|
||||
} else if (i.type == "angle") {
|
||||
angle += true;
|
||||
} else if (i.type == "t") {
|
||||
t += true;
|
||||
}
|
||||
}
|
||||
|
||||
let connect_elem = document.createElement("div");
|
||||
|
||||
if ((line && angle)) {
|
||||
connect_elem.style = "background-image: url('../media/atree/connect_t.png'); background-size: cover; width: 100%; height: 100%;"
|
||||
connect_elem.classList.add("rotate-180")
|
||||
atree_connectors_map.set(pos, [{connector: connect_elem, type: "t"}])
|
||||
}
|
||||
if (node.parents.length == 3 && t && atree_same_row(node)) {
|
||||
connect_elem.style = "background-image: url('../media/atree/connect_c.png'); background-size: cover; width: 100%; height: 100%;"
|
||||
atree_connectors_map.set(pos, [{connector: connect_elem, type: "c"}])
|
||||
}
|
||||
// override the conflict with the first children
|
||||
atree_connectors_map.set(pos, [atree_connectors_map.get(pos)[0]])
|
||||
}
|
||||
|
||||
// check if a node doesn't have same row w/ its parents (used to solve conflict)
|
||||
function atree_same_row(node) {
|
||||
for (let i of node.parents) {
|
||||
if (node.display.row == atree_map.get(i).display.row) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// draw the connector onto the screen
|
||||
function atree_render_connection() {
|
||||
for (let i of atree_connectors_map.keys()) {
|
||||
if (atree_connectors_map.get(i).length != 0) {
|
||||
document.getElementById("atree-row-" + i.split(",")[0]).children[i.split(",")[1]].appendChild(atree_connectors_map.get(i)[0].connector)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
121
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];
|
||||
|
@ -101,16 +105,6 @@ async function load(init_func) {
|
|||
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 add_tx = db.transaction(['item_db', 'set_db'], 'readwrite');
|
||||
add_tx.onabort = function(e) {
|
||||
console.log(e);
|
||||
|
@ -131,50 +125,42 @@ async function load(init_func) {
|
|||
add_promises.push(sets_store.add(sets[set], 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 +184,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();
|
||||
// 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"],
|
||||
|
@ -222,12 +204,12 @@ function init_maps() {
|
|||
["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 +227,22 @@ function init_maps() {
|
|||
item.aDam = "0-0";
|
||||
clean_item(item);
|
||||
|
||||
noneItems[i] = item;
|
||||
none_items[i] = item;
|
||||
}
|
||||
items = items.concat(noneItems);
|
||||
|
||||
function init_maps() {
|
||||
//warp
|
||||
itemMap = new Map();
|
||||
/* Mapping from item names to set names. */
|
||||
idMap = new Map();
|
||||
redirectMap = new Map();
|
||||
items = items.concat(none_items);
|
||||
//console.log(items);
|
||||
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 = 2;
|
||||
// @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 {
|
||||
|
|
89
js/optimize.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
function optimizeStrDex() {
|
||||
if (!player_build) {
|
||||
return;
|
||||
}
|
||||
const remaining = levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints;
|
||||
const base_skillpoints = player_build.base_skillpoints;
|
||||
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
|
||||
|
||||
const base_total_skillpoints = player_build.total_skillpoints;
|
||||
let str_bonus = remaining;
|
||||
let dex_bonus = 0;
|
||||
let best_skillpoints = player_build.total_skillpoints;
|
||||
let best_damage = 0;
|
||||
for (let i = 0; i <= remaining; ++i) {
|
||||
let total_skillpoints = base_total_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 _results = calculateSpellDamage(stats, part.conversion,
|
||||
stats.get("sdRaw"), stats.get("sdPct"),
|
||||
part.multiplier / 100, player_build.weapon.statMap, total_skillpoints, 1);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
532
js/sq2bs.js
|
@ -1,532 +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";
|
||||
};
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// 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();
|
||||
},
|
||||
},
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
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);
|
||||
|
|
138
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);
|
||||
|
@ -413,3 +389,113 @@ 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.");
|
||||
}
|
||||
|
|
BIN
media/atree/connect_angle.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
media/atree/connect_c.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
media/atree/connect_line.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
media/atree/connect_t.png
Normal file
After Width: | Height: | Size: 692 B |
BIN
media/atree/node-blocked.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
media/atree/node-selected.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
media/atree/node.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
media/audio/bruh_sound_effect.mp3
Normal file
20
tomes.json
|
@ -9,6 +9,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 60,
|
||||
"defmobs": 3,
|
||||
"thorns": 6,
|
||||
"ref": 6,
|
||||
"hpBonus": 120,
|
||||
|
@ -23,6 +24,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 5,
|
||||
"thorns": 8,
|
||||
"ref": 8,
|
||||
"fixID": false,
|
||||
|
@ -36,6 +38,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 60,
|
||||
"defMobs": 3,
|
||||
"exploding": 5,
|
||||
"mdPct": 5,
|
||||
"hpBonus": 120,
|
||||
|
@ -50,6 +53,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 5,
|
||||
"thorns": 6,
|
||||
"reflection": 6,
|
||||
"fixID": false,
|
||||
|
@ -63,6 +67,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 60,
|
||||
"defMobs": 3,
|
||||
"sdPct": 5,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
|
@ -76,6 +81,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 5,
|
||||
"sdPct": 6,
|
||||
"fixID": false,
|
||||
"id": 5
|
||||
|
@ -88,6 +94,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 60,
|
||||
"defMobs": 3,
|
||||
"hprRaw": 15,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
|
@ -101,6 +108,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 5,
|
||||
"hprRaw": 60,
|
||||
"fixID": false,
|
||||
"id": 7
|
||||
|
@ -113,6 +121,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 60,
|
||||
"defMobs": 3,
|
||||
"ls": 25,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
|
@ -126,6 +135,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 5,
|
||||
"ls": 85,
|
||||
"fixID": false,
|
||||
"id": 9
|
||||
|
@ -138,6 +148,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 60,
|
||||
"defMobs": 3,
|
||||
"lb": 5,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
|
@ -151,6 +162,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 5,
|
||||
"lb": 6,
|
||||
"fixID": false,
|
||||
"id": 11
|
||||
|
@ -163,6 +175,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 60,
|
||||
"defMobs": 3,
|
||||
"spd": 5,
|
||||
"hpBonus": 120,
|
||||
"fixID": false,
|
||||
|
@ -176,6 +189,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 5,
|
||||
"spd": 6,
|
||||
"fixID": false,
|
||||
"id": 13
|
||||
|
@ -188,6 +202,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 8,
|
||||
"eDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
|
@ -201,6 +216,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 8,
|
||||
"tDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
|
@ -214,6 +230,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 8,
|
||||
"wDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
|
@ -227,6 +244,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 8,
|
||||
"fDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
|
@ -240,6 +258,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 8,
|
||||
"aDefPct": 10,
|
||||
"hpBonus": 150,
|
||||
"fixID": false,
|
||||
|
@ -253,6 +272,7 @@
|
|||
"drop": "never",
|
||||
"restrict": "Soulbound Item",
|
||||
"lvl": 100,
|
||||
"defMobs": 8,
|
||||
"eDefPct": 6,
|
||||
"tDefPct": 6,
|
||||
"wDefPct": 6,
|
||||
|
|