Add YAML-driven quest system with context-aware offering:
Core Implementation:
- Quest data models (Quest, QuestObjective, QuestReward, QuestTriggers)
- QuestService for YAML loading and caching
- QuestEligibilityService with level, location, and probability filtering
- LoreService stub (MockLoreService) ready for Phase 6 Weaviate integration
Quest Content:
- 5 example quests across difficulty tiers (2 easy, 2 medium, 1 hard)
- Quest-centric design: quests define their NPC givers
- Location-based probability weights for natural quest offering
AI Integration:
- Quest offering section in npc_dialogue.j2 template
- Response parser extracts [QUEST_OFFER:quest_id] markers
- AI naturally weaves quest offers into NPC conversations
API Endpoints:
- POST /api/v1/quests/accept - Accept quest offer
- POST /api/v1/quests/decline - Decline quest offer
- POST /api/v1/quests/progress - Update objective progress
- POST /api/v1/quests/complete - Complete quest, claim rewards
- POST /api/v1/quests/abandon - Abandon active quest
- GET /api/v1/characters/{id}/quests - List character quests
- GET /api/v1/quests/{quest_id} - Get quest details
Frontend:
- Quest tracker sidebar with HTMX integration
- Quest offer modal for accept/decline flow
- Quest detail modal for viewing progress
- Combat service integration for kill objective tracking
Testing:
- Unit tests for Quest models and serialization
- Integration tests for full quest lifecycle
- Comprehensive test coverage for eligibility service
Documentation:
- Reorganized docs into /docs/phases/ structure
- Added Phase 5-12 planning documents
- Updated ROADMAP.md with new structure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
13 KiB
13 KiB
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)
# /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.
# /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:
- LoreService interface - Replace
MockLoreServicewithWeaviateLoreService - Quest.lore_context - Supplement embedded lore with Weaviate queries
- NPC dialogue template - Lore section already prepared
- 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- Requiredimage_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