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