183 lines
6.5 KiB
Python
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()
|