436 lines
9.6 KiB
Markdown
436 lines
9.6 KiB
Markdown
# Session Management
|
|
|
|
This document describes the game session system for Code of Conquest, covering both solo and multiplayer sessions.
|
|
|
|
**Last Updated:** November 22, 2025
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Game sessions track the state of gameplay including:
|
|
- Current location and discovered locations
|
|
- Conversation history between player and AI DM
|
|
- Active quests (max 2)
|
|
- World events
|
|
- Combat encounters
|
|
|
|
Sessions support both **solo play** (single character) and **multiplayer** (party-based).
|
|
|
|
---
|
|
|
|
## Data Models
|
|
|
|
### SessionType Enum
|
|
|
|
```python
|
|
class SessionType(Enum):
|
|
SOLO = "solo" # Single-player session
|
|
MULTIPLAYER = "multiplayer" # Multi-player party session
|
|
```
|
|
|
|
### LocationType Enum
|
|
|
|
```python
|
|
class LocationType(Enum):
|
|
TOWN = "town" # Town or city
|
|
TAVERN = "tavern" # Tavern or inn
|
|
WILDERNESS = "wilderness" # Outdoor wilderness areas
|
|
DUNGEON = "dungeon" # Underground dungeons/caves
|
|
RUINS = "ruins" # Ancient ruins
|
|
LIBRARY = "library" # Library or archive
|
|
SAFE_AREA = "safe_area" # Safe rest areas
|
|
```
|
|
|
|
### GameState
|
|
|
|
Tracks current world state for a session:
|
|
|
|
```python
|
|
@dataclass
|
|
class GameState:
|
|
current_location: str = "Crossroads Village"
|
|
location_type: LocationType = LocationType.TOWN
|
|
discovered_locations: List[str] = []
|
|
active_quests: List[str] = [] # Max 2
|
|
world_events: List[Dict] = []
|
|
```
|
|
|
|
### ConversationEntry
|
|
|
|
Single turn in the conversation history:
|
|
|
|
```python
|
|
@dataclass
|
|
class ConversationEntry:
|
|
turn: int # Turn number (1-indexed)
|
|
character_id: str # Acting character
|
|
character_name: str # Character display name
|
|
action: str # Player's action text
|
|
dm_response: str # AI DM's response
|
|
timestamp: str # ISO timestamp (auto-generated)
|
|
combat_log: List[Dict] = [] # Combat actions if any
|
|
quest_offered: Optional[Dict] = None # Quest offering info
|
|
```
|
|
|
|
### GameSession
|
|
|
|
Main session object:
|
|
|
|
```python
|
|
@dataclass
|
|
class GameSession:
|
|
session_id: str
|
|
session_type: SessionType = SessionType.SOLO
|
|
solo_character_id: Optional[str] = None # For solo sessions
|
|
user_id: str = ""
|
|
party_member_ids: List[str] = [] # For multiplayer
|
|
config: SessionConfig
|
|
combat_encounter: Optional[CombatEncounter] = None
|
|
conversation_history: List[ConversationEntry] = []
|
|
game_state: GameState
|
|
turn_order: List[str] = []
|
|
current_turn: int = 0
|
|
turn_number: int = 0
|
|
created_at: str # ISO timestamp
|
|
last_activity: str # ISO timestamp
|
|
status: SessionStatus = SessionStatus.ACTIVE
|
|
```
|
|
|
|
---
|
|
|
|
## SessionService
|
|
|
|
The `SessionService` class (`app/services/session_service.py`) provides all session operations.
|
|
|
|
### Initialization
|
|
|
|
```python
|
|
from app.services.session_service import get_session_service
|
|
|
|
service = get_session_service()
|
|
```
|
|
|
|
### Creating Sessions
|
|
|
|
#### Solo Session
|
|
|
|
```python
|
|
session = service.create_solo_session(
|
|
user_id="user_123",
|
|
character_id="char_456",
|
|
starting_location="Crossroads Village", # Optional
|
|
starting_location_type=LocationType.TOWN # Optional
|
|
)
|
|
```
|
|
|
|
**Validations:**
|
|
- User must own the character
|
|
- User cannot exceed 5 active sessions
|
|
|
|
**Returns:** `GameSession` instance
|
|
|
|
**Raises:**
|
|
- `CharacterNotFound` - Character doesn't exist or user doesn't own it
|
|
- `SessionLimitExceeded` - User has 5+ active sessions
|
|
|
|
### Retrieving Sessions
|
|
|
|
#### Get Single Session
|
|
|
|
```python
|
|
session = service.get_session(
|
|
session_id="sess_789",
|
|
user_id="user_123" # Optional, validates ownership
|
|
)
|
|
```
|
|
|
|
**Raises:** `SessionNotFound`
|
|
|
|
#### Get User's Sessions
|
|
|
|
```python
|
|
sessions = service.get_user_sessions(
|
|
user_id="user_123",
|
|
active_only=True, # Default: True
|
|
limit=25 # Default: 25
|
|
)
|
|
```
|
|
|
|
#### Count Sessions
|
|
|
|
```python
|
|
count = service.count_user_sessions(
|
|
user_id="user_123",
|
|
active_only=True
|
|
)
|
|
```
|
|
|
|
### Updating Sessions
|
|
|
|
#### Direct Update
|
|
|
|
```python
|
|
session.turn_number += 1
|
|
session = service.update_session(session)
|
|
```
|
|
|
|
#### End Session
|
|
|
|
```python
|
|
session = service.end_session(
|
|
session_id="sess_789",
|
|
user_id="user_123"
|
|
)
|
|
# Sets status to COMPLETED
|
|
```
|
|
|
|
---
|
|
|
|
## Conversation History
|
|
|
|
### Adding Entries
|
|
|
|
```python
|
|
session = service.add_conversation_entry(
|
|
session_id="sess_789",
|
|
character_id="char_456",
|
|
character_name="Brave Hero",
|
|
action="I explore the tavern",
|
|
dm_response="You enter a smoky tavern filled with patrons...",
|
|
combat_log=[], # Optional
|
|
quest_offered={"quest_id": "q1", "name": "..."} # Optional
|
|
)
|
|
```
|
|
|
|
**Automatic behaviors:**
|
|
- Increments `turn_number`
|
|
- Adds timestamp
|
|
- Updates `last_activity`
|
|
|
|
### Retrieving History
|
|
|
|
```python
|
|
# Get all history (with pagination)
|
|
history = service.get_conversation_history(
|
|
session_id="sess_789",
|
|
limit=20, # Optional
|
|
offset=0 # Optional, from end
|
|
)
|
|
|
|
# Get recent entries for AI context
|
|
recent = service.get_recent_history(
|
|
session_id="sess_789",
|
|
num_turns=3 # Default: 3
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## Game State Tracking
|
|
|
|
### Location Management
|
|
|
|
```python
|
|
# Update current location
|
|
session = service.update_location(
|
|
session_id="sess_789",
|
|
new_location="Dark Forest",
|
|
location_type=LocationType.WILDERNESS
|
|
)
|
|
# Also adds to discovered_locations if new
|
|
|
|
# Add discovered location without traveling
|
|
session = service.add_discovered_location(
|
|
session_id="sess_789",
|
|
location="Ancient Ruins"
|
|
)
|
|
```
|
|
|
|
### Quest Management
|
|
|
|
```python
|
|
# Add active quest (max 2)
|
|
session = service.add_active_quest(
|
|
session_id="sess_789",
|
|
quest_id="quest_goblin_cave"
|
|
)
|
|
|
|
# Remove quest (on completion or abandonment)
|
|
session = service.remove_active_quest(
|
|
session_id="sess_789",
|
|
quest_id="quest_goblin_cave"
|
|
)
|
|
```
|
|
|
|
**Raises:** `SessionValidationError` if adding 3rd quest
|
|
|
|
### World Events
|
|
|
|
```python
|
|
session = service.add_world_event(
|
|
session_id="sess_789",
|
|
event={
|
|
"type": "festival",
|
|
"description": "A harvest festival begins in town"
|
|
}
|
|
)
|
|
# Timestamp auto-added
|
|
```
|
|
|
|
---
|
|
|
|
## Session Limits
|
|
|
|
| Limit | Value | Notes |
|
|
|-------|-------|-------|
|
|
| Active sessions per user | 5 | End existing sessions to create new |
|
|
| Active quests per session | 2 | Complete or abandon to accept new |
|
|
| Conversation history | Unlimited | Consider archiving for very long sessions |
|
|
|
|
---
|
|
|
|
## Database Schema
|
|
|
|
Sessions are stored in Appwrite `game_sessions` collection:
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `$id` | string | Session ID (document ID) |
|
|
| `userId` | string | Owner's user ID |
|
|
| `sessionData` | string | JSON serialized GameSession |
|
|
| `status` | string | "active", "completed", "timeout" |
|
|
| `sessionType` | string | "solo" or "multiplayer" |
|
|
|
|
### Indexes
|
|
|
|
- `userId` - For user session queries
|
|
- `status` - For active session filtering
|
|
- `userId + status` - Composite for active user sessions
|
|
|
|
---
|
|
|
|
## Usage Examples
|
|
|
|
### Complete Solo Gameplay Flow
|
|
|
|
```python
|
|
from app.services.session_service import get_session_service
|
|
from app.models.enums import LocationType
|
|
|
|
service = get_session_service()
|
|
|
|
# 1. Create session
|
|
session = service.create_solo_session(
|
|
user_id="user_123",
|
|
character_id="char_456"
|
|
)
|
|
|
|
# 2. Player takes action, AI responds
|
|
session = service.add_conversation_entry(
|
|
session_id=session.session_id,
|
|
character_id="char_456",
|
|
character_name="Hero",
|
|
action="I look around the village square",
|
|
dm_response="The village square bustles with activity..."
|
|
)
|
|
|
|
# 3. Player travels
|
|
session = service.update_location(
|
|
session_id=session.session_id,
|
|
new_location="The Rusty Anchor Tavern",
|
|
location_type=LocationType.TAVERN
|
|
)
|
|
|
|
# 4. Quest offered and accepted
|
|
session = service.add_active_quest(
|
|
session_id=session.session_id,
|
|
quest_id="quest_goblin_cave"
|
|
)
|
|
|
|
# 5. End session
|
|
session = service.end_session(
|
|
session_id=session.session_id,
|
|
user_id="user_123"
|
|
)
|
|
```
|
|
|
|
### Checking Session State
|
|
|
|
```python
|
|
session = service.get_session("sess_789")
|
|
|
|
# Check session type
|
|
if session.is_solo():
|
|
char_id = session.solo_character_id
|
|
else:
|
|
char_id = session.get_current_character_id()
|
|
|
|
# Check current location
|
|
location = session.game_state.current_location
|
|
location_type = session.game_state.location_type
|
|
|
|
# Check active quests
|
|
quests = session.game_state.active_quests
|
|
can_accept_quest = len(quests) < 2
|
|
|
|
# Get recent context for AI
|
|
recent = service.get_recent_history(session.session_id, num_turns=3)
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Exception Classes
|
|
|
|
```python
|
|
from app.services.session_service import (
|
|
SessionNotFound,
|
|
SessionLimitExceeded,
|
|
SessionValidationError,
|
|
)
|
|
|
|
try:
|
|
session = service.get_session("invalid_id", "user_123")
|
|
except SessionNotFound:
|
|
# Session doesn't exist or user doesn't own it
|
|
pass
|
|
|
|
try:
|
|
service.create_solo_session(user_id, char_id)
|
|
except SessionLimitExceeded:
|
|
# User has 5+ active sessions
|
|
pass
|
|
|
|
try:
|
|
service.add_active_quest(session_id, "quest_3")
|
|
except SessionValidationError:
|
|
# Already have 2 active quests
|
|
pass
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
Run session tests:
|
|
|
|
```bash
|
|
# Unit tests
|
|
pytest tests/test_session_model.py -v
|
|
pytest tests/test_session_service.py -v
|
|
|
|
# Verification script (requires TEST_USER_ID and TEST_CHARACTER_ID in .env)
|
|
python scripts/verify_session_persistence.py
|
|
```
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [DATA_MODELS.md](DATA_MODELS.md) - Character and item models
|
|
- [STORY_PROGRESSION.md](STORY_PROGRESSION.md) - Story turn system
|
|
- [QUEST_SYSTEM.md](QUEST_SYSTEM.md) - Quest mechanics
|
|
- [API_REFERENCE.md](API_REFERENCE.md) - Session API endpoints
|