614 lines
23 KiB
Python
614 lines
23 KiB
Python
"""Agent model for the Village Simulation.
|
|
|
|
Agent stats are loaded dynamically from the global config.
|
|
Each agent now has unique personality traits and skills that create
|
|
emergent professions and behavioral diversity.
|
|
|
|
NEW: Agents now have religion and faction membership for realistic
|
|
social dynamics including religious beliefs and geopolitical allegiances.
|
|
"""
|
|
|
|
import math
|
|
import random
|
|
from dataclasses import dataclass, field
|
|
from enum import Enum
|
|
from typing import Optional
|
|
from uuid import uuid4
|
|
|
|
from .resources import Resource, ResourceType, RESOURCE_EFFECTS
|
|
from .personality import (
|
|
PersonalityTraits, Skills, ProfessionType,
|
|
determine_profession
|
|
)
|
|
from .religion import ReligiousBeliefs
|
|
from .diplomacy import AgentDiplomacy
|
|
|
|
|
|
def _get_agent_stats_config():
|
|
"""Get agent stats configuration from global config."""
|
|
from backend.config import get_config
|
|
return get_config().agent_stats
|
|
|
|
|
|
class Profession(Enum):
|
|
"""Agent professions - now derived from personality and skills."""
|
|
VILLAGER = "villager"
|
|
HUNTER = "hunter"
|
|
GATHERER = "gatherer"
|
|
WOODCUTTER = "woodcutter"
|
|
TRADER = "trader"
|
|
CRAFTER = "crafter"
|
|
OIL_WORKER = "oil_worker" # NEW: Oil industry worker
|
|
PRIEST = "priest" # NEW: Religious leader
|
|
|
|
|
|
@dataclass
|
|
class Position:
|
|
"""2D position on the map (floating point for smooth movement)."""
|
|
x: float = 0.0
|
|
y: float = 0.0
|
|
|
|
def distance_to(self, other: "Position") -> float:
|
|
"""Calculate distance to another position."""
|
|
return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2)
|
|
|
|
def move_towards(self, target: "Position", speed: float = 0.5) -> bool:
|
|
"""Move towards target position. Returns True if reached."""
|
|
dist = self.distance_to(target)
|
|
if dist <= speed:
|
|
self.x = target.x
|
|
self.y = target.y
|
|
return True
|
|
|
|
# Calculate direction
|
|
dx = target.x - self.x
|
|
dy = target.y - self.y
|
|
|
|
# Normalize and apply speed
|
|
self.x += (dx / dist) * speed
|
|
self.y += (dy / dist) * speed
|
|
return False
|
|
|
|
def to_dict(self) -> dict:
|
|
return {"x": round(self.x, 2), "y": round(self.y, 2)}
|
|
|
|
def copy(self) -> "Position":
|
|
return Position(self.x, self.y)
|
|
|
|
|
|
@dataclass
|
|
class AgentStats:
|
|
"""Vital statistics for an agent.
|
|
|
|
Values are loaded from config.json. Default values are used as fallback.
|
|
"""
|
|
# Current values - defaults will be overwritten by factory function
|
|
energy: int = field(default=50)
|
|
hunger: int = field(default=80)
|
|
thirst: int = field(default=70)
|
|
heat: int = field(default=100)
|
|
faith: int = field(default=50) # NEW: Religious faith level
|
|
|
|
# Maximum values - loaded from config
|
|
MAX_ENERGY: int = field(default=50)
|
|
MAX_HUNGER: int = field(default=100)
|
|
MAX_THIRST: int = field(default=100)
|
|
MAX_HEAT: int = field(default=100)
|
|
MAX_FAITH: int = field(default=100) # NEW
|
|
|
|
# Passive decay rates per turn - loaded from config
|
|
ENERGY_DECAY: int = field(default=1)
|
|
HUNGER_DECAY: int = field(default=2)
|
|
THIRST_DECAY: int = field(default=3)
|
|
HEAT_DECAY: int = field(default=2)
|
|
FAITH_DECAY: int = field(default=1) # NEW
|
|
|
|
# Critical threshold - loaded from config
|
|
CRITICAL_THRESHOLD: float = field(default=0.25)
|
|
|
|
def apply_passive_decay(self, has_clothes: bool = False) -> None:
|
|
"""Apply passive stat decay each turn."""
|
|
self.energy = max(0, self.energy - self.ENERGY_DECAY)
|
|
self.hunger = max(0, self.hunger - self.HUNGER_DECAY)
|
|
self.thirst = max(0, self.thirst - self.THIRST_DECAY)
|
|
|
|
# Clothes reduce heat loss by 50%
|
|
heat_decay = self.HEAT_DECAY // 2 if has_clothes else self.HEAT_DECAY
|
|
self.heat = max(0, self.heat - heat_decay)
|
|
|
|
# Faith decays slowly - praying restores it
|
|
self.faith = max(0, self.faith - self.FAITH_DECAY)
|
|
|
|
def is_critical(self) -> bool:
|
|
"""Check if any vital stat is below critical threshold."""
|
|
threshold_hunger = int(self.MAX_HUNGER * self.CRITICAL_THRESHOLD)
|
|
threshold_thirst = int(self.MAX_THIRST * self.CRITICAL_THRESHOLD)
|
|
threshold_heat = int(self.MAX_HEAT * self.CRITICAL_THRESHOLD)
|
|
return (
|
|
self.hunger < threshold_hunger or
|
|
self.thirst < threshold_thirst or
|
|
self.heat < threshold_heat
|
|
)
|
|
|
|
def get_critical_stat(self) -> Optional[str]:
|
|
"""Get the name of the most critical stat, if any."""
|
|
threshold_thirst = int(self.MAX_THIRST * self.CRITICAL_THRESHOLD)
|
|
threshold_hunger = int(self.MAX_HUNGER * self.CRITICAL_THRESHOLD)
|
|
threshold_heat = int(self.MAX_HEAT * self.CRITICAL_THRESHOLD)
|
|
|
|
if self.thirst < threshold_thirst:
|
|
return "thirst"
|
|
if self.hunger < threshold_hunger:
|
|
return "hunger"
|
|
if self.heat < threshold_heat:
|
|
return "heat"
|
|
return None
|
|
|
|
def can_work(self, energy_required: int) -> bool:
|
|
"""Check if agent has enough energy to perform an action."""
|
|
return self.energy >= abs(energy_required)
|
|
|
|
def gain_faith(self, amount: int) -> None:
|
|
"""Increase faith level."""
|
|
self.faith = min(self.MAX_FAITH, self.faith + amount)
|
|
|
|
def lose_faith(self, amount: int) -> None:
|
|
"""Decrease faith level."""
|
|
self.faith = max(0, self.faith - amount)
|
|
|
|
@property
|
|
def is_zealot(self) -> bool:
|
|
"""Check if agent has zealot-level faith."""
|
|
return self.faith >= int(self.MAX_FAITH * 0.80)
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"energy": self.energy,
|
|
"hunger": self.hunger,
|
|
"thirst": self.thirst,
|
|
"heat": self.heat,
|
|
"faith": self.faith,
|
|
"max_energy": self.MAX_ENERGY,
|
|
"max_hunger": self.MAX_HUNGER,
|
|
"max_thirst": self.MAX_THIRST,
|
|
"max_heat": self.MAX_HEAT,
|
|
"max_faith": self.MAX_FAITH,
|
|
}
|
|
|
|
|
|
def create_agent_stats() -> AgentStats:
|
|
"""Factory function to create AgentStats from config."""
|
|
config = _get_agent_stats_config()
|
|
return AgentStats(
|
|
energy=config.start_energy,
|
|
hunger=config.start_hunger,
|
|
thirst=config.start_thirst,
|
|
heat=config.start_heat,
|
|
faith=getattr(config, 'start_faith', 50),
|
|
MAX_ENERGY=config.max_energy,
|
|
MAX_HUNGER=config.max_hunger,
|
|
MAX_THIRST=config.max_thirst,
|
|
MAX_HEAT=config.max_heat,
|
|
MAX_FAITH=getattr(config, 'max_faith', 100),
|
|
ENERGY_DECAY=config.energy_decay,
|
|
HUNGER_DECAY=config.hunger_decay,
|
|
THIRST_DECAY=config.thirst_decay,
|
|
HEAT_DECAY=config.heat_decay,
|
|
FAITH_DECAY=getattr(config, 'faith_decay', 1),
|
|
CRITICAL_THRESHOLD=config.critical_threshold,
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class AgentAction:
|
|
"""Current action being performed by an agent."""
|
|
action_type: str = "" # e.g., "hunt", "gather", "trade", "rest", "pray"
|
|
target_position: Optional[Position] = None
|
|
target_resource: Optional[str] = None
|
|
target_agent: Optional[str] = None # NEW: For diplomatic/religious actions
|
|
progress: float = 0.0 # 0.0 to 1.0
|
|
is_moving: bool = False
|
|
message: str = ""
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"action_type": self.action_type,
|
|
"target_position": self.target_position.to_dict() if self.target_position else None,
|
|
"target_resource": self.target_resource,
|
|
"target_agent": self.target_agent,
|
|
"progress": round(self.progress, 2),
|
|
"is_moving": self.is_moving,
|
|
"message": self.message,
|
|
}
|
|
|
|
|
|
# Action location mappings (relative positions on the map for each action type)
|
|
ACTION_LOCATIONS = {
|
|
"hunt": {"zone": "forest", "offset_range": (0.6, 0.9)},
|
|
"gather": {"zone": "bushes", "offset_range": (0.1, 0.4)},
|
|
"chop_wood": {"zone": "forest", "offset_range": (0.7, 0.95)},
|
|
"get_water": {"zone": "river", "offset_range": (0.0, 0.15)},
|
|
"weave": {"zone": "village", "offset_range": (0.4, 0.6)},
|
|
"build_fire": {"zone": "village", "offset_range": (0.45, 0.55)},
|
|
"burn_fuel": {"zone": "village", "offset_range": (0.45, 0.55)},
|
|
"trade": {"zone": "market", "offset_range": (0.5, 0.6)},
|
|
"rest": {"zone": "home", "offset_range": (0.4, 0.6)},
|
|
"sleep": {"zone": "home", "offset_range": (0.4, 0.6)},
|
|
"consume": {"zone": "current", "offset_range": (0, 0)},
|
|
# NEW: Oil industry locations
|
|
"drill_oil": {"zone": "oil_field", "offset_range": (0.8, 0.95)},
|
|
"refine": {"zone": "refinery", "offset_range": (0.7, 0.85)},
|
|
# NEW: Religious locations
|
|
"pray": {"zone": "temple", "offset_range": (0.45, 0.55)},
|
|
"preach": {"zone": "village", "offset_range": (0.4, 0.6)},
|
|
# NEW: Diplomatic locations
|
|
"negotiate": {"zone": "market", "offset_range": (0.5, 0.6)},
|
|
"declare_war": {"zone": "village", "offset_range": (0.5, 0.5)},
|
|
"make_peace": {"zone": "market", "offset_range": (0.5, 0.6)},
|
|
}
|
|
|
|
|
|
def _get_world_config():
|
|
"""Get world configuration from global config."""
|
|
from backend.config import get_config
|
|
return get_config().world
|
|
|
|
|
|
@dataclass
|
|
class Agent:
|
|
"""An agent in the village simulation.
|
|
|
|
Stats, inventory slots, and starting money are loaded from config.json.
|
|
Each agent now has unique personality traits and skills that create
|
|
emergent behaviors and professions.
|
|
|
|
NEW: Agents now have religious beliefs and faction membership.
|
|
"""
|
|
id: str = field(default_factory=lambda: str(uuid4())[:8])
|
|
name: str = ""
|
|
profession: Profession = Profession.VILLAGER
|
|
position: Position = field(default_factory=Position)
|
|
stats: AgentStats = field(default_factory=create_agent_stats)
|
|
inventory: list[Resource] = field(default_factory=list)
|
|
money: int = field(default=-1)
|
|
|
|
# Personality and skills
|
|
personality: PersonalityTraits = field(default_factory=PersonalityTraits)
|
|
skills: Skills = field(default_factory=Skills)
|
|
|
|
# NEW: Religion and diplomacy
|
|
religion: ReligiousBeliefs = field(default_factory=ReligiousBeliefs)
|
|
diplomacy: AgentDiplomacy = field(default_factory=AgentDiplomacy)
|
|
|
|
# Movement and action tracking
|
|
home_position: Position = field(default_factory=Position)
|
|
current_action: AgentAction = field(default_factory=AgentAction)
|
|
last_action_result: str = ""
|
|
|
|
# Death tracking for corpse visualization
|
|
death_turn: int = -1
|
|
death_reason: str = ""
|
|
|
|
# Statistics tracking for profession determination
|
|
actions_performed: dict = field(default_factory=lambda: {
|
|
"hunt": 0, "gather": 0, "chop_wood": 0, "trade": 0, "craft": 0,
|
|
"drill_oil": 0, "refine": 0, "pray": 0, "preach": 0,
|
|
"negotiate": 0, "declare_war": 0, "make_peace": 0,
|
|
})
|
|
total_trades_completed: int = 0
|
|
total_money_earned: int = 0
|
|
|
|
# Configuration - loaded from config
|
|
INVENTORY_SLOTS: int = field(default=-1)
|
|
MOVE_SPEED: float = 0.8
|
|
|
|
def __post_init__(self):
|
|
if not self.name:
|
|
self.name = f"Agent_{self.id}"
|
|
self.home_position = self.position.copy()
|
|
|
|
config = _get_world_config()
|
|
if self.money == -1:
|
|
self.money = config.starting_money
|
|
if self.INVENTORY_SLOTS == -1:
|
|
self.INVENTORY_SLOTS = config.inventory_slots
|
|
|
|
self._update_profession()
|
|
|
|
def _update_profession(self) -> None:
|
|
"""Update profession based on personality, skills, and activities."""
|
|
# Check for specialized professions first
|
|
|
|
# High religious activity = Priest
|
|
if self.actions_performed.get("pray", 0) + self.actions_performed.get("preach", 0) > 10:
|
|
if self.stats.faith > 70:
|
|
self.profession = Profession.PRIEST
|
|
return
|
|
|
|
# High oil activity = Oil Worker
|
|
if self.actions_performed.get("drill_oil", 0) + self.actions_performed.get("refine", 0) > 10:
|
|
self.profession = Profession.OIL_WORKER
|
|
return
|
|
|
|
# Standard profession determination
|
|
prof_type = determine_profession(self.personality, self.skills)
|
|
profession_map = {
|
|
ProfessionType.HUNTER: Profession.HUNTER,
|
|
ProfessionType.GATHERER: Profession.GATHERER,
|
|
ProfessionType.WOODCUTTER: Profession.WOODCUTTER,
|
|
ProfessionType.TRADER: Profession.TRADER,
|
|
ProfessionType.GENERALIST: Profession.VILLAGER,
|
|
}
|
|
self.profession = profession_map.get(prof_type, Profession.VILLAGER)
|
|
|
|
def record_action(self, action_type: str) -> None:
|
|
"""Record an action for profession tracking."""
|
|
if action_type in self.actions_performed:
|
|
self.actions_performed[action_type] += 1
|
|
|
|
def record_trade(self, money_earned: int) -> None:
|
|
"""Record a completed trade for statistics."""
|
|
self.total_trades_completed += 1
|
|
if money_earned > 0:
|
|
self.total_money_earned += money_earned
|
|
|
|
def is_alive(self) -> bool:
|
|
"""Check if the agent is still alive."""
|
|
return (
|
|
self.stats.hunger > 0 and
|
|
self.stats.thirst > 0 and
|
|
self.stats.heat > 0
|
|
)
|
|
|
|
def is_corpse(self) -> bool:
|
|
"""Check if this agent is a corpse."""
|
|
return self.death_turn >= 0
|
|
|
|
def can_act(self) -> bool:
|
|
"""Check if agent can perform active actions."""
|
|
return self.is_alive() and self.stats.energy > 0
|
|
|
|
def has_clothes(self) -> bool:
|
|
"""Check if agent has clothes equipped."""
|
|
return any(r.type == ResourceType.CLOTHES for r in self.inventory)
|
|
|
|
def has_oil(self) -> bool:
|
|
"""Check if agent has oil."""
|
|
return any(r.type == ResourceType.OIL for r in self.inventory)
|
|
|
|
def has_fuel(self) -> bool:
|
|
"""Check if agent has fuel."""
|
|
return any(r.type == ResourceType.FUEL for r in self.inventory)
|
|
|
|
def inventory_space(self) -> int:
|
|
"""Get remaining inventory slots."""
|
|
total_items = sum(r.quantity for r in self.inventory)
|
|
return max(0, self.INVENTORY_SLOTS - total_items)
|
|
|
|
def inventory_full(self) -> bool:
|
|
"""Check if inventory is full."""
|
|
return self.inventory_space() <= 0
|
|
|
|
def set_action(
|
|
self,
|
|
action_type: str,
|
|
world_width: int,
|
|
world_height: int,
|
|
message: str = "",
|
|
target_resource: Optional[str] = None,
|
|
target_agent: Optional[str] = None,
|
|
) -> None:
|
|
"""Set the current action and calculate target position."""
|
|
location = ACTION_LOCATIONS.get(action_type, {"zone": "current", "offset_range": (0, 0)})
|
|
|
|
if location["zone"] == "current":
|
|
target = self.position.copy()
|
|
is_moving = False
|
|
else:
|
|
offset_range = location["offset_range"]
|
|
offset_min = float(offset_range[0]) if offset_range else 0.0
|
|
offset_max = float(offset_range[1]) if offset_range else 0.0
|
|
target_x = world_width * random.uniform(offset_min, offset_max)
|
|
|
|
target_y = self.home_position.y + random.uniform(-2, 2)
|
|
target_y = max(0.5, min(world_height - 0.5, target_y))
|
|
|
|
target = Position(target_x, target_y)
|
|
is_moving = self.position.distance_to(target) > 0.5
|
|
|
|
self.current_action = AgentAction(
|
|
action_type=action_type,
|
|
target_position=target,
|
|
target_resource=target_resource,
|
|
target_agent=target_agent,
|
|
progress=0.0,
|
|
is_moving=is_moving,
|
|
message=message,
|
|
)
|
|
|
|
def update_movement(self) -> None:
|
|
"""Update agent position moving towards target."""
|
|
if self.current_action.target_position and self.current_action.is_moving:
|
|
reached = self.position.move_towards(
|
|
self.current_action.target_position,
|
|
self.MOVE_SPEED
|
|
)
|
|
if reached:
|
|
self.current_action.is_moving = False
|
|
self.current_action.progress = 0.5
|
|
|
|
def complete_action(self, success: bool, message: str) -> None:
|
|
"""Mark current action as complete."""
|
|
self.current_action.progress = 1.0
|
|
self.current_action.is_moving = False
|
|
self.last_action_result = message
|
|
self.current_action.message = message if success else f"Failed: {message}"
|
|
|
|
def add_to_inventory(self, resource: Resource) -> int:
|
|
"""Add resource to inventory, returns quantity actually added."""
|
|
space = self.inventory_space()
|
|
if space <= 0:
|
|
return 0
|
|
|
|
quantity_to_add = min(resource.quantity, space)
|
|
|
|
for existing in self.inventory:
|
|
if existing.type == resource.type:
|
|
existing.quantity += quantity_to_add
|
|
return quantity_to_add
|
|
|
|
new_resource = Resource(
|
|
type=resource.type,
|
|
quantity=quantity_to_add,
|
|
created_turn=resource.created_turn,
|
|
)
|
|
self.inventory.append(new_resource)
|
|
return quantity_to_add
|
|
|
|
def remove_from_inventory(self, resource_type: ResourceType, quantity: int = 1) -> int:
|
|
"""Remove resource from inventory, returns quantity actually removed."""
|
|
removed = 0
|
|
for resource in self.inventory[:]:
|
|
if resource.type == resource_type:
|
|
take = min(resource.quantity, quantity - removed)
|
|
resource.quantity -= take
|
|
removed += take
|
|
|
|
if resource.quantity <= 0:
|
|
self.inventory.remove(resource)
|
|
|
|
if removed >= quantity:
|
|
break
|
|
|
|
return removed
|
|
|
|
def get_resource_count(self, resource_type: ResourceType) -> int:
|
|
"""Get total count of a resource type in inventory."""
|
|
return sum(
|
|
r.quantity for r in self.inventory
|
|
if r.type == resource_type
|
|
)
|
|
|
|
def has_resource(self, resource_type: ResourceType, quantity: int = 1) -> bool:
|
|
"""Check if agent has at least the specified quantity of a resource."""
|
|
return self.get_resource_count(resource_type) >= quantity
|
|
|
|
def consume(self, resource_type: ResourceType) -> bool:
|
|
"""Consume a resource from inventory and apply its effects."""
|
|
if not self.has_resource(resource_type, 1):
|
|
return False
|
|
|
|
effect = RESOURCE_EFFECTS[resource_type]
|
|
self.stats.hunger = min(
|
|
self.stats.MAX_HUNGER,
|
|
self.stats.hunger + effect.hunger
|
|
)
|
|
self.stats.thirst = min(
|
|
self.stats.MAX_THIRST,
|
|
self.stats.thirst + effect.thirst
|
|
)
|
|
self.stats.heat = min(
|
|
self.stats.MAX_HEAT,
|
|
self.stats.heat + effect.heat
|
|
)
|
|
self.stats.energy = min(
|
|
self.stats.MAX_ENERGY,
|
|
self.stats.energy + effect.energy
|
|
)
|
|
|
|
self.remove_from_inventory(resource_type, 1)
|
|
return True
|
|
|
|
def apply_heat(self, amount: int) -> None:
|
|
"""Apply heat from a fire or fuel."""
|
|
self.stats.heat = min(self.stats.MAX_HEAT, self.stats.heat + amount)
|
|
|
|
def restore_energy(self, amount: int) -> None:
|
|
"""Restore energy (from sleep/rest)."""
|
|
self.stats.energy = min(self.stats.MAX_ENERGY, self.stats.energy + amount)
|
|
|
|
def spend_energy(self, amount: int) -> bool:
|
|
"""Spend energy on an action. Returns False if not enough energy."""
|
|
if self.stats.energy < amount:
|
|
return False
|
|
self.stats.energy -= amount
|
|
return True
|
|
|
|
def gain_faith(self, amount: int) -> None:
|
|
"""Increase faith from religious activity."""
|
|
self.stats.gain_faith(amount)
|
|
self.religion.gain_faith(amount)
|
|
|
|
def decay_inventory(self, current_turn: int) -> list[Resource]:
|
|
"""Remove expired resources from inventory."""
|
|
expired = []
|
|
for resource in self.inventory[:]:
|
|
if resource.is_expired(current_turn):
|
|
expired.append(resource)
|
|
self.inventory.remove(resource)
|
|
return expired
|
|
|
|
def apply_passive_decay(self) -> None:
|
|
"""Apply passive stat decay for this turn."""
|
|
self.stats.apply_passive_decay(has_clothes=self.has_clothes())
|
|
self.religion.apply_decay()
|
|
|
|
def mark_dead(self, turn: int, reason: str) -> None:
|
|
"""Mark this agent as dead."""
|
|
self.death_turn = turn
|
|
self.death_reason = reason
|
|
|
|
def shares_religion_with(self, other: "Agent") -> bool:
|
|
"""Check if agent shares religion with another."""
|
|
return self.religion.religion == other.religion.religion
|
|
|
|
def shares_faction_with(self, other: "Agent") -> bool:
|
|
"""Check if agent shares faction with another."""
|
|
return self.diplomacy.faction == other.diplomacy.faction
|
|
|
|
def get_trade_modifier_for(self, other: "Agent") -> float:
|
|
"""Get combined trade modifier when trading with another agent."""
|
|
# Religion modifier
|
|
religion_mod = self.religion.get_trade_modifier(other.religion)
|
|
|
|
# Faction modifier (from global relations)
|
|
from .diplomacy import get_faction_relations
|
|
faction_relations = get_faction_relations()
|
|
faction_mod = faction_relations.get_trade_modifier(
|
|
self.diplomacy.faction,
|
|
other.diplomacy.faction
|
|
)
|
|
|
|
return religion_mod * faction_mod
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Convert to dictionary for API serialization."""
|
|
self._update_profession()
|
|
|
|
return {
|
|
"id": self.id,
|
|
"name": self.name,
|
|
"profession": self.profession.value,
|
|
"position": self.position.to_dict(),
|
|
"home_position": self.home_position.to_dict(),
|
|
"stats": self.stats.to_dict(),
|
|
"inventory": [r.to_dict() for r in self.inventory],
|
|
"money": self.money,
|
|
"is_alive": self.is_alive(),
|
|
"is_corpse": self.is_corpse(),
|
|
"can_act": self.can_act(),
|
|
"current_action": self.current_action.to_dict(),
|
|
"last_action_result": self.last_action_result,
|
|
"death_turn": self.death_turn,
|
|
"death_reason": self.death_reason,
|
|
# Personality and skills
|
|
"personality": self.personality.to_dict(),
|
|
"skills": self.skills.to_dict(),
|
|
"actions_performed": self.actions_performed.copy(),
|
|
"total_trades": self.total_trades_completed,
|
|
"total_money_earned": self.total_money_earned,
|
|
# NEW: Religion and diplomacy
|
|
"religion": self.religion.to_dict(),
|
|
"diplomacy": self.diplomacy.to_dict(),
|
|
}
|