# Phase 4: Combat & Progression Systems - Implementation Plan **Status:** In Progress - Week 2 Complete, Week 3 Next **Timeline:** 4-5 weeks **Last Updated:** November 27, 2025 **Document Version:** 1.3 --- ## Completion Summary ### Week 1: Combat Backend - COMPLETE | Task | Description | Status | Tests | |------|-------------|--------|-------| | 1.1 | Verify Combat Data Models | ✅ Complete | - | | 1.2 | Implement Combat Service | ✅ Complete | 25 tests | | 1.3 | Implement Damage Calculator | ✅ Complete | 39 tests | | 1.4 | Implement Effect Processor | ✅ Complete | - | | 1.5 | Implement Combat Actions | ✅ Complete | - | | 1.6 | Combat API Endpoints | ✅ Complete | 19 tests | | 1.7 | Manual API Testing | ⏭️ Skipped | - | **Files Created:** - `/api/app/models/enemy.py` - EnemyTemplate, LootEntry dataclasses - `/api/app/services/enemy_loader.py` - YAML-based enemy loading - `/api/app/services/combat_service.py` - Combat orchestration service - `/api/app/services/damage_calculator.py` - Damage formula calculations - `/api/app/api/combat.py` - REST API endpoints - `/api/app/data/enemies/*.yaml` - 6 sample enemy definitions - `/api/tests/test_damage_calculator.py` - 39 tests - `/api/tests/test_enemy_loader.py` - 25 tests - `/api/tests/test_combat_service.py` - 25 tests - `/api/tests/test_combat_api.py` - 19 tests **Total Tests:** 108 passing ### Week 2: Inventory & Equipment - COMPLETE | Task | Description | Status | Tests | |------|-------------|--------|-------| | 2.1 | Item Data Models (Affixes) | ✅ Complete | 24 tests | | 2.2 | Item Data Files (YAML) | ✅ Complete | - | | 2.2.1 | Item Generator Service | ✅ Complete | 35 tests | | 2.3 | Inventory Service | ✅ Complete | 24 tests | | 2.4 | Inventory API Endpoints | ✅ Complete | 25 tests | | 2.5 | Character Stats Calculation | ✅ Complete | 17 tests | | 2.6 | Equipment-Combat Integration | ✅ Complete | 140 tests | | 2.7 | Combat Loot Integration | ✅ Complete | 59 tests | **Files Created/Modified:** - `/api/app/models/items.py` - Item with affix support, spell_power field - `/api/app/models/affixes.py` - Affix, BaseItemTemplate dataclasses - `/api/app/models/stats.py` - spell_power_bonus, updated damage formula - `/api/app/models/combat.py` - Combatant weapon properties - `/api/app/services/item_generator.py` - Procedural item generation - `/api/app/services/inventory_service.py` - Equipment management - `/api/app/services/damage_calculator.py` - Refactored to use stats properties - `/api/app/services/combat_service.py` - Equipment integration - `/api/app/api/inventory.py` - REST API endpoints **Total Tests (Week 2):** 324+ passing --- ## Overview This phase implements the core combat and progression systems for Code of Conquest, enabling turn-based tactical combat, inventory management, equipment, skill trees, and the NPC shop. This is a prerequisite for the story progression and quest systems. **Key Deliverables:** - Turn-based combat system (API + UI) - Inventory & equipment management - Skill tree visualization and unlocking - XP and leveling system - NPC shop --- ## Phase Structure | Sub-Phase | Duration | Focus | |-----------|----------|-------| | **Phase 4A** | 2-3 weeks | Combat Foundation | | **Phase 4B** | 1-2 weeks | Skill Trees & Leveling | | **Phase 4C** | 3-4 days | NPC Shop | **Total Estimated Time:** 4-5 weeks (~140-175 hours) --- ## Phase 4A: Combat Foundation (Weeks 1-3) ### Week 1: Combat Backend & Data Models ✅ COMPLETE #### Task 1.1: Verify Combat Data Models ✅ COMPLETE **Files:** `/api/app/models/combat.py`, `effects.py`, `abilities.py`, `stats.py` Verified: Combatant, CombatEncounter dataclasses, effect types (BUFF, DEBUFF, DOT, HOT, STUN, SHIELD), stacking logic, YAML ability loading, serialization methods. --- #### Task 1.2: Implement Combat Service ✅ COMPLETE **File:** `/api/app/services/combat_service.py` Implemented: `CombatService` class with `initiate_combat()`, `process_action()`, initiative rolling, turn management, death checking, combat end detection. --- #### Task 1.3: Implement Damage Calculator ✅ COMPLETE **File:** `/api/app/services/damage_calculator.py` Implemented: `calculate_physical_damage()`, `calculate_magical_damage()`, `apply_damage()` with shield absorption. Physical formula: `weapon.damage + (STR/2) - defense`. 39 unit tests. --- #### Task 1.4: Implement Effect Processor ✅ COMPLETE **File:** `/api/app/models/effects.py` Implemented: `tick()` method for DOT/HOT damage/healing, duration tracking, stat modifiers via `get_effective_stats()`. --- #### Task 1.5: Implement Combat Actions ✅ COMPLETE **File:** `/api/app/services/combat_service.py` Implemented: `_execute_attack()`, `_execute_spell()`, `_execute_item()`, `_execute_defend()` with mana costs, cooldowns, effect application. --- #### Task 1.6: Combat API Endpoints ✅ COMPLETE **File:** `/api/app/api/combat.py` **Endpoints:** - `POST /api/v1/combat/start` - Initiate combat - `POST /api/v1/combat//action` - Take action - `GET /api/v1/combat//state` - Get state - `POST /api/v1/combat//flee` - Attempt flee - `POST /api/v1/combat//enemy-turn` - Enemy AI - `GET /api/v1/combat/enemies` - List templates (public) - `GET /api/v1/combat/enemies/` - Enemy details (public) 19 integration tests passing. --- #### Task 1.7: Manual API Testing ⏭️ SKIPPED Covered by 108 comprehensive automated tests. --- ### Week 2: Inventory & Equipment System ✅ COMPLETE #### Task 2.1: Item Data Models ✅ COMPLETE **Files:** `/api/app/models/items.py`, `affixes.py`, `enums.py` Implemented: `Item` dataclass with affix support (`applied_affixes`, `base_template_id`, `generated_name`, `is_generated`), `Affix` model (PREFIX/SUFFIX types, MINOR/MAJOR/LEGENDARY tiers), `BaseItemTemplate` for procedural generation. 24 tests. --- #### Task 2.2: Item Data Files ✅ COMPLETE **Directory:** `/api/app/data/` Created: - `base_items/weapons.yaml` - 13 weapon templates - `base_items/armor.yaml` - 12 armor templates (cloth/leather/chain/plate) - `affixes/prefixes.yaml` - 18 prefixes (elemental, material, quality, legendary) - `affixes/suffixes.yaml` - 11 suffixes (stat bonuses, animal totems, legendary) - `items/consumables/potions.yaml` - Health/mana potions (small/medium/large) --- #### Task 2.2.1: Item Generator Service ✅ COMPLETE **Files:** `/api/app/services/item_generator.py`, `affix_loader.py`, `base_item_loader.py` Implemented Diablo-style procedural generation: - Affix distribution: COMMON/UNCOMMON (0), RARE (1), EPIC (2), LEGENDARY (3) - Name generation: "Flaming Dagger of Strength" - Tier weights by rarity (RARE: 80% MINOR, EPIC: 70% MAJOR, LEGENDARY: 50% LEGENDARY) - Luck-influenced rarity rolling 35 tests. --- #### Task 2.3: Implement Inventory Service ✅ COMPLETE **File:** `/api/app/services/inventory_service.py` Implemented: `add_item()`, `remove_item()`, `equip_item()`, `unequip_item()`, `use_consumable()`, `use_consumable_in_combat()`. Full object storage for generated items. Validation for slots, levels, item types. 24 tests. --- #### Task 2.4: Inventory API Endpoints ✅ COMPLETE **File:** `/api/app/api/inventory.py` **Endpoints:** - `GET /api/v1/characters//inventory` - Get inventory + equipped - `POST /api/v1/characters//inventory/equip` - Equip item - `POST /api/v1/characters//inventory/unequip` - Unequip item - `POST /api/v1/characters//inventory/use` - Use consumable - `DELETE /api/v1/characters//inventory/` - Drop item 25 tests. --- #### Task 2.5: Update Character Stats Calculation ✅ COMPLETE **Files:** `/api/app/models/stats.py`, `character.py` Added `damage_bonus`, `defense_bonus`, `resistance_bonus` fields to Stats. Updated `get_effective_stats()` to populate from equipped weapon/armor. 17 tests. --- #### Task 2.6: Equipment-Combat Integration ✅ COMPLETE **Files:** `stats.py`, `items.py`, `character.py`, `combat.py`, `combat_service.py`, `damage_calculator.py` Key changes: - Damage scaling: `int(STR * 0.75) + damage_bonus` (was `STR // 2`) - Added `spell_power` system for magical weapons - Combatant weapon properties (crit_chance, crit_multiplier, elemental support) - DamageCalculator uses `stats.damage` directly (removed `weapon_damage` param) 140 tests. --- #### Task 2.7: Combat Loot Integration ✅ COMPLETE **Files:** `combat_loot_service.py`, `static_item_loader.py`, `app/models/enemy.py` Implemented hybrid loot system: - Static drops (consumables, materials) via `StaticItemLoader` - Procedural drops (equipment) via `ItemGenerator` - Difficulty bonuses: EASY +0%, MEDIUM +5%, HARD +15%, BOSS +30% - Enemy variants: goblin_scout, goblin_warrior, goblin_chieftain 59 tests. --- ### Week 3: Combat UI #### Task 3.1: Create Combat Template (1 day / 8 hours) **Objective:** Build HTMX-powered combat interface **File:** `/public_web/templates/game/combat.html` **Layout:** ``` ┌─────────────────────────────────────────────────────────────┐ │ COMBAT ENCOUNTER │ ├───────────────┬─────────────────────────┬───────────────────┤ │ │ │ │ │ YOUR │ COMBAT LOG │ TURN ORDER │ │ CHARACTER │ │ ─────────── │ │ ───────── │ Goblin attacks you │ 1. Aragorn ✓ │ │ HP: ████ 80 │ for 12 damage! │ 2. Goblin │ │ MP: ███ 60 │ │ 3. Orc │ │ │ You attack Goblin │ │ │ ENEMY │ for 18 damage! │ ACTIVE EFFECTS │ │ ───────── │ CRITICAL HIT! │ ─────────── │ │ Goblin │ │ 🛡️ Defending │ │ HP: ██ 12 │ Goblin is stunned! │ (1 turn) │ │ │ │ │ │ │ ───────────────── │ │ │ │ ACTION BUTTONS │ │ │ │ ───────────────── │ │ │ │ [Attack] [Spell] │ │ │ │ [Item] [Defend] │ │ │ │ │ │ └───────────────┴─────────────────────────┴───────────────────┘ ``` **Implementation:** ```html {% extends "base.html" %} {% block title %}Combat - Code of Conquest{% endblock %} {% block extra_head %} {% endblock %} {% block content %}

