## Phase 4C: NPC Shop (Days 15-18) ### Task 5.1: Define Shop Inventory ✅ COMPLETE **Objective:** Create YAML for shop items **File:** `/api/app/data/shop/general_store.yaml` ```yaml shop_id: "general_store" shop_name: "General Store" shop_description: "A well-stocked general store with essential supplies." shopkeeper_name: "Merchant Guildmaster" inventory: # Weapons - item_id: "iron_sword" stock: -1 # Unlimited stock (-1) price: 50 - item_id: "oak_bow" stock: -1 price: 45 # Armor - item_id: "leather_helmet" stock: -1 price: 30 - item_id: "leather_chest" stock: -1 price: 60 # Consumables - item_id: "health_potion_small" stock: -1 price: 10 - item_id: "health_potion_medium" stock: -1 price: 30 - item_id: "mana_potion_small" stock: -1 price: 15 - item_id: "antidote" stock: -1 price: 20 ``` **Acceptance Criteria:** - Shop inventory defined in YAML - Mix of weapons, armor, consumables - Reasonable pricing - Unlimited stock for basics --- ### Task 5.2: Shop API Endpoints ✅ COMPLETE **Objective:** Create shop endpoints **File:** `/api/app/api/shop.py` ```python """ Shop API Blueprint Endpoints: - GET /api/v1/shop/inventory - Browse shop items - POST /api/v1/shop/purchase - Purchase item """ from flask import Blueprint, request, g from app.services.shop_service import ShopService from app.services.character_service import get_character_service from app.services.appwrite_service import get_appwrite_service from app.utils.response import success_response, error_response from app.utils.auth import require_auth from app.utils.logging import get_logger logger = get_logger(__file__) shop_bp = Blueprint('shop', __name__) @shop_bp.route('/inventory', methods=['GET']) @require_auth def get_shop_inventory(): """Get shop inventory.""" shop_service = ShopService() inventory = shop_service.get_shop_inventory("general_store") return success_response({ 'shop_name': "General Store", 'inventory': [ { 'item': item.to_dict(), 'price': price, 'in_stock': True } for item, price in inventory ] }) @shop_bp.route('/purchase', methods=['POST']) @require_auth def purchase_item(): """ Purchase item from shop. Request JSON: { "character_id": "char_abc", "item_id": "iron_sword", "quantity": 1 } """ data = request.get_json() character_id = data.get('character_id') item_id = data.get('item_id') quantity = data.get('quantity', 1) # Get character char_service = get_character_service() character = char_service.get_character(character_id, g.user_id) # Purchase item shop_service = ShopService() try: result = shop_service.purchase_item( character, "general_store", item_id, quantity ) # Save character char_service.update_character(character) return success_response(result) except Exception as e: return error_response(str(e), 400) ``` **Also create `/api/app/services/shop_service.py`:** ```python """ Shop Service Manages NPC shop inventory and purchases. """ import yaml from typing import List, Tuple from app.models.items import Item from app.models.character import Character from app.services.item_loader import ItemLoader from app.utils.logging import get_logger logger = get_logger(__file__) class ShopService: """Service for NPC shops.""" def __init__(self): self.item_loader = ItemLoader() self.shops = self._load_shops() def _load_shops(self) -> dict: """Load all shop data from YAML.""" shops = {} with open('app/data/shop/general_store.yaml', 'r') as f: shop_data = yaml.safe_load(f) shops[shop_data['shop_id']] = shop_data return shops def get_shop_inventory(self, shop_id: str) -> List[Tuple[Item, int]]: """ Get shop inventory. Returns: List of (Item, price) tuples """ shop = self.shops.get(shop_id) if not shop: return [] inventory = [] for item_data in shop['inventory']: item = self.item_loader.get_item(item_data['item_id']) price = item_data['price'] inventory.append((item, price)) return inventory def purchase_item( self, character: Character, shop_id: str, item_id: str, quantity: int = 1 ) -> dict: """ Purchase item from shop. Args: character: Character instance shop_id: Shop ID item_id: Item to purchase quantity: Quantity to buy Returns: Purchase result dict Raises: ValueError: If insufficient gold or item not found """ shop = self.shops.get(shop_id) if not shop: raise ValueError("Shop not found") # Find item in shop inventory item_data = next( (i for i in shop['inventory'] if i['item_id'] == item_id), None ) if not item_data: raise ValueError("Item not available in shop") price = item_data['price'] * quantity # Check if character has enough gold if character.gold < price: raise ValueError(f"Not enough gold. Need {price}, have {character.gold}") # Deduct gold character.gold -= price # Add items to inventory for _ in range(quantity): if item_id not in character.inventory_item_ids: character.inventory_item_ids.append(item_id) else: # Item already exists, increment stack (if stackable) # For now, just add multiple entries character.inventory_item_ids.append(item_id) logger.info(f"Character {character.character_id} purchased {quantity}x {item_id} for {price} gold") return { 'item_purchased': item_id, 'quantity': quantity, 'total_cost': price, 'gold_remaining': character.gold } ``` **Acceptance Criteria:** - Shop inventory endpoint works - Purchase endpoint validates gold - Items added to inventory - Gold deducted - Transactions logged --- ### Task 5.3: Shop UI ✅ COMPLETE **Objective:** Shop browse and purchase interface **File:** `/public_web/templates/shop/index.html` ```html {% extends "base.html" %} {% block title %}Shop - Code of Conquest{% endblock %} {% block content %}
Shopkeeper: {{ shopkeeper_name }}
Your Gold: {{ character.gold }}
{{ item_entry.item.description }}