Add comprehensive web interface enhancements and encounter tracking
Major Features Added: - Complete petdex page showing all available pets with stats, types, evolution info - Encounter tracking system recording pet discoveries and catch statistics - Gym badges display on player profiles with victory counts and dates - Enhanced player profiles with discovery progress and completion percentages Technical Implementation: - New /petdex route with rarity-organized pet encyclopedia - Database encounter tracking with automatic integration into exploration/catch - Updated webserver.py with encounter data fetching and display - Fixed battle_system.py syntax error in gym battle completion logic - Organized project by moving unused bot files to backup_bots/ folder Database Changes: - Added player_encounters table for tracking discoveries - Added methods: record_encounter, get_player_encounters, get_encounter_stats - Enhanced player profile queries to include gym badges and encounters Web Interface Updates: - Petdex page with search stats, rarity grouping, and spawn location info - Player profiles now show species seen, completion %, gym badges earned - Encounter section displaying discovered pets with catch statistics - Updated navigation to include petdex link on main game hub 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bd455f1be5
commit
1ce7158200
7 changed files with 684 additions and 63 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -75,4 +75,4 @@ Thumbs.db
|
|||
|
||||
# IRC bot specific
|
||||
*.pid
|
||||
*.lock
|
||||
*.lockbackup_bots/
|
||||
|
|
|
|||
|
|
@ -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 <move> or !use <item>")
|
||||
|
||||
# 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 <move> or !use <item>")
|
||||
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
109
src/database.py
109
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
|
||||
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)
|
||||
}
|
||||
507
webserver.py
507
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):
|
|||
<h3>🗺️ Locations</h3>
|
||||
<p>Explore all game locations and see what pets can be found where</p>
|
||||
</a>
|
||||
|
||||
<a href="/petdex" class="nav-card">
|
||||
<h3>📖 Petdex</h3>
|
||||
<p>Complete encyclopedia of all available pets with stats, types, and evolution info</p>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="status">
|
||||
|
|
@ -819,6 +826,376 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
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"""
|
||||
<div class="rarity-section">
|
||||
<h2 style="color: {rarity_color}; border-bottom: 2px solid {rarity_color}; padding-bottom: 10px;">
|
||||
{rarity_name} ({len(pets_in_rarity)} species)
|
||||
</h2>
|
||||
<div class="pets-grid">"""
|
||||
|
||||
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"<br><strong>Evolves:</strong> Level {pet['evolution_level']} → {pet['evolves_to_name']}"
|
||||
elif pet['evolution_level']:
|
||||
evolution_info = f"<br><strong>Evolves:</strong> 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"<br><strong>Found in:</strong> {', '.join(locations)}"
|
||||
else:
|
||||
spawn_info = "<br><strong>Found in:</strong> Not yet available"
|
||||
|
||||
# Calculate total base stats
|
||||
total_stats = pet['base_hp'] + pet['base_attack'] + pet['base_defense'] + pet['base_speed']
|
||||
|
||||
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>
|
||||
<span class="type-badge">{type_str}</span>
|
||||
</div>
|
||||
<div class="pet-stats">
|
||||
<div class="stat-row">
|
||||
<span>HP: {pet['base_hp']}</span>
|
||||
<span>ATK: {pet['base_attack']}</span>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>DEF: {pet['base_defense']}</span>
|
||||
<span>SPD: {pet['base_speed']}</span>
|
||||
</div>
|
||||
<div class="total-stats">Total: {total_stats}</div>
|
||||
</div>
|
||||
<div class="pet-info">
|
||||
<strong>Rarity:</strong> {rarity_name}{evolution_info}{spawn_info}
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
petdex_html += """
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
if not petdex_data:
|
||||
petdex_html = """
|
||||
<div style="text-align: center; padding: 60px; color: var(--text-secondary);">
|
||||
<h2>No pet species found!</h2>
|
||||
<p>The petdex appears to be empty. Contact an administrator.</p>
|
||||
</div>"""
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PetBot - Petdex</title>
|
||||
<style>
|
||||
:root {{
|
||||
--bg-primary: #0f0f23;
|
||||
--bg-secondary: #1e1e3f;
|
||||
--bg-tertiary: #2a2a4a;
|
||||
--text-primary: #cccccc;
|
||||
--text-secondary: #999999;
|
||||
--text-accent: #66ff66;
|
||||
--border-color: #333366;
|
||||
--hover-color: #3a3a5a;
|
||||
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--shadow-dark: 0 4px 20px rgba(0,0,0,0.3);
|
||||
}}
|
||||
|
||||
body {{
|
||||
font-family: 'Segoe UI', 'Roboto', 'Arial', sans-serif;
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
}}
|
||||
|
||||
.header {{
|
||||
text-align: center;
|
||||
background: var(--gradient-primary);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: var(--shadow-dark);
|
||||
}}
|
||||
|
||||
.header h1 {{
|
||||
margin: 0;
|
||||
font-size: 2.5em;
|
||||
font-weight: 700;
|
||||
}}
|
||||
|
||||
.back-link {{
|
||||
color: var(--text-accent);
|
||||
text-decoration: none;
|
||||
margin-bottom: 20px;
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
}}
|
||||
|
||||
.back-link:hover {{
|
||||
text-decoration: underline;
|
||||
}}
|
||||
|
||||
.stats-summary {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
|
||||
.stat-card {{
|
||||
background: var(--bg-secondary);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
border: 1px solid var(--border-color);
|
||||
}}
|
||||
|
||||
.stat-value {{
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: var(--text-accent);
|
||||
margin-bottom: 5px;
|
||||
}}
|
||||
|
||||
.stat-label {{
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9em;
|
||||
}}
|
||||
|
||||
.rarity-section {{
|
||||
margin-bottom: 40px;
|
||||
}}
|
||||
|
||||
.pets-grid {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
|
||||
.pet-card {{
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-dark);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: transform 0.3s ease;
|
||||
}}
|
||||
|
||||
.pet-card:hover {{
|
||||
transform: translateY(-3px);
|
||||
}}
|
||||
|
||||
.pet-header {{
|
||||
background: var(--bg-tertiary);
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}}
|
||||
|
||||
.pet-header h3 {{
|
||||
margin: 0;
|
||||
font-size: 1.2em;
|
||||
}}
|
||||
|
||||
.type-badge {{
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-accent);
|
||||
padding: 4px 12px;
|
||||
border-radius: 15px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 600;
|
||||
}}
|
||||
|
||||
.pet-stats {{
|
||||
padding: 15px;
|
||||
background: var(--bg-tertiary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}}
|
||||
|
||||
.stat-row {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 5px;
|
||||
}}
|
||||
|
||||
.total-stats {{
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
font-weight: bold;
|
||||
color: var(--text-accent);
|
||||
}}
|
||||
|
||||
.pet-info {{
|
||||
padding: 15px;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4;
|
||||
}}
|
||||
|
||||
.search-filter {{
|
||||
background: var(--bg-secondary);
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/" class="back-link">← Back to Game Hub</a>
|
||||
|
||||
<div class="header">
|
||||
<h1>📖 Petdex - Complete Pet Encyclopedia</h1>
|
||||
<p>Comprehensive guide to all available pet species</p>
|
||||
</div>
|
||||
|
||||
<div class="search-filter">
|
||||
<div class="stats-summary">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{total_species}</div>
|
||||
<div class="stat-label">Total Species</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{len([p for p in petdex_data if p['type1'] == 'Fire' or p['type2'] == 'Fire'])}</div>
|
||||
<div class="stat-label">Fire Types</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{len([p for p in petdex_data if p['type1'] == 'Water' or p['type2'] == 'Water'])}</div>
|
||||
<div class="stat-label">Water Types</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{len([p for p in petdex_data if p['evolution_level']])}</div>
|
||||
<div class="stat-label">Can Evolve</div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="color: var(--text-secondary); margin: 0;">🎯 Pets are organized by rarity. Use <code>!wild <location></code> in #petz to see what spawns where!</p>
|
||||
</div>
|
||||
|
||||
{petdex_html}
|
||||
|
||||
</body>
|
||||
</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!
|
||||
</div>"""
|
||||
|
||||
# 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"""
|
||||
<div style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid gold;">
|
||||
<strong>{badge['badge_icon']} {badge['badge_name']}</strong><br>
|
||||
<small>Earned from {badge['gym_name']} ({badge['location_name']})</small><br>
|
||||
<em style="color: var(--text-secondary);">First victory: {badge_date} | Total victories: {badge['victories']} | Highest difficulty: Level {badge['highest_difficulty']}</em>
|
||||
</div>"""
|
||||
else:
|
||||
badges_html = """
|
||||
<div style="text-align: center; padding: 40px; color: var(--text-secondary);">
|
||||
No gym badges yet. Challenge gyms to earn badges and prove your training skills!
|
||||
</div>"""
|
||||
|
||||
# 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"""
|
||||
<div style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px; margin: 10px 0; border-left: 4px solid {rarity_color};">
|
||||
<strong style="color: {rarity_color};">{encounter['species_name']}</strong> <span class="type-badge">{type_str}</span><br>
|
||||
<small>Encountered {encounter['total_encounters']} times | Caught {encounter['caught_count']} times</small><br>
|
||||
<em style="color: var(--text-secondary);">First seen: {encounter_date}</em>
|
||||
</div>"""
|
||||
else:
|
||||
encounters_html = """
|
||||
<div style="text-align: center; padding: 40px; color: var(--text-secondary);">
|
||||
No pets encountered yet. Use !explore to discover wild pets!
|
||||
</div>"""
|
||||
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
|
@ -1381,6 +1859,14 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
<div class="stat-value">{len(achievements)}</div>
|
||||
<div class="stat-label">Achievements</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{encounter_stats.get('species_encountered', 0)}</div>
|
||||
<div class="stat-label">Species Seen</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">{encounter_stats.get('completion_percentage', 0)}%</div>
|
||||
<div class="stat-label">Petdex Complete</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1420,6 +1906,25 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
{inventory_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">🏆 Gym Badges</div>
|
||||
<div class="section-content">
|
||||
{badges_html}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-header">👁️ Pet Encounters</div>
|
||||
<div class="section-content">
|
||||
<div style="margin-bottom: 20px; text-align: center; color: var(--text-secondary);">
|
||||
<p>Species discovered: {encounter_stats.get('species_encountered', 0)}/{encounter_stats.get('total_species', 0)}
|
||||
({encounter_stats.get('completion_percentage', 0)}% complete)</p>
|
||||
<p>Total encounters: {encounter_stats.get('total_encounters', 0)}</p>
|
||||
</div>
|
||||
{encounters_html}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue