/** * THE MARKSMEN RANGE (wave 3, 2026-10-19; dreadwoods-backend/COVERAGE_1_40.md section 6, "Held back for wave 3"): a dedicated Archery trainer past the War Camp's * long butts. Archery 30-40 had one trainer, the three butts of the War Camp drill ground (warCampDrill.ts), and nothing that is more than a butt. This is a * fenced yard on the open snow NORTH of the garrison camp (nothing else stands there; the wolves keep to the east side), a few steps from the camp's hearth, with * no gate to open and no bank (a bank is a trip back to a town): * * - THREE LANES (12, 18 and 22 tiles), each a drill target on the Beacon Ledge's and the War Camp's pattern (a band of Archery levels, a firing tile of its own, * no chained bow lent: an archer at 30 owns a bow); * - the RANGE-MASTER, Edda Fenn, who runs a TIMED ROUND: sixty seconds from the first arrow, one arrow a drill beat at the lane you choose, each scored by the * lane (the near lane is easy and pays one point, the far lane is hard and pays three; skill and level move the odds), a gold, silver or bronze mark for the * total, paid once an hour per player (so a better round is always free but pays nothing more that hour), and a SCOREBOARD of the week's five best rounds that * she reads out and the world remembers across a restart; * - one quest of hers, Three Lanes, that walks a new arrival down all three. * * Every number is a WORLD tile (x east, z south). Every level, count, point and prize is a PLACEHOLDER for the balance pass (James, 2026-10-12: balance last). * Nothing here is 41+: the top of every band is 40. Drawn from the camp's own winter kit as stand-ins (backend upgrades list `marksmen-*`). */ import type { StoryQuestDef } from './storyQuests.js'; export declare const MARKSMEN_RANGE_NAME = "The Marksmen Range"; export declare const MARKSMEN_MASTER_NAME = "Range-Master Edda Fenn"; export declare const MARKSMEN_MASTER_ID = "mrange-master"; /** The fence line (the yard's outside); the yard is levelled to one height with the camp's and eased three tiles into the land. */ export declare const MARKSMEN_FENCE_BOX: { readonly minX: 108; readonly maxX: 141; readonly minZ: 444; readonly maxZ: 472; }; export declare const MARKSMEN_HEIGHT = 2; export declare const MARKSMEN_EASE = 3; /** The gap in the south fence (five tiles of palisade left out; the spears' footprint closes the two outer ones, so three are walkable: x 119-121) and the cleared lane from it to the camp's north edge. */ export declare const MARKSMEN_GATE: { readonly z: 472; readonly minX: 118; readonly maxX: 122; }; export declare const MARKSMEN_GATE_OPEN: { readonly minX: 119; readonly maxX: 121; }; export declare const MARKSMEN_LANE_IN: { readonly minX: 118; readonly maxX: 122; readonly minZ: 473; readonly maxZ: 477; }; /** Where a player is told to stand to start (just inside the gate). */ export declare const MARKSMEN_STAND: { readonly x: 120; readonly z: 470; }; /** Every lane is shot from this row, the targets stand 12, 18 and 22 tiles north of it. */ export declare const MARKSMEN_FIRING_Z = 468; /** The Range-Master stands on the firing line between the first two lanes, facing the targets. */ export declare const MARKSMEN_MASTER_SPOT: { readonly x: 119; readonly z: 469; readonly faceX: 119; readonly faceZ: 462; readonly leash: 1; }; export declare function inMarksmenRange(x: number, z: number, pad?: number): boolean; /** Distance in tiles from the fence box (0 inside it); the backend levels the ground within a few tiles of it. */ export declare function marksmenEdgeDistance(x: number, z: number): number; /** Is this tile in the cleared lane from the camp to the gate? */ export declare function inMarksmenLaneIn(x: number, z: number): boolean; export declare const MARKSMEN_LANE_ID_PREFIX = "mrange-lane-"; export declare const MARKSMEN_MIN_ARCHERY = 30; export type MarksmenBand = { min: number; max: number; xp: number; }; export type MarksmenLane = { id: string; x: number; z: number; /** The firing tile of the lane. */ fx: number; fz: number; /** Tiles from the firing tile to the target. */ distance: number; /** Points a hit scores in a timed round. */ points: number; /** The chance a shot at Archery 30 lands; each Archery level above adds MARKSMEN_HIT_PER_LEVEL. */ base: number; /** The practice band: the lane teaches from `min` to `max` at `xp` a beat (every beat, as the War Camp's butts do). */ band: MarksmenBand; }; /** One band per lane (the War Camp's pattern): the near lane stops paying at 36, the middle at 38, the far one at 40, and the farther the lane the more a beat pays. */ export declare const MARKSMEN_LANES: readonly MarksmenLane[]; export declare function marksmenLaneAt(x: number, z: number): MarksmenLane | undefined; export declare function marksmenLaneById(id: string | undefined | null): MarksmenLane | undefined; export declare function isMarksmenLaneId(id: string | undefined | null): boolean; /** The archery band of a lane, or null when the id is not one (the backend's archeryTargetBand reads this). */ export declare function marksmenLaneBand(id: string | undefined | null): MarksmenBand | null; /** The round runs this long from the first arrow. */ export declare const MARKSMEN_ROUND_MS = 60000; /** A round taken and never started is forgotten after this long. */ export declare const MARKSMEN_SESSION_TTL_MS: number; export declare const MARKSMEN_HIT_PER_LEVEL = 0.012; export declare const MARKSMEN_HIT_MIN = 0.1; export declare const MARKSMEN_HIT_MAX = 0.95; /** The chance one arrow lands in a lane: the lane's base at Archery 30, a little more for each level above, clamped. */ export declare function marksmenHitChance(archery: number, lane: Pick): number; /** Points one arrow scores: the lane's points on a hit (roll under the chance), nothing on a miss. */ export declare function marksmenArrowScore(archery: number, lane: Pick, roll: number): number; export type MarksmenPlacing = 'gold' | 'silver' | 'bronze' | 'none'; export declare const MARKSMEN_TIERS: { readonly gold: 48; readonly silver: 38; readonly bronze: 28; }; export declare function marksmenPlacing(total: number): MarksmenPlacing; export type MarksmenPrize = { coins: number; archeryXp: number; explorerXp: number; }; export declare const MARKSMEN_PRIZES: Readonly>; /** A placing pays once a window per player; another round is always free but pays nothing more in the window. */ export declare const MARKSMEN_PRIZE_WINDOW_MS: number; export declare const MARKSMEN_BOARD_SIZE = 5; export declare const MARKSMEN_BOARD_TIMER_KEY = "marksmen-range"; export type MarksmenRow = { name: string; score: number; at: number; }; export type MarksmenSave = { week: string; rows: MarksmenRow[]; }; /** The UTC ISO week a moment falls in, "2026-W43": the board starts again each Monday. */ export declare function marksmenWeekKey(nowMs: number): string; export declare function emptyMarksmenSave(nowMs: number): MarksmenSave; /** The board as the world saved it, or a fresh one: nothing unreadable is trusted. */ export declare function parseMarksmenSave(raw: unknown, nowMs: number): MarksmenSave; /** Best first; a tie goes to whoever scored it first. */ export declare function rankMarksmenRows(rows: readonly MarksmenRow[]): MarksmenRow[]; /** * A finished round goes on the board: one place per name (only a better round replaces a worse one), a new week starts a fresh board, and a score of nothing * is never written. Returns the new board and whether this round changed it. */ export declare function recordMarksmenScore(save: MarksmenSave, name: string, score: number, nowMs: number): { save: MarksmenSave; changed: boolean; place: number; }; /** 1-based place on a board, or 0 when not on it. */ export declare function marksmenPlaceOf(rows: readonly MarksmenRow[], name: string): number; export declare function marksmenBoardText(save: MarksmenSave): string; /** npc_action ids the Range-Master answers (the client sends them on untouched). */ export declare const MARKSMEN_ACTION_PREFIX = "mrange:"; export declare const MARKSMEN_ACTIONS: { readonly begin: "mrange:begin"; readonly board: "mrange:board"; readonly stop: "mrange:stop"; }; export declare const MARKSMEN_OPTION_LABELS: Readonly>; /** What the range says (the customer's words, not the code's). */ export declare const MARKSMEN_LINES: { readonly tooEarly: "These lanes want a steadier draw than yours. Practise on the War Camp butts or the Beacon Ledge until your Archery reaches the figure on the sign."; readonly capped: "This lane has nothing more to teach you. Try the next one along, or run a timed round for the board."; readonly allCapped: "The range has nothing more to teach you. Run a timed round for the board, or find a real target."; readonly start: "You nock a practice arrow and draw on the target, a long way up the yard."; readonly noBow: "You need a bow in hand to shoot the range."; readonly needArchery: "The range is for archers of Archery 30 and over. Practise on the War Camp butts first."; readonly inFight: "Finish your fight first."; readonly alreadyRunning: "Your round is on. Shoot any lane, and the minute is out when the last arrow is."; readonly stopped: "Round called off. Nothing is scored and nothing is paid."; readonly noRound: "You have no round running."; readonly waiting: "Round entered. The minute starts with your first arrow: take a firing mark and shoot any lane."; }; export declare const MARKSMEN_RULES_TEXT: string; export type MarksmenFolk = { readonly id: string; readonly name: string; readonly place: string; readonly x: number; readonly z: number; readonly faceX: number; readonly faceZ: number; readonly leash: number; readonly speak: string; readonly extras: ReadonlyArray<{ readonly id: string; readonly label: string; readonly say: string; }>; }; export declare const MARKSMEN_FOLK: ReadonlyArray; export declare function marksmenFolkByName(name: string | undefined): MarksmenFolk | null; export declare function marksmenFolkById(id: string | undefined): MarksmenFolk | null; export type MarksmenPiece = { id: string; kind: string; style: string; x: number; z: number; }; /** The fence: the camp's own spear palisade on every tile of the box's edge, but the gap in the south run. */ export declare function marksmenFence(): MarksmenPiece[]; /** The yard's working pieces: the lane targets (real `yardprop` entities the archery drill reads), backstops, firing marks, rack, water, braziers and the sign. */ export declare const MARKSMEN_STATIONS: readonly MarksmenPiece[]; export declare const MARKSMEN_QUEST_IDS: readonly ["marksmenThreeLanes"]; export type MarksmenQuestId = (typeof MARKSMEN_QUEST_IDS)[number]; export declare const MARKSMEN_QUEST_GIVER_TEXT = "Range-Master Edda Fenn keeps The Marksmen Range, the fenced yard just north of the garrison camp beyond the Northern Gate."; export declare const MARKSMEN_QUESTS: Readonly>; export declare const MARKSMEN_QUEST_META: Readonly>; export declare const MARKSMEN_GIVER_PLACES: Readonly>; //# sourceMappingURL=marksmenRange.d.ts.map