# Phase 5 Quest System - NPC Integration Plan ## Summary Implement a quest-centric system where quests define their NPC givers, and NPCs automatically offer eligible quests during conversations using a probability roll + AI selection approach. ## Key Design Decisions | Decision | Choice | Rationale | |----------|--------|-----------| | Data ownership | Quest-centric | Quests define `quest_giver_npc_ids`. Adding new quests doesn't touch NPC files. | | Offer trigger | Probability + AI select | Location-based roll first, then AI naturally weaves selected quest into dialogue. | | Lore integration | Stub service | Create `LoreService` interface with mock data now; swap to Weaviate in Phase 6. | ## Architecture Overview ``` NPC Conversation Flow with Quest Integration: POST /api/v1/npcs/{npc_id}/talk │ ▼ ┌─────────────────────────────┐ │ 1. Load Context │ │ - NPC, Character, Location│ └────────────┬────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 2. Quest Eligibility Check │ │ QuestEligibilityService │ │ - Find quests where NPC │ │ is quest_giver │ │ - Filter by char level, │ │ completed, active, etc │ └────────────┬────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 3. Probability Roll │ │ - Town: 30%, Tavern: 35% │ │ - Wilderness: 5% │ │ - If fail → no offer │ └────────────┬────────────────┘ │ (if success) ▼ ┌─────────────────────────────┐ │ 4. Get Lore Context (stub) │ │ LoreService.get_context()│ │ - Quest embedded lore │ │ - Mock regional lore │ └────────────┬────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 5. Build AI Prompt │ │ - NPC persona + knowledge│ │ - Quest offering context │ │ - Lore context │ └────────────┬────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 6. AI Generates Dialogue │ │ Naturally weaves quest │ │ offer into conversation │ │ Includes [QUEST_OFFER:id]│ └────────────┬────────────────┘ │ ▼ ┌─────────────────────────────┐ │ 7. Parse Response │ │ Extract quest_offered │ │ Return to frontend │ └─────────────────────────────┘ ``` --- ## YAML Schema Designs ### Quest YAML (Quest-Centric Approach) ```yaml # /api/app/data/quests/easy/cellar_rats.yaml quest_id: quest_cellar_rats name: "Rat Problem in the Cellar" description: | Giant rats have infested the Rusty Anchor's cellar. difficulty: easy # NPC Quest Givers (quest defines this, not NPC) quest_giver_npc_ids: - npc_grom_ironbeard quest_giver_name: "Grom Ironbeard" # Display fallback # Location context location_id: crossville_tavern region_id: crossville # Offering conditions offering_triggers: location_types: ["tavern"] min_character_level: 1 max_character_level: 5 required_quests_completed: [] probability_weights: tavern: 0.35 town: 0.20 # NPC-specific offer dialogue (keyed by NPC ID) npc_offer_dialogues: npc_grom_ironbeard: dialogue: | *leans in conspiratorially* Got a problem, friend. Giant rats in me cellar. Been scaring off customers. 50 gold for whoever clears 'em out. conditions: min_relationship: 30 required_flags: [] forbidden_flags: ["refused_rat_quest"] # What NPCs know about this quest (for AI context) npc_quest_knowledge: npc_grom_ironbeard: - "The rats started appearing about a week ago" - "They seem bigger than normal rats" - "Old smuggling tunnels under the cellar" # Embedded lore (used before Weaviate exists) lore_context: backstory: | The cellar connects to old smuggling tunnels from Captain Morgath's days. Recent earthquakes may have reopened them. world_connections: - "The earthquakes also disturbed the Old Mines" regional_hints: - "Smuggling was common 50 years ago in Crossville" # Narrative hooks for natural dialogue dialogue_templates: narrative_hooks: - "mentions unusual scratching sounds from below" - "complains about spoiled food supplies" - "nervously glances toward the cellar door" # Objectives objectives: - objective_id: kill_rats description: "Clear out the giant rats (0/10)" objective_type: kill required_progress: 10 target_enemy_type: giant_rat # Rewards rewards: gold: 50 experience: 100 items: [] relationship_bonuses: npc_grom_ironbeard: 10 unlocks_quests: ["quest_tunnel_mystery"] # Completion completion_dialogue: npc_grom_ironbeard: | *actually smiles* Well done! Rats are gone, cellar's safe. Here's your coin. Drink's on the house tonight. ``` ### NPC YAML (Simplified - No Quest Lists) NPCs no longer need `quest_giver_for` since quests define their givers. ```yaml # /api/app/data/npcs/crossville/npc_grom_ironbeard.yaml npc_id: npc_grom_ironbeard name: Grom Ironbeard role: bartender location_id: crossville_tavern image_url: /static/images/npcs/crossville/grom_ironbeard.png # All other existing fields remain unchanged personality: { ... } appearance: { ... } knowledge: { ... } dialogue_hooks: { ... } # NO quest_giver_for field needed ``` --- ## Implementation Tasks ### Group 1: Quest Data Models (3 tasks) | ID | Task | File | Notes | |----|------|------|-------| | 5.1 | Create Quest dataclass | `/api/app/models/quest.py` | Include all fields from YAML schema | | 5.2 | Create QuestObjective, QuestReward, QuestTriggers | `/api/app/models/quest.py` | Objective types: kill, collect, travel, interact, discover | | 5.3 | Add `to_dict()`/`from_dict()` serialization | `/api/app/models/quest.py` | Test round-trip JSON serialization | ### Group 2: Quest Loading Service (3 tasks) | ID | Task | File | Notes | |----|------|------|-------| | 5.4 | Create `/api/app/data/quests/` directory structure | directories | `easy/`, `medium/`, `hard/`, `epic/` | | 5.5 | Implement QuestService (loader) | `/api/app/services/quest_service.py` | Follow `npc_loader.py` pattern with caching | | 5.6 | Write 5 example quests | `/api/app/data/quests/*.yaml` | 2 easy, 2 medium, 1 hard | ### Group 3: Quest Eligibility Service (3 tasks) | ID | Task | File | Notes | |----|------|------|-------| | 5.7 | Create QuestEligibilityService | `/api/app/services/quest_eligibility_service.py` | Core eligibility logic | | 5.8 | Implement `get_quests_for_npc()` | same file | Find quests where NPC is quest_giver | | 5.9 | Implement probability roll + filtering | same file | Location-based weights, level check, status check | ### Group 4: Lore Service Stub (2 tasks) | ID | Task | File | Notes | |----|------|------|-------| | 5.10 | Create LoreService interface | `/api/app/services/lore_service.py` | Abstract interface for Phase 6 | | 5.11 | Implement MockLoreService | same file | Returns quest's embedded lore_context | ### Group 5: AI Prompt Integration (3 tasks) | ID | Task | File | Notes | |----|------|------|-------| | 5.12 | Add quest offering section to template | `/api/app/ai/templates/npc_dialogue.j2` | Quest context + natural weaving instructions | | 5.13 | Add lore context section to template | same file | Filtered lore for NPC knowledge | | 5.14 | Implement quest offer parsing | `/api/app/ai/response_parser.py` | Extract `[QUEST_OFFER:quest_id]` markers | ### Group 6: NPC API Integration (4 tasks) | ID | Task | File | Notes | |----|------|------|-------| | 5.15 | Integrate eligibility check into `talk_to_npc` | `/api/app/api/npcs.py` | Check before building AI context | | 5.16 | Add quest context to AI task | `/api/app/tasks/ai_tasks.py` | Modify `_process_npc_dialogue_task` | | 5.17 | Handle quest_offered in response | `/api/app/api/npcs.py` | Parse and include in API response | | 5.18 | Remove `quest_giver_for` from NPC model | `/api/app/models/npc.py` | Clean up old field if exists | ### Group 7: Quest Accept/Manage Endpoints (4 tasks) | ID | Task | File | Notes | |----|------|------|-------| | 5.19 | Create quests blueprint | `/api/app/api/quests.py` | Register in `__init__.py` | | 5.20 | Implement `POST /api/v1/quests/accept` | same file | Add to active_quests, update relationship | | 5.21 | Implement `POST /api/v1/quests/decline` | same file | Set `refused_{quest_id}` flag | | 5.22 | Implement `GET /api/v1/characters/{id}/quests` | same file | List active and completed quests | ### Group 8: Testing & Validation (3 tasks) | ID | Task | File | Notes | |----|------|------|-------| | 5.23 | Unit tests for Quest models | `/api/tests/test_quest_models.py` | Serialization, validation | | 5.24 | Unit tests for QuestEligibilityService | `/api/tests/test_quest_eligibility.py` | Filtering logic | | 5.25 | Integration test: full quest offer flow | `/api/tests/test_quest_integration.py` | NPC talk → offer → accept | --- ## Critical Files to Read Before Implementation | File | Reason | |------|--------| | `/api/app/services/npc_loader.py` | Pattern for YAML loading with caching | | `/api/app/models/npc.py` | Current NPC dataclass structure | | `/api/app/api/npcs.py` | Current `talk_to_npc` endpoint implementation | | `/api/app/tasks/ai_tasks.py` | `_process_npc_dialogue_task` function (lines 667-795) | | `/api/app/ai/templates/npc_dialogue.j2` | Current prompt template structure | | `/api/app/models/character.py` | `active_quests`, `completed_quests` fields | --- ## Data Flow Summary ``` 1. Player talks to NPC → POST /api/v1/npcs/{npc_id}/talk 2. Backend: a. QuestService.get_quests_for_npc(npc_id) → Find quests where npc_id in quest_giver_npc_ids b. QuestEligibilityService.filter_eligible(quests, character) → Remove: already active, completed, wrong level, flags block c. Probability roll based on location → 35% chance in tavern, 5% in wilderness, etc. d. If roll succeeds + eligible quests exist: → Pick quest (first eligible or AI-selected if multiple) → Build QuestOfferContext with dialogue + lore e. Add quest context to AI prompt f. AI generates dialogue, naturally mentions quest → Includes [QUEST_OFFER:quest_cellar_rats] if offering 3. Parse response, return to frontend: { "dialogue": "NPC's natural dialogue...", "quest_offered": { "quest_id": "quest_cellar_rats", "name": "Rat Problem", "description": "...", "rewards": {...} } } 4. Frontend shows quest offer UI → Player clicks Accept 5. POST /api/v1/quests/accept → Add to character.active_quests → Update NPC relationship (+5) → Return acceptance dialogue ``` --- ## Phase 6 Integration Points When implementing Phase 6 (Weaviate lore), these touchpoints enable integration: 1. **LoreService interface** - Replace `MockLoreService` with `WeaviateLoreService` 2. **Quest.lore_context** - Supplement embedded lore with Weaviate queries 3. **NPC dialogue template** - Lore section already prepared 4. **Knowledge filtering** - `LoreService.filter_for_npc()` method exists --- ## NPC YAML Migration Existing NPC files need these changes: **Remove (if exists):** - `quest_giver_for: [...]` - No longer needed **Keep unchanged:** - `location_id` - Required - `image_url` - Required - All other fields - Unchanged The `quest_giver_for` field in `npc_mayor_aldric.yaml` will be removed since quests now define their givers. --- ## Success Criteria - [ ] Quest YAML schema implemented and validated - [ ] QuestService loads quests from YAML with caching - [ ] QuestEligibilityService filters correctly by all conditions - [ ] Probability roll works per location type - [ ] AI prompt includes quest context when offering - [ ] AI naturally weaves quest offers into dialogue - [ ] Quest offer parsing extracts `[QUEST_OFFER:id]` correctly - [ ] Accept endpoint adds quest to active_quests - [ ] Max 2 active quests enforced - [ ] Relationship bonus applied on quest completion - [ ] LoreService stub returns embedded lore_context