Add comprehensive startup script validation and enhanced pet system
- Enhanced start_petbot.sh with extensive validation and error checking - Added emoji support to pet species system with database migration - Expanded pet species from 9 to 33 unique pets with balanced spawn rates - Improved database integrity validation and orphaned pet detection - Added comprehensive pre-startup testing and configuration validation - Enhanced locations with diverse species spawning across all areas - Added dual-type pets and rarity-based spawn distribution - Improved startup information display with feature overview - Added background monitoring and validation systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
add7731d80
commit
fca0423c84
8 changed files with 640 additions and 81 deletions
|
|
@ -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": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
]
|
||||
}
|
||||
]
|
||||
315
config/pets.json
315
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": "🧊"
|
||||
}
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = ?
|
||||
|
|
|
|||
|
|
@ -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...")
|
||||
|
|
|
|||
164
start_petbot.sh
164
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
|
||||
# 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
|
||||
22
webserver.py
22
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"""
|
||||
<div class="pet-card" style="border-left: 4px solid {rarity_color};">
|
||||
<div class="pet-header">
|
||||
<h3 style="color: {rarity_color};">{pet['name']}</h3>
|
||||
<h3 style="color: {rarity_color};">{pet.get('emoji', '🐾')} {pet['name']}</h3>
|
||||
<span class="type-badge">{type_str}</span>
|
||||
</div>
|
||||
<div class="pet-stats">
|
||||
|
|
@ -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"""
|
||||
<tr>
|
||||
<td class="{status_class}">{status}</td>
|
||||
<td><strong>{name}</strong></td>
|
||||
<td><strong>{pet.get('emoji', '🐾')} {name}</strong></td>
|
||||
<td>{pet['species_name']}</td>
|
||||
<td><span class="type-badge">{type_str}</span></td>
|
||||
<td>{pet['level']}</td>
|
||||
|
|
@ -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"""
|
||||
<div class="pet-card {status_class}" draggable="true" data-pet-id="{pet['id']}" data-active="{str(is_active).lower()}" data-team-order="{pet.get('team_order', '')}">
|
||||
<div class="pet-header">
|
||||
<h4 class="pet-name">{name}</h4>
|
||||
<h4 class="pet-name">{emoji} {name}</h4>
|
||||
<div class="status-badge">{status}</div>
|
||||
</div>
|
||||
<div class="pet-species">Level {pet['level']} {pet['species_name']}</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue