Files
echoes-of-the-ash/api/routers/equipment.py
2026-02-05 15:00:49 +01:00

792 lines
34 KiB
Python

"""
Equipment router.
Auto-generated from main.py migration.
"""
from fastapi import APIRouter, HTTPException, Depends, status, Request
from fastapi.security import HTTPAuthorizationCredentials
from typing import Optional, Dict, Any
from datetime import datetime
import random
import json
import logging
from ..core.security import get_current_user, security, verify_internal_key
from ..services.models import *
from ..services.helpers import calculate_distance, calculate_stamina_cost, calculate_player_capacity, calculate_crafting_stamina_cost, get_game_message, get_locale_string
from .. import database as db
from ..items import ItemsManager
from .. import game_logic
from ..core.websockets import manager
logger = logging.getLogger(__name__)
# These will be injected by main.py
LOCATIONS = None
ITEMS_MANAGER = None
WORLD = None
def init_router_dependencies(locations, items_manager, world):
"""Initialize router with game data dependencies"""
global LOCATIONS, ITEMS_MANAGER, WORLD
LOCATIONS = locations
ITEMS_MANAGER = items_manager
WORLD = world
router = APIRouter(tags=["equipment"])
# Endpoints
@router.post("/api/game/equip")
async def equip_item(
equip_req: EquipItemRequest,
request: Request,
current_user: dict = Depends(get_current_user)
):
"""Equip an item from inventory"""
player_id = current_user['id']
locale = request.headers.get('Accept-Language', 'en')
# Get the inventory item
inv_item = await db.get_inventory_item_by_id(equip_req.inventory_id)
if not inv_item or inv_item['character_id'] != player_id:
raise HTTPException(status_code=404, detail="Item not found in inventory")
# Get item definition
item_def = ITEMS_MANAGER.get_item(inv_item['item_id'])
if not item_def:
raise HTTPException(status_code=404, detail="Item definition not found")
# Check if item is equippable
if not item_def.equippable or not item_def.slot:
raise HTTPException(status_code=400, detail="This item cannot be equipped")
# Check if slot is valid
valid_slots = ['head', 'torso', 'legs', 'feet', 'weapon', 'offhand', 'backpack']
if item_def.slot not in valid_slots:
raise HTTPException(status_code=400, detail=f"Invalid equipment slot: {item_def.slot}")
# Check if slot is already occupied
current_equipped = await db.get_equipped_item_in_slot(player_id, item_def.slot)
unequipped_item_name = None
if current_equipped and current_equipped.get('item_id'):
# Get the old item's name for the message
old_inv_item = await db.get_inventory_item_by_id(current_equipped['item_id'])
if old_inv_item:
old_item_def = ITEMS_MANAGER.get_item(old_inv_item['item_id'])
unequipped_item_name = old_item_def.name if old_item_def else "previous item"
# Unequip current item first
await db.unequip_item(player_id, item_def.slot)
# Mark as not equipped in inventory
await db.update_inventory_item(current_equipped['item_id'], is_equipped=False)
# Equip the new item
await db.equip_item(player_id, item_def.slot, equip_req.inventory_id)
# Mark as equipped in inventory
await db.update_inventory_item(equip_req.inventory_id, is_equipped=True)
# Initialize unique_item if this is first time equipping an equippable with durability
if inv_item.get('unique_item_id') is None and item_def.durability:
# Create a unique_item instance for this equipment
# Save base stats to unique_stats
base_stats = {k: int(v) if isinstance(v, (int, float)) else v for k, v in item_def.stats.items()} if item_def.stats else {}
unique_item_id = await db.create_unique_item(
item_id=item_def.id,
durability=item_def.durability,
max_durability=item_def.durability,
tier=item_def.tier if hasattr(item_def, 'tier') else 1,
unique_stats=base_stats
)
# Link the inventory item to this unique_item
await db.update_inventory_item(
equip_req.inventory_id,
unique_item_id=unique_item_id
)
# Build message
if unequipped_item_name:
message = get_game_message('unequip_equip', locale, old=unequipped_item_name, new=get_locale_string(item_def.name, locale))
else:
message = get_game_message('equipped', locale, item=get_locale_string(item_def.name, locale))
return {
"success": True,
"message": message,
"slot": item_def.slot,
"unequipped_item": unequipped_item_name
}
@router.post("/api/game/unequip")
async def unequip_item(
unequip_req: UnequipItemRequest,
request: Request,
current_user: dict = Depends(get_current_user)
):
"""Unequip an item from equipment slot"""
player_id = current_user['id']
locale = request.headers.get('Accept-Language', 'en')
# Check if slot is valid
valid_slots = ['head', 'torso', 'legs', 'feet', 'weapon', 'offhand', 'backpack']
if unequip_req.slot not in valid_slots:
raise HTTPException(status_code=400, detail=f"Invalid equipment slot: {unequip_req.slot}")
# Get currently equipped item
equipped = await db.get_equipped_item_in_slot(player_id, unequip_req.slot)
if not equipped:
raise HTTPException(status_code=400, detail=f"No item equipped in {unequip_req.slot} slot")
# Get inventory item and item definition
inv_item = await db.get_inventory_item_by_id(equipped['item_id'])
item_def = ITEMS_MANAGER.get_item(inv_item['item_id'])
# Check if inventory has space (volume-wise)
inventory = await db.get_inventory(player_id)
total_volume = sum(
ITEMS_MANAGER.get_item(i['item_id']).volume * i['quantity']
for i in inventory
if ITEMS_MANAGER.get_item(i['item_id']) and not i['is_equipped']
)
# Get max volume (base 10 + backpack bonus)
max_volume = 10.0
for inv in inventory:
if inv['is_equipped']:
item = ITEMS_MANAGER.get_item(inv['item_id'])
if item:
# Use unique_stats if this is a unique item, otherwise fall back to default stats
if inv.get('unique_item_id'):
unique_item = await db.get_unique_item(inv['unique_item_id'])
if unique_item and unique_item.get('unique_stats'):
max_volume += unique_item['unique_stats'].get('volume_capacity', 0)
elif item.stats:
max_volume += item.stats.get('volume_capacity', 0)
# If unequipping backpack, check if items will fit
if unequip_req.slot == 'backpack':
# Get the backpack's volume capacity from unique_stats if available
backpack_volume = 0
if inv_item.get('unique_item_id'):
unique_item = await db.get_unique_item(inv_item['unique_item_id'])
if unique_item and unique_item.get('unique_stats'):
backpack_volume = unique_item['unique_stats'].get('volume_capacity', 0)
elif item_def.stats:
backpack_volume = item_def.stats.get('volume_capacity', 0)
if backpack_volume > 0 and total_volume > (max_volume - backpack_volume):
raise HTTPException(
status_code=400,
detail="Cannot unequip backpack: inventory would exceed volume capacity"
)
# Check if adding this item would exceed volume
if total_volume + item_def.volume > max_volume:
# Drop to ground instead
await db.unequip_item(player_id, unequip_req.slot)
await db.update_inventory_item(equipped['item_id'], is_equipped=False)
await db.drop_item(player_id, inv_item['item_id'], 1, current_user['location_id'])
await db.remove_from_inventory(player_id, inv_item['item_id'], 1)
return {
"success": True,
"message": get_game_message('unequip_dropped', locale, item=get_locale_string(item_def.name, locale)),
"dropped": True
}
# Unequip the item
await db.unequip_item(player_id, unequip_req.slot)
await db.update_inventory_item(equipped['item_id'], is_equipped=False)
return {
"success": True,
"message": get_game_message('unequipped', locale, item=get_locale_string(item_def.name, locale)),
"dropped": False
}
@router.get("/api/game/equipment")
async def get_equipment(current_user: dict = Depends(get_current_user)):
"""Get all equipped items"""
player_id = current_user['id']
equipment = await db.get_all_equipment(player_id)
# Enrich with item data
enriched = {}
for slot, item_data in equipment.items():
if item_data:
inv_item = await db.get_inventory_item_by_id(item_data['item_id'])
item_def = ITEMS_MANAGER.get_item(inv_item['item_id'])
if item_def:
enriched[slot] = {
"inventory_id": item_data['item_id'],
"item_id": item_def.id,
"name": item_def.name,
"description": item_def.description,
"emoji": item_def.emoji,
"image_path": item_def.image_path,
"durability": inv_item.get('durability'),
"max_durability": inv_item.get('max_durability'),
"tier": inv_item.get('tier', 1),
"stats": item_def.stats,
"encumbrance": item_def.encumbrance
}
else:
enriched[slot] = None
return {"equipment": enriched}
@router.post("/api/game/repair_item")
async def repair_item(
repair_req: RepairItemRequest,
request: Request,
current_user: dict = Depends(get_current_user)
):
"""Repair an item using materials at a workbench location"""
player_id = current_user['id']
locale = request.headers.get('Accept-Language', 'en')
# Get player's location
player = await db.get_player_by_id(player_id)
location = LOCATIONS.get(player['location_id'])
if not location:
raise HTTPException(status_code=404, detail="Location not found")
# Check if location has workbench
location_tags = getattr(location, 'tags', [])
if 'workbench' not in location_tags and 'repair_station' not in location_tags:
raise HTTPException(
status_code=400,
detail="You need to be at a location with a workbench to repair items. Try the Gas Station!"
)
# Get inventory item
inv_item = await db.get_inventory_item(repair_req.inventory_id)
if not inv_item or inv_item['character_id'] != player_id:
raise HTTPException(status_code=404, detail="Item not found in inventory")
# Get item definition
item_def = ITEMS_MANAGER.get_item(inv_item['item_id'])
if not item_def:
raise HTTPException(status_code=404, detail="Item definition not found")
# Check if item is repairable
if not getattr(item_def, 'repairable', False):
raise HTTPException(status_code=400, detail=f"{item_def.name} cannot be repaired")
# Check if item has durability (unique item)
if not inv_item.get('unique_item_id'):
raise HTTPException(status_code=400, detail="This item doesn't have durability tracking")
# Get unique item data
unique_item = await db.get_unique_item(inv_item['unique_item_id'])
if not unique_item:
raise HTTPException(status_code=500, detail="Unique item data not found")
current_durability = unique_item.get('durability', 0)
max_durability = unique_item.get('max_durability', 100)
# Check if item needs repair
if current_durability >= max_durability:
raise HTTPException(status_code=400, detail=f"{item_def.name} is already at full durability")
# Get repair materials
repair_materials = getattr(item_def, 'repair_materials', [])
if not repair_materials:
raise HTTPException(status_code=500, detail="Item repair configuration missing")
# Get repair tools
repair_tools = getattr(item_def, 'repair_tools', [])
# Check if player has all required materials and tools
player_inventory = await db.get_inventory(player_id)
inventory_dict = {item['item_id']: item['quantity'] for item in player_inventory}
missing_materials = []
for material in repair_materials:
required_qty = material.get('quantity', 1)
available_qty = inventory_dict.get(material['item_id'], 0)
if available_qty < required_qty:
material_def = ITEMS_MANAGER.get_item(material['item_id'])
material_name = material_def.name if material_def else material['item_id']
missing_materials.append(f"{material_name} ({available_qty}/{required_qty})")
if missing_materials:
raise HTTPException(
status_code=400,
detail=f"Missing materials: {', '.join(missing_materials)}"
)
# Check and consume tools if required
tools_consumed = []
if repair_tools:
success, error_msg, tools_consumed = await consume_tool_durability(player_id, repair_tools, player_inventory)
if not success:
raise HTTPException(status_code=400, detail=error_msg)
# Calculate stamina cost
stamina_cost = calculate_crafting_stamina_cost(unique_item.get('tier', 1), 'repair')
# Check stamina
if player['stamina'] < stamina_cost:
raise HTTPException(status_code=400, detail=f"Not enough stamina. Need {stamina_cost}, have {player['stamina']}")
# Deduct stamina
new_stamina = max(0, player['stamina'] - stamina_cost)
await db.update_player_stamina(player_id, new_stamina)
# Consume materials
for material in repair_materials:
await db.remove_item_from_inventory(player_id, material['item_id'], material['quantity'])
# Calculate repair amount
repair_percentage = getattr(item_def, 'repair_percentage', 25)
repair_amount = int((max_durability * repair_percentage) / 100)
new_durability = min(current_durability + repair_amount, max_durability)
# Update unique item durability
await db.update_unique_item(inv_item['unique_item_id'], durability=new_durability)
# Build materials consumed message
materials_used = []
for material in repair_materials:
material_def = ITEMS_MANAGER.get_item(material['item_id'])
emoji = material_def.emoji if material_def and hasattr(material_def, 'emoji') else '📦'
name = material_def.name if material_def else material['item_id']
materials_used.append(f"{emoji} {name} x{material['quantity']}")
return {
"success": True,
"message": get_game_message('repaired_success', locale, item=get_locale_string(item_def.name, locale), amount=repair_amount),
"item_name": item_def.name,
"old_durability": current_durability,
"new_durability": new_durability,
"max_durability": max_durability,
"materials_consumed": materials_used,
"tools_consumed": tools_consumed,
"repair_amount": repair_amount,
"stamina_cost": stamina_cost,
"new_stamina": new_stamina
}
async def reduce_armor_durability(player_id: int, damage_taken: int) -> tuple:
"""
Reduce durability of equipped armor pieces when taking damage.
Formula: durability_loss = max(1, (damage_taken / armor_value) * base_reduction_rate)
Base reduction rate: 0.5 (so 10 damage with 5 armor = 1 durability loss)
Returns: (armor_damage_absorbed, broken_armor_pieces)
"""
equipment = await db.get_all_equipment(player_id)
armor_pieces = ['head', 'torso', 'legs', 'feet']
total_armor = 0
equipped_armor = []
# Collect all equipped armor
for slot in armor_pieces:
if equipment.get(slot) and equipment[slot]:
armor_slot = equipment[slot]
inv_item = await db.get_inventory_item_by_id(armor_slot['item_id'])
if inv_item and inv_item.get('unique_item_id'):
item_def = ITEMS_MANAGER.get_item(inv_item['item_id'])
if item_def and item_def.stats and 'armor' in item_def.stats:
armor_value = item_def.stats['armor']
total_armor += armor_value
equipped_armor.append({
'slot': slot,
'inv_item_id': armor_slot['item_id'],
'unique_item_id': inv_item['unique_item_id'],
'item_id': inv_item['item_id'],
'item_def': item_def,
'armor_value': armor_value
})
if not equipped_armor:
return 0, []
# Calculate damage absorbed by armor (total armor reduces damage)
armor_absorbed = min(damage_taken // 2, total_armor) # Armor absorbs up to half the damage
# Calculate durability loss for each armor piece
# Balanced formula: armor should last many combats (10-20+ hits for low tier)
base_reduction_rate = 0.1 # Reduced from 0.5 to make armor more durable
broken_armor = []
for armor in equipped_armor:
# Each piece takes durability loss proportional to its armor value
proportion = armor['armor_value'] / total_armor if total_armor > 0 else 0
# Formula: durability_loss = (damage_taken * proportion / armor_value) * base_rate
# This means higher armor value = less durability loss per hit
# With base_rate = 0.1, a 5 armor piece taking 10 damage loses ~0.2 durability per hit
durability_loss = max(1, int((damage_taken * proportion / max(armor['armor_value'], 1)) * base_reduction_rate * 10))
# Get current durability
unique_item = await db.get_unique_item(armor['unique_item_id'])
if unique_item:
current_durability = unique_item.get('durability', 0)
new_durability = max(0, current_durability - durability_loss)
await db.update_unique_item(armor['unique_item_id'], durability=new_durability)
# If armor broke, unequip and remove from inventory
if new_durability <= 0:
await db.unequip_item(player_id, armor['slot'])
await db.remove_inventory_row(armor['inv_item_id'])
broken_armor.append({
'name': armor['item_def'].name,
'emoji': armor['item_def'].emoji,
'slot': armor['slot']
})
return armor_absorbed, broken_armor
async def consume_tool_durability(user_id: int, tools: list, inventory: list) -> tuple:
"""
Consume durability from required tools.
Returns: (success, error_message, consumed_tools_info)
"""
consumed_tools = []
tools_map = {}
# Build map of available tools with durability
for inv_item in inventory:
if inv_item.get('unique_item_id'):
unique_item = await db.get_unique_item(inv_item['unique_item_id'])
if unique_item:
item_id = inv_item['item_id']
durability = unique_item.get('durability', 0)
if item_id not in tools_map:
tools_map[item_id] = []
tools_map[item_id].append({
'inventory_id': inv_item['id'],
'unique_item_id': inv_item['unique_item_id'],
'durability': durability,
'max_durability': unique_item.get('max_durability', 100)
})
# Check and consume tools
for tool_req in tools:
tool_id = tool_req['item_id']
durability_cost = tool_req['durability_cost']
if tool_id not in tools_map or not tools_map[tool_id]:
tool_def = ITEMS_MANAGER.items.get(tool_id)
tool_name = tool_def.name if tool_def else tool_id
return False, f"Missing required tool: {tool_name}", []
# Find tool with enough durability
tool_found = None
for tool in tools_map[tool_id]:
if tool['durability'] >= durability_cost:
tool_found = tool
break
if not tool_found:
tool_def = ITEMS_MANAGER.items.get(tool_id)
tool_name = tool_def.name if tool_def else tool_id
return False, f"Tool {tool_name} doesn't have enough durability (need {durability_cost})", []
# Consume durability
new_durability = tool_found['durability'] - durability_cost
await db.update_unique_item(tool_found['unique_item_id'], durability=new_durability)
# If tool breaks, remove from inventory
if new_durability <= 0:
await db.remove_inventory_row(tool_found['inventory_id'])
tool_def = ITEMS_MANAGER.items.get(tool_id)
consumed_tools.append({
'item_id': tool_id,
'name': tool_def.name if tool_def else tool_id,
'durability_cost': durability_cost,
'broke': new_durability <= 0
})
return True, "", consumed_tools
@router.get("/api/game/repairable")
async def get_repairable_items(current_user: dict = Depends(get_current_user)):
"""Get all repairable items from inventory and equipped slots"""
try:
player = current_user # current_user is already the character dict
if not player:
raise HTTPException(status_code=404, detail="Player not found")
location_id = player['location_id']
location = LOCATIONS.get(location_id)
# Check if player is at a repair station
if not location or 'repair_station' not in getattr(location, 'tags', []):
raise HTTPException(status_code=400, detail="You must be at a repair station to repair items")
repairable_items = []
# Check inventory items
inventory = await db.get_inventory(current_user['id'])
inventory_counts = {}
for inv_item in inventory:
item_id = inv_item['item_id']
quantity = inv_item.get('quantity', 1)
inventory_counts[item_id] = inventory_counts.get(item_id, 0) + quantity
for inv_item in inventory:
if inv_item.get('unique_item_id'):
unique_item = await db.get_unique_item(inv_item['unique_item_id'])
if not unique_item:
continue
item_def = ITEMS_MANAGER.items.get(inv_item['item_id'])
if not item_def or not getattr(item_def, 'repairable', False):
continue
current_durability = unique_item.get('durability', 0)
max_durability = unique_item.get('max_durability', 100)
needs_repair = current_durability < max_durability
# Check materials availability
repair_materials = getattr(item_def, 'repair_materials', [])
materials_info = []
has_materials = True
for material in repair_materials:
mat_item_def = ITEMS_MANAGER.items.get(material['item_id'])
available = inventory_counts.get(material['item_id'], 0)
required = material['quantity']
materials_info.append({
'item_id': material['item_id'],
'name': mat_item_def.name if mat_item_def else material['item_id'],
'emoji': mat_item_def.emoji if mat_item_def else '📦',
'quantity': required,
'available': available,
'has_enough': available >= required
})
if available < required:
has_materials = False
# Check tools availability
repair_tools = getattr(item_def, 'repair_tools', [])
tools_info = []
has_tools = True
for tool_req in repair_tools:
tool_id = tool_req['item_id']
durability_cost = tool_req['durability_cost']
tool_def = ITEMS_MANAGER.items.get(tool_id)
# Check if player has this tool (find one with highest durability)
tool_found = False
tool_durability = 0
tool_max_durability = 0
best_tool_unique = None
for check_item in inventory:
if check_item['item_id'] == tool_id and check_item.get('unique_item_id'):
unique = await db.get_unique_item(check_item['unique_item_id'])
if unique and unique.get('durability', 0) >= durability_cost:
if best_tool_unique is None or unique.get('durability', 0) > best_tool_unique.get('durability', 0):
best_tool_unique = unique
tool_found = True
tool_durability = unique.get('durability', 0)
tool_max_durability = unique.get('max_durability', 100)
tools_info.append({
'item_id': tool_id,
'name': tool_def.name if tool_def else tool_id,
'emoji': tool_def.emoji if tool_def else '🔧',
'durability_cost': durability_cost,
'has_tool': tool_found,
'tool_durability': tool_durability,
'tool_max_durability': tool_max_durability
})
if not tool_found:
has_tools = False
can_repair = needs_repair and has_materials and has_tools
repairable_items.append({
'inventory_id': inv_item['id'],
'unique_item_id': inv_item['unique_item_id'],
'item_id': inv_item['item_id'],
'name': item_def.name,
'emoji': item_def.emoji,
'unique_item_data': {k: int(v) if isinstance(v, (int, float)) and k != 'durability_percent' else v for k, v in unique_item.items()},
'tier': unique_item.get('tier', 1),
'current_durability': current_durability,
'max_durability': max_durability,
'durability_percent': int((current_durability / max_durability) * 100),
'repair_percentage': getattr(item_def, 'repair_percentage', 25),
'needs_repair': needs_repair,
'materials': materials_info,
'tools': tools_info,
'can_repair': can_repair,
'location': 'equipped' if inv_item.get('is_equipped') else 'inventory',
'stamina_cost': calculate_crafting_stamina_cost(unique_item.get('tier', 1), 'repair'),
'type': getattr(item_def, 'type', 'misc')
})
# Sort: repairable items first (can_repair=True), then by durability percent (lowest first), then by name
repairable_items.sort(key=lambda x: (not x['can_repair'], -x['durability_percent'], str(x['name'])))
return {'repairable_items': repairable_items}
except Exception as e:
print(f"Error getting repairable items: {e}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))
@router.get("/api/game/salvageable")
async def get_salvageable_items(current_user: dict = Depends(get_current_user)):
"""Get list of salvageable (uncraftable) items from inventory with their unique stats"""
try:
player = current_user # current_user is already the character dict
if not player:
raise HTTPException(status_code=404, detail="Player not found")
location_id = player['location_id']
location = LOCATIONS.get(location_id)
# Check if player is at a workbench
if not location or 'workbench' not in getattr(location, 'tags', []):
return {'salvageable_items': [], 'at_workbench': False}
# Get inventory
inventory = await db.get_inventory(current_user['id'])
salvageable_items = []
for inv_item in inventory:
item_id = inv_item['item_id']
item_def = ITEMS_MANAGER.items.get(item_id)
if not item_def or not getattr(item_def, 'uncraftable', False):
continue
# Get unique item details if it exists
unique_item_data = None
if inv_item.get('unique_item_id'):
unique_item = await db.get_unique_item(inv_item['unique_item_id'])
if unique_item:
current_durability = unique_item.get('durability', 0)
max_durability = unique_item.get('max_durability', 1)
durability_percent = int((current_durability / max_durability) * 100) if max_durability > 0 else 0
# Get item stats from definition merged with unique stats
item_stats = {}
if item_def.stats:
item_stats = dict(item_def.stats)
if unique_item.get('unique_stats'):
item_stats.update(unique_item.get('unique_stats'))
unique_item_data = {
'current_durability': current_durability,
'max_durability': max_durability,
'durability_percent': durability_percent,
'tier': unique_item.get('tier', 1),
'unique_stats': item_stats # Includes both base stats and unique overrides
}
# Get uncraft yield
uncraft_yield = getattr(item_def, 'uncraft_yield', [])
yield_info = []
for material in uncraft_yield:
mat_def = ITEMS_MANAGER.items.get(material['item_id'])
yield_info.append({
'item_id': material['item_id'],
'name': mat_def.name if mat_def else material['item_id'],
'emoji': mat_def.emoji if mat_def else '📦',
'quantity': material['quantity']
})
# Check tools availability for uncrafting
uncraft_tools = getattr(item_def, 'uncraft_tools', [])
tools_info = []
has_tools = True
for tool_req in uncraft_tools:
tool_id = tool_req['item_id']
durability_cost = tool_req['durability_cost']
tool_def = ITEMS_MANAGER.items.get(tool_id)
# Check if player has this tool (find one with highest durability)
tool_found = False
tool_durability = 0
best_tool_unique = None
for check_item in inventory:
if check_item['item_id'] == tool_id and check_item.get('unique_item_id'):
unique = await db.get_unique_item(check_item['unique_item_id'])
if unique and unique.get('durability', 0) >= durability_cost:
if best_tool_unique is None or unique.get('durability', 0) > best_tool_unique.get('durability', 0):
best_tool_unique = unique
tool_found = True
tool_durability = unique.get('durability', 0)
tools_info.append({
'item_id': tool_id,
'name': tool_def.name if tool_def else tool_id,
'emoji': tool_def.emoji if tool_def else '🔧',
'durability_cost': durability_cost,
'has_tool': tool_found,
'tool_durability': tool_durability
})
if not tool_found:
has_tools = False
can_uncraft = has_tools
# Build item entry
item_entry = {
'inventory_id': inv_item['id'],
'unique_item_id': inv_item.get('unique_item_id'),
'item_id': item_id,
'name': item_def.name,
'emoji': item_def.emoji,
'image_path': getattr(item_def, 'image_path', None),
'tier': getattr(item_def, 'tier', 1),
'quantity': inv_item['quantity'],
'base_yield': yield_info,
'loss_chance': getattr(item_def, 'uncraft_loss_chance', 0.3),
'stamina_cost': calculate_crafting_stamina_cost(getattr(item_def, 'tier', 1), 'uncraft'),
'can_uncraft': can_uncraft,
'uncraft_tools': tools_info,
'location': 'equipped' if inv_item.get('is_equipped') else 'inventory',
'type': getattr(item_def, 'type', 'misc')
}
# Add unique item data if available
if unique_item_data:
item_entry['unique_item_data'] = unique_item_data
item_entry['unique_stats'] = unique_item_data.get('unique_stats', {})
item_entry['current_durability'] = unique_item_data.get('current_durability')
item_entry['max_durability'] = unique_item_data.get('max_durability')
item_entry['durability_percent'] = unique_item_data.get('durability_percent')
salvageable_items.append(item_entry)
return {
'salvageable_items': salvageable_items,
'at_workbench': True
}
except Exception as e:
print(f"Error getting salvageable items: {e}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=str(e))
class LootCorpseRequest(BaseModel):
corpse_id: str
item_index: Optional[int] = None # Index of specific item to loot (None = all)