Files
echoes-of-the-ash/api/services/skills.py

183 lines
6.5 KiB
Python

"""
Skills service - loads skill definitions and provides skill availability logic.
"""
import json
from typing import Dict, Any, List, Optional
from pathlib import Path
class Skill:
"""Represents a combat skill."""
def __init__(self, skill_id: str, data: Dict[str, Any]):
self.id = skill_id
self.name = data.get('name', skill_id)
self.description = data.get('description', '')
self.icon = data.get('icon', '⚔️')
self.stat_requirement = data.get('stat_requirement', 'strength')
self.stat_threshold = data.get('stat_threshold', 0)
self.level_requirement = data.get('level_requirement', 1)
self.cooldown = data.get('cooldown', 3)
self.stamina_cost = data.get('stamina_cost', 5)
self.effects = data.get('effects', {})
class SkillsManager:
"""Loads and manages skill definitions from JSON."""
def __init__(self, gamedata_path: str = "./gamedata"):
self.gamedata_path = Path(gamedata_path)
self.skills: Dict[str, Skill] = {}
self.load_skills()
def load_skills(self):
"""Load skills from skills.json."""
json_path = self.gamedata_path / 'skills.json'
try:
with open(json_path, 'r') as f:
data = json.load(f)
for skill_id, skill_data in data.get('skills', {}).items():
self.skills[skill_id] = Skill(skill_id, skill_data)
print(f"⚔️ Loaded {len(self.skills)} skills")
except FileNotFoundError:
print("⚠️ skills.json not found")
except Exception as e:
print(f"⚠️ Error loading skills.json: {e}")
def get_skill(self, skill_id: str) -> Optional[Skill]:
"""Get a skill by ID."""
return self.skills.get(skill_id)
def get_all_skills(self) -> Dict[str, Skill]:
"""Get all skills."""
return self.skills
def get_available_skills(self, character: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
Get all skills available to a character based on their stats and level.
Returns list of skill dicts with availability info.
"""
available = []
for skill_id, skill in self.skills.items():
# Skip NPC-only skills (assumed to be those with 0 stat threshold and level 1 requirement)
if (skill.stat_threshold <= 0 and skill.level_requirement <= 1) or getattr(skill, 'npc_only', False):
continue
stat_value = character.get(skill.stat_requirement, 0)
level = character.get('level', 1)
unlocked = (stat_value >= skill.stat_threshold and level >= skill.level_requirement)
skill_info = {
"id": skill.id,
"name": skill.name,
"description": skill.description,
"icon": skill.icon,
"stat_requirement": skill.stat_requirement,
"stat_threshold": skill.stat_threshold,
"level_requirement": skill.level_requirement,
"cooldown": skill.cooldown,
"stamina_cost": skill.stamina_cost,
"unlocked": unlocked,
"effects": skill.effects,
}
available.append(skill_info)
return available
class Perk:
"""Represents a passive perk."""
def __init__(self, perk_id: str, data: Dict[str, Any]):
self.id = perk_id
self.name = data.get('name', perk_id)
self.description = data.get('description', '')
self.icon = data.get('icon', '')
self.requirements = data.get('requirements', {})
self.effects = data.get('effects', {})
class PerksManager:
"""Loads and manages perk definitions from JSON."""
def __init__(self, gamedata_path: str = "./gamedata"):
self.gamedata_path = Path(gamedata_path)
self.perks: Dict[str, Perk] = {}
self.load_perks()
def load_perks(self):
"""Load perks from perks.json."""
json_path = self.gamedata_path / 'perks.json'
try:
with open(json_path, 'r') as f:
data = json.load(f)
for perk_id, perk_data in data.get('perks', {}).items():
self.perks[perk_id] = Perk(perk_id, perk_data)
print(f"⭐ Loaded {len(self.perks)} perks")
except FileNotFoundError:
print("⚠️ perks.json not found")
except Exception as e:
print(f"⚠️ Error loading perks.json: {e}")
def get_perk(self, perk_id: str) -> Optional[Perk]:
"""Get a perk by ID."""
return self.perks.get(perk_id)
def get_all_perks(self) -> Dict[str, Perk]:
"""Get all perks."""
return self.perks
def check_requirements(self, perk: Perk, character: Dict[str, Any]) -> bool:
"""Check if a character meets a perk's requirements."""
for req_key, req_value in perk.requirements.items():
if req_key.endswith('_max'):
# Max constraint (e.g., endurance_max: 8 means END must be ≤ 8)
stat_name = req_key.replace('_max', '')
if character.get(stat_name, 0) > req_value:
return False
else:
# Min constraint
if character.get(req_key, 0) < req_value:
return False
return True
def get_available_perks(self, character: Dict[str, Any], owned_perk_ids: List[str]) -> List[Dict[str, Any]]:
"""
Get all perks with availability status for a character.
"""
available = []
for perk_id, perk in self.perks.items():
meets_requirements = self.check_requirements(perk, character)
owned = perk_id in owned_perk_ids
perk_info = {
"id": perk.id,
"name": perk.name,
"description": perk.description,
"icon": perk.icon,
"requirements": perk.requirements,
"effects": perk.effects,
"meets_requirements": meets_requirements,
"owned": owned,
}
available.append(perk_info)
return available
# Perk points per level
PERK_POINT_INTERVAL = 5 # Every 5 levels
def get_total_perk_points(level: int) -> int:
"""Calculate total perk points available for a given level."""
return level // PERK_POINT_INTERVAL
# Global instances
skills_manager = SkillsManager()
perks_manager = PerksManager()