Implement gym battle system with location-based challenges

🏛️ Gym Battle System - Phase 1:
- Complete database schema with 4 new tables (gyms, gym_teams, player_gym_battles)
- 6 unique gyms across all locations with themed leaders and badges
- Location-based challenges (must travel to gym location)
- Difficulty scaling system (gets harder with each victory)
- Badge tracking and progress system

🎯 Features Added:
- \!gym - List gyms in current location with progress
- \!gym list - Show all gyms across all locations
- \!gym challenge "<name>" - Challenge a gym (location validation)
- \!gym info "<name>" - Get detailed gym information
- \!gym status - Quick status overview

🏆 Gym Roster:
- Starter Town: Forest Guardian (Grass) - Trainer Verde
- Whispering Woods: Nature's Haven (Grass) - Elder Sage
- Electric Canyon: Storm Master (Electric) - Captain Volt
- Crystal Caves: Stone Crusher (Rock) - Miner Magnus
- Frozen Tundra: Ice Breaker (Ice/Water) - Arctic Queen
- Dragon's Peak: Dragon Slayer (Fire) - Champion Drake

⚔️ Battle Mechanics:
- Teams scale with victory count (20% stat increase per win)
- First victory awards badge + bonus rewards
- Repeat challenges give scaling rewards
- Simulated battles (full battle system integration pending)

🔧 Technical Implementation:
- Config-driven gym system (config/gyms.json)
- Scalable database design for easy expansion
- Location validation prevents remote challenges
- Automatic gym data loading on startup

Next phase: Integrate with full battle system and add web interface badges.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
megaproxy 2025-07-14 12:36:40 +01:00
parent d74c6f2897
commit 87eff2a336
7 changed files with 703 additions and 6 deletions

View file

