chore: save progress before layout changes

This commit is contained in:
Joan
2026-02-10 10:48:53 +01:00
parent 70dc35b4b2
commit bba5d1d9dd
48 changed files with 1535 additions and 690 deletions

View File

@@ -375,6 +375,7 @@ async def craft_item(request: CraftItemRequest, current_user: dict = Depends(get
class UncraftItemRequest(BaseModel):
inventory_id: int
quantity: int = 1
@router.post("/api/game/uncraft_item")
@@ -402,6 +403,14 @@ async def uncraft_item(request: UncraftItemRequest, current_user: dict = Depends
if not inv_item:
raise HTTPException(status_code=404, detail="Item not found in inventory")
# Check quantity
if request.quantity <= 0:
raise HTTPException(status_code=400, detail="Quantity must be greater than 0")
current_quantity = inv_item.get('quantity', 1)
if request.quantity > current_quantity:
raise HTTPException(status_code=400, detail=f"Not enough items. Have {current_quantity}, requested {request.quantity}")
# Get item definition
item_def = ITEMS_MANAGER.items.get(inv_item['item_id'])
@@ -415,29 +424,50 @@ async def uncraft_item(request: UncraftItemRequest, current_user: dict = Depends
if not uncraft_yield:
raise HTTPException(status_code=400, detail="No uncraft recipe found")
# Check tools requirement
# Check tools requirement (once per operation? or per item?)
# Usually tools are checked once for the operation, but durability cost might be per item.
# Logic above for crafting consumes tool durability for the batch?
# In craft_item above, it loops through craft_tools but seemingly only once?
# Wait, craft_item does NOT loop for quantity because craft_item only crafts 1 at a time (request has no quantity).
# For uncrafting multiple, we should multiply tool cost.
uncraft_tools = getattr(item_def, 'uncraft_tools', [])
tools_consumed = []
if uncraft_tools:
success, error_msg, tools_consumed = await consume_tool_durability(current_user['id'], uncraft_tools, inventory)
# Scale tool cost by quantity
scaled_uncraft_tools = []
for tool_req in uncraft_tools:
scaled_req = tool_req.copy()
scaled_req['durability_cost'] = tool_req['durability_cost'] * request.quantity
scaled_uncraft_tools.append(scaled_req)
success, error_msg, tools_consumed = await consume_tool_durability(current_user['id'], scaled_uncraft_tools, inventory)
if not success:
raise HTTPException(status_code=400, detail=error_msg)
else:
tools_consumed = []
# Calculate stamina cost
stamina_cost = calculate_crafting_stamina_cost(getattr(item_def, 'tier', 1), 'uncraft')
base_stamina_cost = calculate_crafting_stamina_cost(getattr(item_def, 'tier', 1), 'uncraft')
total_stamina_cost = base_stamina_cost * request.quantity
# Check stamina
if player['stamina'] < stamina_cost:
raise HTTPException(status_code=400, detail=f"Not enough stamina. Need {stamina_cost}, have {player['stamina']}")
if player['stamina'] < total_stamina_cost:
raise HTTPException(status_code=400, detail=f"Not enough stamina. Need {total_stamina_cost}, have {player['stamina']}")
# Deduct stamina
new_stamina = max(0, player['stamina'] - stamina_cost)
new_stamina = max(0, player['stamina'] - total_stamina_cost)
await db.update_player_stamina(current_user['id'], new_stamina)
# Remove the item from inventory
# Use remove_inventory_row since we have the inventory ID
await db.remove_inventory_row(inv_item['id'])
# Update inventory item
if request.quantity == current_quantity:
# Remove the item row entirely
await db.remove_inventory_row(inv_item['id'])
else:
# Update quantity
await db.update_inventory_item(
inv_item['id'],
quantity=current_quantity - request.quantity
)
# Calculate durability ratio for yield reduction
durability_ratio = 1.0 # Default: full yield
@@ -449,96 +479,120 @@ async def uncraft_item(request: UncraftItemRequest, current_user: dict = Depends
if max_durability > 0:
durability_ratio = current_durability / max_durability
# Re-fetch inventory to get updated capacity after removing the item
# Re-fetch inventory to get updated capacity
inventory = await db.get_inventory(current_user['id'])
current_weight, max_weight, current_volume, max_volume = await calculate_player_capacity(inventory, ITEMS_MANAGER)
# Calculate materials with loss chance and durability reduction
# Calculate materials
import random
loss_chance = getattr(item_def, 'uncraft_loss_chance', 0.3)
yield_info = {
'base_yield': uncraft_yield,
'loss_chance': loss_chance,
'stamina_cost': calculate_crafting_stamina_cost(getattr(item_def, 'tier', 1), 'uncraft')
}
materials_yielded_dict = {}
materials_lost_dict = {}
materials_dropped_dict = {}
# Loop for each item being uncrafted to calculate yield fairly
for _ in range(request.quantity):
for material in uncraft_yield:
# Apply durability reduction first
base_quantity = material['quantity']
# Calculate adjusted quantity based on durability
adjusted_quantity = int(round(base_quantity * durability_ratio))
mat_def = ITEMS_MANAGER.items.get(material['item_id'])
mat_name = mat_def.name if mat_def else material['item_id']
loss_key = (material['item_id'], mat_name)
# If durability is too low (< 10%), yield nothing for this material
if durability_ratio < 0.1 or adjusted_quantity <= 0:
if loss_key not in materials_lost_dict:
materials_lost_dict[loss_key] = 0
materials_lost_dict[loss_key] += base_quantity
continue
# Roll for loss chance
if random.random() < loss_chance:
# Lost this material
if loss_key not in materials_lost_dict:
materials_lost_dict[loss_key] = 0
materials_lost_dict[loss_key] += adjusted_quantity
else:
# Check if it fits in inventory (incremental check?)
# For simplicity, check per unit or accumulate and check at end.
# Checking per unit is safer but slower.
# Since we are modifying inventory in loop (potentially), we should be careful.
# Actually, we should accumulate yield then add to inventory at end to optimize DB calls?
# But we need to check capacity.
# Let's accumulate pending yield.
yield_key = (material['item_id'], mat_name, mat_def.emoji if mat_def else '📦', mat_def)
if yield_key not in materials_yielded_dict:
materials_yielded_dict[yield_key] = 0
materials_yielded_dict[yield_key] += adjusted_quantity
# Now process the accumulated yield
materials_yielded = []
materials_lost = []
materials_dropped = []
for material in uncraft_yield:
# Apply durability reduction first
base_quantity = material['quantity']
# Convert lost dict to list
for (item_id, name), qty in materials_lost_dict.items():
materials_lost.append({
'item_id': item_id,
'name': name,
'quantity': qty,
'reason': 'lost_or_low_durability'
})
# Calculate adjusted quantity based on durability
# Use round() to ensure minimum yield of 1 for high durability items (e.g. 90% of 1 = 0.9 -> 1)
adjusted_quantity = int(round(base_quantity * durability_ratio))
# Process yield
for (item_id, name, emoji, mat_def), qty in materials_yielded_dict.items():
mat_weight = getattr(mat_def, 'weight', 0) * qty
mat_volume = getattr(mat_def, 'volume', 0) * qty
mat_def = ITEMS_MANAGER.items.get(material['item_id'])
# Simple check against capacity (assuming current_weight was just updated from DB)
# Note: we might fill up mid-loop. ideally we add one by one or check total.
# Let's check total.
# If durability is too low (< 10%), yield nothing for this material
if durability_ratio < 0.1 or adjusted_quantity <= 0:
materials_lost.append({
'item_id': material['item_id'],
'name': mat_def.name if mat_def else material['item_id'],
'quantity': base_quantity,
'reason': 'durability_too_low'
})
continue
# Roll for each material separately with loss chance
if random.random() < loss_chance:
# Lost this material
materials_lost.append({
'item_id': material['item_id'],
'name': mat_def.name if mat_def else material['item_id'],
'quantity': adjusted_quantity,
'reason': 'random_loss'
if current_weight + mat_weight <= max_weight and current_volume + mat_volume <= max_volume:
# Fits
await db.add_item_to_inventory(
player_id=current_user['id'],
item_id=item_id,
quantity=qty
)
current_weight += mat_weight
current_volume += mat_volume
materials_yielded.append({
'item_id': item_id,
'name': name,
'emoji': emoji,
'quantity': qty
})
else:
# Check if it fits in inventory
mat_weight = getattr(mat_def, 'weight', 0) * adjusted_quantity
mat_volume = getattr(mat_def, 'volume', 0) * adjusted_quantity
# Drop
await db.drop_item_to_world(
item_id=item_id,
quantity=qty,
location_id=player['location_id']
)
if current_weight + mat_weight <= max_weight and current_volume + mat_volume <= max_volume:
# Fits in inventory
await db.add_item_to_inventory(
player_id=current_user['id'],
item_id=material['item_id'],
quantity=adjusted_quantity
)
# Update current capacity tracking
current_weight += mat_weight
current_volume += mat_volume
materials_yielded.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': adjusted_quantity
})
else:
# Inventory full - drop to ground
await db.drop_item_to_world(
item_id=material['item_id'],
quantity=adjusted_quantity,
location_id=player['location_id']
)
materials_dropped.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': adjusted_quantity
})
materials_dropped.append({
'item_id': item_id,
'name': name,
'emoji': emoji,
'quantity': qty
})
message = f"Uncrafted {item_def.name}!"
message = f"Uncrafted {request.quantity}x {item_def.name}!"
if durability_ratio < 1.0:
message += f" (Item condition reduced yield by {int((1 - durability_ratio) * 100)}%)"
message += f" (Condition reduced yield)"
if materials_lost:
message += f" Lost {len(materials_lost)} material type(s)."
message += f" Lost materials."
if materials_dropped:
message += f" Inventory full! Dropped {len(materials_dropped)} item(s) to the ground."
message += f" Inventory full! Dropped items."
return {
'success': True,
@@ -550,7 +604,7 @@ async def uncraft_item(request: UncraftItemRequest, current_user: dict = Depends
'tools_consumed': tools_consumed,
'loss_chance': loss_chance,
'durability_ratio': round(durability_ratio, 2),
'stamina_cost': stamina_cost,
'stamina_cost': total_stamina_cost,
'new_stamina': new_stamina
}

View File

@@ -783,7 +783,8 @@ async def get_current_location(request: Request, current_user: dict = Depends(ge
"name": f"{get_locale_string(npc_def.name, locale) if npc_def else corpse['npc_id']} Corpse",
"emoji": "💀",
"loot_count": len(loot),
"timestamp": corpse['death_timestamp']
"timestamp": corpse['death_timestamp'],
"image_path": npc_def.image_path if npc_def else None
})
for corpse in player_corpses:
@@ -957,7 +958,8 @@ async def move(
response = {
"success": True,
"message": message,
"new_location_id": new_location_id
"new_location_id": new_location_id,
"new_location_name": new_location.name if new_location else "Unknown" # Add location name for frontend
}
# Add encounter info if triggered
@@ -1469,10 +1471,21 @@ async def drop_item(
# Get inventory item by item_id (string), not database id
inventory = await db.get_inventory(player_id)
inv_item = None
for item in inventory:
if item['item_id'] == item_id:
inv_item = item
break
# If inventory_id is provided, use it to find precise item
inventory_id = drop_req.get('inventory_id')
if inventory_id:
for item in inventory:
if item['id'] == inventory_id:
inv_item = item
break
else:
# Fallback to legacy behavior (first matching item_id)
for item in inventory:
if item['item_id'] == item_id:
inv_item = item
break
if not inv_item:
raise HTTPException(status_code=404, detail="Item not found in inventory")

View File

@@ -62,7 +62,22 @@ async def get_corpse_details(
# Get player's inventory to check available tools
inventory = await db.get_inventory(player['id'])
available_tools = set([item['item_id'] for item in inventory])
# Map item_id to max durability found in inventory for that item
tools_durability = {}
for item in inventory:
item_id = item['item_id']
durability = 0
# Helper to get actual durability from unique item data
if item.get('unique_item_id'):
unique_item = await db.get_unique_item(item['unique_item_id'])
if unique_item:
durability = unique_item.get('durability', 0)
if item_id not in tools_durability or durability > tools_durability[item_id]:
tools_durability[item_id] = durability
available_tools = set(tools_durability.keys())
if corpse_type == 'npc':
# Get NPC corpse
@@ -80,9 +95,16 @@ async def get_corpse_details(
loot_items = []
for idx, loot_item in enumerate(loot_remaining):
required_tool = loot_item.get('required_tool')
durability_cost = loot_item.get('tool_durability_cost', 5)
item_def = ITEMS_MANAGER.get_item(loot_item['item_id'])
has_tool = required_tool is None or required_tool in available_tools
has_tool = True
if required_tool:
if required_tool not in tools_durability:
has_tool = False
elif tools_durability[required_tool] < durability_cost:
has_tool = False
tool_def = ITEMS_MANAGER.get_item(required_tool) if required_tool else None
loot_items.append({