Implement comprehensive experience and leveling system
**NEW FEATURES:** - Complete EXP system with Pokemon-style stat calculation - Level-based stat growth and automatic HP restoration on level up - Experience gain from catches and battle victories - Visual level up notifications with stat increases **EXPERIENCE SOURCES:** - Successful catches: 5 EXP × caught pet level - Wild battle victories: 10 EXP × defeated pet level - Gym battle victories: 20 EXP × defeated pet level (2x multiplier) **LEVELING MECHANICS:** - Cubic growth formula: level³ × 4 - 12 (smooth progression) - Level cap at 100 - Stats recalculated on level up using Pokemon formulas - Full HP restoration on level up - Real-time stat increase display **EXPERIENCE DISPLAY:** - \!team command now shows "EXP: X to next" for active pets - Level up messages show exact stat gains - Experience awarded immediately after catch/victory **STAT CALCULATION:** - HP: (2 × base + 31) × level / 100 + level + 10 - Other stats: (2 × base + 31) × level / 100 + 5 - Progressive growth ensures meaningful advancement **BATTLE INTEGRATION:** - EXP awarded after wild battles, gym individual battles, and catches - Different multipliers for different victory types - First active pet receives all experience This creates proper RPG progression where pets grow stronger through gameplay, encouraging both exploration (catches) and combat (battles) for advancement. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6053161b6e
commit
bd455f1be5
4 changed files with 233 additions and 4 deletions
|
|
@ -134,8 +134,11 @@ class BattleSystem(BaseModule):
|
||||||
# Regular wild battle
|
# Regular wild battle
|
||||||
if result["winner"] == "player":
|
if result["winner"] == "player":
|
||||||
self.send_message(channel, f"🎉 {nickname}: You won the battle!")
|
self.send_message(channel, f"🎉 {nickname}: You won the battle!")
|
||||||
# Remove encounter since battle is over
|
|
||||||
|
# Award experience for victory
|
||||||
if player["id"] in self.bot.active_encounters:
|
if player["id"] in self.bot.active_encounters:
|
||||||
|
wild_pet = self.bot.active_encounters[player["id"]]
|
||||||
|
await self.award_battle_experience(channel, nickname, player, wild_pet, "wild")
|
||||||
del self.bot.active_encounters[player["id"]]
|
del self.bot.active_encounters[player["id"]]
|
||||||
else:
|
else:
|
||||||
self.send_message(channel, f"💀 {nickname}: Your pet fainted! You lost the battle...")
|
self.send_message(channel, f"💀 {nickname}: Your pet fainted! You lost the battle...")
|
||||||
|
|
@ -224,6 +227,9 @@ class BattleSystem(BaseModule):
|
||||||
|
|
||||||
self.send_message(channel, f"🎉 {nickname}: You defeated {defeated_pet['species_name']}!")
|
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
|
# Check if there are more pets
|
||||||
if await self.database.advance_gym_battle(player["id"]):
|
if await self.database.advance_gym_battle(player["id"]):
|
||||||
# More pets to fight
|
# More pets to fight
|
||||||
|
|
@ -291,4 +297,71 @@ class BattleSystem(BaseModule):
|
||||||
self.send_message(channel, f"❌ {nickname}: Gym battle error occurred - please !forfeit and try again")
|
self.send_message(channel, f"❌ {nickname}: Gym battle error occurred - please !forfeit and try again")
|
||||||
print(f"Gym battle completion error: {e}")
|
print(f"Gym battle completion error: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
async def award_battle_experience(self, channel, nickname, player, defeated_pet, battle_type="wild"):
|
||||||
|
"""Award experience to active pets for battle victory"""
|
||||||
|
active_pets = await self.database.get_active_pets(player["id"])
|
||||||
|
if not active_pets:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate experience based on defeated pet and battle type
|
||||||
|
base_exp = self.calculate_battle_exp(defeated_pet, battle_type)
|
||||||
|
|
||||||
|
# Award to first active pet (the one that was battling)
|
||||||
|
main_pet = active_pets[0]
|
||||||
|
exp_result = await self.database.award_experience(main_pet["id"], base_exp)
|
||||||
|
|
||||||
|
if exp_result["success"]:
|
||||||
|
# Display experience gain
|
||||||
|
self.send_message(channel,
|
||||||
|
f"⭐ {exp_result['pet_name']} gained {exp_result['exp_gained']} EXP!")
|
||||||
|
|
||||||
|
# Handle level up
|
||||||
|
if exp_result["leveled_up"]:
|
||||||
|
await self.handle_level_up_display(channel, nickname, exp_result)
|
||||||
|
|
||||||
|
def calculate_battle_exp(self, defeated_pet, battle_type="wild"):
|
||||||
|
"""Calculate experience gain for defeating a pet"""
|
||||||
|
base_exp = defeated_pet["level"] * 10 # Base: 10 EXP per level
|
||||||
|
|
||||||
|
# Battle type multipliers
|
||||||
|
multipliers = {
|
||||||
|
"wild": 1.0,
|
||||||
|
"gym": 2.0, # Double EXP for gym battles
|
||||||
|
"trainer": 1.5 # Future: trainer battles
|
||||||
|
}
|
||||||
|
|
||||||
|
multiplier = multipliers.get(battle_type, 1.0)
|
||||||
|
return int(base_exp * multiplier)
|
||||||
|
|
||||||
|
async def handle_level_up_display(self, channel, nickname, exp_result):
|
||||||
|
"""Display level up information"""
|
||||||
|
levels_gained = exp_result["levels_gained"]
|
||||||
|
pet_name = exp_result["pet_name"]
|
||||||
|
|
||||||
|
if levels_gained == 1:
|
||||||
|
self.send_message(channel,
|
||||||
|
f"🎉 {pet_name} leveled up! Now level {exp_result['new_level']}!")
|
||||||
|
else:
|
||||||
|
self.send_message(channel,
|
||||||
|
f"🎉 {pet_name} gained {levels_gained} levels! Now level {exp_result['new_level']}!")
|
||||||
|
|
||||||
|
# Show stat increases
|
||||||
|
if "stat_increases" in exp_result:
|
||||||
|
stats = exp_result["stat_increases"]
|
||||||
|
stat_msg = f"📈 Stats increased: "
|
||||||
|
stat_parts = []
|
||||||
|
|
||||||
|
if stats["hp"] > 0:
|
||||||
|
stat_parts.append(f"HP +{stats['hp']}")
|
||||||
|
if stats["attack"] > 0:
|
||||||
|
stat_parts.append(f"ATK +{stats['attack']}")
|
||||||
|
if stats["defense"] > 0:
|
||||||
|
stat_parts.append(f"DEF +{stats['defense']}")
|
||||||
|
if stats["speed"] > 0:
|
||||||
|
stat_parts.append(f"SPD +{stats['speed']}")
|
||||||
|
|
||||||
|
if stat_parts:
|
||||||
|
stat_msg += " | ".join(stat_parts)
|
||||||
|
self.send_message(channel, stat_msg)
|
||||||
|
|
@ -212,6 +212,9 @@ class Exploration(BaseModule):
|
||||||
for achievement in all_achievements:
|
for achievement in all_achievements:
|
||||||
self.send_message(channel, f"🏆 {nickname}: Achievement unlocked: {achievement['name']}! {achievement['description']}")
|
self.send_message(channel, f"🏆 {nickname}: Achievement unlocked: {achievement['name']}! {achievement['description']}")
|
||||||
|
|
||||||
|
# Award experience for successful catch
|
||||||
|
await self.award_catch_experience(channel, nickname, player, wild_pet)
|
||||||
|
|
||||||
# Remove encounter
|
# Remove encounter
|
||||||
if player["id"] in self.bot.active_encounters:
|
if player["id"] in self.bot.active_encounters:
|
||||||
del self.bot.active_encounters[player["id"]]
|
del self.bot.active_encounters[player["id"]]
|
||||||
|
|
@ -233,6 +236,9 @@ class Exploration(BaseModule):
|
||||||
|
|
||||||
# Check for achievements after successful catch
|
# Check for achievements after successful catch
|
||||||
if "Success!" in result:
|
if "Success!" in result:
|
||||||
|
# Award experience for successful catch
|
||||||
|
await self.award_catch_experience(channel, nickname, player, target_pet)
|
||||||
|
|
||||||
type_achievements = await self.game_engine.check_and_award_achievements(player["id"], "catch_type", "")
|
type_achievements = await self.game_engine.check_and_award_achievements(player["id"], "catch_type", "")
|
||||||
total_achievements = await self.game_engine.check_and_award_achievements(player["id"], "catch_total", "")
|
total_achievements = await self.game_engine.check_and_award_achievements(player["id"], "catch_total", "")
|
||||||
|
|
||||||
|
|
@ -244,4 +250,32 @@ class Exploration(BaseModule):
|
||||||
# Remove the encounter regardless of success
|
# Remove the encounter regardless of success
|
||||||
del self.bot.active_encounters[player["id"]]
|
del self.bot.active_encounters[player["id"]]
|
||||||
|
|
||||||
self.send_message(channel, f"🎯 {nickname}: {result}")
|
self.send_message(channel, f"🎯 {nickname}: {result}")
|
||||||
|
|
||||||
|
async def award_catch_experience(self, channel, nickname, player, caught_pet):
|
||||||
|
"""Award experience to active pets for successful catch"""
|
||||||
|
active_pets = await self.database.get_active_pets(player["id"])
|
||||||
|
if not active_pets:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Calculate experience for catch (less than battle victory)
|
||||||
|
base_exp = caught_pet["level"] * 5 # 5 EXP per level for catches
|
||||||
|
|
||||||
|
# Award to first active pet
|
||||||
|
main_pet = active_pets[0]
|
||||||
|
exp_result = await self.database.award_experience(main_pet["id"], base_exp)
|
||||||
|
|
||||||
|
if exp_result["success"]:
|
||||||
|
# Display experience gain
|
||||||
|
self.send_message(channel,
|
||||||
|
f"⭐ {exp_result['pet_name']} gained {exp_result['exp_gained']} EXP for the catch!")
|
||||||
|
|
||||||
|
# Handle level up
|
||||||
|
if exp_result["leveled_up"]:
|
||||||
|
await self.handle_level_up_display(channel, nickname, exp_result)
|
||||||
|
|
||||||
|
async def handle_level_up_display(self, channel, nickname, exp_result):
|
||||||
|
"""Display level up information (shared with battle system)"""
|
||||||
|
from .battle_system import BattleSystem
|
||||||
|
battle_system = BattleSystem(self.bot, self.database, self.game_engine)
|
||||||
|
await battle_system.handle_level_up_display(channel, nickname, exp_result)
|
||||||
|
|
@ -41,7 +41,19 @@ class PetManagement(BaseModule):
|
||||||
# Active pets with star
|
# Active pets with star
|
||||||
for pet in active_pets:
|
for pet in active_pets:
|
||||||
name = pet["nickname"] or pet["species_name"]
|
name = pet["nickname"] or pet["species_name"]
|
||||||
team_info.append(f"⭐{name} (Lv.{pet['level']}) - {pet['hp']}/{pet['max_hp']} HP")
|
|
||||||
|
# Calculate EXP progress
|
||||||
|
current_exp = pet.get("experience", 0)
|
||||||
|
next_level_exp = self.database.calculate_exp_for_level(pet["level"] + 1)
|
||||||
|
current_level_exp = self.database.calculate_exp_for_level(pet["level"])
|
||||||
|
exp_needed = next_level_exp - current_exp
|
||||||
|
|
||||||
|
if pet["level"] >= 100:
|
||||||
|
exp_display = "MAX"
|
||||||
|
else:
|
||||||
|
exp_display = f"{exp_needed} to next"
|
||||||
|
|
||||||
|
team_info.append(f"⭐{name} (Lv.{pet['level']}) - {pet['hp']}/{pet['max_hp']} HP | EXP: {exp_display}")
|
||||||
|
|
||||||
# Inactive pets
|
# Inactive pets
|
||||||
for pet in inactive_pets[:5]: # Show max 5 inactive
|
for pet in inactive_pets[:5]: # Show max 5 inactive
|
||||||
|
|
|
||||||
110
src/database.py
110
src/database.py
|
|
@ -769,6 +769,116 @@ class Database:
|
||||||
rows = await cursor.fetchall()
|
rows = await cursor.fetchall()
|
||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
def calculate_exp_for_level(self, level: int) -> int:
|
||||||
|
"""Calculate total experience needed to reach a level"""
|
||||||
|
# Using a cubic growth formula: level^3 * 4 - 12
|
||||||
|
return max(0, (level ** 3) * 4 - 12)
|
||||||
|
|
||||||
|
def calculate_level_from_exp(self, exp: int) -> int:
|
||||||
|
"""Calculate what level a pet should be based on experience"""
|
||||||
|
level = 1
|
||||||
|
while self.calculate_exp_for_level(level + 1) <= exp:
|
||||||
|
level += 1
|
||||||
|
return min(level, 100) # Cap at level 100
|
||||||
|
|
||||||
|
async def award_experience(self, pet_id: int, exp_amount: int) -> Dict:
|
||||||
|
"""Award experience to a pet and handle leveling up"""
|
||||||
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
|
||||||
|
# Get current pet data
|
||||||
|
cursor = await db.execute("""
|
||||||
|
SELECT p.*, ps.name as species_name, ps.base_hp, ps.base_attack,
|
||||||
|
ps.base_defense, ps.base_speed
|
||||||
|
FROM pets p
|
||||||
|
JOIN pet_species ps ON p.species_id = ps.id
|
||||||
|
WHERE p.id = ?
|
||||||
|
""", (pet_id,))
|
||||||
|
|
||||||
|
pet = await cursor.fetchone()
|
||||||
|
if not pet:
|
||||||
|
return {"success": False, "error": "Pet not found"}
|
||||||
|
|
||||||
|
pet_dict = dict(pet)
|
||||||
|
old_level = pet_dict["level"]
|
||||||
|
old_exp = pet_dict["experience"]
|
||||||
|
new_exp = old_exp + exp_amount
|
||||||
|
new_level = self.calculate_level_from_exp(new_exp)
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"success": True,
|
||||||
|
"pet_id": pet_id,
|
||||||
|
"pet_name": pet_dict["nickname"] or pet_dict["species_name"],
|
||||||
|
"species_name": pet_dict["species_name"],
|
||||||
|
"old_level": old_level,
|
||||||
|
"new_level": new_level,
|
||||||
|
"old_exp": old_exp,
|
||||||
|
"new_exp": new_exp,
|
||||||
|
"exp_gained": exp_amount,
|
||||||
|
"leveled_up": new_level > old_level,
|
||||||
|
"levels_gained": new_level - old_level
|
||||||
|
}
|
||||||
|
|
||||||
|
# Update experience
|
||||||
|
await db.execute("""
|
||||||
|
UPDATE pets SET experience = ? WHERE id = ?
|
||||||
|
""", (new_exp, pet_id))
|
||||||
|
|
||||||
|
# Handle level up if it occurred
|
||||||
|
if new_level > old_level:
|
||||||
|
await self._handle_level_up(db, pet_dict, new_level)
|
||||||
|
result["stat_increases"] = await self._calculate_stat_increases(pet_dict, old_level, new_level)
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
return result
|
||||||
|
|
||||||
|
async def _handle_level_up(self, db, pet_dict: Dict, new_level: int):
|
||||||
|
"""Handle pet leveling up - recalculate stats and HP"""
|
||||||
|
# Calculate new stats based on level
|
||||||
|
new_stats = self._calculate_pet_stats(pet_dict, new_level)
|
||||||
|
|
||||||
|
# Update pet stats and level
|
||||||
|
await db.execute("""
|
||||||
|
UPDATE pets
|
||||||
|
SET level = ?, max_hp = ?, attack = ?, defense = ?, speed = ?, hp = ?
|
||||||
|
WHERE id = ?
|
||||||
|
""", (
|
||||||
|
new_level,
|
||||||
|
new_stats["hp"],
|
||||||
|
new_stats["attack"],
|
||||||
|
new_stats["defense"],
|
||||||
|
new_stats["speed"],
|
||||||
|
new_stats["hp"], # Restore full HP on level up
|
||||||
|
pet_dict["id"]
|
||||||
|
))
|
||||||
|
|
||||||
|
def _calculate_pet_stats(self, pet_dict: Dict, level: int) -> Dict:
|
||||||
|
"""Calculate pet stats for a given level"""
|
||||||
|
# Pokémon-style stat calculation
|
||||||
|
hp = int((2 * pet_dict["base_hp"] + 31) * level / 100) + level + 10
|
||||||
|
attack = int((2 * pet_dict["base_attack"] + 31) * level / 100) + 5
|
||||||
|
defense = int((2 * pet_dict["base_defense"] + 31) * level / 100) + 5
|
||||||
|
speed = int((2 * pet_dict["base_speed"] + 31) * level / 100) + 5
|
||||||
|
|
||||||
|
return {
|
||||||
|
"hp": hp,
|
||||||
|
"attack": attack,
|
||||||
|
"defense": defense,
|
||||||
|
"speed": speed
|
||||||
|
}
|
||||||
|
|
||||||
|
async def _calculate_stat_increases(self, pet_dict: Dict, old_level: int, new_level: int) -> Dict:
|
||||||
|
"""Calculate stat increases from leveling up"""
|
||||||
|
old_stats = self._calculate_pet_stats(pet_dict, old_level)
|
||||||
|
new_stats = self._calculate_pet_stats(pet_dict, new_level)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"hp": new_stats["hp"] - old_stats["hp"],
|
||||||
|
"attack": new_stats["attack"] - old_stats["attack"],
|
||||||
|
"defense": new_stats["defense"] - old_stats["defense"],
|
||||||
|
"speed": new_stats["speed"] - old_stats["speed"]
|
||||||
|
}
|
||||||
|
|
||||||
# Gym Battle System Methods
|
# Gym Battle System Methods
|
||||||
async def get_gyms_in_location(self, location_id: int, player_id: int = None) -> List[Dict]:
|
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"""
|
"""Get all gyms in a specific location with optional player progress"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue