farming-weight
Version:
Tools for calculating farming weight and fortune in Hypixel Skyblock
375 lines • 13.9 kB
JavaScript
import { FARMING_ENCHANTS } from '../constants/enchants.js';
import { ReforgeTarget } from '../constants/reforges.js';
import { Skill } from '../constants/skills.js';
import { MATCHING_SPECIAL_CROP } from '../constants/specialcrops.js';
import { Stat } from '../constants/stats.js';
import { calculateAverageSpecialCrops } from '../crops/special.js';
import { ARMOR_INFO, ARMOR_SET_BONUS, GEAR_SLOTS, GearSlot } from '../items/armor.js';
import { EQUIPMENT_INFO } from '../items/equipment.js';
import { ARMOR_SET_FORTUNE_SOURCES } from '../upgrades/sources/armorsetsources.js';
import { GEAR_FORTUNE_SOURCES } from '../upgrades/sources/gearsources.js';
import { getItemUpgrades, getLastItemUpgradeableTo, getSourceProgress } from '../upgrades/upgrades.js';
import { getFortuneFromEnchant } from '../util/enchants.js';
import { getPeridotFortune } from '../util/gems.js';
import { FarmingEquipment } from './farmingequipment.js';
import { UpgradeableBase } from './upgradeable.js';
export class ArmorSet {
get armor() {
return [this.helmet ?? null, this.chestplate ?? null, this.leggings ?? null, this.boots ?? null];
}
get equipment() {
return [this.necklace ?? null, this.cloak ?? null, this.belt ?? null, this.gloves ?? null];
}
get fortune() {
return this.armorFortune + this.equipmentFortune;
}
constructor(armor, equipment, options) {
this.setBonuses = [];
this.equipmentSetBonuses = [];
if (options) {
for (const piece of armor) {
piece.setOptions(options);
}
for (const piece of equipment ?? []) {
piece.setOptions(options);
}
}
this.setArmor(armor);
if (equipment) {
this.setEquipment(equipment);
}
else {
this.recalculateFamilies();
}
}
setArmor(armor) {
armor.sort((a, b) => b.potential - a.potential);
this.pieces = armor;
this.helmet = armor.find((a) => a.slot === GearSlot.Helmet);
this.chestplate = armor.find((a) => a.slot === GearSlot.Chestplate);
this.leggings = armor.find((a) => a.slot === GearSlot.Leggings);
this.boots = armor.find((a) => a.slot === GearSlot.Boots);
this.recalculateFamilies();
}
setEquipment(equipment) {
equipment.sort((a, b) => b.fortune - a.fortune);
this.equipmentPieces = equipment;
this.necklace = equipment.find((a) => a.slot === GearSlot.Necklace);
this.cloak = equipment.find((a) => a.slot === GearSlot.Cloak);
this.belt = equipment.find((a) => a.slot === GearSlot.Belt);
this.gloves = equipment.find((a) => a.slot === GearSlot.Gloves);
this.recalculateFamilies();
}
setOptions(options) {
for (const piece of this.pieces) {
piece.setOptions(options);
}
for (const piece of this.equipmentPieces) {
piece.setOptions(options);
}
this.setArmor(this.pieces);
this.setEquipment(this.equipmentPieces);
}
getPiece(slot) {
switch (slot) {
case GearSlot.Helmet:
return this.helmet;
case GearSlot.Chestplate:
return this.chestplate;
case GearSlot.Leggings:
return this.leggings;
case GearSlot.Boots:
return this.boots;
case GearSlot.Necklace:
return this.necklace;
case GearSlot.Cloak:
return this.cloak;
case GearSlot.Belt:
return this.belt;
case GearSlot.Gloves:
return this.gloves;
default:
return;
}
}
setPiece(armor) {
if (armor instanceof FarmingArmor) {
switch (armor.slot) {
case GearSlot.Helmet:
this.helmet = armor;
break;
case GearSlot.Chestplate:
this.chestplate = armor;
break;
case GearSlot.Leggings:
this.leggings = armor;
break;
case GearSlot.Boots:
this.boots = armor;
break;
}
}
else if (armor instanceof FarmingEquipment) {
switch (armor.slot) {
case GearSlot.Necklace:
this.necklace = armor;
break;
case GearSlot.Cloak:
this.cloak = armor;
break;
case GearSlot.Belt:
this.belt = armor;
break;
case GearSlot.Gloves:
this.gloves = armor;
break;
}
}
this.getFortuneBreakdown(true);
}
recalculateFamilies() {
this.setBonuses = ArmorSet.getSetBonusFrom(this.armor ?? []);
this.equipmentSetBonuses = ArmorSet.getSetBonusFrom(this.equipment ?? []);
this.getFortuneBreakdown();
}
static getSetBonusFrom(armor) {
const families = new Map();
const result = [];
for (const piece of armor) {
if (!piece?.info.family)
continue;
families.set(piece.info.family, (families.get(piece.info.family) ?? 0) + 1);
}
for (const [family, count] of families.entries()) {
if (count < 2)
continue;
const bonus = ARMOR_SET_BONUS[family];
if (!bonus)
continue;
result.push({
count: count,
from: armor.filter((a) => a?.info.family === family).map((a) => a?.slot),
bonus: bonus,
special: bonus.special,
});
}
return result;
}
getFortuneBreakdown(reloadFamilies = false) {
if (reloadFamilies) {
this.recalculateFamilies();
}
let sum = 0;
const breakdown = {};
// Armor fortune
for (const piece of this.armor) {
if (!piece)
continue;
const fortune = piece.fortune;
if (fortune > 0) {
breakdown[piece.item.name ?? ''] = fortune;
sum += fortune;
}
}
// Armor set bonuses
for (const { bonus, count } of this.setBonuses) {
if (count < 2 || count > 4)
continue;
const fortune = bonus.stats?.[count]?.[Stat.FarmingFortune] ?? 0;
if (fortune > 0) {
breakdown[bonus.name] = fortune;
sum += fortune;
}
}
this.armorFortune = sum;
// Equipment fortune
let equipmentSum = 0;
for (const piece of this.equipment) {
if (!piece)
continue;
const fortune = piece.fortune;
if (fortune > 0) {
breakdown[piece.item.name ?? ''] = fortune;
equipmentSum += fortune;
}
}
// Equipment set bonuses
for (const { bonus, count } of this.equipmentSetBonuses) {
if (count < 2 || count > 4)
continue;
const fortune = bonus.stats?.[count]?.[Stat.FarmingFortune] ?? 0;
if (fortune > 0) {
breakdown[bonus.name] = fortune;
equipmentSum += fortune;
}
}
this.equipmentFortune = equipmentSum;
return breakdown;
}
specialDropsCalc(blocksBroken, crop) {
const count = this.specialDropsCount(crop);
if (count === 0)
return null;
return calculateAverageSpecialCrops(blocksBroken, crop, count);
}
specialDropsCount(crop) {
const special = MATCHING_SPECIAL_CROP[crop];
const applicableBonuses = this.setBonuses.filter((b) => b.special?.includes(special));
if (applicableBonuses.length === 0)
return 0;
// Armor set counts need to be combined for special crops
// There will only be 2 applicable bonuses at most when Fermento armor plus
// a lower tier armor is used. Hypixel appears to count these as the same
// set bonus instead of rolling them separately.
let count = 0;
for (const bonus of applicableBonuses) {
count += bonus.count;
}
return count;
}
getProgress(zeroed = false) {
return getSourceProgress(this, ARMOR_SET_FORTUNE_SOURCES, zeroed);
}
getPieceProgress(slot) {
let piece = this.getPiece(slot);
if (!piece) {
piece =
GEAR_SLOTS[slot].target === ReforgeTarget.Armor
? FarmingArmor.fakeItem(ARMOR_INFO[GEAR_SLOTS[slot].startingItem])
: FarmingEquipment.fakeItem(EQUIPMENT_INFO[GEAR_SLOTS[slot].startingItem]);
return piece?.getProgress(true) ?? [];
}
return piece.getProgress() ?? [];
}
get slots() {
return {
[GearSlot.Helmet]: this.helmet,
[GearSlot.Chestplate]: this.chestplate,
[GearSlot.Leggings]: this.leggings,
[GearSlot.Boots]: this.boots,
[GearSlot.Necklace]: this.necklace,
[GearSlot.Cloak]: this.cloak,
[GearSlot.Belt]: this.belt,
[GearSlot.Gloves]: this.gloves,
};
}
get slotOptions() {
return {
[GearSlot.Helmet]: this.pieces.filter((a) => a.slot === GearSlot.Helmet),
[GearSlot.Chestplate]: this.pieces.filter((a) => a.slot === GearSlot.Chestplate),
[GearSlot.Leggings]: this.pieces.filter((a) => a.slot === GearSlot.Leggings),
[GearSlot.Boots]: this.pieces.filter((a) => a.slot === GearSlot.Boots),
[GearSlot.Necklace]: this.equipmentPieces.filter((a) => a.slot === GearSlot.Necklace),
[GearSlot.Cloak]: this.equipmentPieces.filter((a) => a.slot === GearSlot.Cloak),
[GearSlot.Belt]: this.equipmentPieces.filter((a) => a.slot === GearSlot.Belt),
[GearSlot.Gloves]: this.equipmentPieces.filter((a) => a.slot === GearSlot.Gloves),
};
}
}
export class FarmingArmor extends UpgradeableBase {
get type() {
return ReforgeTarget.Armor;
}
// Backwards compatibility
get armor() {
return this.info;
}
get slot() {
return this.info.slot;
}
get potential() {
if (!this.info.family) {
return this.fortune;
}
// Add the set bonus potential to the fortune
return this.fortune + (ARMOR_SET_BONUS[this.info.family]?.piecePotential?.[Stat.FarmingFortune] ?? 0);
}
constructor(item, options) {
super({ item, options, items: ARMOR_INFO });
this.getFortune();
}
setOptions(options) {
this.options = options;
this.fortune = this.getFortune();
}
getFortune() {
this.fortuneBreakdown = {};
let sum = 0;
// Base fortune
const base = this.info.baseStats?.[Stat.FarmingFortune] ?? 0;
if (base > 0) {
this.fortuneBreakdown['Base Stats'] = base;
sum += base;
}
// Per farming level stats like Rancher's Boots
if (this.info.perLevelStats?.skill === Skill.Farming && this.options?.farmingLevel) {
const perLevel = this.info.perLevelStats?.stats[Stat.FarmingFortune] ?? 0;
if (perLevel > 0) {
this.fortuneBreakdown['Farming Level'] = perLevel * this.options.farmingLevel;
sum += perLevel * this.options.farmingLevel;
}
}
// Reforge stats
const reforge = this.reforgeStats?.stats?.[Stat.FarmingFortune] ?? 0;
if (reforge > 0) {
this.fortuneBreakdown[this.reforge?.name ?? 'Reforge'] = reforge;
sum += reforge;
}
// Gems
const peridot = getPeridotFortune(this.rarity, this.item);
if (peridot > 0) {
this.fortuneBreakdown['Peridot Gems'] = peridot;
sum += peridot;
}
// Enchantments
const enchantments = Object.entries(this.item.enchantments ?? {});
for (const [enchant, level] of enchantments) {
if (!level)
continue;
const enchantment = FARMING_ENCHANTS[enchant];
if (!enchantment || !level || enchantment.cropSpecific)
continue;
const fortune = getFortuneFromEnchant(level, enchantment, this.options);
if (fortune > 0) {
this.fortuneBreakdown[enchantment.name] = fortune;
sum += fortune;
}
}
this.fortune = sum;
return sum;
}
getUpgrades() {
return getItemUpgrades(this);
}
getItemUpgrade() {
return this.info.upgrade;
}
getLastItemUpgrade() {
return getLastItemUpgradeableTo(this, ARMOR_INFO);
}
getProgress(zeroed = false) {
return getSourceProgress(this, GEAR_FORTUNE_SOURCES, zeroed);
}
static isValid(item) {
return ARMOR_INFO[item.skyblockId] !== undefined;
}
static fromArray(items, options) {
return items
.filter((item) => FarmingArmor.isValid(item))
.map((item) => new FarmingArmor(item, options))
.sort((a, b) => b.fortune - a.fortune);
}
static fakeItem(info, options) {
const fake = {
name: 'Fake Item',
skyblockId: info.skyblockId,
lore: [],
attributes: {},
enchantments: {},
};
if (!FarmingArmor.isValid(fake))
return undefined;
return new FarmingArmor(fake, options);
}
}
//# sourceMappingURL=farmingarmor.js.map