diff --git a/server/src/game/GameLoop.ts b/server/src/game/GameLoop.ts index 86cabde..28ff46f 100644 --- a/server/src/game/GameLoop.ts +++ b/server/src/game/GameLoop.ts @@ -109,7 +109,10 @@ export class GameLoop { trunkDecorations: this.map.trunkDecorations, resourceTiles: this.map.resourceTiles, obstacles: new Set(this.map.getObstacles().map(p => `${p.x},${p.y}`)), - foodPositions: [], + berryBushes: [...this.map.berryBushes.entries()].map(([key, state]) => { + const [x, y] = key.split(',').map(Number); + return { x, y, ...state }; + }), }); this.saveManager.saveEntityState(this.world, 0, this.runtimeConstants); this.wirePersistenceCallbacks(); @@ -123,6 +126,15 @@ export class GameLoop { // Load map this.saveManager!.loadMapState(this.map); + // If no berry bushes were loaded (old save), initialize from resource tiles + if (this.map.berryBushes.size === 0) { + for (const rt of this.map.resourceTiles) { + if (rt.resourceType === 'berries') { + this.map.initBerryBush(rt.x, rt.y, industryConfig.berryBushMaxCount); + } + } + } + // Set non-entity singletons this.world.setSingleton('itemRegistry', ItemRegistry.createDefault()); this.world.setSingleton('recipeRegistry', RecipeRegistry.createDefault()); diff --git a/server/src/persistence/__tests__/integration.test.ts b/server/src/persistence/__tests__/integration.test.ts index b0ad906..fb33f75 100644 --- a/server/src/persistence/__tests__/integration.test.ts +++ b/server/src/persistence/__tests__/integration.test.ts @@ -94,7 +94,7 @@ describe('integration: full save/load cycle', () => { trunkDecorations: generated.trunkDecorations, resourceTiles: generated.resourceTiles, obstacles: generated.obstacles, - foodPositions: generated.foodPositions, + berryBushes: [], }; manager.initNewWorld(world, map, tileData); manager.saveEntityState(world, 500); diff --git a/server/src/persistence/__tests__/saveManager.test.ts b/server/src/persistence/__tests__/saveManager.test.ts index 3a67bef..d350646 100644 --- a/server/src/persistence/__tests__/saveManager.test.ts +++ b/server/src/persistence/__tests__/saveManager.test.ts @@ -47,7 +47,7 @@ describe('SaveManager', () => { trunkDecorations: new Array(16).fill(-1), resourceTiles: [], obstacles: new Set(), - foodPositions: [], + berryBushes: [], }; manager.initNewWorld(world, map, tileData); @@ -71,7 +71,7 @@ describe('SaveManager', () => { trunkDecorations: new Array(16).fill(-1), resourceTiles: [{ x: 2, y: 3, resourceType: 'tree' }], obstacles: new Set(['1,1', '2,2']), - foodPositions: [], + berryBushes: [], }; manager.initNewWorld(world, map, tileData); @@ -103,7 +103,7 @@ describe('SaveManager', () => { trunkDecorations: new Array(16).fill(-1), resourceTiles: [], obstacles: new Set(), - foodPositions: [], + berryBushes: [], }; manager.initNewWorld(world, map, tileData); @@ -152,7 +152,7 @@ describe('SaveManager', () => { trunkDecorations: new Array(16).fill(-1), resourceTiles: [], obstacles: new Set(), - foodPositions: [], + berryBushes: [], }; manager.initNewWorld(world, map, tileData); @@ -177,7 +177,7 @@ describe('SaveManager', () => { trunkDecorations: new Array(16).fill(-1), resourceTiles: [], obstacles: new Set(), - foodPositions: [], + berryBushes: [], }; // Open and close, then clear tiles to simulate empty DB diff --git a/server/src/persistence/__tests__/worldSerializer.test.ts b/server/src/persistence/__tests__/worldSerializer.test.ts index 308391e..a154573 100644 --- a/server/src/persistence/__tests__/worldSerializer.test.ts +++ b/server/src/persistence/__tests__/worldSerializer.test.ts @@ -44,7 +44,10 @@ describe('worldSerializer', () => { { x: 2, y: 1, resourceType: 'stone' }, ], obstacles: new Set(['1,0', '2,1']), - foodPositions: [{ x: 0, y: 0 }, { x: 2, y: 1 }], + berryBushes: [ + { x: 0, y: 0, berryCount: 3, maxBerries: 5, ticksSinceLastRegrowth: 0 }, + { x: 2, y: 1, berryCount: 1, maxBerries: 5, ticksSinceLastRegrowth: 10 }, + ], }; saveTiles(width, height, data); @@ -56,7 +59,7 @@ describe('worldSerializer', () => { expect(loaded!.trunkDecorations).toEqual(data.trunkDecorations); expect(loaded!.resourceTiles).toEqual(data.resourceTiles); expect(loaded!.obstacles).toEqual(data.obstacles); - expect(loaded!.foodPositions).toEqual(data.foodPositions); + expect(loaded!.berryBushes).toEqual(data.berryBushes); }); it('returns null when no tiles are saved', () => { @@ -76,7 +79,7 @@ describe('worldSerializer', () => { trunkDecorations: [-1, -1], resourceTiles: [], obstacles: new Set(), - foodPositions: [], + berryBushes: [], }; saveTiles(width, height, data1); @@ -86,7 +89,7 @@ describe('worldSerializer', () => { trunkDecorations: [7, 8], resourceTiles: [{ x: 0, y: 0, resourceType: 'iron' }], obstacles: new Set(['0,0']), - foodPositions: [{ x: 1, y: 0 }], + berryBushes: [{ x: 1, y: 0, berryCount: 5, maxBerries: 5, ticksSinceLastRegrowth: 0 }], }; saveTiles(width, height, data2); diff --git a/server/src/persistence/saveManager.ts b/server/src/persistence/saveManager.ts index 551ea57..bbd319d 100644 --- a/server/src/persistence/saveManager.ts +++ b/server/src/persistence/saveManager.ts @@ -86,6 +86,14 @@ export class SaveManager { map.resourceTiles = tileData.resourceTiles; map.loadObstacles(tileData.obstacles); + for (const bush of tileData.berryBushes) { + map.berryBushes.set(`${bush.x},${bush.y}`, { + berryCount: bush.berryCount, + maxBerries: bush.maxBerries, + ticksSinceLastRegrowth: bush.ticksSinceLastRegrowth, + }); + } + return true; } diff --git a/server/src/persistence/worldSerializer.ts b/server/src/persistence/worldSerializer.ts index 772b4f8..cd2d1fc 100644 --- a/server/src/persistence/worldSerializer.ts +++ b/server/src/persistence/worldSerializer.ts @@ -6,7 +6,7 @@ export interface TileData { trunkDecorations: number[]; resourceTiles: Array<{ x: number; y: number; resourceType: string }>; obstacles: Set; - foodPositions: Array<{ x: number; y: number }>; + berryBushes: Array<{ x: number; y: number; berryCount: number; maxBerries: number; ticksSinceLastRegrowth: number }>; } export function saveTiles(width: number, height: number, data: TileData): void { @@ -45,7 +45,7 @@ export function saveTiles(width: number, height: number, data: TileData): void { "INSERT INTO metadata (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value" ); upsertMeta.run('obstacles', JSON.stringify([...data.obstacles])); - upsertMeta.run('food_positions', JSON.stringify(data.foodPositions)); + upsertMeta.run('berry_bushes', JSON.stringify(data.berryBushes)); }); transaction(); @@ -88,10 +88,10 @@ export function loadTiles(width: number, height: number): TileData | null { const obstaclesRow = db.prepare("SELECT value FROM metadata WHERE key = 'obstacles'").get() as { value: string } | undefined; const obstacles = new Set(obstaclesRow ? JSON.parse(obstaclesRow.value) as string[] : []); - const foodRow = db.prepare("SELECT value FROM metadata WHERE key = 'food_positions'").get() as { value: string } | undefined; - const foodPositions: Array<{ x: number; y: number }> = foodRow ? JSON.parse(foodRow.value) : []; + const berryRow = db.prepare("SELECT value FROM metadata WHERE key = 'berry_bushes'").get() as { value: string } | undefined; + const berryBushes: TileData['berryBushes'] = berryRow ? JSON.parse(berryRow.value) : []; - return { terrain, decorations, trunkDecorations, resourceTiles, obstacles, foodPositions }; + return { terrain, decorations, trunkDecorations, resourceTiles, obstacles, berryBushes }; } export function saveMetadata(data: { tick: number; lastSavedAt: string }): void {