const FISH_STATS_ID = "mod:fish-stats"; const FISH_STAMINA_COST = 5; const FISH_MIN_STAMINA = 5; export default { manifest: { id: "sample-game-mod", version: "0.1.0" }, hooks: { onDayStart(ctx) { const { world, time, emit } = ctx; const { day, season } = time.get(); let stats = world.get("mod:sample-stats"); if (!stats) { stats = world.create({ id: "mod:sample-stats", type: "mod-state", greetings: 0 }); } stats.greetings += 1; let mayor = world.get("npc:mayor"); if (!mayor) { mayor = world.create({ id: "npc:mayor", type: "npc", x: 6, y: 3, name: "Mayor", label: "Mayor", dialogue: "mayor", }); } const routineState = world.get("sm:mayor-routine")?.state ?? "sleeping"; const walkX = routineState === "market-day" ? 12 : 6; const walkY = 3 + (day % 4); mayor.x = walkX; mayor.y = walkY; const weather = world.get("weather")?.weatherId ?? "unknown"; emit("mod:sample-day-start", { modId: "sample-game-mod", day, season, weather, greetings: stats.greetings, mayor: { x: walkX, y: walkY }, }); }, onPlayerIntent(ctx) { const intent = ctx.payload; if (!intent || typeof intent !== "object") return; if (intent.action !== "interact" || intent.ok !== true) return; const tile = intent.tile; if (!tile || typeof tile.x !== "number" || typeof tile.y !== "number") return; if (!isWaterTile(ctx, tile.x, tile.y)) return; const player = ctx.world.get("player"); if (!player) return; const stamina = typeof player.stamina === "number" ? player.stamina : 100; const day = ctx.time.get().day; if (stamina < FISH_MIN_STAMINA) { ctx.emit("mod:fish-too-tired", { x: tile.x, y: tile.y, stamina, day }); return; } const inventory = isRecord(player.inventory) ? player.inventory : {}; player.inventory = inventory; const total = (inventory.fish ?? 0) + 1; inventory.fish = total; player.stamina = stamina - FISH_STAMINA_COST; const skills = isRecord(player.skills) ? player.skills : {}; player.skills = skills; skills.fishing = (skills.fishing ?? 0) + 1; const stats = fishStats(ctx); stats.catches += 1; ctx.emit("item-acquire", { itemId: "fish", qty: 1, total }); ctx.emit("mod:fish-caught", { fish: "fish", size: 1 + ((stats.catches - 1) % 3), x: tile.x, y: tile.y, stamina: player.stamina, catches: stats.catches, day, }); ctx.world.remove(`plot:${tile.x}:${tile.y}`); }, }, }; function isWaterTile(ctx, x, y) { const map = ctx.world.query({ type: "map" })[0]; if (!map) return false; const layers = Array.isArray(map.layers) ? map.layers : []; const layer = layers.find((candidate) => candidate.name === "ground") ?? layers[0]; if (!layer || !Array.isArray(layer.data)) return false; return layer.data[y]?.[x] === "water"; } function fishStats(ctx) { let stats = ctx.world.get(FISH_STATS_ID); if (!stats) { stats = ctx.world.create({ id: FISH_STATS_ID, type: "mod-state", catches: 0 }); } return stats; } function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }