mud/src/mudlib/gametime.py
Jared Miller 52f49104eb
Add NPC schedule system with game time
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.
2026-02-14 14:31:39 -05:00

90 lines
2.7 KiB
Python

"""Game time system for converting real time to in-game time."""
import time
class GameTime:
"""Tracks game time based on real time with configurable ratio.
By default: 1 real minute = 1 game hour (24 real minutes = 1 game day).
"""
def __init__(self, epoch: float, real_minutes_per_game_hour: float = 1.0):
"""Initialize game time.
Args:
epoch: Unix timestamp for game time hour 0
real_minutes_per_game_hour: Real minutes per game hour (default 1.0)
"""
self.epoch = epoch
self.real_minutes_per_game_hour = real_minutes_per_game_hour
def get_game_hour(self) -> int:
"""Get current game hour (0-23)."""
elapsed_real_seconds = time.time() - self.epoch
elapsed_real_minutes = elapsed_real_seconds / 60
elapsed_game_hours = elapsed_real_minutes / self.real_minutes_per_game_hour
return int(elapsed_game_hours) % 24
def get_game_time(self) -> tuple[int, int]:
"""Get current game time as (hour, minute).
Returns:
Tuple of (hour 0-23, minute 0-59)
"""
elapsed_real_seconds = time.time() - self.epoch
elapsed_real_minutes = elapsed_real_seconds / 60
elapsed_game_hours = elapsed_real_minutes / self.real_minutes_per_game_hour
hour = int(elapsed_game_hours) % 24
minute_fraction = elapsed_game_hours - int(elapsed_game_hours)
minute = int(minute_fraction * 60) % 60
return hour, minute
# Global game time instance (initialized at server startup)
_game_time: GameTime | None = None
def init_game_time(
epoch: float | None = None, real_minutes_per_game_hour: float = 1.0
) -> None:
"""Initialize the global game time instance.
Args:
epoch: Unix timestamp for game time hour 0 (default: current time)
real_minutes_per_game_hour: Real minutes per game hour (default 1.0)
"""
global _game_time
if epoch is None:
epoch = time.time()
_game_time = GameTime(epoch, real_minutes_per_game_hour)
def get_game_hour() -> int:
"""Get current game hour from global instance.
Returns:
Current game hour (0-23)
Raises:
RuntimeError: If game time not initialized
"""
if _game_time is None:
raise RuntimeError("Game time not initialized. Call init_game_time() first.")
return _game_time.get_game_hour()
def get_game_time() -> tuple[int, int]:
"""Get current game time from global instance.
Returns:
Tuple of (hour 0-23, minute 0-59)
Raises:
RuntimeError: If game time not initialized
"""
if _game_time is None:
raise RuntimeError("Game time not initialized. Call init_game_time() first.")
return _game_time.get_game_time()