");
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(`
`);
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;
});