Spaces:
Sleeping
Sleeping
| """ | |
| Campaign and session management data models | |
| """ | |
| from datetime import datetime | |
| from typing import Optional, List, Dict, Any | |
| from enum import Enum | |
| from pydantic import BaseModel, Field | |
| class CampaignTheme(str, Enum): | |
| """Campaign themes""" | |
| HIGH_FANTASY = "High Fantasy" | |
| DARK_FANTASY = "Dark Fantasy" | |
| URBAN_FANTASY = "Urban Fantasy" | |
| POLITICAL_INTRIGUE = "Political Intrigue" | |
| HORROR = "Horror" | |
| EXPLORATION = "Exploration" | |
| DUNGEON_CRAWL = "Dungeon Crawl" | |
| CUSTOM = "Custom" | |
| class EventType(str, Enum): | |
| """Types of campaign events""" | |
| COMBAT = "Combat" | |
| SOCIAL = "Social" | |
| EXPLORATION = "Exploration" | |
| DISCOVERY = "Discovery" | |
| PLOT_DEVELOPMENT = "Plot Development" | |
| CHARACTER_MOMENT = "Character Moment" | |
| NPC_INTERACTION = "NPC Interaction" | |
| QUEST_UPDATE = "Quest Update" | |
| class CampaignEvent(BaseModel): | |
| """Individual campaign event/memory""" | |
| id: Optional[str] = Field(default=None, description="Event ID") | |
| campaign_id: str = Field(description="Campaign this event belongs to") | |
| session_number: int = Field(ge=1, description="Session number") | |
| event_type: EventType = Field(description="Type of event") | |
| title: str = Field(min_length=1, max_length=200, description="Event title") | |
| description: str = Field(description="Full event description") | |
| # Participants | |
| characters_involved: List[str] = Field(default_factory=list, description="Character IDs") | |
| npcs_involved: List[str] = Field(default_factory=list, description="NPC IDs") | |
| locations: List[str] = Field(default_factory=list, description="Location names") | |
| # Context | |
| consequences: List[str] = Field(default_factory=list, description="Event consequences") | |
| items_gained: List[str] = Field(default_factory=list, description="Items acquired") | |
| items_lost: List[str] = Field(default_factory=list, description="Items lost") | |
| experience_awarded: int = Field(ge=0, default=0, description="XP awarded") | |
| # Metadata | |
| timestamp: datetime = Field(default_factory=datetime.now) | |
| importance: int = Field(ge=1, le=5, default=3, description="Event importance (1-5)") | |
| tags: List[str] = Field(default_factory=list, description="Event tags") | |
| # GM notes | |
| gm_notes: str = Field(default="", description="Private GM notes") | |
| player_visible: bool = Field(default=True, description="Visible to players") | |
| def to_markdown(self) -> str: | |
| """Convert to markdown""" | |
| return f"""## {self.title} | |
| **Type:** {self.event_type.value} | **Importance:** {'⭐' * self.importance} | |
| **Session:** {self.session_number} | **Date:** {self.timestamp.strftime('%Y-%m-%d')} | |
| {self.description} | |
| **Participants:** {', '.join(self.characters_involved)} | |
| **NPCs:** {', '.join(self.npcs_involved)} | |
| **Locations:** {', '.join(self.locations)} | |
| **Consequences:** | |
| {chr(10).join(f"- {c}" for c in self.consequences)} | |
| """ | |
| class CampaignMemory(BaseModel): | |
| """Searchable campaign memory/context""" | |
| campaign_id: str = Field(description="Campaign ID") | |
| # Full context for AI | |
| full_context: str = Field(default="", description="Complete campaign context") | |
| # Key information | |
| major_npcs: Dict[str, str] = Field(default_factory=dict, description="NPC name -> description") | |
| key_locations: Dict[str, str] = Field(default_factory=dict, description="Location -> description") | |
| active_quests: List[str] = Field(default_factory=list, description="Current quests") | |
| completed_quests: List[str] = Field(default_factory=list, description="Finished quests") | |
| # Story beats | |
| important_events: List[str] = Field(default_factory=list, description="Key story moments") | |
| unresolved_threads: List[str] = Field(default_factory=list, description="Plot threads") | |
| secrets_discovered: List[str] = Field(default_factory=list, description="Revealed secrets") | |
| secrets_hidden: List[str] = Field(default_factory=list, description="Hidden secrets") | |
| # Relationships | |
| faction_standings: Dict[str, int] = Field(default_factory=dict, description="Faction name -> standing (-100 to 100)") | |
| npc_relationships: Dict[str, str] = Field(default_factory=dict, description="NPC -> relationship status") | |
| # Metadata | |
| last_updated: datetime = Field(default_factory=datetime.now) | |
| total_events: int = Field(ge=0, default=0) | |
| def add_event_to_memory(self, event: CampaignEvent): | |
| """Update memory with new event""" | |
| self.total_events += 1 | |
| # Add NPCs | |
| for npc in event.npcs_involved: | |
| if npc not in self.major_npcs: | |
| self.major_npcs[npc] = "Recently introduced" | |
| # Add locations | |
| for location in event.locations: | |
| if location not in self.key_locations: | |
| self.key_locations[location] = "Visited" | |
| # Add to important events if high importance | |
| if event.importance >= 4: | |
| self.important_events.append(event.title) | |
| self.last_updated = datetime.now() | |
| class Campaign(BaseModel): | |
| """Complete D&D campaign""" | |
| # Core identity | |
| id: Optional[str] = Field(default=None, description="Campaign ID") | |
| name: str = Field(min_length=1, max_length=100, description="Campaign name") | |
| theme: CampaignTheme = Field(description="Campaign theme") | |
| # Setting | |
| setting: str = Field(description="Campaign setting description") | |
| world_name: str = Field(default="", description="World/realm name") | |
| starting_location: str = Field(default="", description="Starting location") | |
| # Campaign info | |
| summary: str = Field(description="Campaign summary/hook") | |
| current_arc: str = Field(default="", description="Current story arc") | |
| level_range: str = Field(default="1-5", description="Expected level range") | |
| # Story elements | |
| main_conflict: str = Field(description="Central conflict") | |
| key_factions: List[str] = Field(default_factory=list, description="Important factions") | |
| major_villains: List[str] = Field(default_factory=list, description="Main antagonists") | |
| central_mysteries: List[str] = Field(default_factory=list, description="Ongoing mysteries") | |
| # Characters | |
| character_ids: List[str] = Field(default_factory=list, description="Player character IDs") | |
| party_size: int = Field(ge=1, le=10, default=4, description="Expected party size") | |
| # Session tracking | |
| current_session: int = Field(ge=1, default=1, description="Current session number") | |
| total_sessions: int = Field(ge=0, default=0, description="Total sessions played") | |
| # Memory | |
| memory: CampaignMemory = Field(default=None, description="Campaign memory") | |
| # Metadata | |
| game_master: str = Field(default="", description="GM name") | |
| created_at: datetime = Field(default_factory=datetime.now) | |
| updated_at: datetime = Field(default_factory=datetime.now) | |
| last_session_date: Optional[datetime] = Field(default=None) | |
| # Settings | |
| is_active: bool = Field(default=True, description="Campaign active?") | |
| homebrew_rules: List[str] = Field(default_factory=list, description="Custom rules") | |
| notes: str = Field(default="", description="GM notes") | |
| def __init__(self, **data): | |
| super().__init__(**data) | |
| if self.memory is None: | |
| self.memory = CampaignMemory(campaign_id=self.id or "") | |
| def add_character(self, character_id: str): | |
| """Add character to campaign""" | |
| if character_id not in self.character_ids: | |
| self.character_ids.append(character_id) | |
| self.updated_at = datetime.now() | |
| def remove_character(self, character_id: str): | |
| """Remove character from campaign""" | |
| if character_id in self.character_ids: | |
| self.character_ids.remove(character_id) | |
| self.updated_at = datetime.now() | |
| def start_new_session(self): | |
| """Start a new session""" | |
| self.current_session += 1 | |
| self.total_sessions += 1 | |
| self.last_session_date = datetime.now() | |
| self.updated_at = datetime.now() | |
| def add_event(self, event: CampaignEvent): | |
| """Add event to campaign memory""" | |
| self.memory.add_event_to_memory(event) | |
| self.updated_at = datetime.now() | |
| def to_context_string(self) -> str: | |
| """Generate context string for AI""" | |
| return f"""Campaign: {self.name} | |
| Theme: {self.theme.value} | |
| Setting: {self.setting} | |
| Main Conflict: {self.main_conflict} | |
| Current Arc: {self.current_arc} | |
| Session: {self.current_session} | |
| Key Factions: {', '.join(self.key_factions)} | |
| Major Villains: {', '.join(self.major_villains)} | |
| Active Quests: {', '.join(self.memory.active_quests)} | |
| Important Events: {', '.join(self.memory.important_events[-5:])} # Last 5 events | |
| Party Size: {self.party_size} | |
| Level Range: {self.level_range} | |
| """ | |
| def to_markdown(self) -> str: | |
| """Generate markdown campaign summary""" | |
| return f"""# {self.name} | |
| **{self.theme.value} Campaign** | |
| ## Setting | |
| {self.setting} | |
| **World:** {self.world_name} | |
| **Starting Location:** {self.starting_location} | |
| ## Campaign Summary | |
| {self.summary} | |
| ## Main Conflict | |
| {self.main_conflict} | |
| ## Current Story Arc | |
| {self.current_arc} | |
| ## Key Elements | |
| **Factions:** {', '.join(self.key_factions)} | |
| **Villains:** {', '.join(self.major_villains)} | |
| **Mysteries:** {', '.join(self.central_mysteries)} | |
| ## Party Information | |
| **Party Size:** {self.party_size} | |
| **Level Range:** {self.level_range} | |
| **Current Session:** {self.current_session} | |
| **Total Sessions:** {self.total_sessions} | |
| ## Campaign Status | |
| **Active:** {'Yes' if self.is_active else 'No'} | |
| **Last Session:** {self.last_session_date.strftime('%Y-%m-%d') if self.last_session_date else 'Not started'} | |
| ## GM Notes | |
| {self.notes} | |
| """ | |
| class Config: | |
| json_schema_extra = { | |
| "example": { | |
| "name": "The Shattered Crown", | |
| "theme": "High Fantasy", | |
| "setting": "A kingdom torn by civil war", | |
| "summary": "Adventurers must unite the realm", | |
| "main_conflict": "Succession crisis threatens the kingdom", | |
| "party_size": 4, | |
| "level_range": "1-10" | |
| } | |
| } | |