@ -213,6 +213,49 @@ class Database:
)
""")
await db.execute("""
CREATE TABLE IF NOT EXISTS gyms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
location_id INTEGER NOT NULL,
name TEXT UNIQUE NOT NULL,
leader_name TEXT NOT NULL,
description TEXT,
theme TEXT NOT NULL,
badge_name TEXT NOT NULL,
badge_icon TEXT NOT NULL,
badge_description TEXT,
FOREIGN KEY (location_id) REFERENCES locations (id)
)
""")
await db.execute("""
CREATE TABLE IF NOT EXISTS gym_teams (
id INTEGER PRIMARY KEY AUTOINCREMENT,
gym_id INTEGER NOT NULL,
species_id INTEGER NOT NULL,
base_level INTEGER NOT NULL,
move_set TEXT,
team_position INTEGER NOT NULL,
FOREIGN KEY (gym_id) REFERENCES gyms (id),
FOREIGN KEY (species_id) REFERENCES pet_species (id)
)
""")
await db.execute("""
CREATE TABLE IF NOT EXISTS player_gym_battles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
player_id INTEGER NOT NULL,
gym_id INTEGER NOT NULL,
victories INTEGER DEFAULT 0,
highest_difficulty INTEGER DEFAULT 0,
first_victory_date TIMESTAMP,
last_battle_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (player_id) REFERENCES players (id),
FOREIGN KEY (gym_id) REFERENCES gyms (id),
UNIQUE(player_id, gym_id)
)
""")
await db.commit()
async def get_player(self, nickname: str) -> Optional[Dict]:
@ -286,6 +329,14 @@ class Database:
row = await cursor.fetchone()
return dict(row) if row else None
async def get_all_locations(self) -> List[Dict]:
"""Get all locations"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("SELECT * FROM locations ORDER BY id")
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def check_player_achievements(self, player_id: int, achievement_type: str, data: str):
"""Check and award achievements based on player actions"""
async with aiosqlite.connect(self.db_path) as db:
@ -686,4 +737,207 @@ class Database:
async with aiosqlite.connect(self.db_path) as db:
await db.execute("UPDATE pets SET hp = ? WHERE id = ?", (new_hp, pet_id))
await db.commit()
return True
return True
# Gym Battle System Methods
async def get_gyms_in_location(self, location_id: int, player_id: int = None) -> List[Dict]:
"""Get all gyms in a specific location with optional player progress"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
if player_id:
cursor = await db.execute("""
SELECT g.*, l.name as location_name,
COALESCE(pgb.victories, 0) as victories,
COALESCE(pgb.highest_difficulty, 0) as highest_difficulty,
pgb.first_victory_date
FROM gyms g
JOIN locations l ON g.location_id = l.id
LEFT JOIN player_gym_battles pgb ON g.id = pgb.gym_id AND pgb.player_id = ?
WHERE g.location_id = ?
ORDER BY g.id
""", (player_id, location_id))
else:
cursor = await db.execute("""
SELECT g.*, l.name as location_name
FROM gyms g
JOIN locations l ON g.location_id = l.id
WHERE g.location_id = ?
ORDER BY g.id
""", (location_id,))
rows = await cursor.fetchall()
return [dict(row) for row in rows]
async def get_gym_by_name(self, gym_name: str) -> Optional[Dict]:
"""Get gym details by name"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT g.*, l.name as location_name
FROM gyms g
JOIN locations l ON g.location_id = l.id
WHERE g.name = ?
""", (gym_name,))
row = await cursor.fetchone()
return dict(row) if row else None
async def get_gym_team(self, gym_id: int, difficulty_multiplier: float = 1.0) -> List[Dict]:
"""Get gym team with difficulty scaling applied"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT gt.*, ps.name as species_name, ps.type1, ps.type2,
ps.base_hp, ps.base_attack, ps.base_defense, ps.base_speed
FROM gym_teams gt
JOIN pet_species ps ON gt.species_id = ps.id
WHERE gt.gym_id = ?
ORDER BY gt.team_position
""", (gym_id,))
rows = await cursor.fetchall()
team = []
for row in rows:
# Apply difficulty scaling
scaled_level = int(row["base_level"] * difficulty_multiplier)
stat_multiplier = 1.0 + (difficulty_multiplier - 1.0) * 0.5 # Less aggressive stat scaling
# Calculate scaled stats
hp = int((2 * row["base_hp"] + 31) * scaled_level / 100) + scaled_level + 10
attack = int(((2 * row["base_attack"] + 31) * scaled_level / 100) + 5) * stat_multiplier
defense = int(((2 * row["base_defense"] + 31) * scaled_level / 100) + 5) * stat_multiplier
speed = int(((2 * row["base_speed"] + 31) * scaled_level / 100) + 5) * stat_multiplier
pet_data = {
"species_id": row["species_id"],
"species_name": row["species_name"],
"level": scaled_level,
"type1": row["type1"],
"type2": row["type2"],
"hp": int(hp),
"max_hp": int(hp),
"attack": int(attack),
"defense": int(defense),
"speed": int(speed),
"moves": row["move_set"].split(",") if row["move_set"] else ["Tackle", "Growl"],
"position": row["team_position"]
}
team.append(pet_data)
return team
async def get_player_gym_progress(self, player_id: int, gym_id: int) -> Optional[Dict]:
"""Get player's progress for a specific gym"""
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute("""
SELECT * FROM player_gym_battles
WHERE player_id = ? AND gym_id = ?
""", (player_id, gym_id))
row = await cursor.fetchone()
return dict(row) if row else None
async def record_gym_victory(self, player_id: int, gym_id: int) -> Dict:
"""Record a gym victory and update player progress"""
async with aiosqlite.connect(self.db_path) as db:
# Get current progress
cursor = await db.execute("""
SELECT victories, first_victory_date FROM player_gym_battles
WHERE player_id = ? AND gym_id = ?
""", (player_id, gym_id))
current = await cursor.fetchone()
if current:
new_victories = current[0] + 1
await db.execute("""
UPDATE player_gym_battles
SET victories = ?, highest_difficulty = ?, last_battle_date = CURRENT_TIMESTAMP
WHERE player_id = ? AND gym_id = ?
""", (new_victories, new_victories, player_id, gym_id))
else:
new_victories = 1
await db.execute("""
INSERT INTO player_gym_battles
(player_id, gym_id, victories, highest_difficulty, first_victory_date, last_battle_date)
VALUES (?, ?, 1, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
""", (player_id, gym_id))
await db.commit()
return {
"victories": new_victories,
"is_first_victory": current is None,
"next_difficulty": new_victories + 1
}
async def initialize_gyms(self):
"""Initialize gyms from config file"""
import json
try:
with open("config/gyms.json", "r") as f:
gyms_data = json.load(f)
except FileNotFoundError:
print("Gyms config file not found")
return
async with aiosqlite.connect(self.db_path) as db:
# Clear existing gym data
await db.execute("DELETE FROM gym_teams")
await db.execute("DELETE FROM gyms")
for gym_config in gyms_data:
# Get location ID
cursor = await db.execute(
"SELECT id FROM locations WHERE name = ?",
(gym_config["location"],)
)
location_row = await cursor.fetchone()
if not location_row:
print(f"Location '{gym_config['location']}' not found for gym '{gym_config['name']}'")
continue
location_id = location_row[0]
# Insert gym
cursor = await db.execute("""
INSERT OR REPLACE INTO gyms
(location_id, name, leader_name, description, theme, badge_name, badge_icon, badge_description)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
location_id,
gym_config["name"],
gym_config["leader_name"],
gym_config["description"],
gym_config["theme"],
gym_config["badge"]["name"],
gym_config["badge"]["icon"],
gym_config["badge"]["description"]
))
gym_id = cursor.lastrowid
# Insert gym team
for member in gym_config["team"]:
# Get species ID
species_cursor = await db.execute(
"SELECT id FROM pet_species WHERE name = ?",
(member["species"],)
)
species_row = await species_cursor.fetchone()
if species_row:
moves_str = ",".join(member["moves"]) if member["moves"] else ""
await db.execute("""
INSERT INTO gym_teams
(gym_id, species_id, base_level, move_set, team_position)
VALUES (?, ?, ?, ?, ?)
""", (
gym_id,
species_row[0],
member["base_level"],
moves_str,
member["position"]
))
await db.commit()
print("Gyms initialized from config")