Implements time-based behavior transitions for NPCs:
- GameTime converts real time to game time (1 real min = 1 game hour)
- ScheduleEntry defines hour/state/location/data transitions
- NpcSchedule manages multiple entries with midnight wrapping
- process_schedules() applies transitions when game hour changes
- TOML support for schedule data in mob templates
- Integrated into game loop with hourly checks
Tests cover schedule transitions, game time calculation, TOML loading, and preventing duplicate processing.
Adds behavior state tracking to Mob entity with five states: idle, patrol,
converse, flee, and working. Each state has specific processing logic:
- idle: no-op (existing wander logic handles movement)
- patrol: cycles through waypoints with toroidal wrapping support
- converse: stationary during player-driven dialogue
- flee: moves away from threat coordinates
- working: stationary NPC at their post
The behavior module is self-contained and testable, ready for integration
with mob_ai.py in a later step.
Objects were comparing by value instead of identity, causing
list.remove() to remove the wrong object when moving items with
identical attributes. Set eq=False on all dataclasses to use
identity-based comparison.
ThingTemplate dataclass mirrors MobTemplate pattern. load_thing_template
and load_thing_templates parse TOML files from content/things/. spawn_thing
creates Thing instances from templates. Includes rock and fountain examples.
Thing is an Object subclass with description, portable flag, and aliases.
Entity.can_accept() returns True for portable Things, enabling the
containment model where entities carry items in their contents.
Entity gains location from Object and narrows x/y back to int
(entities always have spatial coordinates). No behavioral change —
all existing tests pass unchanged.
Phase 1 of fightable mobs: MobTemplate dataclass loaded from TOML,
global mobs list, spawn_mob/despawn_mob/get_nearby_mob with
wrapping-aware distance. Mob entity gets moves and next_action_at fields.
Defenses now work outside combat mode with stamina cost, recovery lock
(based on timing_window_ms), and broadcast to nearby players. Lock
prevents spamming defenses — you commit to the move. Stamina deduction
moved from encounter.defend() to do_defend command layer. Defense
commands registered with mode="*" instead of "combat".