Add new Luck stat to the character stats system with class-specific values: - Assassin: 12 (highest - critical specialists) - Luminary: 11 (divine favor) - Wildstrider/Lorekeeper: 10 (average) - Arcanist/Oathkeeper: 9 (modest) - Vanguard: 8 (default - relies on strength) - Necromancer: 7 (lowest - dark arts cost) Changes: - Add luck field to Stats dataclass with default of 8 - Add LUCK to StatType enum - Update all 8 class YAML files with luck values - Display LUK in character panel (play page) and detail page - Update DATA_MODELS.md documentation Backward compatible: existing characters without luck default to 8.
1377 lines
46 KiB
Markdown
1377 lines
46 KiB
Markdown
# 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 |
|
||
| `LUCK` | Fortune and fate (affects crits, loot, random outcomes) |
|
||
|
||
### 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 |
|
||
| `recent_messages` | List[Dict] | **Last 3 messages for quick AI context** |
|
||
| `total_messages` | int | **Total conversation message count** |
|
||
| `dialogue_history` | List[Dict] | **DEPRECATED** - Use ChatMessageService for full history |
|
||
|
||
**Recent Messages Entry Format:**
|
||
```json
|
||
{
|
||
"player_message": "What have you heard about the old mines?",
|
||
"npc_response": "Aye, strange noises coming from there lately...",
|
||
"timestamp": "2025-11-25T10:30:00Z"
|
||
}
|
||
```
|
||
|
||
**Conversation History Architecture:**
|
||
- **Recent Messages Cache**: Last 3 messages stored in `recent_messages` field for quick AI context (no database query)
|
||
- **Full History**: Complete unlimited conversation history stored in `chat_messages` collection
|
||
- **Deprecated Field**: `dialogue_history` maintained for backward compatibility, will be removed after full migration
|
||
|
||
The recent messages cache enables fast AI dialogue generation by providing immediate context without querying the chat_messages collection. For full conversation history, use the ChatMessageService (see Chat/Conversation History API endpoints).
|
||
|
||
**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
|
||
|
||
---
|
||
|
||
## Chat / Conversation History System
|
||
|
||
The chat system stores complete player-NPC conversation history in a dedicated `chat_messages` collection for unlimited history, with a performance-optimized cache in character documents.
|
||
|
||
### ChatMessage
|
||
|
||
Complete message exchange between player and NPC.
|
||
|
||
**Location:** `/app/models/chat_message.py`
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `message_id` | str | Unique identifier (UUID) |
|
||
| `character_id` | str | Player's character ID |
|
||
| `npc_id` | str | NPC identifier |
|
||
| `player_message` | str | What the player said (max 2000 chars) |
|
||
| `npc_response` | str | NPC's reply (max 5000 chars) |
|
||
| `timestamp` | str | ISO 8601 timestamp |
|
||
| `session_id` | Optional[str] | Game session reference |
|
||
| `location_id` | Optional[str] | Where conversation happened |
|
||
| `context` | MessageContext | Type of interaction (enum) |
|
||
| `metadata` | Dict[str, Any] | Extensible metadata (quest_id, item_id, etc.) |
|
||
| `is_deleted` | bool | Soft delete flag (default: False) |
|
||
|
||
**Storage:**
|
||
- Stored in Appwrite `chat_messages` collection
|
||
- Indexed by character_id, npc_id, timestamp for fast queries
|
||
- Unlimited history (no cap on message count)
|
||
|
||
**Example:**
|
||
```json
|
||
{
|
||
"message_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"character_id": "char_123",
|
||
"npc_id": "npc_grom_ironbeard",
|
||
"player_message": "What rumors have you heard?",
|
||
"npc_response": "*leans in* Strange folk been coming through lately...",
|
||
"timestamp": "2025-11-25T14:30:00Z",
|
||
"context": "dialogue",
|
||
"location_id": "crossville_tavern",
|
||
"session_id": "sess_789",
|
||
"metadata": {},
|
||
"is_deleted": false
|
||
}
|
||
```
|
||
|
||
### MessageContext (Enum)
|
||
|
||
Type of interaction that generated the message.
|
||
|
||
| Value | Description |
|
||
|-------|-------------|
|
||
| `dialogue` | General conversation |
|
||
| `quest_offered` | Quest offering dialogue |
|
||
| `quest_completed` | Quest completion dialogue |
|
||
| `shop` | Merchant transaction |
|
||
| `location_revealed` | New location discovered through chat |
|
||
| `lore` | Lore/backstory reveals |
|
||
|
||
**Usage:**
|
||
```python
|
||
from app.models.chat_message import MessageContext
|
||
|
||
context = MessageContext.QUEST_OFFERED
|
||
```
|
||
|
||
### ConversationSummary
|
||
|
||
Summary of all messages with a specific NPC for UI display.
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `npc_id` | str | NPC identifier |
|
||
| `npc_name` | str | NPC display name |
|
||
| `last_message_timestamp` | str | When the last message was sent |
|
||
| `message_count` | int | Total number of messages exchanged |
|
||
| `recent_preview` | str | Short preview of most recent NPC response |
|
||
|
||
**Example:**
|
||
```json
|
||
{
|
||
"npc_id": "npc_grom_ironbeard",
|
||
"npc_name": "Grom Ironbeard",
|
||
"last_message_timestamp": "2025-11-25T14:30:00Z",
|
||
"message_count": 15,
|
||
"recent_preview": "Aye, the rats in the cellar have been causing trouble..."
|
||
}
|
||
```
|
||
|
||
### ChatMessageService
|
||
|
||
Service for managing player-NPC conversation history.
|
||
|
||
**Location:** `/app/services/chat_message_service.py`
|
||
|
||
**Core Methods:**
|
||
|
||
```python
|
||
from app.services.chat_message_service import get_chat_message_service
|
||
from app.models.chat_message import MessageContext
|
||
|
||
service = get_chat_message_service()
|
||
|
||
# Save a dialogue exchange (also updates character's recent_messages cache)
|
||
message = service.save_dialogue_exchange(
|
||
character_id="char_123",
|
||
user_id="user_456",
|
||
npc_id="npc_grom_ironbeard",
|
||
player_message="What rumors have you heard?",
|
||
npc_response="*leans in* Strange folk...",
|
||
context=MessageContext.DIALOGUE,
|
||
metadata={},
|
||
session_id="sess_789",
|
||
location_id="crossville_tavern"
|
||
)
|
||
|
||
# Get conversation history with pagination
|
||
messages = service.get_conversation_history(
|
||
character_id="char_123",
|
||
user_id="user_456",
|
||
npc_id="npc_grom_ironbeard",
|
||
limit=50,
|
||
offset=0
|
||
)
|
||
|
||
# Search messages with filters
|
||
results = service.search_messages(
|
||
character_id="char_123",
|
||
user_id="user_456",
|
||
search_text="quest",
|
||
npc_id="npc_grom_ironbeard",
|
||
context=MessageContext.QUEST_OFFERED,
|
||
date_from="2025-11-01T00:00:00Z",
|
||
date_to="2025-11-30T23:59:59Z",
|
||
limit=50,
|
||
offset=0
|
||
)
|
||
|
||
# Get all conversations summary for UI
|
||
summaries = service.get_all_conversations_summary(
|
||
character_id="char_123",
|
||
user_id="user_456"
|
||
)
|
||
|
||
# Soft delete a message (privacy/moderation)
|
||
success = service.soft_delete_message(
|
||
message_id="msg_abc123",
|
||
character_id="char_123",
|
||
user_id="user_456"
|
||
)
|
||
```
|
||
|
||
**Performance Architecture:**
|
||
- **Recent Messages Cache**: Last 3 messages stored in `character.npc_interactions[npc_id].recent_messages`
|
||
- **Full History**: All messages in dedicated `chat_messages` collection
|
||
- **AI Context**: Reads from cache (no database query) for 90% of cases
|
||
- **User Queries**: Reads from collection with pagination and filters
|
||
|
||
**Database Indexes:**
|
||
1. `idx_character_npc_time` - character_id + npc_id + timestamp DESC
|
||
2. `idx_character_time` - character_id + timestamp DESC
|
||
3. `idx_session_time` - session_id + timestamp DESC
|
||
4. `idx_context` - context
|
||
5. `idx_timestamp` - timestamp DESC
|
||
|
||
**See Also:**
|
||
- Chat API endpoints in API_REFERENCE.md
|
||
- CHAT_SYSTEM.md for architecture details
|
||
|
||
---
|
||
|
||
## Character System
|
||
|
||
### Stats
|
||
|
||
| Field | Type | Default | Description |
|
||
|-------|------|---------|-------------|
|
||
| `strength` | int | 10 | Physical power |
|
||
| `dexterity` | int | 10 | Agility and precision |
|
||
| `constitution` | int | 10 | Endurance and health |
|
||
| `intelligence` | int | 10 | Magical power |
|
||
| `wisdom` | int | 10 | Perception and insight |
|
||
| `charisma` | int | 10 | Social influence |
|
||
| `luck` | int | 8 | Fortune and fate (affects crits, loot, random outcomes) |
|
||
|
||
**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.
|
||
|
||
**Luck Stat:** The luck stat has a lower default (8) compared to other stats (10). Each class has a specific luck value ranging from 7 (Necromancer) to 12 (Assassin). Luck will influence critical hit chance, hit/miss calculations, base damage variance, NPC interactions, loot generation, and spell power in future implementations.
|
||
|
||
### 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 | LUK | Skill Tree 1 | Skill Tree 2 |
|
||
|-------|-------|-----|--------------|--------------|
|
||
| **Vanguard** | Tank/melee | 8 | Defensive (shields, armor, taunts) | Offensive (weapon mastery, heavy strikes) |
|
||
| **Assassin** | Stealth/critical | 12 | Assassination (critical hits, poisons) | Shadow (stealth, evasion) |
|
||
| **Arcanist** | Elemental spells | 9 | Pyromancy (fire spells, DoT) | Cryomancy (ice spells, crowd control) |
|
||
| **Luminary** | Healing/support | 11 | Holy (healing, buffs) | Divine Wrath (smite, undead damage) |
|
||
| **Wildstrider** | Ranged/nature | 10 | Marksmanship (bow skills, critical shots) | Beast Master (pet companion, nature magic) |
|
||
| **Oathkeeper** | Hybrid tank/healer | 9 | Protection (defensive auras, healing) | Retribution (holy damage, smites) |
|
||
| **Necromancer** | Death magic/summon | 7 | Dark Arts (curses, life drain) | Summoning (undead minions) |
|
||
| **Lorekeeper** | Support/control | 10 | Performance (buffs, debuffs via music) | Trickery (illusions, charm) |
|
||
|
||
**Class Luck Values:**
|
||
- **Assassin (12):** Highest luck - critical strike specialists benefit most from fortune
|
||
- **Luminary (11):** Divine favor grants above-average luck
|
||
- **Wildstrider (10):** Average luck - self-reliant nature
|
||
- **Lorekeeper (10):** Average luck - knowledge is their advantage
|
||
- **Arcanist (9):** Slight chaos magic influence
|
||
- **Oathkeeper (9):** Honorable path grants modest fortune
|
||
- **Vanguard (8):** Relies on strength and skill, not luck
|
||
- **Necromancer (7):** Lowest luck - dark arts exact a toll
|
||
|
||
**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)
|