diff --git a/.gitignore b/.gitignore index 81d0570..06fad92 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,4 @@ Thumbs.db # IRC bot specific *.pid -*.lock \ No newline at end of file +*.lockbackup_bots/ diff --git a/run_bot.py b/backup_bots/run_bot.py similarity index 100% rename from run_bot.py rename to backup_bots/run_bot.py diff --git a/run_bot_original.py b/backup_bots/run_bot_original.py similarity index 100% rename from run_bot_original.py rename to backup_bots/run_bot_original.py diff --git a/modules/battle_system.py b/modules/battle_system.py index 4a7a0a8..d50abeb 100644 --- a/modules/battle_system.py +++ b/modules/battle_system.py @@ -224,74 +224,74 @@ class BattleSystem(BaseModule): return defeated_pet = gym_team[current_pet_index] - - self.send_message(channel, f"πŸŽ‰ {nickname}: You defeated {defeated_pet['species_name']}!") - - # Award experience for defeating gym pet - await self.award_battle_experience(channel, nickname, player, defeated_pet, "gym") - - # Check if there are more pets - if await self.database.advance_gym_battle(player["id"]): - # More pets to fight - next_index = current_pet_index + 1 - next_pet = gym_team[next_index] - self.send_message(channel, - f"πŸ₯Š {gym_battle['leader_name']} sends out {next_pet['species_name']} (Lv.{next_pet['level']})!") + self.send_message(channel, f"πŸŽ‰ {nickname}: You defeated {defeated_pet['species_name']}!") - # Start battle with next gym pet - active_pets = await self.database.get_active_pets(player["id"]) - player_pet = active_pets[0] # Use first active pet + # Award experience for defeating gym pet + await self.award_battle_experience(channel, nickname, player, defeated_pet, "gym") - # Create gym pet data for battle engine - next_gym_pet_data = { - "species_name": next_pet["species_name"], - "level": next_pet["level"], - "type1": next_pet["type1"], - "type2": next_pet["type2"], - "stats": { - "hp": next_pet["hp"], - "attack": next_pet["attack"], - "defense": next_pet["defense"], - "speed": next_pet["speed"] + # Check if there are more pets + if await self.database.advance_gym_battle(player["id"]): + # More pets to fight + next_index = current_pet_index + 1 + next_pet = gym_team[next_index] + + self.send_message(channel, + f"πŸ₯Š {gym_battle['leader_name']} sends out {next_pet['species_name']} (Lv.{next_pet['level']})!") + + # Start battle with next gym pet + active_pets = await self.database.get_active_pets(player["id"]) + player_pet = active_pets[0] # Use first active pet + + # Create gym pet data for battle engine + next_gym_pet_data = { + "species_name": next_pet["species_name"], + "level": next_pet["level"], + "type1": next_pet["type1"], + "type2": next_pet["type2"], + "stats": { + "hp": next_pet["hp"], + "attack": next_pet["attack"], + "defense": next_pet["defense"], + "speed": next_pet["speed"] + } } - } - - # Start next battle - battle = await self.game_engine.battle_engine.start_battle(player["id"], player_pet, next_gym_pet_data) - - self.send_message(channel, - f"βš”οΈ Your {player_pet['species_name']} (HP: {battle['player_hp']}/{player_pet['max_hp']}) vs {next_pet['species_name']} (HP: {battle['wild_hp']}/{next_pet['hp']})") - - # Show available moves - moves_colored = " | ".join([ - f"{self.get_move_color(move['type'])}{move['name']}\x0F" - for move in battle["available_moves"] - ]) - self.send_message(channel, f"🎯 Moves: {moves_colored} | Use !attack or !use ") + + # Start next battle + battle = await self.game_engine.battle_engine.start_battle(player["id"], player_pet, next_gym_pet_data) + + self.send_message(channel, + f"βš”οΈ Your {player_pet['species_name']} (HP: {battle['player_hp']}/{player_pet['max_hp']}) vs {next_pet['species_name']} (HP: {battle['wild_hp']}/{next_pet['hp']})") + + # Show available moves + moves_colored = " | ".join([ + f"{self.get_move_color(move['type'])}{move['name']}\x0F" + for move in battle["available_moves"] + ]) + self.send_message(channel, f"🎯 Moves: {moves_colored} | Use !attack or !use ") + + else: + # All gym pets defeated - gym victory! + result = await self.database.end_gym_battle(player["id"], victory=True) + + self.send_message(channel, f"πŸ† {nickname}: You defeated all of {gym_battle['leader_name']}'s pets!") + self.send_message(channel, + f"{gym_battle['badge_icon']} {gym_battle['leader_name']}: \"Impressive! You've earned the {gym_battle['gym_name']} badge!\"") + self.send_message(channel, f"πŸŽ‰ {nickname} earned the {gym_battle['gym_name']} badge {gym_battle['badge_icon']}!") + + # Award rewards based on difficulty + money_reward = 500 + (result["difficulty_level"] * 100) + self.send_message(channel, f"πŸ’° Rewards: ${money_reward} | 🌟 Gym mastery increased!") else: - # All gym pets defeated - gym victory! - result = await self.database.end_gym_battle(player["id"], victory=True) + # Player lost gym battle + result = await self.database.end_gym_battle(player["id"], victory=False) - self.send_message(channel, f"πŸ† {nickname}: You defeated all of {gym_battle['leader_name']}'s pets!") + self.send_message(channel, f"πŸ’€ {nickname}: Your pet fainted!") self.send_message(channel, - f"{gym_battle['badge_icon']} {gym_battle['leader_name']}: \"Impressive! You've earned the {gym_battle['gym_name']} badge!\"") - self.send_message(channel, f"πŸŽ‰ {nickname} earned the {gym_battle['gym_name']} badge {gym_battle['badge_icon']}!") - - # Award rewards based on difficulty - money_reward = 500 + (result["difficulty_level"] * 100) - self.send_message(channel, f"πŸ’° Rewards: ${money_reward} | 🌟 Gym mastery increased!") - - else: - # Player lost gym battle - result = await self.database.end_gym_battle(player["id"], victory=False) - - self.send_message(channel, f"πŸ’€ {nickname}: Your pet fainted!") - self.send_message(channel, - f"{gym_battle['badge_icon']} {gym_battle['leader_name']}: \"Good battle! Train more and come back stronger!\"") - self.send_message(channel, - f"πŸ’‘ {nickname}: Try leveling up your pets or bringing items to heal during battle!") + f"{gym_battle['badge_icon']} {gym_battle['leader_name']}: \"Good battle! Train more and come back stronger!\"") + self.send_message(channel, + f"πŸ’‘ {nickname}: Try leveling up your pets or bringing items to heal during battle!") except Exception as e: self.send_message(channel, f"❌ {nickname}: Gym battle error occurred - please !forfeit and try again") diff --git a/modules/exploration.py b/modules/exploration.py index b59a83a..ccbc2e3 100644 --- a/modules/exploration.py +++ b/modules/exploration.py @@ -46,6 +46,9 @@ class Exploration(BaseModule): if pet["type2"]: type_str += f"/{pet['type2']}" + # Record the encounter + await self.database.record_encounter(player["id"], pet["species_name"]) + self.send_message(channel, f"🐾 {nickname}: A wild Level {pet['level']} {pet['species_name']} ({type_str}) appeared in {encounter['location']}!") self.send_message(channel, f"Choose your action: !battle to fight it, or !catch to try catching it directly!") @@ -200,6 +203,9 @@ class Exploration(BaseModule): # Successful catch during battle result = await self.game_engine.attempt_catch_current_location(player["id"], wild_pet) + # Record the successful catch + await self.database.record_encounter(player["id"], wild_pet["species_name"], was_caught=True) + # End the battle await_result = await self.game_engine.battle_engine.end_battle(player["id"], "caught") @@ -236,6 +242,9 @@ class Exploration(BaseModule): # Check for achievements after successful catch if "Success!" in result: + # Record the successful catch + await self.database.record_encounter(player["id"], target_pet["species_name"], was_caught=True) + # Award experience for successful catch await self.award_catch_experience(channel, nickname, player, target_pet) diff --git a/src/database.py b/src/database.py index 4a9ddee..42aeec8 100644 --- a/src/database.py +++ b/src/database.py @@ -271,6 +271,21 @@ class Database: ) """) + await db.execute(""" + CREATE TABLE IF NOT EXISTS player_encounters ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + player_id INTEGER NOT NULL, + species_id INTEGER NOT NULL, + first_encounter_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + total_encounters INTEGER DEFAULT 1, + last_encounter_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + caught_count INTEGER DEFAULT 0, + FOREIGN KEY (player_id) REFERENCES players (id), + FOREIGN KEY (species_id) REFERENCES pet_species (id), + UNIQUE(player_id, species_id) + ) + """) + await db.commit() async def get_player(self, nickname: str) -> Optional[Dict]: @@ -1209,4 +1224,96 @@ class Database: await self.record_gym_victory(player_id, battle_dict["gym_id"]) await db.commit() - return result \ No newline at end of file + return result + + async def record_encounter(self, player_id: int, species_name: str, was_caught: bool = False) -> bool: + """Record a player encountering a pet species""" + try: + async with aiosqlite.connect(self.db_path) as db: + # Get species ID + cursor = await db.execute( + "SELECT id FROM pet_species WHERE name = ?", (species_name,) + ) + species_row = await cursor.fetchone() + if not species_row: + return False + + species_id = species_row[0] + + # Check if encounter already exists + cursor = await db.execute(""" + SELECT total_encounters, caught_count FROM player_encounters + WHERE player_id = ? AND species_id = ? + """, (player_id, species_id)) + existing = await cursor.fetchone() + + if existing: + # Update existing encounter + new_total = existing[0] + 1 + new_caught = existing[1] + (1 if was_caught else 0) + await db.execute(""" + UPDATE player_encounters + SET total_encounters = ?, last_encounter_date = CURRENT_TIMESTAMP, caught_count = ? + WHERE player_id = ? AND species_id = ? + """, (new_total, new_caught, player_id, species_id)) + else: + # Create new encounter record + caught_count = 1 if was_caught else 0 + await db.execute(""" + INSERT INTO player_encounters (player_id, species_id, caught_count) + VALUES (?, ?, ?) + """, (player_id, species_id, caught_count)) + + await db.commit() + return True + + except Exception as e: + print(f"Error recording encounter: {e}") + return False + + async def get_player_encounters(self, player_id: int) -> List[Dict]: + """Get all encounters for a player""" + async with aiosqlite.connect(self.db_path) as db: + db.row_factory = aiosqlite.Row + cursor = await db.execute(""" + SELECT pe.*, ps.name as species_name, ps.type1, ps.type2, ps.rarity + FROM player_encounters pe + JOIN pet_species ps ON pe.species_id = ps.id + WHERE pe.player_id = ? + ORDER BY pe.first_encounter_date ASC + """, (player_id,)) + + rows = await cursor.fetchall() + return [dict(row) for row in rows] + + async def get_encounter_stats(self, player_id: int) -> Dict: + """Get encounter statistics for a player""" + async with aiosqlite.connect(self.db_path) as db: + # Total species encountered + cursor = await db.execute(""" + SELECT COUNT(*) FROM player_encounters WHERE player_id = ? + """, (player_id,)) + species_encountered = (await cursor.fetchone())[0] + + # Total encounters + cursor = await db.execute(""" + SELECT SUM(total_encounters) FROM player_encounters WHERE player_id = ? + """, (player_id,)) + total_encounters_result = await cursor.fetchone() + total_encounters = total_encounters_result[0] if total_encounters_result[0] else 0 + + # Total species available + cursor = await db.execute(""" + SELECT COUNT(*) FROM pet_species + """) + total_species = (await cursor.fetchone())[0] + + # Calculate completion percentage + completion_percentage = (species_encountered / total_species * 100) if total_species > 0 else 0 + + return { + "species_encountered": species_encountered, + "total_encounters": total_encounters, + "total_species": total_species, + "completion_percentage": round(completion_percentage, 1) + } \ No newline at end of file diff --git a/webserver.py b/webserver.py index e63d165..7af62b2 100644 --- a/webserver.py +++ b/webserver.py @@ -39,6 +39,8 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): self.serve_leaderboard() elif path == '/locations': self.serve_locations() + elif path == '/petdex': + self.serve_petdex() else: self.send_error(404, "Page not found") @@ -148,6 +150,11 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):