⚔️ COMBAT ENCOUNTER

{# Left Panel - Combatants #} {# Middle Panel - Combat Log & Actions #}

Combat Log

{% for entry in combat_log[-10:] %}
{{ entry }}
{% endfor %}

Your Turn

{# Right Panel - Turn Order & Effects #}
{# Modal Container #} {% endblock %} {% block scripts %} {% endblock %} ``` **Also create `/public_web/static/css/combat.css`** **Acceptance Criteria:** - 3-column layout works - Combat log displays messages - HP/MP bars update dynamically - Action buttons trigger HTMX requests - Turn order displays correctly - Active effects shown --- #### Task 3.2: Combat HTMX Integration (1 day / 8 hours) **Objective:** Wire combat UI to API via HTMX **File:** `/public_web/app/views/combat.py` **Implementation:** ```python """ Combat Views Routes for combat UI. """ from flask import Blueprint, render_template, request, g, redirect, url_for from app.services.api_client import APIClient, APIError from app.utils.auth import require_auth from app.utils.logging import get_logger logger = get_logger(__file__) combat_bp = Blueprint('combat', __name__) @combat_bp.route('/') @require_auth def combat_view(combat_id: str): """Display combat interface.""" api_client = APIClient() try: # Get combat state response = api_client.get(f'/combat/{combat_id}/state') combat_state = response['result'] return render_template( 'game/combat.html', combat_id=combat_id, combat_state=combat_state, turn_order=combat_state['turn_order'], current_turn_index=combat_state['current_turn_index'], combat_log=combat_state['combat_log'], character=combat_state['combatants'][0], # Player is first enemies=combat_state['combatants'][1:] # Rest are enemies ) except APIError as e: logger.error(f"Failed to load combat {combat_id}: {e}") return redirect(url_for('game.play')) @combat_bp.route('//action', methods=['POST']) @require_auth def combat_action(combat_id: str): """Process combat action (HTMX endpoint).""" api_client = APIClient() action_data = { 'action_type': request.form.get('action_type'), 'ability_id': request.form.get('ability_id'), 'target_id': request.form.get('target_id'), 'item_id': request.form.get('item_id') } try: # Submit action to API response = api_client.post(f'/combat/{combat_id}/action', json=action_data) result = response['result'] # Check if combat ended if result['combat_state']['status'] in ['victory', 'defeat']: return redirect(url_for('combat.combat_results', combat_id=combat_id)) # Re-render combat view with updated state return render_template( 'game/combat.html', combat_id=combat_id, combat_state=result['combat_state'], turn_order=result['combat_state']['turn_order'], current_turn_index=result['combat_state']['current_turn_index'], combat_log=result['combat_state']['combat_log'], character=result['combat_state']['combatants'][0], enemies=result['combat_state']['combatants'][1:] ) except APIError as e: logger.error(f"Combat action failed: {e}") return render_template('partials/error.html', error=str(e)) @combat_bp.route('//results') @require_auth def combat_results(combat_id: str): """Display combat results (victory/defeat).""" api_client = APIClient() try: response = api_client.get(f'/combat/{combat_id}/results') results = response['result'] return render_template( 'game/combat_results.html', victory=results['victory'], xp_gained=results['xp_gained'], gold_gained=results['gold_gained'], loot=results['loot'] ) except APIError as e: logger.error(f"Failed to load combat results: {e}") return redirect(url_for('game.play')) ``` **Register blueprint in `/public_web/app/__init__.py`:** ```python from app.views.combat import combat_bp app.register_blueprint(combat_bp, url_prefix='/combat') ``` **Acceptance Criteria:** - Combat view loads from API - Action buttons submit to API - Combat state updates dynamically - Combat results shown at end - Errors handled gracefully --- #### Task 3.3: Inventory UI (1 day / 8 hours) **Objective:** Add inventory accordion to character panel **File:** `/public_web/templates/game/partials/character_panel.html` **Add Inventory Section:** ```html {# Existing character panel code #} {# Add Inventory Accordion #}
{% for item in inventory %}
{{ item.name }} {{ item.name }}
{% endfor %}
{# Equipment Section #}
{% if character.equipped.weapon %} {{ get_item_name(character.equipped.weapon) }} {% else %} Empty {% endif %}
{# Similar for helmet, chest, boots, etc. #}
``` **Create `/public_web/templates/game/partials/item_modal.html`:** ```html ``` **Acceptance Criteria:** - Inventory displays in character panel - Click item shows modal with details - Equip/unequip works via HTMX - Use consumable works - Equipment slots show equipped items --- #### Task 3.4: Combat Testing & Polish (1 day / 8 hours) **Objective:** Playtest combat and fix bugs **Testing Checklist:** - [ ] Start combat from story session - [ ] Turn order correct - [ ] Attack deals damage - [ ] Critical hits work - [ ] Spells consume mana - [ ] Effects apply and tick correctly - [ ] Items can be used in combat - [ ] Defend action works - [ ] Victory awards XP/gold/loot - [ ] Defeat handling works - [ ] Combat log readable - [ ] HP/MP bars update - [ ] Multiple enemies work - [ ] Combat state persists (refresh page) **Bug Fixes & Polish:** - Fix any calculation errors - Improve combat log messages - Add visual feedback (animations, highlights) - Improve mobile responsiveness - Add loading states **Acceptance Criteria:** - Combat flows smoothly start to finish - No critical bugs - UX feels responsive and clear - Ready for real gameplay --- ## Phase 4B: Skill Trees & Leveling (Week 4) ### Task 4.1: Verify Skill Tree Data (2 hours) **Objective:** Review skill system **Files to Review:** - `/api/app/models/skills.py` - SkillNode, SkillTree, PlayerClass - `/api/app/data/skills/` - Skill YAML files for all 8 classes **Verification Checklist:** - [ ] Skill trees loaded from YAML - [ ] Each class has 2 skill trees - [ ] Each tree has 5 tiers - [ ] Prerequisites work correctly - [ ] Stat bonuses apply correctly **Acceptance Criteria:** - All 8 classes have complete skill trees - Unlock logic works - Respec logic implemented --- ### Task 4.2: Create Skill Tree Template (2 days / 16 hours) **Objective:** Visual skill tree UI **File:** `/public_web/templates/character/skills.html` **Layout:** ``` ┌─────────────────────────────────────────────────────────────┐ │ CHARACTER SKILL TREES │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Skill Points Available: 5 [Respec] ($$$)│ │ │ │ ┌────────────────────────┐ ┌────────────────────────┐ │ │ │ TREE 1: Combat │ │ TREE 2: Utility │ │ │ ├────────────────────────┤ ├────────────────────────┤ │ │ │ │ │ │ │ │ │ Tier 5: [⬢] [⬢] │ │ Tier 5: [⬢] [⬢] │ │ │ │ │ │ │ │ │ │ │ │ │ │ Tier 4: [⬢] [⬢] │ │ Tier 4: [⬢] [⬢] │ │ │ │ │ │ │ │ │ │ │ │ │ │ Tier 3: [⬢] [⬢] │ │ Tier 3: [⬢] [⬢] │ │ │ │ │ │ │ │ │ │ │ │ │ │ Tier 2: [✓] [⬢] │ │ Tier 2: [⬢] [✓] │ │ │ │ │ │ │ │ │ │ │ │ │ │ Tier 1: [✓] [✓] │ │ Tier 1: [✓] [✓] │ │ │ │ │ │ │ │ │ └────────────────────────┘ └────────────────────────┘ │ │ │ │ Legend: [✓] Unlocked [⬡] Available [⬢] Locked │ │ │ └─────────────────────────────────────────────────────────────┘ ``` **Implementation:** ```html {% extends "base.html" %} {% block title %}Skill Trees - {{ character.name }}{% endblock %} {% block content %}

{{ character.name }}'s Skill Trees

Skill Points: {{ character.skill_points }}
{% for tree in character.skill_trees %}

{{ tree.name }}

{{ tree.description }}

{% for tier in range(5, 0, -1) %}
Tier {{ tier }}
{% for node in tree.get_nodes_by_tier(tier) %}
{% if node.skill_id in character.unlocked_skills %} ✓ {% elif character.can_unlock(node.skill_id) %} ⬡ {% else %} ⬢ {% endif %}
{{ node.name }} {% if character.can_unlock(node.skill_id) and character.skill_points > 0 %} {% endif %}
{# Draw prerequisite lines #} {% if node.prerequisite_skill_id %}
{% endif %} {% endfor %}
{% endfor %}
{% endfor %}
{# Skill Tooltip (populated via HTMX) #}
{% endblock %} ``` **Also create `/public_web/templates/character/partials/skill_tooltip.html`:** ```html

{{ skill.name }}

{{ skill.description }}

Bonuses:
    {% for stat, bonus in skill.stat_bonuses.items() %}
  • +{{ bonus }} {{ stat|title }}
  • {% endfor %}
{% if skill.prerequisite_skill_id %}

Requires: {{ get_skill_name(skill.prerequisite_skill_id) }}

{% endif %}
``` **Acceptance Criteria:** - Dual skill tree layout works - 5 tiers × 2 nodes per tree displayed - Locked/available/unlocked states visual - Prerequisite lines drawn - Hover shows tooltip - Mobile responsive --- ### Task 4.3: Skill Unlock HTMX (4 hours) **Objective:** Click to unlock skills **File:** `/public_web/app/views/skills.py` ```python """ Skill Views Routes for skill tree UI. """ from flask import Blueprint, render_template, request, g from app.services.api_client import APIClient, APIError from app.utils.auth import require_auth from app.utils.logging import get_logger logger = get_logger(__file__) skills_bp = Blueprint('skills', __name__) @skills_bp.route('//tooltip', methods=['GET']) @require_auth def skill_tooltip(skill_id: str): """Get skill tooltip (HTMX partial).""" # Load skill data # Return rendered tooltip pass @skills_bp.route('/characters//skills', methods=['GET']) @require_auth def character_skills(character_id: str): """Display character skill trees.""" api_client = APIClient() try: # Get character response = api_client.get(f'/characters/{character_id}') character = response['result'] # Calculate respec cost respec_cost = character['level'] * 100 return render_template( 'character/skills.html', character=character, respec_cost=respec_cost ) except APIError as e: logger.error(f"Failed to load skills: {e}") return render_template('partials/error.html', error=str(e)) @skills_bp.route('/characters//skills/unlock', methods=['POST']) @require_auth def unlock_skill(character_id: str): """Unlock skill (HTMX endpoint).""" api_client = APIClient() skill_id = request.form.get('skill_id') try: # Unlock skill via API response = api_client.post( f'/characters/{character_id}/skills/unlock', json={'skill_id': skill_id} ) # Re-render skill trees character = response['result']['character'] respec_cost = character['level'] * 100 return render_template( 'character/skills.html', character=character, respec_cost=respec_cost ) except APIError as e: logger.error(f"Failed to unlock skill: {e}") return render_template('partials/error.html', error=str(e)) ``` **Acceptance Criteria:** - Click available node unlocks skill - Skill points decrease - Stat bonuses apply immediately - Prerequisites enforced - UI updates without page reload --- ### Task 4.4: Respec Functionality (4 hours) **Objective:** Respec button with confirmation **Implementation:** (in `skills_bp`) ```python @skills_bp.route('/characters//skills/respec', methods=['POST']) @require_auth def respec_skills(character_id: str): """Respec all skills.""" api_client = APIClient() try: response = api_client.post(f'/characters/{character_id}/skills/respec') character = response['result']['character'] respec_cost = character['level'] * 100 return render_template( 'character/skills.html', character=character, respec_cost=respec_cost, message="Skills reset! All skill points refunded." ) except APIError as e: logger.error(f"Failed to respec: {e}") return render_template('partials/error.html', error=str(e)) ``` **Acceptance Criteria:** - Respec button costs gold - Confirmation modal shown - All skills reset - Skill points refunded - Gold deducted --- ### Task 4.5: XP & Leveling System (1 day / 8 hours) **Objective:** Award XP after combat, level up grants skill points **File:** `/api/app/services/leveling_service.py` ```python """ Leveling Service Manages XP gain and level ups. """ from app.models.character import Character from app.utils.logging import get_logger logger = get_logger(__file__) class LevelingService: """Service for XP and leveling.""" @staticmethod def xp_required_for_level(level: int) -> int: """ Calculate XP required for a given level. Formula: 100 * (level ^ 2) """ return 100 * (level ** 2) @staticmethod def award_xp(character: Character, xp_amount: int) -> dict: """ Award XP to character and check for level up. Args: character: Character instance xp_amount: XP to award Returns: Dict with leveled_up, new_level, skill_points_gained """ character.experience += xp_amount leveled_up = False levels_gained = 0 # Check for level ups (can level multiple times) while character.experience >= LevelingService.xp_required_for_level(character.level + 1): character.level += 1 character.skill_points += 1 levels_gained += 1 leveled_up = True logger.info(f"Character {character.character_id} leveled up to {character.level}") return { 'leveled_up': leveled_up, 'new_level': character.level if leveled_up else None, 'skill_points_gained': levels_gained, 'xp_gained': xp_amount } ``` **Update Combat Results Endpoint:** ```python # In /api/app/api/combat.py @combat_bp.route('//results', methods=['GET']) @require_auth def get_combat_results(combat_id: str): """Get combat results with XP/loot.""" combat_service = CombatService(get_appwrite_service()) encounter = combat_service.get_encounter(combat_id) if encounter.status != CombatStatus.VICTORY: return error_response("Combat not won", 400) # Calculate XP (based on enemy difficulty) xp_gained = sum(enemy.level * 50 for enemy in encounter.combatants if not enemy.is_player) # Award XP to character char_service = get_character_service() character = char_service.get_character(encounter.character_id, g.user_id) from app.services.leveling_service import LevelingService level_result = LevelingService.award_xp(character, xp_gained) # Award gold gold_gained = sum(enemy.level * 25 for enemy in encounter.combatants if not enemy.is_player) character.gold += gold_gained # Generate loot (TODO: implement loot tables) loot = [] # Save character char_service.update_character(character) return success_response({ 'victory': True, 'xp_gained': xp_gained, 'gold_gained': gold_gained, 'loot': loot, 'level_up': level_result }) ``` **Create Level Up Modal Template:** **File:** `/public_web/templates/game/partials/level_up_modal.html` ```html ``` **Acceptance Criteria:** - XP awarded after combat victory - Level up triggers at XP threshold - Skill points granted on level up - Level up modal shown - Character stats increase --- ## Phase 4C: NPC Shop (Days 15-18) ### Task 5.1: Define Shop Inventory (4 hours) **Objective:** Create YAML for shop items **File:** `/api/app/data/shop/general_store.yaml` ```yaml shop_id: "general_store" shop_name: "General Store" shop_description: "A well-stocked general store with essential supplies." shopkeeper_name: "Merchant Guildmaster" inventory: # Weapons - item_id: "iron_sword" stock: -1 # Unlimited stock (-1) price: 50 - item_id: "oak_bow" stock: -1 price: 45 # Armor - item_id: "leather_helmet" stock: -1 price: 30 - item_id: "leather_chest" stock: -1 price: 60 # Consumables - item_id: "health_potion_small" stock: -1 price: 10 - item_id: "health_potion_medium" stock: -1 price: 30 - item_id: "mana_potion_small" stock: -1 price: 15 - item_id: "antidote" stock: -1 price: 20 ``` **Acceptance Criteria:** - Shop inventory defined in YAML - Mix of weapons, armor, consumables - Reasonable pricing - Unlimited stock for basics --- ### Task 5.2: Shop API Endpoints (4 hours) **Objective:** Create shop endpoints **File:** `/api/app/api/shop.py` ```python """ Shop API Blueprint Endpoints: - GET /api/v1/shop/inventory - Browse shop items - POST /api/v1/shop/purchase - Purchase item """ from flask import Blueprint, request, g from app.services.shop_service import ShopService from app.services.character_service import get_character_service from app.services.appwrite_service import get_appwrite_service from app.utils.response import success_response, error_response from app.utils.auth import require_auth from app.utils.logging import get_logger logger = get_logger(__file__) shop_bp = Blueprint('shop', __name__) @shop_bp.route('/inventory', methods=['GET']) @require_auth def get_shop_inventory(): """Get shop inventory.""" shop_service = ShopService() inventory = shop_service.get_shop_inventory("general_store") return success_response({ 'shop_name': "General Store", 'inventory': [ { 'item': item.to_dict(), 'price': price, 'in_stock': True } for item, price in inventory ] }) @shop_bp.route('/purchase', methods=['POST']) @require_auth def purchase_item(): """ Purchase item from shop. Request JSON: { "character_id": "char_abc", "item_id": "iron_sword", "quantity": 1 } """ data = request.get_json() character_id = data.get('character_id') item_id = data.get('item_id') quantity = data.get('quantity', 1) # Get character char_service = get_character_service() character = char_service.get_character(character_id, g.user_id) # Purchase item shop_service = ShopService() try: result = shop_service.purchase_item( character, "general_store", item_id, quantity ) # Save character char_service.update_character(character) return success_response(result) except Exception as e: return error_response(str(e), 400) ``` **Also create `/api/app/services/shop_service.py`:** ```python """ Shop Service Manages NPC shop inventory and purchases. """ import yaml from typing import List, Tuple from app.models.items import Item from app.models.character import Character from app.services.item_loader import ItemLoader from app.utils.logging import get_logger logger = get_logger(__file__) class ShopService: """Service for NPC shops.""" def __init__(self): self.item_loader = ItemLoader() self.shops = self._load_shops() def _load_shops(self) -> dict: """Load all shop data from YAML.""" shops = {} with open('app/data/shop/general_store.yaml', 'r') as f: shop_data = yaml.safe_load(f) shops[shop_data['shop_id']] = shop_data return shops def get_shop_inventory(self, shop_id: str) -> List[Tuple[Item, int]]: """ Get shop inventory. Returns: List of (Item, price) tuples """ shop = self.shops.get(shop_id) if not shop: return [] inventory = [] for item_data in shop['inventory']: item = self.item_loader.get_item(item_data['item_id']) price = item_data['price'] inventory.append((item, price)) return inventory def purchase_item( self, character: Character, shop_id: str, item_id: str, quantity: int = 1 ) -> dict: """ Purchase item from shop. Args: character: Character instance shop_id: Shop ID item_id: Item to purchase quantity: Quantity to buy Returns: Purchase result dict Raises: ValueError: If insufficient gold or item not found """ shop = self.shops.get(shop_id) if not shop: raise ValueError("Shop not found") # Find item in shop inventory item_data = next( (i for i in shop['inventory'] if i['item_id'] == item_id), None ) if not item_data: raise ValueError("Item not available in shop") price = item_data['price'] * quantity # Check if character has enough gold if character.gold < price: raise ValueError(f"Not enough gold. Need {price}, have {character.gold}") # Deduct gold character.gold -= price # Add items to inventory for _ in range(quantity): if item_id not in character.inventory_item_ids: character.inventory_item_ids.append(item_id) else: # Item already exists, increment stack (if stackable) # For now, just add multiple entries character.inventory_item_ids.append(item_id) logger.info(f"Character {character.character_id} purchased {quantity}x {item_id} for {price} gold") return { 'item_purchased': item_id, 'quantity': quantity, 'total_cost': price, 'gold_remaining': character.gold } ``` **Acceptance Criteria:** - Shop inventory endpoint works - Purchase endpoint validates gold - Items added to inventory - Gold deducted - Transactions logged --- ### Task 5.3: Shop UI (1 day / 8 hours) **Objective:** Shop browse and purchase interface **File:** `/public_web/templates/shop/index.html` ```html {% extends "base.html" %} {% block title %}Shop - Code of Conquest{% endblock %} {% block content %}

🏪 {{ shop_name }}

Shopkeeper: {{ shopkeeper_name }}

Your Gold: {{ character.gold }}

{% for item_entry in inventory %}

{{ item_entry.item.name }}

{{ item_entry.price }} gold

{{ item_entry.item.description }}

{% if item_entry.item.item_type == 'weapon' %} ⚔️ Damage: {{ item_entry.item.damage }} {% elif item_entry.item.item_type == 'armor' %} 🛡️ Defense: {{ item_entry.item.defense }} {% elif item_entry.item.item_type == 'consumable' %} ❤️ Restores: {{ item_entry.item.hp_restore }} HP {% endif %}
{% endfor %}
{% endblock %} ``` **Create view in `/public_web/app/views/shop.py`:** ```python """ Shop Views """ from flask import Blueprint, render_template, request, g from app.services.api_client import APIClient, APIError from app.utils.auth import require_auth from app.utils.logging import get_logger logger = get_logger(__file__) shop_bp = Blueprint('shop', __name__) @shop_bp.route('/') @require_auth def shop_index(): """Display shop.""" api_client = APIClient() try: # Get shop inventory shop_response = api_client.get('/shop/inventory') inventory = shop_response['result']['inventory'] # Get character (for gold display) char_response = api_client.get(f'/characters/{g.character_id}') character = char_response['result'] return render_template( 'shop/index.html', shop_name="General Store", shopkeeper_name="Merchant Guildmaster", inventory=inventory, character=character ) except APIError as e: logger.error(f"Failed to load shop: {e}") return render_template('partials/error.html', error=str(e)) @shop_bp.route('/purchase', methods=['POST']) @require_auth def purchase(): """Purchase item (HTMX endpoint).""" api_client = APIClient() purchase_data = { 'character_id': request.form.get('character_id'), 'item_id': request.form.get('item_id'), 'quantity': 1 } try: response = api_client.post('/shop/purchase', json=purchase_data) # Reload shop return shop_index() except APIError as e: logger.error(f"Purchase failed: {e}") return render_template('partials/error.html', error=str(e)) ``` **Acceptance Criteria:** - Shop displays all items - Item cards show stats and price - Purchase button disabled if not enough gold - Purchase adds item to inventory - Gold updates dynamically - UI refreshes after purchase --- ### Task 5.4: Transaction Logging (2 hours) **Objective:** Log all shop purchases **File:** `/api/app/models/transaction.py` ```python """ Transaction Model Tracks all gold transactions (shop, trades, etc.) """ from dataclasses import dataclass, field from datetime import datetime from typing import Dict, Any @dataclass class Transaction: """Represents a gold transaction.""" transaction_id: str transaction_type: str # "shop_purchase", "trade", "quest_reward", etc. character_id: str amount: int # Negative for expenses, positive for income description: str timestamp: datetime = field(default_factory=datetime.utcnow) metadata: Dict[str, Any] = field(default_factory=dict) def to_dict(self) -> Dict[str, Any]: """Serialize to dict.""" return { "transaction_id": self.transaction_id, "transaction_type": self.transaction_type, "character_id": self.character_id, "amount": self.amount, "description": self.description, "timestamp": self.timestamp.isoformat(), "metadata": self.metadata } @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'Transaction': """Deserialize from dict.""" return cls( transaction_id=data["transaction_id"], transaction_type=data["transaction_type"], character_id=data["character_id"], amount=data["amount"], description=data["description"], timestamp=datetime.fromisoformat(data["timestamp"]), metadata=data.get("metadata", {}) ) ``` **Update `ShopService.purchase_item()` to log transaction:** ```python # In shop_service.py def purchase_item(...): # ... existing code ... # Log transaction from app.models.transaction import Transaction import uuid transaction = Transaction( transaction_id=str(uuid.uuid4()), transaction_type="shop_purchase", character_id=character.character_id, amount=-price, description=f"Purchased {quantity}x {item_id} from {shop_id}", metadata={ "shop_id": shop_id, "item_id": item_id, "quantity": quantity, "unit_price": item_data['price'] } ) # Save to database from app.services.appwrite_service import get_appwrite_service appwrite = get_appwrite_service() appwrite.create_document("transactions", transaction.transaction_id, transaction.to_dict()) # ... rest of code ... ``` **Acceptance Criteria:** - All purchases logged to database - Transaction records complete - Can query transaction history --- ## Success Criteria - Phase 4 Complete ### Combat System - [ ] Turn-based combat works end-to-end - [ ] Damage calculations correct (physical, magical, critical) - [ ] Effects process correctly (DOT, HOT, buffs, debuffs, shields, stun) - [ ] Combat UI functional and responsive - [ ] Victory awards XP, gold, loot - [ ] Combat state persists ### Inventory System - [ ] Inventory displays in UI - [ ] Equip/unequip items works - [ ] Consumables can be used - [ ] Equipment affects character stats - [ ] Item YAML data loaded correctly ### Skill Trees - [ ] Visual skill tree UI works - [ ] Prerequisites enforced - [ ] Unlock skills with skill points - [ ] Respec functionality works - [ ] Stat bonuses apply immediately ### Leveling - [ ] XP awarded after combat - [ ] Level up triggers at threshold - [ ] Skill points granted on level up - [ ] Level up modal shown - [ ] Character stats increase ### NPC Shop - [ ] Shop inventory displays - [ ] Purchase validation works - [ ] Items added to inventory - [ ] Gold deducted correctly - [ ] Transactions logged --- ## Next Steps After Phase 4 Once Phase 4 is complete, you'll have a fully playable combat game with progression. The next logical phases are: **Phase 5: Story Progression & Quests** (Original Phase 4 from roadmap) - AI-driven story progression - Action prompts (button-based gameplay) - Quest system (YAML-driven, context-aware) - Full gameplay loop: Explore → Combat → Quests → Level Up **Phase 6: Multiplayer Sessions** - Invite-based co-op - Time-limited sessions - AI-generated campaigns **Phase 7: Marketplace & Economy** - Player-to-player trading - Auction system - Economy balancing --- ## Appendix: Testing Strategy ### Manual Testing Checklist **Combat:** - [ ] Start combat from story - [ ] Turn order correct - [ ] Attack deals damage - [ ] Spells work - [ ] Items usable in combat - [ ] Defend action - [ ] Victory conditions - [ ] Defeat handling **Inventory:** - [ ] Add items - [ ] Remove items - [ ] Equip weapons - [ ] Equip armor - [ ] Use consumables - [ ] Inventory UI updates **Skills:** - [ ] View skill trees - [ ] Unlock skills - [ ] Prerequisites enforced - [ ] Stat bonuses apply - [ ] Respec works **Shop:** - [ ] Browse inventory - [ ] Purchase items - [ ] Insufficient gold handling - [ ] Transaction logging --- ## Document Maintenance **Update this document as you complete tasks:** - Mark tasks complete with ✅ - Add notes about implementation decisions - Update time estimates based on actual progress - Document any blockers or challenges **Good luck with Phase 4 implementation!** 🚀