Initial project structure: reusable isometric bot engine with D2R implementation
This commit is contained in:
commit
e0282a7111
44 changed files with 3433 additions and 0 deletions
105
engine/state/manager.py
Normal file
105
engine/state/manager.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
"""Game state machine management.
|
||||
|
||||
Provides a base state manager that game implementations extend
|
||||
to detect and track game states (menu, in-game, inventory, etc.).
|
||||
"""
|
||||
|
||||
from typing import Optional, Callable, Dict, Any
|
||||
from enum import Enum, auto
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseGameState(Enum):
|
||||
"""Base states common to most games."""
|
||||
UNKNOWN = auto()
|
||||
LOADING = auto()
|
||||
MAIN_MENU = auto()
|
||||
CHARACTER_SELECT = auto()
|
||||
IN_GAME = auto()
|
||||
INVENTORY = auto()
|
||||
DEAD = auto()
|
||||
DISCONNECTED = auto()
|
||||
|
||||
|
||||
@dataclass
|
||||
class StateTransition:
|
||||
"""Records a state transition."""
|
||||
from_state: BaseGameState
|
||||
to_state: BaseGameState
|
||||
timestamp: float
|
||||
metadata: Dict[str, Any] = None
|
||||
|
||||
|
||||
class GameStateManager:
|
||||
"""Base class for game state detection and management.
|
||||
|
||||
Game implementations should subclass this and implement
|
||||
detect_state() with game-specific screen analysis.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._current_state: BaseGameState = BaseGameState.UNKNOWN
|
||||
self._previous_state: BaseGameState = BaseGameState.UNKNOWN
|
||||
self._state_enter_time: float = time.time()
|
||||
self._history: list[StateTransition] = []
|
||||
self._callbacks: Dict[BaseGameState, list[Callable]] = {}
|
||||
|
||||
@property
|
||||
def current_state(self) -> BaseGameState:
|
||||
return self._current_state
|
||||
|
||||
@property
|
||||
def previous_state(self) -> BaseGameState:
|
||||
return self._previous_state
|
||||
|
||||
@property
|
||||
def time_in_state(self) -> float:
|
||||
"""Seconds spent in current state."""
|
||||
return time.time() - self._state_enter_time
|
||||
|
||||
def detect_state(self, screen: np.ndarray) -> BaseGameState:
|
||||
"""Detect current game state from screenshot.
|
||||
|
||||
Must be overridden by game implementations.
|
||||
"""
|
||||
raise NotImplementedError("Subclasses must implement detect_state()")
|
||||
|
||||
def update(self, screen: np.ndarray) -> BaseGameState:
|
||||
"""Update state from current screen. Triggers callbacks on change."""
|
||||
new_state = self.detect_state(screen)
|
||||
|
||||
if new_state != self._current_state:
|
||||
transition = StateTransition(
|
||||
from_state=self._current_state,
|
||||
to_state=new_state,
|
||||
timestamp=time.time(),
|
||||
)
|
||||
self._history.append(transition)
|
||||
|
||||
logger.info(f"State: {self._current_state.name} → {new_state.name}")
|
||||
|
||||
self._previous_state = self._current_state
|
||||
self._current_state = new_state
|
||||
self._state_enter_time = time.time()
|
||||
|
||||
# Fire callbacks
|
||||
for cb in self._callbacks.get(new_state, []):
|
||||
try:
|
||||
cb(transition)
|
||||
except Exception as e:
|
||||
logger.error(f"State callback error: {e}")
|
||||
|
||||
return self._current_state
|
||||
|
||||
def on_state(self, state: BaseGameState, callback: Callable) -> None:
|
||||
"""Register a callback for when entering a state."""
|
||||
self._callbacks.setdefault(state, []).append(callback)
|
||||
|
||||
def is_state(self, state: BaseGameState) -> bool:
|
||||
return self._current_state == state
|
||||
Loading…
Add table
Add a link
Reference in a new issue