UNPKG

farming-weight

Version:

Tools for calculating farming weight and fortune in Hypixel Skyblock

375 lines 13.9 kB
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