Phase 4b Abilities and skill trees is finished
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
467
docs/PHASE4b.md
467
docs/PHASE4b.md
@@ -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 %}
|
||||
<div class="skills-container">
|
||||
<div class="skills-header">
|
||||
<h1>{{ character.name }}'s Skill Trees</h1>
|
||||
<div class="skills-info">
|
||||
<span class="skill-points">Skill Points: <strong>{{ character.skill_points }}</strong></span>
|
||||
<button class="btn btn-warning btn-respec"
|
||||
hx-post="/characters/{{ character.character_id }}/skills/respec"
|
||||
hx-confirm="Respec costs {{ respec_cost }} gold. Continue?"
|
||||
hx-target=".skills-container"
|
||||
hx-swap="outerHTML">
|
||||
Respec ({{ respec_cost }} gold)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="skill-trees-grid">
|
||||
{% for tree in character.skill_trees %}
|
||||
<div class="skill-tree">
|
||||
<h2 class="tree-name">{{ tree.name }}</h2>
|
||||
<p class="tree-description">{{ tree.description }}</p>
|
||||
|
||||
<div class="tree-diagram">
|
||||
{% for tier in range(5, 0, -1) %}
|
||||
<div class="skill-tier" data-tier="{{ tier }}">
|
||||
<span class="tier-label">Tier {{ tier }}</span>
|
||||
<div class="skill-nodes">
|
||||
{% for node in tree.get_nodes_by_tier(tier) %}
|
||||
<div class="skill-node {{ get_node_status(node, character) }}"
|
||||
data-skill-id="{{ node.skill_id }}"
|
||||
hx-get="/skills/{{ node.skill_id }}/tooltip"
|
||||
hx-target="#skill-tooltip"
|
||||
hx-swap="innerHTML"
|
||||
hx-trigger="mouseenter">
|
||||
|
||||
<div class="node-icon">
|
||||
{% if node.skill_id in character.unlocked_skills %}
|
||||
✓
|
||||
{% elif character.can_unlock(node.skill_id) %}
|
||||
⬡
|
||||
{% else %}
|
||||
⬢
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<span class="node-name">{{ node.name }}</span>
|
||||
|
||||
{% if character.can_unlock(node.skill_id) and character.skill_points > 0 %}
|
||||
<button class="btn-unlock"
|
||||
hx-post="/characters/{{ character.character_id }}/skills/unlock"
|
||||
hx-vals='{"skill_id": "{{ node.skill_id }}"}'
|
||||
hx-target=".skills-container"
|
||||
hx-swap="outerHTML">
|
||||
Unlock
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Draw prerequisite lines #}
|
||||
{% if node.prerequisite_skill_id %}
|
||||
<div class="prerequisite-line"></div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Skill Tooltip (populated via HTMX) #}
|
||||
<div id="skill-tooltip" class="skill-tooltip"></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
**Also create `/public_web/templates/character/partials/skill_tooltip.html`:**
|
||||
|
||||
```html
|
||||
<div class="tooltip-content">
|
||||
<h3 class="skill-name">{{ skill.name }}</h3>
|
||||
<p class="skill-description">{{ skill.description }}</p>
|
||||
|
||||
<div class="skill-bonuses">
|
||||
<strong>Bonuses:</strong>
|
||||
<ul>
|
||||
{% for stat, bonus in skill.stat_bonuses.items() %}
|
||||
<li>+{{ bonus }} {{ stat|title }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{% if skill.prerequisite_skill_id %}
|
||||
<p class="prerequisite">
|
||||
<strong>Requires:</strong> {{ get_skill_name(skill.prerequisite_skill_id) }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
```
|
||||
|
||||
**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('/<skill_id>/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/<character_id>/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/<character_id>/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/<character_id>/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('/<combat_id>/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
|
||||
<div class="modal-overlay">
|
||||
<div class="modal-content level-up-modal">
|
||||
<div class="modal-header">
|
||||
<h2>🎉 LEVEL UP! 🎉</h2>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p class="level-up-text">
|
||||
Congratulations! You've reached <strong>Level {{ new_level }}</strong>!
|
||||
</p>
|
||||
|
||||
<div class="level-up-rewards">
|
||||
<p>You gained:</p>
|
||||
<ul>
|
||||
<li>+1 Skill Point</li>
|
||||
<li>+{{ stat_increases.vitality }} Vitality</li>
|
||||
<li>+{{ stat_increases.spirit }} Spirit</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" onclick="closeModal()">Awesome!</button>
|
||||
<a href="/characters/{{ character_id }}/skills" class="btn btn-secondary">
|
||||
View Skill Trees
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**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
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user