Added trading and quests, checkpoint push
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user