Exact source used by this build. SHA-256: 9f1ed59d276830f27ad9869ae77e4a65301aaa3b7b254bb3fc6d8693c3d968ab.

1 import type { Area, AreaConfig, Player, SimRules, Tile, WorldConfig, WorldState } from "./types";
2 import { systemRegistry } from "./systems";
3 export { MAX_ENERGY } from "./systems/energy";
4 export { tileIndex } from "./state";
5 
6 export function makeArea(config: AreaConfig): Area {
7   const length = config.width * config.height;
8   const tile: Tile = { ground: config.ground, tillable: config.tillable, tilled: false, watered: false, objectId: null, crop: null };
9   return {
10     id: config.id, name: config.name, width: config.width, height: config.height,
11     clockRateMsPerGameMinute: config.clockRateMsPerGameMinute ?? 700,
12     spawn: config.spawn ?? { x: 0, y: 0 }, landmarks: config.landmarks ?? [],
13     pois: config.pois?.map((poi) => ({ ...poi })) ?? [],
14     outdoor: config.outdoor ?? true, locked: config.locked ?? false,
15     collision: config.collision?.slice() ?? Array<boolean>(length).fill(false),
16     groundTiles: config.groundTiles?.slice() ?? Array<number>(length).fill(1),
17     decorTiles: config.decorTiles?.slice() ?? Array<number>(length).fill(0),
18     warps: config.warps?.map((warp) => ({ ...warp })) ?? [], seasons: config.seasons,
19     systems: {},
20     tiles: Array.from({ length }, (_, index) => ({ ...tile, tillable: config.tillableTiles?.[index] ?? tile.tillable })),
21   };
22 }
23 export function createWorld(seed: number, config: WorldConfig): WorldState {
24   const state = {
25     ...systemRegistry.initializeWorld(config),
26     revision: 0, areas: Object.fromEntries(config.areas.map((area) => [area.id, makeArea(area)])), players: {}, rngState: seed >>> 0,
27     starterPlayer: config.starterPlayer ?? { areaId: config.areas[0]?.id ?? "", x: config.areas[0]?.spawn?.x ?? 0, y: config.areas[0]?.spawn?.y ?? 0, facing: "down", inventory: {} },
28     systems: systemRegistry.worldDefaults(),
29   };
30   return state as WorldState;
31 }
32 export function addPlayer(state: WorldState, input: { id: string; name: string; areaId?: string }, rules: SimRules = { crops: {} }): WorldState {
33   if (state.players[input.id]) return state;
34   const areaId = input.areaId ?? state.starterPlayer.areaId;
35   if (!areaId || !state.areas[areaId]) throw new Error("Cannot add a player without an area");
36   const details = { id: input.id, name: input.name, areaId };
37   const player = { ...systemRegistry.initializePlayer(state, details), ...details, connected: true, systems: systemRegistry.playerDefaults() } as Player;
38   const next = { ...state, revision: state.revision + 1, players: { ...state.players, [input.id]: player } };
39   return systemRegistry.runHook("onPlayerAdded", next, rules, { playerId: input.id }).state;
40 }
41 export function setPlayerConnected(state: WorldState, id: string, connected: boolean, rules: SimRules = { crops: {} }): WorldState {
42   const player = state.players[id];
43   if (!player || player.connected === connected) return state;
44   const next = { ...state, revision: state.revision + 1, players: { ...state.players, [id]: { ...player, connected } } };
45   return systemRegistry.runHook("onPresenceChange", next, rules, { playerId: id, connected }).state;
46 }
47 /** Adds defaults when a save predates a newly registered system, preserving existing slices. */
48 export function initializeSystemState(state: WorldState): WorldState {
49   return systemRegistry.initializeState(state);
50 }
51 /** A recipient sees only the area and player documents belonging to their current area. */
52 export function scopeWorld(state: WorldState, playerId: string): WorldState {
53   const areaId = state.players[playerId]?.areaId;
54   return { ...state, areas: areaId && state.areas[areaId] ? { [areaId]: state.areas[areaId] } : {}, players: Object.fromEntries(Object.entries(state.players).filter(([, player]) => player.areaId === areaId)) };
55 }
56