"""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.""" # Basic survival actions 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 # NEW: Oil industry actions DRILL_OIL = "drill_oil" # Extract oil from oil fields REFINE = "refine" # Convert oil to fuel BURN_FUEL = "burn_fuel" # Use fuel for heat/energy # NEW: Religious actions PRAY = "pray" # Increase faith, slight energy cost PREACH = "preach" # Spread religion, convert others # NEW: Diplomatic actions NEGOTIATE = "negotiate" # Improve relations with another faction DECLARE_WAR = "declare_war" # Declare war on another faction MAKE_PEACE = "make_peace" # Propose peace treaty @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 # NEW: Faith effects faith_gain: int = 0 faith_spread: int = 0 def get_action_config() -> dict[ActionType, ActionConfig]: """Get action configurations from the global config.""" from backend.config import get_config config = get_config() actions = config.actions return { ActionType.SLEEP: ActionConfig( energy_cost=actions.sleep_energy, ), ActionType.REST: ActionConfig( energy_cost=actions.rest_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, ), # NEW: Oil industry actions ActionType.DRILL_OIL: ActionConfig( energy_cost=actions.drill_oil_energy, success_chance=actions.drill_oil_success, min_output=actions.drill_oil_min, max_output=actions.drill_oil_max, output_resource=ResourceType.OIL, ), ActionType.REFINE: ActionConfig( energy_cost=actions.refine_energy, success_chance=1.0, min_output=1, max_output=1, output_resource=ResourceType.FUEL, requires_resource=ResourceType.OIL, requires_quantity=2, # 2 oil -> 1 fuel ), ActionType.BURN_FUEL: ActionConfig( energy_cost=-1, # Minimal effort to burn fuel success_chance=1.0, requires_resource=ResourceType.FUEL, requires_quantity=1, ), # NEW: Religious actions ActionType.PRAY: ActionConfig( energy_cost=actions.pray_energy, success_chance=1.0, faith_gain=actions.pray_faith_gain, ), ActionType.PREACH: ActionConfig( energy_cost=actions.preach_energy, success_chance=actions.preach_convert_chance, faith_spread=actions.preach_faith_spread, ), # NEW: Diplomatic actions ActionType.NEGOTIATE: ActionConfig( energy_cost=actions.negotiate_energy, success_chance=0.7, # Not always successful ), ActionType.DECLARE_WAR: ActionConfig( energy_cost=actions.declare_war_energy, success_chance=1.0, # Always succeeds (but has consequences) ), ActionType.MAKE_PEACE: ActionConfig( energy_cost=actions.make_peace_energy, success_chance=0.5, # Harder to make peace than war ), } # 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 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() # Action categories for AI decision making SURVIVAL_ACTIONS = { ActionType.HUNT, ActionType.GATHER, ActionType.CHOP_WOOD, ActionType.GET_WATER, ActionType.BUILD_FIRE, ActionType.CONSUME } PRODUCTION_ACTIONS = { ActionType.HUNT, ActionType.GATHER, ActionType.CHOP_WOOD, ActionType.GET_WATER, ActionType.DRILL_OIL } CRAFTING_ACTIONS = {ActionType.WEAVE, ActionType.REFINE} RELIGIOUS_ACTIONS = {ActionType.PRAY, ActionType.PREACH} DIPLOMATIC_ACTIONS = {ActionType.NEGOTIATE, ActionType.DECLARE_WAR, ActionType.MAKE_PEACE} ECONOMIC_ACTIONS = {ActionType.TRADE} @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 faith_gained: int = 0 # NEW relation_change: int = 0 # NEW message: str = "" # NEW: Diplomatic effects target_faction: Optional[str] = None diplomatic_effect: Optional[str] = None # "war", "peace", "improved", "degraded" 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, "faith_gained": self.faith_gained, "relation_change": self.relation_change, "target_faction": self.target_faction, "diplomatic_effect": self.diplomatic_effect, "message": self.message, }