chore: save progress before layout changes
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user