farming-weight
Version:
Tools for calculating farming weight and fortune in Hypixel Skyblock
148 lines (147 loc) • 5.96 kB
TypeScript
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>;