# Data Models All data models use **Python dataclasses** serialized to JSON for storage in Appwrite. --- ## Type System (Enums) All enum types are defined in `/app/models/enums.py` for type safety throughout the application. ### EffectType | Value | Description | |-------|-------------| | `BUFF` | Temporarily increase stats | | `DEBUFF` | Temporarily decrease stats | | `DOT` | Damage over time (poison, bleed, burn) | | `HOT` | Heal over time (regeneration) | | `STUN` | Prevent actions (skip turn) | | `SHIELD` | Absorb damage before HP loss | ### DamageType | Value | Description | |-------|-------------| | `PHYSICAL` | Standard weapon damage | | `FIRE` | Fire-based magic damage | | `ICE` | Ice-based magic damage | | `LIGHTNING` | Lightning-based magic damage | | `HOLY` | Holy/divine damage | | `SHADOW` | Dark/shadow magic damage | | `POISON` | Poison damage (usually DoT) | ### ItemType | Value | Description | |-------|-------------| | `WEAPON` | Adds damage, may have special effects | | `ARMOR` | Adds defense/resistance | | `CONSUMABLE` | One-time use (potions, scrolls) | | `QUEST_ITEM` | Story-related, non-tradeable | ### StatType | Value | Description | |-------|-------------| | `STRENGTH` | Physical power | | `DEXTERITY` | Agility and precision | | `CONSTITUTION` | Endurance and health | | `INTELLIGENCE` | Magical power | | `WISDOM` | Perception and insight | | `CHARISMA` | Social influence | ### AbilityType | Value | Description | |-------|-------------| | `ATTACK` | Basic physical attack | | `SPELL` | Magical spell | | `SKILL` | Special class ability | | `ITEM_USE` | Using a consumable item | | `DEFEND` | Defensive action | ### CombatStatus | Value | Description | |-------|-------------| | `ACTIVE` | Combat is ongoing | | `VICTORY` | Player(s) won | | `DEFEAT` | Player(s) lost | | `FLED` | Player(s) escaped | ### SessionStatus | Value | Description | |-------|-------------| | `ACTIVE` | Session is ongoing | | `COMPLETED` | Session ended normally | | `TIMEOUT` | Session ended due to inactivity | ### ListingType & ListingStatus **ListingType:** - `AUCTION` - Bidding system - `FIXED_PRICE` - Immediate purchase at set price **ListingStatus:** - `ACTIVE` - Listing is live - `SOLD` - Item has been sold - `EXPIRED` - Listing time ran out - `REMOVED` - Seller cancelled listing ### LocationType Types of locations in the game world (defined in both `enums.py` and `action_prompt.py`). | Value | Description | |-------|-------------| | `TOWN` | Populated settlements | | `TAVERN` | Taverns and inns | | `WILDERNESS` | Outdoor areas, forests, fields | | `DUNGEON` | Dungeons and caves | | `RUINS` | Ancient ruins | | `LIBRARY` | Libraries and archives | | `SAFE_AREA` | Protected zones, temples | --- ## Location System Locations define the game world structure. They are loaded from YAML files at runtime via `LocationLoader`. ### Location Represents a defined location in the game world. | Field | Type | Description | |-------|------|-------------| | `location_id` | str | Unique identifier (e.g., "crossville_tavern") | | `name` | str | Display name (e.g., "The Rusty Anchor Tavern") | | `location_type` | LocationType | Type (town, tavern, wilderness, dungeon, etc.) | | `region_id` | str | Parent region this location belongs to | | `description` | str | Full description for AI narrative context | | `lore` | Optional[str] | Historical/background information | | `ambient_description` | Optional[str] | Atmospheric details for AI narration | | `available_quests` | List[str] | Quest IDs discoverable at this location | | `npc_ids` | List[str] | NPC IDs present at this location | | `discoverable_locations` | List[str] | Location IDs that can be revealed from here | | `is_starting_location` | bool | Whether valid for new character spawn | | `tags` | List[str] | Metadata tags for filtering/categorization | **Methods:** - `to_dict()` - Serialize for JSON responses - `to_story_dict()` - Trimmed version for AI prompts (reduces token usage) - `from_dict(data)` - Deserialize from YAML/JSON **YAML Format:** ```yaml location_id: "crossville_tavern" name: "The Rusty Anchor Tavern" location_type: "tavern" region_id: "crossville" description: "A cozy tavern known for its hearty stew and warm atmosphere." lore: "Built fifty years ago by a retired sailor." ambient_description: "The smell of roasting meat and spilled ale fills the air." available_quests: - "quest_rats_tavern" npc_ids: - "npc_grom_001" - "npc_elara_001" discoverable_locations: - "crossville_market" - "crossville_forest_path" is_starting_location: false tags: - "social" - "rest" ``` ### Region Represents a geographical region containing multiple locations. | Field | Type | Description | |-------|------|-------------| | `region_id` | str | Unique identifier (e.g., "crossville") | | `name` | str | Display name (e.g., "Crossville Province") | | `description` | str | Region overview and atmosphere | | `location_ids` | List[str] | All location IDs in this region | **YAML Format:** ```yaml region_id: "crossville" name: "Crossville Province" description: "A peaceful farming region on the edge of the kingdom." location_ids: - "crossville_village" - "crossville_tavern" - "crossville_market" ``` ### LocationLoader Service Singleton service that loads and caches location/region data from YAML files. **Location:** `/app/services/location_loader.py` **Usage:** ```python from app.services.location_loader import get_location_loader loader = get_location_loader() # Get specific location location = loader.get_location("crossville_tavern") # Get all locations in a region locations = loader.get_locations_by_region("crossville") # Get starting locations for new characters starting_locations = loader.get_starting_locations() # Get connected locations for travel available = loader.get_discoverable_from("crossville_village") ``` **Data Files:** - `/app/data/regions/crossville.yaml` - Region definition with locations --- ## NPC System NPCs are persistent non-player characters with rich personality, knowledge, and interaction tracking. They are loaded from YAML files via `NPCLoader`. ### NPC Main NPC definition with personality and dialogue data for AI generation. | Field | Type | Description | |-------|------|-------------| | `npc_id` | str | Unique identifier (e.g., "npc_grom_001") | | `name` | str | Display name (e.g., "Grom Ironbeard") | | `role` | str | NPC's job/title (e.g., "bartender", "blacksmith") | | `location_id` | str | ID of location where NPC resides | | `personality` | NPCPersonality | Personality traits and speech patterns | | `appearance` | NPCAppearance | Physical description | | `image_url` | Optional[str] | URL path to NPC portrait image (e.g., "/static/images/npcs/crossville/grom_ironbeard.png") | | `knowledge` | Optional[NPCKnowledge] | What the NPC knows (public and secret) | | `relationships` | List[NPCRelationship] | How NPC feels about other NPCs | | `inventory_for_sale` | List[NPCInventoryItem] | Items NPC sells (if merchant) | | `dialogue_hooks` | Optional[NPCDialogueHooks] | Pre-defined dialogue snippets | | `quest_giver_for` | List[str] | Quest IDs this NPC can give | | `reveals_locations` | List[str] | Location IDs this NPC can unlock | | `tags` | List[str] | Metadata tags (e.g., "merchant", "quest_giver") | **Methods:** - `to_dict()` - Serialize for JSON responses - `to_story_dict()` - Trimmed version for AI dialogue prompts - `from_dict(data)` - Deserialize from YAML/JSON ### NPCPersonality Personality traits for AI dialogue generation. | Field | Type | Description | |-------|------|-------------| | `traits` | List[str] | Personality descriptors (e.g., "gruff", "kind", "suspicious") | | `speech_style` | str | How the NPC speaks (accent, vocabulary, patterns) | | `quirks` | List[str] | Distinctive behaviors or habits | ### NPCAppearance Physical description for AI narration. | Field | Type | Description | |-------|------|-------------| | `brief` | str | Short one-line description for lists | | `detailed` | Optional[str] | Longer description for detailed encounters | ### NPCKnowledge Knowledge an NPC possesses - public and conditionally revealed. | Field | Type | Description | |-------|------|-------------| | `public` | List[str] | Knowledge NPC freely shares with anyone | | `secret` | List[str] | Hidden knowledge (for AI reference only) | | `will_share_if` | List[NPCKnowledgeCondition] | Conditional reveals based on interaction | ### NPCKnowledgeCondition Condition for revealing secret knowledge. | Field | Type | Description | |-------|------|-------------| | `condition` | str | Expression (e.g., "interaction_count >= 3") | | `reveals` | str | Information revealed when condition is met | ### NPCDialogueHooks Pre-defined dialogue snippets for consistent NPC voice. | Field | Type | Description | |-------|------|-------------| | `greeting` | Optional[str] | What NPC says when first addressed | | `farewell` | Optional[str] | What NPC says when conversation ends | | `busy` | Optional[str] | What NPC says when occupied | | `quest_complete` | Optional[str] | What NPC says when player completes their quest | ### NPCRelationship NPC-to-NPC relationship for dialogue context. | Field | Type | Description | |-------|------|-------------| | `npc_id` | str | The other NPC's identifier | | `attitude` | str | Feeling (e.g., "friendly", "distrustful") | | `reason` | Optional[str] | Explanation for the attitude | ### NPCInventoryItem Item available for purchase from merchant NPCs. | Field | Type | Description | |-------|------|-------------| | `item_id` | str | Reference to item definition | | `price` | int | Cost in gold | | `quantity` | Optional[int] | Stock count (None = unlimited) | ### NPCInteractionState Tracks a character's interaction history with an NPC. Stored on Character record. | Field | Type | Description | |-------|------|-------------| | `npc_id` | str | The NPC this state tracks | | `first_met` | str | ISO timestamp of first interaction | | `last_interaction` | str | ISO timestamp of most recent interaction | | `interaction_count` | int | Total number of conversations | | `revealed_secrets` | List[int] | Indices of secrets revealed | | `relationship_level` | int | 0-100 scale (50 is neutral) | | `custom_flags` | Dict[str, Any] | Arbitrary flags for special conditions | | `dialogue_history` | List[Dict] | Recent conversation exchanges (max 10 per NPC) | **Dialogue History Entry Format:** ```json { "player_line": "What have you heard about the old mines?", "npc_response": "Aye, strange noises coming from there lately...", "timestamp": "2025-11-24T10:30:00Z" } ``` The dialogue history enables bidirectional NPC conversations - players can respond to NPC dialogue and continue conversations with context. The system maintains the last 10 exchanges per NPC to provide conversation context without excessive storage. **Relationship Levels:** - 0-20: Hostile - 21-40: Unfriendly - 41-60: Neutral - 61-80: Friendly - 81-100: Trusted **Example NPC YAML:** ```yaml npc_id: "npc_grom_001" name: "Grom Ironbeard" role: "bartender" location_id: "crossville_tavern" image_url: "/static/images/npcs/crossville/grom_ironbeard.png" personality: traits: - "gruff" - "honest" - "protective of locals" speech_style: "Short sentences, occasional dwarven expressions" quirks: - "Polishes same glass repeatedly when nervous" - "Refuses to serve anyone who insults his stew" appearance: brief: "A stocky dwarf with a magnificent iron-grey beard" detailed: "A weathered dwarf standing about four feet tall..." knowledge: public: - "The tavern was built by his grandfather" - "Knows most travelers who pass through" secret: - "Saw strange lights in the forest last week" will_share_if: - condition: "relationship_level >= 70" reveals: "Has heard rumors of goblins gathering in the old mines" dialogue_hooks: greeting: "Welcome to the Rusty Anchor! What'll it be?" farewell: "Safe travels, friend." busy: "Can't talk now, got orders to fill." inventory_for_sale: - item: "ale" price: 2 - item: "hearty_stew" price: 5 quest_giver_for: - "quest_rats_tavern" reveals_locations: - "crossville_old_mines" tags: - "merchant" - "quest_giver" - "information" ``` ### NPCLoader Service Singleton service that loads and caches NPC data from YAML files. **Location:** `/app/services/npc_loader.py` **Usage:** ```python from app.services.npc_loader import get_npc_loader loader = get_npc_loader() # Get specific NPC npc = loader.get_npc("npc_grom_001") # Get all NPCs at a location npcs = loader.get_npcs_at_location("crossville_tavern") # Get NPCs by tag merchants = loader.get_npcs_by_tag("merchant") ``` **Data Files:** - `/app/data/npcs/crossville_npcs.yaml` - NPCs for Crossville region --- ## Character System ### Stats | Field | Type | Description | |-------|------|-------------| | `strength` | int | Physical power | | `dexterity` | int | Agility and precision | | `constitution` | int | Endurance and health | | `intelligence` | int | Magical power | | `wisdom` | int | Perception and insight | | `charisma` | int | Social influence | **Derived Properties (Computed):** - `hit_points` = 10 + (constitution × 2) - `mana_points` = 10 + (intelligence × 2) - `defense` = constitution // 2 (physical damage reduction) - `resistance` = wisdom // 2 (magical damage reduction) **Note:** Defense and resistance are computed properties, not stored values. They are calculated on-the-fly from constitution and wisdom. ### SkillNode | Field | Type | Description | |-------|------|-------------| | `skill_id` | str | Unique identifier | | `name` | str | Display name | | `description` | str | What the skill does | | `tier` | int | 1-5 (1=basic, 5=master) | | `prerequisites` | List[str] | Required skill_ids | | `effects` | Dict | Stat bonuses, abilities unlocked | | `unlocked` | bool | Current unlock status | **Effect Types:** - Passive bonuses (permanent stat increases) - Active abilities (new spells/skills to use) - Unlocks (access to equipment types or features) ### SkillTree | Field | Type | Description | |-------|------|-------------| | `tree_id` | str | Unique identifier | | `name` | str | Tree name | | `description` | str | Tree theme | | `nodes` | List[SkillNode] | All nodes in tree | **Methods:** - `can_unlock(skill_id, unlocked_skills)` - Check if prerequisites met **Progression Rules:** - Must unlock tier 1 before accessing tier 2 - Some nodes have prerequisites within same tier - 1 skill point earned per level - Respec available (costs gold, scales with level) ### PlayerClass | Field | Type | Description | |-------|------|-------------| | `class_id` | str | Unique identifier | | `name` | str | Class name | | `description` | str | Class theme | | `base_stats` | Stats | Starting stats | | `skill_trees` | List[SkillTree] | 2+ skill trees | | `starting_equipment` | List[str] | Starting item IDs | ### Initial 8 Player Classes | Class | Theme | Skill Tree 1 | Skill Tree 2 | |-------|-------|--------------|--------------| | **Vanguard** | Tank/melee | Defensive (shields, armor, taunts) | Offensive (weapon mastery, heavy strikes) | | **Assassin** | Stealth/critical | Assassination (critical hits, poisons) | Shadow (stealth, evasion) | | **Arcanist** | Elemental spells | Pyromancy (fire spells, DoT) | Cryomancy (ice spells, crowd control) | | **Luminary** | Healing/support | Holy (healing, buffs) | Divine Wrath (smite, undead damage) | | **Wildstrider** | Ranged/nature | Marksmanship (bow skills, critical shots) | Beast Master (pet companion, nature magic) | | **Oathkeeper** | Hybrid tank/healer | Protection (defensive auras, healing) | Retribution (holy damage, smites) | | **Necromancer** | Death magic/summon | Dark Arts (curses, life drain) | Summoning (undead minions) | | **Lorekeeper** | Support/control | Performance (buffs, debuffs via music) | Trickery (illusions, charm) | **Extensibility:** Class system designed to easily add more classes in future updates. ### Item | Field | Type | Description | |-------|------|-------------| | `item_id` | str | Unique identifier | | `name` | str | Item name | | `item_type` | str | weapon, armor, consumable, quest_item | | `stats` | Dict[str, int] | {"damage": 10, "defense": 5} | | `effects` | List[Effect] | Buffs/debuffs on use/equip | | `value` | int | Gold value | | `description` | str | Item lore/description | | `is_tradeable` | bool | Can be sold on marketplace | **Item Types:** - **Weapon:** Adds damage, may have special effects - **Armor:** Adds defense/resistance - **Consumable:** One-time use (potions, scrolls) - **Quest Item:** Story-related, non-tradeable ### Ability Abilities represent actions that can be taken in combat (attacks, spells, skills, item uses). | Field | Type | Description | |-------|------|-------------| | `ability_id` | str | Unique identifier | | `name` | str | Display name | | `description` | str | What the ability does | | `ability_type` | AbilityType | ATTACK, SPELL, SKILL, ITEM_USE, DEFEND | | `base_power` | int | Base damage or healing value | | `damage_type` | DamageType | Type of damage dealt (if applicable) | | `scaling_stat` | StatType | Which stat scales this ability's power | | `scaling_factor` | float | Multiplier for scaling stat (default 0.5) | | `mana_cost` | int | MP required to use this ability | | `cooldown` | int | Turns before ability can be used again | | `effects_applied` | List[Effect] | Effects applied to target(s) on hit | | `is_aoe` | bool | Whether this affects multiple targets | | `target_count` | int | Number of targets if AoE (0 = all) | **Damage/Healing Calculation:** ``` Final Power = base_power + (scaling_stat × scaling_factor) Minimum power is always 1 ``` **Example:** - Fireball: base_power=30, scaling_stat=INTELLIGENCE, scaling_factor=0.5 - If caster has 16 intelligence: 30 + (16 × 0.5) = 38 power **Methods:** - `calculate_power(caster_stats)` - Calculate final power based on caster's stats - `get_effects_to_apply()` - Get copies of effects to apply to targets ### AbilityLoader Abilities are loaded from YAML configuration files in `/app/data/abilities/` for data-driven game design. **YAML Format:** ```yaml ability_id: "fireball" name: "Fireball" description: "Hurl a ball of fire at enemies" ability_type: "spell" base_power: 30 damage_type: "fire" scaling_stat: "intelligence" scaling_factor: 0.5 mana_cost: 15 cooldown: 0 is_aoe: false target_count: 1 effects_applied: - effect_id: "burn" name: "Burning" effect_type: "dot" duration: 3 power: 5 max_stacks: 3 ``` **Usage:** ```python from app.models.abilities import AbilityLoader loader = AbilityLoader() fireball = loader.load_ability("fireball") power = fireball.calculate_power(caster_stats) ``` **Benefits:** - Game designers can add/modify abilities without code changes - Easy balancing and iteration - Version control friendly (text files) - Hot-reloading capable ### Character | Field | Type | Description | |-------|------|-------------| | `character_id` | str | Unique identifier | | `user_id` | str | Owner user ID | | `name` | str | Character name | | `player_class` | PlayerClass | Character class | | `level` | int | Current level | | `experience` | int | XP points | | `stats` | Stats | Current stats | | `unlocked_skills` | List[str] | Unlocked skill_ids | | `inventory` | List[Item] | All items | | `equipped` | Dict[str, Item] | {"weapon": Item, "armor": Item} | | `gold` | int | Currency | | `active_quests` | List[str] | Quest IDs | | `discovered_locations` | List[str] | Location IDs | **Methods:** - `to_dict()` - Serialize to dictionary for JSON storage - `from_dict(data)` - Deserialize from dictionary - `get_effective_stats(active_effects)` - **THE CRITICAL METHOD** - Calculate final stats **get_effective_stats() Details:** This is the **single source of truth** for all stat calculations in the game. It combines modifiers from all sources in this order: ```python def get_effective_stats(self, active_effects: Optional[List[Effect]] = None) -> Stats: """ Calculate final effective stats from all sources: 1. Base stats (from character) 2. Equipment bonuses (from equipped items) 3. Skill tree bonuses (from unlocked skills) 4. Active effect modifiers (buffs/debuffs from combat) Returns fully typed Stats object with all modifiers applied. Debuffs are clamped to minimum stat value of 1. """ ``` **Example Calculation:** - Base strength: 12 - Equipped weapon bonus: +5 strength - Unlocked skill bonus: +5 strength - Active buff effect: +3 strength - **Final effective strength: 25** **Important Notes:** - Defense and resistance are calculated from final constitution/wisdom - Debuffs cannot reduce stats below 1 (minimum clamping) - Equipment stat_bonuses dictionary: `{"strength": 5, "constitution": 3}` - Skill effects dictionary: `{"strength": 5}` extracted from unlocked skills --- ## Story Progression System ### ActionPrompt Represents a button-based action prompt available to players during story progression turns. | Field | Type | Description | |-------|------|-------------| | `prompt_id` | str | Unique identifier (e.g., "ask_surroundings") | | `category` | str | Action category: "ask", "travel", "gather" | | `display_text` | str | Button text shown to player | | `description` | str | Tooltip/help text | | `tier_required` | str | Minimum tier: "free", "basic", "premium", "elite" | | `context_filter` | Optional[str] | Where action is available: "town", "wilderness", "any" | | `dm_prompt_template` | str | Jinja2 template for AI prompt generation | **Methods:** - `is_available(user_tier, location_type) -> bool` - Check if action available to user **YAML Format:** ```yaml prompt_id: "ask_surroundings" category: "ask" display_text: "What do I see around me?" description: "Get a description of your current surroundings" tier_required: "free" context_filter: "any" dm_prompt_template: | The player is currently in {{ location_name }}. Describe what they see, hear, and sense around them. ``` **Tier-Based Availability:** - **Free tier**: 4 basic actions (ask surroundings, check dangers, travel, explore) - **Premium tier**: +3 actions (recall memory, ask around, visit tavern) - **Elite tier**: +3 actions (search secrets, seek elder, chart course) - **Premium/Elite**: Custom free-form input (250/500 char limits) **Loading:** Actions are loaded from `/app/data/action_prompts.yaml` via `ActionPromptLoader` service. ### AI Response Parser Data structures for parsing structured game actions from AI narrative responses. #### ParsedAIResponse Complete parsed AI response with narrative and game state changes. | Field | Type | Description | |-------|------|-------------| | `narrative` | str | The narrative text to display to player | | `game_changes` | GameStateChanges | Structured game state changes | | `raw_response` | str | Original unparsed response | | `parse_success` | bool | Whether parsing succeeded | | `parse_errors` | List[str] | Any errors encountered | #### GameStateChanges Structured game state changes extracted from AI response. | Field | Type | Description | |-------|------|-------------| | `items_given` | List[ItemGrant] | Items to add to player inventory | | `items_taken` | List[str] | Item IDs to remove | | `gold_given` | int | Gold to add to player | | `gold_taken` | int | Gold to remove from player | | `experience_given` | int | XP to award player | | `quest_offered` | Optional[str] | Quest ID to offer | | `quest_completed` | Optional[str] | Quest ID completed | | `location_change` | Optional[str] | New location ID | #### ItemGrant Represents an item granted by the AI during gameplay. | Field | Type | Description | |-------|------|-------------| | `item_id` | Optional[str] | ID for existing items from registry | | `name` | Optional[str] | Name for generic items | | `item_type` | Optional[str] | Type: weapon, armor, consumable, quest_item | | `description` | Optional[str] | Description for generic items | | `value` | int | Gold value (default 0) | | `quantity` | int | Number of items (default 1) | **Methods:** - `is_existing_item() -> bool` - Check if references existing item - `is_generic_item() -> bool` - Check if AI-generated generic item **Files:** - Parser: `/app/ai/response_parser.py` - Validator: `/app/services/item_validator.py` - Templates: `/app/data/generic_items.yaml` --- ## Quest System ### Quest Represents a quest with objectives and rewards. | Field | Type | Description | |-------|------|-------------| | `quest_id` | str | Unique identifier (e.g., "quest_rats_tavern") | | `name` | str | Display name (e.g., "Rat Problem") | | `description` | str | Full quest description | | `quest_giver` | str | NPC or source name | | `difficulty` | str | "easy", "medium", "hard", "epic" | | `objectives` | List[QuestObjective] | List of objectives to complete | | `rewards` | QuestReward | Rewards for completion | | `offering_triggers` | QuestTriggers | When/where quest can be offered | | `narrative_hooks` | List[str] | Story snippets for AI to use | | `status` | str | "available", "active", "completed", "failed" | | `progress` | Dict[str, Any] | Objective progress tracking | **Methods:** - `is_complete() -> bool` - Check if all objectives completed - `get_next_objective() -> Optional[QuestObjective]` - Get next incomplete objective - `update_progress(objective_id, progress_value) -> None` - Update objective progress - `to_dict() / from_dict()` - Serialization for JSON storage **YAML Format:** ```yaml quest_id: "quest_rats_tavern" name: "Rat Problem" description: "Clear giant rats from the tavern basement" quest_giver: "Tavern Keeper" difficulty: "easy" objectives: - objective_id: "kill_rats" description: "Kill 10 giant rats" objective_type: "kill" required_progress: 10 rewards: gold: 50 experience: 100 items: [] offering_triggers: location_types: ["town"] min_character_level: 1 max_character_level: 3 probability_weights: town: 0.30 wilderness: 0.0 narrative_hooks: - "The tavern keeper waves you over, mentioning strange noises from the basement." ``` ### QuestObjective Represents a single objective within a quest. | Field | Type | Description | |-------|------|-------------| | `objective_id` | str | Unique ID (e.g., "kill_rats") | | `description` | str | Player-facing description | | `objective_type` | str | "kill", "collect", "travel", "interact", "discover" | | `required_progress` | int | Target value (e.g., 10 rats) | | `current_progress` | int | Current value (e.g., 5 rats killed) | | `completed` | bool | Objective completion status | **Objective Types:** - **kill**: Defeat X enemies - **collect**: Gather X items - **travel**: Reach a specific location - **interact**: Talk to NPCs or interact with objects - **discover**: Find new locations or secrets ### QuestReward Rewards granted upon quest completion. | Field | Type | Description | |-------|------|-------------| | `gold` | int | Gold reward | | `experience` | int | XP reward (may trigger level up) | | `items` | List[str] | Item IDs to grant | | `reputation` | Optional[str] | Reputation faction (future feature) | ### QuestTriggers Defines when and where a quest can be offered. | Field | Type | Description | |-------|------|-------------| | `location_types` | List[str] | ["town", "wilderness", "dungeon"] or ["any"] | | `specific_locations` | List[str] | Specific location IDs or empty for any | | `min_character_level` | int | Minimum level required | | `max_character_level` | int | Maximum level (for scaling) | | `required_quests_completed` | List[str] | Quest prerequisites | | `probability_weights` | Dict[str, float] | Location-specific offering chances | **Methods:** - `get_offer_probability(location_type) -> float` - Get probability for location type - `can_offer(character_level, location, location_type, completed_quests) -> bool` - Check if quest can be offered **Quest Offering Logic:** 1. **Location-based roll**: Towns (30%), Taverns (35%), Wilderness (5%), Dungeons (10%) 2. **Filter eligible quests**: Level requirements, location match, prerequisites met 3. **Context-aware selection**: AI analyzes narrative context to select fitting quest 4. **Max 2 active quests**: Limit enforced to prevent player overwhelm **Quest Storage:** Quests are defined in YAML files in `/app/data/quests/` organized by difficulty: - `/app/data/quests/easy/` - Levels 1-3 - `/app/data/quests/medium/` - Levels 3-7 - `/app/data/quests/hard/` - Levels 10+ - `/app/data/quests/epic/` - End-game content --- ## Combat System ### Effect Effects are temporary status modifiers applied to combatants during combat. | Field | Type | Description | |-------|------|-------------| | `effect_id` | str | Unique identifier | | `name` | str | Effect name | | `effect_type` | EffectType | BUFF, DEBUFF, DOT, HOT, STUN, SHIELD | | `duration` | int | Turns remaining before expiration | | `power` | int | Damage/healing per turn or stat modifier | | `stat_affected` | StatType | Which stat is modified (for BUFF/DEBUFF only) | | `stacks` | int | Current number of stacks (default 1) | | `max_stacks` | int | Maximum stacks allowed (default 5) | | `source` | str | Who/what applied it (ability_id or character_id) | **Effect Types:** | Type | Description | Power Usage | |------|-------------|-------------| | **BUFF** | Increase stats temporarily | Stat modifier (×stacks) | | **DEBUFF** | Decrease stats temporarily | Stat modifier (×stacks) | | **DOT** | Damage over time (poison, bleed, burn) | Damage per turn (×stacks) | | **HOT** | Heal over time (regeneration) | Healing per turn (×stacks) | | **STUN** | Skip turn (cannot act) | Not used | | **SHIELD** | Absorb damage before HP loss | Shield strength (×stacks) | **Methods:** **`tick() -> Dict[str, Any]`** Process one turn of this effect. Called at the start of each combatant's turn. Returns dictionary with: - `effect_name`: Name of the effect - `effect_type`: Type of effect - `value`: Damage dealt (DOT) or healing done (HOT) = power × stacks - `shield_remaining`: Current shield strength (SHIELD only) - `stunned`: True if this is a stun effect - `stat_modifier`: Amount stats are modified (BUFF/DEBUFF) = power × stacks - `expired`: True if duration reached 0 - `message`: Human-readable description Duration is decremented by 1 each tick. Effect is marked expired when duration reaches 0. **`apply_stack(additional_duration) -> None`** Apply an additional stack of this effect (stacking mechanic). Behavior: - Increases `stacks` by 1 (up to `max_stacks`) - Refreshes `duration` to maximum - If already at max_stacks, only refreshes duration Example: Poison with 2 stacks gets re-applied → becomes 3 stacks, duration refreshes **`reduce_shield(damage) -> int`** Reduce shield strength by damage amount (SHIELD effects only). Returns remaining damage after shield absorption. Examples: - Shield power=50, damage=30 → power becomes 20, returns 0 (all absorbed) - Shield power=20, damage=30 → power becomes 0, duration=0, returns 10 (partial) **Effect Stacking Rules:** - Same effect applied multiple times increases stacks - Stacks are capped at `max_stacks` (default 5, configurable per effect) - Power scales linearly: 3 stacks of 5 power poison = 15 damage per turn - Duration refreshes on re-application (does not stack cumulatively) - Different effects (even same name) don't stack with each other ### Combatant Wrapper for a Character or Enemy in combat. Tracks combat-specific state. | Field | Type | Description | |-------|------|-------------| | `combatant_id` | str | Character or enemy ID | | `name` | str | Display name | | `is_player` | bool | True for player characters, False for NPCs | | `current_hp` | int | Current health points | | `max_hp` | int | Maximum health points | | `current_mp` | int | Current mana points | | `max_mp` | int | Maximum mana points | | `stats` | Stats | Combat stats (use Character.get_effective_stats()) | | `active_effects` | List[Effect] | Currently active effects on this combatant | | `abilities` | List[str] | Available ability IDs (not full Ability objects) | | `cooldowns` | Dict[str, int] | {ability_id: turns_remaining} for abilities on cooldown | | `initiative` | int | Turn order value (rolled at combat start) | **Methods:** - `is_alive() -> bool` - Check if combatant has HP > 0 - `is_dead() -> bool` - Check if combatant has HP <= 0 - `is_stunned() -> bool` - Check if any active STUN effect - `take_damage(damage) -> int` - Apply damage with shield absorption, returns actual HP damage dealt - `heal(amount) -> int` - Restore HP (capped at max_hp), returns actual amount healed - `restore_mana(amount) -> int` - Restore MP (capped at max_mp) - `can_use_ability(ability_id, ability) -> bool` - Check if ability can be used (mana, cooldown) - `use_ability_cost(ability, ability_id) -> None` - Consume mana and set cooldown - `tick_effects() -> List[Dict]` - Process all active effects for this turn, remove expired - `tick_cooldowns() -> None` - Reduce all cooldowns by 1 turn - `add_effect(effect) -> None` - Add effect, stacks if same effect exists **Important Notes:** - `abilities` stores ability IDs, not full Ability objects (for serialization) - `stats` should be set to Character.get_effective_stats() for players - Shield effects are processed automatically in `take_damage()` - Effects tick at start of turn via `tick_effects()` ### CombatEncounter | Field | Type | Description | |-------|------|-------------| | `encounter_id` | str | Unique identifier | | `combatants` | List[Combatant] | All fighters | | `turn_order` | List[str] | Combatant IDs in order | | `current_turn_index` | int | Index in turn_order | | `round_number` | int | Current round | | `combat_log` | List[Dict] | Action history | | `status` | str | active, victory, defeat | **Methods:** - `initialize_combat() -> None` - Roll initiative for all combatants, set turn order - `get_current_combatant() -> Combatant` - Get the combatant whose turn it is - `get_combatant(combatant_id) -> Combatant` - Get combatant by ID - `advance_turn() -> None` - Move to next combatant's turn, increment round if needed - `start_turn() -> List[Dict]` - Process effects and cooldowns at turn start - `check_end_condition() -> CombatStatus` - Check for victory/defeat, update status - `log_action(action_type, combatant_id, message, details) -> None` - Add entry to combat log **Combat Flow:** 1. `initialize_combat()` - Roll initiative, sort turn order 2. Loop while status == ACTIVE: - `start_turn()` - Tick effects, check for stun - Execute action (if not stunned) - `check_end_condition()` - Check if combat should end - `advance_turn()` - Move to next combatant 3. End when status becomes VICTORY, DEFEAT, or FLED --- ## Session System ### SessionConfig | Field | Type | Description | |-------|------|-------------| | `min_players` | int | Session ends if below this | | `timeout_minutes` | int | Inactivity timeout | | `auto_save_interval` | int | Turns between auto-saves | ### GameSession | Field | Type | Description | |-------|------|-------------| | `session_id` | str | Unique identifier | | `party_member_ids` | List[str] | Character IDs in party | | `config` | SessionConfig | Session settings | | `combat_encounter` | CombatEncounter | Current combat or null | | `conversation_history` | List[Dict] | Turn-by-turn log | | `game_state` | GameState | Current world state | | `turn_order` | List[str] | Character turn order | | `current_turn` | int | Index in turn_order | | `turn_number` | int | Global turn counter | | `created_at` | ISO Timestamp | Session start | | `last_activity` | ISO Timestamp | Last action time | | `status` | str | active, completed, timeout | ### GameState | Field | Type | Description | |-------|------|-------------| | `current_location` | str | Location name/ID | | `discovered_locations` | List[str] | Location IDs | | `active_quests` | List[str] | Quest IDs | | `world_events` | List[Dict] | Server-wide events | ### Conversation History Entry | Field | Type | Description | |-------|------|-------------| | `turn` | int | Turn number | | `character_id` | str | Acting character | | `character_name` | str | Character name | | `action` | str | Player action text | | `dm_response` | str | AI-generated response | | `combat_log` | List[Dict] | Combat actions (if any) | --- ## Marketplace System ### MarketplaceListing | Field | Type | Description | |-------|------|-------------| | `listing_id` | str | Unique identifier | | `seller_id` | str | User ID | | `character_id` | str | Character ID | | `item_data` | Item | Full item details | | `listing_type` | str | "auction" or "fixed_price" | | `price` | int | For fixed_price | | `starting_bid` | int | For auction | | `current_bid` | int | For auction | | `buyout_price` | int | Optional instant buy | | `bids` | List[Bid] | Bid history | | `auction_end` | ISO Timestamp | For auction | | `status` | str | active, sold, expired, removed | | `created_at` | ISO Timestamp | Listing creation | ### Bid | Field | Type | Description | |-------|------|-------------| | `bidder_id` | str | User ID | | `bidder_name` | str | Character name | | `amount` | int | Bid amount | | `timestamp` | ISO Timestamp | Bid time | ### Transaction | Field | Type | Description | |-------|------|-------------| | `transaction_id` | str | Unique identifier | | `buyer_id` | str | User ID | | `seller_id` | str | User ID | | `listing_id` | str | Listing ID | | `item_data` | Item | Item details | | `price` | int | Final price | | `timestamp` | ISO Timestamp | Transaction time | | `transaction_type` | str | marketplace_sale, shop_purchase, etc. | --- ## NPC Shop System ### ShopItem | Field | Type | Description | |-------|------|-------------| | `item_id` | str | Item identifier | | `item` | Item | Item details | | `stock` | int | Available quantity (-1 = unlimited) | | `price` | int | Fixed gold price | **Shop Categories:** - Consumables (health potions, mana potions) - Basic weapons (tier 1-2) - Basic armor (tier 1-2) - Crafting materials (future feature) **Purpose:** - Provides gold sink to prevent inflation - Always available (not affected by marketplace access) - Sells basic items at fixed prices --- ## Skill Tree Design Each skill tree has **5 tiers** with **3-5 nodes per tier**. ### Example: Vanguard - Shield Bearer Tree | Tier | Node | Type | Prerequisites | Effects | |------|------|------|---------------|---------| | 1 | Shield Bash | Active | None | Unlock shield_bash ability, 5 damage, 1 turn stun | | 1 | Fortify | Passive | None | +5 Defense | | 2 | Shield Wall | Active | Shield Bash | Unlock shield_wall ability, block all damage 1 turn, 3 turn cooldown | | 2 | Iron Skin | Passive | Fortify | +10 Defense, +5 HP | | 3 | Guardian's Resolve | Passive | Shield Wall | Immune to stun | | 3 | Riposte | Active | Shield Bash | Unlock riposte ability, counter attack on block | | 4 | Bulwark | Passive | Iron Skin | +15 Defense, +10 HP, damage reduction 10% | | 5 | Unbreakable | Ultimate | Bulwark | Unlock unbreakable ability, 5 turn buff: 50% damage reduction | --- ## Data Serialization ### JSON Storage in Appwrite All complex dataclasses are serialized to JSON strings for storage: **Storage:** ``` Character dataclass → JSON string → Appwrite document field ``` **Retrieval:** ``` Appwrite document field → JSON string → Character dataclass ``` ### Benefits - Schema flexibility (easy to add fields) - No database migrations needed - Type safety in application code - Easy to serialize/deserialize --- ## Future Data Models (Backlog) ### Planned Additions - **Guild:** Player organizations - **WorldEvent:** Server-wide quests - **Achievement:** Badge system - **CraftingRecipe:** Item creation - **PetCompanion:** Beast Master pets - **LeaderboardEntry:** Rankings ### Additional Player Classes (Backlog) - Monk (martial arts, chi energy) - Druid (shapeshifting, nature magic) - Warlock (pact magic, debuffs) - Artificer (gadgets, constructs)