fix merge conflict

This commit is contained in:
ferricles 2022-06-24 21:26:13 -07:00
commit d8d76a0df9
42 changed files with 7851 additions and 8436 deletions

View file

@ -272,7 +272,7 @@
<div class="col-auto order-xl-0 order-1">
<div class="row h-100 dark-shadow dark-6 rounded" id='weapon-dropdown'>
<div class="col-auto px-lg-1 g-0 dark-7 rounded-end my-auto text-center scaled-item-icon" id="weapon-img-loc">
<img id="weapon-img" class="img-fluid rounded" src="../media/items/new/generic-wand.png">
<img id="weapon-img" class="img-fluid rounded" src="../media/items/new/generic-dagger.png">
</div>
<div class="col-3">
<div class="row row-cols-1 h-100 align-items-center">
@ -307,15 +307,17 @@
<input class="equipment-input text-light form-control form-control-sm" id="level-choice" name="level-choice" value="106" placeholder="Build level" value="" tabindex="2"/>
</div>
<div class="col-auto px-1 text-nowrap scaled-font">
<button class="button fw-bold text-light dark-5 scaled-font rounded" id="reset-button" onclick="sq2ResetFields()">Reset</button>
<button class="button fw-bold text-light dark-5 scaled-font rounded" id="reset-button" onclick="resetFields()">Reset</button>
</div>
</div>
<div class="row align-items-center justify-content-center my-1">
<div class="row align-items-center">
<div class="col-auto px-1 text-nowrap scaled-font">
<button class="button fw-bold text-light dark-5 scaled-font rounded" id="copy-button" onclick="copyBuild()">Copy short</button>
<button class="border-dark text-light dark-5 scaled-font rounded" id=copy-button onclick="copyBuild()">Copy short</button>
</div>
<div class="col-auto px-1 text-nowrap scaled-font">
<button class="button fw-bold text-light dark-5 scaled-font rounded" id="share-button" onclick="shareBuild()">Copy for sharing</button>
<button class="border-dark text-light dark-5 scaled-font rounded" id=share-button onclick="shareBuild(player_build)">Copy for sharing</button>
</div>
</div>
</div>
</div>
@ -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>

View file

@ -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 */
}

4099
js/atree_constants.js Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

171
js/atree_constants_old.js Normal file
View 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},
]
}

View file

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

View file

@ -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
View 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
View 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!";
}
}

View file

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

File diff suppressed because it is too large Load diff

1106
js/builder_graph.js Normal file

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -175,6 +175,7 @@ function getCustomFromHash(hash) {
}
}
statMap.set("hash", "CI-" + name);
statMap.set("custom", true);
return new Custom(statMap);
}
} catch (error) {

View file

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

View file

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

File diff suppressed because it is too large Load diff

218
js/display_atree.js Normal file
View 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)
}
}
}

View file

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

View file

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

View file

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

View file

@ -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();
for (const it of itemTypes) {
// List of 'raw' "none" items (No Helmet, etc), in order helmet, chestplate... ring1, ring2, brace, neck, weapon.
for (const it of itemTypes) {
itemLists.set(it, []);
}
}
let noneItems = [
let none_items = [
["armor", "helmet", "No Helmet"],
["armor", "chestplate", "No Chestplate"],
["armor", "leggings", "No Leggings"],
@ -221,13 +203,13 @@ function init_maps() {
["accessory", "bracelet", "No Bracelet"],
["accessory", "necklace", "No Necklace"],
["weapon", "dagger", "No Weapon"],
];
for (let i = 0; i < noneItems.length; i++) {
];
for (let i = 0; i < none_items.length; i++) {
let item = Object();
item.slots = 0;
item.category = noneItems[i][0];
item.type = noneItems[i][1];
item.name = noneItems[i][2];
item.category = none_items[i][0];
item.type = none_items[i][1];
item.name = none_items[i][2];
item.displayName = item.name;
item.set = null;
item.quest = null;
@ -245,15 +227,22 @@ function init_maps() {
item.aDam = "0-0";
clean_item(item);
noneItems[i] = item;
}
items = items.concat(noneItems);
none_items[i] = item;
}
function init_maps() {
//warp
itemMap = new Map();
/* Mapping from item names to set names. */
idMap = new Map();
redirectMap = new Map();
items = items.concat(none_items);
//console.log(items);
for (const item of items) {
if (item.remapID === undefined) {
itemLists.get(item.type).push(item.displayName);
itemMap.set(item.displayName, item);
if (noneItems.includes(item)) {
if (none_items.includes(item)) {
idMap.set(item.id, "");
}
else {

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
media/atree/connect_c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
media/atree/connect_t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
media/atree/node.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

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