feat(backend): Integrate Derived Stats into combat, loot, and crafting mechanics
This commit is contained in:
@@ -17,6 +17,7 @@ from .. import database as db
|
||||
from ..items import ItemsManager
|
||||
from .. import game_logic
|
||||
from ..core.websockets import manager
|
||||
from ..services.stats import STAT_CAP, invalidate_stats_cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -457,19 +458,28 @@ async def spend_stat_point(
|
||||
if stat not in valid_stats:
|
||||
raise HTTPException(status_code=400, detail=f"Invalid stat. Must be one of: {', '.join(valid_stats)}")
|
||||
|
||||
# Check stat cap
|
||||
if player[stat] >= STAT_CAP:
|
||||
raise HTTPException(status_code=400, detail=f"{stat.capitalize()} is already at maximum ({STAT_CAP})")
|
||||
|
||||
# Update the stat and decrease unspent points
|
||||
update_data = {
|
||||
stat: player[stat] + 1,
|
||||
'unspent_points': player['unspent_points'] - 1
|
||||
}
|
||||
|
||||
# Endurance increases max HP
|
||||
# Endurance increases max HP and max stamina
|
||||
if stat == 'endurance':
|
||||
update_data['max_hp'] = player['max_hp'] + 5
|
||||
update_data['hp'] = min(player['hp'] + 5, update_data['max_hp']) # Also heal by 5
|
||||
update_data['max_stamina'] = player['max_stamina'] + 2
|
||||
update_data['stamina'] = min(player['stamina'] + 2, update_data['max_stamina']) # Also restore by 2
|
||||
|
||||
await db.update_character(current_user['id'], **update_data)
|
||||
|
||||
# Invalidate cached derived stats
|
||||
await invalidate_stats_cache(current_user['id'], redis_manager)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Increased {stat} by 1!",
|
||||
@@ -1550,4 +1560,139 @@ async def drop_item(
|
||||
return {
|
||||
"success": True,
|
||||
"message": get_game_message('dropped_item_success', locale, emoji=item_def.emoji, name=get_locale_string(item_def.name, locale), qty=quantity)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/api/game/character-sheet")
|
||||
async def get_character_sheet(current_user: dict = Depends(get_current_user)):
|
||||
"""Get the full character sheet with base stats, derived stats, skills, and perks."""
|
||||
from ..services.stats import calculate_derived_stats
|
||||
from ..services.skills import skills_manager, perks_manager, get_total_perk_points
|
||||
|
||||
player = current_user
|
||||
character_id = player['id']
|
||||
|
||||
# Get derived stats
|
||||
derived = await calculate_derived_stats(character_id, redis_manager)
|
||||
|
||||
# Get available skills
|
||||
available_skills = skills_manager.get_available_skills(player)
|
||||
|
||||
# Get owned perks
|
||||
owned_perks_rows = await db.get_character_perks(character_id)
|
||||
owned_perk_ids = [row['perk_id'] for row in owned_perks_rows]
|
||||
|
||||
# Get all perks with availability
|
||||
all_perks = perks_manager.get_available_perks(player, owned_perk_ids)
|
||||
|
||||
# Calculate perk points
|
||||
total_perk_points = get_total_perk_points(player['level'])
|
||||
used_perk_points = len(owned_perk_ids)
|
||||
available_perk_points = total_perk_points - used_perk_points
|
||||
|
||||
return {
|
||||
"base_stats": {
|
||||
"strength": player['strength'],
|
||||
"agility": player['agility'],
|
||||
"endurance": player['endurance'],
|
||||
"intellect": player['intellect'],
|
||||
"unspent_points": player['unspent_points'],
|
||||
"stat_cap": STAT_CAP,
|
||||
},
|
||||
"derived_stats": derived,
|
||||
"skills": available_skills,
|
||||
"perks": {
|
||||
"available_points": available_perk_points,
|
||||
"total_points": total_perk_points,
|
||||
"used_points": used_perk_points,
|
||||
"all_perks": all_perks,
|
||||
},
|
||||
"character": {
|
||||
"name": player['name'],
|
||||
"level": player['level'],
|
||||
"xp": player['xp'],
|
||||
"hp": player['hp'],
|
||||
"max_hp": player['max_hp'],
|
||||
"stamina": player['stamina'],
|
||||
"max_stamina": player['max_stamina'],
|
||||
"avatar_data": player.get('avatar_data'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/api/game/select_perk")
|
||||
async def select_perk(
|
||||
perk_id: str,
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""Select a perk for the character."""
|
||||
from ..services.skills import perks_manager, get_total_perk_points
|
||||
|
||||
player = current_user
|
||||
character_id = player['id']
|
||||
|
||||
# Check perk exists
|
||||
perk = perks_manager.get_perk(perk_id)
|
||||
if not perk:
|
||||
raise HTTPException(status_code=404, detail="Perk not found")
|
||||
|
||||
# Check perk points available
|
||||
owned_perks = await db.get_character_perks(character_id)
|
||||
owned_perk_ids = [row['perk_id'] for row in owned_perks]
|
||||
|
||||
total_points = get_total_perk_points(player['level'])
|
||||
used_points = len(owned_perk_ids)
|
||||
|
||||
if used_points >= total_points:
|
||||
raise HTTPException(status_code=400, detail="No perk points available")
|
||||
|
||||
# Check if already owned
|
||||
if perk_id in owned_perk_ids:
|
||||
raise HTTPException(status_code=400, detail="Perk already selected")
|
||||
|
||||
# Check requirements
|
||||
if not perks_manager.check_requirements(perk, player):
|
||||
raise HTTPException(status_code=400, detail="Requirements not met for this perk")
|
||||
|
||||
# Add perk
|
||||
success = await db.add_character_perk(character_id, perk_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=400, detail="Failed to select perk")
|
||||
|
||||
# Invalidate stats cache (perks affect derived stats)
|
||||
await invalidate_stats_cache(character_id, redis_manager)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Perk '{perk_id}' selected!",
|
||||
"perk": {
|
||||
"id": perk.id,
|
||||
"name": perk.name,
|
||||
"description": perk.description,
|
||||
"icon": perk.icon,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/api/game/available-skills")
|
||||
async def get_available_skills(current_user: dict = Depends(get_current_user)):
|
||||
"""Get available skills for the combat UI abilities dropdown."""
|
||||
from ..services.skills import skills_manager
|
||||
from .. import database as db
|
||||
|
||||
player = current_user
|
||||
all_skills = skills_manager.get_available_skills(player)
|
||||
|
||||
# Check cooldowns
|
||||
effects = await db.get_player_effects(player['id'])
|
||||
cooldowns = {eff['source']: eff['ticks_remaining'] for eff in effects if eff.get('effect_type') == 'cooldown'}
|
||||
|
||||
# Only return unlocked skills for the combat dropdown
|
||||
unlocked = []
|
||||
for s in all_skills:
|
||||
if s['unlocked']:
|
||||
cd_source = f"cd:{s['id']}"
|
||||
s['current_cooldown'] = cooldowns.get(cd_source, 0)
|
||||
unlocked.append(s)
|
||||
|
||||
return {"skills": unlocked}
|
||||
Reference in New Issue
Block a user