Pre-menu-integration snapshot: combat, crafting, status effects, gamedata updates
This commit is contained in:
@@ -17,13 +17,14 @@ from ..services.constants import PVP_TURN_TIMEOUT
|
||||
|
||||
from ..core.security import get_current_user, security, verify_internal_key
|
||||
from ..services.models import *
|
||||
from ..services.helpers import calculate_distance, calculate_stamina_cost, calculate_player_capacity, get_locale_string, create_combat_message, get_game_message
|
||||
from ..services.helpers import calculate_distance, calculate_stamina_cost, calculate_player_capacity, get_locale_string, create_combat_message, get_game_message, get_resolved_player_effects
|
||||
from .. import database as db
|
||||
from ..items import ItemsManager
|
||||
from .. import game_logic
|
||||
from ..core.websockets import manager
|
||||
from .equipment import reduce_armor_durability
|
||||
from ..services import combat_engine
|
||||
from ..services.status_effects import status_effects_manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,6 +71,27 @@ async def get_combat_status(current_user: dict = Depends(get_current_user)):
|
||||
time_elapsed = time.time() - turn_started_at
|
||||
turn_time_remaining = max(0, 300 - time_elapsed)
|
||||
|
||||
# Parse NPC status effects
|
||||
npc_effects_list = []
|
||||
npc_status_str = combat.get('npc_status_effects', '') or ''
|
||||
if npc_status_str:
|
||||
for part in npc_status_str.split('|'):
|
||||
tokens = part.split(':')
|
||||
effect_name = tokens[0] if len(tokens) > 0 else ''
|
||||
if not effect_name:
|
||||
continue
|
||||
ticks = int(tokens[2]) if len(tokens) > 2 else (int(tokens[1]) if len(tokens) > 1 else 0)
|
||||
info = status_effects_manager.get_effect_info(effect_name)
|
||||
npc_effects_list.append({
|
||||
'name': info['name'],
|
||||
'icon': info['icon'],
|
||||
'ticks_remaining': ticks,
|
||||
'description': info['description'],
|
||||
})
|
||||
|
||||
# Get player active buffs/debuffs (exclude cooldowns)
|
||||
player_effects = await get_resolved_player_effects(current_user['id'], in_combat=True)
|
||||
|
||||
return {
|
||||
"in_combat": True,
|
||||
"combat": {
|
||||
@@ -80,8 +102,11 @@ async def get_combat_status(current_user: dict = Depends(get_current_user)):
|
||||
"npc_image": f"{npc_def.image_path}" if npc_def else None,
|
||||
"turn": combat['turn'],
|
||||
"round": combat.get('round', 1),
|
||||
"turn_time_remaining": turn_time_remaining
|
||||
}
|
||||
"turn_time_remaining": turn_time_remaining,
|
||||
"npc_effects": npc_effects_list,
|
||||
"npc_intent": combat.get('npc_intent', 'attack')
|
||||
},
|
||||
"player_effects": player_effects
|
||||
}
|
||||
|
||||
|
||||
@@ -154,8 +179,10 @@ async def initiate_combat(
|
||||
"npc_max_hp": npc_hp,
|
||||
"npc_image": f"{npc_def.image_path}",
|
||||
"turn": "player",
|
||||
"round": 1
|
||||
}
|
||||
"round": 1,
|
||||
"npc_intent": "attack"
|
||||
},
|
||||
"player_effects": await get_resolved_player_effects(current_user['id'], in_combat=True)
|
||||
},
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
})
|
||||
@@ -185,8 +212,10 @@ async def initiate_combat(
|
||||
"npc_max_hp": npc_hp,
|
||||
"npc_image": f"{npc_def.image_path}",
|
||||
"turn": "player",
|
||||
"round": 1
|
||||
}
|
||||
"round": 1,
|
||||
"npc_intent": "attack"
|
||||
},
|
||||
"player_effects": await get_resolved_player_effects(current_user['id'], in_combat=True)
|
||||
}
|
||||
|
||||
|
||||
@@ -303,15 +332,20 @@ async def combat_action(
|
||||
exclude_player_id=player['id']
|
||||
)
|
||||
else:
|
||||
# Fetch fresh combat state to capture any player buffs applied
|
||||
fresh_combat = await db.get_active_combat(player['id'])
|
||||
st_effects = fresh_combat.get('npc_status_effects', '') if fresh_combat else combat.get('npc_status_effects', '')
|
||||
|
||||
# NPC turn
|
||||
npc_msgs, player_defeated = await combat_engine.execute_npc_turn(
|
||||
player['id'],
|
||||
{'npc_hp': new_npc_hp, 'npc_max_hp': combat['npc_max_hp'],
|
||||
'npc_intent': combat.get('npc_intent', 'attack'),
|
||||
'npc_status_effects': combat.get('npc_status_effects', '')},
|
||||
'npc_status_effects': st_effects},
|
||||
npc_def,
|
||||
reduce_armor_durability,
|
||||
redis_manager
|
||||
redis_manager,
|
||||
locale=locale
|
||||
)
|
||||
messages.extend(npc_msgs)
|
||||
|
||||
@@ -336,6 +370,7 @@ async def combat_action(
|
||||
items_manager=ITEMS_MANAGER,
|
||||
reduce_armor_func=reduce_armor_durability,
|
||||
redis_manager=redis_manager,
|
||||
locale=locale
|
||||
)
|
||||
|
||||
if result.get('error'):
|
||||
@@ -372,21 +407,28 @@ async def combat_action(
|
||||
exclude_player_id=player['id']
|
||||
)
|
||||
else:
|
||||
# Fetch fresh combat state to capture effects applied by the skill
|
||||
fresh_combat = await db.get_active_combat(player['id'])
|
||||
st_effects = fresh_combat.get('npc_status_effects', '') if fresh_combat else combat.get('npc_status_effects', '')
|
||||
|
||||
# NPC turn after skill
|
||||
npc_msgs, player_defeated = await combat_engine.execute_npc_turn(
|
||||
player['id'],
|
||||
{'npc_hp': new_npc_hp, 'npc_max_hp': combat['npc_max_hp'],
|
||||
'npc_intent': combat.get('npc_intent', 'attack'),
|
||||
'npc_status_effects': combat.get('npc_status_effects', '')},
|
||||
'npc_status_effects': st_effects},
|
||||
npc_def,
|
||||
reduce_armor_durability,
|
||||
redis_manager
|
||||
redis_manager,
|
||||
locale=locale
|
||||
)
|
||||
messages.extend(npc_msgs)
|
||||
|
||||
if player_defeated:
|
||||
await db.remove_non_persistent_effects(player['id'])
|
||||
combat_over = True
|
||||
else:
|
||||
await db.update_combat(player['id'], {'npc_hp': new_npc_hp})
|
||||
|
||||
# ── USE ITEM ──
|
||||
elif req.action == 'use_item':
|
||||
@@ -421,15 +463,20 @@ async def combat_action(
|
||||
messages.extend(victory['messages'])
|
||||
quest_updates = victory.get('quest_updates', [])
|
||||
elif not combat_over:
|
||||
# Fetch fresh combat state to capture effects applied by the item
|
||||
fresh_combat = await db.get_active_combat(player['id'])
|
||||
st_effects = fresh_combat.get('npc_status_effects', '') if fresh_combat else combat.get('npc_status_effects', '')
|
||||
|
||||
# NPC turn after item use
|
||||
npc_msgs, player_defeated = await combat_engine.execute_npc_turn(
|
||||
player['id'],
|
||||
{'npc_hp': result.get('target_hp', combat['npc_hp']), 'npc_max_hp': combat['npc_max_hp'],
|
||||
'npc_intent': combat.get('npc_intent', 'attack'),
|
||||
'npc_status_effects': combat.get('npc_status_effects', '')},
|
||||
'npc_status_effects': st_effects},
|
||||
npc_def,
|
||||
reduce_armor_durability,
|
||||
redis_manager
|
||||
redis_manager,
|
||||
locale=locale
|
||||
)
|
||||
messages.extend(npc_msgs)
|
||||
|
||||
@@ -440,6 +487,38 @@ async def combat_action(
|
||||
# Update NPC HP from throwable damage
|
||||
if result.get('target_hp') is not None and result['target_hp'] != combat['npc_hp']:
|
||||
await db.update_combat(player['id'], {'npc_hp': result['target_hp']})
|
||||
|
||||
# ── DEFEND ──
|
||||
elif req.action == 'defend':
|
||||
result = await combat_engine.execute_defend(
|
||||
player_id=player['id'],
|
||||
player=player,
|
||||
player_stats=stats,
|
||||
is_pvp=False,
|
||||
locale=locale,
|
||||
)
|
||||
messages.extend(result['messages'])
|
||||
|
||||
# Fetch fresh combat state since defend could've updated stats (stamina)
|
||||
fresh_combat = await db.get_active_combat(player['id'])
|
||||
st_effects = fresh_combat.get('npc_status_effects', '') if fresh_combat else combat.get('npc_status_effects', '')
|
||||
|
||||
# NPC turn after defend
|
||||
npc_msgs, player_defeated = await combat_engine.execute_npc_turn(
|
||||
player['id'],
|
||||
{'npc_hp': combat['npc_hp'], 'npc_max_hp': combat['npc_max_hp'],
|
||||
'npc_intent': combat.get('npc_intent', 'attack'),
|
||||
'npc_status_effects': st_effects},
|
||||
npc_def,
|
||||
reduce_armor_durability,
|
||||
redis_manager,
|
||||
locale=locale
|
||||
)
|
||||
messages.extend(npc_msgs)
|
||||
|
||||
if player_defeated:
|
||||
await db.remove_non_persistent_effects(player['id'])
|
||||
combat_over = True
|
||||
|
||||
# ── FLEE ──
|
||||
elif req.action == 'flee':
|
||||
@@ -491,6 +570,7 @@ async def combat_action(
|
||||
|
||||
# ── Build response ──
|
||||
updated_combat = None
|
||||
npc_effects_list = []
|
||||
if not combat_over:
|
||||
raw_combat = await db.get_active_combat(current_user['id'])
|
||||
if raw_combat:
|
||||
@@ -499,6 +579,23 @@ async def combat_action(
|
||||
turn_started_at = raw_combat.get('turn_started_at', 0)
|
||||
turn_time_remaining = max(0, 300 - (time.time() - turn_started_at))
|
||||
|
||||
# Parse NPC status effects string into a list
|
||||
npc_status_str = raw_combat.get('npc_status_effects', '') or ''
|
||||
if npc_status_str:
|
||||
for part in npc_status_str.split('|'):
|
||||
tokens = part.split(':')
|
||||
effect_name = tokens[0] if len(tokens) > 0 else ''
|
||||
if not effect_name:
|
||||
continue
|
||||
ticks = int(tokens[2]) if len(tokens) > 2 else (int(tokens[1]) if len(tokens) > 1 else 0)
|
||||
info = status_effects_manager.get_effect_info(effect_name)
|
||||
npc_effects_list.append({
|
||||
'name': info['name'],
|
||||
'icon': info['icon'],
|
||||
'ticks_remaining': ticks,
|
||||
'description': info['description'],
|
||||
})
|
||||
|
||||
updated_combat = {
|
||||
"npc_id": raw_combat['npc_id'],
|
||||
"npc_name": npc_def.name,
|
||||
@@ -507,13 +604,75 @@ async def combat_action(
|
||||
"npc_image": f"{npc_def.image_path}",
|
||||
"turn": raw_combat['turn'],
|
||||
"round": raw_combat.get('round', 1),
|
||||
"turn_time_remaining": turn_time_remaining
|
||||
"turn_time_remaining": turn_time_remaining,
|
||||
"npc_effects": npc_effects_list,
|
||||
"npc_intent": raw_combat.get('npc_intent', 'attack')
|
||||
}
|
||||
|
||||
# Get player active buffs/debuffs (exclude cooldowns)
|
||||
player_effects = []
|
||||
if not combat_over:
|
||||
from ..services.skills import skills_manager
|
||||
all_effects = await db.get_player_effects(current_user['id'])
|
||||
for eff in all_effects:
|
||||
if eff.get('effect_type') == 'cooldown':
|
||||
continue
|
||||
resolved = status_effects_manager.resolve_player_effect(
|
||||
eff.get('effect_name', ''),
|
||||
eff.get('effect_icon', '⚡'),
|
||||
eff.get('source', ''),
|
||||
skills_manager
|
||||
)
|
||||
player_effects.append({
|
||||
'name': resolved['name'],
|
||||
'icon': resolved['icon'],
|
||||
'ticks_remaining': eff.get('ticks_remaining', 0),
|
||||
'type': eff.get('effect_type', 'buff'),
|
||||
'description': resolved['description'],
|
||||
})
|
||||
|
||||
updated_player = await db.get_player_by_id(current_user['id'])
|
||||
if not updated_player:
|
||||
updated_player = current_user
|
||||
|
||||
equipment_slots = await db.get_all_equipment(current_user['id'])
|
||||
equipment = {}
|
||||
for slot, item_data in equipment_slots.items():
|
||||
if item_data and item_data['item_id']:
|
||||
inv_item = await db.get_inventory_item_by_id(item_data['item_id'])
|
||||
if inv_item:
|
||||
item_def = ITEMS_MANAGER.get_item(inv_item['item_id'])
|
||||
if item_def:
|
||||
# Get unique item data if this is a unique item
|
||||
durability = None
|
||||
max_durability = None
|
||||
tier = None
|
||||
unique_stats = None
|
||||
if inv_item.get('unique_item_id'):
|
||||
unique_item = await db.get_unique_item(inv_item['unique_item_id'])
|
||||
if unique_item:
|
||||
durability = unique_item.get('durability')
|
||||
max_durability = unique_item.get('max_durability')
|
||||
tier = unique_item.get('tier')
|
||||
unique_stats = unique_item.get('unique_stats')
|
||||
|
||||
equipment[slot] = {
|
||||
"inventory_id": item_data['item_id'],
|
||||
"item_id": item_def.id,
|
||||
"name": item_def.name,
|
||||
"description": item_def.description,
|
||||
"emoji": item_def.emoji,
|
||||
"image_path": item_def.image_path,
|
||||
"durability": durability if durability is not None else None,
|
||||
"max_durability": max_durability if max_durability is not None else None,
|
||||
"tier": tier if tier is not None else None,
|
||||
"unique_stats": unique_stats,
|
||||
"stats": item_def.stats,
|
||||
"encumbrance": item_def.encumbrance,
|
||||
"weapon_effects": item_def.weapon_effects if hasattr(item_def, 'weapon_effects') else {}
|
||||
}
|
||||
if slot not in equipment:
|
||||
equipment[slot] = None
|
||||
return {
|
||||
"success": True,
|
||||
"messages": messages,
|
||||
@@ -526,6 +685,8 @@ async def combat_action(
|
||||
"xp": updated_player['xp'],
|
||||
"level": updated_player['level']
|
||||
},
|
||||
"player_effects": player_effects,
|
||||
"equipment": equipment,
|
||||
"quest_updates": quest_updates
|
||||
}
|
||||
|
||||
@@ -887,6 +1048,7 @@ async def pvp_combat_action(
|
||||
items_manager=ITEMS_MANAGER,
|
||||
reduce_armor_func=reduce_armor_durability,
|
||||
redis_manager=redis_manager,
|
||||
locale=locale
|
||||
)
|
||||
|
||||
if result.get('error'):
|
||||
@@ -978,6 +1140,25 @@ async def pvp_combat_action(
|
||||
'last_action': f"{last_action_text}|{time.time()}"
|
||||
})
|
||||
|
||||
# ── DEFEND ──
|
||||
elif req.action == 'defend':
|
||||
result = await combat_engine.execute_defend(
|
||||
player_id=current_player['id'],
|
||||
player=current_player,
|
||||
player_stats=current_player_stats,
|
||||
is_pvp=True,
|
||||
locale=locale,
|
||||
)
|
||||
messages.extend(result['messages'])
|
||||
last_action_text = f"{current_player['name']} took a defensive stance!"
|
||||
|
||||
# Switch turns
|
||||
await db.update_pvp_combat(pvp_combat['id'], {
|
||||
'turn': 'defender' if is_attacker else 'attacker',
|
||||
'turn_started_at': time.time(),
|
||||
'last_action': f"{last_action_text}|{time.time()}"
|
||||
})
|
||||
|
||||
# ── FLEE ──
|
||||
elif req.action == 'flee':
|
||||
result = await combat_engine.execute_flee_pvp(
|
||||
|
||||
Reference in New Issue
Block a user