192 lines
6.4 KiB
Python
192 lines
6.4 KiB
Python
"""Action definitions for the Village Simulation.
|
|
|
|
Action configurations are loaded dynamically from the global config.
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import Optional, TYPE_CHECKING
|
|
|
|
from .resources import ResourceType
|
|
|
|
if TYPE_CHECKING:
|
|
from backend.config import SimulationConfig
|
|
|
|
|
|
class ActionType(Enum):
|
|
"""Types of actions an agent can perform."""
|
|
SLEEP = "sleep" # Night action - restores energy
|
|
REST = "rest" # Day action - restores some energy
|
|
HUNT = "hunt" # Produces meat and hide
|
|
GATHER = "gather" # Produces berries
|
|
CHOP_WOOD = "chop_wood" # Produces wood
|
|
GET_WATER = "get_water" # Produces water
|
|
WEAVE = "weave" # Produces clothes from hide
|
|
BUILD_FIRE = "build_fire" # Consumes wood, provides heat
|
|
TRADE = "trade" # Market interaction
|
|
CONSUME = "consume" # Consume resource from inventory
|
|
|
|
|
|
@dataclass
|
|
class ActionConfig:
|
|
"""Configuration for an action type."""
|
|
energy_cost: int # Negative = cost, positive = restoration
|
|
success_chance: float = 1.0 # 0.0 to 1.0
|
|
min_output: int = 0
|
|
max_output: int = 0
|
|
output_resource: Optional[ResourceType] = None
|
|
secondary_output: Optional[ResourceType] = None
|
|
secondary_min: int = 0
|
|
secondary_max: int = 0
|
|
requires_resource: Optional[ResourceType] = None
|
|
requires_quantity: int = 0
|
|
|
|
|
|
def get_action_config() -> dict[ActionType, ActionConfig]:
|
|
"""Get action configurations from the global config.
|
|
|
|
This function dynamically builds ACTION_CONFIG from config.json values.
|
|
"""
|
|
# Import here to avoid circular imports
|
|
from backend.config import get_config
|
|
|
|
config = get_config()
|
|
actions = config.actions
|
|
|
|
return {
|
|
ActionType.SLEEP: ActionConfig(
|
|
energy_cost=actions.sleep_energy, # Restores energy
|
|
),
|
|
ActionType.REST: ActionConfig(
|
|
energy_cost=actions.rest_energy, # Restores some energy
|
|
),
|
|
ActionType.HUNT: ActionConfig(
|
|
energy_cost=actions.hunt_energy,
|
|
success_chance=actions.hunt_success,
|
|
min_output=actions.hunt_meat_min,
|
|
max_output=actions.hunt_meat_max,
|
|
output_resource=ResourceType.MEAT,
|
|
secondary_output=ResourceType.HIDE,
|
|
secondary_min=actions.hunt_hide_min,
|
|
secondary_max=actions.hunt_hide_max,
|
|
),
|
|
ActionType.GATHER: ActionConfig(
|
|
energy_cost=actions.gather_energy,
|
|
success_chance=1.0,
|
|
min_output=actions.gather_min,
|
|
max_output=actions.gather_max,
|
|
output_resource=ResourceType.BERRIES,
|
|
),
|
|
ActionType.CHOP_WOOD: ActionConfig(
|
|
energy_cost=actions.chop_wood_energy,
|
|
success_chance=actions.chop_wood_success,
|
|
min_output=actions.chop_wood_min,
|
|
max_output=actions.chop_wood_max,
|
|
output_resource=ResourceType.WOOD,
|
|
),
|
|
ActionType.GET_WATER: ActionConfig(
|
|
energy_cost=actions.get_water_energy,
|
|
success_chance=1.0,
|
|
min_output=1,
|
|
max_output=1,
|
|
output_resource=ResourceType.WATER,
|
|
),
|
|
ActionType.WEAVE: ActionConfig(
|
|
energy_cost=actions.weave_energy,
|
|
success_chance=1.0,
|
|
min_output=1,
|
|
max_output=1,
|
|
output_resource=ResourceType.CLOTHES,
|
|
requires_resource=ResourceType.HIDE,
|
|
requires_quantity=1,
|
|
),
|
|
ActionType.BUILD_FIRE: ActionConfig(
|
|
energy_cost=actions.build_fire_energy,
|
|
success_chance=1.0,
|
|
requires_resource=ResourceType.WOOD,
|
|
requires_quantity=1,
|
|
),
|
|
ActionType.TRADE: ActionConfig(
|
|
energy_cost=actions.trade_energy,
|
|
),
|
|
ActionType.CONSUME: ActionConfig(
|
|
energy_cost=0,
|
|
),
|
|
}
|
|
|
|
|
|
# Lazy-loaded action config cache
|
|
_action_config_cache: Optional[dict[ActionType, ActionConfig]] = None
|
|
|
|
|
|
def get_cached_action_config() -> dict[ActionType, ActionConfig]:
|
|
"""Get cached action config (rebuilds if config changes)."""
|
|
global _action_config_cache
|
|
if _action_config_cache is None:
|
|
_action_config_cache = get_action_config()
|
|
return _action_config_cache
|
|
|
|
|
|
def reset_action_config_cache() -> None:
|
|
"""Reset the action config cache (call when config changes)."""
|
|
global _action_config_cache
|
|
_action_config_cache = None
|
|
|
|
|
|
# For backwards compatibility - this is a property-like access
|
|
# that returns fresh config each time (use get_cached_action_config for performance)
|
|
class _ActionConfigAccessor:
|
|
"""Accessor class that provides dict-like access to action configs."""
|
|
|
|
def __getitem__(self, action_type: ActionType) -> ActionConfig:
|
|
return get_cached_action_config()[action_type]
|
|
|
|
def get(self, action_type: ActionType, default=None) -> Optional[ActionConfig]:
|
|
return get_cached_action_config().get(action_type, default)
|
|
|
|
def __contains__(self, action_type: ActionType) -> bool:
|
|
return action_type in get_cached_action_config()
|
|
|
|
def items(self):
|
|
return get_cached_action_config().items()
|
|
|
|
def keys(self):
|
|
return get_cached_action_config().keys()
|
|
|
|
def values(self):
|
|
return get_cached_action_config().values()
|
|
|
|
|
|
# This provides the same interface as before but loads from config
|
|
ACTION_CONFIG = _ActionConfigAccessor()
|
|
|
|
|
|
@dataclass
|
|
class ActionResult:
|
|
"""Result of executing an action."""
|
|
action_type: ActionType
|
|
success: bool
|
|
energy_spent: int = 0
|
|
resources_gained: list = field(default_factory=list)
|
|
resources_consumed: list = field(default_factory=list)
|
|
heat_gained: int = 0
|
|
message: str = ""
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Convert to dictionary for API serialization."""
|
|
return {
|
|
"action_type": self.action_type.value,
|
|
"success": self.success,
|
|
"energy_spent": self.energy_spent,
|
|
"resources_gained": [
|
|
{"type": r.type.value, "quantity": r.quantity}
|
|
for r in self.resources_gained
|
|
],
|
|
"resources_consumed": [
|
|
{"type": r.type.value, "quantity": r.quantity}
|
|
for r in self.resources_consumed
|
|
],
|
|
"heat_gained": self.heat_gained,
|
|
"message": self.message,
|
|
}
|