What a mess

This commit is contained in:
Joan
2025-11-07 15:27:13 +01:00
parent 0b79b3ae59
commit 33cc9586c2
130 changed files with 29819 additions and 1175 deletions

View File

@@ -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()
})