Files
Code_of_Conquest/api/docs/DATA_MODELS.md
Phillip Tarrant 76f67c4a22 feat(api): implement inventory service with equipment system
Add InventoryService for managing character inventory, equipment, and
consumable usage. Key features:

- Add/remove items with inventory capacity checks
- Equipment slot validation (weapon, off_hand, helmet, chest, gloves,
  boots, accessory_1, accessory_2)
- Level and class requirement validation for equipment
- Consumable usage with instant and duration-based effects
- Combat-specific consumable method returning effects for combat system
- Bulk operations (add_items, get_items_by_type, get_equippable_items)

Design decision: Uses full Item object storage (not IDs) to support
procedurally generated items with unique identifiers.

Files added:
- /api/app/services/inventory_service.py (560 lines)
- /api/tests/test_inventory_service.py (51 tests passing)

Task 2.3 of Phase 4 Combat Implementation complete.
2025-11-26 18:38:39 -06:00

51 KiB
Raw Blame History

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:

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:

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:

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:

{
  "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:

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:

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:

{
  "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:

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:

{
  "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:

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

Procedural Item Generation (Affix System)

The game uses a Diablo-style procedural item generation system where weapons and armor are created by combining base templates with random affixes.

Core Models

Affix

Represents a prefix or suffix that modifies an item's stats and name.

Field Type Description
affix_id str Unique identifier
name str Display name ("Flaming", "of Strength")
affix_type AffixType PREFIX or SUFFIX
tier AffixTier MINOR, MAJOR, or LEGENDARY
description str Affix description
stat_bonuses Dict[str, int] Stat modifications
damage_bonus int Flat damage increase
defense_bonus int Flat defense increase
resistance_bonus int Flat resistance increase
damage_type DamageType For elemental affixes
elemental_ratio float Portion of damage converted to element
crit_chance_bonus float Critical hit chance modifier
crit_multiplier_bonus float Critical damage modifier
allowed_item_types List[str] Item types this affix can apply to
required_rarity str Minimum rarity required (for legendary affixes)

Methods:

  • applies_elemental_damage() -> bool - Check if affix adds elemental damage
  • is_legendary_only() -> bool - Check if requires legendary rarity
  • can_apply_to(item_type, rarity) -> bool - Check if affix can be applied

BaseItemTemplate

Foundation template for procedural item generation.

Field Type Description
template_id str Unique identifier
name str Base item name ("Dagger")
item_type str "weapon" or "armor"
description str Template description
base_damage int Starting damage value
base_defense int Starting defense value
base_resistance int Starting resistance value
base_value int Base gold value
damage_type str Physical, fire, etc.
crit_chance float Base critical chance
crit_multiplier float Base critical multiplier
required_level int Minimum level to use
min_rarity str Minimum rarity this generates as
drop_weight int Relative drop probability

Methods:

  • can_generate_at_rarity(rarity) -> bool - Check if template supports rarity
  • can_drop_for_level(level) -> bool - Check level requirement

Item Model Updates for Generated Items

The Item dataclass includes fields for tracking generated items:

Field Type Description
applied_affixes List[str] IDs of affixes on this item
base_template_id str ID of base template used
generated_name str Full name with affixes (e.g., "Flaming Dagger of Strength")
is_generated bool True if procedurally generated

Methods:

  • get_display_name() -> str - Returns generated_name if available, otherwise base name

Generation Enumerations

ItemRarity

Item quality tiers affecting affix count and value:

Value Affix Count Value Multiplier
COMMON 0 1.0×
UNCOMMON 0 1.5×
RARE 1 2.5×
EPIC 2 5.0×
LEGENDARY 3 10.0×

AffixType

Value Description
PREFIX Appears before item name ("Flaming Dagger")
SUFFIX Appears after item name ("Dagger of Strength")

AffixTier

Affix power level, determines eligibility by item rarity:

Value Description Available For
MINOR Basic affixes RARE+
MAJOR Stronger affixes RARE+ (higher weight at EPIC+)
LEGENDARY Most powerful affixes LEGENDARY only

Item Generation Service

Location: /app/services/item_generator.py

Usage:

from app.services.item_generator import get_item_generator
from app.models.enums import ItemRarity

generator = get_item_generator()

# Generate specific item
item = generator.generate_item(
    item_type="weapon",
    rarity=ItemRarity.EPIC,
    character_level=5
)

# Generate random loot drop with luck influence
item = generator.generate_loot_drop(
    character_level=10,
    luck_stat=12
)

Related Loaders:

  • AffixLoader (/app/services/affix_loader.py) - Loads affix definitions from YAML
  • BaseItemLoader (/app/services/base_item_loader.py) - Loads base templates from YAML

Data Files:

  • /app/data/affixes/prefixes.yaml - Prefix definitions
  • /app/data/affixes/suffixes.yaml - Suffix definitions
  • /app/data/base_items/weapons.yaml - Weapon templates
  • /app/data/base_items/armor.yaml - Armor templates

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:

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:

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:

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:

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:

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)