NPC shop implimented
This commit is contained in:
@@ -119,7 +119,8 @@ def _build_character_from_api(char_data: dict) -> dict:
|
||||
'equipped': char_data.get('equipped', {}),
|
||||
'inventory': char_data.get('inventory', []),
|
||||
'gold': char_data.get('gold', 0),
|
||||
'experience': char_data.get('experience', 0)
|
||||
'experience': char_data.get('experience', 0),
|
||||
'unlocked_skills': char_data.get('unlocked_skills', [])
|
||||
}
|
||||
|
||||
|
||||
@@ -1373,3 +1374,215 @@ def talk_to_npc(session_id: str, npc_id: str):
|
||||
except APIError as e:
|
||||
logger.error("failed_to_talk_to_npc", session_id=session_id, npc_id=npc_id, error=str(e))
|
||||
return f'<div class="error">Failed to talk to NPC: {e}</div>', 500
|
||||
|
||||
|
||||
# ===== Shop Routes =====
|
||||
|
||||
@game_bp.route('/session/<session_id>/shop-modal')
|
||||
@require_auth
|
||||
def shop_modal(session_id: str):
|
||||
"""
|
||||
Get shop modal for browsing and purchasing items.
|
||||
|
||||
Supports filtering by item type via ?filter= parameter.
|
||||
Uses the general_store shop.
|
||||
"""
|
||||
client = get_api_client()
|
||||
filter_type = request.args.get('filter', 'all')
|
||||
message = request.args.get('message', '')
|
||||
error = request.args.get('error', '')
|
||||
|
||||
try:
|
||||
# Get session to find character
|
||||
session_response = client.get(f'/api/v1/sessions/{session_id}')
|
||||
session_data = session_response.get('result', {})
|
||||
character_id = session_data.get('character_id')
|
||||
|
||||
gold = 0
|
||||
inventory = []
|
||||
shop = {}
|
||||
|
||||
if character_id:
|
||||
try:
|
||||
# Get shop inventory with character context (for affordability)
|
||||
shop_response = client.get(
|
||||
f'/api/v1/shop/general_store/inventory',
|
||||
params={'character_id': character_id}
|
||||
)
|
||||
shop_data = shop_response.get('result', {})
|
||||
shop = shop_data.get('shop', {})
|
||||
inventory = shop_data.get('inventory', [])
|
||||
|
||||
# Get character gold
|
||||
char_data = shop_data.get('character', {})
|
||||
gold = char_data.get('gold', 0)
|
||||
|
||||
except (APINotFoundError, APIError) as e:
|
||||
logger.warning("failed_to_load_shop", character_id=character_id, error=str(e))
|
||||
error = "Failed to load shop inventory"
|
||||
|
||||
# Filter inventory by type if specified
|
||||
if filter_type != 'all':
|
||||
inventory = [
|
||||
entry for entry in inventory
|
||||
if entry.get('item', {}).get('item_type') == filter_type
|
||||
]
|
||||
|
||||
return render_template(
|
||||
'game/partials/shop_modal.html',
|
||||
session_id=session_id,
|
||||
shop=shop,
|
||||
inventory=inventory,
|
||||
gold=gold,
|
||||
filter=filter_type,
|
||||
message=message,
|
||||
error=error
|
||||
)
|
||||
|
||||
except APIError as e:
|
||||
logger.error("failed_to_load_shop_modal", session_id=session_id, error=str(e))
|
||||
return f'''
|
||||
<div class="modal-overlay" onclick="closeModal()">
|
||||
<div class="modal-content shop-modal">
|
||||
<div class="modal-header">
|
||||
<h2>Shop</h2>
|
||||
<button class="modal-close" onclick="closeModal()">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="shop-empty">Failed to load shop: {e}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
'''
|
||||
|
||||
|
||||
@game_bp.route('/session/<session_id>/shop/purchase', methods=['POST'])
|
||||
@require_auth
|
||||
def shop_purchase(session_id: str):
|
||||
"""
|
||||
Purchase an item from the shop.
|
||||
|
||||
HTMX endpoint - returns updated shop modal.
|
||||
"""
|
||||
client = get_api_client()
|
||||
item_id = request.form.get('item_id')
|
||||
quantity = int(request.form.get('quantity', 1))
|
||||
|
||||
try:
|
||||
# Get session to find character
|
||||
session_response = client.get(f'/api/v1/sessions/{session_id}')
|
||||
session_data = session_response.get('result', {})
|
||||
character_id = session_data.get('character_id')
|
||||
|
||||
if not character_id:
|
||||
return shop_modal_with_error(session_id, "No character found for this session")
|
||||
|
||||
if not item_id:
|
||||
return shop_modal_with_error(session_id, "No item specified")
|
||||
|
||||
# Attempt purchase
|
||||
purchase_data = {
|
||||
'character_id': character_id,
|
||||
'item_id': item_id,
|
||||
'quantity': quantity,
|
||||
'session_id': session_id
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/shop/general_store/purchase', json=purchase_data)
|
||||
result = response.get('result', {})
|
||||
|
||||
# Get item name for message
|
||||
purchase_info = result.get('purchase', {})
|
||||
item_name = purchase_info.get('item_id', item_id)
|
||||
total_cost = purchase_info.get('total_cost', 0)
|
||||
|
||||
message = f"Purchased {item_name} for {total_cost} gold!"
|
||||
|
||||
logger.info(
|
||||
"shop_purchase_success",
|
||||
session_id=session_id,
|
||||
character_id=character_id,
|
||||
item_id=item_id,
|
||||
quantity=quantity,
|
||||
total_cost=total_cost
|
||||
)
|
||||
|
||||
# Re-render shop modal with success message
|
||||
return redirect(url_for('game.shop_modal', session_id=session_id, message=message))
|
||||
|
||||
except APIError as e:
|
||||
logger.error(
|
||||
"shop_purchase_failed",
|
||||
session_id=session_id,
|
||||
item_id=item_id,
|
||||
error=str(e)
|
||||
)
|
||||
error_msg = str(e.message) if hasattr(e, 'message') else str(e)
|
||||
return redirect(url_for('game.shop_modal', session_id=session_id, error=error_msg))
|
||||
|
||||
|
||||
@game_bp.route('/session/<session_id>/shop/sell', methods=['POST'])
|
||||
@require_auth
|
||||
def shop_sell(session_id: str):
|
||||
"""
|
||||
Sell an item to the shop.
|
||||
|
||||
HTMX endpoint - returns updated shop modal.
|
||||
"""
|
||||
client = get_api_client()
|
||||
item_instance_id = request.form.get('item_instance_id')
|
||||
quantity = int(request.form.get('quantity', 1))
|
||||
|
||||
try:
|
||||
# Get session to find character
|
||||
session_response = client.get(f'/api/v1/sessions/{session_id}')
|
||||
session_data = session_response.get('result', {})
|
||||
character_id = session_data.get('character_id')
|
||||
|
||||
if not character_id:
|
||||
return redirect(url_for('game.shop_modal', session_id=session_id, error="No character found"))
|
||||
|
||||
if not item_instance_id:
|
||||
return redirect(url_for('game.shop_modal', session_id=session_id, error="No item specified"))
|
||||
|
||||
# Attempt sale
|
||||
sale_data = {
|
||||
'character_id': character_id,
|
||||
'item_instance_id': item_instance_id,
|
||||
'quantity': quantity,
|
||||
'session_id': session_id
|
||||
}
|
||||
|
||||
response = client.post('/api/v1/shop/general_store/sell', json=sale_data)
|
||||
result = response.get('result', {})
|
||||
|
||||
sale_info = result.get('sale', {})
|
||||
item_name = sale_info.get('item_name', 'Item')
|
||||
total_earned = sale_info.get('total_earned', 0)
|
||||
|
||||
message = f"Sold {item_name} for {total_earned} gold!"
|
||||
|
||||
logger.info(
|
||||
"shop_sell_success",
|
||||
session_id=session_id,
|
||||
character_id=character_id,
|
||||
item_instance_id=item_instance_id,
|
||||
total_earned=total_earned
|
||||
)
|
||||
|
||||
return redirect(url_for('game.shop_modal', session_id=session_id, message=message))
|
||||
|
||||
except APIError as e:
|
||||
logger.error(
|
||||
"shop_sell_failed",
|
||||
session_id=session_id,
|
||||
item_instance_id=item_instance_id,
|
||||
error=str(e)
|
||||
)
|
||||
error_msg = str(e.message) if hasattr(e, 'message') else str(e)
|
||||
return redirect(url_for('game.shop_modal', session_id=session_id, error=error_msg))
|
||||
|
||||
|
||||
def shop_modal_with_error(session_id: str, error: str):
|
||||
"""Helper to render shop modal with an error message."""
|
||||
return redirect(url_for('game.shop_modal', session_id=session_id, error=error))
|
||||
|
||||
Reference in New Issue
Block a user