| from __future__ import annotations |
|
|
| import json |
| from dataclasses import asdict, dataclass, field |
| from typing import Any, Literal |
|
|
| GoalType = Literal[ |
| "survive", |
| "pursue_and_attack_target", |
| "retaliate", |
| "call_for_help", |
| "help_or_defend", |
| "respond_to_event", |
| "investigate", |
| "find_food", |
| "routine_life", |
| ] |
| CitizenMode = Literal[ |
| "routine", |
| "threatened", |
| "hostile_pursuit", |
| "helping", |
| "investigating", |
| "fleeing", |
| "recovering", |
| ] |
| NpcRole = Literal["guard", "builder", "gatherer"] |
| HouseState = Literal["under_construction", "completed", "destroyed"] |
| GameStatus = Literal["running", "win_population", "win_survival", "lose"] |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class Vec3: |
| x: float |
| y: float |
| z: float |
|
|
|
|
| @dataclass(slots=True) |
| class MemoryEntry: |
| tick: int |
| text: str |
|
|
|
|
| @dataclass(slots=True) |
| class MemoryEpisode: |
| tick: int |
| kind: str |
| actor_id: str |
| target_id: str | None |
| object_id: str | None |
| subject_kind: str |
| perspective: str |
| summary: str |
| emotional_weight: float = 0.5 |
| tags: list[str] = field(default_factory=list) |
|
|
|
|
| @dataclass(slots=True) |
| class NpcMemory: |
| episodes: list[MemoryEpisode] = field(default_factory=list) |
| facts: list[str] = field(default_factory=list) |
|
|
|
|
| @dataclass(slots=True) |
| class NpcConversationContext: |
| messages: list[dict[str, str]] = field(default_factory=list) |
| max_turns: int = 5 |
|
|
| def add_turn(self, user_content: str, assistant_content: str) -> None: |
| self.messages.append({"role": "user", "content": user_content}) |
| self.messages.append({"role": "assistant", "content": assistant_content}) |
| max_messages = self.max_turns * 2 |
| if len(self.messages) > max_messages: |
| self.messages = self.messages[-max_messages:] |
|
|
|
|
| @dataclass(slots=True) |
| class CitizenNeeds: |
| safety: int = 20 |
| food: int = 25 |
| social: int = 30 |
| status: int = 30 |
| curiosity: int = 35 |
| rest: int = 20 |
|
|
|
|
| @dataclass(slots=True) |
| class CitizenEmotions: |
| fear: int = 0 |
| anger: int = 0 |
| trust_baseline: int = 50 |
| stress: int = 0 |
|
|
|
|
| @dataclass(slots=True) |
| class CitizenGoal: |
| goal_type: GoalType |
| priority: int |
| reason: str |
| target_npc_id: str | None = None |
| target_entity_id: str | None = None |
| expires_at_tick: int | None = None |
|
|
|
|
| @dataclass(slots=True) |
| class Npc: |
| id: str |
| name: str |
| role: str |
| position: Vec3 |
| health: int = 100 |
| attack_damage: int = 10 |
| personality: str = "neutral" |
| god_directive: str | None = None |
| directive_source: str | None = None |
| directive_issued_tick: int | None = None |
| directive_ttl_ticks: int | None = None |
| intention: str = "idle" |
| |
| |
| last_reasoning: str | None = None |
| last_reasoning_tick: int | None = None |
| memory_summary: str | None = None |
| |
| |
| memory_summary_self_authored: bool = False |
| memory: list[MemoryEntry] = field(default_factory=list) |
| structured_memory: NpcMemory = field(default_factory=NpcMemory) |
| conversation_context: NpcConversationContext = field(default_factory=NpcConversationContext) |
| relationships: dict[str, float] = field(default_factory=dict) |
| needs: CitizenNeeds = field(default_factory=CitizenNeeds) |
| emotions: CitizenEmotions = field(default_factory=CitizenEmotions) |
| current_goal: CitizenGoal | None = None |
| mode: CitizenMode = "routine" |
| |
| hunger: float = 25.0 |
| fear: float = 0.0 |
| safety: float = 0.0 |
| age: int = 0 |
| max_age: int = 400 |
| reproduction_cooldown: int = 0 |
| importance: float = 1.0 |
| survival_goal: str = "routine_life" |
| inventory_food: int = 0 |
| inventory_herbs: int = 0 |
| inventory_wood: int = 0 |
| inventory_weapon: int = 0 |
| inventory_coins: int = 0 |
| help_target_id: str | None = None |
| |
| focus_target_id: str | None = None |
| home_house_id: str | None = None |
| build_target_house_id: str | None = None |
| patrol_index: int = 0 |
| resources_transferred: int = 0 |
| beasts_killed: int = 0 |
| children_count: int = 0 |
| houses_built: int = 0 |
| |
| |
| is_player: bool = False |
| player_owner: str | None = None |
| country_id: str | None = None |
| special_status: str | None = None |
| model_profile_id: str | None = None |
| |
| |
| |
| unrestricted_actions: bool = False |
| |
| |
| connector_id: str | None = None |
|
|
|
|
| @dataclass(slots=True) |
| class ResourceNode: |
| id: str |
| resource_type: str |
| position: Vec3 |
| amount: int |
| max_amount: int = 0 |
|
|
|
|
| @dataclass(slots=True) |
| class Beast: |
| id: str |
| position: Vec3 |
| health: float = 180.0 |
| damage: float = 12.0 |
| speed: float = 1.0 |
| target_npc_id: str | None = None |
| target_house_id: str | None = None |
| retreat_ticks: int = 0 |
| state: str = "hunting" |
|
|
|
|
| @dataclass(slots=True) |
| class House: |
| id: str |
| position: Vec3 |
| owner_ids: list[str] = field(default_factory=list) |
| hp: float = 60.0 |
| max_hp: float = 60.0 |
| state: HouseState = "completed" |
| build_progress: int = 10 |
| capacity: int = 3 |
| occupant_ids: list[str] = field(default_factory=list) |
|
|
|
|
| @dataclass(slots=True) |
| class TreasuryState: |
| id: str |
| position: Vec3 |
| resources: dict[str, int] = field(default_factory=dict) |
|
|
|
|
| @dataclass(slots=True) |
| class CannonState: |
| id: str |
| position: Vec3 |
| operator_id: str | None = None |
| cooldown_until_tick: int = 0 |
| damage: int = 35 |
| radius: int = 5 |
|
|
|
|
| @dataclass(slots=True) |
| class CountryState: |
| id: str |
| name: str |
| color: str |
| badge: str |
| citizen_ids: list[str] = field(default_factory=list) |
| ruler_id: str | None = None |
| policy: str = "Gather resources, protect citizens, and avoid reckless war." |
| treasury: TreasuryState | None = None |
| cannon: CannonState | None = None |
| next_election_tick: int = 50 |
|
|
|
|
| @dataclass(slots=True) |
| class ElectionState: |
| country_id: str |
| start_tick: int |
| end_tick: int |
| candidate_ids: list[str] |
| votes: dict[str, str] = field(default_factory=dict) |
| completed: bool = False |
|
|
|
|
| @dataclass(slots=True) |
| class WorldLogEvent: |
| tick: int |
| type: str |
| summary: str |
| severity: Literal["good", "neutral", "warning", "danger"] = "neutral" |
| actor_id: str | None = None |
| target_id: str | None = None |
| object_id: str | None = None |
|
|
|
|
| @dataclass(slots=True) |
| class Terrain: |
| kind: Literal["plain_green"] |
| width: int |
| depth: int |
| color: str = "#43a047" |
|
|
|
|
| @dataclass(slots=True) |
| class WorldEvent: |
| tick_created: int |
| kind: str |
| description: str |
| position: Vec3 | None |
| radius: float = 8.0 |
| duration_ticks: int = 5 |
|
|
|
|
| @dataclass(slots=True) |
| class WorldState: |
| tick: int |
| seed: int |
| terrain: Terrain |
| npcs: list[Npc] |
| last_tick_source: str = "initial" |
| last_action_debug: list[dict[str, Any]] = field(default_factory=list) |
| resource_nodes: list[ResourceNode] = field(default_factory=list) |
| beasts: list[Beast] = field(default_factory=list) |
| houses: list[House] = field(default_factory=list) |
| active_events: list[WorldEvent] = field(default_factory=list) |
| event_log: list[WorldLogEvent] = field(default_factory=list) |
| countries: list[CountryState] = field(default_factory=list) |
| elections: list[ElectionState] = field(default_factory=list) |
| game_status: GameStatus = "running" |
| population: int = 0 |
| population_cap: int = 12 |
| total_births: int = 0 |
| deaths_by_cause: dict[str, int] = field(default_factory=dict) |
| houses_built: int = 0 |
| beasts_killed: int = 0 |
| peak_population: int = 0 |
| overseer_score: int = 0 |
| chaos_score: int = 0 |
| chaos_intervention_until: int = -1 |
| famine_until: int = -1 |
| famine_saved_max: dict[str, int] = field(default_factory=dict) |
| overseer_mode: Literal["off", "advisor", "autopilot"] = "off" |
| overseer_cycle_ticks: int = 8 |
| overseer_last_tick: int | None = None |
| overseer_last_thoughts: str | None = None |
| overseer_priority_alert: str | None = None |
| overseer_last_directives: list[dict[str, Any]] = field(default_factory=list) |
| overseer_status: str = "idle" |
|
|
| def living_npcs(self) -> list[Npc]: |
| return [npc for npc in self.npcs if npc.health > 0] |
|
|
| def to_dict(self) -> dict[str, Any]: |
| return asdict(self) |
|
|
| def to_json(self, *, indent: int | None = None) -> str: |
| return json.dumps(self.to_dict(), indent=indent, sort_keys=True) |
|
|