865 lines
29 KiB
Markdown
865 lines
29 KiB
Markdown
# 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 | See [`/PHASE4b.md`](/PHASE4b.md)
|
||
| **Phase 4C** | 3-4 days | NPC Shop | [`/PHASE4c.md`](/PHASE4c.md)
|
||
|
||
**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/<combat_id>/action` - Take action
|
||
- `GET /api/v1/combat/<combat_id>/state` - Get state
|
||
- `POST /api/v1/combat/<combat_id>/flee` - Attempt flee
|
||
- `POST /api/v1/combat/<combat_id>/enemy-turn` - Enemy AI
|
||
- `GET /api/v1/combat/enemies` - List templates (public)
|
||
- `GET /api/v1/combat/enemies/<id>` - 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/<id>/inventory` - Get inventory + equipped
|
||
- `POST /api/v1/characters/<id>/inventory/equip` - Equip item
|
||
- `POST /api/v1/characters/<id>/inventory/unequip` - Unequip item
|
||
- `POST /api/v1/characters/<id>/inventory/use` - Use consumable
|
||
- `DELETE /api/v1/characters/<id>/inventory/<item_id>` - 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 ✅ COMPLETE
|
||
|
||
**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 %}
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/combat.css') }}">
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="combat-container">
|
||
<h1 class="combat-title">⚔️ COMBAT ENCOUNTER</h1>
|
||
|
||
<div class="combat-grid">
|
||
{# Left Panel - Combatants #}
|
||
<aside class="combat-panel combat-combatants">
|
||
<div class="combatant-card player-card">
|
||
<h3>{{ character.name }}</h3>
|
||
<div class="hp-bar">
|
||
<div class="hp-fill" style="width: {{ (character.current_hp / character.stats.max_hp * 100)|int }}%"></div>
|
||
<span class="hp-text">HP: {{ character.current_hp }} / {{ character.stats.max_hp }}</span>
|
||
</div>
|
||
<div class="mp-bar">
|
||
<div class="mp-fill" style="width: {{ (character.current_mp / character.stats.max_mp * 100)|int }}%"></div>
|
||
<span class="mp-text">MP: {{ character.current_mp }} / {{ character.stats.max_mp }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vs-divider">VS</div>
|
||
|
||
{% for enemy in enemies %}
|
||
<div class="combatant-card enemy-card" id="enemy-{{ loop.index0 }}">
|
||
<h3>{{ enemy.name }}</h3>
|
||
<div class="hp-bar">
|
||
<div class="hp-fill enemy" style="width: {{ (enemy.current_hp / enemy.stats.max_hp * 100)|int }}%"></div>
|
||
<span class="hp-text">HP: {{ enemy.current_hp }} / {{ enemy.stats.max_hp }}</span>
|
||
</div>
|
||
{% if enemy.current_hp > 0 %}
|
||
<button class="btn btn-target" onclick="selectTarget('{{ enemy.combatant_id }}')">
|
||
Target
|
||
</button>
|
||
{% else %}
|
||
<span class="defeated-badge">DEFEATED</span>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</aside>
|
||
|
||
{# Middle Panel - Combat Log & Actions #}
|
||
<section class="combat-panel combat-main">
|
||
<div class="combat-log" id="combat-log">
|
||
<h3>Combat Log</h3>
|
||
<div class="log-entries">
|
||
{% for entry in combat_log[-10:] %}
|
||
<div class="log-entry">{{ entry }}</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="combat-actions" id="combat-actions">
|
||
<h3>Your Turn</h3>
|
||
<div class="action-buttons">
|
||
<button class="btn btn-action btn-attack"
|
||
hx-post="/combat/{{ combat_id }}/action"
|
||
hx-vals='{"action_type": "attack", "ability_id": "basic_attack", "target_id": ""}'
|
||
hx-target="#combat-container"
|
||
hx-swap="outerHTML">
|
||
⚔️ Attack
|
||
</button>
|
||
|
||
<button class="btn btn-action btn-spell"
|
||
onclick="openSpellMenu()">
|
||
✨ Cast Spell
|
||
</button>
|
||
|
||
<button class="btn btn-action btn-item"
|
||
onclick="openItemMenu()">
|
||
🎒 Use Item
|
||
</button>
|
||
|
||
<button class="btn btn-action btn-defend"
|
||
hx-post="/combat/{{ combat_id }}/action"
|
||
hx-vals='{"action_type": "defend"}'
|
||
hx-target="#combat-container"
|
||
hx-swap="outerHTML">
|
||
🛡️ Defend
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{# Right Panel - Turn Order & Effects #}
|
||
<aside class="combat-panel combat-sidebar">
|
||
<div class="turn-order">
|
||
<h3>Turn Order</h3>
|
||
<ol>
|
||
{% for combatant_id in turn_order %}
|
||
<li class="{% if loop.index0 == current_turn_index %}active-turn{% endif %}">
|
||
{{ get_combatant_name(combatant_id) }}
|
||
{% if loop.index0 == current_turn_index %}✓{% endif %}
|
||
</li>
|
||
{% endfor %}
|
||
</ol>
|
||
</div>
|
||
|
||
<div class="active-effects">
|
||
<h3>Active Effects</h3>
|
||
{% for effect in character.active_effects %}
|
||
<div class="effect-badge {{ effect.effect_type }}">
|
||
{{ effect.name }} ({{ effect.duration }})
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
|
||
{# Modal Container #}
|
||
<div id="modal-container"></div>
|
||
{% endblock %}
|
||
|
||
{% block scripts %}
|
||
<script>
|
||
let selectedTargetId = null;
|
||
|
||
function selectTarget(targetId) {
|
||
selectedTargetId = targetId;
|
||
|
||
// Update UI to show selected target
|
||
document.querySelectorAll('.btn-target').forEach(btn => {
|
||
btn.classList.remove('selected');
|
||
});
|
||
event.target.classList.add('selected');
|
||
}
|
||
|
||
function openSpellMenu() {
|
||
// TODO: Open modal with spell selection
|
||
}
|
||
|
||
function openItemMenu() {
|
||
// TODO: Open modal with item selection
|
||
}
|
||
|
||
// Auto-scroll combat log to bottom
|
||
const logDiv = document.querySelector('.log-entries');
|
||
if (logDiv) {
|
||
logDiv.scrollTop = logDiv.scrollHeight;
|
||
}
|
||
</script>
|
||
{% 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 ✅ COMPLETE
|
||
|
||
**Objective:** Wire combat UI to API via HTMX
|
||
|
||
**File:** `/public_web/app/views/game_views.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('/<combat_id>')
|
||
@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('/<combat_id>/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('/<combat_id>/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 ✅ COMPLETE
|
||
|
||
**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 #}
|
||
<div class="panel-accordion" data-accordion="inventory">
|
||
<button class="panel-accordion-header" onclick="togglePanelAccordion(this)">
|
||
<span>Inventory <span class="count">({{ character.inventory|length }}/{{ inventory_max }})</span></span>
|
||
<span class="accordion-icon">▼</span>
|
||
</button>
|
||
<div class="panel-accordion-content">
|
||
<div class="inventory-grid">
|
||
{% for item in inventory %}
|
||
<div class="inventory-item {{ item.rarity }}"
|
||
hx-get="/inventory/{{ character.character_id }}/item/{{ item.item_id }}"
|
||
hx-target="#modal-container"
|
||
hx-swap="innerHTML">
|
||
<img src="{{ item.icon_url or '/static/img/items/default.png' }}" alt="{{ item.name }}">
|
||
<span class="item-name">{{ item.name }}</span>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Equipment Section #}
|
||
<div class="panel-accordion" data-accordion="equipment">
|
||
<button class="panel-accordion-header" onclick="togglePanelAccordion(this)">
|
||
<span>Equipment</span>
|
||
<span class="accordion-icon">▼</span>
|
||
</button>
|
||
<div class="panel-accordion-content">
|
||
<div class="equipment-slots">
|
||
<div class="equipment-slot">
|
||
<label>Weapon:</label>
|
||
{% if character.equipped.weapon %}
|
||
<span class="equipped-item">{{ get_item_name(character.equipped.weapon) }}</span>
|
||
<button class="btn-small"
|
||
hx-post="/inventory/{{ character.character_id }}/unequip"
|
||
hx-vals='{"slot": "weapon"}'
|
||
hx-target="#character-panel"
|
||
hx-swap="outerHTML">
|
||
Unequip
|
||
</button>
|
||
{% else %}
|
||
<span class="empty-slot">Empty</span>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="equipment-slot">
|
||
<label>Helmet:</label>
|
||
{# Similar for helmet, chest, boots, etc. #}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
**Create `/public_web/templates/game/partials/item_modal.html`:**
|
||
|
||
```html
|
||
<div class="modal-overlay" onclick="closeModal()">
|
||
<div class="modal-content" onclick="event.stopPropagation()">
|
||
<div class="modal-header">
|
||
<h2 class="item-name {{ item.rarity }}">{{ item.name }}</h2>
|
||
<button class="modal-close" onclick="closeModal()">×</button>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
<p class="item-description">{{ item.description }}</p>
|
||
|
||
<div class="item-stats">
|
||
{% if item.item_type == 'weapon' %}
|
||
<p><strong>Damage:</strong> {{ item.damage }}</p>
|
||
<p><strong>Crit Chance:</strong> {{ (item.crit_chance * 100)|int }}%</p>
|
||
{% elif item.item_type == 'armor' %}
|
||
<p><strong>Defense:</strong> {{ item.defense }}</p>
|
||
<p><strong>Resistance:</strong> {{ item.resistance }}</p>
|
||
{% elif item.item_type == 'consumable' %}
|
||
<p><strong>HP Restore:</strong> {{ item.hp_restore }}</p>
|
||
<p><strong>MP Restore:</strong> {{ item.mp_restore }}</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<p class="item-value">Value: {{ item.value }} gold</p>
|
||
</div>
|
||
|
||
<div class="modal-footer">
|
||
{% if item.item_type == 'weapon' %}
|
||
<button class="btn btn-primary"
|
||
hx-post="/inventory/{{ character_id }}/equip"
|
||
hx-vals='{"item_id": "{{ item.item_id }}", "slot": "weapon"}'
|
||
hx-target="#character-panel"
|
||
hx-swap="outerHTML">
|
||
Equip Weapon
|
||
</button>
|
||
{% elif item.item_type == 'consumable' %}
|
||
<button class="btn btn-primary"
|
||
hx-post="/inventory/{{ character_id }}/use"
|
||
hx-vals='{"item_id": "{{ item.item_id }}"}'
|
||
hx-target="#character-panel"
|
||
hx-swap="outerHTML">
|
||
Use Item
|
||
</button>
|
||
{% endif %}
|
||
|
||
<button class="btn btn-secondary" onclick="closeModal()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
**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)
|
||
See [`/PHASE4b.md`](/PHASE4b.md)
|
||
|
||
## Phase 4C: NPC Shop (Days 15-18)
|
||
See [`/PHASE4c.md`](/PHASE4c.md)
|
||
|
||
|
||
## 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!** 🚀
|