diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4588336..e1b8326 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,11 @@ "Bash(pip3 install:*)", "Bash(apt list:*)", "Bash(curl:*)", - "Bash(git commit:*)" + "Bash(git commit:*)", + "Bash(sed:*)", + "Bash(grep:*)", + "Bash(pkill:*)", + "Bash(git add:*)" ], "deny": [] } diff --git a/config/locations.json b/config/locations.json index 0285d02..532f29e 100644 --- a/config/locations.json +++ b/config/locations.json @@ -5,9 +5,11 @@ "level_min": 1, "level_max": 3, "spawns": [ - {"species": "Leafy", "spawn_rate": 0.35, "min_level": 1, "max_level": 2}, - {"species": "Flamey", "spawn_rate": 0.35, "min_level": 1, "max_level": 2}, - {"species": "Aqua", "spawn_rate": 0.3, "min_level": 1, "max_level": 2} + {"species": "Leafy", "spawn_rate": 0.25, "min_level": 1, "max_level": 2}, + {"species": "Flamey", "spawn_rate": 0.25, "min_level": 1, "max_level": 2}, + {"species": "Aqua", "spawn_rate": 0.25, "min_level": 1, "max_level": 2}, + {"species": "Seedling", "spawn_rate": 0.15, "min_level": 1, "max_level": 2}, + {"species": "Furry", "spawn_rate": 0.1, "min_level": 1, "max_level": 3} ] }, { @@ -16,10 +18,13 @@ "level_min": 2, "level_max": 6, "spawns": [ - {"species": "Leafy", "spawn_rate": 0.3, "min_level": 2, "max_level": 4}, - {"species": "Vinewrap", "spawn_rate": 0.35, "min_level": 3, "max_level": 5}, - {"species": "Bloomtail", "spawn_rate": 0.25, "min_level": 4, "max_level": 6}, - {"species": "Flamey", "spawn_rate": 0.1, "min_level": 3, "max_level": 4} + {"species": "Leafy", "spawn_rate": 0.2, "min_level": 2, "max_level": 4}, + {"species": "Vinewrap", "spawn_rate": 0.25, "min_level": 3, "max_level": 5}, + {"species": "Bloomtail", "spawn_rate": 0.2, "min_level": 4, "max_level": 6}, + {"species": "Flamey", "spawn_rate": 0.08, "min_level": 3, "max_level": 4}, + {"species": "Fernwhisk", "spawn_rate": 0.15, "min_level": 3, "max_level": 5}, + {"species": "Furry", "spawn_rate": 0.08, "min_level": 2, "max_level": 4}, + {"species": "Mossrock", "spawn_rate": 0.04, "min_level": 5, "max_level": 6} ] }, { @@ -28,8 +33,11 @@ "level_min": 4, "level_max": 9, "spawns": [ - {"species": "Sparky", "spawn_rate": 0.6, "min_level": 4, "max_level": 7}, - {"species": "Rocky", "spawn_rate": 0.4, "min_level": 5, "max_level": 8} + {"species": "Sparky", "spawn_rate": 0.35, "min_level": 4, "max_level": 7}, + {"species": "Rocky", "spawn_rate": 0.25, "min_level": 5, "max_level": 8}, + {"species": "Zapper", "spawn_rate": 0.25, "min_level": 4, "max_level": 6}, + {"species": "Ember", "spawn_rate": 0.1, "min_level": 4, "max_level": 6}, + {"species": "Swiftpaw", "spawn_rate": 0.05, "min_level": 6, "max_level": 8} ] }, { @@ -38,8 +46,11 @@ "level_min": 6, "level_max": 12, "spawns": [ - {"species": "Rocky", "spawn_rate": 0.7, "min_level": 6, "max_level": 10}, - {"species": "Sparky", "spawn_rate": 0.3, "min_level": 7, "max_level": 9} + {"species": "Rocky", "spawn_rate": 0.4, "min_level": 6, "max_level": 10}, + {"species": "Sparky", "spawn_rate": 0.2, "min_level": 7, "max_level": 9}, + {"species": "Pebble", "spawn_rate": 0.25, "min_level": 6, "max_level": 8}, + {"species": "Crystalback", "spawn_rate": 0.1, "min_level": 9, "max_level": 12}, + {"species": "Voltmane", "spawn_rate": 0.05, "min_level": 10, "max_level": 12} ] }, { @@ -48,9 +59,13 @@ "level_min": 10, "level_max": 16, "spawns": [ - {"species": "Hydrox", "spawn_rate": 0.4, "min_level": 10, "max_level": 14}, - {"species": "Rocky", "spawn_rate": 0.3, "min_level": 11, "max_level": 15}, - {"species": "Sparky", "spawn_rate": 0.3, "min_level": 12, "max_level": 14} + {"species": "Hydrox", "spawn_rate": 0.25, "min_level": 10, "max_level": 14}, + {"species": "Rocky", "spawn_rate": 0.2, "min_level": 11, "max_level": 15}, + {"species": "Sparky", "spawn_rate": 0.15, "min_level": 12, "max_level": 14}, + {"species": "Snowball", "spawn_rate": 0.2, "min_level": 10, "max_level": 12}, + {"species": "Frostbite", "spawn_rate": 0.1, "min_level": 12, "max_level": 15}, + {"species": "Bubblin", "spawn_rate": 0.05, "min_level": 10, "max_level": 13}, + {"species": "Frostleaf", "spawn_rate": 0.05, "min_level": 14, "max_level": 16} ] }, { @@ -59,9 +74,19 @@ "level_min": 15, "level_max": 25, "spawns": [ - {"species": "Blazeon", "spawn_rate": 0.5, "min_level": 15, "max_level": 20}, - {"species": "Hydrox", "spawn_rate": 0.3, "min_level": 16, "max_level": 22}, - {"species": "Rocky", "spawn_rate": 0.2, "min_level": 18, "max_level": 25} + {"species": "Blazeon", "spawn_rate": 0.22, "min_level": 15, "max_level": 20}, + {"species": "Hydrox", "spawn_rate": 0.18, "min_level": 16, "max_level": 22}, + {"species": "Rocky", "spawn_rate": 0.13, "min_level": 18, "max_level": 25}, + {"species": "Scorchclaw", "spawn_rate": 0.07, "min_level": 15, "max_level": 18}, + {"species": "Tidalfin", "spawn_rate": 0.07, "min_level": 16, "max_level": 19}, + {"species": "Infernowyrm", "spawn_rate": 0.05, "min_level": 20, "max_level": 25}, + {"species": "Abyssal", "spawn_rate": 0.05, "min_level": 20, "max_level": 25}, + {"species": "Thornking", "spawn_rate": 0.05, "min_level": 20, "max_level": 25}, + {"species": "Stormcaller", "spawn_rate": 0.05, "min_level": 20, "max_level": 25}, + {"species": "Steamvent", "spawn_rate": 0.04, "min_level": 19, "max_level": 23}, + {"species": "Mountainlord", "spawn_rate": 0.03, "min_level": 22, "max_level": 25}, + {"species": "Glaciarch", "spawn_rate": 0.03, "min_level": 22, "max_level": 25}, + {"species": "Harmonix", "spawn_rate": 0.03, "min_level": 18, "max_level": 22} ] } ] \ No newline at end of file diff --git a/config/pets.json b/config/pets.json index e2d40fe..0457d61 100644 --- a/config/pets.json +++ b/config/pets.json @@ -8,7 +8,8 @@ "base_defense": 43, "base_speed": 65, "evolution_level": null, - "rarity": 1 + "rarity": 1, + "emoji": "đŸ”Ĩ" }, { "name": "Aqua", @@ -19,7 +20,8 @@ "base_defense": 65, "base_speed": 43, "evolution_level": null, - "rarity": 1 + "rarity": 1, + "emoji": "💧" }, { "name": "Leafy", @@ -30,7 +32,8 @@ "base_defense": 49, "base_speed": 45, "evolution_level": null, - "rarity": 1 + "rarity": 1, + "emoji": "🍃" }, { "name": "Sparky", @@ -41,7 +44,8 @@ "base_defense": 40, "base_speed": 90, "evolution_level": null, - "rarity": 2 + "rarity": 2, + "emoji": "⚡" }, { "name": "Rocky", @@ -52,7 +56,8 @@ "base_defense": 100, "base_speed": 25, "evolution_level": null, - "rarity": 2 + "rarity": 2, + "emoji": "đŸ—ŋ" }, { "name": "Blazeon", @@ -63,7 +68,8 @@ "base_defense": 60, "base_speed": 95, "evolution_level": null, - "rarity": 3 + "rarity": 3, + "emoji": "🌋" }, { "name": "Hydrox", @@ -74,7 +80,8 @@ "base_defense": 90, "base_speed": 60, "evolution_level": null, - "rarity": 3 + "rarity": 3, + "emoji": "🌊" }, { "name": "Vinewrap", @@ -85,7 +92,8 @@ "base_defense": 70, "base_speed": 40, "evolution_level": null, - "rarity": 2 + "rarity": 2, + "emoji": "đŸŒŋ" }, { "name": "Bloomtail", @@ -96,6 +104,295 @@ "base_defense": 50, "base_speed": 80, "evolution_level": null, - "rarity": 2 + "rarity": 2, + "emoji": "đŸŒē" + }, + { + "name": "Ember", + "type1": "Fire", + "type2": null, + "base_hp": 42, + "base_attack": 50, + "base_defense": 40, + "base_speed": 68, + "evolution_level": null, + "rarity": 1, + "emoji": "✨" + }, + { + "name": "Scorchclaw", + "type1": "Fire", + "type2": null, + "base_hp": 55, + "base_attack": 75, + "base_defense": 55, + "base_speed": 70, + "evolution_level": null, + "rarity": 2, + "emoji": "🐱" + }, + { + "name": "Infernowyrm", + "type1": "Fire", + "type2": null, + "base_hp": 90, + "base_attack": 120, + "base_defense": 75, + "base_speed": 85, + "evolution_level": null, + "rarity": 4, + "emoji": "🐉" + }, + { + "name": "Bubblin", + "type1": "Water", + "type2": null, + "base_hp": 48, + "base_attack": 40, + "base_defense": 60, + "base_speed": 52, + "evolution_level": null, + "rarity": 1, + "emoji": "đŸĢ§" + }, + { + "name": "Tidalfin", + "type1": "Water", + "type2": null, + "base_hp": 65, + "base_attack": 60, + "base_defense": 70, + "base_speed": 80, + "evolution_level": null, + "rarity": 2, + "emoji": "đŸŦ" + }, + { + "name": "Abyssal", + "type1": "Water", + "type2": null, + "base_hp": 100, + "base_attack": 85, + "base_defense": 110, + "base_speed": 55, + "evolution_level": null, + "rarity": 4, + "emoji": "🐙" + }, + { + "name": "Seedling", + "type1": "Grass", + "type2": null, + "base_hp": 40, + "base_attack": 35, + "base_defense": 50, + "base_speed": 40, + "evolution_level": null, + "rarity": 1, + "emoji": "🌱" + }, + { + "name": "Fernwhisk", + "type1": "Grass", + "type2": null, + "base_hp": 50, + "base_attack": 55, + "base_defense": 65, + "base_speed": 75, + "evolution_level": null, + "rarity": 2, + "emoji": "🌾" + }, + { + "name": "Thornking", + "type1": "Grass", + "type2": null, + "base_hp": 85, + "base_attack": 95, + "base_defense": 120, + "base_speed": 50, + "evolution_level": null, + "rarity": 4, + "emoji": "👑" + }, + { + "name": "Zapper", + "type1": "Electric", + "type2": null, + "base_hp": 30, + "base_attack": 45, + "base_defense": 35, + "base_speed": 95, + "evolution_level": null, + "rarity": 1, + "emoji": "🐭" + }, + { + "name": "Voltmane", + "type1": "Electric", + "type2": null, + "base_hp": 60, + "base_attack": 85, + "base_defense": 50, + "base_speed": 110, + "evolution_level": null, + "rarity": 3, + "emoji": "🐎" + }, + { + "name": "Stormcaller", + "type1": "Electric", + "type2": null, + "base_hp": 75, + "base_attack": 130, + "base_defense": 60, + "base_speed": 125, + "evolution_level": null, + "rarity": 4, + "emoji": "đŸĻ…" + }, + { + "name": "Pebble", + "type1": "Rock", + "type2": null, + "base_hp": 45, + "base_attack": 60, + "base_defense": 80, + "base_speed": 20, + "evolution_level": null, + "rarity": 1, + "emoji": "đŸĒ¨" + }, + { + "name": "Crystalback", + "type1": "Rock", + "type2": null, + "base_hp": 70, + "base_attack": 90, + "base_defense": 130, + "base_speed": 35, + "evolution_level": null, + "rarity": 3, + "emoji": "đŸĸ" + }, + { + "name": "Mountainlord", + "type1": "Rock", + "type2": null, + "base_hp": 120, + "base_attack": 110, + "base_defense": 150, + "base_speed": 20, + "evolution_level": null, + "rarity": 4, + "emoji": "â›°ī¸" + }, + { + "name": "Snowball", + "type1": "Ice", + "type2": null, + "base_hp": 40, + "base_attack": 35, + "base_defense": 55, + "base_speed": 45, + "evolution_level": null, + "rarity": 1, + "emoji": "â˜ƒī¸" + }, + { + "name": "Frostbite", + "type1": "Ice", + "type2": null, + "base_hp": 55, + "base_attack": 65, + "base_defense": 70, + "base_speed": 85, + "evolution_level": null, + "rarity": 2, + "emoji": "đŸĻ¨" + }, + { + "name": "Glaciarch", + "type1": "Ice", + "type2": null, + "base_hp": 95, + "base_attack": 80, + "base_defense": 130, + "base_speed": 45, + "evolution_level": null, + "rarity": 4, + "emoji": "â„ī¸" + }, + { + "name": "Furry", + "type1": "Normal", + "type2": null, + "base_hp": 50, + "base_attack": 45, + "base_defense": 45, + "base_speed": 60, + "evolution_level": null, + "rarity": 1, + "emoji": "🐹" + }, + { + "name": "Swiftpaw", + "type1": "Normal", + "type2": null, + "base_hp": 55, + "base_attack": 70, + "base_defense": 50, + "base_speed": 100, + "evolution_level": null, + "rarity": 2, + "emoji": "đŸē" + }, + { + "name": "Harmonix", + "type1": "Normal", + "type2": null, + "base_hp": 80, + "base_attack": 75, + "base_defense": 75, + "base_speed": 80, + "evolution_level": null, + "rarity": 3, + "emoji": "đŸŽĩ" + }, + { + "name": "Steamvent", + "type1": "Water", + "type2": "Fire", + "base_hp": 65, + "base_attack": 80, + "base_defense": 70, + "base_speed": 75, + "evolution_level": null, + "rarity": 3, + "emoji": "💨" + }, + { + "name": "Mossrock", + "type1": "Grass", + "type2": "Rock", + "base_hp": 70, + "base_attack": 65, + "base_defense": 100, + "base_speed": 40, + "evolution_level": null, + "rarity": 3, + "emoji": "🍄" + }, + { + "name": "Frostleaf", + "type1": "Ice", + "type2": "Grass", + "base_hp": 60, + "base_attack": 55, + "base_defense": 85, + "base_speed": 65, + "evolution_level": null, + "rarity": 3, + "emoji": "🧊" } ] \ No newline at end of file diff --git a/run_bot_debug.py b/run_bot_debug.py index b245824..15d14e2 100644 --- a/run_bot_debug.py +++ b/run_bot_debug.py @@ -57,6 +57,10 @@ class PetBotDebug: loop.run_until_complete(self.validate_all_player_data()) print("✅ Player data validation complete") + print("🔄 Validating database integrity...") + loop.run_until_complete(self.validate_database_integrity()) + print("✅ Database integrity validation complete") + print("🔄 Starting background validation task...") self.start_background_validation(loop) print("✅ Background validation started") @@ -142,6 +146,79 @@ class PetBotDebug: print(f"❌ Error during player data validation: {e}") # Don't fail startup if validation fails + async def validate_database_integrity(self): + """Validate database integrity and fix common issues""" + try: + import aiosqlite + async with aiosqlite.connect(self.database.db_path) as db: + # Check for orphaned pets + cursor = await db.execute(""" + SELECT COUNT(*) FROM pets + WHERE species_id NOT IN (SELECT id FROM pet_species) + """) + orphaned_pets = (await cursor.fetchone())[0] + + if orphaned_pets > 0: + print(f"âš ī¸ Found {orphaned_pets} orphaned pets - fixing references...") + # This should not happen with the new startup logic, but just in case + await self.fix_orphaned_pets(db) + + # Check player data accessibility + cursor = await db.execute("SELECT id, nickname FROM players") + players = await cursor.fetchall() + + total_accessible_pets = 0 + for player_id, nickname in players: + cursor = await db.execute(""" + SELECT COUNT(*) FROM pets p + JOIN pet_species ps ON p.species_id = ps.id + WHERE p.player_id = ? + """, (player_id,)) + accessible_pets = (await cursor.fetchone())[0] + total_accessible_pets += accessible_pets + + if accessible_pets > 0: + print(f" ✅ {nickname}: {accessible_pets} pets accessible") + else: + # Get total pets for this player + cursor = await db.execute("SELECT COUNT(*) FROM pets WHERE player_id = ?", (player_id,)) + total_pets = (await cursor.fetchone())[0] + if total_pets > 0: + print(f" âš ī¸ {nickname}: {total_pets} pets but 0 accessible (orphaned)") + + print(f"✅ Database integrity check: {total_accessible_pets} total accessible pets") + + except Exception as e: + print(f"❌ Database integrity validation failed: {e}") + + async def fix_orphaned_pets(self, db): + """Fix orphaned pet references (emergency fallback)""" + try: + # This is a simplified fix - map common species names to current IDs + common_species = ['Flamey', 'Aqua', 'Leafy', 'Vinewrap', 'Bloomtail', 'Furry'] + + for species_name in common_species: + cursor = await db.execute("SELECT id FROM pet_species WHERE name = ?", (species_name,)) + species_row = await cursor.fetchone() + if species_row: + current_id = species_row[0] + # Update any pets that might be referencing old IDs for this species + await db.execute(""" + UPDATE pets SET species_id = ? + WHERE species_id NOT IN (SELECT id FROM pet_species) + AND species_id IN ( + SELECT DISTINCT p.species_id FROM pets p + WHERE p.species_id NOT IN (SELECT id FROM pet_species) + LIMIT 1 + ) + """, (current_id,)) + + await db.commit() + print(" ✅ Orphaned pets fixed") + + except Exception as e: + print(f" ❌ Failed to fix orphaned pets: {e}") + def start_background_validation(self, loop): """Start background task to periodically validate player data""" import asyncio diff --git a/src/database.py b/src/database.py index f8c53ba..e190959 100644 --- a/src/database.py +++ b/src/database.py @@ -36,10 +36,19 @@ class Database: evolution_level INTEGER, evolution_species_id INTEGER, rarity INTEGER DEFAULT 1, + emoji TEXT, FOREIGN KEY (evolution_species_id) REFERENCES pet_species (id) ) """) + # Add emoji column if it doesn't exist (migration) + try: + await db.execute("ALTER TABLE pet_species ADD COLUMN emoji TEXT") + await db.commit() + except Exception: + # Column already exists or other error, which is fine + pass + await db.execute(""" CREATE TABLE IF NOT EXISTS pets ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -452,7 +461,7 @@ class Database: async with aiosqlite.connect(self.db_path) as db: db.row_factory = aiosqlite.Row query = """ - SELECT p.*, ps.name as species_name, ps.type1, ps.type2 + SELECT p.*, ps.name as species_name, ps.type1, ps.type2, ps.emoji FROM pets p JOIN pet_species ps ON p.species_id = ps.id WHERE p.player_id = ? diff --git a/src/game_engine.py b/src/game_engine.py index c194076..dae192c 100644 --- a/src/game_engine.py +++ b/src/game_engine.py @@ -35,19 +35,28 @@ class GameEngine: species_data = json.load(f) async with aiosqlite.connect(self.database.db_path) as db: - for species in species_data: - await db.execute(""" - INSERT OR IGNORE INTO pet_species - (name, type1, type2, base_hp, base_attack, base_defense, - base_speed, evolution_level, rarity) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - """, ( - species["name"], species["type1"], species.get("type2"), - species["base_hp"], species["base_attack"], species["base_defense"], - species["base_speed"], species.get("evolution_level"), - species.get("rarity", 1) - )) - await db.commit() + # Check if species already exist to avoid re-inserting and changing IDs + cursor = await db.execute("SELECT COUNT(*) FROM pet_species") + existing_count = (await cursor.fetchone())[0] + + if existing_count == 0: + # Only insert if no species exist to avoid ID conflicts + for species in species_data: + await db.execute(""" + INSERT OR IGNORE INTO pet_species + (name, type1, type2, base_hp, base_attack, base_defense, + base_speed, evolution_level, rarity, emoji) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + species["name"], species["type1"], species.get("type2"), + species["base_hp"], species["base_attack"], species["base_defense"], + species["base_speed"], species.get("evolution_level"), + species.get("rarity", 1), species.get("emoji", "🐾") + )) + await db.commit() + print(f"✅ Loaded {len(species_data)} pet species into database") + else: + print(f"✅ Found {existing_count} existing pet species - skipping reload to preserve IDs") except FileNotFoundError: await self.create_default_species() @@ -664,6 +673,38 @@ class GameEngine: except Exception as e: print(f"Error checking expired weather: {e}") + async def get_pet_emoji(self, species_name: str) -> str: + """Get emoji for a pet species""" + try: + async with aiosqlite.connect(self.database.db_path) as db: + cursor = await db.execute( + "SELECT emoji FROM pet_species WHERE name = ?", + (species_name,) + ) + row = await cursor.fetchone() + return row[0] if row and row[0] else "🐾" + except Exception: + return "🐾" # Default emoji if something goes wrong + + def format_pet_name_with_emoji(self, pet_name: str, emoji: str = None, species_name: str = None) -> str: + """Format pet name with emoji for display in IRC or web + + Args: + pet_name: The pet's nickname or species name + emoji: Optional emoji to use (if None, will look up by species_name) + species_name: Species name for emoji lookup if emoji not provided + + Returns: + Formatted string like "đŸ”Ĩ Flamey" or "🐉 Infernowyrm" + """ + if emoji: + return f"{emoji} {pet_name}" + elif species_name: + # This would need to be async, but for IRC we can use sync fallback + return f"🐾 {pet_name}" # Use default for now, can be enhanced later + else: + return f"🐾 {pet_name}" + async def shutdown(self): """Gracefully shutdown the game engine""" print("🔄 Shutting down game engine...") diff --git a/start_petbot.sh b/start_petbot.sh index 4ba2c2d..b7fa441 100755 --- a/start_petbot.sh +++ b/start_petbot.sh @@ -1,7 +1,7 @@ #!/bin/bash # # PetBot Startup Script -# Complete one-command startup for PetBot with all dependencies +# Complete one-command startup for PetBot with all dependencies and validation # # Usage: ./start_petbot.sh # @@ -10,6 +10,8 @@ set -e # Exit on any error echo "🐾 Starting PetBot..." echo "====================" +echo "Version: $(date '+%Y-%m-%d %H:%M:%S') - Enhanced with startup validation" +echo "" # Get script directory (works even if called from elsewhere) SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -29,23 +31,22 @@ fi echo "🔄 Activating virtual environment..." source venv/bin/activate +# Upgrade pip to latest version +echo "🔄 Ensuring pip is up to date..." +pip install --upgrade pip -q + # Check if requirements are installed echo "🔄 Checking dependencies..." -if ! python -c "import aiosqlite, irc, dotenv" 2>/dev/null; then - echo "đŸ“Ļ Installing/updating dependencies..." +if ! python -c "import aiosqlite, irc" 2>/dev/null; then + echo "đŸ“Ļ Installing/updating dependencies from requirements.txt..." - # Create requirements.txt if it doesn't exist + # Verify requirements.txt exists if [ ! -f "requirements.txt" ]; then - echo "📝 Creating requirements.txt..." - cat > requirements.txt << EOF -aiosqlite>=0.17.0 -irc>=20.3.0 -python-dotenv>=0.19.0 -aiohttp>=3.8.0 -EOF + echo "❌ requirements.txt not found!" + echo "🔧 Please run install_prerequisites.sh first" + exit 1 fi - pip install --upgrade pip pip install -r requirements.txt echo "✅ Dependencies installed" else @@ -60,44 +61,143 @@ sys.path.append('.') try: from src.database import Database - from src.game_engine import GameEngine + from src.game_engine import GameEngine from src.rate_limiter import RateLimiter from src.irc_connection_manager import IRCConnectionManager from config import ADMIN_USER, IRC_CONFIG, RATE_LIMIT_CONFIG print('✅ Core modules verified') print(f'â„šī¸ Admin user: {ADMIN_USER}') + print(f'â„šī¸ IRC Channel: {IRC_CONFIG[\"channel\"]}') + print(f'â„šī¸ Rate limiting: {\"Enabled\" if RATE_LIMIT_CONFIG[\"enabled\"] else \"Disabled\"}') except ImportError as e: print(f'❌ Module import error: {e}') + print('💡 Try running: ./install_prerequisites.sh') sys.exit(1) " -# Create data directory if it doesn't exist -if [ ! -d "data" ]; then - echo "📁 Creating data directory..." - mkdir -p data +# Create required directories +echo "🔄 Creating required directories..." +mkdir -p data backups logs + +# Check configuration files +echo "🔄 Verifying configuration files..." +config_files=("config/pets.json" "config/locations.json" "config/items.json" "config/achievements.json" "config/gyms.json") +missing_configs=() + +for config_file in "${config_files[@]}"; do + if [ ! -f "$config_file" ]; then + missing_configs+=("$config_file") + fi +done + +if [ ${#missing_configs[@]} -gt 0 ]; then + echo "❌ Missing configuration files:" + for missing in "${missing_configs[@]}"; do + echo " - $missing" + done + echo "💡 These files are required for proper bot operation" + exit 1 fi -# Create backups directory if it doesn't exist -if [ ! -d "backups" ]; then - echo "📁 Creating backups directory..." - mkdir -p backups -fi +echo "✅ All configuration files present" -# Check if database exists, if not mention first-time setup -if [ ! -f "data/petbot.db" ]; then +# Database pre-flight check +if [ -f "data/petbot.db" ]; then + echo "🔄 Validating existing database..." + + # Quick database validation + python3 -c " +import sqlite3 +import sys + +try: + conn = sqlite3.connect('data/petbot.db') + cursor = conn.cursor() + + # Check essential tables exist + tables = ['players', 'pets', 'pet_species', 'locations'] + for table in tables: + cursor.execute(f'SELECT COUNT(*) FROM {table}') + count = cursor.fetchone()[0] + print(f' ✅ {table}: {count} records') + + conn.close() + print('✅ Database validation passed') + +except Exception as e: + print(f'❌ Database validation failed: {e}') + print('💡 Database may be corrupted - backup and recreate if needed') + sys.exit(1) +" +else echo "â„šī¸ First-time setup detected - database will be created automatically" fi -# Display startup information +# Pre-startup system test +echo "🔄 Running pre-startup system test..." +python3 -c " +import sys +sys.path.append('.') + +try: + # Test basic imports and initialization + from run_bot_debug import PetBotDebug + + # Create a test bot instance to verify everything loads + print(' 🔧 Testing bot initialization...') + bot = PetBotDebug() + print(' ✅ Bot instance created successfully') + print('✅ Pre-startup test passed') + +except Exception as e: + print(f'❌ Pre-startup test failed: {e}') + import traceback + traceback.print_exc() + sys.exit(1) +" + +# Display comprehensive startup information echo "" -echo "🚀 Launching PetBot with Auto-Reconnect..." -echo "🌐 Web interface will be available at: http://localhost:8080" -echo "đŸ’Ŧ IRC: Connecting to irc.libera.chat #petz" -echo "📊 Features: Rate limiting, auto-reconnect, web interface, team builder" +echo "🚀 Launching PetBot with Enhanced Features..." +echo "=============================================" +echo "🌐 Web interface: http://localhost:8080" +echo "📱 Public access: http://petz.rdx4.com/" +echo "đŸ’Ŧ IRC Server: irc.libera.chat" +echo "đŸ“ĸ IRC Channel: #petz" +echo "👤 Admin User: $(python3 -c 'from config import ADMIN_USER; print(ADMIN_USER)')" +echo "" +echo "🎮 Features Available:" +echo " ✅ 33 Pet Species with Emojis" +echo " ✅ 6 Exploration Locations" +echo " ✅ Team Builder with PIN Verification" +echo " ✅ Achievement System" +echo " ✅ Gym Battles" +echo " ✅ Weather System" +echo " ✅ Rate Limiting & Anti-Abuse" +echo " ✅ Auto-Reconnection" +echo " ✅ Startup Data Validation" +echo " ✅ Background Monitoring" +echo "" +echo "🔧 Technical Details:" +echo " 📊 Database: SQLite with validation" +echo " 🌐 Webserver: Integrated with bot instance" +echo " đŸ›Ąī¸ Security: Rate limiting enabled" +echo " 🔄 Reliability: Auto-reconnect on failure" +echo " 📈 Monitoring: Background validation every 30min" echo "" echo "Press Ctrl+C to stop the bot" -echo "====================" +echo "=============================================" echo "" -# Launch the bot -exec python run_bot_with_reconnect.py \ No newline at end of file +# Launch the appropriate bot based on what's available +if [ -f "run_bot_with_reconnect.py" ]; then + echo "🚀 Starting with auto-reconnect support..." + exec python run_bot_with_reconnect.py +elif [ -f "run_bot_debug.py" ]; then + echo "🚀 Starting in debug mode..." + exec python run_bot_debug.py +else + echo "❌ No bot startup file found!" + echo "💡 Expected: run_bot_with_reconnect.py or run_bot_debug.py" + exit 1 +fi \ No newline at end of file diff --git a/webserver.py b/webserver.py index 0cc3e08..3ca4eee 100644 --- a/webserver.py +++ b/webserver.py @@ -2222,7 +2222,9 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): async with aiosqlite.connect(database.db_path) as db: # Get all pet species with evolution information (no duplicates) cursor = await db.execute(""" - SELECT DISTINCT ps.*, + SELECT DISTINCT ps.id, ps.name, ps.type1, ps.type2, ps.base_hp, ps.base_attack, + ps.base_defense, ps.base_speed, ps.evolution_level, ps.evolution_species_id, + ps.rarity, ps.emoji, evolve_to.name as evolves_to_name, (SELECT COUNT(*) FROM location_spawns ls WHERE ls.species_id = ps.id) as location_count FROM pet_species ps @@ -2237,8 +2239,8 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): 'id': row[0], 'name': row[1], 'type1': row[2], 'type2': row[3], 'base_hp': row[4], 'base_attack': row[5], 'base_defense': row[6], 'base_speed': row[7], 'evolution_level': row[8], - 'evolution_species_id': row[9], 'rarity': row[10], - 'evolves_to_name': row[11], 'location_count': row[12] + 'evolution_species_id': row[9], 'rarity': row[10], 'emoji': row[11], + 'evolves_to_name': row[12], 'location_count': row[13] } pets.append(pet_dict) @@ -2359,7 +2361,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): petdex_html += f"""
-

