276 lines
9.6 KiB
Python
276 lines
9.6 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."""
|
|
# 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,
|
|
}
|