From e51f1ef92bce15da3a511371fce5a1088b26c38f Mon Sep 17 00:00:00 2001 From: reschan Date: Sun, 17 Oct 2021 22:53:48 +0700 Subject: [PATCH] sq2 initial commit --- normalize.css | 349 ++++++ sq2.css | 429 +++++++ sq2.html | 1159 ++++++++++++++++++ sq2.js | 302 +++++ sq2build.js | 558 +++++++++ sq2builder.js | 1056 ++++++++++++++++ sq2display.js | 2521 +++++++++++++++++++++++++++++++++++++++ sq2display_constants.js | 419 +++++++ sq2icons.js | 38 + sq2items.js | 200 ++++ 10 files changed, 7031 insertions(+) create mode 100644 normalize.css create mode 100644 sq2.css create mode 100644 sq2.html create mode 100644 sq2.js create mode 100644 sq2build.js create mode 100644 sq2builder.js create mode 100644 sq2display.js create mode 100644 sq2display_constants.js create mode 100644 sq2icons.js create mode 100644 sq2items.js 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..cc1de78 --- /dev/null +++ b/sq2.css @@ -0,0 +1,429 @@ +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: 5px solid rgb(45, 45, 45); +} + +.top-half-border { + border-top: 5px solid rgb(45, 45, 45); + border-left: 5px solid rgb(45, 45, 45); + border-right: 5px solid rgb(45, 45, 45); +} + +.bot-half-border { + border-bottom: 5px solid rgb(45, 45, 45); + border-left: 5px solid rgb(45, 45, 45); + border-right: 5px solid rgb(45, 45, 45); +} + +.se-border { + border-left: 5px solid rgb(45, 45, 45); + border-right: 5px solid rgb(45, 45, 45); +} + +td { + padding: 0; + margin: 0; + white-space: nowrap; +} + +p { + padding: 0; + margin: 0; + padding-top: 1px; + padding-bottom: 1px; +} + +input { + background-color: rgb(40, 40, 40); + border-color:rgb(45, 45, 45); + color: rgb(240, 240, 240); + min-width: 0; + box-sizing: none; +} + +input.item-name { + text-align: center; + font-weight: bold; + width: 13em; +} + +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 { + 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: 7em; + user-select: none; +} + +.potency { + width: 36em; + user-select: none; +} + +.skp-text { + width: 7.75em; +} + +.skp-input { + width: 7em; + height: 1.2em; + 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: 13em; +} + +.skp-tooltip { + font-size: 11px; +} + +.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: 1px solid rgb(30, 30, 30); + border-bottom: 1px solid rgb(45, 45, 45) + /* + border-top: 1px solid rgb(30, 30, 30); + 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: 20.5em; +} + +.nDam { + color: #FFAA00; +} + +.eDam, .Earth { + color: #00AA00; +} + +.Earth:before { content: "\2724" ' '; } + +.tDam, .Thunder { + color: #FFFF55; +} + +.Thunder:before { content: "\2726" ' '; } + +.wDam, .Water { + color: #55FFFF +} + +.Water:before { content: "\2749" ' '; } + +.fDam, .Fire { + color: #FF5555; +} + +.Fire:before { content: "\2739" ' '; } + +.aDam, .Air { + color: #FFFFFF +} + +.Air: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: 1em; + margin-bottom: 1em; +} + +.item-tooltip { + max-width: 20em; + background-color: rgb(30, 30, 30); + color: white; + font-size: 12px; +} + +.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); +} + +.search-result-container { + display: flex; + flex-wrap: wrap; + justify-content: center; + overflow-y: auto; + overflow-x: hidden; + flex-basis: 40em; + max-width: 20em; + flex-grow: 0; + flex-shrink: 0; +} + +.button-boost { + background-color: rgb(45, 45, 45); + width: 10rem; + border: 5px solid rgb(50, 50, 50) +} + +button.toggleOn{ + background-color:#0a0; + border: 3 px solid rgb(0, 70, 0); +} + +.damageSubtitle { + text-align: center; +} \ No newline at end of file diff --git a/sq2.html b/sq2.html new file mode 100644 index 0000000..28220cb --- /dev/null +++ b/sq2.html @@ -0,0 +1,1159 @@ + + + + WynnBuilder^2 + + + + + + + + + + + + +
+
+
+
Overall Build Stats
+
+
+
+
+
+
+
+
sq2-Search
+
+
+
+
+ + + + + + + + + + +
+
+
+ + + + + + +
+
+
+
+
+ +
+
+
+
Active boosts
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Class boosts
+ + + + + + + + + +
Quake
+ + + + + + + + + +
Chain Lightning
+ + + + + + + + + +
Curse
+ + + + + + + + + +
Courage
+ + + + + + + + + +
Wind Prison
+ + + + + + + + + +
+
+
+
+

Powder Specials

+
+
+
+
+ + + + + + + + + +
+
+
    +
  • +
  • +
  • +
  • place
  • +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sq2.js b/sq2.js new file mode 100644 index 0000000..aeb421f --- /dev/null +++ b/sq2.js @@ -0,0 +1,302 @@ +let equipment_keys = ['weapon', 'helmet', 'chestplate', 'leggings', 'boots', 'ring1', 'ring2', 'bracelet', 'necklace']; + +$(document).ready(function(){ + // inits + $("#column2").height($("#column1").height()); + $("#column3").height($("#column1").height()); + + $("#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', + }); + + // update builds + jQuery(document).on("keypress", '.skp-input', function(event){ + if (event.keyCode == 13) { + updateStats(); + } + }); + + jQuery(document).on("keypress", '.search-field', function(event){ + if (event.keyCode == 13) { + doItemSearch(); + } + }); + + // set search style + $("#weapon-choice").on('input', function(){ + set_input_style('weapon'); + check_item($("#weapon-choice").val()); + update_powder_count('weapon'); + }); + + $("#helmet-choice").on('input', function(){ + set_input_style('helmet'); + check_item($("#helmet-choice").val()); + update_powder_count('helmet', '|example: t6t6'); + }); + + $("#chestplate-choice").on('input', function(){ + set_input_style('chestplate'); + check_item($("#chestplate-choice").val()); + update_powder_count('chestplate'); + }); + + $("#leggings-choice").on('input', function(){ + set_input_style('leggings'); + check_item($("#leggings-choice").val()); + update_powder_count('leggings'); + }); + + $("#boots-choice").on('input', function(){ + set_input_style('boots'); + check_item($("#boots-choice").val()); + update_powder_count('boots'); + }); + + $("#ring1-choice").on('input', function(){ + set_input_style('ring1'); + check_item($("#ring1-choice").val()); + }); + + $("#ring2-choice").on('input', function(){ + set_input_style('ring2'); + check_item($("#ring2-choice").val()); + }); + + $("#bracelet-choice").on('input', function(){ + set_input_style('bracelet'); + check_item($("#bracelet-choice").val()); + }); + + $("#necklace-choice").on('input', function(){ + set_input_style('necklace'); + check_item($("#necklace-choice").val()); + }); + + // 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 + init_tooltip_loc() + + $("#weapon-img-loc").hover(function(event){ + $("#weapon-tooltip").show(); + }, function(){ + $("#weapon-tooltip").hide(); + }); + + $("#helmet-img-loc").hover(function(event){ + $("#helmet-tooltip").show(); + }, function(){ + $("#helmet-tooltip").hide(); + }); + + $("#chestplate-img-loc").hover(function(event){ + $("#chestplate-tooltip").show(); + }, function(){ + $("#chestplate-tooltip").hide(); + }); + + $("#leggings-img-loc").hover(function(event){ + $("#leggings-tooltip").show(); + }, function(){ + $("#leggings-tooltip").hide(); + }); + + $("#boots-img-loc").hover(function(event){ + $("#boots-tooltip").show(); + }, function(){ + $("#boots-tooltip").hide(); + }); + + $("#ring1-img-loc").hover(function(event){ + $("#ring1-tooltip").show(); + }, function(){ + $("#ring1-tooltip").hide(); + }); + + $("#ring2-img-loc").hover(function(event){ + $("#ring2-tooltip").show(); + }, function(){ + $("#ring2-tooltip").hide(); + }); + + $("#bracelet-img-loc").hover(function(event){ + $("#bracelet-tooltip").show(); + }, function(){ + $("#bracelet-tooltip").hide(); + }); + + $("#necklace-img-loc").hover(function(event){ + $("#necklace-tooltip").show(); + }, 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 check_item(name) { + if (itemMap.has(name)) { + calculateBuild() + } +} + +function init_tooltip_loc(){ + for (const i in equipment_keys) { + let ImgLoc = document.getElementById(equipment_keys[i]+'-img-loc').getBoundingClientRect(); + $("#"+equipment_keys[i]+"-tooltip").css('top', ImgLoc.bottom); + $("#"+equipment_keys[i]+"-tooltip").css('left', ImgLoc.left); + } +} + + +/* + document.getElementById(armorType+"-choice").addEventListener("change", (event) => { + let item_name = event.target.value; + let nSlots = undefined; + if (itemMap.has(item_name)) { + let item = itemMap.get(item_name); + nSlots = item["slots"]; + //console.log(item); + } + else { + let crafted_custom_item = getCraftFromHash(item_name) !== undefined ? getCraftFromHash(item_name) : (getCustomFromHash(item_name) !== undefined ? getCustomFromHash(item_name) : undefined); + if (crafted_custom_item !== undefined) { + nSlots = crafted_custom_item.statMap.get("slots"); + } + } + if (nSlots !== undefined) { + document.getElementById(armorType+"-slots").textContent = nSlots + " slots"; + } + else { + document.getElementById(armorType+"-slots").textContent = "X slots"; + } + });*/ + +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'); +} \ No newline at end of file diff --git a/sq2build.js b/sq2build.js new file mode 100644 index 0000000..ab44ad9 --- /dev/null +++ b/sq2build.js @@ -0,0 +1,558 @@ + + +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]); + /* + if (equipment[8] !== "No Weapon") { + document.getElementsByClassName("powder-specials")[0].style.display = "grid"; + } else { + document.getElementsByClassName("powder-specials")[0].style.display = "none"; + }*/ + }else{ + try { + let weapon = getCustomFromHash(equipment[8]) ? getCustomFromHash(equipment[8]) : (getCraftFromHash(equipment[8]) ? getCraftFromHash(equipment[8]) : undefined); + if (weapon.statMap.get("category") !== "weapon") { + throw new Error("Not a weapon"); + } + this.weapon = weapon.statMap; + if (this.weapon.get("custom")) { + this.customItems.push(weapon); + } else if (this.weapon.get("crafted")) { //customs can also be crafted, but custom takes priority. + this.craftedItems.push(weapon); + } + this.powders[4] = this.powders[4].slice(0,this.weapon.get("slots")); + this.weapon.set("powders",this.powders[4].slice()); + document.getElementsByClassName("powder-specials")[0].style.display = "grid"; + } catch (Error) { + const weapon = itemMap.get("No Weapon"); + this.powders[4] = this.powders[4].slice(0,weapon.slots); + this.weapon = expandItem(weapon, this.powders[4]); + document.getElementsByClassName("powder-specials")[0].style.display = "none"; + errors.push(new ItemNotFound(equipment[8], "weapon", true)); + } + } + + //console.log(this.craftedItems) + + if (level < 1) { //Should these be constants? + this.level = 1; + } else if (level > 106) { + this.level = 106; + } else if (level <= 106 && level >= 1) { + this.level = level; + } else if (typeof level === "string") { + this.level = level; + errors.push(new IncorrectInput(level, "a number", "level-choice")); + } else { + errors.push("Level is not a string or number."); + } + document.getElementById("level-choice").value = this.level; + + this.availableSkillpoints = levelToSkillPoints(this.level); + this.equipment = [ this.helmet, this.chestplate, this.leggings, this.boots, this.ring1, this.ring2, this.bracelet, this.necklace ]; + this.items = this.equipment.concat([this.weapon]); + // return [equip_order, best_skillpoints, final_skillpoints, best_total]; + let result = calculate_skillpoints(this.equipment, this.weapon); + console.log(result); + this.equip_order = result[0]; + // 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..a32fc12 --- /dev/null +++ b/sq2builder.js @@ -0,0 +1,1056 @@ +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 that takes an item list and populates its corresponding dropdown. + * Used for armors and bracelet/necklace. + */ +function populateItemList(type) { + let item_list = document.getElementById(type+"-items"); + for (const item of itemLists.get(type)) { + let item_obj = itemMap.get(item); + if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { + continue; + } + let el = document.createElement("option"); + el.value = item; + item_list.appendChild(el); + } +} + +/* + * Populate dropdowns, add listeners, etc. + */ +function init() { + console.log("builder.js init"); + + for (const armorType of armorTypes) { + populateItemList(armorType); + // Add change listener to update armor slots. + /* + document.getElementById(armorType+"-choice").addEventListener("change", (event) => { + let item_name = event.target.value; + let nSlots = undefined; + if (itemMap.has(item_name)) { + let item = itemMap.get(item_name); + nSlots = item["slots"]; + //console.log(item); + } + else { + let crafted_custom_item = getCraftFromHash(item_name) !== undefined ? getCraftFromHash(item_name) : (getCustomFromHash(item_name) !== undefined ? getCustomFromHash(item_name) : undefined); + if (crafted_custom_item !== undefined) { + nSlots = crafted_custom_item.statMap.get("slots"); + } + } + if (nSlots !== undefined) { + document.getElementById(armorType+"-slots").textContent = nSlots + " slots"; + } + else { + document.getElementById(armorType+"-slots").textContent = "X slots"; + } + });*/ + } + + let ring1_list = document.getElementById("ring1-items"); + let ring2_list = document.getElementById("ring2-items"); + for (const ring of itemLists.get("ring")) { + let item_obj = itemMap.get(ring); + if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { + continue; + } + let el1 = document.createElement("option"); + let el2 = document.createElement("option"); + el1.value = ring; + el2.value = ring; + ring1_list.appendChild(el1); + ring2_list.appendChild(el2); + } + + populateItemList("bracelet"); + populateItemList("necklace"); + + let weapon_list = document.getElementById("weapon-items"); + for (const weaponType of weaponTypes) { + for (const weapon of itemLists.get(weaponType)) { + let item_obj = itemMap.get(weapon); + if (item_obj["restrict"] && item_obj["restrict"] === "DEPRECATED") { + continue; + } + let el = document.createElement("option"); + el.value = weapon; + weapon_list.appendChild(el); + } + } + + // Add change listener to update weapon slots. + /* + document.getElementById("weapon-choice").addEventListener("change", (event) => { + let item_name = event.target.value; + let item = itemMap.has(item_name) ? itemMap.get(item_name) : (getCraftFromHash(item_name) ? getCraftFromHash(item_name) : (getCustomFromHash(item_name) ? getCustomFromHash(item_name) : undefined)); + if (item !== undefined && event.target.value !== "") { + document.getElementById("weapon-slots").textContent = (item["slots"] ? item["slots"] : (item.statMap !== undefined ? ( item.statMap.has("slots") ? item.statMap.get("slots") : 0): 0) )+ " slots"; + } else { + document.getElementById("weapon-slots").textContent = "X slots"; + } + });*/ + + decodeBuild(url_tag); + init_equipUI(); +} + +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){ + 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()); + displayEquipOrder(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 Value: " + skillpoints[i]); + } + + for (let id of editable_item_fields) { + setValue(id, player_build.statMap.get(id)); + setText(id+"-base", "Original Value: " + player_build.statMap.get(id)); + } + + 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))); + } + 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(); + } + displayPowderSpecials(document.getElementById("powder-special-stats"), powderSpecials, player_build); +} +/* 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", "Manually Assigned: " + 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("center"); + 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("p"); + //skpWarning.classList.add("itemp"); + skpWarning.classList.add("warning"); + skpWarning.classList.add("small-text"); + skpWarning.textContent = "WARNING: Too many skillpoints need to be assigned!"; + let skpCount = document.createElement("p"); + skpCount.classList.add("small-text"); + 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) { + displayExpandedItem(player_build.items[i], buildFields[i], true); + } + + displayWeaponBase(player_build); + displayArmorStats(player_build); + displayMinimalBuildStats("all-stats", player_build, build_overall_display_commands); + displayMinimalBuildStats("minimal-stats", player_build, build_basic_display_commands); + displayMinimalBuildStats("minimal-offensive-stats",player_build, build_offensive_display_commands); + displaySetBonuses("set-info",player_build); + displayNextCosts("int-info",player_build); + + let meleeStats = player_build.getMeleeStats(); + displayMeleeDamage(document.getElementById("build-melee-stats"), document.getElementById("build-melee-statsAvg"), meleeStats); + + displayDefenseStats(document.getElementById("minimal-defensive-stats"),player_build); + + displayPoisonDamage(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"); + displaySpellDamage(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 saveBuild() { + if (player_build) { + let savedBuilds = window.localStorage.getItem("builds") === null ? {} : JSON.parse(window.localStorage.getItem("builds")); + let saveName = document.getElementById("build-name").value; + let 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"; + } 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]; + 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..ce5ccc2 --- /dev/null +++ b/sq2display.js @@ -0,0 +1,2521 @@ +/** + * Apply armor powdering. + * Also for jeweling for crafted items. + */ +function applyArmorPowders(expandedItem, powders) { + applyArmorPowdersOnce(expandedItem, powders); + if (expandedItem.get("crafted")) { + applyArmorPowdersOnce(expandedItem, powders); + } +} +function applyArmorPowdersOnce(expandedItem, powders) { + for(const id of powders){ + let powder = powderStats[id]; + let name = powderNames.get(id); + expandedItem.set(name.charAt(0) + "Def", (expandedItem.get(name.charAt(0)+"Def") || 0) + powder["defPlus"]); + expandedItem.set(skp_elements[(skp_elements.indexOf(name.charAt(0)) + 4 )% 5] + "Def", (expandedItem.get(skp_elements[(skp_elements.indexOf(name.charAt(0)) + 4 )% 5]+"Def") || 0) - powder["defMinus"]); + } +} + +function expandItem(item, powders) { + let minRolls = new Map(); + let maxRolls = new Map(); + let expandedItem = new Map(); + if(item.fixID){ //The item has fixed IDs. + expandedItem.set("fixID",true); + for (const id of rolledIDs){ //all rolled IDs are numerical + let val = (item[id] || 0); + //if(item[id]) { + minRolls.set(id,val); + maxRolls.set(id,val); + //} + } + }else{ //The item does not have fixed IDs. + for (const id of rolledIDs){ + let val = (item[id] || 0); + if(val > 0){ // positive rolled IDs + if (reversedIDs.includes(id)) { + maxRolls.set(id,idRound(val*0.3)); + minRolls.set(id,idRound(val*1.3)); + } else { + maxRolls.set(id,idRound(val*1.3)); + minRolls.set(id,idRound(val*0.3)); + } + }else if(val < 0){ //negative rolled IDs + if (reversedIDs.includes(id)) { + maxRolls.set(id,idRound(val*1.3)); + minRolls.set(id,idRound(val*0.7)); + } + else { + minRolls.set(id,idRound(val*1.3)); + maxRolls.set(id,idRound(val*0.7)); + } + }else{//Id = 0 + minRolls.set(id,0); + maxRolls.set(id,0); + } + } + } + for (const id of nonRolledIDs){ + expandedItem.set(id,item[id]); + } + expandedItem.set("minRolls",minRolls); + expandedItem.set("maxRolls",maxRolls); + expandedItem.set("powders", powders); + if(item.category === "armor") { + applyArmorPowders(expandedItem, powders); + } + return expandedItem; +} + +/* Takes in an ingredient object and returns an equivalent Map(). +*/ +function expandIngredient(ing) { + let expandedIng = new Map(); + let mapIds = ['consumableIDs', 'itemIDs', 'posMods']; + for (const id of mapIds) { + let idMap = new Map(); + for (const key of Object.keys(ing[id])) { + idMap.set(key, ing[id][key]); + } + expandedIng.set(id, idMap); + } + let normIds = ['lvl','name', 'displayName','tier','skills','id']; + for (const id of normIds) { + expandedIng.set(id, ing[id]); + } + if (ing['isPowder']) { + expandedIng.set("isPowder",ing['isPowder']); + expandedIng.set("pid",ing['pid']); + } + //now the actually hard one + let idMap = new Map(); + idMap.set("minRolls", new Map()); + idMap.set("maxRolls", new Map()); + for (const field of ingFields) { + let val = (ing['ids'][field] || 0); + idMap.get("minRolls").set(field, val['minimum']); + idMap.get("maxRolls").set(field, val['maximum']); + } + expandedIng.set("ids",idMap); + return expandedIng; +} + +/* Takes in a recipe object and returns an equivalent Map(). +*/ +function expandRecipe(recipe) { + let expandedRecipe = new Map(); + let normIDs = ["name", "skill", "type","id"]; + for (const id of normIDs) { + expandedRecipe.set(id,recipe[id]); + } + let rangeIDs = ["durability","lvl", "healthOrDamage", "duration", "basicDuration"]; + for (const id of rangeIDs) { + if(recipe[id]){ + expandedRecipe.set(id, [recipe[id]['minimum'], recipe[id]['maximum']]); + } else { + expandedRecipe.set(id, [0,0]); + } + } + expandedRecipe.set("materials", [ new Map([ ["item", recipe['materials'][0]['item']], ["amount", recipe['materials'][0]['amount']] ]) , new Map([ ["item", recipe['materials'][1]['item']], ["amount",recipe['materials'][1]['amount'] ] ]) ]); + return expandedRecipe; +} + +/*An independent helper function that rounds a rolled ID to the nearest integer OR brings the roll away from 0. +* @param id +*/ +function idRound(id){ + rounded = Math.round(id); + if(rounded == 0){ + return 1; //this is a hack, will need changing along w/ rest of ID system if anything changes + }else{ + return rounded; + } +} + +function apply_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('b'); + i_elem.classList.add(element_prefix); + i_elem.textContent = element_prefix; + p_elem.appendChild(i_elem); + + let i_elem2 = document.createElement('b'); + i_elem2.textContent = " " + desc + suffix; + p_elem.appendChild(i_elem2); +} + +function displaySetBonuses(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('itemcenter'); + 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", []); + displayExpandedItem(mock_item, set_elem.id); + console.log(mock_item); + } +} + +function displayMinimalBuildStats(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 === "#cdiv") { + active_elem = document.createElement('div'); + active_elem.classList.add('itemcenter'); + } + else if (command === "#ldiv") { + active_elem = document.createElement('div'); + active_elem.classList.add('itemleft'); + } + else if (command === "#table") { + active_elem = document.createElement('table'); + active_elem.classList.add('full-border') + } + else if (command === "#defense-stats") { + displayDefenseStats(active_elem, build, true); + } + else if(command === "#spacer") { + let row = document.createElement('tr'); + let filler = document.createElement('td'); + filler.colSpan = 2 + filler.classList.add('center') + filler.classList.add('shaded-table') + filler.classList.add('spacer-table') + row.appendChild(filler); + active_elem.appendChild(row); + } + parent_div.appendChild(active_elem); + } + else 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); + } + displayFixedID(active_elem, id, id_val, elemental_format, style); + if (id === "poison" && id_val > 0) { + let row = document.createElement('tr'); + let value_elem = document.createElement('td'); + value_elem.classList.add('right'); + value_elem.setAttribute("colspan", "2"); + 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); + + active_elem.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) { + displayFixedID(active_elem, id, diff, false, style); + } + } + } + } +} + +function displayExpandedItem(item, parent_id, mini=false){ + // 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 = item_display_commands; + + // Clear the parent div. + setHTML(parent_id, ""); + let parent_div = document.getElementById(parent_id); + + let active_elem; + 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) === "#") { + if (command === "#cdiv") { + active_elem = document.createElement('div'); + active_elem.classList.add('center'); + } + else if (command === "#ldiv") { + active_elem = document.createElement('div'); + active_elem.classList.add('left'); + } + else if (command === "#table") { + active_elem = document.createElement('table'); + active_elem.style.width = "19em"; + } + + active_elem.classList.add('item-margin'); + parent_div.appendChild(active_elem); + } + else if (command.charAt(0) === "!") { + // TODO: This is sooo incredibly janky..... + if (command === "!elemental") { + elemental_format = !elemental_format; + } + } + 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("p"); + // 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.classList.add("powderLeft"); powderPrefix.classList.add("left"); + 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.classList.add("powderRight"); powderSuffix.classList.add("left"); + powderSuffix.textContent = "]"; + p_elem.appendChild(powderSuffix); + active_elem.appendChild(p_elem); + } else if (id === "set") { + if (item.get("hideSet")) { continue; } + + let p_elem = document.createElement("p"); + p_elem.classList.add("itemp"); + p_elem.textContent = "Set: " + item.get(id).toString(); + active_elem.appendChild(p_elem); + } else if (id === "majorIds") { + console.log(item.get(id)); + for (let majorID of item.get(id)) { + let p_elem = document.createElement("p"); + p_elem.classList.add("itemp"); + + 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); + } + active_elem.appendChild(p_elem); + } + } else if (id === "lvl" && item.get("tier") === "Crafted") { + let p_elem = document.createElement("p"); + p_elem.classList.add("itemp"); + p_elem.textContent = "Combat Level Min: " + item.get("lvlLow") + "-" + item.get(id); + active_elem.appendChild(p_elem); + } else if (id === "displayName") { + let p_elem = document.createElement("a"); + p_elem.classList.add('itemp'); + p_elem.classList.add("smalltitle"); + 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"); + } + + p_elem.target = "_blank"; + active_elem.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"); + if (mini) { + img.style = " z=index: 1;max-width: 32px; max-height: 32px; position: relative; top: 50%; transform: translateY(-50%);"; + } else { + img.style = " z=index: 1;max-width: 48px; max-height: 48px; position: relative; top: 50%; transform: translateY(-50%);"; + } + let bckgrd = document.createElement("p"); + if (mini) { + bckgrd.style = "width: 48px; height: 48px; border-radius: 50%;background-image: radial-gradient(closest-side, " + colorMap.get(item.get("tier")) + " 20%," + "#121516 80%); margin-left: auto; margin-right: auto;" + } else { + bckgrd.style = "width: 64px; height: 64px; border-radius: 50%;background-image: radial-gradient(closest-side, " + colorMap.get(item.get("tier")) + " 20%," + "#121516 80%); margin-left: auto; margin-right: auto;" + } + bckgrd.classList.add("center"); + bckgrd.classList.add("itemp"); + active_elem.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" && active_elem.nodeName === "DIV") ) { //skp warp + p_elem = displayFixedID(active_elem, id, item.get(id), elemental_format); + } else if (item.get("tier") === "Crafted" && item.get("category") === "armor" && id === "hp") { + p_elem = displayFixedID(active_elem, id, item.get(id+"Low")+"-"+item.get(id), elemental_format); + } + if (id === "lore") { + p_elem.style = "font-style: italic"; + p_elem.classList.add("lore"); + } else if (skp_order.includes(id)) { //id = str, dex, int, def, or agi + if ( item.get("tier") !== "Crafted" && active_elem.nodeName === "DIV") { + p_elem.textContent = ""; + p_elem.classList.add("itemp"); + row = document.createElement("p"); + row.classList.add("left"); + + 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); + p_elem.appendChild(row); + } else if ( item.get("tier") === "Crafted" && active_elem.nodeName === "TABLE") { + let row = displayRolledID(item, id, elemental_format); + active_elem.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) { + displayFixedID(active_elem, id, item.get("minRolls").get(id), elemental_format, style); + } + else { + let row = displayRolledID(item, id, elemental_format); + active_elem.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("p"); + powder_special.classList.add("left"); + 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("p"); + let specialEffects = document.createElement("p"); + addClasses(specialTitle, ["left", "itemp", damageClasses[skp_elements.indexOf(element) + 1]]); + addClasses(specialEffects, ["left", "itemp", "nocolor"]); + 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("itemp"); + 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("p"); + dura_elem.classList.add("itemp"); + 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"); + charges.classList.add("spaceleft"); + active_elem.appendChild(charges); + } + + if (typeof(dura) === "string") { + dura_elem.textContent += dura + suffix; + } else { + dura_elem.textContent += dura[0]+"-"+dura[1] + suffix; + } + active_elem.append(dura_elem); + + } + //Show item tier + if (item.get("tier") && item.get("tier") !== " ") { + let item_desc_elem = document.createElement("p"); + item_desc_elem.classList.add('itemp'); + item_desc_elem.classList.add(item.get("tier")); + item_desc_elem.textContent = item.get("tier")+" "+item.get("type"); + active_elem.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"); + active_elem.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); + } +} + +/* Displays stats about a recipe that are NOT displayed in the craft stats. +* Includes: mat name and amounts +* ingred names in an "array" with ingred effectiveness +*/ +function displayRecipeStats(craft, parent_id) { + let elem = document.getElementById(parent_id); + elem.textContent = ""; + recipe = craft["recipe"]; + mat_tiers = craft["mat_tiers"]; + ingreds = []; + for (const n of craft["ingreds"]) { + ingreds.push(n.get("name")); + } + let effectiveness = craft["statMap"].get("ingredEffectiveness"); + + let ldiv = document.createElement("div"); + ldiv.classList.add("itemleft"); + let title = document.createElement("p"); + title.classList.add("smalltitle"); + title.textContent = "Recipe Stats"; + ldiv.appendChild(title); + let mats = document.createElement("p"); + mats.classList.add("itemp"); + mats.textContent = "Crafting Materials: "; + for (let i = 0; i < 2; i++) { + let tier = mat_tiers[i]; + let row = document.createElement("p"); + row.classList.add("left"); + let b = document.createElement("b"); + let mat = recipe.get("materials")[i]; + b.textContent = "- " + mat.get("amount") + "x " + mat.get("item").split(" ").slice(1).join(" "); + b.classList.add("space"); + let starsB = document.createElement("b"); + starsB.classList.add("T1-bracket"); + starsB.textContent = "["; + row.appendChild(b); + row.appendChild(starsB); + for(let j = 0; j < 3; j ++) { + let star = document.createElement("b"); + star.textContent = "\u272B"; + if(j < tier) { + star.classList.add("T1"); + } else { + star.classList.add("T0"); + } + row.append(star); + } + let starsE = document.createElement("b"); + starsE.classList.add("T1-bracket"); + starsE.textContent = "]"; + row.appendChild(starsE); + mats.appendChild(row); + } + ldiv.appendChild(mats); + + let ingredTable = document.createElement("table"); + ingredTable.classList.add("itemtable"); + ingredTable.classList.add("ingredTable"); + for (let i = 0; i < 3; i++) { + let row = document.createElement("tr"); + for (let j = 0; j < 2; j++) { + let ingredName = ingreds[2 * i + j]; + let cell = document.createElement("td"); + cell.style.minWidth = "50%"; + cell.classList.add("center"); + cell.classList.add("box"); + cell.classList.add("tooltip"); + let b = document.createElement("b"); + b.textContent = ingredName; + b.classList.add("space"); + let eff = document.createElement("b"); + let e = effectiveness[2 * i + j]; + if (e > 0) { + eff.classList.add("positive"); + } else if (e < 0) { + eff.classList.add("negative"); + } + eff.textContent = "[" + e + "%]"; + cell.appendChild(b); + cell.appendChild(eff); + row.appendChild(cell); + + let tooltip = document.createElement("div"); + tooltip.classList.add("tooltiptext"); + tooltip.classList.add("ing-tooltip"); + tooltip.classList.add("center"); + tooltip.id = "tooltip-" + (2*i + j); + cell.appendChild(tooltip); + } + ingredTable.appendChild(row); + } + elem.appendChild(ldiv); + elem.appendChild(ingredTable); +} + +//Displays a craft. If things change, this function should be modified. +function displayCraftStats(craft, parent_id) { + let mock_item = craft.statMap; + displayExpandedItem(mock_item,parent_id); +} + +//Displays an ingredient in item format. However, an ingredient is too far from a normal item to display as one. +function displayExpandedIngredient(ingred, parent_id) { + let parent_elem = document.getElementById(parent_id); + parent_elem.textContent = ""; + let display_order = [ + "#cdiv", + "displayName", //tier will be displayed w/ name + "#table", + "ids", + "#ldiv", + "posMods", + "itemIDs", + "consumableIDs", + "#ldiv", + "lvl", + "skills", + ] + let item_order = [ + "dura", + "strReq", + "dexReq", + "intReq", + "defReq", + "agiReq" + ] + let consumable_order = [ + "dura", + "charges" + ] + let posMods_order = [ + "above", + "under", + "left", + "right", + "touching", + "notTouching" + ]; + let id_display_order = [ + "eDefPct", + "tDefPct", + "wDefPct", + "fDefPct", + "aDefPct", + "eDamPct", + "tDamPct", + "wDamPct", + "fDamPct", + "aDamPct", + "str", + "dex", + "int", + "agi", + "def", + "hpBonus", + "mr", + "ms", + "ls", + "hprRaw", + "hprPct", + "sdRaw", + "sdPct", + "mdRaw", + "mdPct", + "xpb", + "lb", + "lq", + "ref", + "thorns", + "expd", + "spd", + "atkTier", + "poison", + "spRegen", + "eSteal", + "spRaw1", + "spRaw2", + "spRaw3", + "spRaw4", + "spPct1", + "spPct2", + "spPct3", + "spPct4", + "jh", + "sprint", + "sprintReg", + "gXp", + "gSpd", + ]; + let active_elem; + let elemental_format = false; + let style; + for (const command of display_order) { + if (command.charAt(0) === "#") { + if (command === "#cdiv") { + active_elem = document.createElement('div'); + active_elem.classList.add('itemcenter'); + } + else if (command === "#ldiv") { + active_elem = document.createElement('div'); + active_elem.classList.add('itemleft'); + } + else if (command === "#table") { + active_elem = document.createElement('table'); + active_elem.classList.add('itemtable'); + } + parent_elem.appendChild(active_elem); + }else { + let p_elem = document.createElement("p"); + p_elem.classList.add("left"); + if (command === "displayName") { + p_elem.classList.add("title"); + p_elem.classList.remove("left"); + let title_elem = document.createElement("b"); + title_elem.textContent = ingred.get("displayName"); + p_elem.appendChild(title_elem); + + let space = document.createElement("b"); + space.classList.add("space"); + p_elem.appendChild(space); + + let tier = ingred.get("tier"); //tier in [0,3] + let begin = document.createElement("b"); + begin.classList.add("T"+tier+"-bracket"); + begin.textContent = "["; + p_elem.appendChild(begin); + + for (let i = 0; i < 3; i++) { + let tier_elem = document.createElement("b"); + if(i < tier) {tier_elem.classList.add("T"+tier)} + else {tier_elem.classList.add("T0")} + tier_elem.textContent = "\u272B"; + p_elem.appendChild(tier_elem); + } + let end = document.createElement("b"); + end.classList.add("T"+tier+"-bracket"); + end.textContent = "]"; + p_elem.appendChild(end); + }else if (command === "lvl") { + p_elem.textContent = "Crafting Lvl Min: " + ingred.get("lvl"); + }else if (command === "posMods") { + for (const [key,value] of ingred.get("posMods")) { + let p = document.createElement("p"); + p.classList.add("nomarginp"); + if (value != 0) { + let title = document.createElement("b"); + title.textContent = posModPrefixes[key]; + let val = document.createElement("b"); + val.textContent = value + posModSuffixes[key]; + if(value > 0) { + val.classList.add("positive"); + } else { + val.classList.add("negative"); + } + p.appendChild(title); + p.appendChild(val); + p_elem.appendChild(p); + } + } + } else if (command === "itemIDs") { //dura, reqs + for (const [key,value] of ingred.get("itemIDs")) { + let p = document.createElement("p"); + p.classList.add("nomarginp"); + if (value != 0) { + let title = document.createElement("b"); + title.textContent = itemIDPrefixes[key]; + p.appendChild(title); + } + let desc = document.createElement("b"); + if(value > 0) { + if(key !== "dura") { + desc.classList.add("negative"); + } else{ + desc.classList.add("positive"); + } + desc.textContent = "+"+value; + } else if (value < 0){ + if(key !== "dura") { + desc.classList.add("positive"); + } else{ + desc.classList.add("negative"); + } + desc.textContent = value; + } + if(value != 0){ + p.appendChild(desc); + } + p_elem.append(p); + } + } else if (command === "consumableIDs") { //dura, charges + for (const [key,value] of ingred.get("consumableIDs")) { + let p = document.createElement("p"); + p.classList.add("nomarginp"); + if (value != 0) { + let title = document.createElement("b"); + title.textContent = consumableIDPrefixes[key]; + p.appendChild(title); + } + let desc = document.createElement("b"); + if(value > 0) { + desc.classList.add("positive"); + desc.textContent = "+"+value; + } else if (value < 0){ + desc.classList.add("negative"); + desc.textContent = value; + } + if(value != 0){ + p.appendChild(desc); + let suffix = document.createElement("b"); + suffix.textContent = consumableIDSuffixes[key]; + p.appendChild(suffix); + } + p_elem.append(p); + } + }else if (command === "skills") { + p_elem.textContent = "Used in:"; + for(const skill of ingred.get("skills")) { + let p = document.createElement("p"); + p.textContent = skill.charAt(0) + skill.substring(1).toLowerCase(); + p.classList.add("left"); + p_elem.append(p); + } + } else if (command === "ids") { //warp + for (let [key,value] of ingred.get("ids").get("maxRolls")) { + if (value !== undefined && value != 0) { + let row = displayRolledID(ingred.get("ids"), key, false, "auto"); + active_elem.appendChild(row); + } + } + } else {//this shouldn't be happening + } + + active_elem.appendChild(p_elem); + } + } +} + +function displayNextCosts(parent_id, build) { + let p_elem = document.getElementById(parent_id); + let int = build.total_skillpoints[2]; + let spells = spell_table[build.weapon.get("type")]; + + p_elem.textContent = ""; + + let title = document.createElement("p"); + title.classList.add("title"); + title.classList.add("Normal"); + title.textContent = "Next Spell Costs"; + + let int_title = document.createElement("p"); + int_title.classList.add("itemp"); + int_title.textContent = int + " Intelligence points."; + + p_elem.append(title); + p_elem.append(int_title); + + for (const spell of spells) { + let spellp = document.createElement("p"); + let spelltitle = document.createElement("p"); + spelltitle.classList.add("itemp"); + spelltitle.textContent = spell.title; + spellp.appendChild(spelltitle); + let row = document.createElement("p"); + row.classList.add("itemp"); + 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); + spellp.appendChild(row); + + p_elem.append(spellp); + } +} + +function displayRolledID(item, id, elemental_format) { + let row = document.createElement('tr'); + let min_elem = document.createElement('td'); + min_elem.classList.add('shaded-table'); + min_elem.classList.add('left'); + 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]; + row.appendChild(min_elem); + + let desc_elem = document.createElement('td'); + desc_elem.classList.add('center'); + desc_elem.classList.add('shaded-table') + //TODO elemental format jank + if (elemental_format) { + apply_elemental_format(desc_elem, id); + } + else { + desc_elem.textContent = idPrefixes[id]; + } + row.appendChild(desc_elem); + + let max_elem = document.createElement('td'); + let id_max = item.get("maxRolls").get(id) + max_elem.classList.add('right'); + max_elem.classList.add('shaded-table') + 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]; + row.appendChild(max_elem); + return row; +} + +function displayWeaponBase(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 displayFixedID(active, id, value, elemental_format, style) { + if (style) { + let row = document.createElement('tr'); + let desc_elem = document.createElement('td'); + desc_elem.classList.add('shaded-table'); + desc_elem.classList.add('left'); + desc_elem.classList.add('se-padded'); + if (elemental_format) { + apply_elemental_format(desc_elem, id); + } + else { + desc_elem.textContent = idPrefixes[id]; + } + row.appendChild(desc_elem); + + let value_elem = document.createElement('td'); + value_elem.classList.add('right'); + value_elem.classList.add('shaded-table'); + value_elem.classList.add('se-padded'); + 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('p'); + p_elem.classList.add('itemp'); + if (elemental_format) { + apply_elemental_format(p_elem, id, value); + } + else { + p_elem.textContent = idPrefixes[id].concat(value, idSuffixes[id]); + } + active.appendChild(p_elem); + return p_elem; + } +} +function displayEquipOrder(parent_elem,buildOrder){ + parent_elem.textContent = ""; + const order = buildOrder.slice(); + let title_elem = document.createElement("p"); + title_elem.textContent = "Equip order "; + title_elem.classList.add("title"); + title_elem.classList.add("Normal"); + title_elem.classList.add("itemp"); + parent_elem.append(title_elem); + parent_elem.append(document.createElement("br")); + for (const item of order) { + let p_elem = document.createElement("p"); + p_elem.classList.add("itemp"); + p_elem.classList.add("left"); + p_elem.textContent = item.get("displayName"); + parent_elem.append(p_elem); + } +} + +function displayPoisonDamage(overallparent_elem, build) { + overallparent_elem.textContent = ""; + + //Title + let title_elemavg = document.createElement("p"); + title_elemavg.classList.add('no-newline'); + // title_elemavg.classList.add("smalltitle"); + // title_elemavg.classList.add("Normal"); + title_elemavg.textContent = "Poison Stats"; + overallparent_elem.append(title_elemavg); + + let overallpoisonDamage = document.createElement("p"); + // overallpoisonDamage.classList.add("lessbottom"); + let overallpoisonDamageFirst = document.createElement("p"); + let overallpoisonDamageSecond = document.createElement("p"); + overallpoisonDamageFirst.classList.add('no-newline'); + overallpoisonDamageSecond.classList.add('no-newline'); + 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 displayMeleeDamage(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("p"); + title_elemavg.classList.add('no-newline'); + title_elemavg.classList.add("box-title"); + title_elemavg.textContent = "Melee Stats"; + overallparent_elem.append(title_elemavg); + + //average DPS + let averageDamage = document.createElement("p"); + averageDamage.classList.add("left"); + averageDamage.classList.add("itemp"); + averageDamage.classList.add("tooltip"); + 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("p"); + overallaverageDamageFirst.classList.add('no-newline'); + overallaverageDamageFirst.textContent = "Average DPS: " + + let overallaverageDamageSecond = document.createElement("p"); + overallaverageDamageSecond.classList.add('no-newline'); + 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.classList.add("itemp"); + 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("p"); + overallatkSpdFirst.classList.add('no-newline'); + overallatkSpdFirst.textContent = "Attack Speed: "; + let overallatkSpdSecond = document.createElement("p"); + overallatkSpdSecond.classList.add('no-newline'); + 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.classList.add("itemp"); + 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]; + normalDamage.classList.add("itemp"); + 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("itemp"); + 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("p"); + singleHitDamageFirst.classList.add('no-newline'); + singleHitDamageFirst.textContent = "Single Hit Average: "; + let singleHitDamageSecond = document.createElement("p"); + singleHitDamageSecond.classList.add('no-newline'); + 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.classList.add("itemp"); + 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.classList.add("itemp"); + 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]; + critDamage.classList.add("itemp"); + 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]; + critDPS.classList.add("itemp"); + 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.classList.add("itemp"); + critChance.append(document.createElement("br")); + critChance.append(document.createElement("br")); + critStats.append(critChance); + + parent_elem.append(critStats); +} + +function displayArmorStats(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 displayDefenseStats(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("table"); + statsTable.classList.add("full-border"); + + //[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("tr"); + let hp = document.createElement("td"); + hp.classList.add("Health"); + hp.classList.add("shaded-table"); + hp.classList.add("left"); + hp.textContent = "Total HP:"; + let boost = document.createElement("td"); + boost.textContent = stats[0]; + boost.classList.add("right"); + boost.classList.add("shaded-table") + + 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("tr"); + let ehp = document.createElement("td"); + ehp.classList.add("left"); + ehp.classList.add("shaded-table"); + ehp.textContent = "Effective HP:"; + + boost = document.createElement("td"); + boost.textContent = stats[1][0]; + boost.classList.add("right"); + boost.classList.add("shaded-table"); + 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("tr"); + ehp = document.createElement("td"); + ehp.classList.add("left"); + ehp.classList.add("shaded-table"); + ehp.textContent = "Effective HP (no agi):"; + + boost = document.createElement("td"); + boost.textContent = stats[1][1]; + boost.classList.add("right"); + boost.classList.add("shaded-table"); + 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("tr"); + let hpr = document.createElement("td"); + hpr.classList.add("Health"); + hpr.classList.add("shaded-table"); + hpr.classList.add("left"); + hpr.textContent = "HP Regen (Total):"; + boost = document.createElement("td"); + boost.textContent = stats[2]; + boost.classList.add("right"); + boost.classList.add("shaded-table") + + hprRow.appendChild(hpr); + hprRow.appendChild(boost); + + if (insertSummary) { + parent_elem.appendChild(hprRow); + } else { + statsTable.appendChild(hprRow); + } + + //EHPR + let ehprRow = document.createElement("tr"); + let ehpr = document.createElement("td"); + ehpr.classList.add("left"); + ehpr.classList.add("shaded-table"); + ehpr.textContent = "Effective HP Regen:"; + + boost = document.createElement("td"); + boost.textContent = stats[3][0]; + boost.classList.add("right"); + boost.classList.add("shaded-table"); + 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("tr"); + + let eledef = document.createElement("td"); + eledef.classList.add("left"); + eledef.classList.add("shaded-table"); + let eledefTitle = document.createElement("p"); + eledefTitle.classList.add('no-newline'); + eledefTitle.textContent = damageClasses[i+1]; + eledefTitle.classList.add(damageClasses[i+1]); + + let defense = document.createElement("p"); + defense.classList.add('no-newline'); + defense.textContent = " Def (Total): "; + + eledef.appendChild(eledefTitle); + eledef.appendChild(defense); + eledefElemRow.appendChild(eledef); + + let boost = document.createElement("td"); + boost.textContent = eledefs[i]; + boost.classList.add(eledefs[i] >= 0 ? "positive" : "negative"); + boost.classList.add("right"); + boost.classList.add("shaded-table"); + + 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("tr"); + let defElem = document.createElement("td"); + defElem.classList.add("left"); + defElem.classList.add("shaded-table"); + defElem.textContent = "Damage Absorbed %:"; + boost = document.createElement("td"); + boost.classList.add("right"); + boost.classList.add("shaded-table"); + boost.textContent = stats[4][0] + "%"; + defRow.appendChild(defElem); + defRow.appendChild(boost); + statsTable.append(defRow); + + let agiRow = document.createElement("tr"); + let agiElem = document.createElement("td"); + agiElem.classList.add("left"); + agiElem.classList.add("shaded-table"); + agiElem.textContent = "Dodge Chance %:"; + boost = document.createElement("td"); + boost.classList.add("right"); + boost.classList.add("shaded-table") + boost.textContent = stats[4][1] + "%"; + agiRow.appendChild(agiElem); + agiRow.appendChild(boost); + statsTable.append(agiRow); + } + + if (!insertSummary) { + parent_elem.append(statsTable); + } +} + +function displayPowderSpecials(parent_elem, powderSpecials, build) { + parent_elem.textContent = "Powder Specials"; + 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"); + powder_special.classList.add("left"); + 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("left"); + specialTitle.classList.add(damageClasses[powderSpecialStats.indexOf(special[0]) + 1]); + specialEffects.classList.add("left"); + 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" : ""); + for (const [key,value] of effects) { + let effect = document.createElement("p"); + effect.classList.add("item-margin"); + 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 averageLabel = document.createElement("p"); + averageLabel.textContent = "Average: "+averageDamage.toFixed(2); + averageLabel.classList.add("damageSubtitle"); + averageLabel.classList.add("item-margin"); + specialDamage.append(averageLabel); + + + 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 displaySpellDamage(parent_elem, overallparent_elem, build, spell, spellIdx) { + parent_elem.textContent = ""; + + + let tooltip; let tooltiptext; + const stats = build.statMap; + let title_elem = document.createElement("p"); + // title_elem.classList.add("smalltitle"); + // title_elem.classList.add("Normal"); + + overallparent_elem.textContent = ""; + let title_elemavg = document.createElement("p"); + // title_elemavg.classList.add('smalltitle'); + // title_elemavg.classList.add('Normal'); + + if (spellIdx != 0) { + let first = document.createElement("p"); + first.classList.add('no-newline'); + first.textContent = spell.title + " ("; + title_elem.appendChild(first.cloneNode(true)); //cloneNode is needed here. + title_elemavg.appendChild(first); + + let second = document.createElement("p"); + second.classList.add('no-newline'); + second.textContent = build.getSpellCost(spellIdx, spell.cost); + second.classList.add("Mana"); + // second.classList.add("tooltip"); + + 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("p"); + third.classList.add('no-newline'); + third.textContent = ") [Base: " + build.getBaseSpellCost(spellIdx, spell.cost) + " ]"; + title_elem.appendChild(third); + let third_summary = document.createElement("p"); + third_summary.classList.add('no-newline'); + 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); + + let critChance = skillPointsToPercentage(build.total_skillpoints[1]); + + let save_damages = []; + + let part_divavg = document.createElement("p"); + // part_divavg.classList.add("lessbottom"); + 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) { + parent_elem.append(document.createElement("br")); + let part_div = document.createElement("p"); + parent_elem.append(part_div); + + let subtitle_elem = document.createElement("p"); + subtitle_elem.textContent = part.subtitle; + // subtitle_elem.classList.add("nomargin"); + 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("p"); + let second = document.createElement("p"); + first.classList.add('no-newline'); + second.classList.add('no-newline'); + 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"); + overallaverageLabel.classList.add("itemp"); + part_divavg.append(overallaverageLabel); + } + + function _damage_display(label_text, average, result_idx) { + let label = document.createElement("p"); + label.textContent = label_text+average.toFixed(2); + label.classList.add("damageSubtitle"); + 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("damagep"); + p.classList.add(damageClasses[i]); + p.textContent = results[i][result_idx] + " \u2013 " + results[i][result_idx + 1]; + tooltiptext = tooltipinfo.get("damageformulas")[i].slice(0,2).join("\n"); + // tooltip = createTooltip(tooltip, "p", tooltiptext, p, ["spell-tooltip"]); + 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("p"); + let second = document.createElement("p"); + first.classList.add('no-newline'); + second.classList.add('no-newline'); + first.textContent = part.subtitle + ": "; + second.textContent = heal_amount; + overallhealLabel.appendChild(first); + second.classList.add("Set"); + overallhealLabel.appendChild(second); + // overallhealLabel.classList.add("itemp"); + // tooltip = createTooltip(tooltip, "p", tooltiptext, second, ["spell-tooltip"]); + part_divavg.append(overallhealLabel); + + /* + let effectiveHealLabel = document.createElement("p"); + first = document.createElement("b"); + second = document.createElement("b"); + let defStats = build.getDefenseStats(); + tooltiptext = ` = ${heal_amount} * ${defStats[1][0].toFixed(2)} / ${defStats[0]}`; + first.textContent = "Effective Heal: "; + second.textContent = (defStats[1][0]*heal_amount/defStats[0]).toFixed(2); + effectiveHealLabel.appendChild(first); + second.classList.add("Set"); + effectiveHealLabel.appendChild(second); + // effectiveHealLabel.classList.add("itemp"); + // tooltip = createTooltip(tooltip, "p", tooltiptext, second, ["spell-tooltip"]); + part_divavg.append(effectiveHealLabel);*/ + } + } 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"); + overallaverageLabel.classList.add("damageSubtitle"); + let overallaverageLabelFirst = document.createElement("p"); + let overallaverageLabelSecond = document.createElement("p"); + overallaverageLabelFirst.classList.add('no-newline'); + overallaverageLabelSecond.classList.add('no-newline'); + overallaverageLabelFirst.textContent = "Average: "; + overallaverageLabelSecond.textContent = total_damage.toFixed(2); + overallaverageLabelSecond.classList.add("Damage"); + // tooltip = createTooltip(tooltip, "p", tooltiptext, overallaverageLabel, ["spell-tooltip", "summary-tooltip"]); + + + overallaverageLabel.appendChild(overallaverageLabelFirst); + overallaverageLabel.appendChild(overallaverageLabelSecond); + part_divavg.append(overallaverageLabel); + } + } +} + +/** Displays the ID costs of an item + * + * @param {String} elemID - the id of the parent element. + * @param {Map} item - the statMap of an item. + */ +function displayIDCosts(elemID, item) { + let parent_elem = document.getElementById(elemID); + let tier = item.get("tier"); + if ( (item.has("fixID") && item.get("fixID")) || ["Normal","Crafted","Custom","none", " ",].includes(item.get("tier"))) { + return; + } else { + /** Returns the number of inventory slots minimum an amount of emeralds would take up + the configuration of doing so. + * Returns an array of [invSpace, E, EB, LE, Stx LE] + * + * @param {number} ems - the total numerical value of emeralds to compact. + */ + function emsToInvSpace(ems) { + let stx = Math.floor(ems/262144); + ems -= stx*4096*64; + let LE = Math.floor(ems/4096); + ems -= LE*4096; + let EB = Math.floor(ems/64); + ems -= EB*64; + let e = ems; + return [ stx + Math.ceil(LE/64) + Math.ceil(EB/64) + Math.ceil(e/64) , e, EB, LE, stx]; + } + /** + * + * @param {String} tier - item tier + * @param {Number} lvl - item level + */ + function getIDCost(tier, lvl) { + switch (tier) { + case "Unique": + return Math.round(0.5*lvl + 3); + case "Rare": + return Math.round(1.2*lvl + 8); + case "Legendary": + return Math.round(4.5*lvl + 12); + case "Fabled": + return Math.round(12*lvl + 26); + case "Mythic": + return Math.round(18*lvl + 90); + case "Set": + return Math.round(1.5*lvl + 8) + default: + return -1; + } + } + + parent_elem.style = "display: visible"; + let lvl = item.get("lvl"); + if (typeof(lvl) === "string") { lvl = parseFloat(lvl); } + + let title_elem = document.createElement("p"); + title_elem.classList.add("smalltitle"); + title_elem.style.color = "white"; + title_elem.textContent = "Identification Costs"; + parent_elem.appendChild(title_elem); + parent_elem.appendChild(document.createElement("br")); + + let grid_item = document.createElement("div"); + grid_item.style.display = "flex"; + grid_item.style.flexDirection = "rows"; + grid_item.style.flexWrap = "wrap"; + grid_item.style.gap = "5px"; + parent_elem.appendChild(grid_item); + + let IDcost = getIDCost(tier, lvl); + let initIDcost = IDcost; + let invSpace = emsToInvSpace(IDcost); + let rerolls = 0; + + while(invSpace[0] <= 28 && IDcost > 0) { + let container = document.createElement("div"); + container.classList.add("container"); + container.style = "grid-item-" + (rerolls+1); + container.style.maxWidth = "max(120px, 15%)"; + + let container_title = document.createElement("p"); + container_title.style.color = "white"; + if (rerolls == 0) { + container_title.textContent = "Initial ID Cost: "; + } else { + container_title.textContent = "Reroll to [" + (rerolls+1) + "] Cost:"; + } + container.appendChild(container_title); + let total_cost_container = document.createElement("p"); + let total_cost_number = document.createElement("b"); + total_cost_number.classList.add("Set"); + total_cost_number.textContent = IDcost + " "; + let total_cost_suffix = document.createElement("b"); + total_cost_suffix.textContent = "emeralds." + total_cost_container.appendChild(total_cost_number); + total_cost_container.appendChild(total_cost_suffix); + container.appendChild(total_cost_container); + + let OR = document.createElement("p"); + OR.classList.add("center"); + OR.textContent = "OR"; + container.appendChild(OR); + + let esuffixes = ["", "emeralds.", "EB.", "LE.", "stacks of LE."]; + for (let i = 4; i > 0; i--) { + let n_container = document.createElement("p"); + let n_number = document.createElement("b"); + n_number.classList.add("Set"); + n_number.textContent = invSpace[i] + " "; + let n_suffix = document.createElement("b"); + n_suffix.textContent = esuffixes[i]; + n_container.appendChild(n_number); + n_container.appendChild(n_suffix); + container.appendChild(n_container); + } + grid_item.appendChild(container); + + rerolls += 1; + IDcost = Math.round(initIDcost * (5 ** rerolls)); + invSpace = emsToInvSpace(IDcost); + } + } +} + +/** Displays Additional Info for + * + * @param {String} elemID - the parent element's id + * @param {Map} item - the statMap of the item + * @returns + */ +function displayAdditionalInfo(elemID, item) { + let parent_elem = document.getElementById(elemID); + parent_elem.classList.add("left"); + + let droptype_elem = document.createElement("div"); + droptype_elem.classList.add("container"); + droptype_elem.style.marginBottom = "5px"; + droptype_elem.textContent = "Drop type: " + (item.has("drop") ? item.get("drop"): "NEVER"); + parent_elem.appendChild(droptype_elem); + + let warning_elem = document.createElement("div"); + warning_elem.classList.add("container"); + warning_elem.style.marginBottom ="5px"; + warning_elem.textContent = "This page is incomplete. Will work on it later."; + parent_elem.appendChild(warning_elem); + + return; +} + +/** Displays all set bonuses (0/n, 1/n, ... n/n) for a given set + * + * @param {String} parent_id - id of the parent element + * @param {String} setName - the name of the set + */ + function displayAllSetBonuses(parent_id,setName) { + let parent_elem = document.getElementById(parent_id); + parent_elem.style.display = ""; + let set = sets[setName]; + let title_elem = document.createElement("p"); + title_elem.textContent = setName + " Set Bonuses"; + title_elem.classList.add("Set"); + title_elem.classList.add("title"); + parent_elem.appendChild(title_elem); + let grid_elem = document.createElement("div"); + grid_elem.style.display = "flex"; + grid_elem.style.flexDirection = "rows"; + grid_elem.style.flexWrap = "wrap"; + grid_elem.style.gap = "5px"; + parent_elem.appendChild(grid_elem); + + for (let i = 0; i < set.items.length; i++) { + + let set_elem = document.createElement('p'); + set_elem.classList.add("container"); + set_elem.style = "grid-item-"+(i+1); + set_elem.style.maxWidth = "max(180px, 15%)"; + set_elem.id = "set-"+setName+"-"+i; + grid_elem.appendChild(set_elem); + const bonus = set.bonuses[i]; + let mock_item = new Map(); + mock_item.set("fixID", true); + mock_item.set("displayName", setName+" Set: " + (i+1) + "/"+sets[setName].items.length); + set_elem.textContent = mock_item.get("displayName"); + 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", []); + displayExpandedItem(mock_item, set_elem.id); + } + +} + +/** Displays the individual probabilities of each possible value of each rollable ID for this item. + * + * @param {String} parent_id the document id of the parent element + * @param {String} item expandedItem object + * @param {String} amp the level of corkian amplifier used. 0 means no amp, 1 means Corkian Amplifier I, etc. [0,3] + */ +function displayIDProbabilities(parent_id, item, amp) { + if (item.has("fixID") && item.get("fixID")) {return} + let parent_elem = document.getElementById(parent_id); + parent_elem.style.display = ""; + parent_elem.innerHTML = ""; + let title_elem = document.createElement("p"); + title_elem.textContent = "Identification Probabilities"; + title_elem.id = "ID_PROB_TITLE"; + title_elem.classList.add("Legendary"); + title_elem.classList.add("title"); + parent_elem.appendChild(title_elem); + + let disclaimer_elem = document.createElement("p"); + disclaimer_elem.textContent = "IDs are rolled on a uniform distribution. A chance of 0% means that either the minimum or maximum possible multiplier must be rolled to get this value." + parent_elem.appendChild(disclaimer_elem); + + let amp_row = document.createElement("p"); + amp_row.id = "amp_row"; + let amp_text = document.createElement("b"); + amp_text.textContent = "Corkian Amplifier Used: " + amp_row.appendChild(amp_text); + let amp_1 = document.createElement("button"); + amp_1.id = "cork_amp_1"; + amp_1.textContent = "I"; + amp_row.appendChild(amp_1); + let amp_2 = document.createElement("button"); + amp_2.id = "cork_amp_2"; + amp_2.textContent = "II"; + amp_row.appendChild(amp_2); + let amp_3 = document.createElement("button"); + amp_3.id = "cork_amp_3"; + amp_3.textContent = "III"; + amp_row.appendChild(amp_3); + amp_1.addEventListener("click", (event) => {toggleAmps(1)}); + amp_2.addEventListener("click", (event) => {toggleAmps(2)}); + amp_3.addEventListener("click", (event) => {toggleAmps(3)}); + parent_elem.appendChild(amp_row); + + if (amp != 0) {toggleButton("cork_amp_" + amp)} + + let item_name = item.get("displayName"); + console.log(itemMap.get(item_name)) + + let table_elem = document.createElement("table"); + parent_elem.appendChild(table_elem); + for (const [id,val] of Object.entries(itemMap.get(item_name))) { + if (rolledIDs.includes(id)) { + let min = item.get("minRolls").get(id); + let max = item.get("maxRolls").get(id); + //Apply corkian amps + if (val > 0) { + let base = itemMap.get(item_name)[id]; + if (reversedIDs.includes(id)) {max = Math.max( Math.round((0.3 + 0.05*amp) * base), 1)} + else {min = Math.max( Math.round((0.3 + 0.05*amp) * base), 1)} + } + + let row_title = document.createElement("tr"); + //row_title.style.textAlign = "left"; + let title_left = document.createElement("td"); + let left_elem = document.createElement("p"); + let left_val_title = document.createElement("b"); + let left_val_elem = document.createElement("b"); + title_left.style.textAlign = "left"; + left_val_title.textContent = idPrefixes[id] + "Base "; + left_val_elem.textContent = val + idSuffixes[id]; + if (val > 0 == !reversedIDs.includes(id)) { + left_val_elem.classList.add("positive"); + } else if (val > 0 == reversedIDs.includes(id)) { + left_val_elem.classList.add("negative"); + } + left_elem.appendChild(left_val_title); + left_elem.appendChild(left_val_elem); + title_left.appendChild(left_elem); + row_title.appendChild(title_left); + + let title_right = document.createElement("td"); + let title_right_text = document.createElement("b"); + title_right.style.textAlign = "left"; + title_right_text.textContent = "[ " + min + idSuffixes[id] + ", " + max + idSuffixes[id] + " ]"; + if ( (min > 0 && max > 0 && !reversedIDs.includes(id)) || (min < 0 && max < 0 && reversedIDs.includes(id)) ) { + title_right_text.classList.add("positive"); + } else if ( (min < 0 && max < 0 && !reversedIDs.includes(id)) || (min > 0 && max > 0 && reversedIDs.includes(id)) ) { + title_right_text.classList.add("negative"); + } + title_right.appendChild(title_right_text); + + let title_input = document.createElement("td"); + let title_input_slider = document.createElement("input"); + title_input_slider.type = "range"; + title_input_slider.id = id+"-slider"; + if (!reversedIDs.includes(id)) { + title_input_slider.step = 1; + title_input_slider.min = `${min}`; + title_input_slider.max = `${max}`; + title_input_slider.value = `${max}`; + } else { + title_input_slider.step = 1; + title_input_slider.min = `${-1*min}`; + title_input_slider.max = `${-1*max}`; + title_input_slider.value = `${-1*max}`; + } + let title_input_textbox = document.createElement("input"); + title_input_textbox.type = "text"; + title_input_textbox.value = `${max}`; + title_input_textbox.id = id+"-textbox"; + title_input_textbox.classList.add("small-input"); + title_input.appendChild(title_input_slider); + title_input.appendChild(title_input_textbox); + + row_title.appendChild(title_left); + row_title.appendChild(title_right); + row_title.appendChild(title_input); + + let row_chances = document.createElement("tr"); + let chance_cdf = document.createElement("td"); + let chance_pdf = document.createElement("td"); + let cdf_p = document.createElement("p"); + cdf_p.id = id+"-cdf"; + let pdf_p = document.createElement("p"); + pdf_p.id = id+"-pdf"; + + chance_cdf.appendChild(cdf_p); + chance_pdf.appendChild(pdf_p); + row_chances.appendChild(chance_cdf); + row_chances.appendChild(chance_pdf); + + table_elem.appendChild(row_title); + table_elem.appendChild(row_chances); + + + + stringPDF(id, max, val, amp); //val is base roll + stringCDF(id, max, val, amp); //val is base roll + title_input_slider.addEventListener("change", (event) => { + let id_name = event.target.id.split("-")[0]; + let textbox_elem = document.getElementById(id_name+"-textbox"); + + if (reversedIDs.includes(id_name)) { + if (event.target.value < -1*min) { event.target.value = -1*min} + if (event.target.value > -1*max) { event.target.value = -1*max} + stringPDF(id_name, -1*event.target.value, val, amp); //val is base roll + stringCDF(id_name, -1*event.target.value, val, amp); //val is base roll + } else { + if (event.target.value < min) { event.target.value = min} + if (event.target.value > max) { event.target.value = max} + stringPDF(id_name, 1*event.target.value, val, amp); //val is base roll + stringCDF(id_name, 1*event.target.value, val, amp); //val is base roll + } + + if (textbox_elem && textbox_elem.value !== event.target.value) { + if (reversedIDs.includes(id_name)) { + textbox_elem.value = -event.target.value; + } else { + textbox_elem.value = event.target.value; + } + } + + + }); + title_input_textbox.addEventListener("change", (event) => { + let id_name = event.target.id.split("-")[0]; + if (reversedIDs.includes(id_name)) { + if (event.target.value > min) { event.target.value = min} + if (event.target.value < max) { event.target.value = max} + } else { + if (event.target.value < min) { event.target.value = min} + if (event.target.value > max) { event.target.value = max} + } + let slider_elem = document.getElementById(id_name+"-slider"); + if (slider_elem.value !== event.target.value) { + slider_elem.value = -event.target.value; + } + + stringPDF(id_name, 1*event.target.value, val, amp); + stringCDF(id_name, 1*event.target.value, val, amp); + }); + } + } +} + +//helper functions. id - the string of the id's name, val - the value of the id, base - the base value of the item for this id +function stringPDF(id,val,base,amp) { + /** [0.3b,1.3b] positive normal + * [1.3b,0.3b] positive reversed + * [1.3b,0.7b] negative normal + * [0.7b,1.3b] negative reversed + * + * [0.3, 1.3] minr, maxr [0.3b, 1.3b] min, max + * the minr/maxr decimal roll that corresponds to val -> minround, maxround + */ + let p; let min; let max; let minr; let maxr; let minround; let maxround; + if (base > 0) { + minr = 0.3 + 0.05*amp; maxr = 1.3; + min = Math.max(1, Math.round(minr*base)); max = Math.max(1, Math.round(maxr*base)); + minround = (min == max) ? (minr) : ( Math.max(minr, (val-0.5) / base) ); + maxround = (min == max) ? (maxr) : ( Math.min(maxr, (val+0.5) / base) ); + } else { + minr = 1.3; maxr = 0.7; + min = Math.min(-1, Math.round(minr*base)); max = Math.min(-1, Math.round(maxr*base)); + minround = (min == max) ? (minr) : ( Math.min(minr, (val-0.5) / base) ); + maxround = (min == max) ? (maxr) : ( Math.max(maxr, (val+0.5) / base) ); + } + + p = Math.abs(maxround-minround)/Math.abs(maxr-minr)*100; + p = p.toFixed(3); + + let b1 = document.createElement("b"); + b1.textContent = "Roll exactly "; + let b2 = document.createElement("b"); + b2.textContent = val + idSuffixes[id]; + if (val > 0 == !reversedIDs.includes(id)) {b2.classList.add("positive")} + if (val > 0 == reversedIDs.includes(id)) {b2.classList.add("negative")} + let b3 = document.createElement("b"); + b3.textContent = ": " + p + "%"; + document.getElementById(id + "-pdf").innerHTML = ""; + document.getElementById(id + "-pdf").appendChild(b1); + document.getElementById(id + "-pdf").appendChild(b2); + document.getElementById(id + "-pdf").appendChild(b3); + document.getElementById(id + "-pdf").style.textAlign = "left"; +} +function stringCDF(id,val,base,amp) { + let p; let min; let max; let minr; let maxr; let minround; let maxround; + if (base > 0) { + minr = 0.3 + 0.05*amp; maxr = 1.3; + min = Math.max(1, Math.round(minr*base)); max = Math.max(1, Math.round(maxr*base)); + minround = (min == max) ? (minr) : ( Math.max(minr, (val-0.5) / base) ); + maxround = (min == max) ? (maxr) : ( Math.min(maxr, (val+0.5) / base) ); + } else { + minr = 1.3; maxr = 0.7; + min = Math.min(-1, Math.round(minr*base)); max = Math.min(-1, Math.round(maxr*base)); + minround = (min == max) ? (minr) : ( Math.min(minr, (val-0.5) / base) ); + maxround = (min == max) ? (maxr) : ( Math.max(maxr, (val+0.5) / base) ); + } + + if (reversedIDs.includes(id)) { + p = Math.abs(minr-maxround)/Math.abs(maxr-minr)*100; + } else { + p = Math.abs(maxr-minround)/Math.abs(maxr-minr)*100; + } + p = p.toFixed(3); + + let b1 = document.createElement("b"); + b1.textContent = "Roll "; + let b2 = document.createElement("b"); + b2.textContent = val + idSuffixes[id]; + if (val > 0 == !reversedIDs.includes(id)) {b2.classList.add("positive")} + if (val > 0 == reversedIDs.includes(id)) {b2.classList.add("negative")} + let b3 = document.createElement("b"); + b3.textContent= " or better: " + p + "%"; + document.getElementById(id + "-cdf").innerHTML = ""; + document.getElementById(id + "-cdf").appendChild(b1); + document.getElementById(id + "-cdf").appendChild(b2); + document.getElementById(id + "-cdf").appendChild(b3); + document.getElementById(id + "-cdf").style.textAlign = "left"; +} diff --git a/sq2display_constants.js b/sq2display_constants.js new file mode 100644 index 0000000..3626e03 --- /dev/null +++ b/sq2display_constants.js @@ -0,0 +1,419 @@ +let nonRolledIDs = [ + "name", + "lore", + "displayName", + "tier", + "set", + "slots", + "type", + "material", + "drop", + "quest", + "restrict", + "nDam", + "fDam", + "wDam", + "aDam", + "tDam", + "eDam", + "atkSpd", + "hp", + "fDef", + "wDef", + "aDef", + "tDef", + "eDef", + "lvl", + "classReq", + "strReq", + "dexReq", + "intReq", + "defReq", + "agiReq","str", + "dex", + "int", + "agi", + "def", + "fixID", + "category", + "id", + "skillpoints", + "reqs", + "nDam_", + "fDam_", + "wDam_", + "aDam_", + "tDam_", + "eDam_", + "majorIds"]; +let rolledIDs = [ + "hprPct", + "mr", + "sdPct", + "mdPct", + "ls", + "ms", + "xpb", + "lb", + "ref", + "thorns", + "expd", + "spd", + "atkTier", + "poison", + "hpBonus", + "spRegen", + "eSteal", + "hprRaw", + "sdRaw", + "mdRaw", + "fDamPct", + "wDamPct", + "aDamPct", + "tDamPct", + "eDamPct", + "fDefPct", + "wDefPct", + "aDefPct", + "tDefPct", + "eDefPct", + "spPct1", + "spRaw1", + "spPct2", + "spRaw2", + "spPct3", + "spRaw3", + "spPct4", + "spRaw4", + "rainbowRaw", + "sprint", + "sprintReg", + "jh", + "lq", + "gXp", + "gSpd" +]; +let reversedIDs = [ "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4" ]; +let colorMap = new Map( + [ + ["Normal", "#fff"], + ["Unique", "#ff5"], + ["Rare","#f5f"], + ["Legendary","#5ff"], + ["Fabled","#f55"], + ["Mythic","#a0a"], + ["Crafted","#0aa"], + ["Custom","#0aa"], + ["Set","#5f5"] + ] +); +let idPrefixes = {"displayName": "", + "lvl":"Combat Level Min: ", + "classReq":"Class Req: ", + "strReq":"Strength Min: ", + "dexReq":"Dexterity Min: ", + "intReq":"Intelligence Min: ", + "defReq":"Defense Min: ", + "agiReq":"Agility Min: ", + "nDam_":"Neutral Damage: ", + "eDam_":"Earth Damage: ", + "tDam_":"Thunder Damage: ", + "wDam_":"Water Damage: ", + "fDam_":"Fire Damage: ", + "aDam_":"Air Damage: ", + "atkSpd":"Attack Speed: ", + "hp":"Health : ", + "eDef":"Earth Defense: ", + "tDef":"Thunder Defense: ", + "wDef":"Water Defense: ", + "fDef":"Fire Defense: ", + "aDef":"Air Defense: ", + "str":"Strength: ", + "dex":"Dexterity: ", + "int":"Intelligence: ", + "def":"Defense: ","agi":"Agility: ", + "hpBonus":"Health Bonus: ", + "hprRaw":"Health Regen Raw: ", + "hprPct":"Health Regen %: ", + "sdRaw":"Raw Spell Damage: ", + "sdPct":"Spell Damage %: ", + "mdRaw":"Raw Melee Damage: ", + "mdPct":"Melee Damage %: ", + "mr":"Mana Regen: ", + "ms":"Mana Steal: ", + "ref":"Reflection: ", + "ls":"Life Steal: ", + "poison":"Poison: ", + "thorns":"Thorns: ", + "expd":"Exploding: ", + "spd":"Walk Speed Bonus: ", + "atkTier":"Attack Speed Bonus: ", + "eDamPct":"Earth Damage %: ", + "tDamPct":"Thunder Damage %: ", + "wDamPct":"Water Damage %: ", + "fDamPct":"Fire Damage %: ", + "aDamPct":"Air Damage %: ", + "eDefPct":"Earth Defense %: ", + "tDefPct":"Thunder Defense %: ", + "wDefPct":"Water Defense %: ", + "fDefPct":"Fire Defense %: ", + "aDefPct":"Air Defense %: ", + "spPct1":"1st Spell Cost %: ", + "spRaw1":"1st Spell Cost Raw: ", + "spPct2":"2nd Spell Cost %: ", + "spRaw2":"2nd Spell Cost Raw: ", + "spPct3":"3rd Spell Cost %: ", + "spRaw3":"3rd Spell Cost Raw: ", + "spPct4":"4th Spell Cost %: ", + "spRaw4":"4th Spell Cost Raw: ", + "rainbowRaw":"Rainbow Spell Damage Raw: ", + "sprint":"Sprint Bonus: ", + "sprintReg":"Sprint Regen Bonus: ", + "jh":"Jump Height: ", + "xpb":"Combat XP Bonus: ", + "lb":"Loot Bonus: ", + "lq":"Loot Quality: ", + "spRegen":"Soul Point Regen: ", + "eSteal":"Stealing: ", + "gXp":"Gathering XP Bonus: ", + "gSpd":"Gathering Speed Bonus: ", + "slots":"Powder Slots: ", + "set":"Set: ", + "quest":"Quest Req: ", + "restrict":"", + "lore": "" +}; +let idSuffixes = {"displayName": "", + "lvl":"", + "classReq":"", + "strReq":"", + "dexReq":"", + "intReq":"", + "defReq":"", + "agiReq":"", + "nDam_":"", + "eDam_":"", + "tDam_":"", + "wDam_":"", + "fDam_":"", + "aDam_":"", + "atkSpd":"", + "hp":"", + "eDef":"", + "tDef":"", + "wDef":"", + "fDef":"", + "aDef":"", + "str":"", + "dex":"", + "int":"", + "def":"", + "agi":"", + "hpBonus":"", + "hprRaw":"", + "hprPct":"%", + "sdRaw":"", + "sdPct":"%", + "mdRaw":"", + "mdPct":"%", + "mr":"/5s", + "ms":"/3s", + "ref":"%", + "ls":"/3s", + "poison":"/3s", + "thorns":"%", + "expd":"%", + "spd":"%", + "atkTier":" tier", + "eDamPct":"%", + "tDamPct":"%", + "wDamPct":"%", + "fDamPct":"%", + "aDamPct":"%", + "eDefPct":"%", + "tDefPct":"%", + "wDefPct":"%", + "fDefPct":"%", + "aDefPct":"%", + "spPct1":"%", + "spRaw1":"", + "spPct2":"%", + "spRaw2":"", + "spPct3":"%", + "spRaw3":"", + "spPct4":"%", + "spRaw4":"", + "rainbowRaw":"", + "sprint":"%", + "sprintReg":"%", + "jh":"", + "xpb":"%", + "lb":"%", + "lq":"%", + "spRegen":"%", + "eSteal":"%", + "gXp":"%", + "gSpd":"%", + "slots":"", + "set":" set.", + "quest":"", + "restrict":"", + "lore": "" +}; + +//Used for item IDs and ingredient id field IDs +//Used for ingredient IDs - name, lvl, tier. As of now, not used. +/*let ingPrefixes = {"name": "", "lvl": "", "tier": ""}; +let ingSuffixes = {"name": "", "lvl": "", "tier": ""}*/ +//Used for ingredient consumableIDs +let consumableIDPrefixes = { + "charges": "Charges: ", + "dura": "Duration: " +} +let consumableIDSuffixes = { + "charges": "", + "dura": " sec." +} +//Used for ingredient itemIDs +let itemIDPrefixes = { + "dura": "Durability: ", + "strReq": "Strength Min: ", + "dexReq": "Dexterity Min: ", + "intReq": "Intelligence Min: ", + "defReq": "Defense Min: ", + "agiReq": "Agility Min: " +} + +//Used for ingredient posMods IDs +let posModPrefixes = { + "left":"Effectiveness Left: ", + "right":"EFfectiveness Right: ", + "above":"Effectiveness Above: ", + "under":"Effectiveness Under: ", + "touching":"EFfectiveness Touching: ", + "notTouching":"Effectiveness Not Touching: " +} +let posModSuffixes = { + "left":"%", + "right":"%", + "above":"%", + "under":"%", + "touching":"%", + "notTouching":"%" +} + +/* + * Display commands + */ +let build_overall_display_commands = [ + "#table", + "#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 = [ + '#table', + "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 = [ + '#table', + '#defense-stats', + '#spacer', + // 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 item_display_commands = [ + "#cdiv", + "displayName", + //"type", //REPLACE THIS WITH SKIN + "#ldiv", + "atkSpd", + "#ldiv", + "!elemental", + "hp", + "nDam_", "fDam_", "wDam_", "aDam_", "tDam_", "eDam_", + "fDef", "wDef", "aDef", "tDef", "eDef", + "!elemental", + "#ldiv", + "classReq", + "lvl", + "strReq", "dexReq", "intReq", "defReq","agiReq", + "#ldiv", + "str", "dex", "int", "def", "agi", + "#table", + "str", "dex", "int", "def", "agi", //jank lmao + "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", + "#ldiv", + "majorIds", + "slots", + "set", + "lore", + "quest", + "restrict" +]; diff --git a/sq2icons.js b/sq2icons.js new file mode 100644 index 0000000..6c03a9d --- /dev/null +++ b/sq2icons.js @@ -0,0 +1,38 @@ +//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()} + +//REMOVE THIS IN THE REAL VERSION 7 OR SOMETHING +window_storage.removeItem("rick"); + +/** Toggle icons on the ENTIRE page. + * + */ +/* +function toggleIcons() { + newIcons = !newIcons; + let imgs = document.getElementsByTagName("IMG"); + let favicon = document.querySelector("link[rel~='icon']"); + let toggleiconbutton = document.getElementById("toggle-icon-button"); + + if (newIcons) { //switch to new + favicon.href = favicon.href.replace("media/icons/old","media/icons/new"); + for (const img of imgs) { + if (img.src.includes("media/icons/old")) {img.src = img.src.replace("media/icons/old","media/icons/new");} + if (img.src.includes("media/items/old")) {img.src = img.src.replace("media/items/old","media/items/new");} + } + toggleiconbutton.textContent = "Use Old Icons"; + window_storage.setItem("newicons","true"); + } else { //switch to old + favicon.href = favicon.href.replace("media/icons/new","media/icons/old"); + for (const img of imgs) { + if (img.src.includes("media/icons/new")) {img.src = img.src.replace("media/icons/new","media/icons/old");} + if (img.src.includes("media/items/new")) {img.src = img.src.replace("media/items/new","media/items/old");} + } + toggleiconbutton.textContent = "Use New Icons"; + window_storage.setItem("newicons","false"); + } +}*/ \ No newline at end of file diff --git a/sq2items.js b/sq2items.js new file mode 100644 index 0000000..8520422 --- /dev/null +++ b/sq2items.js @@ -0,0 +1,200 @@ + + +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 = document.getElementById("filter-items"); +for (let x in translate_mappings) { + let el = document.createElement("option"); + el.value = x; + itemFilters.appendChild(el); +} +for (let x in special_mappings) { + let el = document.createElement("option"); + el.value = x; + itemFilters.appendChild(el); +} + +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("main"); + for (let i in items_copy) { + let item = items_copy[i]; + let box = document.createElement("div"); + box.style.flex = "1"; + box.classList.add("box"); + box.id = "item"+i; + items_parent.appendChild(box); + displayExpandedItem(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; + console.log("category: "+categoryOrType) + 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("main").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." + console.log('a') + console.log(items_copy); + displayItems(items_copy); +} + +function init_items() { + items_expanded = items.filter( (i) => !("remapID" in i) ).map( (i) => expandItem(i, []) ); +} + +load_init(init_items);