Initial commit: Echoes of the Ashes - Telegram RPG Bot
This commit is contained in:
154
main.py
Normal file
154
main.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import signal
|
||||
import os
|
||||
import time
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from telegram import Update
|
||||
from telegram.ext import Application, CommandHandler, CallbackQueryHandler
|
||||
|
||||
from bot import database, handlers
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
||||
)
|
||||
# Quieten down the HTTPX logger, which is very verbose
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# A global event to signal shutdown
|
||||
shutdown_event = asyncio.Event()
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
"""Gracefully handle shutdown signals."""
|
||||
logger.info("Shutdown signal received. Shutting down gracefully...")
|
||||
shutdown_event.set()
|
||||
|
||||
async def decay_dropped_items():
|
||||
"""A background task that periodically cleans up old dropped items."""
|
||||
while not shutdown_event.is_set():
|
||||
try:
|
||||
# Wait for 5 minutes before the next cleanup
|
||||
await asyncio.wait_for(shutdown_event.wait(), timeout=300)
|
||||
except asyncio.TimeoutError:
|
||||
logger.info("Running item decay task...")
|
||||
# Set decay time to 1 hour (3600 seconds)
|
||||
decay_seconds = 3600
|
||||
timestamp_limit = int(time.time()) - decay_seconds
|
||||
items_removed = await database.remove_expired_dropped_items(timestamp_limit)
|
||||
if items_removed > 0:
|
||||
logger.info(f"Decayed and removed {items_removed} old items.")
|
||||
|
||||
async def regenerate_stamina():
|
||||
"""A background task that periodically regenerates stamina for all players."""
|
||||
while not shutdown_event.is_set():
|
||||
try:
|
||||
# Wait for 5 minutes before the next regeneration cycle
|
||||
await asyncio.wait_for(shutdown_event.wait(), timeout=300)
|
||||
except asyncio.TimeoutError:
|
||||
logger.info("Running stamina regeneration...")
|
||||
players_updated = await database.regenerate_all_players_stamina()
|
||||
if players_updated > 0:
|
||||
logger.info(f"Regenerated stamina for {players_updated} players.")
|
||||
|
||||
async def check_combat_timers():
|
||||
"""A background task that checks for idle combat turns and auto-attacks."""
|
||||
while not shutdown_event.is_set():
|
||||
try:
|
||||
# Wait for 30 seconds before next check
|
||||
await asyncio.wait_for(shutdown_event.wait(), timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
# Check for combats idle for more than 5 minutes (300 seconds)
|
||||
idle_threshold = time.time() - 300
|
||||
idle_combats = await database.get_all_idle_combats(idle_threshold)
|
||||
|
||||
for combat in idle_combats:
|
||||
try:
|
||||
from bot import combat as combat_logic
|
||||
# Force end player's turn and let NPC attack
|
||||
if combat['turn'] == 'player':
|
||||
logger.info(f"Player {combat['player_id']} idle in combat - auto-ending turn")
|
||||
await database.update_combat(combat['player_id'], {
|
||||
'turn': 'npc',
|
||||
'turn_started_at': time.time()
|
||||
})
|
||||
# NPC attacks
|
||||
await combat_logic.npc_attack(combat['player_id'])
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing idle combat: {e}")
|
||||
|
||||
async def decay_corpses():
|
||||
"""A background task that removes old corpses."""
|
||||
while not shutdown_event.is_set():
|
||||
try:
|
||||
# Wait for 10 minutes before next cleanup
|
||||
await asyncio.wait_for(shutdown_event.wait(), timeout=600)
|
||||
except asyncio.TimeoutError:
|
||||
logger.info("Running corpse decay...")
|
||||
# Player corpses decay after 24 hours
|
||||
player_corpse_limit = time.time() - (24 * 3600)
|
||||
player_corpses_removed = await database.remove_expired_player_corpses(player_corpse_limit)
|
||||
|
||||
# NPC corpses decay after 2 hours
|
||||
npc_corpse_limit = time.time() - (2 * 3600)
|
||||
npc_corpses_removed = await database.remove_expired_npc_corpses(npc_corpse_limit)
|
||||
|
||||
if player_corpses_removed > 0 or npc_corpses_removed > 0:
|
||||
logger.info(f"Decayed {player_corpses_removed} player corpses and {npc_corpses_removed} NPC corpses.")
|
||||
|
||||
async def main() -> None:
|
||||
"""Start the bot and wait for a shutdown signal."""
|
||||
load_dotenv()
|
||||
TOKEN = os.getenv("TELEGRAM_TOKEN")
|
||||
|
||||
if not TOKEN or TOKEN == "YOUR_TELEGRAM_BOT_TOKEN_HERE":
|
||||
logger.error("TELEGRAM_TOKEN is not set! Please edit your .env file.")
|
||||
return
|
||||
|
||||
await database.create_tables()
|
||||
|
||||
application = Application.builder().token(TOKEN).build()
|
||||
|
||||
application.add_handler(CommandHandler("start", handlers.start))
|
||||
application.add_handler(CommandHandler("map", handlers.export_map))
|
||||
application.add_handler(CommandHandler("spawns", handlers.spawn_stats))
|
||||
application.add_handler(CallbackQueryHandler(handlers.button_handler))
|
||||
|
||||
async with application:
|
||||
await application.start()
|
||||
await application.updater.start_polling(allowed_updates=Update.ALL_TYPES)
|
||||
logger.info("Bot is running and polling for updates...")
|
||||
|
||||
# Start the spawn manager
|
||||
from bot import spawn_manager
|
||||
await spawn_manager.start_spawn_manager()
|
||||
|
||||
# Start the background tasks
|
||||
decay_task = asyncio.create_task(decay_dropped_items())
|
||||
stamina_task = asyncio.create_task(regenerate_stamina())
|
||||
combat_timer_task = asyncio.create_task(check_combat_timers())
|
||||
corpse_decay_task = asyncio.create_task(decay_corpses())
|
||||
|
||||
await shutdown_event.wait()
|
||||
|
||||
await application.updater.stop()
|
||||
await application.stop()
|
||||
|
||||
# Ensure the background tasks are also cancelled on shutdown
|
||||
decay_task.cancel()
|
||||
stamina_task.cancel()
|
||||
combat_timer_task.cancel()
|
||||
corpse_decay_task.cancel()
|
||||
logger.info("Bot has been shut down.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
logger.info("Main function interrupted.")
|
||||
Reference in New Issue
Block a user