πŸ—ΊοΈ Locations

Explore all game locations and see what pets can be found where

+ + +

πŸ“– Petdex

+

Complete encyclopedia of all available pets with stats, types, and evolution info

+
@@ -819,6 +826,376 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):

+""" + + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(html.encode()) + + def serve_petdex(self): + """Serve the petdex page with all pet species data""" + # Get database instance from the server class + database = self.server.database if hasattr(self.server, 'database') else None + + if not database: + self.serve_error_page("Petdex", "Database not available") + return + + # Fetch petdex data + try: + import asyncio + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + petdex_data = loop.run_until_complete(self.fetch_petdex_data(database)) + loop.close() + + self.serve_petdex_data(petdex_data) + + except Exception as e: + print(f"Error fetching petdex data: {e}") + self.serve_error_page("Petdex", f"Error loading petdex: {str(e)}") + + async def fetch_petdex_data(self, database): + """Fetch all pet species data from database""" + try: + import aiosqlite + async with aiosqlite.connect(database.db_path) as db: + # Get all pet species with evolution information + cursor = await db.execute(""" + SELECT ps.*, + 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 + LEFT JOIN pet_species evolve_to ON ps.evolution_species_id = evolve_to.id + ORDER BY ps.rarity ASC, ps.name ASC + """) + + rows = await cursor.fetchall() + pets = [] + for row in rows: + pet_dict = { + '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] + } + pets.append(pet_dict) + + # Get spawn locations for each pet + for pet in pets: + cursor = await db.execute(""" + SELECT l.name, ls.min_level, ls.max_level, ls.spawn_rate + FROM location_spawns ls + JOIN locations l ON ls.location_id = l.id + WHERE ls.species_id = ? + ORDER BY l.name ASC + """, (pet['id'],)) + spawn_rows = await cursor.fetchall() + pet['spawn_locations'] = [] + for spawn_row in spawn_rows: + spawn_dict = { + 'location_name': spawn_row[0], 'min_level': spawn_row[1], + 'max_level': spawn_row[2], 'spawn_rate': spawn_row[3] + } + pet['spawn_locations'].append(spawn_dict) + + return pets + + except Exception as e: + print(f"Database error fetching petdex: {e}") + return [] + + def serve_petdex_data(self, petdex_data): + """Serve petdex page with all pet species data""" + + # Build pet cards HTML grouped by rarity + rarity_names = {1: "Common", 2: "Uncommon", 3: "Rare", 4: "Epic", 5: "Legendary"} + rarity_colors = {1: "#ffffff", 2: "#1eff00", 3: "#0070dd", 4: "#a335ee", 5: "#ff8000"} + + pets_by_rarity = {} + for pet in petdex_data: + rarity = pet['rarity'] + if rarity not in pets_by_rarity: + pets_by_rarity[rarity] = [] + pets_by_rarity[rarity].append(pet) + + petdex_html = "" + total_species = len(petdex_data) + + for rarity in sorted(pets_by_rarity.keys()): + pets_in_rarity = pets_by_rarity[rarity] + rarity_name = rarity_names.get(rarity, f"Rarity {rarity}") + rarity_color = rarity_colors.get(rarity, "#ffffff") + + petdex_html += f""" +
+

+ {rarity_name} ({len(pets_in_rarity)} species) +

+
""" + + for pet in pets_in_rarity: + # Build type display + type_str = pet['type1'] + if pet['type2']: + type_str += f"/{pet['type2']}" + + # Build evolution info + evolution_info = "" + if pet['evolution_level'] and pet['evolves_to_name']: + evolution_info = f"
Evolves: Level {pet['evolution_level']} β†’ {pet['evolves_to_name']}" + elif pet['evolution_level']: + evolution_info = f"
Evolves: Level {pet['evolution_level']}" + + # Build spawn locations + spawn_info = "" + if pet['spawn_locations']: + locations = [f"{loc['location_name']} (Lv.{loc['min_level']}-{loc['max_level']})" + for loc in pet['spawn_locations'][:3]] + if len(pet['spawn_locations']) > 3: + locations.append(f"+{len(pet['spawn_locations']) - 3} more") + spawn_info = f"
Found in: {', '.join(locations)}" + else: + spawn_info = "
Found in: Not yet available" + + # Calculate total base stats + total_stats = pet['base_hp'] + pet['base_attack'] + pet['base_defense'] + pet['base_speed'] + + petdex_html += f""" +
+
+

{pet['name']}

+ {type_str} +
+
+
+ HP: {pet['base_hp']} + ATK: {pet['base_attack']} +
+
+ DEF: {pet['base_defense']} + SPD: {pet['base_speed']} +
+
Total: {total_stats}
+
+
+ Rarity: {rarity_name}{evolution_info}{spawn_info} +
+
""" + + petdex_html += """ +
+
""" + + if not petdex_data: + petdex_html = """ +
+

