"""NPC schedule system for time-based behavior transitions.""" from dataclasses import dataclass from mudlib.entity import Mob from mudlib.npc_behavior import transition_state @dataclass class ScheduleEntry: """A single schedule entry for a specific hour.""" hour: int # Game hour (0-23) when this entry activates state: str # Behavior state to transition to location: dict | None = None # Optional {"x": int, "y": int} to move to data: dict | None = None # Optional behavior_data to set @dataclass class NpcSchedule: """Schedule for an NPC with multiple time-based entries.""" entries: list[ScheduleEntry] def get_active_entry(self, hour: int) -> ScheduleEntry: """Get the schedule entry that should be active at the given hour. Returns the most recent entry at or before the given hour, wrapping around midnight if necessary. Args: hour: Game hour (0-23) Returns: The active ScheduleEntry for this hour """ if not self.entries: raise ValueError("Schedule has no entries") # Find the most recent entry at or before this hour best_entry = None best_hour = -1 for entry in self.entries: if entry.hour <= hour and entry.hour > best_hour: best_entry = entry best_hour = entry.hour # If no entry found before current hour, wrap around to last entry if best_entry is None: # Find the entry with the highest hour best_entry = max(self.entries, key=lambda e: e.hour) return best_entry def process_schedules(mob_list: list[Mob], game_hour: int) -> None: """Process NPC schedules and apply transitions if hour has changed. Args: mob_list: List of mobs to process game_hour: Current game hour (0-23) """ for mob in mob_list: if not mob.alive: continue # Skip mobs in conversation (don't interrupt active player interaction) if mob.behavior_state == "converse": continue schedule = mob.schedule if schedule is None or not isinstance(schedule, NpcSchedule): continue # Check if we've already processed this hour if mob.schedule_last_hour == game_hour: continue # Get the active entry for this hour entry = schedule.get_active_entry(game_hour) # Check if anything needs to change needs_state_change = mob.behavior_state != entry.state needs_location_change = entry.location is not None and ( mob.x != entry.location["x"] or mob.y != entry.location["y"] ) needs_data_change = entry.data is not None # Only apply changes if something actually changed if needs_state_change or needs_location_change or needs_data_change: # Transition state (this also sets behavior_data if entry.data is set) transition_state(mob, entry.state, entry.data) # Move mob if location specified if entry.location is not None: mob.x = entry.location["x"] mob.y = entry.location["y"] # Mark this hour as processed mob.schedule_last_hour = game_hour