{pet['name']}

+

{pet.get('emoji', '🐾')} {pet['name']}

{type_str}
@@ -2478,7 +2480,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): # Get player pets cursor = await db.execute(""" - SELECT p.*, ps.name as species_name, ps.type1, ps.type2 + SELECT p.*, ps.name as species_name, ps.type1, ps.type2, ps.emoji FROM pets p JOIN pet_species ps ON p.species_id = ps.id WHERE p.player_id = ? @@ -2493,7 +2495,8 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): 'hp': row[6], 'max_hp': row[7], 'attack': row[8], 'defense': row[9], 'speed': row[10], 'happiness': row[11], 'caught_at': row[12], 'is_active': bool(row[13]), # Convert to proper boolean - 'team_order': row[14], 'species_name': row[15], 'type1': row[16], 'type2': row[17] + 'team_order': row[14], 'species_name': row[15], 'type1': row[16], 'type2': row[17], + 'emoji': row[18] if row[18] else '🐾' # Add emoji support } pets.append(pet_dict) @@ -2717,7 +2720,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): pets_html += f""" {status} - {name} + {pet.get('emoji', '🐾')} {name} {pet['species_name']} {type_str} {pet['level']} @@ -3456,6 +3459,9 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): if pet['type2']: type_str += f"/{pet['type2']}" + # Get emoji for the pet species + emoji = pet.get('emoji', '🐾') # Default to paw emoji if none specified + # Debug logging print(f"Making pet card for {name} (ID: {pet['id']}): is_active={pet['is_active']}, passed_is_active={is_active}, status_class={status_class}, team_order={pet.get('team_order', 'None')}") @@ -3466,7 +3472,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): return f"""
-

{name}

+

{emoji} {name}

{status}
Level {pet['level']} {pet['species_name']}