UNPKG

farming-weight

Version:

Tools for calculating farming weight and fortune in Hypixel Skyblock

380 lines 18.4 kB
import { CROP_INFO } from '../../constants/crops.js'; import { FARMING_ENCHANTS } from '../../constants/enchants.js'; import { Rarity, REFORGES, ReforgeTarget } from '../../constants/reforges.js'; import { Stat } from '../../constants/stats.js'; import { UpgradeAction, UpgradeCategory } from '../../constants/upgrades.js'; import { GemRarity } from '../../fortune/item.js'; import { getMaxStatFromEnchant, getStatFromEnchant } from '../../util/enchants.js'; import { getPeridotFortune, getPeridotGemFortune } from '../../util/gems.js'; import { getUpgradeableEnchant } from '../enchantupgrades.js'; import { getCurrentReforgeEffectSummaries, getReforgeEffectSummaries, getUpgradeableGems } from '../upgrades.js'; const CROP_FORTUNE_STATS = new Set(Object.values(CROP_INFO).map((c) => c.fortuneType)); export const TOOL_FORTUNE_SOURCES = [ { name: 'Tool Level', exists: () => true, max: () => 50 * 4, current: (tool) => tool.level * 4, maxStat: (tool, stat) => { for (const crop of tool.crops) { if (stat === CROP_INFO[crop]?.fortuneType) { return 50 * 4; } } return 0; }, currentStat: (tool, stat) => { for (const crop of tool.crops) { if (stat === CROP_INFO[crop]?.fortuneType) { return tool.level * 4; } } return 0; }, info: (tool) => ({ item: tool.item, info: tool.info, nextInfo: tool.getNextItemUpgrade()?.info, maxInfo: tool.getLastItemUpgrade()?.info, }), }, { name: 'Reforge Stats', wiki: () => REFORGES?.bountiful?.wiki, exists: (tool) => tool.type !== ReforgeTarget.Sword, max: (tool) => { const last = (tool.getLastItemUpgrade() ?? tool)?.info; return tool.reforge?.name === 'Bountiful' ? (REFORGES.bountiful?.tiers[last.maxRarity]?.stats[Stat.FarmingFortune] ?? 0) : (REFORGES.blessed?.tiers?.[last.maxRarity]?.stats[Stat.FarmingFortune] ?? 0); }, current: (tool) => { return tool.reforgeStats?.stats?.[Stat.FarmingFortune] ?? 0; }, maxStat: (tool, stat) => { const last = (tool.getLastItemUpgrade() ?? tool)?.info; const current = tool.reforgeStats?.stats?.[stat] ?? 0; let best = 0; for (const reforge of Object.values(REFORGES)) { if (!reforge || !tool.type || !reforge.appliesTo.includes(tool.type)) continue; if (!reforge.stone?.id) continue; const value = reforge.tiers[last.maxRarity]?.stats?.[stat] ?? 0; if (value > best) best = value; } return Math.max(best, current); }, currentStat: (tool, stat) => tool.reforgeStats?.stats?.[stat] ?? 0, effects: getCurrentReforgeEffectSummaries, upgrades: (tool, stats) => { const requestedStats = stats && stats.length > 0 ? stats : [Stat.FarmingFortune]; const primaryStat = requestedStats[0] ?? Stat.FarmingFortune; const currentStats = tool.reforgeStats?.stats ?? {}; const currentPrimary = currentStats?.[primaryStat] ?? 0; // Find if there's a priority reforge that applies to this tool const priorityReforge = Object.values(REFORGES).find((r) => r?.priority && tool.type && r.appliesTo.includes(tool.type)); const result = []; for (const [reforgeId, reforge] of Object.entries(REFORGES)) { // Skip if the reforge doesn't apply to the item or is currently applied if (!reforge || !tool.type || !reforge.appliesTo.includes(tool.type) || reforge === tool.reforge) { continue; } // For Farming Fortune views, keep the existing priority reforge behavior. // Other stat views can surface non-priority reforges that affect that stat. if (priorityReforge && reforge !== priorityReforge && !reforge.priority && primaryStat === Stat.FarmingFortune) continue; // Only suggest reforges with an explicit reforge stone if (!reforge.stone?.id) continue; const tier = reforge.tiers[tool.rarity]; if (!tier || !tier.stats) continue; const nextPrimary = tier.stats?.[primaryStat] ?? 0; const effects = getReforgeEffectSummaries(tool, reforgeId, requestedStats); // Allow priority reforges even if they have less fortune if (!reforge.priority && nextPrimary <= currentPrimary && !requestedStats.some((stat) => (tier.stats?.[stat] ?? 0) > (currentStats?.[stat] ?? 0)) && effects.length === 0) { continue; } const deltaStats = {}; for (const s of Object.values(Stat)) { const before = currentStats?.[s] ?? 0; const after = tier.stats?.[s] ?? 0; const diff = after - before; if (diff !== 0) deltaStats[s] = diff; } const increase = deltaStats[Stat.FarmingFortune] ?? 0; result.push({ title: 'Reforge to ' + reforge.name, increase, stats: deltaStats, effects: effects.length > 0 ? effects : undefined, action: UpgradeAction.Apply, category: UpgradeCategory.Reforge, conflictKey: 'reforge', wiki: reforge.wiki, // Optional if this is a priority reforge, and previous reforge is more fortune optional: reforge.priority && nextPrimary < currentPrimary, onto: { name: tool.item.name, skyblockId: tool.item.skyblockId, }, meta: { itemUuid: tool.item.uuid ?? undefined, type: 'reforge', id: reforge.name.toLowerCase().replaceAll(' ', '_'), }, cost: reforge.stone?.id ? { items: { [reforge.stone.id]: 1, }, applyCost: tier?.cost ? { coins: tier?.cost, } : undefined, } : undefined, }); } return result; }, }, { name: 'Gemstone Slots', wiki: () => 'https://w.elitesb.gg/Gemstone_Slot', exists: (upgradeable) => { const last = (upgradeable.getLastItemUpgrade() ?? upgradeable)?.info; return last?.gemSlots?.some((s) => s.slot_type === 'PERIDOT') !== undefined; }, max: (upgradeable) => { const last = (upgradeable.getLastItemUpgrade() ?? upgradeable)?.info; const peridotSlots = last?.gemSlots?.filter((s) => s.slot_type === 'PERIDOT').length ?? 0; const maxRarity = last?.maxRarity ?? Rarity.Common; return peridotSlots * getPeridotGemFortune(maxRarity, GemRarity.Perfect); }, current: (upgradeable) => { return getPeridotFortune(upgradeable.rarity, upgradeable.item); }, maxStat: (upgradeable, stat) => { if (stat !== Stat.FarmingFortune) return 0; const last = (upgradeable.getLastItemUpgrade() ?? upgradeable)?.info; const peridotSlots = last?.gemSlots?.filter((s) => s.slot_type === 'PERIDOT').length ?? 0; const maxRarity = last?.maxRarity ?? Rarity.Common; return peridotSlots * getPeridotGemFortune(maxRarity, GemRarity.Perfect); }, currentStat: (upgradeable, stat) => stat === Stat.FarmingFortune ? getPeridotFortune(upgradeable.rarity, upgradeable.item) : 0, upgrades: getUpgradeableGems, }, { name: 'Farming For Dummies', wiki: () => 'https://w.elitesb.gg/Farming_For_Dummies', exists: (tool) => tool.type !== ReforgeTarget.Sword, max: () => 5, current: (tool) => { return +(tool.item.attributes?.farming_for_dummies_count ?? 0); }, maxStat: (_tool, stat) => (stat === Stat.FarmingFortune ? 5 : 0), currentStat: (tool, stat) => stat === Stat.FarmingFortune ? +(tool.item.attributes?.farming_for_dummies_count ?? 0) : 0, upgrades: (tool) => { const count = +(tool.item.attributes?.farming_for_dummies_count ?? 0); if (count < 0 || count >= 5) return []; return [ { title: 'Farming For Dummies', increase: 1, stats: { [Stat.FarmingFortune]: 1, }, action: UpgradeAction.Apply, category: UpgradeCategory.Item, conflictKey: 'farming_for_dummies', repeatable: 5 - count, wiki: 'https://w.elitesb.gg/Farming_For_Dummies', cost: { items: { FARMING_FOR_DUMMIES: 1, }, }, onto: { name: tool.item.name, skyblockId: tool.item.skyblockId, }, meta: { itemUuid: tool.item.uuid ?? undefined, type: 'item', id: 'farming_for_dummies_count', value: count + 1, }, }, ]; }, }, ...Object.entries(FARMING_ENCHANTS).map(([id, enchant]) => enchantSourceBuilder(id, enchant)), ]; function enchantSourceBuilder(id, enchant) { // Progress-only enchants (declared in the enchant definition) if (enchant.alwaysInclude) { return { name: enchant.name, wiki: () => enchant.wiki, alwaysInclude: true, exists: (tool) => tool.type !== undefined && enchant.appliesTo.includes(tool.type) && (!enchant.cropSpecific || tool.crops.includes(enchant.cropSpecific)), max: () => 0, current: () => 0, maxStat: () => 0, currentStat: () => 0, progress: (tool) => { const level = tool.item.enchantments?.[id] ?? 0; return [ { name: 'Level', current: level, max: enchant.maxLevel, ratio: Math.min(isNaN(level / enchant.maxLevel) ? 0 : level / enchant.maxLevel, 1), }, ]; }, upgrades: (tool) => getUpgradeableEnchant(tool, id, Stat.FarmingFortune, { includeWhenNoStatImpact: true }), }; } return { name: enchant.name, wiki: () => enchant.wiki, exists: (tool) => tool.type !== undefined && enchant.appliesTo.includes(tool.type) && (!enchant.cropSpecific || tool.crops.includes(enchant.cropSpecific)), max: (tool) => { const currentOverbloom = getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, Stat.Overbloom, tool.options, tool.crops[0]); const maxOverbloom = currentOverbloom > 0 ? getMaxStatFromEnchant(enchant, Stat.Overbloom, tool.options, tool.crops[0]) : 0; // For multi-crop tools (e.g., Eclipse Hoe), take the best single-crop value to avoid double counting if (tool.crops.length > 1) { let best = 0; for (const crop of tool.crops) { best = Math.max(best, getMaxStatFromEnchant(enchant, CROP_INFO[crop].fortuneType, tool.options, crop)); } return (best || getMaxStatFromEnchant(enchant, Stat.FarmingFortune, tool.options, tool.crops[0]) || maxOverbloom); } let sum = 0; for (const crop of tool.crops) { sum += getMaxStatFromEnchant(enchant, CROP_INFO[crop].fortuneType, tool.options, crop); } return (sum || getMaxStatFromEnchant(enchant, Stat.FarmingFortune, tool.options, tool.crops[0]) || maxOverbloom); }, current: (tool) => { const currentOverbloom = getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, Stat.Overbloom, tool.options, tool.crops[0]); if (tool.crops.length > 1) { let best = 0; for (const crop of tool.crops) { best = Math.max(best, getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, CROP_INFO[crop].fortuneType, tool.options, crop)); } return (best || getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, Stat.FarmingFortune, tool.options, tool.crops[0]) || currentOverbloom); } let sum = 0; for (const crop of tool.crops) { sum += getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, CROP_INFO[crop].fortuneType, tool.options, crop); } return (sum || getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, Stat.FarmingFortune, tool.options, tool.crops[0]) || currentOverbloom); }, maxStat: (tool, stat) => { if (stat === Stat.FarmingFortune && tool.crops.length > 1) { let best = 0; for (const crop of tool.crops) { best = Math.max(best, getMaxStatFromEnchant(enchant, stat, tool.options, crop)); } return best || getMaxStatFromEnchant(enchant, stat, tool.options, tool.crops[0]); } if (stat !== Stat.FarmingFortune && CROP_FORTUNE_STATS.has(stat)) { let best = 0; for (const crop of tool.crops) { if (CROP_INFO[crop]?.fortuneType !== stat) continue; best = Math.max(best, getMaxStatFromEnchant(enchant, stat, tool.options, crop)); } return best || getMaxStatFromEnchant(enchant, stat, tool.options, tool.crops[0]); } // Non-crop-fortune stats (e.g. Overbloom from Feast) are flat per enchant level, // not per crop. Resolve once with one of the tool's crops. if (stat !== Stat.FarmingFortune) { return getMaxStatFromEnchant(enchant, stat, tool.options, tool.crops[0]); } let sum = 0; for (const crop of tool.crops) { sum += getMaxStatFromEnchant(enchant, stat, tool.options, crop); } return sum || getMaxStatFromEnchant(enchant, stat, tool.options, tool.crops[0]); }, currentStat: (tool, stat) => { if (stat === Stat.FarmingFortune && tool.crops.length > 1) { let best = 0; for (const crop of tool.crops) { best = Math.max(best, getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, stat, tool.options, crop)); } return (best || getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, stat, tool.options, tool.crops[0])); } if (stat !== Stat.FarmingFortune && CROP_FORTUNE_STATS.has(stat)) { let best = 0; for (const crop of tool.crops) { if (CROP_INFO[crop]?.fortuneType !== stat) continue; best = Math.max(best, getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, stat, tool.options, crop)); } return (best || getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, stat, tool.options, tool.crops[0])); } // Non-crop-fortune stats (e.g. Overbloom from Feast) are flat per enchant level. if (stat !== Stat.FarmingFortune) { return getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, stat, tool.options, tool.crops[0]); } let sum = 0; for (const crop of tool.crops) { sum += getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, stat, tool.options, crop); } return (sum || getStatFromEnchant(tool.item.enchantments?.[id] ?? 0, enchant, stat, tool.options, tool.crops[0])); }, upgrades: (tool, stats) => { const primaryStat = stats?.[0] ?? Stat.FarmingFortune; const chains = [primaryStat]; if (CROP_FORTUNE_STATS.has(primaryStat) && !chains.includes(Stat.FarmingFortune)) { chains.push(Stat.FarmingFortune); } for (const s of stats ?? []) { if (!chains.includes(s)) chains.push(s); } const upgrades = []; for (const s of chains) { upgrades.push(...getUpgradeableEnchant(tool, id, s)); } const seen = new Set(); return upgrades.filter((u) => { const key = u.conflictKey ?? `${u.title}:${u.action}`; if (seen.has(key)) return false; seen.add(key); return true; }); }, }; } //# sourceMappingURL=toolsources.js.map