No pet species found!

+

The petdex appears to be empty. Contact an administrator.

+
""" + + html = f""" + + + + + PetBot - Petdex + + + + ← Back to Game Hub + +
+

πŸ“– Petdex - Complete Pet Encyclopedia

+

Comprehensive guide to all available pet species

+
+ +
+
+
+
{total_species}
+
Total Species
+
+
+
{len([p for p in petdex_data if p['type1'] == 'Fire' or p['type2'] == 'Fire'])}
+
Fire Types
+
+
+
{len([p for p in petdex_data if p['type1'] == 'Water' or p['type2'] == 'Water'])}
+
Water Types
+
+
+
{len([p for p in petdex_data if p['evolution_level']])}
+
Can Evolve
+
+
+

🎯 Pets are organized by rarity. Use !wild <location> in #petz to see what spawns where!

+
+ + {petdex_html} + + """ self.send_response(200) @@ -945,11 +1322,68 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): } inventory.append(item_dict) + # Get player gym badges + cursor = await db.execute(""" + SELECT g.name, g.badge_name, g.badge_icon, l.name as location_name, + pgb.victories, pgb.first_victory_date, pgb.highest_difficulty + FROM player_gym_battles pgb + JOIN gyms g ON pgb.gym_id = g.id + JOIN locations l ON g.location_id = l.id + WHERE pgb.player_id = ? AND pgb.victories > 0 + ORDER BY pgb.first_victory_date ASC + """, (player_dict['id'],)) + gym_badges_rows = await cursor.fetchall() + gym_badges = [] + for row in gym_badges_rows: + badge_dict = { + 'gym_name': row[0], 'badge_name': row[1], 'badge_icon': row[2], + 'location_name': row[3], 'victories': row[4], + 'first_victory_date': row[5], 'highest_difficulty': row[6] + } + gym_badges.append(badge_dict) + + # Get player encounters + cursor = await db.execute(""" + SELECT pe.*, ps.name as species_name, ps.type1, ps.type2, ps.rarity + FROM player_encounters pe + JOIN pet_species ps ON pe.species_id = ps.id + WHERE pe.player_id = ? + ORDER BY pe.first_encounter_date ASC + """, (player_dict['id'],)) + encounters_rows = await cursor.fetchall() + encounters = [] + for row in encounters_rows: + encounter_dict = { + 'species_name': row[6], 'type1': row[7], 'type2': row[8], 'rarity': row[9], + 'total_encounters': row[4], 'caught_count': row[5], 'first_encounter_date': row[2] + } + encounters.append(encounter_dict) + + # Get encounter stats + cursor = await db.execute(""" + SELECT COUNT(*) as species_encountered, + SUM(total_encounters) as total_encounters, + (SELECT COUNT(*) FROM pet_species) as total_species + FROM player_encounters + WHERE player_id = ? + """, (player_dict['id'],)) + stats_row = await cursor.fetchone() + encounter_stats = { + 'species_encountered': stats_row[0] if stats_row[0] else 0, + 'total_encounters': stats_row[1] if stats_row[1] else 0, + 'total_species': stats_row[2] if stats_row[2] else 0 + } + completion_percentage = (encounter_stats['species_encountered'] / encounter_stats['total_species'] * 100) if encounter_stats['total_species'] > 0 else 0 + encounter_stats['completion_percentage'] = round(completion_percentage, 1) + return { 'player': player_dict, 'pets': pets, 'achievements': achievements, - 'inventory': inventory + 'inventory': inventory, + 'gym_badges': gym_badges, + 'encounters': encounters, + 'encounter_stats': encounter_stats } except Exception as e: @@ -1106,6 +1540,9 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): pets = player_data['pets'] achievements = player_data['achievements'] inventory = player_data.get('inventory', []) + gym_badges = player_data.get('gym_badges', []) + encounters = player_data.get('encounters', []) + encounter_stats = player_data.get('encounter_stats', {}) # Calculate stats active_pets = [pet for pet in pets if pet['is_active']] @@ -1193,6 +1630,47 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): No items yet. Try exploring to find useful items! """ + # Build gym badges HTML + badges_html = "" + if gym_badges: + for badge in gym_badges: + badge_date = badge['first_victory_date'].split()[0] if badge['first_victory_date'] else 'Unknown' + badges_html += f""" +
+ {badge['badge_icon']} {badge['badge_name']}
+ Earned from {badge['gym_name']} ({badge['location_name']})
+ First victory: {badge_date} | Total victories: {badge['victories']} | Highest difficulty: Level {badge['highest_difficulty']} +
""" + else: + badges_html = """ +
+ No gym badges yet. Challenge gyms to earn badges and prove your training skills! +
""" + + # Build encounters HTML + encounters_html = "" + if encounters: + rarity_colors = {1: "#ffffff", 2: "#1eff00", 3: "#0070dd", 4: "#a335ee", 5: "#ff8000"} + for encounter in encounters: + rarity_color = rarity_colors.get(encounter['rarity'], "#ffffff") + type_str = encounter['type1'] + if encounter['type2']: + type_str += f"/{encounter['type2']}" + + encounter_date = encounter['first_encounter_date'].split()[0] if encounter['first_encounter_date'] else 'Unknown' + + encounters_html += f""" +
+ {encounter['species_name']} {type_str}
+ Encountered {encounter['total_encounters']} times | Caught {encounter['caught_count']} times
+ First seen: {encounter_date} +
""" + else: + encounters_html = """ +
+ No pets encountered yet. Use !explore to discover wild pets! +
""" + html = f""" @@ -1381,6 +1859,14 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
{len(achievements)}
Achievements
+
+
{encounter_stats.get('species_encountered', 0)}
+
Species Seen
+
+
+
{encounter_stats.get('completion_percentage', 0)}%
+
Petdex Complete
+
@@ -1420,6 +1906,25 @@ class PetBotRequestHandler(BaseHTTPRequestHandler): {inventory_html} + +
+
πŸ† Gym Badges
+
+ {badges_html} +
+
+ +
+
πŸ‘οΈ Pet Encounters
+
+
+

Species discovered: {encounter_stats.get('species_encountered', 0)}/{encounter_stats.get('total_species', 0)} + ({encounter_stats.get('completion_percentage', 0)}% complete)

+

Total encounters: {encounter_stats.get('total_encounters', 0)}

+
+ {encounters_html} +
+
"""