Files
echoes-of-the-ash/api/items.py
2026-02-08 20:18:42 +01:00

168 lines
7.1 KiB
Python

"""
Standalone items module for the API.
Loads and manages game items from JSON without bot dependencies.
"""
import json
from pathlib import Path
from typing import Dict, Any, Optional, Union
from dataclasses import dataclass
@dataclass
class Item:
"""Represents a game item"""
id: str
name: Union[str, Dict[str, str]]
description: Union[str, Dict[str, str]]
type: str
image_path: str = ""
emoji: str = "📦"
stackable: bool = True
equippable: bool = False
consumable: bool = False
weight: float = 0.0
volume: float = 0.0
stats: Dict[str, int] = None
effects: Dict[str, Any] = None
value: int = 10 # Base value for trading
# Equipment system
slot: str = None # Equipment slot: head, torso, legs, feet, weapon, offhand, backpack
durability: int = None # Max durability for equippable items
tier: int = 1 # Item tier (1-5)
encumbrance: int = 0 # Encumbrance penalty when equipped
weapon_effects: Dict[str, Any] = None # Weapon effects: bleeding, stun, etc.
# Repair system
repairable: bool = False # Can this item be repaired?
repair_materials: list = None # Materials needed for repair
repair_percentage: int = 25 # Percentage of durability restored per repair
repair_tools: list = None # Tools required for repair (consumed durability)
# Crafting system
craftable: bool = False # Can this item be crafted?
craft_materials: list = None # Materials needed to craft this item
craft_level: int = 1 # Minimum level required to craft this item
craft_tools: list = None # Tools required for crafting (consumed durability)
# Uncrafting system
uncraftable: bool = False # Can this item be uncrafted?
uncraft_yield: list = None # Materials yielded from uncrafting (before loss chance)
uncraft_loss_chance: float = 0.3 # Chance to lose materials when uncrafting (0.3 = 30%)
uncraft_tools: list = None # Tools required for uncrafting
# Combat system
combat_usable: bool = False # Can be used during combat
combat_only: bool = False # Can ONLY be used during combat
combat_effects: Dict[str, Any] = None # Effects applied in combat (damage, status)
def __post_init__(self):
if self.stats is None:
self.stats = {}
if self.effects is None:
self.effects = {}
if self.weapon_effects is None:
self.weapon_effects = {}
if self.repair_materials is None:
self.repair_materials = []
if self.craft_materials is None:
self.craft_materials = []
if self.repair_tools is None:
self.repair_tools = []
if self.craft_tools is None:
self.craft_tools = []
if self.uncraft_yield is None:
self.uncraft_yield = []
if self.uncraft_tools is None:
self.uncraft_tools = []
if self.combat_effects is None:
self.combat_effects = {}
class ItemsManager:
"""Manages all game items"""
def __init__(self, gamedata_path: str = "./gamedata"):
self.gamedata_path = Path(gamedata_path)
self.items: Dict[str, Item] = {}
self.load_items()
def load_items(self):
"""Load all items from items.json"""
json_path = self.gamedata_path / 'items.json'
try:
with open(json_path, 'r') as f:
data = json.load(f)
for item_id, item_data in data.get('items', {}).items():
item_type = item_data.get('type', 'misc')
# Automatically mark as consumable if type is consumable
is_consumable = item_data.get('consumable', item_type == 'consumable')
# Collect effects from root level or effects dict
effects = item_data.get('effects', {}).copy()
# Add common consumable effects if they exist at root level
if 'hp_restore' in item_data:
effects['hp_restore'] = item_data['hp_restore']
if 'stamina_restore' in item_data:
effects['stamina_restore'] = item_data['stamina_restore']
if 'treats' in item_data:
effects['treats'] = item_data['treats']
item = Item(
id=item_id,
name=item_data.get('name', 'Unknown Item'),
description=item_data.get('description', ''),
type=item_type,
value=item_data.get('value', 10),
image_path=item_data.get('image_path', ''),
emoji=item_data.get('emoji', '📦'),
stackable=item_data.get('stackable', True),
equippable=item_data.get('equippable', False),
consumable=is_consumable,
weight=item_data.get('weight', 0.0),
volume=item_data.get('volume', 0.0),
stats=item_data.get('stats', {}),
effects=effects,
slot=item_data.get('slot'),
durability=item_data.get('durability'),
tier=item_data.get('tier', 1),
encumbrance=item_data.get('encumbrance', 0),
weapon_effects=item_data.get('weapon_effects', {}),
repairable=item_data.get('repairable', False),
repair_materials=item_data.get('repair_materials', []),
repair_percentage=item_data.get('repair_percentage', 25),
repair_tools=item_data.get('repair_tools', []),
craftable=item_data.get('craftable', False),
craft_materials=item_data.get('craft_materials', []),
craft_level=item_data.get('craft_level', 1),
craft_tools=item_data.get('craft_tools', []),
uncraftable=item_data.get('uncraftable', False),
uncraft_yield=item_data.get('uncraft_yield', []),
uncraft_loss_chance=item_data.get('uncraft_loss_chance', 0.3),
uncraft_tools=item_data.get('uncraft_tools', []),
combat_usable=item_data.get('combat_usable', is_consumable), # Default: consumables are combat usable
combat_only=item_data.get('combat_only', False),
combat_effects=item_data.get('combat_effects', {})
)
self.items[item_id] = item
print(f"📦 Loaded {len(self.items)} items")
except FileNotFoundError:
print("⚠️ items.json not found")
except Exception as e:
print(f"⚠️ Error loading items.json: {e}")
def get_item(self, item_id: str) -> Optional[Item]:
"""Get an item by ID"""
return self.items.get(item_id)
def get_all_items(self) -> Dict[str, Item]:
"""Get all items"""
return self.items
# Global items manager instance
items_manager = ItemsManager()
def get_item(item_id: str) -> Optional[Item]:
"""Convenience function to get an item"""
return items_manager.get_item(item_id)