""" 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 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_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'], 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)