Exact source used by this build. SHA-256: f4954025ce71dab9aa7427b43082895a1288bfce00abd30d68c68a9158fc0e60.
1 import type { MinesContent } from "../../../../content/src/domains/mines/schema";
2 import type { Area, Player, SimRules, TilePosition, WorldState } from "../../types";
3 import type { ItemStack } from "../inventory";
4 import { professionEffects, skillLevel } from "../skills";
5 export type { MinesContent };
6 export interface MineRock { id: string; nodeId: string; x: number; y: number; hits: number }
7 export interface MineMonster { id: string; monsterId: string; x: number; y: number; health: number; attackReadyAtMs: number; hopReadyAtMs: number; stunnedUntilMs: number; facingX: number; facingY: number }
8 export interface MineProjectile { id: string; ownerId: string; x: number; y: number; vx: number; vy: number; damage: number; expiresAtMs: number }
9 export interface MineFloor { number: number; day: string; seed: number; rngState: number; band: string; kind: "normal" | "infested" | "mushroom" | "treasure"; rocks: Record<string, MineRock>; monsters: Record<string, MineMonster>; ladder: TilePosition | null; chest: (TilePosition & { opened: boolean }) | null; mushrooms: Record<string, TilePosition & { itemId: string }>; projectiles: MineProjectile[]; nextProjectileId: number }
10 export interface MinesState { worldSeed: number | null; elevatorFloors: number[]; timeMs: number; claimedTreasureFloors: number[] }
11 export interface MinePlayerState { health: number; maxHealth: number; equipment: { weapon: string | null; rings: [string | null, string | null]; boots: string | null }; invulnerableUntilMs: number; attackReadyAtMs: number; specialReadyAtMs: number; blockUntilMs: number; miningReadyAtMs: number; starterReceived: boolean; lastKnockout: { goldLost: number; items: ItemStack[]; day: string } | null; returnedTools: ItemStack[] }
12 export const mineWorldDefaults = (): MinesState => ({ worldSeed: null, elevatorFloors: [0], timeMs: 0, claimedTreasureFloors: [] });
13 export const minePlayerDefaults = (): MinePlayerState => ({ health: 100, maxHealth: 100, equipment: { weapon: null, rings: [null, null], boots: null }, invulnerableUntilMs: 0, attackReadyAtMs: 0, specialReadyAtMs: 0, blockUntilMs: 0, miningReadyAtMs: 0, starterReceived: false, lastKnockout: null, returnedTools: [] });
14 export function getMines(state: WorldState): MinesState { return { ...mineWorldDefaults(), ...state.systems.mines as Partial<MinesState> | undefined }; }
15 export function getMinePlayer(player: Player): MinePlayerState { const saved = player.systems.mines as Partial<MinePlayerState> | undefined; return { ...minePlayerDefaults(), ...saved, equipment: { ...minePlayerDefaults().equipment, ...saved?.equipment } }; }
16 export function getMineContent(rules: SimRules): MinesContent | undefined { const data = rules.content?.mines; return data && typeof data === "object" && "config" in data ? data as MinesContent : undefined; }
17 export function mineAreaId(floor: number): string { return `mine_floor_${floor}`; }
18 export function getMineFloor(state: WorldState, floorOrAreaId?: number | string): MineFloor | undefined { const area = floorOrAreaId === undefined ? Object.values(state.areas).find((entry) => entry.systems.mines) : state.areas[typeof floorOrAreaId === "number" ? mineAreaId(floorOrAreaId) : floorOrAreaId]; return area?.systems.mines as MineFloor | undefined; }
19 export function setMinePlayer(player: Player, mine: MinePlayerState): Player { return { ...player, systems: { ...player.systems, mines: mine } }; }
20 /** Shared vitals adapter for food and other health consumers; combat owns health. */
21 export function setMineHealth(player: Player, health: number, maxHealth = getMinePlayer(player).maxHealth): Player {
22 return Number.isFinite(health) && Number.isFinite(maxHealth) && maxHealth > 0
23 ? setMinePlayer(player, { ...getMinePlayer(player), health: Math.max(0, Math.min(maxHealth, health)), maxHealth }) : player;
24 }
25 export function setMines(state: WorldState, mines: MinesState): WorldState { return { ...state, revision: state.revision + 1, systems: { ...state.systems, mines } }; }
26 export function setMineFloor(state: WorldState, area: Area, floor: MineFloor): WorldState { return { ...state, revision: state.revision + 1, areas: { ...state.areas, [area.id]: { ...area, systems: { ...area.systems, mines: floor } } } }; }
27 export function getMineEquipmentEffects(player: Player, rules: SimRules): Record<"glow" | "magnet" | "defense" | "attack" | "speed" | "lifeSteal", number> {
28 const data = getMineContent(rules), mine = getMinePlayer(player), effects = { glow: 0, magnet: 0, defense: 0, attack: 0, speed: 0, lifeSteal: 0 };
29 for (const id of mine.equipment.rings) for (const effect of data?.rings.find((ring) => ring.itemId === id)?.effects ?? []) effects[effect.kind] += effect.value;
30 effects.defense += data?.boots.find((boots) => boots.itemId === mine.equipment.boots)?.defense ?? 0;
31 return effects;
32 }
33 export function maxMineHealth(player: Player, data: MinesContent, rules: SimRules): number { const level = skillLevel(player, "combat"); const hooks = professionEffects(player, rules).filter((effect) => effect.kind === "hook").map((effect) => effect.hook); const bonus = data.config.combatProfessionHealth.filter((entry) => hooks.includes(entry.hook as typeof hooks[number])).reduce((total, entry) => total + entry.bonus, 0); return bonus + data.config.baseHealth + (level - data.config.healthExcludedLevels.filter((value) => value <= level).length) * data.config.healthPerCombatLevel; }
34 export function mineDay(state: WorldState): string { return `${state.clock.year}:${state.clock.season}:${state.clock.day}`; }
35 export function entrancePosition(state: WorldState, data: MinesContent): TilePosition & { areaId: string } { const config = data.config.entrance, warp = state.areas[config.areaId]?.warps.find((entry) => entry.id === config.warpId); return { areaId: config.areaId, x: warp ? warp.x + warp.width / 2 : config.x, y: warp ? warp.y + warp.height / 2 : config.y }; }
36