From 8784fbaa880b3e8d3ed52250548562f4f0b19745 Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Fri, 28 Nov 2025 22:02:57 -0600 Subject: [PATCH] Phase 4b Abilities and skill trees is finished --- api/app/services/combat_service.py | 9 +- api/docs/GAME_SYSTEMS.md | 163 +++++++++- docs/PHASE4b.md | 467 ----------------------------- 3 files changed, 157 insertions(+), 482 deletions(-) delete mode 100644 docs/PHASE4b.md diff --git a/api/app/services/combat_service.py b/api/app/services/combat_service.py index 4bfbb75..2336de4 100644 --- a/api/app/services/combat_service.py +++ b/api/app/services/combat_service.py @@ -1269,9 +1269,9 @@ class CombatService: character = self.character_service.get_character(char_id, user_id) # Add XP and check for level up - old_level = character.level - character.experience += xp_per_player - # TODO: Add level up logic based on XP thresholds + leveled_up = character.add_experience(xp_per_player) + if leveled_up: + rewards.level_ups.append(char_id) # Add gold character.gold += gold_per_player @@ -1279,9 +1279,6 @@ class CombatService: # Save character self.character_service.update_character(character, user_id) - if character.level > old_level: - rewards.level_ups.append(char_id) - except Exception as e: logger.error("Failed to distribute rewards to character", char_id=char_id, diff --git a/api/docs/GAME_SYSTEMS.md b/api/docs/GAME_SYSTEMS.md index 4296e26..05e3956 100644 --- a/api/docs/GAME_SYSTEMS.md +++ b/api/docs/GAME_SYSTEMS.md @@ -365,17 +365,162 @@ effects_applied: ### Experience & Leveling -| Source | XP Gain | -|--------|---------| -| Combat victory | Based on enemy difficulty | -| Quest completion | Fixed quest reward | -| Story milestones | Major plot points | -| Exploration | Discovering new locations | +**XP Sources:** + +| Source | XP Gain | Notes | +|--------|---------|-------| +| Combat victory | Based on enemy `experience_reward` field | Divided evenly among party members | +| Quest completion | Fixed quest reward | Defined in quest data | +| Story milestones | Major plot points | AI-driven narrative rewards | +| Exploration | Discovering new locations | Future enhancement | **Level Progression:** -- XP required increases per level (exponential curve) -- Each level grants +1 skill point -- Stats may increase based on class + +The XP requirement for each level follows an exponential curve using the formula: + +``` +XP Required = 100 × (current_level ^ 1.5) +``` + +| Level | XP Required | Cumulative XP | +|-------|-------------|---------------| +| 1→2 | 100 | 100 | +| 2→3 | 282 | 382 | +| 3→4 | 519 | 901 | +| 4→5 | 800 | 1,701 | +| 5→6 | 1,118 | 2,819 | +| 6→7 | 1,469 | 4,288 | +| 7→8 | 1,849 | 6,137 | +| 8→9 | 2,254 | 8,391 | +| 9→10 | 2,683 | 11,074 | + +**Leveling Mechanics:** +- Each level grants **+1 skill point** to spend in skill trees +- Skill points calculated: `level - unlocked_skills.length` +- Overflow XP automatically carries to next level +- Level up triggers automatically when threshold reached +- Base stats remain constant (progression via skill trees & equipment) + +**Implementation:** +- Leveling logic lives in `Character` model (`add_experience()`, `level_up()` methods) +- No separate service needed (OOP design pattern) +- See `api/app/models/character.py` lines 312-358 + +### Skill Trees + +**Overview:** + +Each character class has **2-3 skill trees** representing different specializations or playstyles. Players earn **1 skill point per level** to unlock skills, which provide permanent bonuses and unlock combat abilities. + +**Skill Points:** +``` +Available Skill Points = Character Level - Unlocked Skills Count +``` + +**Skill Tree Structure:** + +Each skill tree contains **5 tiers** of increasing power: + +| Tier | Description | Typical Effects | +|------|-------------|-----------------| +| **1** | Entry-level skills | Basic abilities, small stat bonuses (+3-5) | +| **2** | Intermediate skills | Enhanced abilities, moderate bonuses (+5-8) | +| **3** | Advanced skills | Powerful abilities, passive effects | +| **4** | Expert skills | Ability enhancements, large bonuses (+10-15) | +| **5** | Ultimate skills | Class-defining abilities, massive bonuses (+20+) | + +**Prerequisites:** + +Skills have prerequisites that create progression paths: +- Tier 1 skills have **no prerequisites** (open choices) +- Higher tier skills require **specific lower-tier skills** +- Cannot skip tiers (must unlock Tier 1 before Tier 2, etc.) +- Can mix between trees within same class + +**Skill Effects:** + +Skills provide multiple types of benefits: + +1. **Stat Bonuses** - Permanent increases to stats + ```yaml + effects: + stat_bonuses: + strength: 10 + defense: 5 + ``` + +2. **Ability Unlocks** - Grant new combat abilities + ```yaml + effects: + abilities: + - shield_bash + - riposte + ``` + +3. **Passive Effects** - Special mechanics + ```yaml + effects: + passive_effects: + - stun_resistance + - damage_reflection + ``` + +4. **Ability Enhancements** - Modify existing abilities + ```yaml + effects: + ability_enhancements: + fireball: + damage_bonus: 15 + mana_cost_reduction: 5 + ``` + +5. **Combat Bonuses** - Crit chance, crit multiplier, etc. + ```yaml + effects: + combat_bonuses: + crit_chance: 0.1 # +10% + crit_multiplier: 0.5 # +0.5x + ``` + +**Example Progression Path:** + +**Vanguard - Shield Bearer Tree:** +``` +Level 1: No skills yet (0 points) +Level 2: Unlock "Shield Bash" (Tier 1) → Gain shield bash ability +Level 3: Unlock "Fortify" (Tier 1) → +5 defense bonus +Level 4: Unlock "Shield Wall" (Tier 2, requires Shield Bash) → Shield wall ability +Level 5: Unlock "Iron Skin" (Tier 2, requires Fortify) → +5 constitution +Level 6: Unlock "Guardian's Resolve" (Tier 3) → +10 defense + stun resistance +... +``` + +**Class Specializations:** + +Each class offers distinct playstyles through their trees: + +| Class | Tree 1 | Tree 2 | Tree 3 | +|-------|--------|--------|--------| +| **Vanguard** | Shield Bearer (Tank) | Weapon Master (DPS) | - | +| **Arcanist** | Pyromancy (Fire) | Cryomancy (Ice) | Electromancy (Lightning) | +| **Wildstrider** | Beast Mastery | Nature Magic | - | +| **Assassin** | Shadow Arts | Poison Master | - | +| **Luminary** | Holy Magic | Divine Protection | - | +| **Necromancer** | Death Magic | Corpse Summoning | - | +| **Lorekeeper** | Arcane Knowledge | Support Magic | - | +| **Oathkeeper** | Divine Wrath | Holy Shield | - | + +**Design Notes:** +- Skill choices are **permanent** (no respec system currently) +- Players can mix skills from different trees within same class +- Some skills are **mutually exclusive** by design (different playstyles) +- Skill point allocation encourages specialization vs. generalization + +**Implementation:** +- Skills defined in class YAML files at `api/app/data/classes/*.yaml` +- Character stores only `unlocked_skills: List[str]` (skill IDs) +- Bonuses calculated dynamically via `Character.get_effective_stats()` +- Full documentation: [SKILLS_AND_ABILITIES.md](SKILLS_AND_ABILITIES.md) ### Loot System diff --git a/docs/PHASE4b.md b/docs/PHASE4b.md deleted file mode 100644 index 9be4ba9..0000000 --- a/docs/PHASE4b.md +++ /dev/null @@ -1,467 +0,0 @@ - -## 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 - ----