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 + + + + + + + + + + + + +
+
+
+
Overall Build Stats
+
+
+
+
+
+
+
+
sq2-Search
+
+
+
+
+ + + + + + + + + + +
+
+
+ + + + + + +
+
+
+
+
+ +
+
+
+
Active boosts
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Class boosts
+ + + + + + + + + +
Quake
+ + + + + + + + + +
Chain Lightning
+ + + + + + + + + +
Curse
+ + + + + + + + + +
Courage
+ + + + + + + + + +
Wind Prison
+ + + + + + + + + +
+
+
+
+

Powder Specials

+
+
+
+
+ + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ base?? +
+
+ ?? +
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ Level: +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+ Assign: 0 +
+
+ Original: 0 +
+
+
+
+
+
+
+ + +
+ Assign: 0 +
+
+ Original: 0 +
+
+
+
+
+
+
+ + +
+ Assign: 0 +
+
+ Original: 0 +
+
+
+
+
+
+
+ + +
+ Assign: 0 +
+
+ Original: 0 +
+
+
+
+
+
+
+ + +
+ Assign: 0 +
+
+ Original: 0 +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Active boosts +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+ Earth +
+
+ Thunder +
+
+ Water +
+
+ Fire +
+
+ Air +
+
+
+ + +
+
+
+ Curse (Active) +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ Concentration (Passive) +
+
+ placeholder +
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ Offense +
+
+ Defense +
+
+ Overall +
+
+
+ + +
+
+
+
+
+
+
melee
+ +
+
+
poison
+
+
+
spell1
+ +
+
+
spell2
+ +
+
+
spell3
+ +
+
+
spell4
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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);