feat(backend): Implement base framework for Perks, Skills, and Derived Stats
This commit is contained in:
178
api/services/skills.py
Normal file
178
api/services/skills.py
Normal file
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
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():
|
||||
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()
|
||||
Reference in New Issue
Block a user