diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index df9e4f7..0000000 --- a/TODO.txt +++ /dev/null @@ -1,1410 +0,0 @@ -Item identifications -- Base max/min, rounding correctly, and handling pre-ID - - Appears to be working correctly. Need to continue checking. - -Damage calculation -- General calculation framework -- Weapon powdering -- Spell multipler -- Melee dps and multiplier -- Poison calculations - -Skillpoint engine -- Make it work... DONE! SURPRISINGLY! - -Build encoding -- Allow exporting/importing builds to strings - -LATER STUFF: -- Custom items integration - - wynndata parse? And/or google sheets -- Crafted Items - -"use strict"; -$(function () { - // some globals - let globalItemDb = {}; - let globalHashItemDb = {}; - let dropdowns = [ - ["helmet_select", "helmet"], - ["chestplate_select", "chestplate"], - ["leggings_select", "leggings"], - ["boots_select", "boots"], - ["ring0_select", "ring"], - ["ring1_select", "ring"], - ["bracelet_select", "bracelet"], - ["necklace_select", "necklace"], - ["weapon_select", "weapon"] - ]; - let correctOrder = ["helmet", "chestplate", "leggings", "boots", "ring", "ring", "bracelet", "necklace", "weapon"]; - // [suffix, id] - let idMap = { - damage: { - spellPercent: ["%", "Spell Damage"], - spellRaw: ["", "Neutral Spell Damage"], - meleePercent: ["%", "Main Attack Damage"], - meleeRaw: ["", "Main Attack Neutral Damage"], - earth: ["%", "Earth Damage"], - thunder: ["%", "Thunder Damage"], - water: ["%", "Water Damage"], - fire: ["%", "Fire Damage"], - air: ["%", "Air Damage"], - }, - regen: { - healthPercent: ["%", "Health Regen"], - healthRaw: ["", "Health Regen"], - mana: ["/4s", "Mana Regen"], - soulPoint: ["%", "Soul Pint Regen"] - }, - steal: { - mana: ["/4s", "Mana Steal"], - health: ["/4s", "Life Steal"] - }, - others: { - attackSpeedBonus: [" Tier", "Attack Speed"], - exploding: ["%", "Exploding"], - healthBonus: ["", "Health"], - jumpHeight: ["", "Jump Height"], - lootBonus: ["%", "Loot Bonus"], - lootQuality: ["%", "Loot Quality"], - poison: ["/3s", "Poison"], - reflection: ["%", "Reflection"], - sprint: ["%", "Sprint"], - sprintRegen: ["%", "Sprint Regen"], - stealing: ["%", "Stealing"], - thorns: ["%", "Thorns"], - walkSpeed: ["%", "Walk Speed"], - xpBonus: ["%", "XP Bonus"] - } - }; - // min dmg, max dmg, conversion, +def, -def, powder code - let powderStats = { - E: [["earth","air"],[3,6,17,2,1,'0'],[6,9,21,4,2,'1'],[8,14,25,8,3,'2'],[11,16,31,14,5,'3'],[15,18,38,22,9,'4'],[18,22,46,30,13,'5']], - T: [["thunder","earth"],[1,8,9,3,1,'6'],[1,13,11,5,1,'7'],[2,18,14,9,2,'8'],[3,24,17,14,4,'9'],[3,32,22,20,7,'A'],[5,40,28,28,10,'B']], - W: [["water","thunder"],[3,4,13,3,1,'C'],[4,7,15,6,1,'D'],[6,10,17,11,2,'E'],[8,12,21,18,4,'F'],[11,14,26,28,7,'G'],[13,17,32,40,10,'H']], - F: [["fire","water"],[2,5,14,3,1,'J'],[4,8,16,5,2,'K'],[6,10,19,9,3,'L'],[9,13,24,16,5,'M'],[12,16,30,25,9,'N'],[15,19,37,36,13,'P']], - A: [["air","fire"],[2,6,11,3,1,'Q'],[4,9,14,6,2,'R'],[7,10,17,10,3,'S'],[9,13,22,16,5,'T'],[13,18,28,24,9,'U'],[16,18,35,34,13,'W']] - }; - let skillBounsPct = [ - 0.0, 1.0, 2.0, 2.9, 3.9, 4.9, 5.8, 6.7, 7.7, 8.6, - 9.5,10.4,11.3,12.2,13.1,13.9,14.8,15.7,16.5,17.3, - 18.2,19.0,19.8,20.6,21.4,22.2,23.0,23.8,24.6,25.3, - 26.1,26.8,27.6,28.3,29.0,29.8,30.5,31.2,31.9,32.6, - 33.3,34.0,34.6,35.3,36.0,36.6,37.3,37.9,38.6,39.2, - 39.9,40.5,41.1,41.7,42.3,42.9,43.5,44.1,44.7,45.3, - 45.8,46.4,47.0,47.5,48.1,48.6,49.2,49.7,50.3,50.8, - 51.3,51.8,52.3,52.8,53.4,53.9,54.3,54.8,55.3,55.8, - 56.3,56.8,57.2,57.7,58.1,58.6,59.1,59.5,59.9,60.4, - 60.8,61.3,61.7,62.1,62.5,62.9,63.3,63.8,64.2,64.6, - 65.0,65.4,65.7,66.1,66.5,66.9,67.3,67.6,68.0,68.4, - 68.7,69.1,69.4,69.8,70.1,70.5,70.8,71.2,71.5,71.8, - 72.2,72.5,72.8,73.1,73.5,73.8,74.1,74.4,74.7,75.0, - 75.3,75.6,75.9,76.2,76.5,76.8,77.1,77.3,77.6,77.9, - 78.2,78.4,78.7,79.0,79.2,79.5,79.8,80.0,80.3,80.5,80.8]; - let elemIcons = { - earth: "✤", - thunder: "✦", - water: "❉", - fire: "✹", - air: "❋" - }; - let elemColours = { - earth: "dark_green", - thunder: "yellow", - water: "aqua", - fire: "red", - air: "white" - }; - let elementList = ["earth", "thunder", "water", "fire", "air"]; - let skillList = ["strength", "dexterity", "intelligence", "defense", "agility"]; - let itemListBox = $("#item_list_box"); - let currentReq = { - req: { - strength: 0, - dexterity: 0, - intelligence: 0, - defense: 0, - agility: 0 - }, - bonus: { - strength: 0, - dexterity: 0, - intelligence: 0, - defense: 0, - agility: 0 - }, - order: [] - }; // for the wearables - let realReq = { - req: { - strength: 0, - dexterity: 0, - intelligence: 0, - defense: 0, - agility: 0 - }, - bonus: { - strength: 0, - dexterity: 0, - intelligence: 0, - defense: 0, - agility: 0 - }, - order: [] - }; // for the wearables+wep - let powderList = {helmet: [], chestplate: [], leggings: [], boots: [], weapon: []}; - - console.log("window ready"); - // load item db - loadItemDb().then(itemDb => { - itemDb.forEach(item => globalItemDb[item.displayName || item.info.name] = item); - itemDb.forEach(item => globalHashItemDb[item.info.hash] = item); - - // add powder button handlers - $("span.powder").click(e => { - let type = e.target.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.id.replace("_powders", ""); - let powder = e.target.classList[1].substr(7).toUpperCase(); - let item = globalItemDb[$(`#${type}_select > select`).val()]; - let sockets = item.info.sockets; - let powderArray = powderList[type]; - for (let i = 0; i < sockets; i++) { - if (powderArray.length <= i) { - powderArray.push(powder); - break; - } - if (!powderArray[i]) { - powderArray[i] = powder; - break; - } - } - let powderBox = $(`#${type}_powders`); - let powderListBox = powderBox.find("div > div.powder_list"); - renderSockets(powderListBox, type, realReq); - }); - $('.sp_input').change(() => { - let build = dropdowns.map(dropdown => { - let select = $("#" + dropdown[0] + " > select"); - let name = select.val(); - return {name, powder: powderList[dropdown[1]]}; - }); - let total = $('.sp_input').map((i, v) => 1*v.value).toArray().reduce((a, b) => a + b); - $('#sp_remaining').html(`Assign Skill Points (${200 - total} remaining):`); - renderBuild(calculateBuild(build)); - }); - $('.reset_button').click(() => { - $('.sp_input').map((i, v) => v.value = v.getAttribute("min")); - $('.sp_input[data-slot=0]').change(); - }); - $('#copy_btn').click(function (e) { - let copyText = $('#link_box'); - copyText.select(); - document.execCommand("copy"); - $(e.target).html('Copied!'); - }); - - // load into dropdown menus - let readySelects = 0; - dropdowns.forEach((dropdown) => { - let select = $("#" + dropdown[0] + " > select"); - itemDb.filter((x) => (x.info.type || x.accessoryType).toLowerCase() === dropdown[1] || x.category.toLowerCase() === dropdown[1]) - .map((x) => x.displayName || x.info.name).sort().forEach(name => { - select.append(``); - }); - select.change((e, o) => { - let build = dropdowns.map(dropdown => { - let select = $("#" + dropdown[0] + " > select"); - let name = select.val(); - return {name, powder: powderList[dropdown[1]]}; - }); - let calculatedBuild = calculateBuild(build); - let parentId = e.target.parentElement.id; - let type = parentId.replace("_select", ""); - let index = correctOrder.indexOf(type.replace(/\d/, "")); - if (o && !o.deferCalc) { - if (type !== "weapon") { - currentReq = findStatReq(calculatedBuild.items); - realReq = JSON.parse(JSON.stringify(currentReq)); - } - if (calculatedBuild.items[8]) { - // take the skills we have and see what else do we need - let weaponItem = calculatedBuild.items[8]; - skillList.forEach(skill => { - let ownedPoints = currentReq.req[skill] + currentReq.bonus[skill]; - let diff; - if (!weaponItem.req[skill]) { - diff = 0; - } else if (weaponItem.req[skill] > ownedPoints) { - diff = weaponItem.req[skill] - ownedPoints; - } else { - diff = 0; - } - realReq.req[skill] = currentReq.req[skill] + diff; - }); - let bonus = {}; - skillList.forEach(skill => { - bonus[skill] = currentReq.bonus[skill] + weaponItem.base.skill[skill]; - }); - realReq.bonus = bonus; - realReq.order = currentReq.order; - } - skillList.forEach((skill, i) => { - let spInput = $(`.sp_input[data-slot=${i}]`); - spInput.attr("min", realReq.req[skill]); - spInput.val(realReq.req[skill]); - }); - } - let item = calculatedBuild.items[index]; - let box = $(`#${type}_div`).empty(); - let powderBox = $(`#${type}_powders`); - if (item && item.info.sockets) { - box.append(generateItemBox(item, false)); - powderBox.show(); - powderBox.find("div > p.large").text(item.displayName || item.info.name).attr("class", "large item_name " + item.info.tier.toLowerCase()); - let powderListBox = powderBox.find("div > div.powder_list"); - renderSockets(powderListBox, type); - } else { - powderBox.hide(); - renderBuild(calculatedBuild); - } - // $(`#${type}_div`).empty(); - }); - select.on("chosen:ready", () => { - ++readySelects; - if (readySelects == 9) { - let query = document.location.search.substr(1); - if (query.split('-').length === 14) { - let _s = query.split('-'); - let itemNames = _s.slice(0, 9).map(h => (globalHashItemDb[h] || {info: {name: ""}}).info.name); - let powders = []; - let selects = $('.item_select > select'); - for (let i = 0; i < 5; ++i) { - let _pows = _s[9 + i]; - let pows = []; - for (let j = 0; j < _pows.length; ++j) { - let idx = '0123456789ABCDEFGHJKLMNPQRSTUW'.indexOf(_pows[j]); - pows.push('ETWFA'[Math.floor(idx/6)]+(1+(idx%6))); - } - powders.push(pows); - } - for (let i = 0; i < 9; ++i) { - $(selects[i]).val(itemNames[i]).trigger('chosen:updated'); - } - powderList.helmet = powders[0]; - powderList.chestplate = powders[1]; - powderList.leggings = powders[2]; - powderList.boots = powders[3]; - powderList.weapon = powders[4]; - for (let i = 0; i < 5; ++i) { - for (let j = 0; j < powders[i].length; ++j) { - $(`.powder_choices:eq(${i})`).find(`.powder_${powders[i][j].toLowerCase()}`).click(); - } - } - $(selects[0]).trigger("change", {deferCalc: true}); - $(selects[1]).trigger("change", {deferCalc: true}); - $(selects[2]).trigger("change", {deferCalc: true}); - $(selects[3]).trigger("change", {deferCalc: true}); - $(selects[7]).trigger("change", {deferCalc: false}); - $(selects[8]).trigger("change", {deferCalc: false}); - - window.selects = selects; - $('.reset_button').click(); - } - } - }); - select.chosen({width: "100%"}).addClass("col-md-3 col-sm-4"); - }); - $(window).resize(resetPos); - }); - - function spellDamage(build, totalSkills, spellMultiplier, elemMultiplier) { - let weaponDamage = JSON.parse(JSON.stringify(build.base.damage)); - for (let i in weaponDamage) { - weaponDamage[i] = weaponDamage[i].split("-").map(x => 1*x); - } - // consider wep powders, move elemMultiplier.neutral to the respective elem - powderList.weapon.forEach(powder => { - if (!powder || !elemMultiplier.neutral) { - return; - } - let stat = powderStats[powder[0]][powder[1]]; - let conversion = stat[2]; - if (elemMultiplier.neutral > conversion) { - elemMultiplier.neutral -= conversion; - elemMultiplier[powderStats[powder[0]][0][0]] += conversion; - } else { - elemMultiplier[powderStats[powder[0]][0][0]] += elemMultiplier.neutral; - elemMultiplier.neutral = 0; - } - }); - // base damage is `neutral * multiplier + elemDamageOnWep` then multiply by attack speed multiplier, then spell multiplier - let base = {}; - let attackSpeedMultiplier = {SUPER_SLOW: 0.51, VERY_SLOW: 0.83, SLOW: 1.5, NORMAL: 2.05, FAST: 2.5, VERY_FAST: 3.1, SUPER_FAST: 4.3}[build.base.attackSpeed]; - base.neutral = weaponDamage.neutral.map(x => x * elemMultiplier.neutral / 100 * attackSpeedMultiplier * spellMultiplier / 100); - elementList.forEach(elem => { - base[elem] = [0, 1].map(i => (weaponDamage.neutral[i] * elemMultiplier[elem]) / 100 + weaponDamage[elem][i]) - .map(x => x * attackSpeedMultiplier * spellMultiplier / 100); - }); - // console.log(base); - // multiply by (1 + spellDamage + strengthPercentage + elemSkillPercentage + elemDamagePercentage) - let normal = {}; - normal.neutral = base.neutral.map(x => (x * (100 + build.identification.damage.spellPercent + skillBounsPct[totalSkills[0]]) + build.identification.damage.spellRaw * spellMultiplier) / 100).map(Math.floor); - normal.total = [normal.neutral[0], normal.neutral[1]]; - elementList.forEach((elem, i) => { - normal[elem] = base[elem].map(x => x * (100 + build.identification.damage.spellPercent + skillBounsPct[totalSkills[0]] + skillBounsPct[totalSkills[i]] + build.identification.damage[elem]) / 100).map(Math.floor); - normal.total = sum(normal.total, normal[elem]); - }); - // for crit hits change the 1 to 2 - let critical = {}; - critical.neutral = base.neutral.map(x => (x * (200 + build.identification.damage.spellPercent + skillBounsPct[totalSkills[0]]) + build.identification.damage.spellRaw * spellMultiplier) / 100).map(Math.floor); - critical.total = [critical.neutral[0], critical.neutral[1]]; - elementList.forEach((elem, i) => { - critical[elem] = base[elem].map(x => x * (200 + build.identification.damage.spellPercent + skillBounsPct[totalSkills[0]] + skillBounsPct[totalSkills[i]] + build.identification.damage[elem]) / 100).map(Math.floor); - critical.total = sum(critical.total, critical[elem]); - }); - let args = [build.identification.damage.spellPercent, elementList.map(elem => build.identification.damage[elem]), skillBounsPct[totalSkills[0]], totalSkills.map(x => skillBounsPct[x])]; - return {normal, critical, args}; - } - - function renderSockets(powderListBox, type) { - let box = $(`#${type}_div`).empty(); - let powderBox = $(`#${type}_powders`); - let index = correctOrder.indexOf(type); - - let build = dropdowns.map(dropdown => { - let select = $("#" + dropdown[0] + " > select"); - let name = select.val(); - return {name, powder: powderList[dropdown[1]]}; - }); - let calculatedBuild = calculateBuild(build); - let item = calculatedBuild.items[index]; - let sockets = item.info.sockets; - if (sockets) { - box.append(generateItemBox(item, false)); - powderBox.show(); - powderBox.find("div > p.large").text(item.displayName || item.info.name).attr("class", "large item_name " + item.info.tier.toLowerCase()); - let powderListBox = powderBox.find("div > div.powder_list"); - powderListBox.empty(); - for (let i = 0; i < sockets; i++) { - let powderBox; - if (powderList[type].length <= i || !powderList[type][i]) { - powderBox = $(`
`); - } else { - powderBox = $(`
`); - powderBox.click(() => { - powderList[type][i] = undefined; - renderSockets(powderListBox, type); - }); - } - powderListBox.append(powderBox); - } - } else { - powderBox.hide(); - } - renderBuild(calculatedBuild); - } - - async function loadItemDb() { - // load from server lol, maybe cached tho - return await fetch("/build/itemdb.json").then(data => data.json()).then(json => json.itemDB) - } - - /** - * Calculates the build"s stats according to the items and skill points allocated. - * Normally we use the item"s base stats as outlined in itemDB, but stats can be overridden, - * even if the override is outside of the normal range we won"t check - * @param items The items equipped as an array - */ - function calculateBuild(items) { - // check for the correct types [helm, chest, leggings, boots, ring, ring, bracelet, necklace, wep] - let totalIdentifications = {}; - let totalBase = {}; - let totalReq = {quest: []}; - let realItems = []; - let conversion = {neutral: 100, earth: 0, thunder: 0, water: 0, fire: 0, air: 0}; - for (let i = 0; i < 9; i++) { - if (!items[i]) { - realItems.push(null); - continue; - } - let name = items[i].name; - let item = globalItemDb[name]; - if (!item) { - realItems.push(null); - if (name.length) { - console.warn("Item " + name + " not found"); - } - continue; - } - if ((item.info.type || item.accessoryType).toLowerCase() !== correctOrder[i] && item.category.toLowerCase() !== correctOrder[i]) { - return null; - } - // must clone since we don"t want to override the base item - let realItem = JSON.parse(JSON.stringify(item)); - // this item passed the check, populate it with the real one - override(realItem.identification, items.override); - // powders - if (Array.isArray(items[i].powder)) { - let powders = items[i].powder.slice(0, realItem.info.sockets); - realItem.powders = powders; - - for (let j = 0; j < powders.length; j++) { - if (!powders[j]) { - continue; - } - let elem = powders[j][0]; - let tier = powders[j][1]; - let elemName = powderStats[elem][0][0]; - let weakElemName = powderStats[elem][0][1]; - let powder = powderStats[elem][tier]; - let conversionPct = Math.min(conversion.neutral, powder[2]); - if (i === 8) { - // weapon, modify conversion and damage - conversion.neutral -= conversionPct; - conversion[elemName] += conversionPct; - let originalDamage = realItem.base.damage[elemName] || "0-0"; - let [lower, upper] = originalDamage.split("-").map(x => parseInt(x)); - // console.log(lower, upper, originalDamage); - lower += powder[0]; - upper += powder[1]; - realItem.base.damage[elemName] = lower + "-" + upper; - switch (realItem.info.type) { - case "Relik": - realItem.req.class = "Shaman"; - break; - case "Wand": - realItem.req.class = "Mage"; - break; - case "Spear": - realItem.req.class = "Warrior"; - break; - case "Bow": - realItem.req.class = "Archer"; - break; - case "Dagger": - realItem.req.class = "Assassin"; - break; - } - } else { - // non weapon, modify defense - realItem.base.defense[elemName] += powder[3]; - realItem.base.defense[weakElemName] -= powder[4]; - } - } - } - // this goes after all the modifications - totalIdentifications = sum(totalIdentifications, realItem.identification); - totalBase = sum(totalBase, realItem.base); - totalReq = max(totalReq, realItem.req); - - realItems.push(realItem); - } - // adjust health according to lvl req - totalBase.health = (totalBase.health || 0) + Math.max((totalReq.level || 1), 101) * 5 + 5; - - if (realItems[8]) { - let totalConverted = [0, 0]; - let displayDamage = JSON.parse(JSON.stringify(totalBase.damage)); - for (let i in displayDamage) { - displayDamage[i] = displayDamage[i].split("-").map(x => 1*x); - } - elementList.forEach(elem => { - if (conversion[elem]) { - let converted = totalBase.damage.neutral.split("-").map(x => Math.round(x * conversion[elem] / 100)); - [0, 1].forEach(i => { - if (converted[i] + totalConverted[i] > displayDamage.neutral[i]) { - converted[i] = displayDamage.neutral[i] - totalConverted[i]; - } - }); - displayDamage[elem] = sum(displayDamage[elem], converted); - totalConverted[0] += converted[0]; - totalConverted[1] += converted[1]; - } - }); - displayDamage.neutral[0] -= totalConverted[0]; - displayDamage.neutral[1] -= totalConverted[1]; - realItems[8].displayDamage = displayDamage; - return { - identification: totalIdentifications, - base: totalBase, - req: totalReq, - items: realItems, - conversion, - displayDamage - }; - } - return { - identification: totalIdentifications, - base: totalBase, - req: totalReq, - items: realItems, - conversion - }; - } - - function renderBuild(build) { - if (!build.items.filter(x => x).length) { - // empty build - return; - } - let buildCode = build.items.map(x => x ? x.info.hash : ""); - [["helmet", 0], ["chestplate", 1], ["leggings", 2], ["boots", 3], ["weapon", 8]].forEach(v => { - let type = v[0]; - let index = v[1]; - let item = build.items[index]; - if (item) { - let powderCode = ""; - let sockets = Math.min(powderList[type].length, item.info.sockets); - for (let i = 0; i < sockets; i++) { - let powder = powderList[type][i]; - powderCode += powderStats[powder[0]][powder[1]][5]; - } - buildCode.push(powderCode); - } else { - buildCode.push(""); - } - }); - $("#link_box").val(`${location.protocol}//${location.host}${location.pathname}?${buildCode.join("-")}`); - history.replaceState({}, "", "?" + buildCode.join("-")); - itemListBox.empty(); - build.items.forEach(item => { - if (item !== null) { - itemListBox.append(generateItemBox(item, true)); - } - }); - resetPos(); - // requirements - let req = build.req; - let reqBox = $("#build_req > .build_content"); - reqBox.empty(); - if (req.quest.length) { - for (let i = 0; i < req.quest.length; i++) { - reqBox.append(`Quest Req: ${req.quest[i]}
`); - } - } - if (req.class) { - reqBox.append(`Class Req: ${req.class}
`); - } - if (req.level) { - reqBox.append(`Combat Lv. Min: ${req.level}
`); - } - skillList.forEach(skill => { - let value = req[skill]; - if (value) { - reqBox.append(`${capitalize(skill)} Min: ${value}
`); - } - }); - // how to wear - let howToWearBox = $("#build_howto > .build_content"); - howToWearBox.empty(); - howToWearBox.append("

Assign the follow Skill Points:

"); - skillList.forEach(skill => { - howToWearBox.append(`${capitalize(skill)}: ${realReq.req[skill]}
`); - }); - howToWearBox.append("
Then wear your items in the following order:
"); - realReq.order.forEach((index, i) => { - howToWearBox.append(`${i + 1}. ${capitalize(correctOrder[index])}
`); - }); - if (build.items[8]) { - howToWearBox.append(`${realReq.order.length + 1}. Weapon
`); - } - let {others, damage, regen, steal} = build.identification; - let {healthRaw, healthPercent} = regen; - // defenses - let defensesBox = $("#build_defs > .build_content"); - defensesBox.empty(); - defensesBox.empty(); - defensesBox.append("
"); - defensesBox = defensesBox.children("table"); - defensesBox.append(`❤ Health:${build.base.health}`); - defensesBox.append(`❤ Health Regen:${healthRaw}${nToString(healthPercent)}%=${Math.round(healthRaw + Math.abs(healthRaw) * healthPercent / 100)}`); - defensesBox.append(" "); - elementList.forEach(elem => { - let raw = build.base.defense[elem] || 0; - let percent = build.identification.defense[elem] || 0; - let final = Math.round(raw + Math.abs(raw) * percent / 100); - defensesBox.append(`${elemIcons[elem]} ${capitalize(elem)} Defense:${raw}${nToString(percent)}%=${final}`); - }); - // other ids - let othersBox = $("#build_ids > .build_content"); - othersBox.empty(); - othersBox.append("
"); - othersBox = othersBox.children("table"); - skillList.forEach(skill => { - if (realReq.bonus[skill]) { - let value = realReq.bonus[skill]; - othersBox.append(`${capitalize(skill)}${nToString(value)}`); - } - }); - for (let i in damage) { - if (damage.hasOwnProperty(i)) { - let value = damage[i]; - if (value) { - othersBox.append(`${idMap.damage[i][1]}${nToString(value)}${idMap.damage[i][0]}`); - } - } - } - for (let i in regen) { - if (regen.hasOwnProperty(i)) { - if (i.startsWith('health')) { - continue; - } - let value = regen[i]; - if (value) { - othersBox.append(`${idMap.regen[i][1]}${nToString(value)}${idMap.regen[i][0]}`); - } - } - } - for (let i in steal) { - if (steal.hasOwnProperty(i)) { - let value = steal[i]; - if (value) { - othersBox.append(`${idMap.steal[i][1]}${nToString(value)}${idMap.steal[i][0]}`); - } - } - } - let ordinals = ["1st", "2nd", "3rd", "4th"]; - let spellCostPct = build.identification.spellCost.percent; - let spellCostRaw = build.identification.spellCost.raw; - for (let i = 0; i < 4; i++) { - if (spellCostRaw[i]) { - othersBox.append(`${ordinals[i]} Spell Cost: ${nToString(spellCostRaw[i])}`); - } - if (spellCostPct[i]) { - othersBox.append(`${ordinals[i]} Spell Cost: ${nToString(spellCostPct[i])}%`); - } - } - for (let i in others) { - if (others.hasOwnProperty(i)) { - let value = others[i]; - if (value) { - othersBox.append(`${idMap.others[i][1]}${nToString(value)}${idMap.others[i][0]}`); - } - } - } - // get skills - let skills = $('input.sp_input').map((i, v) => 1*v.value).toArray(); - skillList.forEach((v, i) => { - skills[i] += realReq.bonus[v] || 0; - skills[i] = Math.max(0, Math.min(skills[i], 150)); - }); - let damagesBox = $('#build_dmg > .build_content'); - damagesBox.empty(); - if (build.items[8]) { - // calculate damages - // melee - let weaponDamage = JSON.parse(JSON.stringify(build.displayDamage)); - let {damage} = build.identification; - let meleeDamage = {normal: {}, critical: {}}; - for (let i in weaponDamage) { - if (!weaponDamage.hasOwnProperty(i)) continue; - meleeDamage.normal.neutral = build.displayDamage.neutral.map(x => x * (100 + skillBounsPct[skills[0]] + damage.meleePercent) / 100 + damage.meleeRaw).map(Math.floor); - meleeDamage.critical.neutral = build.displayDamage.neutral.map(x => x * (200 + skillBounsPct[skills[0]] + damage.meleePercent) / 100 + damage.meleeRaw).map(Math.floor); - meleeDamage.normal.total = [meleeDamage.normal.neutral[0], meleeDamage.normal.neutral[1]]; - meleeDamage.critical.total = [meleeDamage.critical.neutral[0], meleeDamage.critical.neutral[1]]; - elementList.forEach((elem, i) => { - meleeDamage.normal[elem] = build.displayDamage[elem].map(x => x * (100 + skillBounsPct[skills[0]] + skillBounsPct[skills[i]] + damage.meleePercent + damage[elem]) / 100).map(Math.floor); - meleeDamage.critical[elem] = build.displayDamage[elem].map(x => x * (200 + skillBounsPct[skills[0]] + skillBounsPct[skills[i]] + damage.meleePercent + damage[elem]) / 100).map(Math.floor); - meleeDamage.normal.total = sum(meleeDamage.normal.total, meleeDamage.normal[elem]); - meleeDamage.critical.total = sum(meleeDamage.critical.total, meleeDamage.critical[elem]); - }); - } - - // spell - let spells = []; - switch (build.items[8].info.type.toLowerCase()) { - case "spear": - // warrior - // bash - spells.push({spell: "Bash", subtitle: "First Explosion", damage: spellDamage(build, skills, 130, {neutral: 60, earth: 40, thunder: 0, water: 0, fire: 0, air: 0})}); - spells.push({spell: "Bash", subtitle: "Second Explosion", damage: spellDamage(build, skills, 130, {neutral: 100, earth: 0, thunder: 0, water: 0, fire: 0, air: 0})}); - spells.push({spell: "Bash", subtitle: "Total Damage", primary: true, damage: sum(spells[0].damage, spells[1].damage)}); - // charge - spells.push({spell: "Charge", subtitle: "", primary: true, damage: spellDamage(build, skills, 150, {neutral: 60, earth: 0, thunder: 0, water: 0, fire: 40, air: 0})}); - // uppercut - spells.push({spell: "Uppercut", subtitle: "First Damage", damage: spellDamage(build, skills, 300, {neutral: 85, earth: 15, thunder: 0, water: 0, fire: 0, air: 0})}); - spells.push({spell: "Uppercut", subtitle: "Fireworks Damage", damage: spellDamage(build, skills, 50, {neutral: 85, earth: 0, thunder: 15, water: 0, fire: 0, air: 0})}); - spells.push({spell: "Uppercut", subtitle: "Comet Damage", damage: spellDamage(build, skills, 50, {neutral: 100, earth: 0, thunder: 0, water: 0, fire: 0, air: 0})}); - spells.push({spell: "Uppercut", subtitle: "Total Damage", primary: true, damage: sum(sum(spells[4].damage, spells[5].damage), spells[6].damage)}); - // war scream - spells.push({spell: "War Scream", subtitle: "Per Hit", primary: true, damage: spellDamage(build, skills, 50, {neutral: 0, earth: 0, thunder: 0, water: 0, fire: 75, air: 25})}); - break; - case "bow": - // archer - // arrow storm - spells.push({spell: "Arrow Storm", subtitle: "Per Arrow", damage: spellDamage(build, skills, 10, {neutral: 60, earth: 0, thunder: 25, water: 0, fire: 15, air: 0})}); - spells.push({spell: "Arrow Storm", subtitle: "Per 60 Arrows", primary: true, damage: multiply(spells[0].damage, 60)}); - // escape - spells.push({spell: "Escape", subtitle: "", primary: true, damage: spellDamage(build, skills, 100, {neutral: 50, earth: 0, thunder: 0, water: 0, fire: 0, air: 50})}); - // bomb - spells.push({spell: "Bomb", subtitle: "", primary: true, damage: spellDamage(build, skills, 250, {neutral: 60, earth: 25, thunder: 0, water: 0, fire: 15, air: 0})}); - // arrow shield - spells.push({spell: "Arrow Shield", subtitle: "", primary: true, damage: spellDamage(build, skills, 100, {neutral: 70, earth: 0, thunder: 0, water: 30, fire: 0, air: 0})}); - spells.push({spell: "Arrow Shield", subtitle: "Arrow Rain Damage", damage: spellDamage(build, skills, 250, {neutral: 70, earth: 0, thunder: 0, water: 0, fire: 0, air: 30})}); - break; - case "wand": - // mage - // heal - spells.push({spell: "Heal", subtitle: "", primary: true, heal: [heal(build, 0.12), heal(build, 0.06), heal(build, 0.06)]}); - // teleport - spells.push({spell: "Teleport", subtitle: "", primary: true, damage: spellDamage(build, skills, 100, {neutral: 60, earth: 0, thunder: 40, water: 0, fire: 0, air: 0})}); - // meteor - spells.push({spell: "Meteor", subtitle: "Explosion Damage", primary: true, damage: spellDamage(build, skills, 500, {neutral: 40, earth: 30, thunder: 0, water: 0, fire: 30, air: 0})}); - spells.push({spell: "Meteor", subtitle: "Burning Damage", damage: spellDamage(build, skills, 125, {neutral: 100, earth: 0, thunder: 0, water: 0, fire: 0, air: 0})}); - // ice snake - spells.push({spell: "Ice Snake", subtitle: "", primary: true, damage: spellDamage(build, skills, 70, {neutral: 50, earth: 0, thunder: 0, water: 50, fire: 0, air: 0})}); - break; - case "dagger": - // assassin - // spin attack - spells.push({spell: "Spin Attack", subtitle: "", primary: true, damage: spellDamage(build, skills, 150, {neutral: 70, earth: 0, thunder: 30, water: 0, fire: 0, air: 0})}); - // multihit - spells.push({spell: "Multihit", subtitle: "First 10 Hits", damage: spellDamage(build, skills, 30, {neutral: 70, earth: 0, thunder: 30, water: 0, fire: 0, air: 0})}); - spells.push({spell: "Multihit", subtitle: "Last Hit", damage: spellDamage(build, skills, 30, {neutral: 40, earth: 0, thunder: 30, water: 30, fire: 0, air: 0})}); - spells.push({spell: "Multihit", subtitle: "Total Damage", primary: true, damage: sum(multiply(spells[1].damage, 10), spells[2].damage)}); - // smoke bomb - spells.push({spell: "Smoke Bomb", subtitle: "Total Damage", primary: true, damage: spellDamage(build, skills, 60, {neutral: 50, earth: 25, thunder: 0, water: 0, fire: 0, air: 25})}); - spells.push({spell: "Smoke Bomb", subtitle: "Per Second", damage: multiply(spells[4].damage, 5)}); - break; - case "relik": - // shaman - // totem - spells.push({spell: "Totem", subtitle: "Damage Per Second", primary: true, damage: spellDamage(build, skills, 20, {neutral: 60, earth: 0, thunder: 0, water: 0, fire: 20, air: 20})}); - spells.push({spell: "Totem", subtitle: "Heal Per Second", primary: true, heal: [heal(build, 0.04)]}); - spells.push({spell: "Totem", subtitle: "Landing Damage", damage: spellDamage(build, skills, 100, {neutral: 100, earth: 0, thunder: 0, water: 0, fire: 0, air: 0})}); - // haul - spells.push({spell: "Haul", subtitle: "", primary: true, damage: spellDamage(build, skills, 100, {neutral: 100, earth: 0, thunder: 0, water: 0, fire: 0, air: 0})}); - // explosive blender - spells.push({spell: "Aura", subtitle: "Center Damage", primary: true, damage: spellDamage(build, skills, 200, {neutral: 70, earth: 0, thunder: 0, water: 30, fire: 0, air: 0})}); - // uproot - spells.push({spell: "Uproot", subtitle: "", primary: true, damage: spellDamage(build, skills, 50, {neutral: 70, earth: 30, thunder: 0, water: 0, fire: 0, air: 0})}); - } - spells.sort((a,b) => (b.primary||0)-(a.primary||0)); - let div = $('
'); - div.append(`

Melee Damage

`); - let {normal, critical} = meleeDamage; - let normalAvg = (normal.total[0] + normal.total[1]) / 2; - let criticalAvg = (critical.total[0] + critical.total[1]) / 2; - let critChance = skillBounsPct[skills[1]]; - let avg = Math.round(normalAvg * (1 - critChance / 100) + criticalAvg * critChance / 100); - let attackSpeeds = ['SUPER_SLOW', 'VERY_SLOW', 'SLOW', 'NORMAL', 'FAST', 'VERY_FAST', 'SUPER_FAST']; - let hitsPerSec = [0.51, 0.83, 1.5, 2.05, 2.5, 3.4, 4.3]; - let speedTier = Math.min(6, Math.max(0, attackSpeeds.indexOf(build.items[8].base.attackSpeed) + build.identification.others.attackSpeedBonus)); - div.append(`${capitalize(attackSpeeds[speedTier])} Attack Speed`); - div.append(`DPS: ${Math.floor(avg * hitsPerSec[speedTier] + build.identification.others.poison * (1 + skillBounsPct[skills[0]] / 100) / 3)}`); - div.append(`Average Damage: ${avg}`); - let leftDamageBox = $(`
Normal Damage
Total: ${normal.total[0]} - ${normal.total[1]} (${Math.round(normalAvg)})
✤ ${normal.neutral[0]} - ${normal.neutral[1]}
`); - let rightDamageBox = $(`
Critical Damage
Total: ${critical.total[0]} - ${critical.total[1]} (${Math.round(criticalAvg)})
✤ ${critical.neutral[0]} - ${critical.neutral[1]}
`); - elementList.forEach(elem => { - if (normal[elem][1]) { - leftDamageBox.append(`${elemIcons[elem]} ${normal[elem][0]} - ${normal[elem][1]}
`); - rightDamageBox.append(`${elemIcons[elem]} ${critical[elem][0]} - ${critical[elem][1]}
`); - } - }); - div.append(leftDamageBox); - div.append(rightDamageBox); - damagesBox.append(div); - spells.forEach(spell => { - if (spell.damage) { - let div = $('
'); - div.append(`

${spell.spell}

`); - let {normal, critical} = spell.damage; - let normalAvg = (normal.total[0] + normal.total[1]) / 2; - let criticalAvg = (critical.total[0] + critical.total[1]) / 2; - let critChance = skillBounsPct[skills[1]]; - let avg = Math.round(normalAvg * (1 - critChance / 100) + criticalAvg * critChance / 100); - if (spell.subtitle.length) { - div.append(`${spell.subtitle}`); - } - div.append(`Average Damage: ${avg}`); - let leftDamageBox = $(`
Normal Damage
Total: ${normal.total[0]} - ${normal.total[1]} (${Math.round(normalAvg)})
✤ ${normal.neutral[0]} - ${normal.neutral[1]}
`); - let rightDamageBox = $(`
Critical Damage
Total: ${critical.total[0]} - ${critical.total[1]} (${Math.round(criticalAvg)})
✤ ${critical.neutral[0]} - ${critical.neutral[1]}
`); - elementList.forEach(elem => { - if (normal[elem][1]) { - leftDamageBox.append(`${elemIcons[elem]} ${normal[elem][0]} - ${normal[elem][1]}
`); - rightDamageBox.append(`${elemIcons[elem]} ${critical[elem][0]} - ${critical[elem][1]}
`); - } - }); - div.append(leftDamageBox); - div.append(rightDamageBox); - damagesBox.append(div); - } - if (spell.heal) { - let div = $('
'); - let {heal} = spell; - div.append(`

${spell.spell}

`); - if (spell.subtitle.length) { - div.append(`${spell.subtitle}`); - } - switch (spell.spell) { - case "Heal": - div.append(`Total Heal: ${heal[0] + heal[1] + heal[2]}`); - for (let i = 0; i < 3; i++) { - div.append(`${['1st', '2nd', '3rd'][i]} Pulse: ${heal[i]}`); - } - case "Totem": - div.append(`Heal: ${heal[0]}`); - break; - } - damagesBox.append(div); - } - }); - } else { - // no weapon to calculate damages - damagesBox.html('No weapon to selected.'); - } - } - - function heal(build, ratio) { - return Math.floor(build.base.health * ratio * (1 + build.identification.damage.water / 200)); - } - - function generateItemBox(item, floatLeft) { - let itemBox; - let newLine = false; - if (floatLeft) { - itemBox = $(`
`); - } else { - itemBox = $(`
`); - } - // 1. name coloured according to tier - itemBox.append(`${item.displayName || item.info.name}
`); - // 2. weapon attack speed - if (item.category === "weapon") { - itemBox.append(`${capitalize(item.base.attackSpeed)} Attack Speed
`); - } - itemBox.append("
"); - if (item.category !== "weapon") { - // 3. armour health - if (item.base.health) { - itemBox.append(`❤ Health: ${nToString(item.base.health)}
`); - newLine = true; - } - // 4. armour defenses - elementList.forEach(elem => { - let value = item.base.defense[elem]; - if (value) { - itemBox.append(`${elemIcons[elem]} ${capitalize(elem)} Defense: ${nToString(value)}
`); - newLine = true; - } - }); - } else { - // 5. weapon damage - if (item.displayDamage.neutral[1] !== 0) { - itemBox.append(`${elemIcons.earth} Neutral Damage: ${item.displayDamage.neutral[0]}-${item.displayDamage.neutral[1]}
`); - } - elementList.forEach(elem => { - let value = item.displayDamage[elem]; - if (value[1] !== 0) { - itemBox.append(`${elemIcons[elem]} ${capitalize(elem)} Damage: ${value[0]}-${value[1]}
`); - newLine = true; - } - }); - } - // 6. powder special - if (item.category !== "accessory"){ - let powders; - if (item.category === "weapon") { - powders = powderList.weapon; - } else { - powders = powderList[item.info.type.toLowerCase()]; - } - let count = {E: 0, T: 0, W: 0, F: 0, A: 0}; - let sum = {E: 0, T: 0, W: 0, F: 0, A: 0}; - let specialElem; - let specialTier = 0; - for (const powder of powders) { - if (!powder) { - continue; - } - let elem = powder[0]; - let tier = powder[1]; - if (tier < 4) { - continue; - } - ++count[elem]; - sum[elem] += 1*tier; - if (count[elem] == 2) { - specialElem = elem; - specialTier = sum[elem]; - break; - } - } - if (specialTier) { - if (item.category === "weapon") { - // weapon specials - switch (specialElem) { - case 'E': - itemBox.append(`  Quake
`); - itemBox.append(`  - Radius: ${specialTier / 2 + 1} blocks
`); - itemBox.append(`  - Damage: ${specialTier * 65 - 365}% ${elemIcons.earth}
`); - break; - case 'T': - itemBox.append(`  Chained Lightning
`); - itemBox.append(`  - Chains: ${specialTier - 3}
`); - itemBox.append(`  - Damage: ${specialTier * 40 - 240}% ${elemIcons.thunder}
`); - break; - case 'W': - itemBox.append(`  Curse
`); - itemBox.append(`  - Duration: ${specialTier - 3} seconds
`); - itemBox.append(`  - Damage Boost: +${specialTier * 30 - 150}%
`); - break; - case 'F': - itemBox.append(`  Courage
`); - itemBox.append(`  - Duration: ${specialTier / 2 + 2} seconds
`); - itemBox.append(`  - Damage: ${specialTier * 12.5 - 25}% ${elemIcons.fire}
`); - itemBox.append(`  - Damage Boost: +${specialTier * 20 - 90}%
`); - break; - case 'A': - itemBox.append(`  Wind Prison
`); - itemBox.append(`  - Duration: ${specialTier / 2 - 1} seconds
`); - itemBox.append(`  - Damage Boost: +${specialTier * 100 - 600}%
`); - itemBox.append(`  - Knockback: ${specialTier * 4 - 24} blocks
`); - break; - } - } else { - // armour specials - switch (specialElem) { - case 'E': - itemBox.append(`  Rage [% ❤ Missing]
`); - itemBox.append(`  - Damage: +${(specialTier - 4 + (specialTier == 12 ? 2 : 0)) / 10}% ${elemIcons.earth}
`); - break; - case 'T': - itemBox.append(`  Kill Streak [Mob Killed]
`); - itemBox.append(`  - Damage: +${specialTier * 1.5 - 9}% ${elemIcons.thunder}
`); - itemBox.append(`  - Duration: 5 seconds
`); - break; - case 'W': - itemBox.append(`  Concentration [Mana Used]
`); - itemBox.append(`  - Damage: ${specialTier - 7}% ${elemIcons.water} / Mana
`); - itemBox.append(`  - Duration: 1 Sec. / Mana
`); - break; - case 'F': - itemBox.append(`  Endurance [Hit Taken]
`); - itemBox.append(`  - Damage: ${specialTier - 6}% ${elemIcons.fire}
`); - itemBox.append(`  - Duration: 8 seconds
`); - break; - case 'A': - itemBox.append(`  Dodge [Near Mobs]
`); - itemBox.append(`  - Damage: ${specialTier - 6}% ${elemIcons.air}
`); - itemBox.append(`  - Duration: 6 seconds
`); - break; - } - } - } - } - if (newLine) { - itemBox.append("
"); - newLine = false; - } - // 7. requirements - // 7.1 quest req - if (item.req.quest) { - itemBox.append(`Quest Req: ${item.req.quest}
`); - newLine = true; - } - // 7.2 class req - if (item.req.class) { - itemBox.append(`Class Req: ${item.req.class}
`); - newLine = true; - } - // 7.3 combat lvl req - if (item.req.level) { - itemBox.append(`Combat Lv. Min: ${item.req.level}
`); - newLine = true; - } - // 7.4 skill req - skillList.forEach(skill => { - let value = item.req[skill]; - if (value) { - itemBox.append(`${capitalize(skill)} Min: ${value}
`); - newLine = true; - } - }); - if (newLine) { - itemBox.append("
"); - newLine = false; - } - // 8. bonus skills - skillList.forEach(skill => { - let value = item.base.skill[skill]; - if (value) { - itemBox.append(`${nToString(value)} ${capitalize(skill)}
`); - newLine = true; - } - }); - if (newLine) { - itemBox.append("
"); - newLine = false; - } - // 9. ids - { - // damage - { - elementList.forEach(elem => { - let value = item.identification.damage[elem]; - if (value) { - itemBox.append(`${nToString(value)}% ${capitalize(elem)} Damage
`); - newLine = true; - } - }); - for (let i in idMap.damage) { - if (idMap.damage.hasOwnProperty(i)) { - let value = item.identification.damage[i]; - let line = idMap.damage[i]; - if (value) { - itemBox.append(`${nToString(value)}${line[0]} ${line[1]}
`); - } - } - } - } - // defenses - { - elementList.forEach(elem => { - let value = item.identification.defense[elem]; - if (value) { - itemBox.append(`${nToString(value)}% ${capitalize(elem)} Defense
`); - newLine = true; - } - }); - } - // regen - { - for (let i in idMap.regen) { - if (idMap.regen.hasOwnProperty(i)) { - let value = item.identification.regen[i]; - let line = idMap.regen[i]; - if (value) { - itemBox.append(`${nToString(value)}${line[0]} ${line[1]}
`); - } - } - } - } - // spell cost - { - let ordinals = ["1st", "2nd", "3rd", "4th"]; - let spellCostPct = item.identification.spellCost.percent; - let spellCostRaw = item.identification.spellCost.raw; - for (let i = 0; i < 4; i++) { - if (spellCostRaw[i]) { - itemBox.append(`${nToString(spellCostRaw[i])} ${ordinals[i]} Spell Cost
`); - newLine = true; - } - if (spellCostPct[i]) { - itemBox.append(`${nToString(spellCostPct[i])}% ${ordinals[i]} Spell Cost
`); - newLine = true; - } - } - } - // steal - { - for (let i in idMap.steal) { - if (idMap.steal.hasOwnProperty(i)) { - let value = item.identification.steal[i]; - let line = idMap.steal[i]; - if (value) { - itemBox.append(`${nToString(value)}${line[0]} ${line[1]}
`); - newLine = true; - } - } - } - } - // others - { - for (let i in idMap.others) { - if (idMap.others.hasOwnProperty(i)) { - let value = item.identification.others[i]; - let line = idMap.others[i]; - if (value) { - itemBox.append(`${nToString(value)}${line[0]} ${line[1]}
`); - newLine = true; - } - } - } - } - } - if (newLine) { - itemBox.append("
"); - newLine = false; - } - // 10. powder slots - { - let type = (item.info.type || item.accessoryType).toLowerCase(); - let {sockets} = item.info; - if (type == "relik" || type == "wand" || type == "bow" || type == "spear" || type == "dagger") { - type = "weapon"; - } - let powderArray = (powderList[type] || []).filter(x => !!x); - let powderCount = Math.min(powderArray.length, sockets || 0); - if (sockets) { - if (!powderCount) { - itemBox.append(`[0/${sockets}] Powder Slots
`); - } else { - let powderString = ""; - let count = Math.min(powderArray.length, item.info.sockets); - for (let i = 0; i < count; i++) { - let elem = powderStats[powderArray[i][0].toUpperCase()][0][0]; - powderString += `${elemIcons[elem]}`; - } - itemBox.append(`[${powderCount}/${sockets}] Powder Slots [${powderString}]
`); - } - } - } - // 11. tier - itemBox.append(`${item.info.tier} Item
`); - // 12. restrictions - if (item.restrictions) { - itemBox.append(`${item.restrictions} Item
`); - } - // 13. lore - if (item.info.lore) { - itemBox.append(`${item.info.lore}
`); - } - return itemBox; - } - - function capitalize(s) { - return s.split("_").map(x => x.substr(0, 1).toUpperCase() + x.substr(1).toLowerCase()).join(" "); - } - - function resetPos() { - const CONTAINER = "#item_list_box"; - const SELECTOR = ".item.float_left"; - const COLUMN_WIDTH = 250; - const VERTICAL_MARGIN = 40; - const HORIZONTAL_MARGIN = 24; - - let container = $(CONTAINER); - let columns = Math.floor(container.width() / (HORIZONTAL_MARGIN + COLUMN_WIDTH)); - let height_occupied = []; - let elems = container.children(SELECTOR); - container.css("position", "relative"); - for (let i = 0; i < columns; i++) { - height_occupied.push(0); - } - for (let i = 0; i < elems.length; i++) { - // find the col with least occupied height - let idx = 0; - let min = height_occupied[0]; - for (let j = 1; j < columns; j++) { - if (height_occupied[j] < min) { - min = height_occupied[j]; - idx = j; - } - } - let $elem = $(elems[i]); - $elem.css("position", "absolute").css("top", min).css("left", COLUMN_WIDTH * idx + HORIZONTAL_MARGIN * idx); - height_occupied[idx] += $elem.height() + VERTICAL_MARGIN; - } - let h = Math.max.apply(this, height_occupied); - container.height(h); - } - - function nToString(n) { - return (n >= 0 ? "+" : "") + n; - } - - function findStatReq(items) { - items = items.slice(0, 8); // remove weapon since it always come last - let combinations = []; - for (let i = 0; i < 256; i++) { - let buildItems = [null, null, null, null, null, null, null, null, null]; - for (let j = 0; j < 8; j++) { - if (items[j] && (i & (1 << j))) { - buildItems[j] = {name: items[j].displayName || items[j].info.name}; - } - } - combinations.push(calculateBuild(buildItems)); - } - let currentOrder = [0, 1, 2, 3, 4, 5, 6, 7]; - currentOrder = currentOrder.filter(i => items[i]); // so that we don"t consider empty slots - // Absolute minimum, even if it means that you need to allocate 200 points in one skill - let currentMin; - let currentMinSum = 694201337; - // The minimum that is actually valid (<=200 in total, <=100 in any skill) - let currentValidMin; - let currentValidMinSum = 694201337; - do { - let bitSet = 0; - let currentReq = { - req: { - strength: 0, - dexterity: 0, - intelligence: 0, - defense: 0, - agility: 0 - }, - bonus: { - strength: 0, - dexterity: 0, - intelligence: 0, - defense: 0, - agility: 0 - } - }; - let sum = 0; - let valid = true; - for (let i = 0; i < 8; i++) { - bitSet |= 1 << currentOrder[i]; - let build = combinations[bitSet]; - let stageReq = build.req; - skillList.forEach(skill => { - let ownedPoints = currentReq.req[skill] + currentReq.bonus[skill]; - let diff; - if (!stageReq[skill]) { - diff = 0; - } else if (stageReq[skill] > ownedPoints) { - diff = stageReq[skill] - ownedPoints; - } else { - diff = 0; - } - currentReq.req[skill] += diff; - currentReq.bonus[skill] = build.base.skill[skill]; - sum += diff; - valid = sum <= 200 && currentReq[skill] <= 100; - }); - } - if (sum < currentMinSum) { - currentReq.order = currentOrder.slice(0); - currentMinSum = sum; - currentMin = currentReq; - if (valid) { - currentValidMinSum = sum; - currentValidMin = currentReq; - } - } - } while (nextPermutation(currentOrder)); - return currentValidMin || currentMin; - } - - // overrides source with data, overriding an object with a primitive won"t work - function override(source, data) { - if (data === undefined) { - return source; - } - for (let i in data) { - if (data.hasOwnProperty(i)) { - let obj = source[i]; - if (typeof obj === "object") { - override(obj, data[i]); - } else { - if (data[i] !== undefined) { - source[i] = data[i]; - } - } - } - } - } - - // similar to override(), but adds instead of assigns, and clones the source - function sum(left, right) { - return combine(left, right, (x, y) => { - if (Array.isArray(x)) { - return x.concat(y); - } - if (x !== undefined) { - return x + y; - } - return y; - - }); - } - - function multiply(obj, val) { - let result = {}; - for (let i in obj) { - if (obj.hasOwnProperty(i)) { - if ("object" !== typeof obj[i]) { - result[i] = val * obj[i]; - } else { - result[i] = multiply(obj[i], val); - } - } - } - return result; - } - - function max(left, right) { - return combine(left, right, (x, y) => { - if (Array.isArray(x)) { - return x.concat(y); - } - if (x !== undefined) { - return Math.max(x, y); - } else { - return y; - } - }); - } - - // similar to override(), but adds instead of assigns, and clones the source - function combine(left, right, combiner) { - if (right === undefined || right === null) { - return left; - } - if (left === undefined || left === null) { - return right; - } - let result = JSON.parse(JSON.stringify(left)); - if (typeof right === "string") { - return combiner(left, right); - } - for (let i in right) { - if (right.hasOwnProperty(i)) { - if (typeof left[i] === "object") { - result[i] = combine(left[i], right[i], combiner); - } else { - result[i] = combiner(left[i], right[i]); - } - } - } - return result; - } - - function nextPermutation(array) { - /* The algorithm: find the longest decreasing subsequence from the end, since we don"t have a larger permutation - for such a sequence, then find the smallest number that is larger than the one before the decreasing - subsequence, and swap the smallest with the one before. That way we increment the whole sequence the least. - However this way the decreasing subsequence that we"ve found is still decreasing, so we reverse it to become an - increasing one, just like 39+1=40, the last digit is reset from the highest possible value to the lowest. - 6 8 7 4 3 [(5) 2 1] -> 6 8 7 4 (5) [3 2 1] -> 6 8 7 4 5 1 2 3 - */ - // The last index of the array, our starting point. - let i = array.length - 1; - let last = i; - // `i--` returns the value before the decrement, so it is one larger than the decremented `i`. - // So this expands to `while (i--, array[i + 1] < array[i]);`, which continues iff the next item is is smaller - // than the last item i.e. true if the pair is decreasing. - // When `i` reaches -1, it"s comparing undefined and a number, which gives false. - // When this completes, `i` is pointing at the index right before the longest decreasing subsequence from the end. - while (array[i--] < array[i]); - // if `i` is -1, the whole sequence is decreasing. There"s no next permutation. - if (!(i+1)) return false; - // This is the number to be swapped out, 3 in the example. We are going to find a number in the subsequence that - // is slightly larger than this. - let n = array[i]; - // This is the starting point of our search for the slightly larger number. - let m = array[i+1]; - let mi = i+1; - for (let j = last; j > i; j--) { - let k = array[j]; - // This statements checks if the current item is larger than the number to be swapped out and smaller than - // the existing candidate because we want it to be as small as possible for minimum increment. - if (k > n && k < m) { - m = k; - mi = j; - } - } - // Usual swapping. - array[mi] = array[i]; - array[i] = m; - // Recall that `i` is pointing at the index right before the longest decreasing subsequence from the end. Plus - // one and it's the start of the decreasing sequence. Remove the subsequence from the array by using .splice(), - // reverse it and push it back to the array. - // [6 8 7 4 5 3 2 1] -> [6 8 7 4 5] [3 2 1] -> [6 8 7 4 5] [1 2 3] -> [6 8 7 4 5 1 2 3] - array.push(...array.splice(i+1).reverse()); - return true; - } - - window.calculateBuild = calculateBuild; - window.findStatReq = findStatReq; -}); \ No newline at end of file