Added trading and quests, checkpoint push

This commit is contained in:
Joan
2026-02-08 20:18:42 +01:00
parent 8820cd897e
commit 70dc35b4b2
36 changed files with 3583 additions and 279 deletions

View File

@@ -28,11 +28,52 @@ redis_manager = None
def init_router_dependencies(locations, items_manager, world, redis_mgr=None):
"""Initialize router with game data dependencies"""
print("🔧 INITIALIZING GAME ROUTE DEPENDENCIES")
global LOCATIONS, ITEMS_MANAGER, WORLD, redis_manager
LOCATIONS = locations
ITEMS_MANAGER = items_manager
WORLD = world
redis_manager = redis_mgr
print(f"🔧 Locations keys: {list(LOCATIONS.keys())}")
# Load separate static NPCs
from pathlib import Path
try:
# Use relative path consistent with Docker WORKDIR /app
json_path = Path("./gamedata/static_npcs.json")
with open(json_path, "r") as f:
npc_data = json.load(f).get("static_npcs", {})
print(f"🔧 Loaded static NPCs data keys: {list(npc_data.keys())}")
for npc_id, npc_def in npc_data.items():
loc_id = npc_def.get("location_id")
if loc_id and loc_id in LOCATIONS:
# Check for duplication
location = LOCATIONS[loc_id]
existing = False
for existing_npc in location.npcs:
if isinstance(existing_npc, dict) and existing_npc.get("id") == npc_id:
existing = True
break
if not existing:
# Inject
location.npcs.append({
"id": npc_id,
"name": npc_def.get("name"), # Keep as dict/string, frontend handles localization
"type": "npc",
"level": 1,
"image_path": npc_def.get("image"),
"is_static": True,
"trade": npc_def.get("trade", {}) # Setup trade config for frontend checks
})
print(f"✅ Injected static NPC {npc_id} into {loc_id}")
else:
print(f"⚠️ Could not inject NPC {npc_id}: Location {loc_id} not found")
except Exception as e:
print(f"❌ Failed to inject static NPCs: {e}")
router = APIRouter(tags=["game"])
@@ -163,6 +204,7 @@ async def _get_enriched_inventory(player_id: int):
"damage_max": item.stats.get('damage_max') if item.stats else None,
"stats": item.stats,
# Workbench flags
"value": getattr(item, 'value', 10),
"is_repairable": is_repairable,
"is_salvageable": is_salvageable,
"current_durability": current_durability,
@@ -239,8 +281,42 @@ async def get_game_state(current_user: dict = Depends(get_current_user)):
if slot not in equipment:
equipment[slot] = None
# Get combat state
# Get active combat (PvE)
combat = await db.get_active_combat(player_id)
pvp_combat = None
# If no PvE combat, check for PvP combat
if not combat:
pvp_combat = await db.get_pvp_combat_by_player(player_id)
if pvp_combat:
# Format PvP combat to match frontend expectations or pass as dedicated field
# Ideally, we pass it as 'pvp_combat' in the response and let frontend handle it,
# OR we standardize the 'combat' field. Game.tsx seems to handle both.
# But let's check Game.tsx or Combat.tsx props.
# Combat.tsx expects: initialCombatData which has { combat: ..., pvp_combat: ..., is_pvp: bool }
# If we return it in the main dict, Game.tsx passes the whole response to Combat.
# Enrich PvP combat with opponent data for the API response
is_attacker = pvp_combat['attacker_character_id'] == player_id
opponent_id = pvp_combat['defender_character_id'] if is_attacker else pvp_combat['attacker_character_id']
opponent = await db.get_player_by_id(opponent_id)
if is_attacker:
pvp_combat['attacker'] = player
pvp_combat['defender'] = opponent
pvp_combat['is_attacker'] = True
else:
pvp_combat['attacker'] = opponent
pvp_combat['defender'] = player
pvp_combat['is_attacker'] = False
# Determine if it's "combat_over" based on fled status or HP
# This helps the frontend break out of the loop
if pvp_combat.get('attacker_fled') or pvp_combat.get('defender_fled') or \
pvp_combat.get('attacker_acknowledged') and pvp_combat.get('defender_acknowledged'): # Wait, if both ack, it's deleted.
# If just fled, it's over but waiting for ack
pass
if combat:
# Ensure intent is present (handle legacy)
if 'npc_intent' not in combat or not combat['npc_intent']:
@@ -319,6 +395,8 @@ async def get_game_state(current_user: dict = Depends(get_current_user)):
"inventory": inventory,
"equipment": equipment,
"combat": combat,
"pvp_combat": pvp_combat,
"is_pvp": pvp_combat is not None,
"dropped_items": dropped_items
}
@@ -529,8 +607,12 @@ async def get_current_location(request: Request, current_user: dict = Depends(ge
"name": npc.get('name', 'Unknown NPC'),
"type": npc.get('type', 'npc'),
"level": npc.get('level'),
"is_wandering": False
"is_wandering": False,
"image_path": npc.get('image_path'),
"is_static": npc.get('is_static', False),
"trade": npc.get('trade')
})
else:
npcs_data.append({
"id": npc,
@@ -539,6 +621,9 @@ async def get_current_location(request: Request, current_user: dict = Depends(ge
"is_wandering": False
})
# Debug logging for missing NPCs - UNCONDITIONAL
logger.info(f"📍 Requested Location: {location.id}, NPCs: {[n.get('id') for n in npcs_data]}")
# Enrich dropped items with metadata - DON'T consolidate unique items!
items_dict = {}
for item in dropped_items:
@@ -1053,7 +1138,7 @@ async def interact(
"instance_id": interact_req.interactable_id,
"action_id": interact_req.action_id,
"cooldown_remaining": cooldown_remaining,
"message": get_game_message('interactable_cooldown', locale, user=current_user, interactable=get_locale_string(interactable_name, locale), action=get_locale_string(action_display, locale)),
"message": get_game_message('interactable_cooldown', locale, user=current_user['name'], interactable=get_locale_string(interactable_name, locale), action=get_locale_string(action_display, locale)),
},
"timestamp": datetime.utcnow().isoformat()
}