diff --git a/api/app/api/npcs.py b/api/app/api/npcs.py index b4834b9..a8631d9 100644 --- a/api/app/api/npcs.py +++ b/api/app/api/npcs.py @@ -281,6 +281,7 @@ def get_npcs_at_location(location_id: str): "role": npc.role, "appearance": npc.appearance.brief, "tags": npc.tags, + "image_url": npc.image_url, }) return success_response({ diff --git a/api/app/data/npcs/crossville/npc_blacksmith_hilda.yaml b/api/app/data/npcs/crossville/npc_blacksmith_hilda.yaml index 1aa8d9e..ac21a9f 100644 --- a/api/app/data/npcs/crossville/npc_blacksmith_hilda.yaml +++ b/api/app/data/npcs/crossville/npc_blacksmith_hilda.yaml @@ -3,6 +3,7 @@ npc_id: npc_blacksmith_hilda name: Hilda Ironforge role: blacksmith location_id: crossville_village +image_url: /static/images/npcs/crossville/blacksmith_hilda.png personality: traits: diff --git a/api/app/data/npcs/crossville/npc_grom_ironbeard.yaml b/api/app/data/npcs/crossville/npc_grom_ironbeard.yaml index 0635d23..fd06c16 100644 --- a/api/app/data/npcs/crossville/npc_grom_ironbeard.yaml +++ b/api/app/data/npcs/crossville/npc_grom_ironbeard.yaml @@ -3,6 +3,7 @@ npc_id: npc_grom_ironbeard name: Grom Ironbeard role: bartender location_id: crossville_tavern +image_url: /static/images/npcs/crossville/grom_ironbeard.png personality: traits: diff --git a/api/app/data/npcs/crossville/npc_mayor_aldric.yaml b/api/app/data/npcs/crossville/npc_mayor_aldric.yaml index c71a2ec..fdaf007 100644 --- a/api/app/data/npcs/crossville/npc_mayor_aldric.yaml +++ b/api/app/data/npcs/crossville/npc_mayor_aldric.yaml @@ -3,6 +3,7 @@ npc_id: npc_mayor_aldric name: Mayor Aldric Thornwood role: mayor location_id: crossville_village +image_url: /static/images/npcs/crossville/mayor_aldric.png personality: traits: diff --git a/api/app/data/npcs/crossville/npc_mira_swiftfoot.yaml b/api/app/data/npcs/crossville/npc_mira_swiftfoot.yaml index 0163058..79ae8cd 100644 --- a/api/app/data/npcs/crossville/npc_mira_swiftfoot.yaml +++ b/api/app/data/npcs/crossville/npc_mira_swiftfoot.yaml @@ -3,6 +3,7 @@ npc_id: npc_mira_swiftfoot name: Mira Swiftfoot role: rogue location_id: crossville_tavern +image_url: /static/images/npcs/crossville/mira_swiftfoot.png personality: traits: diff --git a/api/app/models/npc.py b/api/app/models/npc.py index 05510ce..2ea3e2e 100644 --- a/api/app/models/npc.py +++ b/api/app/models/npc.py @@ -296,6 +296,7 @@ class NPC: location_id: str personality: NPCPersonality appearance: NPCAppearance + image_url: Optional[str] = None knowledge: Optional[NPCKnowledge] = None relationships: List[NPCRelationship] = field(default_factory=list) inventory_for_sale: List[NPCInventoryItem] = field(default_factory=list) @@ -316,6 +317,7 @@ class NPC: "name": self.name, "role": self.role, "location_id": self.location_id, + "image_url": self.image_url, "personality": self.personality.to_dict(), "appearance": self.appearance.to_dict(), "knowledge": self.knowledge.to_dict() if self.knowledge else None, @@ -400,6 +402,7 @@ class NPC: name=data["name"], role=data["role"], location_id=data["location_id"], + image_url=data.get("image_url"), personality=personality, appearance=appearance, knowledge=knowledge, diff --git a/api/app/services/npc_loader.py b/api/app/services/npc_loader.py index 0b1054a..b0cf4b7 100644 --- a/api/app/services/npc_loader.py +++ b/api/app/services/npc_loader.py @@ -212,6 +212,7 @@ class NPCLoader: name=data["name"], role=data["role"], location_id=data["location_id"], + image_url=data.get("image_url"), personality=personality, appearance=appearance, knowledge=knowledge, diff --git a/api/docs/API_REFERENCE.md b/api/docs/API_REFERENCE.md index 6221002..92729ae 100644 --- a/api/docs/API_REFERENCE.md +++ b/api/docs/API_REFERENCE.md @@ -740,8 +740,75 @@ Set-Cookie: coc_session=; HttpOnly; Secure; SameSite=Lax; Max-Age --- +## Health + +### Health Check + +| | | +|---|---| +| **Endpoint** | `GET /api/v1/health` | +| **Description** | Check API service status and version | +| **Auth Required** | No | + +**Response (200 OK):** +```json +{ + "app": "Code of Conquest", + "version": "0.1.0", + "status": 200, + "timestamp": "2025-11-16T10:30:00Z", + "result": { + "status": "ok", + "service": "Code of Conquest API", + "version": "0.1.0" + }, + "error": null, + "meta": {} +} +``` + +--- + ## Sessions +### List Sessions + +| | | +|---|---| +| **Endpoint** | `GET /api/v1/sessions` | +| **Description** | Get all active sessions for current user | +| **Auth Required** | Yes | + +**Response (200 OK):** +```json +{ + "app": "Code of Conquest", + "version": "0.1.0", + "status": 200, + "timestamp": "2025-11-16T10:30:00Z", + "result": [ + { + "session_id": "sess_789", + "character_id": "char_456", + "turn_number": 5, + "status": "active", + "created_at": "2025-11-16T10:00:00Z", + "last_activity": "2025-11-16T10:25:00Z", + "game_state": { + "current_location": "crossville_village", + "location_type": "town" + } + } + ] +} +``` + +**Error Responses:** +- `401` - Not authenticated +- `500` - Internal server error + +--- + ### Create Session | | | @@ -1066,17 +1133,19 @@ The Travel API enables location-based world exploration. Locations are defined i "timestamp": "2025-11-25T10:30:00Z", "result": { "current_location": "crossville_village", - "available_locations": [ + "destinations": [ { "location_id": "crossville_tavern", "name": "The Rusty Anchor Tavern", "location_type": "tavern", + "region_id": "crossville", "description": "A cozy tavern where travelers share tales..." }, { "location_id": "crossville_forest", "name": "Whispering Woods", "location_type": "wilderness", + "region_id": "crossville", "description": "A dense forest on the outskirts of town..." } ] @@ -1084,6 +1153,11 @@ The Travel API enables location-based world exploration. Locations are defined i } ``` +**Error Responses:** +- `400` - Missing session_id parameter +- `404` - Session or character not found +- `500` - Internal server error + ### Travel to Location | | | @@ -1100,20 +1174,40 @@ The Travel API enables location-based world exploration. Locations are defined i } ``` -**Response (202 Accepted):** +**Response (200 OK):** ```json { "app": "Code of Conquest", "version": "0.1.0", - "status": 202, + "status": 200, "timestamp": "2025-11-25T10:30:00Z", "result": { - "job_id": "ai_travel_abc123", - "status": "queued", - "message": "Traveling to The Rusty Anchor Tavern...", - "destination": { + "location": { "location_id": "crossville_tavern", - "name": "The Rusty Anchor Tavern" + "name": "The Rusty Anchor Tavern", + "location_type": "tavern", + "region_id": "crossville", + "description": "A cozy tavern where travelers share tales...", + "lore": "Founded decades ago by a retired adventurer...", + "ambient_description": "The scent of ale and roasting meat fills the air...", + "available_quests": ["quest_missing_trader"], + "npc_ids": ["npc_grom_ironbeard"], + "discoverable_locations": ["crossville_forest"], + "is_starting_location": false, + "tags": ["tavern", "social", "merchant", "safe"] + }, + "npcs_present": [ + { + "npc_id": "npc_grom_ironbeard", + "name": "Grom Ironbeard", + "role": "bartender", + "appearance": "Stout dwarf with a braided grey beard" + } + ], + "game_state": { + "current_location": "crossville_tavern", + "location_type": "tavern", + "active_quests": [] } } } @@ -1121,7 +1215,9 @@ The Travel API enables location-based world exploration. Locations are defined i **Error Responses:** - `400` - Location not discovered +- `403` - Location not discovered - `404` - Session or location not found +- `500` - Internal server error ### Get Location Details @@ -1139,22 +1235,36 @@ The Travel API enables location-based world exploration. Locations are defined i "status": 200, "timestamp": "2025-11-25T10:30:00Z", "result": { - "location_id": "crossville_village", - "name": "Crossville Village", - "location_type": "town", - "region_id": "crossville", - "description": "A modest farming village built around a central square...", - "lore": "Founded two centuries ago by settlers from the eastern kingdoms...", - "ambient_description": "The village square bustles with activity...", - "available_quests": ["quest_mayors_request"], - "npc_ids": ["npc_mayor_aldric", "npc_blacksmith_hilda"], - "discoverable_locations": ["crossville_tavern", "crossville_forest"], - "is_starting_location": true, - "tags": ["town", "social", "merchant", "safe"] + "location": { + "location_id": "crossville_village", + "name": "Crossville Village", + "location_type": "town", + "region_id": "crossville", + "description": "A modest farming village built around a central square...", + "lore": "Founded two centuries ago by settlers from the eastern kingdoms...", + "ambient_description": "The village square bustles with activity...", + "available_quests": ["quest_mayors_request"], + "npc_ids": ["npc_mayor_aldric", "npc_blacksmith_hilda"], + "discoverable_locations": ["crossville_tavern", "crossville_forest"], + "is_starting_location": true, + "tags": ["town", "social", "merchant", "safe"] + }, + "npcs_present": [ + { + "npc_id": "npc_mayor_aldric", + "name": "Mayor Aldric", + "role": "village mayor", + "appearance": "A portly man in fine robes" + } + ] } } ``` +**Error Responses:** +- `404` - Location not found +- `500` - Internal server error + ### Get Current Location | | | @@ -1225,6 +1335,7 @@ The NPC API enables interaction with persistent NPCs. NPCs have personalities, k "name": "Grom Ironbeard", "role": "bartender", "location_id": "crossville_tavern", + "image_url": "/static/images/npcs/crossville/grom_ironbeard.png", "personality": { "traits": ["gruff", "observant", "secretly kind"], "speech_style": "Uses dwarven expressions, speaks in short sentences", @@ -1304,7 +1415,7 @@ The NPC API enables interaction with persistent NPCs. NPCs have personalities, k "dialogue": "*polishes mug thoughtfully* \"Ah, another adventurer. What'll it be?\"", "tokens_used": 728, "npc_name": "Grom Ironbeard", - "npc_id": "npc_grom_001", + "npc_id": "npc_grom_ironbeard", "character_name": "Thorin", "player_line": "greeting", "conversation_history": [ @@ -1327,6 +1438,11 @@ The NPC API enables interaction with persistent NPCs. NPCs have personalities, k - The AI receives the last 3 exchanges as context for continuity - The job result includes prior `conversation_history` for UI display +**Bidirectional Dialogue:** +- If `player_response` is provided in the request, it overrides `topic` and enables full bidirectional conversation +- The player's response is stored in the conversation history +- The NPC's reply takes into account the full conversation context + **Error Responses:** - `400` - NPC not at current location - `404` - NPC or session not found @@ -1354,14 +1470,16 @@ The NPC API enables interaction with persistent NPCs. NPCs have personalities, k "name": "Grom Ironbeard", "role": "bartender", "appearance": "Stout dwarf with a braided grey beard", - "tags": ["merchant", "quest_giver"] + "tags": ["merchant", "quest_giver"], + "image_url": "/static/images/npcs/crossville/grom_ironbeard.png" }, { "npc_id": "npc_mira_swiftfoot", "name": "Mira Swiftfoot", "role": "traveling rogue", "appearance": "Lithe half-elf with sharp eyes", - "tags": ["information", "secret_keeper"] + "tags": ["information", "secret_keeper"], + "image_url": "/static/images/npcs/crossville/mira_swiftfoot.png" } ] } diff --git a/api/docs/DATA_MODELS.md b/api/docs/DATA_MODELS.md index 49958b5..5eed184 100644 --- a/api/docs/DATA_MODELS.md +++ b/api/docs/DATA_MODELS.md @@ -225,6 +225,7 @@ Main NPC definition with personality and dialogue data for AI generation. | `location_id` | str | ID of location where NPC resides | | `personality` | NPCPersonality | Personality traits and speech patterns | | `appearance` | NPCAppearance | Physical description | +| `image_url` | Optional[str] | URL path to NPC portrait image (e.g., "/static/images/npcs/crossville/grom_ironbeard.png") | | `knowledge` | Optional[NPCKnowledge] | What the NPC knows (public and secret) | | `relationships` | List[NPCRelationship] | How NPC feels about other NPCs | | `inventory_for_sale` | List[NPCInventoryItem] | Items NPC sells (if merchant) | @@ -346,6 +347,7 @@ npc_id: "npc_grom_001" name: "Grom Ironbeard" role: "bartender" location_id: "crossville_tavern" +image_url: "/static/images/npcs/crossville/grom_ironbeard.png" personality: traits: - "gruff" diff --git a/public_web/app/views/game_views.py b/public_web/app/views/game_views.py index 423d220..8bd607a 100644 --- a/public_web/app/views/game_views.py +++ b/public_web/app/views/game_views.py @@ -704,7 +704,8 @@ def npc_chat_modal(session_id: str, npc_id: str): 'name': npc_data.get('name'), 'role': npc_data.get('role'), 'appearance': npc_data.get('appearance', {}).get('brief', ''), - 'tags': npc_data.get('tags', []) + 'tags': npc_data.get('tags', []), + 'image_url': npc_data.get('image_url') } # Get relationship info diff --git a/public_web/static/images/npcs/crossville/blacksmith_hilda.png b/public_web/static/images/npcs/crossville/blacksmith_hilda.png new file mode 100644 index 0000000..0ce881c Binary files /dev/null and b/public_web/static/images/npcs/crossville/blacksmith_hilda.png differ diff --git a/public_web/static/images/npcs/crossville/grom_ironbeard.png b/public_web/static/images/npcs/crossville/grom_ironbeard.png new file mode 100644 index 0000000..a6393ef Binary files /dev/null and b/public_web/static/images/npcs/crossville/grom_ironbeard.png differ diff --git a/public_web/static/images/npcs/crossville/mayor_aldric.png b/public_web/static/images/npcs/crossville/mayor_aldric.png new file mode 100644 index 0000000..88219df Binary files /dev/null and b/public_web/static/images/npcs/crossville/mayor_aldric.png differ diff --git a/public_web/static/images/npcs/crossville/mira_swiftfoot.png b/public_web/static/images/npcs/crossville/mira_swiftfoot.png new file mode 100644 index 0000000..205c4ca Binary files /dev/null and b/public_web/static/images/npcs/crossville/mira_swiftfoot.png differ