diff --git a/.gitignore b/.gitignore
index ce62501..bf43729 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,6 @@ sets/
.idea/
*.iml
+node_modules/
+package.json
+package-lock.json
diff --git a/media/icons/new/copy.png b/media/icons/new/copy.png
new file mode 100644
index 0000000..ec5b053
Binary files /dev/null and b/media/icons/new/copy.png differ
diff --git a/media/icons/new/save.png b/media/icons/new/save.png
new file mode 100644
index 0000000..f553932
Binary files /dev/null and b/media/icons/new/save.png differ
diff --git a/normalize.css b/normalize.css
new file mode 100644
index 0000000..192eb9c
--- /dev/null
+++ b/normalize.css
@@ -0,0 +1,349 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+ ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+html {
+ line-height: 1.15; /* 1 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/* Sections
+ ========================================================================== */
+
+/**
+ * Remove the margin in all browsers.
+ */
+
+body {
+ margin: 0;
+}
+
+/**
+ * Render the `main` element consistently in IE.
+ */
+
+main {
+ display: block;
+}
+
+/**
+ * Correct the font size and margin on `h1` elements within `section` and
+ * `article` contexts in Chrome, Firefox, and Safari.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * 1. Add the correct box sizing in Firefox.
+ * 2. Show the overflow in Edge and IE.
+ */
+
+hr {
+ box-sizing: content-box; /* 1 */
+ height: 0; /* 1 */
+ overflow: visible; /* 2 */
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+pre {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Remove the gray background on active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * 1. Remove the bottom border in Chrome 57-
+ * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+ */
+
+abbr[title] {
+ border-bottom: none; /* 1 */
+ text-decoration: underline; /* 2 */
+ text-decoration: underline dotted; /* 2 */
+}
+
+/**
+ * Add the correct font weight in Chrome, Edge, and Safari.
+ */
+
+b,
+strong {
+ font-weight: bolder;
+}
+
+/**
+ * 1. Correct the inheritance and scaling of font size in all browsers.
+ * 2. Correct the odd `em` font sizing in all browsers.
+ */
+
+code,
+kbd,
+samp {
+ font-family: monospace, monospace; /* 1 */
+ font-size: 1em; /* 2 */
+}
+
+/**
+ * Add the correct font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` elements from affecting the line height in
+ * all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+sup {
+ top: -0.5em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove the border on images inside links in IE 10.
+ */
+
+img {
+ border-style: none;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * 1. Change the font styles in all browsers.
+ * 2. Remove the margin in Firefox and Safari.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ font-family: inherit; /* 1 */
+ font-size: 100%; /* 1 */
+ line-height: 1.15; /* 1 */
+ margin: 0; /* 2 */
+}
+
+/**
+ * Show the overflow in IE.
+ * 1. Show the overflow in Edge.
+ */
+
+button,
+input { /* 1 */
+ overflow: visible;
+}
+
+/**
+ * Remove the inheritance of text transform in Edge, Firefox, and IE.
+ * 1. Remove the inheritance of text transform in Firefox.
+ */
+
+button,
+select { /* 1 */
+ text-transform: none;
+}
+
+/**
+ * Correct the inability to style clickable types in iOS and Safari.
+ */
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+ -webkit-appearance: button;
+}
+
+/**
+ * Remove the inner border and padding in Firefox.
+ */
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+ border-style: none;
+ padding: 0;
+}
+
+/**
+ * Restore the focus styles unset by the previous rule.
+ */
+
+button:-moz-focusring,
+[type="button"]:-moz-focusring,
+[type="reset"]:-moz-focusring,
+[type="submit"]:-moz-focusring {
+ outline: 1px dotted ButtonText;
+}
+
+/**
+ * Correct the padding in Firefox.
+ */
+
+fieldset {
+ padding: 0.35em 0.75em 0.625em;
+}
+
+/**
+ * 1. Correct the text wrapping in Edge and IE.
+ * 2. Correct the color inheritance from `fieldset` elements in IE.
+ * 3. Remove the padding so developers are not caught out when they zero out
+ * `fieldset` elements in all browsers.
+ */
+
+legend {
+ box-sizing: border-box; /* 1 */
+ color: inherit; /* 2 */
+ display: table; /* 1 */
+ max-width: 100%; /* 1 */
+ padding: 0; /* 3 */
+ white-space: normal; /* 1 */
+}
+
+/**
+ * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+ */
+
+progress {
+ vertical-align: baseline;
+}
+
+/**
+ * Remove the default vertical scrollbar in IE 10+.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * 1. Add the correct box sizing in IE 10.
+ * 2. Remove the padding in IE 10.
+ */
+
+[type="checkbox"],
+[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Correct the cursor style of increment and decrement buttons in Chrome.
+ */
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Correct the odd appearance in Chrome and Safari.
+ * 2. Correct the outline style in Safari.
+ */
+
+[type="search"] {
+ -webkit-appearance: textfield; /* 1 */
+ outline-offset: -2px; /* 2 */
+}
+
+/**
+ * Remove the inner padding in Chrome and Safari on macOS.
+ */
+
+[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * 1. Correct the inability to style clickable types in iOS and Safari.
+ * 2. Change font properties to `inherit` in Safari.
+ */
+
+::-webkit-file-upload-button {
+ -webkit-appearance: button; /* 1 */
+ font: inherit; /* 2 */
+}
+
+/* Interactive
+ ========================================================================== */
+
+/*
+ * Add the correct display in Edge, IE 10+, and Firefox.
+ */
+
+details {
+ display: block;
+}
+
+/*
+ * Add the correct display in all browsers.
+ */
+
+summary {
+ display: list-item;
+}
+
+/* Misc
+ ========================================================================== */
+
+/**
+ * Add the correct display in IE 10+.
+ */
+
+template {
+ display: none;
+}
+
+/**
+ * Add the correct display in IE 10.
+ */
+
+[hidden] {
+ display: none;
+}
diff --git a/sq2.css b/sq2.css
new file mode 100644
index 0000000..8555546
--- /dev/null
+++ b/sq2.css
@@ -0,0 +1,440 @@
+html {
+ height: 100%;
+}
+
+* {
+ font-family: 'Nunito', sans-serif;
+}
+
+input::-webkit-calendar-picker-indicator {
+ display: none !important;
+}
+
+input {
+ -webkit-appearance : none;
+ border-radius : 0;
+}
+
+textarea, input { outline: none; }
+
+body {
+ height: 100%;
+ overflow: hidden; /* makes the body non-scrollable (we will add scrolling to the sidebar and main content containers) */
+ margin: 0; /* removes default style */
+ display: flex; /* enables flex content for its children */
+ box-sizing: border-box;
+ font-family: 'Nunito', sans-serif;
+}
+
+/* Works on Firefox */
+* {
+ scrollbar-width: thin;
+ scrollbar-color: rgb(30, 30, 30) rgb(45, 45, 45);
+}
+
+/* Works on Chrome, Edge, and Safari */
+*::-webkit-scrollbar {
+ width: 5px;
+}
+
+*::-webkit-scrollbar-track {
+ background: rgb(45, 45, 45);
+}
+
+*::-webkit-scrollbar-thumb {
+ background-color: rgb(30, 30, 30);
+}
+
+.column {
+ height: 100%; /* allows both columns to span the full height of the browser window */
+ display: flex;
+ flex-direction: column; /* places the left and right headers above the bottom content */
+}
+
+#left { /* makes sure that content is not cut off in a smaller browser window */
+ flex-shrink: 1;
+ background-color: rgb(30, 30, 30);
+}
+
+#right {
+ flex-grow: 1;
+ background-color: rgb(40, 40, 40);
+ color: rgb(240, 240, 240);
+ padding: 2%;
+}
+
+ul {
+ list-style: none;
+ padding: 0;
+}
+
+img.item-icon {
+ padding: 0;
+ margin: 0;
+ border: none;
+}
+
+.bottom {
+ flex-grow: 1; /* ensures that the container will take up the full height of the parent container */
+ overflow-y: auto;
+ overflow-x: auto;
+}
+
+table {
+ vertical-align: top;
+}
+
+.full-border, .box {
+ border: .3vw solid rgb(45, 45, 45);
+}
+
+.se-border {
+ border-left: .3vw solid rgb(45, 45, 45);
+ border-right: .3vw solid rgb(45, 45, 45);
+}
+
+td {
+ padding: 0;
+ margin: 0;
+ white-space: nowrap;
+ font-size: .8vw;
+}
+
+p {
+ padding: 0;
+ margin: 0;
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+input {
+ background-color: rgb(40, 40, 40);
+ border-top: .15vw solid rgb(25, 25, 25) !important;
+ border-left: .15vw solid rgb(25, 25, 25) !important;
+ border-bottom: .15vw solid rgb(45, 45, 45) !important;
+ border-right: .15vw solid rgb(45, 45, 45) !important;
+ box-sizing: border-box !important;
+ color: rgb(240, 240, 240);
+ min-width: 0;
+ min-height: 0;
+ font-size: .8vw;
+ box-sizing: none;
+}
+
+input.item-name {
+ text-align: center;
+ font-weight: bold;
+ width: 10vw;
+ height: 1.2vw;
+}
+
+input.search-field {
+ text-align: center;
+ font-weight: bold;
+ width: 9em;
+}
+
+.equipment-container {
+ background-color: rgb(30, 30, 30);
+ flex-direction: column;
+ display: inline-flex;
+}
+
+.all-equipment {
+ display: inline-flex;
+ flex-direction: column;
+}
+
+.weapon-container {
+ display: inline-flex;
+ background-color: rgb(30, 30, 30);
+}
+
+.skp-container {
+ width: 38vw;
+ height: 10vw;
+ display: inline-flex;
+ flex-direction: column;
+ background-color: rgb(30, 30, 30);
+}
+
+td.mono-font {
+ font-family: 'Courier New', Courier, monospace;
+}
+
+td.damage-size {
+ width: 5vw;
+ user-select: none;
+}
+
+.potency {
+ width: 36em;
+ user-select: none;
+}
+
+.skp-text {
+ width: 7.75em;
+}
+
+.skp-input {
+ width: 6vw;
+ height: 1.2vw;
+ text-align: center;
+}
+
+.center-screen {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.powder-input {
+ text-align: center;
+ font-family: 'Courier New', Courier, monospace;
+ font-weight: bold;
+ width: 10vw;
+ height: 1.2vw;
+}
+
+.skp-tooltip {
+ font-size: .6vw;
+}
+
+.spell-container {
+ display: inline-flex;
+ vertical-align: top;
+ background-color: rgb(30, 30, 30);
+ width: 18vw;
+ height: 24.5vw;
+}
+
+.draggable {
+ position: absolute !important;
+ z-index: 99;
+}
+
+.draggable-header {
+ cursor: move;
+ z-index: 10;
+ display: inline-flex;
+ flex-direction: row;
+ text-align: center;
+}
+p.Damage {
+ color: rgb(255, 198, 85)
+}
+
+.Set {
+ display: inline;
+ color: #5f5;
+}
+
+.Mana { color: #5ff;}
+.Mana:after { content: "\273A"}
+
+.left {
+ text-align: left;
+}
+
+.right {
+ text-align: right;
+}
+
+.center {
+ text-align: center;
+}
+
+.f-w {
+ width: 100%;
+}
+
+.warning {
+ color: red;
+}
+
+.shaded-table {
+ background-color: rgb(30, 30, 30);
+ padding-top: 2px;
+ padding-bottom: 2px;
+
+ border-bottom: 1px solid rgb(45, 45, 45)
+}
+
+.spacer-table {
+ padding: 8px;
+}
+
+.minimal-stats-container {
+ display: inline-flex;
+ flex-direction: column;
+ vertical-align: top;
+ background-color: rgb(30, 30, 30);
+ width: 18vw;
+ height: 24.5vw;
+}
+
+.nDam {
+ color: #FFAA00;
+}
+
+.eDam, .Earth, .Earth_powder {
+ color: #00AA00;
+}
+
+.Earth:before, .Earth_powder:before { content: "\2724" ' '; }
+
+.tDam, .Thunder, .Thunder_powder {
+ color: #FFFF55;
+}
+
+.Thunder:before, .Thunder_powder:before { content: "\2726" ' '; }
+
+.wDam, .Water, .Water_powder {
+ color: #55FFFF
+}
+
+.Water:before, .Water_powder:before { content: "\2749" ' '; }
+
+.fDam, .Fire, .Fire_powder {
+ color: #FF5555;
+}
+
+.Fire:before, .Fire_powder:before { content: "\2739" ' '; }
+
+.aDam, .Air, .Air_powder {
+ color: #FFFFFF
+}
+
+.Air:before, .Air_powder:before { content: "\274b" ' '; }
+
+.Neutral { color: #fa0; }
+.Neutral:before { content: "\2724" ' '; }
+.Damage { color: rgb(255, 198, 85)}
+
+.Health {
+ color: #AA0000
+}
+
+.Health:before {
+ content: "\2764" ' ';
+}
+
+.Normal {
+ color: #FFFFFF;
+}
+
+.Unique {
+ color: #FFFF55;
+}
+
+.Rare {
+ color: #FF55FF;
+}
+
+.Legendary {
+ color: #55FFFF;
+}
+
+.Fabled {
+ color: #FF5555;
+}
+
+.Mythic {
+ color: #AA00AA
+}
+
+p.no-newline {
+ display: inline;
+}
+
+.clickable {
+ cursor: pointer;
+}
+
+.small-text {
+ font-size: 12px;
+}
+
+.lvl {
+ color: #d4d4d4
+}
+
+.lvl:before {
+ content: "Lv. "
+}
+
+button {
+ background-color: rgb(30, 30, 30);
+ color: white;
+ border: none;
+ cursor: pointer;
+}
+
+.positive {
+ color: #5f5;
+ /*text-shadow: 2px 2px 0 #153f15;*/
+}
+
+.negative {
+ color: #f55;
+ /*text-shadow: 2px 2px 0 #1f1515;*/
+}
+
+.item-margin {
+ margin-top: .8vw;
+ margin-bottom: .8vw;
+}
+
+.item-tooltip {
+ width: 15rem;
+ background-color: rgb(30, 30, 30);
+ color: white;
+ font-size: 0.8rem;
+}
+
+.window-container {
+ display: inline-flex;
+ flex-direction: column;
+ background-color: rgb(30, 30, 30);
+ color: white;
+}
+
+.window-header {
+ display: flex;
+ cursor: move;
+ background-color: rgb(45, 45, 45);
+ font-size: .8vw;
+}
+
+.search-result-container {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ overflow-y: auto;
+ overflow-x: hidden;
+ flex-basis: 30vw;
+ max-width: 14.4vw;
+}
+
+.button-boost {
+ background-color: rgb(45, 45, 45);
+ width: 10rem;
+ border: 5px solid rgb(50, 50, 50)
+}
+
+button.toggleOn{
+ background-color:#0a0;
+ border: 3px solid rgb(0, 70, 0);
+}
+
+.damageSubtitle {
+ text-align: center;
+}
+
+.itemp {
+ font-size: .8vw;
+}
+/*
+div:not(.item-tooltip) {
+ font-size: .8vw;
+}*/
diff --git a/sq2.html b/sq2.html
new file mode 100644
index 0000000..59f2aa2
--- /dev/null
+++ b/sq2.html
@@ -0,0 +1,1155 @@
+
+
+
+ WynnBuilder^2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Class boosts |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Quake |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Chain Lightning |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Curse |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Courage |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+ Wind Prison |
+
+
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+ ✤Strength
+
+
+ Manually Assigned: 0
+
+
+ Original Value: 0
+
+
+
+ |
+
+ ✦Dexterity
+
+
+ Manually Assigned: 0
+
+
+ Original Value: 0
+
+
+
+ |
+
+ ❉Intelligence
+
+
+ Manually Assigned: 0
+
+
+ Original Value: 0
+
+
+
+ |
+
+ ✹Defense
+
+
+ Manually Assigned: 0
+
+
+ Original Value: 0
+
+
+
+ |
+
+ ❋Agility
+
+
+ Manually Assigned: 0
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ Basic |
+ Offensive |
+ Defensive |
+
+
+
+
+
+
+
+
+
+
+ melee
+ |
+
+
+
+ poison
+ |
+
+
+
+ spell1
+ |
+
+
+
+ spell2
+ |
+
+
+
+ spell3
+ |
+
+
+
+ spell4
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Damage values:
+ |
+
+
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+ Defense values:
+ |
+
+
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+ Utility values:
+ |
+
+
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+ Original Value: 0
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sq2.js b/sq2.js
new file mode 100644
index 0000000..1b30f5f
--- /dev/null
+++ b/sq2.js
@@ -0,0 +1,347 @@
+let equipment_keys = ['weapon', 'helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace'];
+
+$(document).ready(function(){
+ // inits
+
+ $("#overall-window").toggle();
+ $("#search-container").toggle();
+ $("#boost-container").toggle();
+
+ // pot/base damage switch for weap display
+ /*
+ $(".damage-size").click(function(){
+ $(".damage-size").hide();
+ $(".potency").show();
+ });
+ $(".potency").click(function(){
+ $(".potency").hide();
+ $(".damage-size").show();
+ });*/
+
+ // windows
+ $("#overall-window").draggable({
+ handle: '#overall-window-header',
+ }).resizable({
+ alsoResize: "#all-stats",
+ handles: 'n, e, s ,w'
+ });
+
+ $("#search-container").draggable({
+ handle: '#search-container-header',
+ });
+
+ $("#boost-container").draggable({
+ handle: '#boost-container-header',
+ });
+
+ // window priority
+ $("#overall-window").mousedown(function() {
+ $(".window-container").css("z-index", 10);
+ $(this).css("z-index", 11);
+ });
+
+ $("#search-container").mousedown(function() {
+ $(".window-container").css("z-index", 10);
+ $(this).css("z-index", 11);
+ });
+
+ $("#boost-container").mousedown(function() {
+ $(".window-container").css("z-index", 10);
+ $(this).css("z-index", 11);
+ });
+
+ // update builds
+ jQuery(document).on("input", '.skp-input', function(event){
+ updateStatSchedule();
+ });
+
+ jQuery(document).on("input", '.search-field', function(event){
+ doSearchSchedule();
+ });
+
+ // set listeners/checks
+ $("#weapon-choice").on('input', function(){
+ set_input_style('weapon');
+ calcBuildSchedule();
+ update_powder_count('weapon');
+ });
+
+ $("#weapon-powder").on('input', function(){
+ calcBuildSchedule();
+ });
+
+ $("#helmet-choice").on('input', function(){
+ set_input_style('helmet');
+ calcBuildSchedule();
+ update_powder_count('helmet', '|example: t6t6');
+ });
+
+ $("#helmet-powder").on('input', function(){
+ calcBuildSchedule();
+ });
+
+ $("#chestplate-choice").on('input', function(){
+ set_input_style('chestplate');
+ calcBuildSchedule();
+ update_powder_count('chestplate');
+ });
+
+ $("#chestplate-powder").on('input', function(){
+ calcBuildSchedule();
+ });
+
+ $("#leggings-choice").on('input', function(){
+ set_input_style('leggings');
+ calcBuildSchedule();
+ update_powder_count('leggings');
+ });
+
+ $("#leggings-powder").on('input', function(){
+ calcBuildSchedule();
+ });
+
+ $("#boots-choice").on('input', function(){
+ set_input_style('boots');
+ calcBuildSchedule();
+ update_powder_count('boots');
+ });
+
+ $("#boots-powder").on('input', function(){
+ calcBuildSchedule();
+ });
+
+ $("#ring1-choice").on('input', function(){
+ set_input_style('ring1');
+ calcBuildSchedule();
+ });
+
+ $("#ring2-choice").on('input', function(){
+ set_input_style('ring2');
+ calcBuildSchedule();
+ });
+
+ $("#bracelet-choice").on('input', function(){
+ set_input_style('bracelet');
+ calcBuildSchedule();
+ });
+
+ $("#necklace-choice").on('input', function(){
+ set_input_style('necklace');
+ calcBuildSchedule();
+ });
+
+ // control vars
+ let basic_stats_ctrl = true;
+ let off_stats_ctrl = false;
+ let def_stats_ctrl = false;
+
+ $("#basic-stats-btn").click(function(){
+ basic_stats_ctrl = true;
+ off_stats_ctrl = false;
+ def_stats_ctrl = false;
+
+ $("#minimal-stats").show();
+ $("#minimal-offensive-stats").hide();
+ $("#minimal-defensive-stats").hide();
+
+ $("#off-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ $("#def-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ });
+ $("#basic-stats-btn").hover(
+ function(){
+ $("#basic-stats-btn").css("background-color", "rgb(40, 40, 40)");
+ },function(){
+ if (basic_stats_ctrl) {
+ $("#basic-stats-btn").css("background-color", "rgb(30, 30, 30)");
+ } else {
+ $("#basic-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ }
+ });
+
+ $("#off-stats-btn").click(function(){
+ basic_stats_ctrl = false;
+ off_stats_ctrl = true;
+ def_stats_ctrl = false;
+
+ $("#minimal-stats").hide();
+ $("#minimal-offensive-stats").show();
+ $("#minimal-defensive-stats").hide();
+
+ $("#basic-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ $("#def-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ });
+ $("#off-stats-btn").hover(
+ function(){
+ $("#off-stats-btn").css("background-color", "rgb(40, 40, 40)");
+ },function(){
+ if (off_stats_ctrl) {
+ $("#off-stats-btn").css("background-color", "rgb(30, 30, 30)");
+ } else {
+ $("#off-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ }
+ });
+
+ $("#def-stats-btn").click(function(){
+ basic_stats_ctrl = false;
+ off_stats_ctrl = false;
+ def_stats_ctrl = true;
+
+ $("#minimal-stats").hide();
+ $("#minimal-offensive-stats").hide();
+ $("#minimal-defensive-stats").show();
+
+ $("#off-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ $("#basic-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ });
+ $("#def-stats-btn").hover(
+ function(){
+ $("#def-stats-btn").css("background-color", "rgb(40, 40, 40)");
+ },function(){
+ if (def_stats_ctrl) {
+ $("#def-stats-btn").css("background-color", "rgb(30, 30, 30)");
+ } else {
+ $("#def-stats-btn").css("background-color", "rgb(45, 45, 45)");
+ }
+ });
+
+
+ // item tooltip
+
+ $("#weapon-img-loc").hover(function(event){
+ $("#weapon-tooltip").show();
+ init_tooltip_loc('weapon');
+ }, function(){
+ $("#weapon-tooltip").hide();
+ });
+
+ $("#helmet-img-loc").hover(function(event){
+ $("#helmet-tooltip").show();
+ init_tooltip_loc('helmet');
+ }, function(){
+ $("#helmet-tooltip").hide();
+ });
+
+ $("#chestplate-img-loc").hover(function(event){
+ $("#chestplate-tooltip").show();
+ init_tooltip_loc('chestplate');
+ }, function(){
+ $("#chestplate-tooltip").hide();
+ });
+
+ $("#leggings-img-loc").hover(function(event){
+ $("#leggings-tooltip").show();
+ init_tooltip_loc('leggings');
+ }, function(){
+ $("#leggings-tooltip").hide();
+ });
+
+ $("#boots-img-loc").hover(function(event){
+ $("#boots-tooltip").show();
+ init_tooltip_loc('boots');
+ }, function(){
+ $("#boots-tooltip").hide();
+ });
+
+ $("#ring1-img-loc").hover(function(event){
+ $("#ring1-tooltip").show();
+ init_tooltip_loc('ring1');
+ }, function(){
+ $("#ring1-tooltip").hide();
+ });
+
+ $("#ring2-img-loc").hover(function(event){
+ $("#ring2-tooltip").show();
+ init_tooltip_loc('ring2');
+ }, function(){
+ $("#ring2-tooltip").hide();
+ });
+
+ $("#bracelet-img-loc").hover(function(event){
+ $("#bracelet-tooltip").show();
+ init_tooltip_loc('bracelet');
+ }, function(){
+ $("#bracelet-tooltip").hide();
+ });
+
+ $("#necklace-img-loc").hover(function(event){
+ $("#necklace-tooltip").show();
+ init_tooltip_loc('necklace');
+ }, function(){
+ $("#necklace-tooltip").hide();
+ });
+
+});
+
+function set_input_style(type) {
+ let item = itemMap.get($("#"+type+"-choice").val());
+ if (item) {
+ $("#"+type+"-choice").addClass(item.tier);
+ if (type == 'weapon') {
+ $("#"+type+"-img").attr('src', 'media/items/new/generic-'+item.type+'.png');
+ }
+ } else {
+ $("#"+type+"-choice").attr('class', 'item-name');
+ }
+}
+
+function init_tooltip_loc(equipment){
+ let ImgLoc = document.getElementById(equipment+'-img-loc').getBoundingClientRect();
+ let tooltipRect = document.getElementById(equipment+"-tooltip").getBoundingClientRect();
+ let windowHeight = $(window).height()
+
+ $("#"+equipment+"-tooltip").css('top', Math.min(ImgLoc.top, windowHeight - (tooltipRect.bottom - tooltipRect.top)));
+ $("#"+equipment+"-tooltip").css('left', ImgLoc.right);
+}
+
+function update_powder_count(type, alt="") {
+ let item = itemMap.get($("#"+type+"-choice").val());
+ if (item) {
+ $("#"+type+"-powder").attr("placeholder", item["slots"]+" slots"+alt);
+ }
+}
+
+function init_equipUI() {
+ for (const i in equipment_keys) {
+ set_input_style(equipment_keys[i]);
+ }
+ update_powder_count('weapon');
+ update_powder_count('helmet', '|example: t6t6');
+ update_powder_count('chestplate');
+ update_powder_count('leggings');
+ update_powder_count('boots');
+}
+
+// phanta method of handling input <3
+let calcBuildTask = null;
+let updateStatTask = null;
+let doSearchTask = null;
+
+function calcBuildSchedule(){
+ if (calcBuildTask !== null) {
+ clearTimeout(calcBuildTask);
+ }
+ calcBuildTask = setTimeout(function(){
+ calcBuildTask = null;
+ calculateBuild();
+ }, 500);
+}
+
+function updateStatSchedule(){
+ if (updateStatTask !== null) {
+ clearTimeout(updateStatTask);
+ }
+ updateStatTask = setTimeout(function(){
+ updateStatTask = null;
+ updateStats();
+ }, 500);
+}
+
+function doSearchSchedule(){
+ if (doSearchTask !== null) {
+ clearTimeout(doSearchTask);
+ }
+ doSearchTask = setTimeout(function(){
+ doSearchTask = null;
+ doItemSearch();
+ }, 500);
+}
\ No newline at end of file
diff --git a/sq2bs.css b/sq2bs.css
new file mode 100644
index 0000000..28e0acb
--- /dev/null
+++ b/sq2bs.css
@@ -0,0 +1,437 @@
+* {
+ font-family: 'Nunito', sans-serif;
+}
+
+/* sidebar stuff */
+.sidebar {
+ height: 100%; /* 100% Full-height */
+ width: 3.5vw; /* 0 width - change this with JavaScript */
+ position: fixed; /* Stay in place */
+ top: 0;
+ left: 0;
+ overflow-x: hidden; /* Disable horizontal scroll */
+ transition: 0.5s; /* 0.5 second transition effect to slide in the sidebar */
+}
+
+.sidebar:hover {
+ width: 11vw;
+}
+
+.sidebar a {
+ padding: .4vw .4vw .4vw .4vw;
+ display: block;
+ color: white;
+ white-space: nowrap;
+ text-decoration: none;
+}
+
+.sidebar a img {
+ margin-right: .6vw;
+ width: 2.5vw;
+}
+
+.sidebar a b {
+ font-size: .8vw;
+}
+
+.sidebar a:hover {
+ background-color: hsl(0, 0%, 8%);
+ color: rgb(210, 210, 210);
+}
+
+@media screen and (max-width: 992px) {
+ .sidebar {display: none;}
+}
+
+/* builder containers */
+
+/* wynn-related css(es) */
+.positive {
+ color: #5f5;
+}
+
+.negative {
+ color: #f55;
+}
+
+.Health {
+ color: #AA0000
+}
+
+.Health:before {
+ content: "\2764" ' ';
+}
+
+.lvl:before {
+ content: 'Lv. '
+}
+
+.Damage {
+ color: rgb(255, 198, 85)
+}
+
+.Normal {
+ color: #FFFFFF !important;
+}
+
+.Unique {
+ color: #FFFF55 !important;
+}
+
+.Rare {
+ color: #FF55FF !important;
+}
+
+.Legendary {
+ color: #55FFFF !important;
+}
+
+.Fabled {
+ color: #FF5555 !important;
+}
+
+.Mythic {
+ color: #AA00AA !important;
+}
+
+.Set {
+ color: #5f5 !important;
+}
+
+.eDam, .Earth, .Earth_powder {
+ color: #00AA00;
+}
+
+.eDam:before, .Earth:before, .Earth_powder:before { content: "\2724" ' '; }
+
+.tDam, .Thunder, .Thunder_powder {
+ color: #FFFF55;
+}
+
+.tDam:before, .Thunder:before, .Thunder_powder:before { content: "\2726" ' '; }
+
+.wDam, .Water, .Water_powder {
+ color: #55FFFF
+}
+
+.wDam:before, .Water:before, .Water_powder:before { content: "\2749" ' '; }
+
+.fDam, .Fire, .Fire_powder {
+ color: #FF5555;
+}
+
+.fDam:before, .Fire:before, .Fire_powder:before { content: "\2739" ' '; }
+
+.aDam, .Air, .Air_powder {
+ color: #FFFFFF
+}
+
+.aDam:before, .Air:before, .Air_powder:before { content: "\274b" ' '; }
+
+.nDam, .Neutral {
+ color: #FFAA00;
+}
+
+.nDam:before, .Neutral:before {
+ content: "\2724" ' ';
+}
+
+.Mana {
+ color: #5ff;
+}
+
+.Mana:after {
+ content: "\273A"
+}
+
+/* equipment field specifics */
+/* inputs and dropdowns */
+.form-control {
+ transition: none !important;
+ box-shadow: none !important;
+ width: 95% !important;
+}
+
+ul {
+ list-style-type: none;
+}
+
+ul.search-box {
+ position: absolute;
+ padding: 0;
+}
+
+li.search-item {
+ cursor: pointer;
+}
+
+li.search-item:hover {
+ background-color: hsl(0, 0%, 11%) !important;
+}
+
+/* boosts styles */
+.button-boost:hover {
+ background-color: rgba(255, 255, 255, .1);
+}
+
+.toggleOn {
+ background-color: #0a0 !important;
+}
+
+/* floating tooltip styles */
+.float-tooltip {
+ background-color: hsl(0, 0%, 16%);
+ position: absolute;
+ transition: .3s;
+ cursor: pointer;
+}
+
+/* generic */
+
+input {
+ min-width: 0;
+ width: 100%;
+}
+
+input.equipment-input {
+ font-weight: bold;
+}
+
+.text-right {
+ float: right;
+}
+
+.text-left {
+ float: left;
+}
+
+.spell-display p {
+ margin-bottom: 0;
+}
+
+.spell-display b {
+ font-size: 3rem;
+ font-weight: bold;
+}
+
+.spell-expand {
+ cursor: pointer;
+}
+
+.scaled-font {
+ font-size: 2.5rem;
+}
+
+.skp-tooltip {
+ font-size: 2.1875rem;
+ font-weight: bold;
+}
+
+.spellcost-tooltip b {
+ font-size: 2.1875rem !important;
+ font-weight: bold;
+}
+
+.warning {
+ color: #ff8180;
+ font-size: 1.875rem;
+ margin-bottom: 0;
+ font-weight: bold;
+}
+
+.scaled-item-icon {
+ width: 8rem;
+}
+
+.scaled-item-icon img {
+ width: 6.5rem;
+}
+
+.scaled-bckgrd {
+ width: 10rem;
+ height: 10rem;
+}
+
+.scaled-bckgrd img {
+ width: 6.5rem;
+}
+
+@media screen and (min-width: 1200px) and (max-width: 1400px) {
+ .scaled-font {
+ font-size: .8rem;
+ }
+
+ .skp-tooltip {
+ font-size: .625rem;
+ }
+
+ .spellcost-tooltip b {
+ font-size: .625rem !important;
+ }
+
+ .scaled-item-icon {
+ width: 3.2rem;
+ }
+
+ .scaled-item-icon img {
+ width: 2.8rem;
+ }
+
+ .scaled-bckgrd {
+ width: 4rem;
+ height: 4rem;
+ }
+
+ .scaled-bckgrd img {
+ width: 2.8rem;
+ }
+
+ .warning {
+ font-size: .7rem;
+ }
+
+ .spell-display b {
+ font-size: 1rem;
+ }
+}
+
+@media screen and (min-width: 1400px) {
+ .scaled-font {
+ font-size: 1rem;
+ }
+
+ .skp-tooltip {
+ font-size: .78rem;
+ }
+
+ .spellcost-tooltip b {
+ font-size: .78rem !important;
+ }
+
+ .scaled-item-icon {
+ width: 4rem;
+ }
+
+ .scaled-item-icon img {
+ width: 3.5rem;
+ }
+
+ .scaled-bckgrd {
+ width: 5rem;
+ height: 5rem;
+ }
+
+ .scaled-bckgrd img {
+ width: 3.5rem;
+ }
+
+
+ .warning {
+ font-size: .8rem;
+ }
+
+ .spell-display b {
+ font-size: 1.2rem;
+ }
+}
+/* WynnAtlas Mini */
+.search-field {
+ background-color: hsl(0, 0%, 14%) !important;
+ color: white;
+ font-weight: bold;
+ border-color: hsl(0, 0%, 8%);
+}
+
+/* Fake button for build stats */
+.fake-button {
+ cursor: pointer;
+}
+
+.fake-button:hover {
+ background-color: hsl(0, 0%, 14%) !important;
+}
+
+/* material design dark mode */
+.dark-1 {
+ background-color: hsl(0, 0%, 5%) !important;
+}
+
+.dark-2 {
+ background-color: hsl(0, 0%, 7%) !important;
+}
+
+.dark-3 {
+ background-color: hsl(0, 0%, 8%) !important;
+}
+
+.dark-4 {
+ background-color: hsl(0, 0%, 9%) !important;
+}
+
+.dark-5 {
+ background-color: hsl(0, 0%, 11%) !important;
+}
+
+.dark-6 {
+ background-color: hsl(0, 0%, 12%) !important;
+}
+
+.dark-7 {
+ background-color: hsl(0, 0%, 14%) !important;
+}
+
+.dark-8 {
+ background-color: hsl(0, 0%, 15%) !important;
+}
+
+.dark-9 {
+ background-color: hsl(0, 0%, 16%) !important;
+}
+
+.dark-1u {
+ background-color: hsl(0, 0%, 5%);
+}
+
+.dark-2u {
+ background-color: hsl(0, 0%, 7%);
+}
+
+.dark-3u {
+ background-color: hsl(0, 0%, 8%);
+}
+
+.dark-4u {
+ background-color: hsl(0, 0%, 9%);
+}
+
+.dark-5u {
+ background-color: hsl(0, 0%, 11%);
+}
+
+.dark-6u {
+ background-color: hsl(0, 0%, 12%);
+}
+
+.dark-7u {
+ background-color: hsl(0, 0%, 14%);
+}
+
+.dark-8u {
+ background-color: hsl(0, 0%, 15%);
+}
+
+.dark-9u {
+ background-color: hsl(0, 0%, 16%);
+}
+
+.dark-shadow {
+ box-shadow: 0rem 0rem 1.25rem 0.1875rem black;
+}
+
+.dark-shadow-sm {
+ box-shadow: 0rem 0rem 0.625rem 0.125rem black;
+}
+
+.border-dark-7 {
+ border-color:hsl(0, 0%, 14%) !important;
+}
\ No newline at end of file
diff --git a/sq2bs.html b/sq2bs.html
new file mode 100644
index 0000000..43a4fde
--- /dev/null
+++ b/sq2bs.html
@@ -0,0 +1,1229 @@
+
+
+
+ WynnBuilder^2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 09000
+
+
+ Lv. 123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 09000
+
+
+ Lv. 123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 09000
+
+
+ Lv. 123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 09000
+
+
+ Lv. 123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 09000
+
+
+ Lv. 123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 09000
+
+
+ Lv. 123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 09000
+
+
+ Lv. 123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 09000
+
+
+ Lv. 123
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Assign: 0
+
+
+ Original: 0
+
+
+
+
+
+
+
+
+
+
+ Assign: 0
+
+
+ Original: 0
+
+
+
+
+
+
+
+
+
+
+ Assign: 0
+
+
+ Original: 0
+
+
+
+
+
+
+
+
+
+
+ Assign: 0
+
+
+ Original: 0
+
+
+
+
+
+
+
+
+
+
+ Assign: 0
+
+
+ Original: 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Earth
+
+
+ Thunder
+
+
+ Water
+
+
+ Fire
+
+
+ Air
+
+
+
+
+
+
+ Quake (Active)
+
+
+
+ Rage (Passive)
+
+
+ placeholder
+
+
+
+
+
+
+ Chain Lightning (Active)
+
+
+
+ Kill Streak (Passive)
+
+
+ placeholder
+
+
+
+
+
+
+ Curse (Active)
+
+
+
+ Concentration (Passive)
+
+
+ placeholder
+
+
+
+
+
+
+ Courage (Active)
+
+
+
+ Endurance (Passive)
+
+
+ how do calc
+
+
+
+
+
+
+ Wind Prison (Active)
+
+
+
+ Dodge (Passive)
+
+
+ how do calc
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Offense
+
+
+ Defense
+
+
+ Overall
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Set info
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sq2bs.js b/sq2bs.js
new file mode 100644
index 0000000..f04df78
--- /dev/null
+++ b/sq2bs.js
@@ -0,0 +1,385 @@
+let equipment_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace', 'weapon'];
+let weapon_keys = ['dagger', 'wand', 'bow', 'relik', 'spear'];
+let skp_keys = ['str', 'dex', 'int', 'def', 'agi'];
+
+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+"-powder").setAttribute("oninput", "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()")
+
+ 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;
+ calculateBuild();
+ }, 500);
+}
+
+function updateStatSchedule(){
+ if (updateStatTask !== null) {
+ clearTimeout(updateStatTask);
+ }
+ updateStatTask = setTimeout(function(){
+ updateStatTask = null;
+ updateStats();
+ }, 500);
+}
+
+function doSearchSchedule(){
+ if (doSearchTask !== null) {
+ clearTimeout(doSearchTask);
+ }
+ doSearchTask = setTimeout(function(){
+ doSearchTask = null;
+ doItemSearch();
+ window.dispatchEvent(new Event('resize'));
+ }, 500);
+}
+
+// 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');
+
+ 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 {
+ // item not found
+ 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;
+ }
+
+ 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);
+
+ // 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;
+ }
+
+ // set weapon img
+ if (category == 'weapon') {
+ document.querySelector("#weapon-img").setAttribute('src', 'media/items/new/generic-'+type+'.png');
+ }
+
+ // call calc build
+}
+/* tabulars | man i hate this code but too lazy to fix /shrug */
+
+let tabs = ['all-stats', 'minimal-offensive-stats', 'minimal-defensive-stats'];
+
+function show_tab(tab) {
+ console.log(itemFilters)
+ for (const i in tabs) {
+ document.querySelector("#"+tabs[i]).style.display = "none";
+ }
+ document.querySelector("#"+tab).style.display = "";
+}
+
+function toggle_spell_tab(tab) {
+ if (document.querySelector("#"+tab).style.display == "none") {
+ document.querySelector("#"+tab).style.display = "";
+ } else {
+ document.querySelector("#"+tab).style.display = "none";
+ }
+}
+
+function toggle_boost_tab(tab) {
+ for (const i of skp_keys) {
+ document.querySelector("#"+i+"-boost").style.display = "none";
+ }
+ document.querySelector("#"+tab+"-boost").style.display = "";
+}
+
+// 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;
+
+ 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.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) {
+ // build dropdown
+ console.log('init dropdown for '+ eq)
+ 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: {
+ 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";
+
+ 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();
+ },
+ },
+ }
+ }));
+ }
+ let filter_loc = ["filter1", "filter2", "filter3", "filter4"];
+ for (const i of filter_loc) {
+ console.log(i);
+ console.log('init dropdown for '+i+"-choice" )
+ dropdowns.set(i+"-choice", new autoComplete({
+ data: {
+ src: itemFilters,
+ },
+ 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();
+ },
+ },
+ }
+ }));
+ }
+}
\ No newline at end of file
diff --git a/sq2build.js b/sq2build.js
new file mode 100644
index 0000000..0cbf697
--- /dev/null
+++ b/sq2build.js
@@ -0,0 +1,551 @@
+
+
+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.
+ */
+ 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.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));
+ }
+ }
+
+ //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];
+ // 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].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")));
+ }
+}
diff --git a/sq2builder.js b/sq2builder.js
new file mode 100644
index 0000000..330fa72
--- /dev/null
+++ b/sq2builder.js
@@ -0,0 +1,985 @@
+const url_tag = location.hash.slice(1);
+// console.log(url_base);
+// console.log(url_tag);
+
+
+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 equipment_names = [
+ "Helmet",
+ "Chestplate",
+ "Leggings",
+ "Boots",
+ "Ring 1",
+ "Ring 2",
+ "Bracelet",
+ "Necklace",
+ "Weapon"
+];
+let equipmentInputs = equipment_fields.map(x => x + "-choice");
+let buildFields = equipment_fields.map(x => x+"-tooltip");
+
+let powderInputs = [
+ "helmet-powder",
+ "chestplate-powder",
+ "leggings-powder",
+ "boots-powder",
+ "weapon-powder",
+];
+
+function init() {
+ console.log("builder.js init");
+ init_autocomplete();
+ for (const i of equipment_keys) {
+ update_field(i);
+ }
+ decodeBuild(url_tag);
+}
+
+function getItemNameFromID(id) {
+ if (redirectMap.has(id)) {
+ return getItemNameFromID(redirectMap.get(id));
+ }
+ return idMap.get(id);
+}
+
+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);
+ console.log(block);
+ 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;
+}
+
+/*
+ * Populate fields based on url, and calculate build.
+ */
+function decodeBuild(url_tag) {
+ if (url_tag) {
+ let equipment = [null, null, 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;
+ if (version === "0" || version === "1" || version === "2" || version === "3") {
+ 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);
+ }
+ if (version === "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);
+ }
+ if (version === "5") {
+ 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);
+ }
+ if (version === "1") {
+ let powder_info = info[1];
+ powdering = parsePowdering(powder_info);
+ } else if (version === "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);
+ powdering = parsePowdering(powder_info);
+ } else if (version === "3" || version === "4" || version === "5"){
+ 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);
+
+ powdering = parsePowdering(powder_info);
+ }
+
+ for (let i in powderInputs) {
+ setValue(powderInputs[i], powdering[i]);
+ }
+ for (let i in equipment) {
+ setValue(equipmentInputs[i], equipment[i]);
+ }
+ calculateBuild(save_skp, skillpoints);
+ }
+}
+
+/* Stores the entire build in a string using B64 encryption and adds it to the URL.
+*/
+function encodeBuild() {
+
+ if (player_build) {
+ let build_string;
+ if (player_build.customItems.length > 0) { //v5 encoding
+ build_string = "5_";
+ let crafted_idx = 0;
+ let custom_idx = 0;
+ for (const item of player_build.items) {
+
+ if (item.get("custom")) {
+ let custom = "CI-"+encodeCustom(player_build.customItems[custom_idx],true);
+ build_string += Base64.fromIntN(custom.length, 3) + custom;
+ custom_idx += 1;
+ } else if (item.get("crafted")) {
+ build_string += "CR-"+encodeCraft(player_build.craftedItems[crafted_idx]);
+ crafted_idx += 1;
+ } else {
+ build_string += Base64.fromIntN(item.get("id"), 3);
+ }
+ }
+
+ for (const skp of skp_order) {
+ build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048
+ }
+ build_string += Base64.fromIntN(player_build.level, 2);
+ for (const _powderset of player_build.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);
+ }
+ }
+ } else { //v4 encoding
+ build_string = "4_";
+ let crafted_idx = 0;
+ for (const item of player_build.items) {
+ if (item.get("crafted")) {
+ build_string += "-"+encodeCraft(player_build.craftedItems[crafted_idx]);
+ crafted_idx += 1;
+ } else {
+ build_string += Base64.fromIntN(item.get("id"), 3);
+ }
+ }
+
+ for (const skp of skp_order) {
+ build_string += Base64.fromIntN(getValue(skp + "-skp"), 2); // Maximum skillpoints: 2048
+ }
+ build_string += Base64.fromIntN(player_build.level, 2);
+ for (const _powderset of player_build.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);
+ }
+ }
+ }
+ return build_string;
+ }
+ // this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ];
+ // let build_string = "3_" + Base64.fromIntN(player_build.helmet.get("id"), 3) +
+ // Base64.fromIntN(player_build.chestplate.get("id"), 3) +
+ // Base64.fromIntN(player_build.leggings.get("id"), 3) +
+ // Base64.fromIntN(player_build.boots.get("id"), 3) +
+ // Base64.fromIntN(player_build.ring1.get("id"), 3) +
+ // Base64.fromIntN(player_build.ring2.get("id"), 3) +
+ // Base64.fromIntN(player_build.bracelet.get("id"), 3) +
+ // Base64.fromIntN(player_build.necklace.get("id"), 3) +
+ // Base64.fromIntN(player_build.weapon.get("id"), 3);
+ return "";
+}
+
+function calculateBuild(save_skp, skp){
+ try {
+ /*
+ let specialNames = ["Quake", "Chain_Lightning", "Curse", "Courage", "Wind_Prison"];
+ for (const sName of specialNames) {
+ for (let i = 1; i < 6; i++) {
+ let elem = document.getElementById(sName + "-" + i);
+ let name = sName.replace("_", " ");
+ if (elem.classList.contains("toggleOn")) { //toggle the pressed button off
+ elem.classList.remove("toggleOn");
+ }
+ }
+ }
+ */
+ if(player_build){
+ reset_powder_specials();
+ updateBoosts("skip", false);
+ updatePowderSpecials("skip", false);
+ }
+ let weaponName = getValue(equipmentInputs[8]);
+ if (weaponName.startsWith("Morph-")) {
+ let equipment = [ "Morph-Stardust", "Morph-Steel", "Morph-Iron", "Morph-Gold", "Morph-Topaz", "Morph-Emerald", "Morph-Amethyst", "Morph-Ruby", weaponName.substring(6) ];
+ for (let i in equipment) {
+ setValue(equipmentInputs[i], equipment[i]);
+ }
+ }
+
+ //updatePowderSpecials("skip"); //jank pt 1
+ save_skp = (typeof save_skp !== 'undefined') ? save_skp : false;
+ /* TODO: implement level changing
+ Make this entire function prettier
+ */
+ let equipment = [ null, null, null, null, null, null, null, null, null ];
+ for (let i in equipment) {
+ let equip = getValue(equipmentInputs[i]).trim();
+ if (equip === "") {
+ equip = "No " + equipment_names[i]
+ }
+ else {
+ setValue(equipmentInputs[i], equip);
+ }
+ equipment[i] = equip;
+ }
+ let powderings = [];
+ let errors = [];
+ for (const i in powderInputs) {
+ // read in two characters at a time.
+ // TODO: make this more robust.
+ let input = getValue(powderInputs[i]).trim();
+ let powdering = [];
+ let errorederrors = [];
+ while (input) {
+ let first = input.slice(0, 2);
+ let powder = powderIDs.get(first);
+ console.log(powder);
+ if (powder === undefined) {
+ errorederrors.push(first);
+ } else {
+ powdering.push(powder);
+ }
+ input = input.slice(2);
+ }
+ if (errorederrors.length > 0) {
+ if (errorederrors.length > 1)
+ errors.push(new IncorrectInput(errorederrors.join(""), "t6w6", powderInputs[i]));
+ else
+ errors.push(new IncorrectInput(errorederrors[0], "t6 or e3", powderInputs[i]));
+ }
+ //console.log("POWDERING: " + powdering);
+ powderings.push(powdering);
+ }
+
+
+ let level = document.getElementById("level-choice").value;
+ player_build = new Build(level, equipment, powderings, new Map(), errors);
+ console.log(player_build);
+ for (let i of document.getElementsByClassName("hide-container-block")) {
+ i.style.display = "block";
+ }
+ for (let i of document.getElementsByClassName("hide-container-grid")) {
+ i.style.display = "grid";
+ }
+
+ console.log(player_build.toString());
+ displaysq2EquipOrder(document.getElementById("build-order"),player_build.equip_order);
+
+ const assigned = player_build.base_skillpoints;
+ const skillpoints = player_build.total_skillpoints;
+ for (let i in skp_order){ //big bren
+ setText(skp_order[i] + "-skp-base", "Original: " + skillpoints[i]);
+ }
+
+ if (save_skp) {
+ // TODO: reduce duplicated code, @updateStats
+ let skillpoints = player_build.total_skillpoints;
+ let delta_total = 0;
+ for (let i in skp_order) {
+ let manual_assigned = skp[i];
+ let delta = manual_assigned - skillpoints[i];
+ skillpoints[i] = manual_assigned;
+ player_build.base_skillpoints[i] += delta;
+ delta_total += delta;
+ }
+ player_build.assigned_skillpoints += delta_total;
+ }
+
+ calculateBuildStats();
+ // setTitle();
+ if (player_build.errored)
+ throw new ListError(player_build.errors);
+
+ }
+ catch (error) {
+ console.log(error);
+ }
+}
+
+function handleBuilderError(error) {
+ if (error instanceof ListError) {
+ for (let i of error.errors) {
+ if (i instanceof ItemNotFound) {
+ i.element.textContent = i.message;
+ } else if (i instanceof IncorrectInput) {
+ if (document.getElementById(i.id) !== null) {
+ document.getElementById(i.id).parentElement.querySelectorAll("p.error")[0].textContent = i.message;
+ }
+ } else {
+ let msg = i.stack;
+ let lines = msg.split("\n");
+ let header = document.getElementById("header");
+ header.textContent = "";
+ for (const line of lines) {
+ let p = document.createElement("p");
+ p.classList.add("itemp");
+ p.textContent = line;
+ header.appendChild(p);
+ }
+ let p2 = document.createElement("p");
+ p2.textContent = "If you believe this is an error, contact hppeng on forums or discord.";
+ header.appendChild(p2);
+ }
+ }
+ } else {
+ let msg = error.stack;
+ let lines = msg.split("\n");
+ let header = document.getElementById("header");
+ header.textContent = "";
+ for (const line of lines) {
+ let p = document.createElement("p");
+ p.classList.add("itemp");
+ p.textContent = line;
+ header.appendChild(p);
+ }
+ let p2 = document.createElement("p");
+ p2.textContent = "If you believe this is an error, contact hppeng on forums or discord.";
+ header.appendChild(p2);
+ }
+}
+
+/* Updates all build statistics based on (for now) the skillpoint input fields and then calculates build stats.
+*/
+function updateStats() {
+
+ let specialNames = ["Quake", "Chain_Lightning", "Curse", "Courage", "Wind_Prison"];
+ for (const sName of specialNames) {
+ for (let i = 1; i < 6; i++) {
+ let elem = document.getElementById(sName + "-" + i);
+ let name = sName.replace("_", " ");
+ if (elem.classList.contains("toggleOn")) { //toggle the pressed button off
+ elem.classList.remove("toggleOn");
+ let special = powderSpecialStats[specialNames.indexOf(sName)];
+ console.log(special);
+ if (special["weaponSpecialEffects"].has("Damage Boost")) {
+ if (name === "Courage" || name === "Curse") { //courage is universal damage boost
+ //player_build.damageMultiplier -= special.weaponSpecialEffects.get("Damage Boost")[i-1]/100;
+ player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]);
+ player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]);
+ player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]);
+ } else if (name === "Wind Prison") {
+ player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]);
+ player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[i-1];
+ }
+ }
+ }
+ }
+ }
+
+
+
+ let skillpoints = player_build.total_skillpoints;
+ let delta_total = 0;
+ for (let i in skp_order) {
+ let value = document.getElementById(skp_order[i] + "-skp").value;
+ if (value === ""){value = 0; setValue(skp_order[i] + "-skp", value)}
+ let manual_assigned = 0;
+ if (value.includes("+")) {
+ let skp = value.split("+");
+ for (const s of skp) {
+ manual_assigned += parseInt(s,10);
+ }
+ } else {
+ manual_assigned = parseInt(value,10);
+ }
+ let delta = manual_assigned - skillpoints[i];
+ skillpoints[i] = manual_assigned;
+ player_build.base_skillpoints[i] += delta;
+ delta_total += delta;
+ }
+ player_build.assigned_skillpoints += delta_total;
+ if(player_build){
+ updatePowderSpecials("skip", false);
+ updateBoosts("skip", false);
+ }
+ /*
+ for (let id of editable_item_fields) {
+ player_build.statMap.set(id, parseInt(getValue(id)));
+ console.log(player_build.statMap.get(id));
+ }
+ }*/
+ player_build.aggregateStats();
+ console.log(player_build.statMap);
+ calculateBuildStats();
+}
+/* Updates all spell boosts
+*/
+function updateBoosts(buttonId, recalcStats) {
+ let elem = document.getElementById(buttonId);
+ let name = buttonId.split("-")[0];
+ if(buttonId !== "skip") {
+ if (elem.classList.contains("toggleOn")) {
+ player_build.damageMultiplier -= damageMultipliers.get(name);
+ if (name === "warscream") {
+ player_build.defenseMultiplier -= .20;
+ }
+ if (name === "vanish") {
+ player_build.defenseMultiplier -= .15;
+ }
+ elem.classList.remove("toggleOn");
+ }else{
+ player_build.damageMultiplier += damageMultipliers.get(name);
+ if (name === "warscream") {
+ player_build.defenseMultiplier += .20;
+ }
+ if (name === "vanish") {
+ player_build.defenseMultiplier += .15;
+ }
+ elem.classList.add("toggleOn");
+ }
+ updatePowderSpecials("skip", false); //jank pt 1
+ } else {
+ for (const [key, value] of damageMultipliers) {
+ let elem = document.getElementById(key + "-boost")
+ if (elem.classList.contains("toggleOn")) {
+ elem.classList.remove("toggleOn");
+ player_build.damageMultiplier -= value;
+ if (key === "warscream") { player_build.defenseMultiplier -= .20 }
+ if (key === "vanish") { player_build.defenseMultiplier -= .15 }
+ }
+ }
+ }
+ if (recalcStats) {
+ calculateBuildStats();
+ }
+}
+
+/* Updates all powder special boosts
+*/
+function updatePowderSpecials(buttonId, recalcStats) {
+ //console.log(player_build.statMap);
+
+ let name = (buttonId).split("-")[0];
+ let power = (buttonId).split("-")[1]; // [1, 5]
+ let specialNames = ["Quake", "Chain Lightning", "Curse", "Courage", "Wind Prison"];
+ let powderSpecials = []; // [ [special, power], [special, power]]
+
+
+ if(name !== "skip"){
+ let elem = document.getElementById(buttonId);
+ if (elem.classList.contains("toggleOn")) { //toggle the pressed button off
+ elem.classList.remove("toggleOn");
+ let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))];
+ if (special.weaponSpecialEffects.has("Damage Boost")) {
+ name = name.replace("_", " ");
+ if (name === "Courage" || name === "Curse") { //courage and curse are universal damage boost
+ player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]);
+ player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]);
+ player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]);
+ //poison?
+ } else if (name === "Wind Prison") {
+ player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[power-1]);
+ player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[power-1];
+ }
+ }
+ } else {
+ for (let i = 1;i < 6; i++) { //toggle all pressed buttons of the same powder special off
+ //name is same, power is i
+ if(document.getElementById(name.replace(" ", "_") + "-" + i).classList.contains("toggleOn")) {
+ document.getElementById(name.replace(" ", "_") + "-" + i).classList.remove("toggleOn");
+ let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))];
+ if (special.weaponSpecialEffects.has("Damage Boost")) {
+ name = name.replace("_", " "); //might be redundant
+ if (name === "Courage" || name === "Curse") { //courage is universal damage boost
+ //player_build.damageMultiplier -= special.weaponSpecialEffects.get("Damage Boost")[i-1]/100;
+ player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]);
+ player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]);
+ player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]);
+ } else if (name === "Wind Prison") {
+ player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") - special.weaponSpecialEffects.get("Damage Boost")[i-1]);
+ player_build.externalStats.get("damageBonus")[4] -= special.weaponSpecialEffects.get("Damage Boost")[i-1];
+ }
+ }
+ }
+ }
+ //toggle the pressed button on
+ elem.classList.add("toggleOn");
+ }
+ }
+
+ for (const sName of specialNames) {
+ for (let i = 1;i < 6; i++) {
+ if (document.getElementById(sName.replace(" ","_") + "-" + i).classList.contains("toggleOn")) {
+ let powderSpecial = powderSpecialStats[specialNames.indexOf(sName.replace("_"," "))];
+ powderSpecials.push([powderSpecial, i]);
+ break;
+ }
+ }
+ }
+
+
+ if (name !== "skip") {
+ let elem = document.getElementById(buttonId);
+ if (elem.classList.contains("toggleOn")) {
+ let special = powderSpecialStats[specialNames.indexOf(name.replace("_", " "))];
+ if (special["weaponSpecialEffects"].has("Damage Boost")) {
+ let name = special["weaponSpecialName"];
+ if (name === "Courage" || name === "Curse") { //courage and curse are is universal damage boost
+ player_build.externalStats.set("sdPct", player_build.externalStats.get("sdPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]);
+ player_build.externalStats.set("mdPct", player_build.externalStats.get("mdPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]);
+ player_build.externalStats.set("poisonPct", player_build.externalStats.get("poisonPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]);
+ } else if (name === "Wind Prison") {
+ player_build.externalStats.set("aDamPct", player_build.externalStats.get("aDamPct") + special.weaponSpecialEffects.get("Damage Boost")[power-1]);
+ player_build.externalStats.get("damageBonus")[4] += special.weaponSpecialEffects.get("Damage Boost")[power-1];
+ }
+ }
+ }
+ }
+
+ if (recalcStats) {
+ calculateBuildStats();
+ }
+ displaysq2PowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build, true);
+}
+
+/* Calculates all build statistics and updates the entire display.
+*/
+function calculateBuildStats() {
+ const assigned = player_build.base_skillpoints;
+ const skillpoints = player_build.total_skillpoints;
+ let skp_effects = ["% more damage dealt.","% chance to crit.","% spell cost reduction.","% less damage taken.","% chance to dodge."];
+ for (let i in skp_order){ //big bren
+ setText(skp_order[i] + "-skp-assign", "Assign: " + assigned[i]);
+ setValue(skp_order[i] + "-skp", skillpoints[i]);
+ let linebreak = document.createElement("br");
+ linebreak.classList.add("itemp");
+ document.getElementById(skp_order[i] + "-skp-label");
+ setText(skp_order[i] + "-skp-pct", (skillPointsToPercentage(skillpoints[i])*100).toFixed(1).concat(skp_effects[i]));
+ document.getElementById(skp_order[i]+"-warnings").textContent = ''
+ if (assigned[i] > 100) {
+ let skp_warning = document.createElement("p");
+ skp_warning.classList.add("warning");
+ skp_warning.classList.add("small-text")
+ skp_warning.textContent += "Cannot assign " + assigned[i] + " skillpoints in " + ["Strength","Dexterity","Intelligence","Defense","Agility"][i] + " manually.";
+ document.getElementById(skp_order[i]+"-warnings").textContent = ''
+ document.getElementById(skp_order[i]+"-warnings").appendChild(skp_warning);
+ }
+ }
+
+ let summarybox = document.getElementById("summary-box");
+ summarybox.textContent = "";
+ let skpRow = document.createElement("p");
+ // let td = document.createElement("p");
+
+ let remainingSkp = document.createElement("p");
+ remainingSkp.classList.add("scaled-font");
+ let remainingSkpTitle = document.createElement("b");
+ remainingSkpTitle.textContent = "Assigned " + player_build.assigned_skillpoints + " skillpoints. Remaining skillpoints: ";
+ let remainingSkpContent = document.createElement("b");
+ remainingSkpContent.textContent = "" + (levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints);
+ remainingSkpContent.classList.add(levelToSkillPoints(player_build.level) - player_build.assigned_skillpoints < 0 ? "negative" : "positive");
+
+ remainingSkp.appendChild(remainingSkpTitle);
+ remainingSkp.appendChild(remainingSkpContent);
+
+
+ summarybox.append(skpRow);
+ summarybox.append(remainingSkp);
+ if(player_build.assigned_skillpoints > levelToSkillPoints(player_build.level)){
+ let skpWarning = document.createElement("span");
+ //skpWarning.classList.add("itemp");
+ skpWarning.classList.add("warning");
+ // skpWarning.classList.add("skp-tooltip");
+ skpWarning.textContent = "WARNING: Too many skillpoints need to be assigned!";
+ let skpCount = document.createElement("p");
+ skpCount.classList.add("warning");
+ // skpCount.classList.add("skp-tooltip");
+ skpCount.textContent = "For level " + (player_build.level>101 ? "101+" : player_build.level) + ", there are only " + levelToSkillPoints(player_build.level) + " skill points available.";
+ summarybox.append(skpWarning);
+ summarybox.append(skpCount);
+ }
+ let lvlWarning;
+ for (const item of player_build.items) {
+ let item_lvl;
+ if (item.get("crafted")) {
+ //item_lvl = item.get("lvlLow") + "-" + item.get("lvl");
+ item_lvl = item.get("lvlLow");
+ }
+ else {
+ item_lvl = item.get("lvl");
+ }
+
+ if (player_build.level < item_lvl) {
+ if (!lvlWarning) {
+ lvlWarning = document.createElement("p");
+ lvlWarning.classList.add("itemp");
+ lvlWarning.classList.add("warning");
+ lvlWarning.textContent = "WARNING: A level " + player_build.level + " player cannot use some piece(s) of this build."
+ }
+ let baditem = document.createElement("p");
+ baditem.classList.add("nocolor");
+ baditem.classList.add("itemp");
+ baditem.textContent = item.get("displayName") + " requires level " + item.get("lvl") + " to use.";
+ lvlWarning.appendChild(baditem);
+ }
+ }
+ if(lvlWarning){
+ summarybox.append(lvlWarning);
+ }
+ for (const [setName, count] of player_build.activeSetCounts) {
+ const bonus = sets[setName].bonuses[count-1];
+ // console.log(setName);
+ if (bonus["illegal"]) {
+ let setWarning = document.createElement("p");
+ setWarning.classList.add("itemp");
+ setWarning.classList.add("warning");
+ setWarning.textContent = "WARNING: illegal item combination: " + setName
+ summarybox.append(setWarning);
+ }
+ }
+
+ for (let i in player_build.items) {
+ if (player_build.items[i].get("id") > 9999) {
+ continue;
+ }
+ displaysq2ExpandedItem(player_build.items[i], buildFields[i]);
+ collapse_element("#"+equipment_keys[i]+"-tooltip");
+ }
+
+ displaysq2ArmorStats(player_build);
+ displaysq2BuildStats("all-stats", player_build, build_all_display_commands);
+ displaysq2BuildStats("minimal-offensive-stats",player_build, build_offensive_display_commands);
+ displaysq2SetBonuses("set-info",player_build);
+
+ let meleeStats = player_build.getMeleeStats();
+ displaysq2MeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats);
+
+ displaysq2DefenseStats(document.getElementById("minimal-defensive-stats"),player_build);
+
+ displaysq2PoisonDamage(document.getElementById("build-poison-stats"),player_build);
+
+ let spells = spell_table[player_build.weapon.get("type")];
+ for (let i = 0; i < 4; ++i) {
+ let parent_elem = document.getElementById("spell"+i+"-info");
+ let overallparent_elem = document.getElementById("spell"+i+"-infoAvg");
+ displaysq2SpellDamage(parent_elem, overallparent_elem, player_build, spells[i], i+1);
+ }
+
+ location.hash = encodeBuild();
+ clear_highlights();
+}
+
+function copyBuild() {
+ if (player_build) {
+ copyTextToClipboard(url_base+location.hash);
+ document.getElementById("copy-button").textContent = "Copied!";
+ }
+}
+
+function shareBuild() {
+ if (player_build) {
+ let text = url_base+location.hash+"\n"+
+ "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("")+"]";
+ copyTextToClipboard(text);
+ document.getElementById("share-button").textContent = "Copied!";
+ }
+}
+
+function populateBuildList() {
+ const buildList = document.getElementById("build-choice");
+ const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
+
+ for (const buildName of Object.keys(savedBuilds).sort()) {
+ const buildOption = document.createElement("option");
+ buildOption.setAttribute("value", buildName);
+ buildList.appendChild(buildOption);
+ }
+}
+
+function saveBuild() {
+ if (player_build) {
+ const savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
+ const saveName = document.getElementById("build-name").value;
+ const encodedBuild = encodeBuild();
+ if ((!Object.keys(savedBuilds).includes(saveName)
+ || document.getElementById("saved-error").textContent !== "") && encodedBuild !== "") {
+ savedBuilds[saveName] = encodedBuild.replace("#", "");
+ window.localStorage.setItem("builds", JSON.stringify(savedBuilds));
+
+ document.getElementById("saved-error").textContent = "";
+ document.getElementById("saved-build").textContent = "Build saved locally";
+
+ const buildList = document.getElementById("build-choice");
+ const buildOption = document.createElement("option");
+ buildOption.setAttribute("value", saveName);
+ buildList.appendChild(buildOption);
+ } else {
+ document.getElementById("saved-build").textContent = "";
+ if (encodedBuild === "") {
+ document.getElementById("saved-error").textContent = "Empty build";
+ }
+ else {
+ document.getElementById("saved-error").textContent = "Exists. Overwrite?";
+ }
+ }
+ }
+}
+
+function loadBuild() {
+ let savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds"));
+ let saveName = document.getElementById("build-name").value;
+
+ if (Object.keys(savedBuilds).includes(saveName)) {
+ decodeBuild(savedBuilds[saveName])
+ document.getElementById("loaded-error").textContent = "";
+ document.getElementById("loaded-build").textContent = "Build loaded";
+ } else {
+ document.getElementById("loaded-build").textContent = "";
+ document.getElementById("loaded-error").textContent = "Build doesn't exist";
+ }
+}
+
+function resetFields(){
+ for (let i in powderInputs) {
+ setValue(powderInputs[i], "");
+ }
+ for (let i in equipmentInputs) {
+ setValue(equipmentInputs[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();
+}
+
+function toggleID() {
+ let button = document.getElementById("show-id-button");
+ let targetDiv = document.getElementById("id-edit");
+ if (button.classList.contains("toggleOn")) { //toggle the pressed button off
+ targetDiv.style.display = "none";
+ button.classList.remove("toggleOn");
+ }
+ else {
+ targetDiv.style.display = "block";
+ button.classList.add("toggleOn");
+ }
+}
+
+function optimizeStrDex() {
+ 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.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") + player_build.externalStats.get("sdPct"),
+ part.multiplier / 100, player_build.weapon, total_skillpoints,
+ player_build.damageMultiplier, player_build.externalStats);
+ 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;
+
+ }
+ // TODO: reduce duplicated code, @calculateBuild
+ let skillpoints = player_build.total_skillpoints;
+ let delta_total = 0;
+ for (let i in skp_order) {
+ let manual_assigned = best_skillpoints[i];
+ let delta = manual_assigned - skillpoints[i];
+ skillpoints[i] = manual_assigned;
+ player_build.base_skillpoints[i] += delta;
+ delta_total += delta;
+ }
+ player_build.assigned_skillpoints += delta_total;
+
+ try {
+ calculateBuildStats();
+ setTitle();
+ if (player_build.errored)
+ throw new ListError(player_build.errors);
+ }
+ catch (error) {
+ handleBuilderError(error);
+ }
+}
+
+// TODO: Learn and use await
+function init2() {
+ load_ing_init(init);
+}
+load_init(init2);
\ No newline at end of file
diff --git a/sq2display.js b/sq2display.js
new file mode 100644
index 0000000..25a95b3
--- /dev/null
+++ b/sq2display.js
@@ -0,0 +1,1468 @@
+function displaysq2BuildStats(parent_id,build,command_group){
+ // Commands to "script" the creation of nice formatting.
+ // #commands create a new element.
+ // !elemental is some janky hack for elemental damage.
+ // normals just display a thing.
+
+ let display_commands = command_group;
+ console.log(display_commands);
+
+ // Clear the parent div.
+ setHTML(parent_id, "");
+ let parent_div = document.getElementById(parent_id);
+
+ let stats = build.statMap;
+ console.log(build.statMap);
+
+ let active_elem;
+ let elemental_format = false;
+
+ //TODO this is put here for readability, consolidate with definition in build.js
+ let staticIDs = ["hp", "eDef", "tDef", "wDef", "fDef", "aDef"];
+
+ for (const command of display_commands) {
+ // style instructions
+
+ if (command.charAt(0) === "#") {
+ if (command === "#defense-stats") {
+ displaysq2DefenseStats(parent_div, build, true);
+ }
+ }
+ if (command.charAt(0) === "!") {
+ // TODO: This is sooo incredibly janky.....
+ if (command === "!elemental") {
+ elemental_format = !elemental_format;
+ }
+ }
+
+ // id instruction
+ else {
+ let id = command;
+ if (stats.get(id)) {
+ let style = null;
+
+ // TODO: add pos and neg style
+ if (!staticIDs.includes(id)) {
+ style = "positive";
+ if (stats.get(id) < 0) {
+ style = "negative";
+ }
+ }
+
+ // ignore
+ let id_val = stats.get(id);
+ if (reversedIDs.includes(id)) {
+ style === "positive" ? style = "negative" : style = "positive";
+ }
+ if (id === "poison" && id_val > 0) {
+ id_val = Math.ceil(id_val*build.statMap.get("poisonPct")/100);
+ }
+ displaysq2FixedID(parent_div, id, id_val, elemental_format, style);
+ if (id === "poison" && id_val > 0) {
+ let row = document.createElement('div');
+ row.classList.add("row")
+ let value_elem = document.createElement('div');
+ value_elem.classList.add('col');
+ value_elem.classList.add('text-end');
+
+ let prefix_elem = document.createElement('b');
+ prefix_elem.textContent = "\u279C With Strength: ";
+ let number_elem = document.createElement('b');
+ number_elem.classList.add(style);
+ number_elem.textContent = (id_val * (1+skillPointsToPercentage(build.total_skillpoints[0])) ).toFixed(0) + idSuffixes[id];
+ value_elem.append(prefix_elem);
+ value_elem.append(number_elem);
+ row.appendChild(value_elem);
+
+ parent_div.appendChild(row);
+ }
+
+ // sp thingy
+ } else if (skp_order.includes(id)) {
+ let total_assigned = build.total_skillpoints[skp_order.indexOf(id)];
+ let base_assigned = build.base_skillpoints[skp_order.indexOf(id)];
+ let diff = total_assigned - base_assigned;
+ let style;
+ if (diff > 0) {
+ style = "positive";
+ } else if (diff < 0) {
+ style = "negative";
+ }
+ if (diff != 0) {
+ displaysq2FixedID(parent_div, id, diff, false, style);
+ }
+ }
+ }
+ }
+}
+
+function displaysq2ExpandedItem(item, parent_id){
+ // Commands to "script" the creation of nice formatting.
+ // #commands create a new element.
+ // !elemental is some janky hack for elemental damage.
+ // normals just display a thing.
+ if (item.get("category") === "weapon") {
+ let stats = new Map();
+ stats.set("atkSpd", item.get("atkSpd"));
+ stats.set("damageBonus", [0, 0, 0, 0, 0]);
+
+ //SUPER JANK @HPP PLS FIX
+ let damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
+ if (item.get("tier") !== "Crafted") {
+ stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]);
+ let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
+ let damages = results[2];
+ let total_damage = 0;
+ for (const i in damage_keys) {
+ total_damage += damages[i][0] + damages[i][1];
+ item.set(damage_keys[i], damages[i][0]+"-"+damages[i][1]);
+ }
+ total_damage = total_damage / 2;
+ item.set("basedps", total_damage);
+
+ } else {
+ stats.set("damageRaw", [item.get("nDamLow"), item.get("eDamLow"), item.get("tDamLow"), item.get("wDamLow"), item.get("fDamLow"), item.get("aDamLow")]);
+ stats.set("damageBases", [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")]);
+ let resultsLow = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
+ let damagesLow = resultsLow[2];
+ stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]);
+ stats.set("damageBases", [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")]);
+ let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, item, [0, 0, 0, 0, 0], 1, undefined);
+ let damages = results[2];
+ console.log(damages);
+
+ let total_damage_min = 0;
+ let total_damage_max = 0;
+ for (const i in damage_keys) {
+ total_damage_min += damagesLow[i][0] + damagesLow[i][1];
+ total_damage_max += damages[i][0] + damages[i][1];
+ item.set(damage_keys[i], damagesLow[i][0]+"-"+damagesLow[i][1]+"\u279c"+damages[i][0]+"-"+damages[i][1]);
+ }
+ total_damage_min = total_damage_min / 2;
+ total_damage_max = total_damage_max / 2;
+ item.set("basedps", [total_damage_min, total_damage_max]);
+ }
+ } else if (item.get("category") === "armor") {
+ }
+
+ let display_commands = sq2_item_display_commands;
+
+ // Clear the parent div.
+ setHTML(parent_id, "");
+ let parent_div = document.getElementById(parent_id);
+
+ let fix_id = item.has("fixID") && item.get("fixID");
+ let elemental_format = false;
+ for (let i = 0; i < display_commands.length; i++) {
+ const command = display_commands[i];
+ if (command.charAt(0) === "!") {
+ // TODO: This is sooo incredibly janky.....
+ if (command === "!elemental") {
+ elemental_format = !elemental_format;
+ }
+ else if (command === "!spacer") {
+ let spacer = document.createElement('div');
+ spacer.classList.add("row", "my-2");
+ parent_div.appendChild(spacer);
+ continue;
+ }
+ }
+ else {
+ let id = command;
+ if(nonRolledIDs.includes(id)){//nonRolledID & non-0/non-null/non-und ID
+ if (!item.get(id)) {
+ if (! (item.get("crafted") && skp_order.includes(id) &&
+ (item.get("maxRolls").get(id) || item.get("minRolls").get(id)))) {
+ continue;
+ }
+ }
+ if (id === "slots") {
+ let p_elem = document.createElement("div");
+ p_elem.classList.add("col");
+
+ // PROPER POWDER DISPLAYING
+ let numerals = new Map([[1, "I"], [2, "II"], [3, "III"], [4, "IV"], [5, "V"], [6, "VI"]]);
+
+ let powderPrefix = document.createElement("b");
+ powderPrefix.textContent = "Powder Slots: " + item.get(id) + " [";
+ p_elem.appendChild(powderPrefix);
+
+ let powders = item.get("powders");
+ for (let i = 0; i < powders.length; i++) {
+ let powder = document.createElement("b");
+ powder.textContent = numerals.get((powders[i]%6)+1)+" ";
+ powder.classList.add(damageClasses[Math.floor(powders[i]/6)+1]+"_powder");
+ p_elem.appendChild(powder);
+ }
+
+ let powderSuffix = document.createElement("b");
+ powderSuffix.textContent = "]";
+ p_elem.appendChild(powderSuffix);
+ parent_div.appendChild(p_elem);
+ } else if (id === "set") {
+ if (item.get("hideSet")) { continue; }
+
+ let p_elem = document.createElement("div");
+ p_elem.classList.add("col");
+ p_elem.textContent = "Set: " + item.get(id).toString();
+ parent_div.appendChild(p_elem);
+ } else if (id === "majorIds") {
+ console.log(item.get(id));
+ for (let majorID of item.get(id)) {
+ let p_elem = document.createElement("div");
+ p_elem.classList.add("col");
+
+ let title_elem = document.createElement("b");
+ let b_elem = document.createElement("b");
+ if (majorID.includes(":")) {
+ let name = majorID.substring(0, majorID.indexOf(":")+1);
+ let mid = majorID.substring(majorID.indexOf(":")+1);
+ if (name.charAt(0) !== "+") {name = "+" + name}
+ title_elem.classList.add("Legendary");
+ title_elem.textContent = name;
+ b_elem.classList.add("Crafted");
+ b_elem.textContent = mid;
+ p_elem.appendChild(title_elem);
+ p_elem.appendChild(b_elem);
+ } else {
+ let name = item.get(id).toString()
+ if (name.charAt(0) !== "+") {name = "+" + name}
+ b_elem.classList.add("Legendary");
+ b_elem.textContent = name;
+ p_elem.appendChild(b_elem);
+ }
+ parent_div.appendChild(p_elem);
+ }
+ } else if (id === "lvl" && item.get("tier") === "Crafted") {
+ let p_elem = document.createElement("div");
+ p_elems.classList.add("col");
+ p_elem.textContent = "Combat Level Min: " + item.get("lvlLow") + "-" + item.get(id);
+ parent_div.appendChild(p_elem);
+ } else if (id === "displayName") {
+ let p_elem = document.createElement("div");
+ p_elem.classList.add("col", "text-center", "no-collapse");
+ p_elem.classList.add(item.has("tier") ? item.get("tier").replace(" ","") : "none");
+
+ if (item.get("custom")) {
+ // p_elem.href = url_base.replace(/\w+.html/, "") + "customizer.html#" + item.get("hash");
+ p_elem.textContent = item.get("displayName");
+ } else if (item.get("crafted")) {
+ // p_elem.href = url_base.replace(/\w+.html/, "") + "crafter.html#" + item.get("hash");
+ p_elem.textContent = item.get(id);
+ } else {
+ // p_elem.href = url_base.replace(/\w+.html/, "") + "item.html#" + item.get("displayName");
+ p_elem.textContent = item.get("displayName");
+ }
+
+ parent_div.appendChild(p_elem);
+ let img = document.createElement("img");
+ if (item && item.has("type")) {
+ img.src = "./media/items/" + (newIcons ? "new/":"old/") + "generic-" + item.get("type") + ".png";
+ img.alt = item.get("type");
+ img.style = " z=index: 1; position: relative;";
+ let bckgrd = document.createElement("div");
+ bckgrd.classList.add("col", "px-0", "d-flex", "align-items-center", "justify-content-center", "no-collapse");
+ bckgrd.style = "border-radius: 50%;background-image: radial-gradient(closest-side, " + colorMap.get(item.get("tier")) + " 20%," + "hsl(0, 0%, 16%) 80%); margin-left: auto; margin-right: auto;"
+ bckgrd.classList.add("scaled-bckgrd");
+ parent_div.appendChild(bckgrd);
+ bckgrd.appendChild(img);
+ }
+ } else {
+ let p_elem;
+ if ( !(item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") && (!skp_order.includes(id)) || (skp_order.includes(id) && item.get("tier") !== "Crafted" && parent_div.nodeName === "table") ) { //skp warp
+ p_elem = displaysq2FixedID(parent_div, id, item.get(id), elemental_format);
+ } else if (item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") {
+ p_elem = displaysq2FixedID(parent_div, id, item.get(id+"Low")+"-"+item.get(id), elemental_format);
+ }
+ if (id === "lore") {
+ p_elem.style = "font-style: italic";
+ } else if (skp_order.includes(id)) { //id = str, dex, int, def, or agi
+ if ( item.get("tier") !== "Crafted" && parent_div.nodeName === "DIV") {
+ row = document.createElement("div");
+ row.classList.add("col");
+
+ let title = document.createElement("b");
+ title.textContent = idPrefixes[id] + " ";
+ let boost = document.createElement("b");
+ if (item.get(id) < 0) {
+ boost.classList.add("negative");
+ } else { //boost = 0 SHOULD not come up
+ boost.classList.add("positive");
+ }
+ boost.textContent = item.get(id);
+ row.appendChild(title);
+ row.appendChild(boost);
+ parent_div.appendChild(row);
+ } else if ( item.get("tier") === "Crafted" && parent_div.nodeName === "TABLE") {
+ let row = displaysq2RolledID(item, id, elemental_format);
+ parent_div.appendChild(row);
+ }
+ } else if (id === "restrict") {
+ p_elem.classList.add("restrict");
+ }
+ }
+ }
+ else if ( rolledIDs.includes(id) &&
+ ((item.get("maxRolls") && item.get("maxRolls").get(id))
+ || (item.get("minRolls") && item.get("minRolls").get(id)))) {
+ let style = "positive";
+ if (item.get("minRolls").get(id) < 0) {
+ style = "negative";
+ }
+ if(reversedIDs.includes(id)){
+ style === "positive" ? style = "negative" : style = "positive";
+ }
+ if (fix_id) {
+ p_elem = document.createElement("div");
+ p_elem.classList.add("col", "text-nowrap");
+ if (id == "dex") {
+ console.log("dex activated at fix_id")
+ }
+ displaysq2FixedID(p_elem, id, item.get("minRolls").get(id), elemental_format, style);
+ parent_div.appendChild(p_elem);
+ }
+ else {
+ let row = displaysq2RolledID(item, id, elemental_format);
+ parent_div.appendChild(row);
+ }
+ }else{
+ // :/
+ }
+ }
+ }
+ //Show powder specials ;-;
+ let nonConsumables = ["relik", "wand", "bow", "spear", "dagger", "chestplate", "helmet", "leggings", "boots", "ring", "bracelet", "necklace"];
+ if(nonConsumables.includes(item.get("type"))) {
+ let powder_special = document.createElement("div");
+ powder_special.classList.add("col");
+ let powders = item.get("powders");
+ let element = "";
+ let power = 0;
+ for (let i = 0; i < powders.length; i++) {
+ let firstPowderType = skp_elements[Math.floor(powders[i]/6)];
+ if (element !== "") break;
+ else if (powders[i]%6 > 2) { //t4+
+ for (let j = i+1; j < powders.length; j++) {
+ let currentPowderType = skp_elements[Math.floor(powders[j]/6)]
+ if (powders[j] % 6 > 2 && firstPowderType === currentPowderType) {
+ element = currentPowderType;
+ power = Math.round(((powders[i] % 6 + powders[j] % 6 + 2) / 2 - 4) * 2);
+ break;
+ }
+ }
+ }
+ }
+ if (element !== "") {//powder special is "[e,t,w,f,a]+[0,1,2,3,4]"
+ let powderSpecial = powderSpecialStats[ skp_elements.indexOf(element)];
+ let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]);
+ let specialTitle = document.createElement("span");
+ let specialEffects = document.createElement("span");
+ addClasses(specialTitle, [damageClasses[skp_elements.indexOf(element) + 1]]);
+ let effects;
+ if (item.get("category") === "weapon") {//weapon
+ effects = powderSpecial["weaponSpecialEffects"];
+ specialTitle.textContent = powderSpecial["weaponSpecialName"];
+ }else if (item.get("category") === "armor") {//armor
+ effects = powderSpecial["armorSpecialEffects"];
+ specialTitle.textContent += powderSpecial["armorSpecialName"] + ": ";
+ }
+ for (const [key,value] of effects.entries()) {
+ if (key !== "Description") {
+ let effect = document.createElement("p");
+ effect.classList.add("m-0");
+ effect.textContent = key + ": " + value[power] + specialSuffixes.get(key);
+ if(key === "Damage"){
+ effect.textContent += elementIcons[skp_elements.indexOf(element)];
+ }
+ if (element === "w" && item.get("category") === "armor") {
+ effect.textContent += " / Mana Used";
+ }
+ specialEffects.appendChild(effect);
+ }else{
+ specialTitle.textContent += "[ " + effects.get("Description") + " ]";
+ }
+ }
+ powder_special.appendChild(specialTitle);
+ powder_special.appendChild(specialEffects);
+ parent_div.appendChild(powder_special);
+ }
+ }
+
+ if(item.get("tier") && item.get("tier") === "Crafted") {
+ let dura_elem = document.createElement("div");
+ dura_elem.classList.add("col");
+ let dura = [];
+ let suffix = "";
+ if(nonConsumables.includes(item.get("type"))) {
+ dura = item.get("durability");
+ dura_elem.textContent = "Durability: "
+ } else {
+ dura = item.get("duration");
+ dura_elem.textContent = "Duration: "
+ suffix = " sec."
+ let charges = document.createElement("b");
+ charges.textContent = "Charges: " + item.get("charges");
+ parent_div.appendChild(charges);
+ }
+
+ if (typeof(dura) === "string") {
+ dura_elem.textContent += dura + suffix;
+ } else {
+ dura_elem.textContent += dura[0]+"-"+dura[1] + suffix;
+ }
+ parent_div.append(dura_elem);
+
+ }
+ //Show item tier
+ if (item.get("tier") && item.get("tier") !== " ") {
+ let item_desc_elem = document.createElement("div");
+ item_desc_elem.classList.add("col");
+ item_desc_elem.classList.add(item.get("tier"));
+ item_desc_elem.textContent = item.get("tier")+" "+item.get("type");
+ parent_div.append(item_desc_elem);
+ }
+
+ //Show item hash if applicable
+ if (item.get("crafted") || item.get("custom")) {
+ let item_desc_elem = document.createElement("p");
+ item_desc_elem.classList.add('itemp');
+ item_desc_elem.style.maxWidth = "100%";
+ item_desc_elem.style.wordWrap = "break-word";
+ item_desc_elem.style.wordBreak = "break-word";
+ item_desc_elem.textContent = item.get("hash");
+ parent_div.append(item_desc_elem);
+ }
+
+ if (item.get("category") === "weapon") {
+ let damage_mult = baseDamageMultiplier[attackSpeeds.indexOf(item.get("atkSpd"))];
+ let total_damages = item.get("basedps");
+ let base_dps_elem = document.createElement("p");
+ base_dps_elem.classList.add("left");
+ base_dps_elem.classList.add("itemp");
+ if (item.get("tier") === "Crafted") {
+ let base_dps_min = total_damages[0] * damage_mult;
+ let base_dps_max = total_damages[1] * damage_mult;
+
+ base_dps_elem.textContent = "Base DPS: "+base_dps_min.toFixed(3)+"\u279c"+base_dps_max.toFixed(3);
+ }
+ else {
+ base_dps_elem.textContent = "Base DPS: "+(total_damages * damage_mult);
+ }
+ parent_div.appendChild(document.createElement("p"));
+ parent_div.appendChild(base_dps_elem);
+ }
+}
+
+function displaysq2RolledID(item, id, elemental_format) {
+ let row = document.createElement('div');
+ row.classList.add('col');
+
+ let item_div = document.createElement('div');
+ item_div.classList.add('row');
+
+ let min_elem = document.createElement('div');
+ min_elem.classList.add('col', 'text-start');
+ let id_min = item.get("minRolls").get(id)
+ let style = id_min < 0 ? "negative" : "positive";
+ if(reversedIDs.includes(id)){
+ style === "positive" ? style = "negative" : style = "positive";
+ }
+ min_elem.classList.add(style);
+ min_elem.textContent = id_min + idSuffixes[id];
+ item_div.appendChild(min_elem);
+
+ let desc_elem = document.createElement('div');
+ desc_elem.classList.add('col', 'text-center', 'text-nowrap');
+ //TODO elemental format jank
+ if (elemental_format) {
+ apply_sq2_elemental_format(desc_elem, id);
+ }
+ else {
+ desc_elem.textContent = idPrefixes[id];
+ }
+ item_div.appendChild(desc_elem);
+
+ let max_elem = document.createElement('div');
+ let id_max = item.get("maxRolls").get(id)
+ max_elem.classList.add('col', 'text-end');
+ style = id_max < 0 ? "negative" : "positive";
+ if(reversedIDs.includes(id)){
+ style === "positive" ? style = "negative" : style = "positive";
+ }
+ max_elem.classList.add(style);
+ max_elem.textContent = id_max + idSuffixes[id];
+ item_div.appendChild(max_elem);
+ row.appendChild(item_div);
+ return row;
+}
+
+function displaysq2WeaponBase(build) {
+ // let base_damage = build.get('damageRaw');
+ let damage_keys = [ "nDam", "eDam", "tDam", "wDam", "fDam", "aDam" ];
+
+ // pP base calc (why do i still use pP)
+ let item = build.weapon;
+ let stats = new Map();
+ stats.set("atkSpd", item.get("atkSpd"));
+ stats.set("damageBonus", [0, 0, 0, 0, 0]);
+ stats.set("damageRaw", [item.get("nDam"), item.get("eDam"), item.get("tDam"), item.get("wDam"), item.get("fDam"), item.get("aDam")]);
+
+ let results = calculateSpellDamage(stats, [100, 0, 0, 0, 0, 0], 0, 0, 0, build.weapon, [0, 0, 0, 0, 0], 1, undefined);
+ let powdered_base = results[2];
+
+ const powdered_map = new Map();
+ powdered_map.set('nDam', powdered_base[0][0]+'-'+powdered_base[0][1]);
+ powdered_map.set('eDam', powdered_base[1][0]+'-'+powdered_base[1][1]);
+ powdered_map.set('tDam', powdered_base[2][0]+'-'+powdered_base[2][1]);
+ powdered_map.set('wDam', powdered_base[3][0]+'-'+powdered_base[3][1]);
+ powdered_map.set('fDam', powdered_base[4][0]+'-'+powdered_base[4][1]);
+ powdered_map.set('aDam', powdered_base[5][0]+'-'+powdered_base[5][1]);
+
+ // display
+ for (const i in damage_keys) {
+ document.getElementById(damage_keys[i]+"-base").textContent = powdered_map.get(damage_keys[i]);
+ }
+}
+
+function displaysq2FixedID(active, id, value, elemental_format, style) {
+ if (style) {
+ let row = document.createElement('div');
+ row.classList.add("row");
+ let desc_elem = document.createElement('div');
+ desc_elem.classList.add('col');
+ desc_elem.classList.add('text-start');
+
+ if (elemental_format) {
+ apply_sq2_elemental_format(desc_elem, id);
+ }
+ else {
+ desc_elem.textContent = idPrefixes[id];
+ }
+ row.appendChild(desc_elem);
+
+ let value_elem = document.createElement('div');
+ value_elem.classList.add('col');
+ value_elem.classList.add('text-end');
+ value_elem.classList.add(style);
+ value_elem.textContent = value + idSuffixes[id];
+ row.appendChild(value_elem);
+ active.appendChild(row);
+ return row;
+ }
+ else {
+ // HACK TO AVOID DISPLAYING ZERO DAMAGE! TODO
+ if (value === "0-0" || value === "0-0\u279c0-0") {
+ return;
+ }
+ let p_elem = document.createElement('div');
+ p_elem.classList.add('col');
+ if (elemental_format) {
+ apply_sq2_elemental_format(p_elem, id, value);
+ }
+ else {
+ p_elem.textContent = idPrefixes[id].concat(value, idSuffixes[id]);
+ }
+ active.appendChild(p_elem);
+ return p_elem;
+ }
+}
+
+function displaysq2PoisonDamage(overallparent_elem, build) {
+ overallparent_elem.textContent = "";
+
+ //Title
+ let title_elemavg = document.createElement("b");
+ title_elemavg.textContent = "Poison Stats";
+ overallparent_elem.append(title_elemavg);
+
+ let overallpoisonDamage = document.createElement("p");
+ let overallpoisonDamageFirst = document.createElement("span");
+ let overallpoisonDamageSecond = document.createElement("span");
+ let poison_tick = Math.ceil(build.statMap.get("poison") * (1+skillPointsToPercentage(build.total_skillpoints[0])) * (build.statMap.get("poisonPct") + build.externalStats.get("poisonPct"))/100 /3);
+ overallpoisonDamageFirst.textContent = "Poison Tick: ";
+ overallpoisonDamageSecond.textContent = Math.max(poison_tick,0);
+ overallpoisonDamageSecond.classList.add("Damage");
+
+ overallpoisonDamage.appendChild(overallpoisonDamageFirst);
+ overallpoisonDamage.appendChild(overallpoisonDamageSecond);
+ overallparent_elem.append(overallpoisonDamage);
+}
+
+function displaysq2MeleeDamage(parent_elem, overallparent_elem, meleeStats){
+ // console.log("Melee Stats");
+ // console.log(meleeStats);
+ let tooltipinfo = meleeStats[13];
+ let attackSpeeds = ["Super Slow", "Very Slow", "Slow", "Normal", "Fast", "Very Fast", "Super Fast"];
+ //let damagePrefixes = ["Neutral Damage: ","Earth Damage: ","Thunder Damage: ","Water Damage: ","Fire Damage: ","Air Damage: "];
+ parent_elem.textContent = "";
+ overallparent_elem.textContent = "";
+ const stats = meleeStats.slice();
+
+ for (let i = 0; i < 6; ++i) {
+ for (let j in stats[i]) {
+ stats[i][j] = stats[i][j].toFixed(2);
+ }
+ }
+ for (let i = 6; i < 8; ++i) {
+ for (let j = 0; j < 2; j++) {
+ stats[i][j] = stats[i][j].toFixed(2);
+ }
+ }
+ for (let i = 8; i < 11; ++i){
+ stats[i] = stats[i].toFixed(2);
+ }
+ //tooltipelem, tooltiptext
+ let tooltip; let tooltiptext;
+
+ //title
+ let title_elem = document.createElement("p");
+ title_elem.classList.add("title");
+ title_elem.textContent = "Melee Stats";
+ parent_elem.append(title_elem);
+ parent_elem.append(document.createElement("br"));
+
+ //overall title
+ let title_elemavg = document.createElement("b");
+ title_elemavg.textContent = "Melee Stats";
+ overallparent_elem.append(title_elemavg);
+
+ //average DPS
+ let averageDamage = document.createElement("p");
+ averageDamage.classList.add("left");
+ averageDamage.textContent = "Average DPS: " + stats[10];
+ tooltiptext = `= ((${stats[8]} * ${(stats[6][2]).toFixed(2)}) + (${stats[9]} * ${(stats[7][2]).toFixed(2)}))`
+ tooltip = createTooltip(tooltip, "p", tooltiptext, averageDamage, ["melee-tooltip"]);
+ averageDamage.appendChild(tooltip);
+ parent_elem.append(averageDamage);
+
+ //overall average DPS
+ let overallaverageDamage = document.createElement("p");
+ let overallaverageDamageFirst = document.createElement("span");
+ overallaverageDamageFirst.textContent = "Average DPS: "
+
+ let overallaverageDamageSecond = document.createElement("span");
+ overallaverageDamageSecond.classList.add("Damage");
+ overallaverageDamageSecond.textContent = stats[10];
+ overallaverageDamage.appendChild(overallaverageDamageFirst);
+ overallaverageDamage.appendChild(overallaverageDamageSecond);
+
+ overallparent_elem.append(overallaverageDamage);
+ //overallparent_elem.append(document.createElement("br"));
+
+ //attack speed
+ let atkSpd = document.createElement("p");
+ atkSpd.classList.add("left");
+ atkSpd.textContent = "Attack Speed: " + attackSpeeds[stats[11]];
+ parent_elem.append(atkSpd);
+ parent_elem.append(document.createElement("br"));
+
+ //overall attack speed
+ let overallatkSpd = document.createElement("p");
+ let overallatkSpdFirst = document.createElement("span");
+ overallatkSpdFirst.textContent = "Attack Speed: ";
+ let overallatkSpdSecond = document.createElement("span");
+ overallatkSpdSecond.classList.add("Damage");
+ overallatkSpdSecond.textContent = attackSpeeds[stats[11]];
+ overallatkSpd.appendChild(overallatkSpdFirst);
+ overallatkSpd.appendChild(overallatkSpdSecond);
+ overallparent_elem.append(overallatkSpd);
+
+ //Non-Crit: n->elem, total dmg, DPS
+ let nonCritStats = document.createElement("p");
+ nonCritStats.classList.add("left");
+ nonCritStats.textContent = "Non-Crit Stats: ";
+ nonCritStats.append(document.createElement("br"));
+ for (let i = 0; i < 6; i++){
+ if(stats[i][1] != 0){
+ let dmg = document.createElement("p");
+ dmg.textContent = stats[i][0] + " \u2013 " + stats[i][1];
+ dmg.classList.add(damageClasses[i]);
+ dmg.classList.add("itemp");
+ tooltiptext = tooltipinfo.get("damageformulas")[i].slice(0,2).join("\n");
+ tooltip = createTooltip(tooltip, "p", tooltiptext, dmg, ["melee-tooltip"]);
+ nonCritStats.append(dmg);
+ }
+ }
+
+ let normalDamage = document.createElement("p");
+ normalDamage.textContent = "Total: " + stats[6][0] + " \u2013 " + stats[6][1];
+ let tooltiparr = ["Min: = ", "Max: = "]
+ let arr = []; let arr2 = [];
+ for (let i = 0; i < 6; i++) {
+ if (stats[i][0] != 0) {
+ arr.push(stats[i][0]);
+ arr2.push(stats[i][1]);
+ }
+ }
+ tooltiptext = tooltiparr[0] + arr.join(" + ") + "\n" + tooltiparr[1] + arr2.join(" + ");
+ tooltip = createTooltip(tooltip, "p", tooltiptext, normalDamage, ["melee-tooltip"]);
+ nonCritStats.append(normalDamage);
+
+ let normalDPS = document.createElement("p");
+ normalDPS.textContent = "Normal DPS: " + stats[8];
+ normalDPS.classList.add("tooltip");
+ tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`;
+ tooltip = createTooltip(tooltip, "p", tooltiptext, normalDPS, ["melee-tooltip"]);
+ nonCritStats.append(normalDPS);
+
+ //overall average DPS
+ let singleHitDamage = document.createElement("p");
+ let singleHitDamageFirst = document.createElement("span");
+ singleHitDamageFirst.textContent = "Single Hit Average: ";
+ let singleHitDamageSecond = document.createElement("span");
+ singleHitDamageSecond.classList.add("Damage");
+ singleHitDamageSecond.textContent = stats[12].toFixed(2);
+ tooltiptext = ` = ((${stats[6][0]} + ${stats[6][1]}) / 2) * ${stats[6][2].toFixed(2)} + ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${stats[7][2].toFixed(2)}`;
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, singleHitDamage, ["melee-tooltip", "summary-tooltip"]);
+
+ singleHitDamage.appendChild(singleHitDamageFirst);
+ singleHitDamage.appendChild(singleHitDamageSecond);
+ overallparent_elem.append(singleHitDamage);
+
+ let normalChance = document.createElement("p");
+ normalChance.textContent = "Non-Crit Chance: " + (stats[6][2]*100).toFixed(2) + "%";
+ normalChance.append(document.createElement("br"));
+ normalChance.append(document.createElement("br"));
+ nonCritStats.append(normalChance);
+
+ parent_elem.append(nonCritStats);
+ parent_elem.append(document.createElement("br"));
+
+ //Crit: n->elem, total dmg, DPS
+ let critStats = document.createElement("p");
+ critStats.classList.add("left");
+ critStats.textContent = "Crit Stats: ";
+ critStats.append(document.createElement("br"));
+ for (let i = 0; i < 6; i++){
+ if(stats[i][3] != 0) {
+ dmg = document.createElement("p");
+ dmg.textContent = stats[i][2] + " \u2013 " + stats[i][3];
+ dmg.classList.add(damageClasses[i]);
+ dmg.classList.add("itemp");
+ tooltiptext = tooltipinfo.get("damageformulas")[i].slice(2,4).join("\n");
+ tooltip = createTooltip(tooltip, "p", tooltiptext, dmg, ["melee-tooltip"]);
+ critStats.append(dmg);
+ }
+ }
+ let critDamage = document.createElement("p");
+ critDamage.textContent = "Total: " + stats[7][0] + " \u2013 " + stats[7][1];
+ tooltiparr = ["Min: = ", "Max: = "]
+ arr = []; arr2 = [];
+ for (let i = 0; i < 6; i++) {
+ if (stats[i][0] != 0) {
+ arr.push(stats[i][2]);
+ arr2.push(stats[i][3]);
+ }
+ }
+ tooltiptext = tooltiparr[0] + arr.join(" + ") + "\n" + tooltiparr[1] + arr2.join(" + ");
+ tooltip = createTooltip(tooltip, "p", tooltiptext, critDamage, ["melee-tooltip"]);
+
+ critStats.append(critDamage);
+
+ let critDPS = document.createElement("p");
+ critDPS.textContent = "Crit DPS: " + stats[9];
+ tooltiptext = ` = ((${stats[7][0]} + ${stats[7][1]}) / 2) * ${baseDamageMultiplier[stats[11]]}`;
+ tooltip = createTooltip(tooltip, "p", tooltiptext, critDPS, ["melee-tooltip"]);
+ critStats.append(critDPS);
+
+ let critChance = document.createElement("p");
+ critChance.textContent = "Crit Chance: " + (stats[7][2]*100).toFixed(2) + "%";
+ critChance.append(document.createElement("br"));
+ critChance.append(document.createElement("br"));
+ critStats.append(critChance);
+
+ parent_elem.append(critStats);
+}
+
+function displaysq2ArmorStats(build) {
+ let armor_keys = ['helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace'];
+
+ for (const i in armor_keys) {
+ document.getElementById(armor_keys[i]+'-health').textContent = build[armor_keys[i]].get('hp');
+ document.getElementById(armor_keys[i]+'-lv').textContent = build[armor_keys[i]].get('lvl');
+ }
+}
+
+function displaysq2DefenseStats(parent_elem, build, insertSummary){
+ let defenseStats = build.getDefenseStats();
+ insertSummary = (typeof insertSummary !== 'undefined') ? insertSummary : false;
+ if (!insertSummary) {
+ parent_elem.textContent = "";
+ }
+ const stats = defenseStats.slice();
+
+ // parent_elem.append(document.createElement("br"));
+ let statsTable = document.createElement("div");
+
+ //[total hp, ehp, total hpr, ehpr, [def%, agi%], [edef,tdef,wdef,fdef,adef]]
+ for(const i in stats){
+ if(typeof stats[i] === "number"){
+ stats[i] = stats[i].toFixed(2);
+ }else{
+ for(const j in stats[i]){
+ stats[i][j] = stats[i][j].toFixed(2);
+ }
+ }
+ }
+
+ //total HP
+ let hpRow = document.createElement("div");
+ hpRow.classList.add('row');
+ let hp = document.createElement("div");
+ hp.classList.add('col');
+ hp.classList.add("Health");
+ hp.classList.add("text-start");
+ hp.textContent = "Total HP:";
+ let boost = document.createElement("div");
+ boost.classList.add('col');
+ boost.textContent = stats[0];
+ boost.classList.add("text-end");
+
+ hpRow.appendChild(hp);
+ hpRow.append(boost);
+
+ if (insertSummary) {
+ parent_elem.appendChild(hpRow);
+ } else {
+ statsTable.appendChild(hpRow);
+ }
+
+ let tooltip; let tooltiptext;
+
+ let defMult = build.statMap.get("defMult");
+ if (!defMult) {defMult = 1}
+
+ //EHP
+ let ehpRow = document.createElement("div");
+ ehpRow.classList.add("row");
+ let ehp = document.createElement("div");
+ ehp.classList.add("col");
+ ehp.classList.add("text-start");
+ ehp.textContent = "Effective HP:";
+
+ boost = document.createElement("div");
+ boost.textContent = stats[1][0];
+ boost.classList.add("col");
+ boost.classList.add("text-end");
+ tooltiptext = `= ${stats[0]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (1 - ${skillPointsToPercentage(build.total_skillpoints[4]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))`
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]);
+
+ ehpRow.appendChild(ehp);
+ ehpRow.append(boost);
+
+ if (insertSummary) {
+ parent_elem.appendChild(ehpRow)
+ } else {
+ statsTable.append(ehpRow);
+ }
+
+ ehpRow = document.createElement("div");
+ ehpRow.classList.add("row");
+ ehp = document.createElement("div");
+ ehp.classList.add("col");
+ ehp.classList.add("text-start");
+ ehp.textContent = "Effective HP (no agi):";
+
+ boost = document.createElement("div");
+ boost.textContent = stats[1][1];
+ boost.classList.add("col");
+ boost.classList.add("text-end");
+ tooltiptext = `= ${stats[0]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))`
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]);
+
+ ehpRow.appendChild(ehp);
+ ehpRow.append(boost);
+ statsTable.append(ehpRow);
+
+ //total HPR
+ let hprRow = document.createElement("div");
+ hprRow.classList.add("row")
+ let hpr = document.createElement("div");
+ hpr.classList.add("Health");
+ hpr.classList.add("col");
+ hpr.classList.add("text-start");
+ hpr.textContent = "HP Regen (Total):";
+ boost = document.createElement("div");
+ boost.textContent = stats[2];
+ boost.classList.add("col");
+ boost.classList.add("text-end");
+
+ hprRow.appendChild(hpr);
+ hprRow.appendChild(boost);
+
+ if (insertSummary) {
+ parent_elem.appendChild(hprRow);
+ } else {
+ statsTable.appendChild(hprRow);
+ }
+
+ //EHPR
+ let ehprRow = document.createElement("div");
+ ehprRow.classList.add("row")
+ let ehpr = document.createElement("div");
+ ehpr.classList.add("col");
+ ehpr.classList.add("text-start");
+ ehpr.textContent = "Effective HP Regen:";
+
+ boost = document.createElement("div");
+ boost.textContent = stats[3][0];
+ boost.classList.add("col");
+ boost.classList.add("text-end");
+ tooltiptext = `= ${stats[2]} / ((1 - ${skillPointsToPercentage(build.total_skillpoints[3]).toFixed(3)}) * (1 - ${skillPointsToPercentage(build.total_skillpoints[4]).toFixed(3)}) * (2 - ${defMult}) * (2 - ${build.defenseMultiplier}))`
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]);
+
+ ehprRow.appendChild(ehpr);
+ ehprRow.append(boost);
+ statsTable.append(ehprRow);
+ /*
+ ehprRow = document.createElement("tr");
+ ehpr = document.createElement("td");
+ ehpr.classList.add("left");
+ ehpr.textContent = "Effective HP Regen (no agi):";
+
+ boost = document.createElement("td");
+ boost.textContent = stats[3][1];
+ boost.classList.add("right");
+
+ ehprRow.appendChild(ehpr);
+ ehprRow.append(boost);
+ statsTable.append(ehprRow); */
+
+ //eledefs
+ let eledefs = stats[5];
+ for (let i = 0; i < eledefs.length; i++){
+ let eledefElemRow = document.createElement("div");
+ eledefElemRow.classList.add("row")
+
+ let eledef = document.createElement("div");
+ eledef.classList.add("col");
+ eledef.classList.add("text-start");
+ let eledefTitle = document.createElement("span");
+ eledefTitle.textContent = damageClasses[i+1];
+ eledefTitle.classList.add(damageClasses[i+1]);
+
+ let defense = document.createElement("span");
+ defense.textContent = " Def (Total): ";
+
+ eledef.appendChild(eledefTitle);
+ eledef.appendChild(defense);
+ eledefElemRow.appendChild(eledef);
+
+ let boost = document.createElement("div");
+ boost.textContent = eledefs[i];
+ boost.classList.add(eledefs[i] >= 0 ? "positive" : "negative");
+ boost.classList.add("col");
+ boost.classList.add("text-end");
+
+ let defRaw = build.statMap.get("defRaw")[i];
+ let defPct = build.statMap.get("defBonus")[i]/100;
+ if (defRaw < 0) {
+ defPct >= 0 ? defPct = "- " + defPct: defPct = "+ " + defPct;
+ tooltiptext = `= min(0, ${defRaw} * (1 ${defPct}))`
+ } else {
+ defPct >= 0 ? defPct = "+ " + defPct: defPct = "- " + defPct;
+ tooltiptext = `= ${defRaw} * (1 ${defPct})`
+ }
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, boost, ["def-tooltip"]);
+
+ eledefElemRow.appendChild(boost);
+
+ if (insertSummary) {
+ parent_elem.appendChild(eledefElemRow);
+ } else {
+ statsTable.appendChild(eledefElemRow);
+ }
+ }
+
+ if (!insertSummary) {
+ //skp
+ let defRow = document.createElement("div");
+ defRow.classList.add("row");
+ let defElem = document.createElement("div");
+ defElem.classList.add("col");
+ defElem.classList.add("text-start");
+ defElem.textContent = "Damage Absorbed %:";
+ boost = document.createElement("div");
+ boost.classList.add("col");
+ boost.classList.add("text-end");
+ boost.textContent = stats[4][0] + "%";
+ defRow.appendChild(defElem);
+ defRow.appendChild(boost);
+ statsTable.append(defRow);
+
+ let agiRow = document.createElement("div");
+ agiRow.classList.add("row");
+ let agiElem = document.createElement("div");
+ agiElem.classList.add("col");
+ agiElem.classList.add("text-start");
+ agiElem.textContent = "Dodge Chance %:";
+ boost = document.createElement("div");
+ boost.classList.add("col");
+ boost.classList.add("text-end");
+ boost.textContent = stats[4][1] + "%";
+ agiRow.appendChild(agiElem);
+ agiRow.appendChild(boost);
+ statsTable.append(agiRow);
+ }
+
+ if (!insertSummary) {
+ parent_elem.append(statsTable);
+ }
+}
+
+function displaysq2PowderSpecials(parent_elem, powderSpecials, build, overall=false) {
+ parent_elem.textContent = ""
+ let title = document.createElement("b");
+ title.textContent = "Powder Specials";
+ parent_elem.appendChild(title);
+ let specials = powderSpecials.slice();
+ let stats = build.statMap;
+ let expandedStats = new Map();
+ //each entry of powderSpecials is [ps, power]
+ for (special of specials) {
+ //iterate through the special and display its effects.
+ let powder_special = document.createElement("p");
+ let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]);
+ let specialTitle = document.createElement("p");
+ let specialEffects = document.createElement("p");
+ specialTitle.classList.add(damageClasses[powderSpecialStats.indexOf(special[0]) + 1]);
+ let effects = special[0]["weaponSpecialEffects"];
+ let power = special[1];
+ specialTitle.textContent = special[0]["weaponSpecialName"] + " " + Math.floor((power-1)*0.5 + 4) + (power % 2 == 0 ? ".5" : "");
+
+ if (!overall || powderSpecialStats.indexOf(special[0]) == 2 || powderSpecialStats.indexOf(special[0]) == 3 || powderSpecialStats.indexOf(special[0]) == 4) {
+ for (const [key,value] of effects) {
+ let effect = document.createElement("p");
+ effect.textContent += key + ": " + value[power-1] + specialSuffixes.get(key);
+ if(key === "Damage"){
+ effect.textContent += elementIcons[powderSpecialStats.indexOf(special[0])];
+ }
+ if(special[0]["weaponSpecialName"] === "Wind Prison" && key === "Damage Boost") {
+ effect.textContent += " (only 1st hit)";
+ }
+ specialEffects.appendChild(effect);
+ }
+ }
+ powder_special.appendChild(specialTitle);
+ powder_special.appendChild(specialEffects);
+
+ //if this special is an instant-damage special (Quake, Chain Lightning, Courage Burst), display the damage.
+ let specialDamage = document.createElement("p");
+ // specialDamage.classList.add("item-margin");
+ let spells = spell_table["powder"];
+ if (powderSpecialStats.indexOf(special[0]) == 0 || powderSpecialStats.indexOf(special[0]) == 1 || powderSpecialStats.indexOf(special[0]) == 3) { //Quake, Chain Lightning, or Courage
+ let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]);
+ let part = spell["parts"][0];
+ let _results = calculateSpellDamage(stats, part.conversion,
+ stats.get("mdRaw"), stats.get("mdPct") + build.externalStats.get("mdPct"),
+ 0, build.weapon, build.total_skillpoints, build.damageMultiplier * ((part.multiplier[power-1] / 100)), build.externalStats);//part.multiplier[power] / 100
+
+ let critChance = skillPointsToPercentage(build.total_skillpoints[1]);
+ let save_damages = [];
+
+ let totalDamNormal = _results[0];
+ let totalDamCrit = _results[1];
+ let results = _results[2];
+ 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;
+
+ let averageWrap = document.createElement("p");
+ let averageLabel = document.createElement("span");
+ averageLabel.textContent = "Average: ";
+
+ let averageLabelDmg = document.createElement("span");
+ averageLabelDmg.classList.add("Damage");
+ averageLabelDmg.textContent = averageDamage.toFixed(2);
+
+ averageWrap.appendChild(averageLabel);
+ averageWrap.appendChild(averageLabelDmg);
+ specialDamage.appendChild(averageWrap);
+
+ if (!overall) {
+ let nonCritLabel = document.createElement("p");
+ nonCritLabel.textContent = "Non-Crit Average: "+nonCritAverage.toFixed(2);
+ nonCritLabel.classList.add("damageSubtitle");
+ nonCritLabel.classList.add("item-margin");
+ specialDamage.append(nonCritLabel);
+
+ for (let i = 0; i < 6; i++){
+ if (results[i][1] > 0){
+ let p = document.createElement("p");
+ p.classList.add("damagep");
+ p.classList.add(damageClasses[i]);
+ p.textContent = results[i][0]+"-"+results[i][1];
+ specialDamage.append(p);
+ }
+ }
+ let normalDamage = document.createElement("p");
+ normalDamage.textContent = "Total: " + totalDamNormal[0].toFixed(2) + "-" + totalDamNormal[1].toFixed(2);
+ normalDamage.classList.add("itemp");
+ specialDamage.append(normalDamage);
+
+ let nonCritChanceLabel = document.createElement("p");
+ nonCritChanceLabel.textContent = "Non-Crit Chance: " + ((1-critChance)*100).toFixed(2) + "%";
+ specialDamage.append(nonCritChanceLabel);
+
+ let critLabel = document.createElement("p");
+ critLabel.textContent = "Crit Average: "+critAverage.toFixed(2);
+ critLabel.classList.add("damageSubtitle");
+ critLabel.classList.add("item-margin");
+
+ specialDamage.append(critLabel);
+ for (let i = 0; i < 6; i++){
+ if (results[i][1] > 0){
+ let p = document.createElement("p");
+ p.classList.add("damagep");
+ p.classList.add(damageClasses[i]);
+ p.textContent = results[i][2]+"-"+results[i][3];
+ specialDamage.append(p);
+ }
+ }
+ let critDamage = document.createElement("p");
+ critDamage.textContent = "Total: " + totalDamCrit[0].toFixed(2) + "-" + totalDamCrit[1].toFixed(2);
+ critDamage.classList.add("itemp");
+ specialDamage.append(critDamage);
+
+ let critChanceLabel = document.createElement("p");
+ critChanceLabel.textContent = "Crit Chance: " + (critChance*100).toFixed(2) + "%";
+ specialDamage.append(critChanceLabel);
+
+ save_damages.push(averageDamage);
+ }
+
+ powder_special.append(specialDamage);
+ }
+
+ parent_elem.appendChild(powder_special);
+ }
+}
+
+function displaysq2SpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx) {
+ parent_elem.textContent = "";
+
+
+ let tooltip; let tooltiptext;
+ const stats = build.statMap;
+ let title_elem = document.createElement("p");
+
+ overallparent_elem.textContent = "";
+ let title_elemavg = document.createElement("b");
+
+ if (spellIdx != 0) {
+ let first = document.createElement("span");
+ first.textContent = spell.title + " (";
+ title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here.
+ title_elemavg.appendChild(first);
+
+ let second = document.createElement("span");
+ second.textContent = build.getSpellCost(spellIdx, spell.cost);
+ second.classList.add("Mana");
+
+ let int_redux = skillPointsToPercentage(build.total_skillpoints[2]).toFixed(2);
+ let spPct_redux = (build.statMap.get("spPct" + spellIdx)/100).toFixed(2);
+ let spRaw_redux = (build.statMap.get("spRaw" + spellIdx)).toFixed(2);
+ spPct_redux >= 0 ? spPct_redux = "+ " + spPct_redux : spPct_redux = "- " + Math.abs(spPct_redux);
+ spRaw_redux >= 0 ? spRaw_redux = "+ " + spRaw_redux : spRaw_redux = "- " + Math.abs(spRaw_redux);
+
+ // tooltiptext = `= max(1, floor((ceil(${spell.cost} * (1 - ${int_redux})) ${spRaw_redux}) * (1 ${spPct_redux})))`;
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, second, ["spellcostcalc"]);
+ // second.appendChild(tooltip);
+ title_elem.appendChild(second.cloneNode(true));
+ title_elemavg.appendChild(second);
+
+
+ let third = document.createElement("span");
+ third.textContent = ") [Base: " + build.getBaseSpellCost(spellIdx, spell.cost) + " ]";
+ title_elem.appendChild(third);
+ let third_summary = document.createElement("span");
+ third_summary.textContent = ")";
+ title_elemavg.appendChild(third_summary);
+ }
+ else {
+ title_elem.textContent = spell.title;
+ title_elemavg.textContent = spell.title;
+ }
+
+ parent_elem.append(title_elem);
+ overallparent_elem.append(title_elemavg);
+
+ overallparent_elem.append(displaysq2NextCosts(spell, build));
+
+
+ let critChance = skillPointsToPercentage(build.total_skillpoints[1]);
+
+ let save_damages = [];
+
+ let part_divavg = document.createElement("p");
+ overallparent_elem.append(part_divavg);
+
+ 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;
+ }
+ }
+ }
+ //console.log(spell_parts);
+
+ for (const part of spell_parts) {
+ let part_div = document.createElement("p");
+ parent_elem.append(part_div);
+
+ let subtitle_elem = document.createElement("p");
+ subtitle_elem.textContent = part.subtitle;
+ part_div.append(subtitle_elem);
+
+ if (part.type === "damage") {
+ //console.log(build.expandedStats);
+ let _results = calculateSpellDamage(stats, part.conversion,
+ stats.get("sdRaw") + stats.get("rainbowRaw"), stats.get("sdPct") + build.externalStats.get("sdPct"),
+ part.multiplier / 100, build.weapon, build.total_skillpoints, build.damageMultiplier, build.externalStats);
+ 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;
+
+ let averageLabel = document.createElement("p");
+ averageLabel.textContent = "Average: "+averageDamage.toFixed(2);
+ tooltiptext = ` = ((1 - ${critChance}) * ${nonCritAverage.toFixed(2)}) + (${critChance} * ${critAverage.toFixed(2)})`
+ // averageLabel.classList.add("damageSubtitle");
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, averageLabel, ["spell-tooltip"]);
+ part_div.append(averageLabel);
+
+
+ if (part.summary == true) {
+ let overallaverageLabel = document.createElement("p");
+ let first = document.createElement("span");
+ let second = document.createElement("span");
+ first.textContent = part.subtitle + " Average: ";
+ second.textContent = averageDamage.toFixed(2);
+ overallaverageLabel.appendChild(first);
+ overallaverageLabel.appendChild(second);
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, overallaverageLabel, ["spell-tooltip", "summary-tooltip"]);
+ second.classList.add("Damage");
+ part_divavg.append(overallaverageLabel);
+ }
+
+ function _damage_display(label_text, average, result_idx) {
+ let label = document.createElement("p");
+ label.textContent = label_text+average.toFixed(2);
+ part_div.append(label);
+
+ let arrmin = [];
+ let arrmax = [];
+ for (let i = 0; i < 6; i++){
+ if (results[i][1] != 0){
+ let p = document.createElement("p");
+ p.classList.add(damageClasses[i]);
+ p.textContent = results[i][result_idx] + " \u2013 " + results[i][result_idx + 1];
+ arrmin.push(results[i][result_idx]);
+ arrmax.push(results[i][result_idx + 1]);
+ part_div.append(p);
+ }
+ }
+ tooltiptext = ` = ((${arrmin.join(" + ")}) + (${arrmax.join(" + ")})) / 2`;
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, label, ["spell-tooltip"]);
+ }
+ _damage_display("Non-Crit Average: ", nonCritAverage, 0);
+ _damage_display("Crit Average: ", critAverage, 2);
+
+ save_damages.push(averageDamage);
+ } else if (part.type === "heal") {
+ let heal_amount = (part.strength * build.getDefenseStats()[0] * Math.max(0.5,Math.min(1.75, 1 + 0.5 * stats.get("wDamPct")/100))).toFixed(2);
+ tooltiptext = ` = ${part.strength} * ${build.getDefenseStats()[0]} * max(0.5, min(1.75, 1 + 0.5 * ${stats.get("wDamPct")/100}))`;
+ let healLabel = document.createElement("p");
+ healLabel.textContent = heal_amount;
+ // healLabel.classList.add("damagep");
+ // tooltip = createTooltip(tooltip, "p", tooltiptext, healLabel, ["spell-tooltip"]);
+ part_div.append(healLabel);
+ if (part.summary == true) {
+ let overallhealLabel = document.createElement("p");
+ let first = document.createElement("span");
+ let second = document.createElement("span");
+ first.textContent = part.subtitle + ": ";
+ second.textContent = heal_amount;
+ overallhealLabel.appendChild(first);
+ second.classList.add("Set");
+ overallhealLabel.appendChild(second);
+ part_divavg.append(overallhealLabel);
+ }
+ } else if (part.type === "total") {
+ let total_damage = 0;
+ tooltiptext = "";
+ for (let i in part.factors) {
+ total_damage += save_damages[i] * part.factors[i];
+ }
+
+ let dmgarr = part.factors.slice();
+ dmgarr = dmgarr.map(x => "(" + x + " * " + save_damages[dmgarr.indexOf(x)].toFixed(2) + ")");
+ tooltiptext = " = " + dmgarr.join(" + ");
+
+
+ let averageLabel = document.createElement("p");
+ averageLabel.textContent = "Average: "+total_damage.toFixed(2);
+ averageLabel.classList.add("damageSubtitle");
+ tooltip = createTooltip(tooltip, "p", tooltiptext, averageLabel, ["spell-tooltip"]);
+ part_div.append(averageLabel);
+
+ let overallaverageLabel = document.createElement("p");
+ let overallaverageLabelFirst = document.createElement("span");
+ let overallaverageLabelSecond = document.createElement("span");
+ overallaverageLabelFirst.textContent = "Average: ";
+ overallaverageLabelSecond.textContent = total_damage.toFixed(2);
+ overallaverageLabelSecond.classList.add("Damage");
+
+
+ overallaverageLabel.appendChild(overallaverageLabelFirst);
+ overallaverageLabel.appendChild(overallaverageLabelSecond);
+ part_divavg.append(overallaverageLabel);
+ }
+ }
+}
+
+function displaysq2EquipOrder(parent_elem, buildOrder){
+ parent_elem.textContent = "";
+ const order = buildOrder.slice();
+ let title_elem = document.createElement("b");
+ title_elem.textContent = "Equip order ";
+ title_elem.classList.add("Normal", "text-center");
+ parent_elem.append(title_elem);
+ for (const item of order) {
+ let p_elem = document.createElement("b");
+ p_elem.textContent = item.get("displayName");
+ parent_elem.append(p_elem);
+ }
+}
+
+function displaysq2NextCosts(spell, build) {
+ let int = build.total_skillpoints[2];
+ let spells = spell_table[build.weapon.get("type")];
+
+ let row = document.createElement("div");
+ row.classList.add("spellcost-tooltip");
+ let init_cost = document.createElement("b");
+ init_cost.textContent = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost);
+ init_cost.classList.add("Mana");
+ let arrow = document.createElement("b");
+ arrow.textContent = "\u279C";
+ let next_cost = document.createElement("b");
+ next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1);
+ next_cost.classList.add("Mana");
+ let int_needed = document.createElement("b");
+ if (init_cost.textContent === "1") {
+ int_needed.textContent = ": n/a (+0)";
+ }else { //do math
+ let target = build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) - 1;
+ let needed = int;
+ let noUpdate = false;
+ //forgive me... I couldn't inverse ceil, floor, and max.
+ while (build.getSpellCost(spells.indexOf(spell) + 1, spell.cost) > target) {
+ if(needed > 150) {
+ noUpdate = true;
+ break;
+ }
+ needed++;
+ build.total_skillpoints[2] = needed;
+ }
+ let missing = needed - int;
+ //in rare circumstances, the next spell cost can jump.
+ if (noUpdate) {
+ next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost)-1);
+ }else {
+ next_cost.textContent = (init_cost.textContent === "1" ? 1 : build.getSpellCost(spells.indexOf(spell) + 1, spell.cost));
+ }
+
+
+ build.total_skillpoints[2] = int;//forgive me pt 2
+ int_needed.textContent = ": " + (needed > 150 ? ">150" : needed) + " int (+" + (needed > 150 ? "n/a" : missing) + ")";
+ }
+
+ // row.appendChild(init_cost);
+ row.appendChild(arrow);
+ row.appendChild(next_cost);
+ row.appendChild(int_needed);
+ return row;
+}
+
+function apply_sq2_elemental_format(p_elem, id, suffix) {
+ suffix = (typeof suffix !== 'undefined') ? suffix : "";
+ // THIS IS SO JANK BUT IM TOO LAZY TO FIX IT TODO
+ let parts = idPrefixes[id].split(/ (.*)/);
+ let element_prefix = parts[0];
+ let desc = parts[1];
+ let i_elem = document.createElement('span');
+ i_elem.classList.add(element_prefix);
+ i_elem.textContent = element_prefix;
+ p_elem.appendChild(i_elem);
+
+ let i_elem2 = document.createElement('span');
+ i_elem2.textContent = " " + desc + suffix;
+ p_elem.appendChild(i_elem2);
+}
+
+function displaysq2SetBonuses(parent_id,build) {
+ setHTML(parent_id, "");
+ let parent_div = document.getElementById(parent_id);
+
+ let set_summary_elem = document.createElement('p');
+ set_summary_elem.classList.add('text-center');
+ set_summary_elem.textContent = "Set Bonuses";
+ parent_div.append(set_summary_elem);
+
+ if (build.activeSetCounts.size) {
+ parent_div.parentElement.style.display = "block";
+ } else {
+ parent_div.parentElement.style.display = "none";
+ }
+
+ for (const [setName, count] of build.activeSetCounts) {
+ const active_set = sets[setName];
+ if (active_set["hidden"]) { continue; }
+
+ let set_elem = document.createElement('p');
+ set_elem.id = "set-"+setName;
+ set_summary_elem.append(set_elem);
+
+ const bonus = active_set.bonuses[count-1];
+ let mock_item = new Map();
+ mock_item.set("fixID", true);
+ mock_item.set("displayName", setName+" Set: "+count+"/"+sets[setName].items.length);
+ let mock_minRolls = new Map();
+ let mock_maxRolls = new Map();
+ mock_item.set("minRolls", mock_minRolls);
+ mock_item.set("maxRolls", mock_maxRolls);
+ for (const id in bonus) {
+ if (rolledIDs.includes(id)) {
+ mock_minRolls.set(id, bonus[id]);
+ mock_maxRolls.set(id, bonus[id]);
+ }
+ else {
+ mock_item.set(id, bonus[id]);
+ }
+ }
+ mock_item.set("powders", []);
+ displaysq2ExpandedItem(mock_item, set_elem.id);
+ console.log(mock_item);
+ }
+}
\ No newline at end of file
diff --git a/sq2display_constants.js b/sq2display_constants.js
new file mode 100644
index 0000000..ac105c5
--- /dev/null
+++ b/sq2display_constants.js
@@ -0,0 +1,108 @@
+/*
+ * 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"
+];
+
+
diff --git a/sq2icons.js b/sq2icons.js
new file mode 100644
index 0000000..6339415
--- /dev/null
+++ b/sq2icons.js
@@ -0,0 +1,6 @@
+//which icons to use
+let window_storage = window.localStorage;
+console.log(window_storage);
+icon_state_stored = window_storage.getItem("newicons");
+newIcons = true;
+if (icon_state_stored === "false") {toggleIcons()}
diff --git a/sq2items.js b/sq2items.js
new file mode 100644
index 0000000..655b838
--- /dev/null
+++ b/sq2items.js
@@ -0,0 +1,195 @@
+
+
+const translate_mappings = {
+ //"Name": "name",
+ //"Display Name": "displayName",
+ //"tier"Tier": ",
+ //"Set": "set",
+ "Powder Slots": "slots",
+ //"Type": "type",
+ //"armorType", (deleted)
+ //"color", (deleted)
+ //"lore", (deleted)
+ //"material", (deleted)
+ "Drop type": "drop",
+ "Quest requirement": "quest",
+ "Restriction": "restrict",
+ //"Base Neutral Damage": "nDam",
+ //"Base Fire Damage": "fDam",
+ //"Base Water Damage": "wDam",
+ //"Base Air Damage": "aDam",
+ //"Base Thunder Damage": "tDam",
+ //"Base Earth Damage": "eDam",
+ //"Base Attack Speed": "atkSpd",
+ "Health": "hp",
+ "Raw Fire Defense": "fDef",
+ "Raw Water Defense": "wDef",
+ "Raw Air Defense": "aDef",
+ "Raw Thunder Defense": "tDef",
+ "Raw Earth Defense": "eDef",
+ "Combat Level": "lvl",
+ //"Class Requirement": "classReq",
+ "Req Strength": "strReq",
+ "Req Dexterity": "dexReq",
+ "Req Intelligence": "intReq",
+ "Req Agility": "agiReq",
+ "Req Defense": "defReq",
+ "% Health Regen": "hprPct",
+ "Mana Regen": "mr",
+ "% Spell Damage": "sdPct",
+ "% Melee Damage": "mdPct",
+ "Life Steal": "ls",
+ "Mana Steal": "ms",
+ "XP Bonus": "xpb",
+ "Loot Bonus": "lb",
+ "Reflection": "ref",
+ "Strength": "str",
+ "Dexterity": "dex",
+ "Intelligence": "int",
+ "Agility": "agi",
+ "Defense": "def",
+ "Thorns": "thorns",
+ "Exploding": "expd",
+ "Walk Speed": "spd",
+ "Attack Speed Bonus": "atkTier",
+ "Poison": "poison",
+ "Health Bonus": "hpBonus",
+ "Soul Point Regen": "spRegen",
+ "Stealing": "eSteal",
+ "Raw Health Regen": "hprRaw",
+ "Raw Spell": "sdRaw",
+ "Raw Melee": "mdRaw",
+ "% Fire Damage": "fDamPct",
+ "% Water Damage": "wDamPct",
+ "% Air Damage": "aDamPct",
+ "% Thunder Damage": "tDamPct",
+ "% Earth Damage": "eDamPct",
+ "% Fire Defense": "fDefPct",
+ "% Water Defense": "wDefPct",
+ "% Air Defense": "aDefPct",
+ "% Thunder Defense": "tDefPct",
+ "% Earth Defense": "eDefPct",
+ "Fixed IDs": "fixID",
+ "Custom Skin": "skin",
+ //"Item Category": "category",
+
+ "1st Spell Cost %": "spPct1",
+ "1st Spell Cost Raw": "spRaw1",
+ "2nd Spell Cost %": "spPct2",
+ "2nd Spell Cost Raw": "spRaw2",
+ "3rd Spell Cost %": "spPct3",
+ "3rd Spell Cost Raw": "spRaw3",
+ "4th Spell Cost %": "spPct4",
+ "4th Spell Cost Raw": "spRaw4",
+
+ "Rainbow Spell Damage": "rainbowRaw",
+ "Sprint": "sprint",
+ "Sprint Regen": "sprintReg",
+ "Jump Height": "jh",
+ "Loot Quality": "lq",
+
+ "Gather XP Bonus": "gXp",
+ "Gather Speed Bonus": "gSpd",
+};
+
+const special_mappings = {
+ "Sum (skill points)": new SumQuery(["str", "dex", "int", "def", "agi"]),
+ "Sum (Mana Sustain)": new SumQuery(["mr", "ms"]),
+ "Sum (Life Sustain)": new SumQuery(["hpr", "ls"]),
+ "Sum (Health + Health Bonus)": new SumQuery(["hp", "hpBonus"]),
+ "No Strength Req": new NegateQuery("strReq"),
+ "No Dexterity Req": new NegateQuery("dexReq"),
+ "No Intelligence Req": new NegateQuery("intReq"),
+ "No Agility Req": new NegateQuery("agiReq"),
+ "No Defense Req": new NegateQuery("defReq"),
+};
+
+let itemFilters = []
+for (let x in translate_mappings) {
+ itemFilters.push(x);
+}
+for (let x in special_mappings) {
+ itemFilters.push(x);
+}
+
+let itemCategories = [ "armor", "accessory", "weapon" ];
+
+function applyQuery(items, query) {
+ return items.filter(query.filter, query).sort(query.compare);
+}
+
+function displayItems(items_copy) {
+ let items_parent = document.getElementById("search-results");
+ for (let i in items_copy) {
+ if (i > 200) {break;}
+ let item = items_copy[i];
+ let box = document.createElement("div");
+ box.classList.add("col-auto", "dark-7", "dark-shadow");
+ box.style.position = "absolute";
+ box.id = "item"+i;
+ box.addEventListener("dblclick", function() {set_item(item);});
+ items_parent.appendChild(box);
+ displaysq2ExpandedItem(item, box.id);
+ }
+}
+
+let items_expanded;
+
+function doItemSearch() {
+ // window.scrollTo(0, 0);
+ let queries = [];
+ queries.push(new NameQuery(document.getElementById("name-choice").value.trim()));
+
+ let categoryOrType = document.getElementById("category-choice").value;
+ if (itemTypes.includes(categoryOrType)) {
+ queries.push(new IdMatchQuery("type", categoryOrType));
+ }
+ else if (itemCategories.includes(categoryOrType)) {
+ queries.push(new IdMatchQuery("category", categoryOrType));
+ }
+
+ let rarity = document.getElementById("rarity-choice").value;
+ if (rarity) {
+ if (rarity === "ANY") {
+
+ }
+ else {
+ queries.push(new IdMatchQuery("tier", rarity));
+ }
+ }
+
+ let level_dat = document.getElementById("search-level-choice").value.split("-");
+ queries.push(new LevelRangeQuery(parseInt(level_dat[0]), parseInt(level_dat[1])));
+
+ for (let i = 1; i <= 4; ++i) {
+ let raw_dat = document.getElementById("filter"+i+"-choice").value;
+ let filter_dat = translate_mappings[raw_dat];
+ if (filter_dat !== undefined) {
+ queries.push(new IdQuery(filter_dat));
+ continue;
+ }
+ filter_dat = special_mappings[raw_dat];
+ if (filter_dat !== undefined) {
+ queries.push(filter_dat);
+ continue;
+ }
+ }
+
+ let items_copy = items_expanded.slice();
+ document.getElementById("search-results").textContent = "";
+ for (const query of queries) {
+ console.log(items_copy.length);
+ console.log(query);
+ console.log(query.filter);
+ items_copy = applyQuery(items_copy, query);
+ console.log(items_copy.length);
+ }
+ // document.getElementById("summary").textContent = items_copy.length + " results."
+ displayItems(items_copy);
+}
+
+function init_items() {
+ items_expanded = items.filter( (i) => !("remapID" in i) ).map( (i) => expandItem(i, []) );
+}
+
+load_init(init_items);