diff --git a/server/src/systems/__tests__/socialSystem.test.ts b/server/src/systems/__tests__/socialSystem.test.ts index 3f37066..6f93a15 100644 --- a/server/src/systems/__tests__/socialSystem.test.ts +++ b/server/src/systems/__tests__/socialSystem.test.ts @@ -109,10 +109,10 @@ describe('socialSystem', () => { expect(sb.phase).toBe('none'); }); - it('rejects target on rest goal with low energy', () => { + it('rejects target on sleep goal with low energy', () => { const world = new World(); const a = createNPC(world, 5, 5); - const b = createNPC(world, 8, 5, { goal: 'rest', energy: ENERGY_THRESHOLD - 5 }); + const b = createNPC(world, 8, 5, { goal: 'sleep', energy: ENERGY_THRESHOLD - 5 }); socialSystem(world); const sa = world.getComponent(a, 'socialState')!; const sb = world.getComponent(b, 'socialState')!; diff --git a/server/src/systems/__tests__/systems.test.ts b/server/src/systems/__tests__/systems.test.ts index 73d15fe..ea3e537 100644 --- a/server/src/systems/__tests__/systems.test.ts +++ b/server/src/systems/__tests__/systems.test.ts @@ -127,14 +127,14 @@ describe('npcBrainSystem', () => { expect(brain.currentGoal).toBe('eat'); }); - it('sets rest goal when energy is low', () => { + it('sets sleep goal when energy is low', () => { const world = new World(); const map = new GameMap(10, 10); map.addPointOfInterest({ type: 'rest', position: { x: 7, y: 7 } }); const e = createNPC(world, 5, 5, 80, 10); npcBrainSystem(world, map); const brain = world.getComponent(e, 'npcBrain')!; - expect(brain.currentGoal).toBe('rest'); + expect(brain.currentGoal).toBe('sleep'); }); it('prioritizes energy over hunger', () => { @@ -145,7 +145,7 @@ describe('npcBrainSystem', () => { const e = createNPC(world, 5, 5, 15, 10); npcBrainSystem(world, map); const brain = world.getComponent(e, 'npcBrain')!; - expect(brain.currentGoal).toBe('rest'); + expect(brain.currentGoal).toBe('sleep'); }); it('sets gather goal when productivity is low', () => { @@ -160,7 +160,7 @@ describe('npcBrainSystem', () => { expect(brain.currentGoal).toBe('gather'); }); - it('prioritizes rest and eat over gather', () => { + it('prioritizes sleep and eat over gather', () => { const world = new World(); const map = new GameMap(10, 10); map.addPointOfInterest({ type: 'rest', position: { x: 7, y: 7 } }); @@ -170,7 +170,7 @@ describe('npcBrainSystem', () => { needs.productivity = 30; npcBrainSystem(world, map); const brain = world.getComponent(e, 'npcBrain')!; - expect(brain.currentGoal).toBe('rest'); + expect(brain.currentGoal).toBe('sleep'); }); it('sets wander when productivity is above threshold', () => { diff --git a/server/src/systems/industrySystem.ts b/server/src/systems/industrySystem.ts index 44f2086..b315c5b 100644 --- a/server/src/systems/industrySystem.ts +++ b/server/src/systems/industrySystem.ts @@ -55,7 +55,7 @@ export function industrySystem(world: World, map: GameMap): void { if (brain.currentGoal === 'craft' && world.getComponent(entity, 'craftingState')) continue; if (brain.currentGoal === 'build' && world.getComponent(entity, 'buildingState')) continue; if (brain.currentGoal === 'gather' && world.getComponent(entity, 'gatheringState')) continue; - if (brain.currentGoal === 'gather' || brain.currentGoal === 'eat' || brain.currentGoal === 'rest') continue; + if (brain.currentGoal === 'gather' || brain.currentGoal === 'eat' || brain.currentGoal === 'sleep') continue; if (brain.currentGoal === 'dropoff') continue; if (brain.currentGoal === 'pickup') continue; diff --git a/server/src/systems/npcBrainSystem.ts b/server/src/systems/npcBrainSystem.ts index 81ae51e..2124584 100644 --- a/server/src/systems/npcBrainSystem.ts +++ b/server/src/systems/npcBrainSystem.ts @@ -78,7 +78,7 @@ export function npcBrainSystem(world: World, map: GameMap, eventMemoryService?: } brain.currentGoal = null; } - if (brain.currentGoal === 'rest' && movement.state === 'idle') { + if (brain.currentGoal === 'sleep' && movement.state === 'idle') { const prevEnergy = needs.energy; needs.energy = Math.min(100, needs.energy + 20); if (eventMemoryService && prevEnergy < ENERGY_THRESHOLD && needs.energy >= ENERGY_THRESHOLD) { @@ -117,7 +117,7 @@ export function npcBrainSystem(world: World, map: GameMap, eventMemoryService?: // Determine new goal based on needs priority const prevGoal = brain.currentGoal; let goal: GoalType = 'wander'; - if (needs.energy < ENERGY_THRESHOLD) goal = 'rest'; + if (needs.energy < ENERGY_THRESHOLD) goal = 'sleep'; else if (needs.hunger < HUNGER_THRESHOLD) goal = 'eat'; else if (needs.productivity < PRODUCTIVITY_THRESHOLD) goal = 'gather'; @@ -125,7 +125,7 @@ export function npcBrainSystem(world: World, map: GameMap, eventMemoryService?: if (eventMemoryService && brain.currentGoal !== prevGoal && prevGoal !== null) { const name = world.getComponent(entity, 'name') ?? 'Unknown'; - const goalLabels: Record = { wander: 'wandering', eat: 'looking for food', rest: 'looking for rest', gather: 'looking for resources', craft: 'crafting', build: 'building', dropoff: 'dropping off items', pickup: 'picking up materials' }; + const goalLabels: Record = { wander: 'wandering', eat: 'looking for food', sleep: 'sleeping', gather: 'looking for resources', craft: 'crafting', build: 'building', dropoff: 'dropping off items', pickup: 'picking up materials' }; eventMemoryService.record(entity, { type: 'goal_change', tick: 0, @@ -139,7 +139,7 @@ export function npcBrainSystem(world: World, map: GameMap, eventMemoryService?: let target: Position | null = null; if (goal === 'eat') { target = closestPOI(map, pos, 'food'); - } else if (goal === 'rest') { + } else if (goal === 'sleep') { target = closestPOI(map, pos, 'rest'); } else if (goal === 'gather') { target = findNearestResource(map, pos); diff --git a/server/src/systems/socialSystem.ts b/server/src/systems/socialSystem.ts index a0096fe..0b1d66f 100644 --- a/server/src/systems/socialSystem.ts +++ b/server/src/systems/socialSystem.ts @@ -33,7 +33,7 @@ function isTargetBusy(world: World, target: EntityId): boolean { const needs = world.getComponent(target, 'needs'); if (!needs) return false; if (brain.currentGoal === 'eat' && needs.hunger < HUNGER_THRESHOLD) return true; - if (brain.currentGoal === 'rest' && needs.energy < ENERGY_THRESHOLD) return true; + if (brain.currentGoal === 'sleep' && needs.energy < ENERGY_THRESHOLD) return true; return false; } diff --git a/shared/src/constants.ts b/shared/src/constants.ts index 03bf607..080496d 100644 --- a/shared/src/constants.ts +++ b/shared/src/constants.ts @@ -16,6 +16,14 @@ export const HUNGER_THRESHOLD = 30; export const ENERGY_THRESHOLD = 20; export const NEED_RECOVERY_RATE = 0.5; +// Sleep system +export const SLEEP_ENERGY_RECOVERY_PER_TICK = 0.048; // ~80 energy over one night (~1667 ticks) +export const SLEEP_HUNGER_DECAY_MULTIPLIER = 0.5; // hunger decays at half rate while sleeping +export const SLEEP_WAKE_THRESHOLD = 85; // energy level where waking chance begins +export const SLEEP_VOLUNTARY_ENERGY_THRESHOLD = 60; // nighttime voluntary sleep threshold +export const SLEEP_NIGHT_START = 0.667; // gameTime when night begins (DAY_HOURS/TOTAL_HOURS) +export const SLEEP_NIGHT_END = 1.0; // gameTime when night ends (wraps to 0.0) + // Productivity need export const PRODUCTIVITY_DECAY_PER_TICK = 0.02; export const PRODUCTIVITY_THRESHOLD = 40; diff --git a/shared/src/types.ts b/shared/src/types.ts index 954d61c..ef33a64 100644 --- a/shared/src/types.ts +++ b/shared/src/types.ts @@ -80,7 +80,7 @@ export interface StatModifiers { } export type MovementState = 'idle' | 'walking'; -export type GoalType = 'wander' | 'eat' | 'rest' | 'gather' | 'craft' | 'build' | 'dropoff' | 'pickup'; +export type GoalType = 'wander' | 'eat' | 'sleep' | 'gather' | 'craft' | 'build' | 'dropoff' | 'pickup'; export interface Movement { state: MovementState;