What a mess
This commit is contained in:
131
bot/combat.py
131
bot/combat.py
@@ -6,7 +6,7 @@ import random
|
||||
import json
|
||||
import time
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
from bot import database
|
||||
from bot.api_client import api_client
|
||||
from bot.utils import format_stat_bar
|
||||
from data.npcs import NPCS, STATUS_EFFECTS
|
||||
from data.items import ITEMS
|
||||
@@ -27,7 +27,7 @@ async def calculate_player_damage(player: dict) -> int:
|
||||
level_bonus = player['level']
|
||||
|
||||
# Check for equipped weapon
|
||||
inventory = await database.get_inventory(player['telegram_id'])
|
||||
inventory = await api_client.get_inventory(player['telegram_id'])
|
||||
weapon_damage = 0
|
||||
|
||||
for item in inventory:
|
||||
@@ -76,7 +76,7 @@ async def initiate_combat(player_id: int, npc_id: str, location_id: str, from_wa
|
||||
npc_hp = random.randint(npc_def.hp_min, npc_def.hp_max)
|
||||
|
||||
# Create combat in database
|
||||
combat_id = await database.create_combat(
|
||||
combat_id = await api_client.create_combat(
|
||||
player_id=player_id,
|
||||
npc_id=npc_id,
|
||||
npc_hp=npc_hp,
|
||||
@@ -85,7 +85,7 @@ async def initiate_combat(player_id: int, npc_id: str, location_id: str, from_wa
|
||||
from_wandering_enemy=from_wandering_enemy
|
||||
)
|
||||
|
||||
return await database.get_combat(player_id)
|
||||
return await api_client.get_combat(player_id)
|
||||
|
||||
|
||||
async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
|
||||
@@ -93,11 +93,11 @@ async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
|
||||
Player attacks the NPC.
|
||||
Returns: (message, npc_died, player_turn_ended)
|
||||
"""
|
||||
combat = await database.get_combat(player_id)
|
||||
combat = await api_client.get_combat(player_id)
|
||||
if not combat or combat['turn'] != 'player':
|
||||
return ("It's not your turn!", False, False)
|
||||
|
||||
player = await database.get_player(player_id)
|
||||
player = await api_client.get_player(player_id)
|
||||
npc_def = NPCS.get(combat['npc_id'])
|
||||
|
||||
if not player or not npc_def:
|
||||
@@ -109,7 +109,7 @@ async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
|
||||
if is_stunned:
|
||||
# Update status effects
|
||||
player_effects = update_status_effects(player_effects)
|
||||
await database.update_combat(player_id, {
|
||||
await api_client.update_combat(player_id, {
|
||||
'turn': 'npc',
|
||||
'turn_started_at': time.time(),
|
||||
'player_status_effects': json.dumps(player_effects)
|
||||
@@ -147,7 +147,7 @@ async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
|
||||
player_effects, status_damage, status_messages = apply_status_effects(player_effects)
|
||||
if status_damage > 0:
|
||||
new_player_hp = max(0, player['hp'] - status_damage)
|
||||
await database.update_player(player_id, {'hp': new_player_hp})
|
||||
await api_client.update_player(player_id, {'hp': new_player_hp})
|
||||
message += f"\n{status_messages}"
|
||||
|
||||
if new_player_hp <= 0:
|
||||
@@ -156,7 +156,7 @@ async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
|
||||
|
||||
# Check if NPC died
|
||||
if new_npc_hp <= 0:
|
||||
await database.update_combat(player_id, {
|
||||
await api_client.update_combat(player_id, {
|
||||
'npc_hp': 0,
|
||||
'npc_status_effects': json.dumps(npc_effects),
|
||||
'player_status_effects': json.dumps(player_effects)
|
||||
@@ -167,7 +167,7 @@ async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
|
||||
return (message + "\n\n" + victory_msg, True, True)
|
||||
|
||||
# Update combat - switch to NPC turn
|
||||
await database.update_combat(player_id, {
|
||||
await api_client.update_combat(player_id, {
|
||||
'npc_hp': new_npc_hp,
|
||||
'turn': 'npc',
|
||||
'turn_started_at': time.time(),
|
||||
@@ -189,11 +189,11 @@ async def npc_attack(player_id: int) -> Tuple[str, bool]:
|
||||
NPC attacks the player.
|
||||
Returns: (message, player_died)
|
||||
"""
|
||||
combat = await database.get_combat(player_id)
|
||||
combat = await api_client.get_combat(player_id)
|
||||
if not combat or combat['turn'] != 'npc':
|
||||
return ("", False)
|
||||
|
||||
player = await database.get_player(player_id)
|
||||
player = await api_client.get_player(player_id)
|
||||
npc_def = NPCS.get(combat['npc_id'])
|
||||
|
||||
if not player or not npc_def:
|
||||
@@ -205,7 +205,7 @@ async def npc_attack(player_id: int) -> Tuple[str, bool]:
|
||||
if is_stunned:
|
||||
# Update status effects
|
||||
npc_effects = update_status_effects(npc_effects)
|
||||
await database.update_combat(player_id, {
|
||||
await api_client.update_combat(player_id, {
|
||||
'turn': 'player',
|
||||
'turn_started_at': time.time(),
|
||||
'npc_status_effects': json.dumps(npc_effects)
|
||||
@@ -217,7 +217,7 @@ async def npc_attack(player_id: int) -> Tuple[str, bool]:
|
||||
|
||||
# Apply damage to player
|
||||
new_player_hp = max(0, player['hp'] - damage)
|
||||
await database.update_player(player_id, {'hp': new_player_hp})
|
||||
await api_client.update_player(player_id, {'hp': new_player_hp})
|
||||
|
||||
message = "━━━ ENEMY TURN ━━━\n"
|
||||
message += f"💥 The {npc_def.name} attacks you for {damage} damage!"
|
||||
@@ -237,7 +237,7 @@ async def npc_attack(player_id: int) -> Tuple[str, bool]:
|
||||
npc_effects, status_damage, status_messages = apply_status_effects(npc_effects)
|
||||
if status_damage > 0:
|
||||
new_npc_hp = max(0, combat['npc_hp'] - status_damage)
|
||||
await database.update_combat(player_id, {'npc_hp': new_npc_hp})
|
||||
await api_client.update_combat(player_id, {'npc_hp': new_npc_hp})
|
||||
message += f"\n{status_messages}"
|
||||
|
||||
if new_npc_hp <= 0:
|
||||
@@ -250,7 +250,7 @@ async def npc_attack(player_id: int) -> Tuple[str, bool]:
|
||||
return (message + "\n\n💀 You have been slain...", True)
|
||||
|
||||
# Update combat - switch to player turn
|
||||
await database.update_combat(player_id, {
|
||||
await api_client.update_combat(player_id, {
|
||||
'turn': 'player',
|
||||
'turn_started_at': time.time(),
|
||||
'player_status_effects': json.dumps(player_effects),
|
||||
@@ -270,11 +270,11 @@ async def flee_attempt(player_id: int) -> Tuple[str, bool, bool]:
|
||||
Player attempts to flee from combat.
|
||||
Returns: (message, fled_successfully, turn_ended)
|
||||
"""
|
||||
combat = await database.get_combat(player_id)
|
||||
combat = await api_client.get_combat(player_id)
|
||||
if not combat or combat['turn'] != 'player':
|
||||
return ("It's not your turn!", False, False)
|
||||
|
||||
player = await database.get_player(player_id)
|
||||
player = await api_client.get_player(player_id)
|
||||
npc_def = NPCS.get(combat['npc_id'])
|
||||
|
||||
# Base flee chance is 50%, modified by agility
|
||||
@@ -283,21 +283,22 @@ async def flee_attempt(player_id: int) -> Tuple[str, bool, bool]:
|
||||
if random.random() < flee_chance:
|
||||
# Success! Check if we need to respawn the wandering enemy
|
||||
if combat.get('from_wandering_enemy', False):
|
||||
# Respawn the enemy at the same location
|
||||
await database.spawn_wandering_enemy(
|
||||
# Respawn the enemy at the same location with full HP
|
||||
await api_client.spawn_wandering_enemy(
|
||||
npc_id=combat['npc_id'],
|
||||
location_id=combat['location_id'],
|
||||
lifetime_seconds=600 # 10 minutes
|
||||
current_hp=npc_def.hp,
|
||||
max_hp=npc_def.hp
|
||||
)
|
||||
|
||||
await database.end_combat(player_id)
|
||||
await api_client.end_combat(player_id)
|
||||
return (f"🏃 You successfully flee from the {npc_def.name}!", True, True)
|
||||
else:
|
||||
# Failed - lose turn and NPC attacks
|
||||
message = f"❌ You failed to escape! The {npc_def.name} takes advantage!"
|
||||
|
||||
# NPC gets a free attack
|
||||
await database.update_combat(player_id, {
|
||||
await api_client.update_combat(player_id, {
|
||||
'turn': 'npc',
|
||||
'turn_started_at': time.time()
|
||||
})
|
||||
@@ -317,26 +318,46 @@ def update_status_effects(effects: List[Dict]) -> List[Dict]:
|
||||
|
||||
def apply_status_effects(effects: List[Dict]) -> Tuple[List[Dict], int, str]:
|
||||
"""
|
||||
Apply status effect damage.
|
||||
Apply status effect damage with stacking.
|
||||
Returns: (updated_effects, total_damage, message)
|
||||
"""
|
||||
from bot.status_utils import stack_status_effects
|
||||
|
||||
if not effects:
|
||||
return effects, 0, ""
|
||||
|
||||
# Convert effect format if needed (name -> effect_name, damage_per_turn -> damage_per_tick)
|
||||
normalized_effects = []
|
||||
for effect in effects:
|
||||
normalized = {
|
||||
'effect_name': effect.get('name', effect.get('effect_name', 'Unknown')),
|
||||
'effect_icon': effect.get('icon', effect.get('effect_icon', '❓')),
|
||||
'damage_per_tick': effect.get('damage_per_turn', effect.get('damage_per_tick', 0)),
|
||||
'ticks_remaining': effect.get('turns_remaining', effect.get('ticks_remaining', 0))
|
||||
}
|
||||
normalized_effects.append(normalized)
|
||||
|
||||
# Stack effects
|
||||
stacked = stack_status_effects(normalized_effects)
|
||||
|
||||
total_damage = 0
|
||||
messages = []
|
||||
|
||||
for effect in effects:
|
||||
if effect['damage_per_turn'] > 0:
|
||||
total_damage += effect['damage_per_turn']
|
||||
if effect['name'] == 'Bleeding':
|
||||
messages.append(f"🩸 Bleeding: -{effect['damage_per_turn']} HP")
|
||||
elif effect['name'] == 'Infected':
|
||||
messages.append(f"🦠 Infection: -{effect['damage_per_turn']} HP")
|
||||
for name, data in stacked.items():
|
||||
if data['total_damage'] > 0:
|
||||
total_damage += data['total_damage']
|
||||
# Show stacked damage
|
||||
if data['stacks'] > 1:
|
||||
messages.append(f"{data['icon']} {name.replace('_', ' ').title()}: -{data['total_damage']} HP (×{data['stacks']})")
|
||||
else:
|
||||
messages.append(f"{data['icon']} {name.replace('_', ' ').title()}: -{data['total_damage']} HP")
|
||||
|
||||
return effects, total_damage, "\n".join(messages)
|
||||
|
||||
|
||||
async def handle_npc_death(player_id: int, combat: Dict, npc_def) -> str:
|
||||
"""Handle NPC death - give XP, drop loot, create corpse."""
|
||||
player = await database.get_player(player_id)
|
||||
player = await api_client.get_player(player_id)
|
||||
|
||||
# Give XP
|
||||
new_xp = player['xp'] + npc_def.xp_reward
|
||||
@@ -353,7 +374,7 @@ async def handle_npc_death(player_id: int, combat: Dict, npc_def) -> str:
|
||||
points_gained = 5
|
||||
new_unspent_points = player.get('unspent_points', 0) + points_gained
|
||||
|
||||
await database.update_player(player_id, {
|
||||
await api_client.update_player(player_id, {
|
||||
'xp': new_xp,
|
||||
'level': new_level,
|
||||
'hp': player['max_hp'], # Heal on level up
|
||||
@@ -366,7 +387,7 @@ async def handle_npc_death(player_id: int, combat: Dict, npc_def) -> str:
|
||||
level_up_msg += f"\n❤️ Fully healed and stamina restored!"
|
||||
level_up_msg += f"\n\n💡 Check your profile to spend your points!"
|
||||
else:
|
||||
await database.update_player(player_id, {'xp': new_xp})
|
||||
await api_client.update_player(player_id, {'xp': new_xp})
|
||||
|
||||
# Drop loot
|
||||
loot_msg = "\n\n💰 Loot dropped:"
|
||||
@@ -374,7 +395,7 @@ async def handle_npc_death(player_id: int, combat: Dict, npc_def) -> str:
|
||||
for loot_item in npc_def.loot_table:
|
||||
if random.random() < loot_item.drop_chance:
|
||||
quantity = random.randint(loot_item.quantity_min, loot_item.quantity_max)
|
||||
await database.drop_item_to_world(
|
||||
await api_client.drop_item_to_world(
|
||||
loot_item.item_id,
|
||||
quantity,
|
||||
combat['location_id']
|
||||
@@ -395,7 +416,7 @@ async def handle_npc_death(player_id: int, combat: Dict, npc_def) -> str:
|
||||
'required_tool': cl.required_tool
|
||||
} for cl in npc_def.corpse_loot])
|
||||
|
||||
await database.create_npc_corpse(
|
||||
await api_client.create_npc_corpse(
|
||||
npc_id=combat['npc_id'],
|
||||
location_id=combat['location_id'],
|
||||
loot_remaining=corpse_loot_json
|
||||
@@ -403,7 +424,7 @@ async def handle_npc_death(player_id: int, combat: Dict, npc_def) -> str:
|
||||
loot_msg += f"\n\n{npc_def.emoji} The corpse can be scavenged for more resources."
|
||||
|
||||
# End combat
|
||||
await database.end_combat(player_id)
|
||||
await api_client.end_combat(player_id)
|
||||
|
||||
message = f"🏆 Victory! {npc_def.death_message}"
|
||||
message += f"\n+{npc_def.xp_reward} XP"
|
||||
@@ -415,17 +436,19 @@ async def handle_npc_death(player_id: int, combat: Dict, npc_def) -> str:
|
||||
|
||||
async def handle_player_death(player_id: int):
|
||||
"""Handle player death - create corpse bag with all items."""
|
||||
player = await database.get_player(player_id)
|
||||
inventory_items = await database.get_inventory(player_id)
|
||||
player = await api_client.get_player(player_id)
|
||||
inventory_items = await api_client.get_inventory(player_id)
|
||||
|
||||
# Check if combat was with a wandering enemy that should respawn
|
||||
combat = await database.get_combat(player_id)
|
||||
combat = await api_client.get_combat(player_id)
|
||||
if combat and combat.get('from_wandering_enemy', False):
|
||||
# Respawn the enemy at the same location
|
||||
await database.spawn_wandering_enemy(
|
||||
# Respawn the enemy at the same location with full HP
|
||||
npc_def = NPCS.get(combat['npc_id'])
|
||||
await api_client.spawn_wandering_enemy(
|
||||
npc_id=combat['npc_id'],
|
||||
location_id=combat['location_id'],
|
||||
lifetime_seconds=600 # 10 minutes
|
||||
current_hp=npc_def.hp,
|
||||
max_hp=npc_def.hp
|
||||
)
|
||||
|
||||
# Create corpse bag if player has items
|
||||
@@ -435,7 +458,7 @@ async def handle_player_death(player_id: int):
|
||||
'quantity': item['quantity']
|
||||
} for item in inventory_items])
|
||||
|
||||
await database.create_player_corpse(
|
||||
await api_client.create_player_corpse(
|
||||
player_name=player['name'],
|
||||
location_id=player['location_id'],
|
||||
items=items_json
|
||||
@@ -443,11 +466,11 @@ async def handle_player_death(player_id: int):
|
||||
|
||||
# Remove all items from player
|
||||
for item in inventory_items:
|
||||
await database.remove_item_from_inventory(item['id'], item['quantity'])
|
||||
await api_client.remove_item_from_inventory(item['id'], item['quantity'])
|
||||
|
||||
# Mark player as dead and end any combat
|
||||
await database.update_player(player_id, {'is_dead': True, 'hp': 0})
|
||||
await database.end_combat(player_id)
|
||||
await api_client.update_player(player_id, {'is_dead': True, 'hp': 0})
|
||||
await api_client.end_combat(player_id)
|
||||
|
||||
|
||||
async def use_item_in_combat(player_id: int, item_db_id: int) -> Tuple[str, bool]:
|
||||
@@ -455,11 +478,11 @@ async def use_item_in_combat(player_id: int, item_db_id: int) -> Tuple[str, bool
|
||||
Use a consumable item during combat.
|
||||
Returns: (message, turn_ended)
|
||||
"""
|
||||
combat = await database.get_combat(player_id)
|
||||
combat = await api_client.get_combat(player_id)
|
||||
if not combat or combat['turn'] != 'player':
|
||||
return ("It's not your turn!", False)
|
||||
|
||||
item_data = await database.get_inventory_item(item_db_id)
|
||||
item_data = await api_client.get_inventory_item(item_db_id)
|
||||
if not item_data or item_data['player_id'] != player_id:
|
||||
return ("You don't have that item!", False)
|
||||
|
||||
@@ -467,7 +490,7 @@ async def use_item_in_combat(player_id: int, item_db_id: int) -> Tuple[str, bool
|
||||
if not item_def or item_def.get('type') != 'consumable':
|
||||
return ("That item cannot be used in combat!", False)
|
||||
|
||||
player = await database.get_player(player_id)
|
||||
player = await api_client.get_player(player_id)
|
||||
|
||||
# Apply consumable effects
|
||||
message = f"💊 Used {item_def['name']}!"
|
||||
@@ -487,16 +510,16 @@ async def use_item_in_combat(player_id: int, item_db_id: int) -> Tuple[str, bool
|
||||
message += f"\n⚡ +{stamina_restore} Stamina"
|
||||
|
||||
if updates:
|
||||
await database.update_player(player_id, updates)
|
||||
await api_client.update_player(player_id, updates)
|
||||
|
||||
# Remove item from inventory
|
||||
if item_data['quantity'] > 1:
|
||||
await database.update_inventory_item(item_db_id, item_data['quantity'] - 1)
|
||||
await api_client.update_inventory_item(item_db_id, item_data['quantity'] - 1)
|
||||
else:
|
||||
await database.remove_item_from_inventory(item_db_id, 1)
|
||||
await api_client.remove_item_from_inventory(item_db_id, 1)
|
||||
|
||||
# Using an item ends your turn
|
||||
await database.update_combat(player_id, {
|
||||
await api_client.update_combat(player_id, {
|
||||
'turn': 'npc',
|
||||
'turn_started_at': time.time()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user