Spaces:
Sleeping
Sleeping
| """ | |
| NPC (Non-Player Character) data models | |
| """ | |
| from datetime import datetime | |
| from typing import Optional, List, Dict | |
| from enum import Enum | |
| from pydantic import BaseModel, Field | |
| class NPCRole(str, Enum): | |
| """NPC roles in the story""" | |
| QUEST_GIVER = "Quest Giver" | |
| MERCHANT = "Merchant" | |
| ALLY = "Ally" | |
| RIVAL = "Rival" | |
| VILLAIN = "Villain" | |
| MENTOR = "Mentor" | |
| INFORMANT = "Informant" | |
| GUARD = "Guard" | |
| NOBLE = "Noble" | |
| COMMONER = "Commoner" | |
| MONSTER = "Monster" | |
| COMPANION = "Companion" | |
| class NPCDisposition(str, Enum): | |
| """NPC attitude toward party""" | |
| HOSTILE = "Hostile" | |
| UNFRIENDLY = "Unfriendly" | |
| NEUTRAL = "Neutral" | |
| FRIENDLY = "Friendly" | |
| HELPFUL = "Helpful" | |
| class NPCPersonality(BaseModel): | |
| """NPC personality traits""" | |
| traits: List[str] = Field(default_factory=list, description="2-3 personality traits") | |
| mannerisms: List[str] = Field(default_factory=list, description="Physical mannerisms") | |
| voice_description: str = Field(default="", description="How they sound") | |
| motivations: List[str] = Field(default_factory=list, description="What drives them") | |
| fears: List[str] = Field(default_factory=list, description="What they fear") | |
| secrets: List[str] = Field(default_factory=list, description="Hidden secrets") | |
| class NPCRelationship(BaseModel): | |
| """Relationship between NPC and character/party""" | |
| character_id: str = Field(description="Character or 'party' for group") | |
| relationship_type: str = Field(description="Type of relationship") | |
| affinity: int = Field(ge=-100, le=100, default=0, description="Relationship strength") | |
| history: str = Field(default="", description="Shared history") | |
| notes: str = Field(default="", description="Relationship notes") | |
| class NPC(BaseModel): | |
| """Non-player character""" | |
| # Core identity | |
| id: Optional[str] = Field(default=None, description="NPC ID") | |
| name: str = Field(min_length=1, max_length=100, description="NPC name") | |
| title: Optional[str] = Field(default=None, description="Title/honorific") | |
| # Basic info | |
| race: str = Field(description="NPC race/species") | |
| age: Optional[str] = Field(default=None, description="Age or age category") | |
| gender: Optional[str] = Field(default=None, description="Gender") | |
| occupation: str = Field(description="Occupation/profession") | |
| # Role in campaign | |
| role: NPCRole = Field(description="Story role") | |
| disposition: NPCDisposition = Field(default=NPCDisposition.NEUTRAL) | |
| importance: int = Field(ge=1, le=5, default=3, description="Story importance (1-5)") | |
| # Description | |
| appearance: str = Field(description="Physical description") | |
| personality: NPCPersonality = Field(default_factory=NPCPersonality) | |
| # Game stats (optional) | |
| challenge_rating: Optional[float] = Field(default=None, description="CR if combatant") | |
| armor_class: Optional[int] = Field(default=None) | |
| hit_points: Optional[int] = Field(default=None) | |
| # Story elements | |
| backstory: str = Field(default="", description="NPC backstory") | |
| current_situation: str = Field(default="", description="Current circumstances") | |
| goals: List[str] = Field(default_factory=list, description="NPC goals") | |
| connections: List[str] = Field(default_factory=list, description="Connected NPCs/factions") | |
| # Relationships | |
| relationships: List[NPCRelationship] = Field(default_factory=list) | |
| # Location & availability | |
| location: Optional[str] = Field(default=None, description="Current location") | |
| availability: str = Field(default="Available", description="When/where to find them") | |
| # Dialogue | |
| greeting: Optional[str] = Field(default=None, description="Standard greeting") | |
| catchphrase: Optional[str] = Field(default=None, description="Memorable phrase") | |
| dialogue_samples: List[str] = Field(default_factory=list, description="Sample dialogue") | |
| # Items & abilities | |
| notable_items: List[str] = Field(default_factory=list, description="Important items") | |
| special_abilities: List[str] = Field(default_factory=list, description="Special abilities") | |
| # Metadata | |
| campaign_id: Optional[str] = Field(default=None, description="Associated campaign") | |
| first_appearance: Optional[int] = Field(default=None, description="Session first appeared") | |
| last_appearance: Optional[int] = Field(default=None, description="Session last appeared") | |
| is_alive: bool = Field(default=True, description="Living status") | |
| created_at: datetime = Field(default_factory=datetime.now) | |
| updated_at: datetime = Field(default_factory=datetime.now) | |
| # GM notes | |
| gm_notes: str = Field(default="", description="Private GM notes") | |
| plot_hooks: List[str] = Field(default_factory=list, description="Plot hooks involving this NPC") | |
| def add_relationship(self, character_id: str, relationship_type: str, affinity: int = 0): | |
| """Add or update relationship""" | |
| for rel in self.relationships: | |
| if rel.character_id == character_id: | |
| rel.relationship_type = relationship_type | |
| rel.affinity = affinity | |
| self.updated_at = datetime.now() | |
| return | |
| # Create new relationship | |
| new_rel = NPCRelationship( | |
| character_id=character_id, | |
| relationship_type=relationship_type, | |
| affinity=affinity | |
| ) | |
| self.relationships.append(new_rel) | |
| self.updated_at = datetime.now() | |
| def change_disposition(self, new_disposition: NPCDisposition): | |
| """Change NPC disposition""" | |
| self.disposition = new_disposition | |
| self.updated_at = datetime.now() | |
| def update_location(self, location: str): | |
| """Update NPC location""" | |
| self.location = location | |
| self.updated_at = datetime.now() | |
| def record_appearance(self, session_number: int): | |
| """Record NPC appearance in session""" | |
| if self.first_appearance is None: | |
| self.first_appearance = session_number | |
| self.last_appearance = session_number | |
| self.updated_at = datetime.now() | |
| def get_relationship_with(self, character_id: str) -> Optional[NPCRelationship]: | |
| """Get relationship with specific character""" | |
| for rel in self.relationships: | |
| if rel.character_id == character_id: | |
| return rel | |
| return None | |
| def to_roleplay_prompt(self) -> str: | |
| """Generate prompt for AI to roleplay this NPC""" | |
| return f"""You are roleplaying as {self.name}, a {self.race} {self.occupation}. | |
| APPEARANCE: {self.appearance} | |
| PERSONALITY: | |
| - Traits: {', '.join(self.personality.traits)} | |
| - Mannerisms: {', '.join(self.personality.mannerisms)} | |
| - Voice: {self.personality.voice_description} | |
| BACKGROUND: {self.backstory} | |
| CURRENT SITUATION: {self.current_situation} | |
| MOTIVATIONS: {', '.join(self.personality.motivations)} | |
| DISPOSITION: {self.disposition.value} | |
| TYPICAL GREETING: {self.greeting or 'Generic greeting'} | |
| Roleplay this character authentically, staying in character and reflecting their personality, motivations, and current circumstances. | |
| """ | |
| def to_markdown(self) -> str: | |
| """Generate markdown NPC sheet""" | |
| return f"""# {self.name} | |
| {f'*{self.title}*' if self.title else ''} | |
| **{self.race} {self.occupation}** | **{self.role.value}** | |
| **Disposition:** {self.disposition.value} | **Importance:** {'⭐' * self.importance} | |
| ## Appearance | |
| {self.appearance} | |
| ## Personality | |
| **Traits:** {', '.join(self.personality.traits)} | |
| **Mannerisms:** {', '.join(self.personality.mannerisms)} | |
| **Voice:** {self.personality.voice_description} | |
| **Motivations:** {', '.join(self.personality.motivations)} | |
| **Fears:** {', '.join(self.personality.fears)} | |
| ## Background | |
| {self.backstory} | |
| ## Current Situation | |
| {self.current_situation} | |
| ## Goals | |
| {chr(10).join(f"- {goal}" for goal in self.goals)} | |
| ## Location & Availability | |
| **Location:** {self.location or 'Unknown'} | |
| **Availability:** {self.availability} | |
| ## Connections | |
| {', '.join(self.connections)} | |
| ## Dialogue | |
| **Greeting:** "{self.greeting or 'Hello there.'}" | |
| **Catchphrase:** "{self.catchphrase or 'N/A'}" | |
| ## Combat Stats | |
| {f"**CR:** {self.challenge_rating} | **AC:** {self.armor_class} | **HP:** {self.hit_points}" if self.challenge_rating else "*Not a combatant*"} | |
| ## Plot Hooks | |
| {chr(10).join(f"- {hook}" for hook in self.plot_hooks)} | |
| ## GM Notes | |
| {self.gm_notes} | |
| """ | |
| class Config: | |
| json_schema_extra = { | |
| "example": { | |
| "name": "Elara Moonwhisper", | |
| "race": "Elf", | |
| "occupation": "Sage", | |
| "role": "Quest Giver", | |
| "disposition": "Friendly", | |
| "appearance": "Ancient elf with silver hair and piercing blue eyes", | |
| "personality": { | |
| "traits": ["Wise", "Mysterious", "Patient"], | |
| "voice_description": "Soft and melodic" | |
| } | |
| } | |
| } | |