import type { EmoteKind } from './emotes.js'; import type { RespawnPointId, RespawnStyle } from './respawnPoints.js'; import type { SledPhase } from './sledRun.js'; import type { RideLane, RidePhase } from './steeredRide.js'; import type { BhPaint } from './brackenHeath.js'; import type { LedgerSnapshot } from './explorerLedger.js'; import type { FieldGuideSnapshot } from './fieldGuide.js'; import type { TitlesSnapshot } from './titles.js'; import type { ShareMoment, ShareMomentKind } from './shareMoments.js'; import type { WeeklyHuntView } from './weeklyHunt.js'; import type { DailyContractView } from './dailyHooks.js'; import type { WardId } from './wards.js'; import type { AquariumHabitat, AquariumState, AquariumXp } from './aquarium.js'; import type { ChunkCoord, ChunkData, NpcKind, WorldEntity } from './constants.js'; import type { AdventureAction, AdventureSnapshot } from './adventure.js'; import type { AreaTransitionId } from './areaTransitions.js'; import type { CharacterAppearance } from './appearance.js'; import type { PlayerPatch } from './rosterDelta.js'; import type { AttackAnimationFamily } from './animation.js'; import type { BossSignatureId } from './bossSignatures.js'; import type { ClientConfig, PartialClientConfig } from './clientConfig.js'; import type { QuestId, QuestReward, QuestsSnapshot } from './quests.js'; import type { QuestGuideStep } from './questGuide.js'; import type { PendingXpChoice, StoryProgressMap, StorySkill } from './storyQuests.js'; import type { ActiveBounty, BountyOffer, BountyTargetKind } from './bounty.js'; import type { TimedAreaId } from './timedAccess.js'; import type { TimedPlaceId } from './timedRun.js'; import type { EquipmentSlots } from './equipment.js'; import type { CombatZoneTier, WorldZoneId } from './zones.js'; import type { LootGrade } from './lootGrades.js'; import type { PetAnim } from './hogWild.js'; import type { BayPetAction, BayPetCompanion } from './bayPets.js'; import type { RingFightView, RingStage } from './worldBossRing.js'; import type { ShipFightView } from './crescentQueen.js'; import type { DerbyView } from './fishingDerby.js'; import type { LampRoundView } from './lamplightCommon.js'; import type { ShippingView } from './voyages.js'; export type { CombatZoneTier, WorldZoneId } from './zones.js'; export type Vec2 = { x: number; y: number; }; export type CharacterStats = { health: number; healthMax: number; combat: number; strength: number; /** Melee power skill; paired with Mastery for attack effectiveness. */ archery: number; /** Shared weapon-use skill. Shown to players as Mastery. */ precision: number; fishing: number; cooking: number; foraging: number; woodwork: number; mining: number; blacksmithing: number; potionmaking: number; explorer: number; /** Bounty Hunter - trained only by completing Bounty Board contracts. */ bounty: number; combatXp: number; strengthXp: number; archeryXp: number; precisionXp: number; fishingXp: number; cookingXp: number; foragingXp: number; woodworkXp: number; miningXp: number; blacksmithingXp: number; potionmakingXp: number; explorerXp: number; bountyXp: number; }; /** The currently selected combat training style. */ export type CombatStance = 'strength' | 'archery' | 'mastery' | 'balanced'; export declare function defaultCombatStance(): CombatStance; export declare function normalizeCombatStance(raw: unknown): CombatStance; export declare function defaultCharacterStats(): CharacterStats; /** * Which tackle a fishing pose shows. `spinrod` is the spinning rod on the * pier (smelt), `icerod` the short rod fished through a hole in the ice, * `cage` a crab cage lowered into the beach shallows. */ export type FishingGearKind = 'net' | 'rod' | 'flyrod' | 'beachrod' | 'spinrod' | 'icerod' | 'cage'; /** * Marked spots that ride the lake_fishing_spots channel. Ice holes are entities, not spots. `rudd` is Henson Pond's * rod spot off the beginner peg (pondFishing.ts). */ export type LakeFishSpotKind = 'perch' | 'tench' | 'trout' | 'smelt' | 'crabcage' | 'conger' | 'river' | 'deep' | 'rudd' | 'tidesnapper'; export type PlayerPublic = { title?: string | null; /** A member who has their gold name switched on (ClientConfig.goldName): draw the name in gold. */ goldName?: boolean; emote?: { kind: EmoteKind; startedAt: number; }; /** Away from the keyboard (emotes.ts AFK_AFTER_MS). Only sent while true. */ afk?: boolean; /** * Riding a moving train (westernTrain.ts): which train, and the rider's tile and path in the train's docked frame (the * rake as it stands at the platform). x/y are the world tile the train has carried them to; the client walks the * docked tile and draws it on the train it computes from the clock. Only sent while the train is moving. */ ride?: { train: number; x: number; y: number; path?: Vec2[]; }; id: string; name: string; color: string; x: number; y: number; appearance: CharacterAppearance; health: number; healthMax: number; level: number; fishing?: boolean; /** * Which tackle the fishing pose should show. Pond fishing is a scoop net; * lake fishing (perch/tench) and the pond's rudd are the small rod; trout is the long fly rod. */ fishingGear?: FishingGearKind; /** * World point this player should visually face, e.g. the water they are * casting at. Same field pair NpcPublic already uses to aim Old Tom at the * pond; without it an angler faces wherever they happened to stop walking. * * For an angler this pair is the CAST tile, where the line and the fish * come down. On the Long Pier that is not the spot that was clicked: the * server picks a tile 3-10 out over the rail and moves it after each * catch, so a change here while `fishing` stays true means a re-cast. */ faceX?: number; faceY?: number; woodwork?: boolean; woodcutTarget?: string; /** Repeating woodcraft at a work bench (knife-on-wood pose). */ carving?: boolean; /** Cooking at a campfire (crouched, tending the fire). */ cooking?: boolean; /** Cutting smelt into bait at the lake's table (knife-on-board pose, facing faceX/faceY). */ cuttingBait?: boolean; /** Working a blacksmith's station (the hammer swing, facing faceX/faceY). */ smithing?: boolean; /** Stirring a cauldron (Potion Making), facing faceX/faceY. */ brewing?: boolean; /** Legacy flag retained for clients predating weapon-aware drills. */ training?: boolean; /** nextAtMs identifies a beat; nextInMs maps it to the client's monotonic clock. */ /** * `lent`: the bow the drill lends (the Beacon Gallery's chained bow, lanternWakeGallery.ts), drawn in hand while the drill * lasts in place of the equipped weapon and offhand. Never an item: it is not in the bag or the equipment. */ trainingDrill?: { mode: 'melee' | 'archery'; family: AttackAnimationFamily; entityId: string; nextAtMs: number; nextInMs: number; lent?: string; }; /** Swinging a pick at an ore seam (two-handed overhead swing, facing faceX/faceY). */ mining?: boolean; /** * Channelled world action (e.g. squeezing a webbed path). `endsAt` is * Date.now() on the server when the channel completes. `kind` lets the * client pose the work (the auger cranks, 'dig' stabs the fork) and * `targetX`/`targetY` is the tile being worked, so the character faces it. * For 'dig' that is the worm cast's tile. 'watch' is keeping watch from cover (Field Shrine Mystery: crouched in the * hawthorn, facing the shrine, which is the target tile). */ channel?: { label: string; endsAt: number; durationMs: number; kind?: 'squeeze' | 'auger' | 'dig' | 'watch'; targetX?: number; targetY?: number; }; /** * Milliseconds left on the escape shield after breaking away from another * player in the woods (PVP_ESCAPE_SHIELD_MS at most). Only sent while * one is running; the client counts it down from receipt. */ pvpShieldMs?: number; weapon?: string; /** * True when this player is holding a rod AND carrying line for it. * * Sent for everyone, not just the owner: whether a rod is strung is part of * how the character reads at a glance, and the client cannot work it out for * anyone else - it only ever sees its own inventory. */ rodLine?: boolean; boots?: string; gloves?: string; helmet?: string; top?: string; bottom?: string; offhand?: string; /** The amulet at the throat: the Frozen Talisman is the first with geometry. */ amulet?: string; /** The face piece (an eye patch, a gold tooth, a scar, an earring, a beard ribbon: pirateCosmetics.ts). */ face?: string; /** The quiver on the back, drawn for everyone like the rest of the worn set. */ quiver?: string; /** The cape on the back (the Frozen Cape is the first with geometry; outfits in this slot draw through the appearance). */ cape?: string; path?: Vec2[]; /** * Movement speed as a multiple of the normal walking pace (running and admin /speed). * Only sent when it is not 1, so a normal walker never carries it. Clients * scale their visual walk, and the thresholds that decide whether a server * step is a walk or a teleport, by it. */ speed?: number; /** Requested run toggle; retained while Dreadwoods terrain forces walking. */ runEnabled?: boolean; /** Actual running gait, independent of the admin speed multiplier. */ running?: boolean; /** Sitting to rest (energy.ts). Only sent while true. */ sitting?: boolean; /** On Hask's sled, in the hold before it goes or on the run (sledRun.ts): drawn crouched on a sled. Only sent while true. */ sled?: boolean; /** * In a steered ride's cart (steeredRide.ts; Bracken Heath's Heath Racer), held at the line or rolling: the paint the cart wears. Drawn sitting * in a one-seat cart. Only sent while riding. */ cart?: BhPaint; /** * Sitting on a train seat (types westernTrain.ts WESTERN_TRAIN_SEATS, the seat's id): the client draws the body on the * bench, facing the aisle, on whichever train the sitter is on. Only sent while sitting there. */ seat?: string; /** * Pet trailing this player, visible to everyone. Server-driven so other * players see your beagle follow you; absent while dismissed to the bank. */ companion?: CompanionPublic | null; }; /** A player's pet, positioned by the server like a miniature NPC. */ export type CompanionPublic = { /** * The member beagle, or the warthog from Hog Wild (hogWild.ts). One is out at a time. A `sheep` is a stray ewe being * led home to Nye's fold (southFieldsQuests.ts): it takes the pet's place until it is in. A `goat` is Bren's Clover, * led home to the fold on Shepherd's Rise the same way (The Lost Goat, hopewoodQuests.ts). The `sheepdog` is Nye's pup from A Dog for the Flock (fairContests.ts), out in the pet's place until sent home. * A bay pet (bayPets.ts, pirate bay phase 6): the parrots ride the owner's shoulder, the hermit crab, ship's cat and seal pup follow. */ kind: 'beagle' | 'warthog' | 'sheep' | 'goat' | 'sheepdog' | BayPetCompanion; name: string; x: number; y: number; /** A one-off pose everyone near sees (the warthog eating, digging, rolling in the mud, belly up). */ anim?: PetAnim; }; export type NpcPublic = { id: string; kind: NpcKind; name?: string; x: number; y: number; level: number; health: number; healthMax: number; /** Optional visual scale for special encounter variants. */ scale?: number; inCombat: boolean; path?: Vec2[]; speed?: number; /** Optional world-space point this NPC should visually face (e.g. Old Tom facing pond water). */ faceX?: number; faceY?: number; /** * Riding an ice floe in from the sea (a polar bear coming ashore at the * Bear Floes): the client draws the floe under it and holds it still on * it. When it clears the bear has stepped off, and the floe drifts away. */ floe?: true; /** * A sheep shorn not long ago (pasture.ts): the client draws it bare, its * fleece gone, until the server clears this when the wool has grown back. */ shorn?: true; /** A one-off pose (a wild warthog eating the turnip it was fed): set for a moment, then cleared. */ anim?: PetAnim; }; export type CombatEndReason = 'victory' | 'defeat' | 'fled' | 'target_gone'; export type CombatTargetKind = 'player' | 'npc'; /** A drop. `precision` rides along for the few kinds whose slot carries one (carved weapons, satchels, coin purses). */ export type LootItem = { kind: string; qty: number; precision?: number; /** Set by the server on a drop worth a graded line in chat (lootGrades.ts); absent on cheap drops and from older servers. */ grade?: LootGrade; }; export type InventorySlot = { kind: string; qty: number; /** Craft quality for weapons (woodwork level when carved). */ precision?: number; }; /** * 'place': set a folding bench up, or light a campfire from a stack of logs (woodwork.ts camp kit). * 'tie': tie a Simple Lure from a Pond Weed and a Snail (pondFishing.ts). */ export type ItemAction = 'eat' | 'drink' | 'drop' | 'equip' | 'unequip' | 'open' | 'place' | 'tie' | 'prepare'; /** * A shop listing a player can buy. Normally costed in gold via `price`, but a * listing can instead be a barter (e.g. Wilson's rod-for-maggots trade) by * setting `costKind`/`costQty` to an item cost - `price` is ignored then. */ export type ShopBuyEntry = { kind: string; price: number; qty: number; costKind?: string; costQty?: number; }; export type ShopSellEntry = { kind: string; price: number; }; /** * `already_online`: the character has a live session somewhere else that is * fighting, or only just stopped. The client waits and retries rather than * taking the session over; once that session has been out of combat long * enough the server hands the character across (and tells the old client * with `session_ended`). */ export type JoinRejectedReason = 'name_taken' | 'invalid_name' | 'already_online'; /** The stages of a server update (release): see the 'server_update' message. */ export type ServerUpdateState = 'countdown' | 'updating' | 'cancelled'; /** The WebSocket close code of the force logout at the end of a server update countdown (4000-4999 is for applications). */ export declare const SERVER_UPDATE_CLOSE_CODE = 4010; /** What a player reads on the update screen once they have been logged out. */ export declare const SERVER_UPDATE_MESSAGE = "The server is being updated. You have been logged out and nothing is lost. We will bring you back as soon as it is ready."; /** * `reward` is for "you just gained something" moments - landing a fish, and * anything else worth a beat of celebration. Distinct from `quiet` (which is a * plain grey chat line and was where catches used to land, so they slid past * unnoticed) and from the default gold notice, which is used for routine * information. */ /** 'event' is a world event told to everyone online (the Hoard Chest, hoardChest.ts): a banner and a gold chat line. */ export type NoticeStyle = 'warning' | 'quiet' | 'combat' | 'reward' | 'area' | 'quest' | 'event'; /** * What a boost lifts. Each has its own badge on the boost panel, so a folded * panel still says at a glance what is helping you (a heart for health, a * blade for strength, and so on). Add a stat here and give it an icon in the * frontend's boosts module. */ export type BoostStat = 'health' | 'combat' | 'strength' | 'archery' | 'precision' | 'fishing' | 'cooking' | 'foraging' | 'woodwork' | 'mining' | 'blacksmithing' | 'potionmaking' | 'explorer' | 'bounty'; export type LevelUpSkill = 'combat' | 'strength' | 'archery' | 'precision' | 'fishing' | 'cooking' | 'foraging' | 'woodwork' | 'mining' | 'blacksmithing' | 'potionmaking' | 'explorer' | 'bounty'; export declare function skillDisplayName(skill: LevelUpSkill): string; export type TradeOfferSlot = { kind: string; qty: number; }; export type TradeSessionSnapshot = { partnerId: string; partnerName: string; you: TradeOfferSlot[]; them: TradeOfferSlot[]; youLocked: boolean; themLocked: boolean; youAccepted: boolean; themAccepted: boolean; }; export type DuelStakeSnapshot = { partnerId: string; partnerName: string; you: TradeOfferSlot[]; them: TradeOfferSlot[]; youLocked: boolean; themLocked: boolean; phase: 'staking' | 'fighting'; }; /** Client SFX keys for trade/duel events (filenames under assets/audio/). */ export type TradeDuelSfx = 'sending_trade_request' | 'trade_started' | 'item_put_up_for_trade' | 'trade_locked_in' | 'trade_completed' | 'duel_request_sent' | 'duel_accepted' | 'item_put_up_as_stake' | 'stake_screen_locked' | 'stake_starting' | 'stake_won' | 'stake_lost'; /** Client SFX keys for the Rat Tunnel dungeon (filenames under assets/audio/). */ export type DungeonSfx = 'enter_dungeon' | 'exit_dungeon' | 'gate_open' | 'gate_close' | 'door_open' | 'door_close' | 'rare_monster_alert_battle' | 'boss_victory' | 'area_effect_start' | 'area_effect_end'; /** Client SFX keys for building and crafting (filenames under assets/audio/). */ export type CraftSfx = 'workbench_build' | 'workbench_carve' | 'item_crafted' | 'torch_light'; /** * One-shot SFX for world gates, paid-area doors, and locked mouths, and the * lake ice: `ice_break` is the auger going through. */ export type WorldSfx = 'deny_entry' | 'ice_break'; export type PotionSfx = 'potion_drink'; /** All server-broadcastable one-shot SFX keys. */ /** The moving train (westernTrain.ts): the loco's whistle as it leaves, the platform bell before it does. */ export type TrainSfx = 'train_whistle' | 'train_bell'; export type ClientSfx = TradeDuelSfx | DungeonSfx | CraftSfx | WorldSfx | PotionSfx | TrainSfx; /** * Why a player reports another (the report message). Google Play's user-generated-content policy asks for an in-app way * to report and to block: the chat is public, so both are on a player's menu and on their name in the chat. `under13` is * "seems under 13" (Dreadwoods is 13+, James 2026-10-14): moderators close such an account, backend MODERATION.md. */ export declare const REPORT_REASONS: readonly ["abuse", "spam", "cheating", "name", "under13", "other"]; export type ReportReason = (typeof REPORT_REASONS)[number]; /** How many players one character can block. */ export declare const BLOCKED_MAX = 100; export type ClientMessage = { type: 'adventure_action'; action: AdventureAction; } /** * `sessionToken` + `characterId` are used by account-backed entry points * (including TikTok). The server verifies both before loading the character; * name-only joins remain for legacy/browser guests. */ | { type: 'join'; name?: string; appearance: CharacterAppearance; sessionToken?: string; characterId?: string; rosterDelta?: boolean; } /** A rosterDelta client lost track (a patch for a player it never got): the server sends the roster in view afresh. */ | { type: 'roster_resync'; } | { type: 'move'; x: number; y: number; } | { type: 'run'; enabled: boolean; } /** Sit to rest (X key or the right-click menu) or stand up again. */ /** `seat`: sit on that train seat (WESTERN_TRAIN_SEATS id): walked to it first, in the train's docked frame. */ | { type: 'sit'; sitting: boolean; seat?: string; } /** An emote from the emote bar (emotes.ts); a looping one holds until you move. */ | { type: 'emote'; kind: EmoteKind; } | { type: 'ping'; t: number; } /** A lean on a bend of Hask's Sled Run (sledRun.ts); counts only inside that bend's window. */ | { type: 'sled_lean'; } /** * Steer a steered ride's cart (steeredRide.ts): `dir` -1 is a lane to the left, 1 a lane to the right; or a tapped tile (`x`, `z`), and the * server picks the lane nearest it. Ignored unless riding, and while a lane change is still under way. */ | { type: 'cart_steer'; dir?: -1 | 1; x?: number; z?: number; } /** The client walked far enough (or the bag changed) that nearest-thing steps may have moved. */ | { type: 'quest_guide_refresh'; } | { type: 'chunk_request'; chunks: ChunkCoord[]; } | { type: 'chat'; text: string; } | { type: 'pm'; to: string; text: string; } | { type: 'friend_add'; name: string; } | { type: 'friend_remove'; name: string; } /** Stops that player's chat and private messages reaching this character (answered with a 'blocked' list). */ | { type: 'block_add'; name: string; } | { type: 'block_remove'; name: string; } /** Tells the team about a player; the server keeps their last few chat lines with it. Answered with a notice. */ | { type: 'report'; name: string; reason: ReportReason; } | { type: 'config'; config: PartialClientConfig; } | { type: 'pickup'; entityId: string; } | { type: 'attack'; targetId: string; } | { type: 'set_combat_stance'; stance: CombatStance; } /** * `slot` is the bag index the player actually clicked. Stacks are capped per * slot, so the same kind can occupy several slots; without this the server * always consumed the first matching stack, which is not the one that was * clicked. Optional so older clients keep working (they get the old * first-match behaviour). */ | { type: 'item_action'; item: string; action: ItemAction; qty?: number; slot?: number; } | { type: 'cook_start'; input: string; qty: number; } /** Load a charcoal mound with logs and light it (charcoal.ts). */ | { type: 'charcoal_start'; wood: string; qty: number; } | { type: 'charcoal_cancel'; } | { type: 'woodcraft_open'; entityId: string; } | { type: 'woodcraft_make'; recipe: string; qty: number; } | { type: 'blacksmith_open'; entityId: string; } | { type: 'blacksmith_make'; recipe: string; qty: number; } /** Potion Making (potionMaking.ts): open a cauldron, brew a recipe (the whole bag's worth), close it. */ | { type: 'brew_open'; entityId: string; } | { type: 'brew_make'; recipe: string; } | { type: 'brew_close'; } /** * The Great Aquarium (aquarium.ts): donate `qty` of a species to the tanks. The server clamps it to the bag, the * first-of-a-kind rule and the hourly cap (planAquariumDonation), and refuses everything if it cannot. */ | { type: 'aquarium_donate'; kind: string; qty: number; } /** Spar a training dummy (the Garrison's yard): strength XP on a clock, capped at TRAINING_DUMMY_MAX_LEVEL. */ | { type: 'train_start'; entityId: string; } | { type: 'train_stop'; } /** Pray at a shrine: a full heal and the temple's blessing, once per TEMPLE_PRAYER_COOLDOWN_MS. */ | { type: 'pray'; entityId: string; } | { type: 'cook_cancel'; } | { type: 'shop_open'; npcId: string; } | { type: 'shop_close'; } | { type: 'shop_sell'; item: string; qty: number; } | { type: 'shop_buy'; item: string; qty: number; } | { type: 'bank_open'; entityId: string; } | { type: 'bank_close'; } | { type: 'bank_deposit'; tab: number; kind: string; qty: number; } | { type: 'bank_withdraw'; tab: number; kind: string; qty: number; } /** Sweep every bag stack whole into `tab` (no per-move cap); the server reports what did not fit. */ | { type: 'bank_deposit_all'; tab: number; } | { type: 'quest_offer'; npcId: string; } | { type: 'quest_accept'; quest: QuestId; } /** Put the first pending skill-choice reward into this skill. */ | { type: 'xp_choice'; skill: StorySkill; } | { type: 'quest_decline'; quest: QuestId; } | { type: 'npc_action'; npcId: string; action: string; } /** Open the Bounty Board - via the board entity or by talking to Bruce. */ | { type: 'bounty_open'; entityId?: string; npcId?: string; } | { type: 'bounty_accept'; target: BountyTargetKind; } | { type: 'bounty_abandon'; } /** Buy `blocks` blocks of time at a paid area's door. */ | { type: 'timed_access_buy'; area: TimedAreaId; blocks: number; npcId?: string; } | { type: 'bounty_claim'; entityId?: string; npcId?: string; } | { type: 'use_item'; item: string; npcId: string; } | { type: 'use_item'; item: string; entityId: string; } | { type: 'milk_cow'; npcId: string; } | { type: 'identify'; kind: string; } /** Ask for the Explorer's Ledger again (the book was opened, or the client lost it). */ | { type: 'ledger_request'; } /** The Field Guide (fieldGuide.ts), asked for again with its catalogue (the book was opened, or the client lost it). */ | { type: 'field_guide_request'; } /** Wear one earned title after your name (titles.ts); empty takes it off. The server refuses a title that is not earned. */ | { type: 'select_title'; title: string; } /** The player pressed Share on a share-moment card (shareMoments.ts): counted once per card shown, nothing is posted by the game. */ | { type: 'share_moment_click'; kind: ShareMomentKind; } | { type: 'fish_start'; /** * The spot the player actually clicked. Without it the server fished * whatever was nearest, so clicking one trout could start you on another. * Omitted when fishing was started from a key or the HUD rather than by * clicking a specific fish. */ x?: number; y?: number; } /** The Claim Tool button on the Reclaim Lost Tool panel (the Lost Tools Crate's offer, starter hub). */ | { type: 'lost_tool_claim'; entityId: string; } | { type: 'woodcut_start'; entityId: string; } | { type: 'mine_start'; entityId: string; } /** * A new appearance from the character designer, after a re-design has been * bought. Appearance only - the name is not editable this way, and the * server ignores anything else in the payload. */ | { type: 'appearance_update'; appearance: CharacterAppearance; } | { type: 'trade_request'; name: string; } | { type: 'trade_respond'; accept: boolean; } | { type: 'trade_offer'; item: string; qty: number; } | { type: 'trade_withdraw'; item: string; qty: number; } | { type: 'trade_lock'; } | { type: 'trade_accept'; } | { type: 'trade_cancel'; } | { type: 'duel_request'; name: string; } | { type: 'duel_respond'; accept: boolean; } | { type: 'duel_stake'; item: string; qty: number; } | { type: 'duel_withdraw'; item: string; qty: number; } | { type: 'duel_lock'; } | { type: 'duel_cancel'; } /** * Claims the rescued beagle, or renames one already claimed - the same * message either way, since "name your beagle" and "rename your beagle" are * the same operation to the server. */ | { type: 'upgrade_member'; } | { type: 'beagle_claim'; name: string; } | { type: 'beagle_summon'; } | { type: 'beagle_dismiss'; } | { type: 'beagle_bark'; } /** Feeds the pet one cooked item, the same way the quest beagle is fed. */ | { type: 'beagle_feed'; } /** * The warthog pet's menu (Hog Wild): pat it, feed it a rotten turnip, rub its belly (the quest's sixth step, and * any time after), send it home to Old Hogger, or call it back out when he is not to hand (summon). */ | { type: 'warthog_action'; action: 'pat' | 'feed' | 'rub' | 'shoo' | 'summon'; } /** The bay pet's menu (bayPets.ts): pat it, or send it back into the bag. Letting one out is the bag's 'open' on its item. */ | { type: 'baypet_action'; action: BayPetAction; } /** * Admin teleport panel: jump to a world tile. Admin accounts only; the * server drops it silently for anyone else. Same path as `/tp `. */ | { type: 'admin_teleport'; x: number; z: number; } | { type: 'client_error'; report: ClientErrorReport; }; /** Crash/error diagnostics pushed from client to server so they land in server logs * instead of only a phone's on-device console (which on TikTok can miss early boot * output - see crashDiagnostics.ts on the client). Best-effort, not persisted server-side. */ export type ClientErrorReport = { reason: string; message?: string; stack?: string; filename?: string; lineno?: number; colno?: number; elapsedSec?: number; breadcrumbs?: string[]; scene?: Record; webglEvents?: Array<{ t: number; type: 'lost' | 'restored'; statusMessage?: string; }>; samples?: Array>; recoveryRan?: boolean; resumeAttempt?: number; }; export type ServerMessage = { type: 'adventure'; snapshot: AdventureSnapshot; } | { type: 'combat_windup'; /** Legacy attacker alias retained for older clients. */ npcId: string; durationMs: number; /** Present for the coordinated player, NPC, and PvP strike protocol. */ attackerId?: string; targetKind?: CombatTargetKind; strikeId?: string; family?: AttackAnimationFamily; /** Time from this cue to authoritative impact, including projectile flight. */ contactMs?: number; /** Time from this cue to projectile release, before its flight. */ releaseMs?: number; projectile?: 'blueberry' | 'arrow' | 'venom' | 'dagger' | 'frostbolt' | 'phial' | 'dragonfire'; /** Time until this announced contact; replaces a client's predicted countdown. */ nextAttackInMs?: number; /** * Set when the attack coming is not the NPC's plain swing: 'dragonfire' * is the dragon drawing breath (dragonfire.ts), with `targetId` the * player it will land on. Absent for every other wind-up. */ attack?: 'dragonfire' | 'signature'; /** With `attack: 'signature'`: which boss signature is coming (bossSignatures.ts). */ signature?: BossSignatureId; /** With a signature that has looks (the Alchemist's phials): which one. */ variant?: number; targetId?: string; } | { /** An announced strike lost eligibility before its authoritative contact. */ type: 'combat_cancel'; attackerId: string; targetId: string; strikeId: string; reason: 'range' | 'blocked' | 'ended'; } | { type: 'welcome'; selfId: string; originChunk: ChunkCoord; chunks: ChunkData[]; players: PlayerPublic[]; npcs: NpcPublic[]; config: ClientConfig; stats: CharacterStats; friends: string[]; inventory: Array; quests: QuestsSnapshot; equipment: EquipmentSlots; weaponPrecision?: number | null; ammoQty?: number; combatStance?: CombatStance; fishingSpot: Vec2; /** Active frog-fishing spots around the pond. */ fishingSpots?: Vec2[]; /** * Marked fishing spots (perch / tench / trout / smelt). Every one is a * fish on a tile: the client draws the fish there and a cast at it lands * on that tile. The pier's smelt move on the lake timer and after each * catch, and arrive by lake_fishing_spots like the perch and tench. */ lakeFishingSpots?: Array<{ kind: LakeFishSpotKind; x: number; y: number; }>; /** Entity kinds this character has identified (server authority). */ identified: string[]; /** Membership unlocks the pet beagle (and whatever else members get). */ isMember?: boolean; /** Null until the rescued beagle has been claimed and named. */ beagleName?: string | null; /** True while the beagle is following; false while dismissed to the bank. */ beagleOut?: boolean; /** The Hog Wild warthog is out: the client must not call the beagle back over it. */ warthogOut?: boolean; /** Persistent fox-NPC kill count for Anne's Protect the Rabbits quest. */ anneFoxKills?: number; /** Persistent Goblin Soldier kill count for the Battlefield Captain. */ captainGoblinKills?: number; /** Where the player has got to in each story quest (storyQuests.ts). */ story?: StoryProgressMap; /** Skill-choice XP waiting for the player to pick a skill. */ xpChoices?: PendingXpChoice[]; /** The player's current Bounty Board contract, if any. */ activeBounty?: ActiveBounty | null; /** The server's Date.now() when sent: the moving train's clock (westernTrain.ts). */ trainClock?: number; /** * The key of the train circuit the server runs (westernTrain.ts WesternTrainCircuit.key, e.g. "v1-1a2b3c4d"), so the * client draws and times THAT circuit whatever its own build would run. Absent: a server from before the circuit * version (slice 2-0), which runs version 1. */ trainCircuit?: string; } | { type: 'join_rejected'; reason: JoinRejectedReason; message: string; } /** * The server closed this session on purpose (the character was logged in * from somewhere else). The client must not reconnect or resume; it shows * the message and goes back to character select. */ | { type: 'session_ended'; message: string; } /** * A server update (a release) is coming or has begun. 'countdown': `remainingMs` is how long is left when this was * sent; the client counts down itself from the moment it arrives. At 0 the server logs everyone out and sends * 'updating' (`message` is shown on the update screen) just before it closes the socket with SERVER_UPDATE_CLOSE_CODE: * the client must not reconnect, it waits for the new server. 'cancelled': the update was called off, hide the countdown. */ | { type: 'server_update'; state: ServerUpdateState; remainingMs?: number; message?: string; } | { type: 'player_joined'; player: PlayerPublic; } | { type: 'player_left'; id: string; } | { type: 'players'; players: PlayerPublic[]; } /** Roster deltas for a client that joined with rosterDelta (rosterDelta.ts): full add once, then field patches, then remove. */ | { type: 'players_delta'; add?: PlayerPublic[]; patch?: PlayerPatch[]; remove?: string[]; } /** Who is online by name (friends list), for a rosterDelta client whose roster only holds the players in view. */ | { type: 'online'; names?: string[]; add?: string; remove?: string; } | { type: 'npcs'; npcs: NpcPublic[]; } | { type: 'pong'; t: number; } /** rainClock: the server's Date.now() when sent, so every client draws the moving train from the same clock. */ | { type: 'chunk_data'; chunks: ChunkData[]; trainClock?: number; trainCircuit?: string; } | { type: 'chunk_unload'; chunks: ChunkCoord[]; } | { type: 'ground_item_add'; cx: number; cz: number; entity: WorldEntity; } | { type: 'ground_item_remove'; entityId: string; } /** * One entity changed in place (a gate swinging, a prop restyled). Replaces * just that model; resending the whole chunk makes the client tear down and * respawn every prop around it, which reads as a visible flash. */ | { type: 'entity_update'; cx: number; cz: number; entity: WorldEntity; } /** * A fixture the world has retired (a den mouth closing behind the player). * The client plays its departure and then disposes it; a whole-chunk * resend would blink it out and rebuild every neighbour prop with it. */ | { type: 'entity_remove'; cx: number; cz: number; entityId: string; } | { type: 'chat'; id: string; name: string; text: string; /** The worn title, shown after the name. */ title?: string; } | { type: 'pm'; from: string; to: string; text: string; } | { type: 'friends'; names: string[]; } /** The names this character has blocked, sent after joining and after every change. */ | { type: 'blocked'; names: string[]; } /** Quest hints can also appear above their NPC; popupText keeps the overhead copy brief. */ | { type: 'notice'; text: string; style?: NoticeStyle; npcId?: string; popupText?: string; } /** * A temporary world effect. Kept generic so future hazards and blessings * share the UI: every active one is a row on the client's boost panel, and * `stat` + `amount` pick the badge it shrinks to when the panel is folded. */ | { type: 'area_effect'; id: string; active: boolean; title: string; detail: string; stat: BoostStat; amount: number; durationMs?: number; remainingMs?: number; /** Set when the row is a ward draught (wards.ts): the HUD draws the ward's own glyph and colour, not the stat's. */ ward?: WardId; } /** * A teleport is about to land the player somewhere the client has to build * from nothing. Sent immediately before that destination's chunk data, so * the client can put its cover up first. See AREA_TRANSITIONS. `title`, when * set, heads the cover instead of the area's own (the cart's stop, the place * a far teleport lands in). */ | { type: 'area_transition'; area: AreaTransitionId; title?: string; } | { type: 'zone'; zone: WorldZoneId; combatTier?: CombatZoneTier | null; } | { type: 'loot'; items: LootItem[]; } /** * The Lost Tools Crate has a tool for this player (the starter hub): the client shows the Reclaim Lost Tool panel, * and its Claim Tool button sends `lost_tool_claim` with the crate's id. */ | { type: 'lost_tool_offer'; entityId: string; kind: string; text: string; } /** * A chest has given up what was in it. The client plays the opening on * that entity (lid, frost, light, sound) and shows the haul on a card; * `rare` marks the openings worth a fanfare. The items are also sent as * a plain `loot` message, which is what writes the log lines. */ | { type: 'chest_opened'; entityId: string; items: LootItem[]; line: string; rare: boolean; } /** * A player has started a timed action at an entity that anyone nearby may * see and break (prying at the Hoard Chest, hoardChest.ts): the client draws * a bar over them for `durationMs`. Sent to everyone watching the chunk. */ | { type: 'channel'; playerId: string; entityId: string; durationMs: number; label: string; } /** That action ended: `done` when it finished, false when it was broken or given up. */ | { type: 'channel_end'; playerId: string; done: boolean; } /** * A world boss ring's countdown (worldBossRing.ts): sent on joining and to everyone whenever the stage changes, so * the ring's sign can show it. `text` is the stage's words, as the chat line says them. */ | { type: 'ringstone'; ringId: string; stage: RingStage; text: string; } /** * A world boss ring's fight (worldBossRing.ts RingFightView, phase 2): sent on joining, on every change (a hold, the * phase, the cage) and every couple of seconds while a fight is on, for the boss bar, the timer, the HELD marks, the * cage and the ring's dressing. */ | ({ type: 'ringstone_fight'; } & RingFightView) /** * A ship's wave fight (crescentQueen.ts ShipFightView, pirate bay phase 4): sent to everyone aboard on every change (a wave in, a * crewman down, a hatch freed, the boss's health) and every couple of seconds while a clock runs; `view` null takes the panel down. */ | { type: 'ship_fight'; view: ShipFightView | null; } /** The Fishing Derby for one player (fishingDerby.ts DerbyView): the board, the plaque and the HUD. */ | { type: 'derby'; view: DerbyView; } /** Lamplight Common's Round for one player (lamplightCommon.ts LampRoundView): their taper's burn, the lamps lit and the moths shooed; `view: null` clears the panel. */ | { type: 'lamp_round'; view: LampRoundView | null; } /** The shipping board at one harbour master (voyages.ts ShippingView): opens or refreshes the panel; `npcId` is the master the buttons answer to. */ | { type: 'shipping'; npcId: string; view: ShippingView; } | { type: 'item_gift'; kind: string; } | { type: 'inventory'; slots: Array; } | { type: 'player_say'; playerId: string; name: string; text: string; } | { type: 'ate'; kind: string; } | { type: 'cooked'; output: string; done: number; total: number; } /** A log has burned down in the mound: `done` of `total` logs, `charcoal` so far. */ | { type: 'charcoal_burned'; done: number; total: number; charcoal: number; } /** The mound was clicked: what the bag can burn, for the panel (charcoal.ts). */ | { type: 'charcoal_open'; woods: Array<{ kind: string; have: number; yield: number; }>; } | { type: 'crafted'; output: string; done: number; total: number; } | { type: 'dropped'; } | { type: 'burrow_disturbed'; x: number; y: number; } /** Server confirmed a kind identification (used when forage level gates the reveal). */ | { type: 'identified'; kind: string; } | { type: 'shop_open'; npcId: string; title: string; buy: ShopBuyEntry[]; sell: ShopSellEntry[]; } | { type: 'shop_close'; } | { type: 'bank_state'; tabs: Array>; } | { type: 'bank_close'; } | { type: 'woodcraft_open'; /** Recipes with the player's current eligibility resolved server-side. */ recipes: Array<{ id: string; output: string; outputQty: number; level: number; xp: number; materials: Array<{ kind: string; qty: number; have: number; }>; craftable: boolean; }>; hasKnife: boolean; } | { type: 'woodcraft_close'; } | { type: 'blacksmith_open'; station: 'anvil' | 'furnace'; entityId: string; hasHammer: boolean; hasTongs: boolean; recipes: Array<{ id: string; output: string; outputQty: number; station: 'anvil' | 'furnace'; category: 'smelting' | 'bronze' | 'iron' | 'steel' | 'silver'; level: number; xp: number; materials: Array<{ kind: string; qty: number; have: number; }>; craftable: boolean; }>; } | { type: 'blacksmith_close'; } | { type: 'brew_open'; entityId: string; /** Corvin's Pestle and Mortar is in the bag: without it nothing brews. */ hasPestle: boolean; /** * A cauldron with its own mortar lends it for one brew (the Western Entrance's porters' cauldron, for Herbal * Remedies' thyme draught): only `recipe` brews with it, one at a time; `text` says so in the panel. */ lent?: { recipe: string; text: string; }; recipes: Array<{ id: string; output: string; tier: 'mini' | 'full'; level: number; xp: number; materials: Array<{ kind: string; qty: number; have: number; }>; craftable: boolean; }>; } | { type: 'brew_close'; } /** * Remaining time inside a paid area, or null once it is spent. * * Sent on login, on purchase, and then only every TIMED_ACCESS_PUSH_MS while * the clock runs. The HUD counts down locally between pushes, so this is a * correction rather than a per-second tick - the timer must not cost a message * a second per player standing in the fishery. */ | { type: 'timed_access'; area: TimedPlaceId | null; msRemaining: number; } /** * Time is up. The client fades out, and the server has already moved the * player, so the fade back in lands them outside the gate. */ | { type: 'timed_access_expired'; area: TimedPlaceId; fadeMs: number; } | { type: 'quests'; quests: QuestsSnapshot; anneFoxKills?: number; captainGoblinKills?: number; story?: StoryProgressMap; xpChoices?: PendingXpChoice[]; } /** A skill-choice reward is waiting: the client opens the picker. Also sent on login while any are pending. */ | { type: 'xp_choice_open'; choices: PendingXpChoice[]; } /** * A story quest step that asks the player to decide (take the bribe, or * turn the guard in). The client shows the options as the NPC's dialog; * a pick goes back as npc_action `story::