UNPKG

farming-weight

Version:

Tools for calculating farming weight and fortune in Hypixel Skyblock

148 lines (147 loc) 5.96 kB
import type { Crop } from '../constants/crops.js'; import type { SpecialCrop } from '../constants/specialcrops.js'; import type { Stat } from '../constants/stats.js'; /** * Built-in well-known drop tags. Sources can use any string, but built-ins are * the auditable set used by the package's own drop entries and effects. * * - `overbloom`: drop is buffed by Overbloom-class effects (`add-rare-pct` + `mul-rare`). * - `rare-crop`: drop is part of the SkyBlock "Rare Crop" pool conceptually. * - `special-crop`: drop is a Special Crop (Cropie/Squash/Fermento/Helianthus). * - `seasoning`: Harvest Feast Seasoning category. * - `feast`: any Harvest Feast drop (seasoning + per-crop materials). * - `pest`: drop comes from a pest, not a regular crop break. */ export type DropTagBuiltin = 'overbloom' | 'rare-crop' | 'special-crop' | 'seasoning' | 'feast' | 'pest'; export type DropTag = DropTagBuiltin | (string & {}); /** * Minimal player/world environment data needed to collect effects. Constructed * exclusively by `buildEffectEnvironment(player, crop?)`. */ export interface EffectEnvironment { /** Active crop for the rate calc, if any. `undefined` for crop-agnostic stat queries. */ crop?: Crop; /** Harvest Feast active in the world. */ harvestFeast: boolean; /** True only when `crop` is provided AND it is in-season under an active Harvest Feast. */ inSeason: boolean; /** Player is currently on an infested plot (probability > 0). */ infestedPlot: boolean; /** Selected crop from the player's options (used by some shards' force-active rules). */ selectedCrop?: Crop; } /** * Drop kinds carried on every candidate drop. Used by `Scope.dropKinds` and * for resolver phase routing. */ export type DropKind = 'rare' | 'rng' | 'special-crop' | 'pest' | 'crop'; /** * Per-drop context built by the resolver during `produce-drops`/`add-rare`/etc. * Sources never construct one of these directly. */ export interface DropContext { env: EffectEnvironment; crop: Crop; dropKind: DropKind; /** Item id of the drop being evaluated, e.g. `'CROPIE'`, `'SEASONING'`, `'WARTY'`. */ itemId: string; specialCropType?: SpecialCrop; tags: ReadonlySet<DropTag>; /** True iff this drop was produced by an `add-drop` effect this run. */ fromAddDrop?: boolean; } /** * Context for stat-shaped queries (`getStat`). Carries no drop fields by design. * The stat matcher rejects any scope that uses drop-only fields. */ export interface StatContext { env: EffectEnvironment; /** Optional crop scoping for crop-specific fortunes. */ crop?: Crop; } /** * Declarative scope for an effect. Empty scope = global match. * * Drop-only fields (`items`, `dropKinds`, `specialCropTypes`, `tags`, `match`) * cause the stat matcher to reject the effect outright. */ export interface Scope { crops?: readonly Crop[]; dropKinds?: readonly DropKind[]; items?: readonly string[]; specialCropTypes?: readonly SpecialCrop[]; /** Match if every listed tag is present on the drop. */ tags?: readonly DropTag[]; /** Effect only applies when a Harvest Feast is active in the env. */ requiresHarvestFeast?: boolean; /** Effect only applies when the drop's crop is in-season. */ requiresInSeason?: boolean; /** * If `false`, the effect won't match drops produced by an `add-drop` effect. * Default `true`. */ appliesToAddedDrops?: boolean; /** Predicate escape hatch. Drop-only; never invoked during stat resolution. */ match?: (ctx: DropContext) => boolean; } export type EffectOp = 'add-stat' | 'add-rare-pct' | 'mul-rare' | 'add-drop' | 'mul-drop'; export type EffectPhase = 'scalar' | 'produce-drops' | 'add-rare' | 'mul-rare' | 'mul-drop'; /** Default phase for each op. Effects rarely override. */ export declare const DEFAULT_PHASE_FOR_OP: Record<EffectOp, EffectPhase>; export interface EffectAddDropPayload { itemId: string; /** Output bucket for the produced drop. Defaults to `rng`. */ output?: 'rng' | 'collection' | 'currency'; /** Optional flat amount per block broken (alternative to `chance`). */ baseAmount?: number; /** Optional 0..1 chance per block broken. */ chance?: number; specialCropType?: SpecialCrop; /** Tags assigned to the produced drop. Defaults to `['rare-crop','overbloom']`. */ tags?: readonly DropTag[]; /** Drop kind for the produced drop. Defaults to `'rare'`. */ dropKind?: DropKind; } /** * Numeric semantics by op (no mixing of factor vs delta within an op): * - `add-stat`: additive scalar contribution to `stat` (e.g. 5 = +5 Fortune). * - `add-rare-pct`: additive percentage points (e.g. 50 = +50% on rare drops). * - `mul-rare`: multiplicative factor (e.g. 1.2 = x1.2). Must be >= 0. * - `mul-drop`: multiplicative factor (e.g. 1.25 = x1.25). Must be >= 0. * - `add-drop`: `value` is unused; `drop` carries the payload. */ export interface Effect { source: string; op: EffectOp; phase?: EffectPhase; scope?: Scope; stat?: Stat; value?: number | ((ctx: DropContext | StatContext) => number); drop?: EffectAddDropPayload; relatedStats?: readonly Stat[]; meta?: { description?: string; valueDisplay?: 'stat' | 'percent' | 'factor'; valueStat?: Stat; }; } /** * Per-drop record of an applied effect, surfaced via `DetailedDropsResult`. */ export interface AppliedEffect { source: string; op: EffectOp; phase: EffectPhase; /** Resolved numeric contribution at this drop. */ amount: number; relatedStats?: readonly Stat[]; scope?: Scope; description?: string; valueDisplay?: 'stat' | 'percent' | 'factor'; valueStat?: Stat; } /** * Aggregate per-source totals across the whole rate calc. * Replaces today's `rareItemBonusBreakdown`. */ export type EffectsBreakdown = Record<string, number>;