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(`${name} `);
- });
- 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