Compare commits
No commits in common. "c8cb99a4d0dcfdfc0282c17c12b3f7ea30c83596" and "39ba55832dfbcf0ff83352e93a1c3f8f0508f9d6" have entirely different histories.
c8cb99a4d0
...
39ba55832d
15 changed files with 1412 additions and 3150 deletions
|
|
@ -99,6 +99,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `!weather` - Check current weather
|
- `!weather` - Check current weather
|
||||||
- `!achievements` - View progress
|
- `!achievements` - View progress
|
||||||
- `!activate/deactivate <pet>` - Manage team
|
- `!activate/deactivate <pet>` - Manage team
|
||||||
|
- `!swap <pet1> <pet2>` - Reorganize team
|
||||||
- `!moves` - View pet abilities
|
- `!moves` - View pet abilities
|
||||||
- `!flee` - Escape battles
|
- `!flee` - Escape battles
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ A feature-rich IRC bot that brings Pokemon-style pet collecting and battling to
|
||||||
- **Pet Collection**: Catch and collect different species of pets
|
- **Pet Collection**: Catch and collect different species of pets
|
||||||
- **Exploration**: Travel between various themed locations
|
- **Exploration**: Travel between various themed locations
|
||||||
- **Battle System**: Engage in turn-based battles with wild pets
|
- **Battle System**: Engage in turn-based battles with wild pets
|
||||||
- **Team Management**: Activate/deactivate pets, manage team composition
|
- **Team Management**: Activate/deactivate pets, swap team members
|
||||||
- **Achievement System**: Unlock new areas by completing challenges
|
- **Achievement System**: Unlock new areas by completing challenges
|
||||||
- **Item Collection**: Discover and collect useful items during exploration
|
- **Item Collection**: Discover and collect useful items during exploration
|
||||||
|
|
||||||
|
|
@ -78,6 +78,7 @@ A feature-rich IRC bot that brings Pokemon-style pet collecting and battling to
|
||||||
### Pet Management
|
### Pet Management
|
||||||
- `!activate <pet>` - Activate a pet for battle
|
- `!activate <pet>` - Activate a pet for battle
|
||||||
- `!deactivate <pet>` - Move a pet to storage
|
- `!deactivate <pet>` - Move a pet to storage
|
||||||
|
- `!swap <pet1> <pet2>` - Swap two pets' active status
|
||||||
|
|
||||||
### Inventory Commands
|
### Inventory Commands
|
||||||
- `!inventory` / `!inv` / `!items` - View your collected items
|
- `!inventory` / `!inv` / `!items` - View your collected items
|
||||||
|
|
|
||||||
|
|
@ -430,6 +430,11 @@
|
||||||
<div class="command-desc">Remove a pet from your active team and put it in storage.</div>
|
<div class="command-desc">Remove a pet from your active team and put it in storage.</div>
|
||||||
<div class="command-example">Example: !deactivate aqua</div>
|
<div class="command-example">Example: !deactivate aqua</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="command">
|
||||||
|
<div class="command-name">!swap <pet1> <pet2></div>
|
||||||
|
<div class="command-desc">Swap the active status of two pets - one becomes active, the other goes to storage.</div>
|
||||||
|
<div class="command-example">Example: !swap leafy flamey</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,14 @@ class Achievements(BaseModule):
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Redirect to web interface for better achievements display
|
|
||||||
self.send_message(channel, f"🏆 {nickname}: View your complete achievements at: http://petz.rdx4.com/player/{nickname}#achievements")
|
|
||||||
|
|
||||||
# Show quick summary in channel
|
|
||||||
achievements = await self.database.get_player_achievements(player["id"])
|
achievements = await self.database.get_player_achievements(player["id"])
|
||||||
|
|
||||||
if achievements:
|
if achievements:
|
||||||
self.send_message(channel, f"📊 Quick summary: {len(achievements)} achievements earned! Check the web interface for details.")
|
self.send_message(channel, f"🏆 {nickname}'s Achievements:")
|
||||||
|
for achievement in achievements[:5]: # Show last 5 achievements
|
||||||
|
self.send_message(channel, f"• {achievement['name']}: {achievement['description']}")
|
||||||
|
|
||||||
|
if len(achievements) > 5:
|
||||||
|
self.send_message(channel, f"... and {len(achievements) - 5} more!")
|
||||||
else:
|
else:
|
||||||
self.send_message(channel, f"💡 No achievements yet! Keep exploring and catching pets to unlock new areas!")
|
self.send_message(channel, f"{nickname}: No achievements yet! Keep exploring and catching pets to unlock new areas!")
|
||||||
|
|
@ -12,15 +12,6 @@ class BaseModule(ABC):
|
||||||
self.database = database
|
self.database = database
|
||||||
self.game_engine = game_engine
|
self.game_engine = game_engine
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def normalize_input(user_input):
|
|
||||||
"""Normalize user input by converting to lowercase for case-insensitive command processing"""
|
|
||||||
if isinstance(user_input, str):
|
|
||||||
return user_input.lower()
|
|
||||||
elif isinstance(user_input, list):
|
|
||||||
return [item.lower() if isinstance(item, str) else item for item in user_input]
|
|
||||||
return user_input
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_commands(self):
|
def get_commands(self):
|
||||||
"""Return list of commands this module handles"""
|
"""Return list of commands this module handles"""
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,6 @@ class BattleSystem(BaseModule):
|
||||||
self.send_message(channel, f"{nickname}: You're already in battle! Use !attack <move> or !flee.")
|
self.send_message(channel, f"{nickname}: You're already in battle! Use !attack <move> or !flee.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if already in gym battle
|
|
||||||
gym_battle = await self.database.get_active_gym_battle(player["id"])
|
|
||||||
if gym_battle:
|
|
||||||
self.send_message(channel, f"{nickname}: You're already in a gym battle! Finish your gym battle first.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get player's active pet
|
# Get player's active pet
|
||||||
pets = await self.database.get_player_pets(player["id"], active_only=True)
|
pets = await self.database.get_player_pets(player["id"], active_only=True)
|
||||||
if not pets:
|
if not pets:
|
||||||
|
|
@ -93,7 +87,7 @@ class BattleSystem(BaseModule):
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
move_name = " ".join(self.normalize_input(args)).title() # Normalize to Title Case
|
move_name = " ".join(args).title() # Normalize to Title Case
|
||||||
result = await self.game_engine.battle_engine.execute_battle_turn(player["id"], move_name)
|
result = await self.game_engine.battle_engine.execute_battle_turn(player["id"], move_name)
|
||||||
|
|
||||||
if "error" in result:
|
if "error" in result:
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,5 @@ class CoreCommands(BaseModule):
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Show quick summary and direct to web interface for detailed stats
|
|
||||||
self.send_message(channel,
|
self.send_message(channel,
|
||||||
f"📊 {nickname}: Level {player['level']} | {player['experience']} XP | ${player['money']}")
|
f"📊 {nickname}: Level {player['level']} | {player['experience']} XP | ${player['money']}")
|
||||||
self.send_message(channel,
|
|
||||||
f"🌐 View detailed statistics at: http://petz.rdx4.com/player/{nickname}#stats")
|
|
||||||
|
|
@ -7,7 +7,7 @@ class Exploration(BaseModule):
|
||||||
"""Handles exploration, travel, location, weather, and wild commands"""
|
"""Handles exploration, travel, location, weather, and wild commands"""
|
||||||
|
|
||||||
def get_commands(self):
|
def get_commands(self):
|
||||||
return ["explore", "travel", "location", "where", "weather", "wild", "catch", "capture", "flee"]
|
return ["explore", "travel", "location", "where", "weather", "wild", "catch", "capture"]
|
||||||
|
|
||||||
async def handle_command(self, channel, nickname, command, args):
|
async def handle_command(self, channel, nickname, command, args):
|
||||||
if command == "explore":
|
if command == "explore":
|
||||||
|
|
@ -22,8 +22,6 @@ class Exploration(BaseModule):
|
||||||
await self.cmd_wild(channel, nickname, args)
|
await self.cmd_wild(channel, nickname, args)
|
||||||
elif command in ["catch", "capture"]:
|
elif command in ["catch", "capture"]:
|
||||||
await self.cmd_catch(channel, nickname)
|
await self.cmd_catch(channel, nickname)
|
||||||
elif command == "flee":
|
|
||||||
await self.cmd_flee_encounter(channel, nickname)
|
|
||||||
|
|
||||||
async def cmd_explore(self, channel, nickname):
|
async def cmd_explore(self, channel, nickname):
|
||||||
"""Explore current location"""
|
"""Explore current location"""
|
||||||
|
|
@ -31,18 +29,6 @@ class Exploration(BaseModule):
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if player has an active encounter that must be resolved first
|
|
||||||
if player["id"] in self.bot.active_encounters:
|
|
||||||
current_encounter = self.bot.active_encounters[player["id"]]
|
|
||||||
self.send_message(channel, f"{nickname}: You already have an active encounter with a wild {current_encounter['species_name']}! You must choose to !battle, !catch, or !flee before exploring again.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if player is in an active battle
|
|
||||||
active_battle = await self.game_engine.battle_engine.get_active_battle(player["id"])
|
|
||||||
if active_battle:
|
|
||||||
self.send_message(channel, f"{nickname}: You're currently in battle! Finish your battle before exploring.")
|
|
||||||
return
|
|
||||||
|
|
||||||
encounter = await self.game_engine.explore_location(player["id"])
|
encounter = await self.game_engine.explore_location(player["id"])
|
||||||
|
|
||||||
if encounter["type"] == "error":
|
if encounter["type"] == "error":
|
||||||
|
|
@ -65,7 +51,7 @@ class Exploration(BaseModule):
|
||||||
|
|
||||||
self.send_message(channel,
|
self.send_message(channel,
|
||||||
f"🐾 {nickname}: A wild Level {pet['level']} {pet['species_name']} ({type_str}) appeared in {encounter['location']}!")
|
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, !catch to try catching it directly, or !flee to escape!")
|
self.send_message(channel, f"Choose your action: !battle to fight it, or !catch to try catching it directly!")
|
||||||
|
|
||||||
async def cmd_travel(self, channel, nickname, args):
|
async def cmd_travel(self, channel, nickname, args):
|
||||||
"""Travel to a different location"""
|
"""Travel to a different location"""
|
||||||
|
|
@ -78,7 +64,7 @@ class Exploration(BaseModule):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Handle various input formats and normalize location names
|
# Handle various input formats and normalize location names
|
||||||
destination_input = self.normalize_input(" ".join(args))
|
destination_input = " ".join(args).lower()
|
||||||
|
|
||||||
# Map common variations to exact location names
|
# Map common variations to exact location names
|
||||||
location_mappings = {
|
location_mappings = {
|
||||||
|
|
@ -96,7 +82,7 @@ class Exploration(BaseModule):
|
||||||
destination = location_mappings.get(destination_input)
|
destination = location_mappings.get(destination_input)
|
||||||
if not destination:
|
if not destination:
|
||||||
# Fall back to title case if no mapping found
|
# Fall back to title case if no mapping found
|
||||||
destination = " ".join(self.normalize_input(args)).title()
|
destination = " ".join(args).title()
|
||||||
|
|
||||||
location = await self.database.get_location_by_name(destination)
|
location = await self.database.get_location_by_name(destination)
|
||||||
|
|
||||||
|
|
@ -185,7 +171,7 @@ class Exploration(BaseModule):
|
||||||
|
|
||||||
if args:
|
if args:
|
||||||
# Specific location requested
|
# Specific location requested
|
||||||
location_name = " ".join(self.normalize_input(args)).title()
|
location_name = " ".join(args).title()
|
||||||
else:
|
else:
|
||||||
# Default to current location
|
# Default to current location
|
||||||
current_location = await self.database.get_player_location(player["id"])
|
current_location = await self.database.get_player_location(player["id"])
|
||||||
|
|
@ -210,13 +196,6 @@ class Exploration(BaseModule):
|
||||||
|
|
||||||
# Check if player is in an active battle
|
# Check if player is in an active battle
|
||||||
active_battle = await self.game_engine.battle_engine.get_active_battle(player["id"])
|
active_battle = await self.game_engine.battle_engine.get_active_battle(player["id"])
|
||||||
gym_battle = await self.database.get_active_gym_battle(player["id"])
|
|
||||||
|
|
||||||
if gym_battle:
|
|
||||||
# Can't catch pets during gym battles
|
|
||||||
self.send_message(channel, f"{nickname}: You can't catch pets during gym battles! Focus on the challenge!")
|
|
||||||
return
|
|
||||||
|
|
||||||
if active_battle:
|
if active_battle:
|
||||||
# Catching during battle
|
# Catching during battle
|
||||||
wild_pet = active_battle["wild_pet"]
|
wild_pet = active_battle["wild_pet"]
|
||||||
|
|
@ -318,36 +297,4 @@ class Exploration(BaseModule):
|
||||||
"""Display level up information (shared with battle system)"""
|
"""Display level up information (shared with battle system)"""
|
||||||
from .battle_system import BattleSystem
|
from .battle_system import BattleSystem
|
||||||
battle_system = BattleSystem(self.bot, self.database, self.game_engine)
|
battle_system = BattleSystem(self.bot, self.database, self.game_engine)
|
||||||
await battle_system.handle_level_up_display(channel, nickname, exp_result)
|
await battle_system.handle_level_up_display(channel, nickname, exp_result)
|
||||||
|
|
||||||
async def cmd_flee_encounter(self, channel, nickname):
|
|
||||||
"""Flee from an active encounter without battling"""
|
|
||||||
player = await self.require_player(channel, nickname)
|
|
||||||
if not player:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if player has an active encounter to flee from
|
|
||||||
if player["id"] not in self.bot.active_encounters:
|
|
||||||
self.send_message(channel, f"{nickname}: You don't have an active encounter to flee from!")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if player is in an active battle - can't flee from exploration if in battle
|
|
||||||
active_battle = await self.game_engine.battle_engine.get_active_battle(player["id"])
|
|
||||||
if active_battle:
|
|
||||||
self.send_message(channel, f"{nickname}: You're in battle! Use the battle system's !flee command to escape combat.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check if player is in a gym battle
|
|
||||||
gym_battle = await self.database.get_active_gym_battle(player["id"])
|
|
||||||
if gym_battle:
|
|
||||||
self.send_message(channel, f"{nickname}: You're in a gym battle! Use !forfeit to leave the gym challenge.")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get encounter details for message
|
|
||||||
encounter = self.bot.active_encounters[player["id"]]
|
|
||||||
|
|
||||||
# Remove the encounter
|
|
||||||
del self.bot.active_encounters[player["id"]]
|
|
||||||
|
|
||||||
self.send_message(channel, f"💨 {nickname}: You fled from the wild {encounter['species_name']}! You can now explore again.")
|
|
||||||
self.send_message(channel, f"💡 Use !explore to search for another encounter!")
|
|
||||||
|
|
@ -13,13 +13,13 @@ class GymBattles(BaseModule):
|
||||||
if command == "gym":
|
if command == "gym":
|
||||||
if not args:
|
if not args:
|
||||||
await self.cmd_gym_list(channel, nickname)
|
await self.cmd_gym_list(channel, nickname)
|
||||||
elif self.normalize_input(args[0]) == "list":
|
elif args[0] == "list":
|
||||||
await self.cmd_gym_list_all(channel, nickname)
|
await self.cmd_gym_list_all(channel, nickname)
|
||||||
elif self.normalize_input(args[0]) == "challenge":
|
elif args[0] == "challenge":
|
||||||
await self.cmd_gym_challenge(channel, nickname, args[1:])
|
await self.cmd_gym_challenge(channel, nickname, args[1:])
|
||||||
elif self.normalize_input(args[0]) == "info":
|
elif args[0] == "info":
|
||||||
await self.cmd_gym_info(channel, nickname, args[1:])
|
await self.cmd_gym_info(channel, nickname, args[1:])
|
||||||
elif self.normalize_input(args[0]) == "status":
|
elif args[0] == "status":
|
||||||
await self.cmd_gym_status(channel, nickname)
|
await self.cmd_gym_status(channel, nickname)
|
||||||
else:
|
else:
|
||||||
await self.cmd_gym_list(channel, nickname)
|
await self.cmd_gym_list(channel, nickname)
|
||||||
|
|
@ -66,7 +66,7 @@ class GymBattles(BaseModule):
|
||||||
f" Status: {status} | Next difficulty: {difficulty}")
|
f" Status: {status} | Next difficulty: {difficulty}")
|
||||||
|
|
||||||
self.send_message(channel,
|
self.send_message(channel,
|
||||||
f"💡 Use '!gym challenge' to battle (gym name optional if only one gym in location)!")
|
f"💡 Use '!gym challenge \"<gym name>\"' to battle!")
|
||||||
|
|
||||||
async def cmd_gym_list_all(self, channel, nickname):
|
async def cmd_gym_list_all(self, channel, nickname):
|
||||||
"""List all gyms across all locations"""
|
"""List all gyms across all locations"""
|
||||||
|
|
@ -97,6 +97,10 @@ class GymBattles(BaseModule):
|
||||||
|
|
||||||
async def cmd_gym_challenge(self, channel, nickname, args):
|
async def cmd_gym_challenge(self, channel, nickname, args):
|
||||||
"""Challenge a gym"""
|
"""Challenge a gym"""
|
||||||
|
if not args:
|
||||||
|
self.send_message(channel, f"{nickname}: Specify a gym to challenge! Example: !gym challenge \"Forest Guardian\"")
|
||||||
|
return
|
||||||
|
|
||||||
player = await self.require_player(channel, nickname)
|
player = await self.require_player(channel, nickname)
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
@ -107,37 +111,19 @@ class GymBattles(BaseModule):
|
||||||
self.send_message(channel, f"{nickname}: You are not in a valid location! Use !travel to go somewhere first.")
|
self.send_message(channel, f"{nickname}: You are not in a valid location! Use !travel to go somewhere first.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get available gyms in current location
|
gym_name = " ".join(args).strip('"')
|
||||||
available_gyms = await self.database.get_gyms_in_location(location["id"])
|
|
||||||
if not available_gyms:
|
|
||||||
self.send_message(channel, f"{nickname}: No gyms found in {location['name']}! Try traveling to a different location.")
|
|
||||||
return
|
|
||||||
|
|
||||||
gym = None
|
# Look for gym in player's current location (case-insensitive)
|
||||||
|
gym = await self.database.get_gym_by_name_in_location(gym_name, location["id"])
|
||||||
if not args:
|
if not gym:
|
||||||
# No gym name provided - auto-challenge if only one gym, otherwise list options
|
# List available gyms in current location for helpful error message
|
||||||
if len(available_gyms) == 1:
|
available_gyms = await self.database.get_gyms_in_location(location["id"])
|
||||||
gym = available_gyms[0]
|
if available_gyms:
|
||||||
self.send_message(channel, f"🏛️ {nickname}: Challenging the {gym['name']} gym in {location['name']}!")
|
|
||||||
else:
|
|
||||||
# Multiple gyms - show list and ask user to specify
|
|
||||||
gym_list = ", ".join([f'"{g["name"]}"' for g in available_gyms])
|
|
||||||
self.send_message(channel, f"{nickname}: Multiple gyms found in {location['name']}! Specify which gym to challenge:")
|
|
||||||
self.send_message(channel, f"Available gyms: {gym_list}")
|
|
||||||
self.send_message(channel, f"💡 Use: !gym challenge \"<gym name>\"")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# Gym name provided - find specific gym
|
|
||||||
gym_name = " ".join(self.normalize_input(args)).strip('"')
|
|
||||||
|
|
||||||
# Look for gym in player's current location (case-insensitive)
|
|
||||||
gym = await self.database.get_gym_by_name_in_location(gym_name, location["id"])
|
|
||||||
if not gym:
|
|
||||||
# List available gyms in current location for helpful error message
|
|
||||||
gym_list = ", ".join([f'"{g["name"]}"' for g in available_gyms])
|
gym_list = ", ".join([f'"{g["name"]}"' for g in available_gyms])
|
||||||
self.send_message(channel, f"{nickname}: No gym named '{gym_name}' found in {location['name']}! Available gyms: {gym_list}")
|
self.send_message(channel, f"{nickname}: No gym named '{gym_name}' found in {location['name']}! Available gyms: {gym_list}")
|
||||||
return
|
else:
|
||||||
|
self.send_message(channel, f"{nickname}: No gyms found in {location['name']}! Try traveling to a different location.")
|
||||||
|
return
|
||||||
|
|
||||||
# Check if player has active pets
|
# Check if player has active pets
|
||||||
active_pets = await self.database.get_active_pets(player["id"])
|
active_pets = await self.database.get_active_pets(player["id"])
|
||||||
|
|
@ -280,7 +266,7 @@ class GymBattles(BaseModule):
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
gym_name = " ".join(self.normalize_input(args)).strip('"')
|
gym_name = " ".join(args).strip('"')
|
||||||
|
|
||||||
# First try to find gym in player's current location
|
# First try to find gym in player's current location
|
||||||
location = await self.database.get_player_location(player["id"])
|
location = await self.database.get_player_location(player["id"])
|
||||||
|
|
@ -325,7 +311,7 @@ class GymBattles(BaseModule):
|
||||||
return
|
return
|
||||||
|
|
||||||
# This will show a summary - for detailed view they can use !gym list
|
# This will show a summary - for detailed view they can use !gym list
|
||||||
self.send_message(channel, f"🏆 {nickname}: Use !gym list to see all gym progress, or check your profile at: http://petz.rdx4.com/player/{nickname}#gym-badges")
|
self.send_message(channel, f"🏆 {nickname}: Use !gym list to see all gym progress, or check your profile at: http://petz.rdx4.com/player/{nickname}")
|
||||||
|
|
||||||
async def cmd_forfeit(self, channel, nickname):
|
async def cmd_forfeit(self, channel, nickname):
|
||||||
"""Forfeit the current gym battle"""
|
"""Forfeit the current gym battle"""
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,51 @@ class Inventory(BaseModule):
|
||||||
await self.cmd_use_item(channel, nickname, args)
|
await self.cmd_use_item(channel, nickname, args)
|
||||||
|
|
||||||
async def cmd_inventory(self, channel, nickname):
|
async def cmd_inventory(self, channel, nickname):
|
||||||
"""Redirect player to their web profile for inventory management"""
|
"""Display player's inventory"""
|
||||||
player = await self.require_player(channel, nickname)
|
player = await self.require_player(channel, nickname)
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Redirect to web interface for better inventory management
|
inventory = await self.database.get_player_inventory(player["id"])
|
||||||
self.send_message(channel, f"🎒 {nickname}: View your complete inventory at: http://petz.rdx4.com/player/{nickname}#inventory")
|
|
||||||
self.send_message(channel, f"💡 The web interface shows detailed item information, categories, and usage options!")
|
if not inventory:
|
||||||
|
self.send_message(channel, f"🎒 {nickname}: Your inventory is empty! Try exploring to find items.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Group items by category
|
||||||
|
categories = {}
|
||||||
|
for item in inventory:
|
||||||
|
category = item["category"]
|
||||||
|
if category not in categories:
|
||||||
|
categories[category] = []
|
||||||
|
categories[category].append(item)
|
||||||
|
|
||||||
|
# Send inventory summary first
|
||||||
|
total_items = sum(item["quantity"] for item in inventory)
|
||||||
|
self.send_message(channel, f"🎒 {nickname}'s Inventory ({total_items} items):")
|
||||||
|
|
||||||
|
# Display items by category
|
||||||
|
rarity_symbols = {
|
||||||
|
"common": "○",
|
||||||
|
"uncommon": "◇",
|
||||||
|
"rare": "◆",
|
||||||
|
"epic": "★",
|
||||||
|
"legendary": "✦"
|
||||||
|
}
|
||||||
|
|
||||||
|
for category, items in categories.items():
|
||||||
|
category_display = category.replace("_", " ").title()
|
||||||
|
self.send_message(channel, f"📦 {category_display}:")
|
||||||
|
|
||||||
|
for item in items[:5]: # Limit to 5 items per category to avoid spam
|
||||||
|
symbol = rarity_symbols.get(item["rarity"], "○")
|
||||||
|
quantity_str = f" x{item['quantity']}" if item["quantity"] > 1 else ""
|
||||||
|
self.send_message(channel, f" {symbol} {item['name']}{quantity_str} - {item['description']}")
|
||||||
|
|
||||||
|
if len(items) > 5:
|
||||||
|
self.send_message(channel, f" ... and {len(items) - 5} more items")
|
||||||
|
|
||||||
|
self.send_message(channel, f"💡 Use '!use <item name>' to use consumable items!")
|
||||||
|
|
||||||
async def cmd_use_item(self, channel, nickname, args):
|
async def cmd_use_item(self, channel, nickname, args):
|
||||||
"""Use an item from inventory"""
|
"""Use an item from inventory"""
|
||||||
|
|
@ -35,7 +72,7 @@ class Inventory(BaseModule):
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
item_name = " ".join(self.normalize_input(args))
|
item_name = " ".join(args)
|
||||||
result = await self.database.use_item(player["id"], item_name)
|
result = await self.database.use_item(player["id"], item_name)
|
||||||
|
|
||||||
if not result["success"]:
|
if not result["success"]:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ class PetManagement(BaseModule):
|
||||||
"""Handles team, pets, and future pet management commands"""
|
"""Handles team, pets, and future pet management commands"""
|
||||||
|
|
||||||
def get_commands(self):
|
def get_commands(self):
|
||||||
return ["team", "pets", "activate", "deactivate", "nickname"]
|
return ["team", "pets", "activate", "deactivate", "swap", "nickname"]
|
||||||
|
|
||||||
async def handle_command(self, channel, nickname, command, args):
|
async def handle_command(self, channel, nickname, command, args):
|
||||||
if command == "team":
|
if command == "team":
|
||||||
|
|
@ -18,6 +18,8 @@ class PetManagement(BaseModule):
|
||||||
await self.cmd_activate(channel, nickname, args)
|
await self.cmd_activate(channel, nickname, args)
|
||||||
elif command == "deactivate":
|
elif command == "deactivate":
|
||||||
await self.cmd_deactivate(channel, nickname, args)
|
await self.cmd_deactivate(channel, nickname, args)
|
||||||
|
elif command == "swap":
|
||||||
|
await self.cmd_swap(channel, nickname, args)
|
||||||
elif command == "nickname":
|
elif command == "nickname":
|
||||||
await self.cmd_nickname(channel, nickname, args)
|
await self.cmd_nickname(channel, nickname, args)
|
||||||
|
|
||||||
|
|
@ -38,7 +40,7 @@ class PetManagement(BaseModule):
|
||||||
|
|
||||||
team_info = []
|
team_info = []
|
||||||
|
|
||||||
# Active pets with star and team position
|
# 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"]
|
||||||
|
|
||||||
|
|
@ -53,9 +55,7 @@ class PetManagement(BaseModule):
|
||||||
else:
|
else:
|
||||||
exp_display = f"{exp_needed} to next"
|
exp_display = f"{exp_needed} to next"
|
||||||
|
|
||||||
# Show team position
|
team_info.append(f"⭐{name} (Lv.{pet['level']}) - {pet['hp']}/{pet['max_hp']} HP | EXP: {exp_display}")
|
||||||
position = pet.get("team_order", "?")
|
|
||||||
team_info.append(f"[{position}]⭐{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
|
||||||
|
|
@ -66,7 +66,6 @@ class PetManagement(BaseModule):
|
||||||
team_info.append(f"... and {len(inactive_pets) - 5} more in storage")
|
team_info.append(f"... and {len(inactive_pets) - 5} more in storage")
|
||||||
|
|
||||||
self.send_message(channel, f"🐾 {nickname}'s team: " + " | ".join(team_info))
|
self.send_message(channel, f"🐾 {nickname}'s team: " + " | ".join(team_info))
|
||||||
self.send_message(channel, f"🌐 View detailed pet collection at: http://petz.rdx4.com/player/{nickname}#pets")
|
|
||||||
|
|
||||||
async def cmd_pets(self, channel, nickname):
|
async def cmd_pets(self, channel, nickname):
|
||||||
"""Show link to pet collection web page"""
|
"""Show link to pet collection web page"""
|
||||||
|
|
@ -75,7 +74,7 @@ class PetManagement(BaseModule):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Send URL to player's profile page instead of PM spam
|
# Send URL to player's profile page instead of PM spam
|
||||||
self.send_message(channel, f"{nickname}: View your complete pet collection at: http://petz.rdx4.com/player/{nickname}#pets")
|
self.send_message(channel, f"{nickname}: View your complete pet collection at: http://petz.rdx4.com/player/{nickname}")
|
||||||
|
|
||||||
async def cmd_activate(self, channel, nickname, args):
|
async def cmd_activate(self, channel, nickname, args):
|
||||||
"""Activate a pet for battle (PM only)"""
|
"""Activate a pet for battle (PM only)"""
|
||||||
|
|
@ -89,14 +88,13 @@ class PetManagement(BaseModule):
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
pet_name = " ".join(self.normalize_input(args))
|
pet_name = " ".join(args)
|
||||||
result = await self.database.activate_pet(player["id"], pet_name)
|
result = await self.database.activate_pet(player["id"], pet_name)
|
||||||
|
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
pet = result["pet"]
|
pet = result["pet"]
|
||||||
display_name = pet["nickname"] or pet["species_name"]
|
display_name = pet["nickname"] or pet["species_name"]
|
||||||
position = result.get("team_position", "?")
|
self.send_pm(nickname, f"✅ {display_name} is now active for battle!")
|
||||||
self.send_pm(nickname, f"✅ {display_name} is now active for battle! Team position: {position}")
|
|
||||||
self.send_message(channel, f"{nickname}: Pet activated successfully!")
|
self.send_message(channel, f"{nickname}: Pet activated successfully!")
|
||||||
else:
|
else:
|
||||||
self.send_pm(nickname, f"❌ {result['error']}")
|
self.send_pm(nickname, f"❌ {result['error']}")
|
||||||
|
|
@ -114,7 +112,7 @@ class PetManagement(BaseModule):
|
||||||
if not player:
|
if not player:
|
||||||
return
|
return
|
||||||
|
|
||||||
pet_name = " ".join(self.normalize_input(args))
|
pet_name = " ".join(args)
|
||||||
result = await self.database.deactivate_pet(player["id"], pet_name)
|
result = await self.database.deactivate_pet(player["id"], pet_name)
|
||||||
|
|
||||||
if result["success"]:
|
if result["success"]:
|
||||||
|
|
@ -126,6 +124,44 @@ class PetManagement(BaseModule):
|
||||||
self.send_pm(nickname, f"❌ {result['error']}")
|
self.send_pm(nickname, f"❌ {result['error']}")
|
||||||
self.send_message(channel, f"{nickname}: Pet deactivation failed - check PM for details!")
|
self.send_message(channel, f"{nickname}: Pet deactivation failed - check PM for details!")
|
||||||
|
|
||||||
|
async def cmd_swap(self, channel, nickname, args):
|
||||||
|
"""Swap active/storage status of two pets (PM only)"""
|
||||||
|
# Redirect to PM for privacy
|
||||||
|
if len(args) < 2:
|
||||||
|
self.send_pm(nickname, "Usage: !swap <pet1> <pet2>")
|
||||||
|
self.send_pm(nickname, "Example: !swap Flamey Aqua")
|
||||||
|
self.send_message(channel, f"{nickname}: Pet swap instructions sent via PM!")
|
||||||
|
return
|
||||||
|
|
||||||
|
player = await self.require_player(channel, nickname)
|
||||||
|
if not player:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle multi-word pet names by splitting on first space vs last space
|
||||||
|
if len(args) == 2:
|
||||||
|
pet1_name, pet2_name = args
|
||||||
|
else:
|
||||||
|
# For more complex parsing, assume equal split
|
||||||
|
mid_point = len(args) // 2
|
||||||
|
pet1_name = " ".join(args[:mid_point])
|
||||||
|
pet2_name = " ".join(args[mid_point:])
|
||||||
|
|
||||||
|
result = await self.database.swap_pets(player["id"], pet1_name, pet2_name)
|
||||||
|
|
||||||
|
if result["success"]:
|
||||||
|
pet1 = result["pet1"]
|
||||||
|
pet2 = result["pet2"]
|
||||||
|
pet1_display = pet1["nickname"] or pet1["species_name"]
|
||||||
|
pet2_display = pet2["nickname"] or pet2["species_name"]
|
||||||
|
|
||||||
|
self.send_pm(nickname, f"🔄 Swap complete!")
|
||||||
|
self.send_pm(nickname, f" • {pet1_display} → {result['pet1_now']}")
|
||||||
|
self.send_pm(nickname, f" • {pet2_display} → {result['pet2_now']}")
|
||||||
|
self.send_message(channel, f"{nickname}: Pet swap completed!")
|
||||||
|
else:
|
||||||
|
self.send_pm(nickname, f"❌ {result['error']}")
|
||||||
|
self.send_message(channel, f"{nickname}: Pet swap failed - check PM for details!")
|
||||||
|
|
||||||
async def cmd_nickname(self, channel, nickname, args):
|
async def cmd_nickname(self, channel, nickname, args):
|
||||||
"""Set a nickname for a pet"""
|
"""Set a nickname for a pet"""
|
||||||
if len(args) < 2:
|
if len(args) < 2:
|
||||||
|
|
@ -138,7 +174,7 @@ class PetManagement(BaseModule):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Split args into pet identifier and new nickname
|
# Split args into pet identifier and new nickname
|
||||||
pet_identifier = self.normalize_input(args[0])
|
pet_identifier = args[0]
|
||||||
new_nickname = " ".join(args[1:])
|
new_nickname = " ".join(args[1:])
|
||||||
|
|
||||||
result = await self.database.set_pet_nickname(player["id"], pet_identifier, new_nickname)
|
result = await self.database.set_pet_nickname(player["id"], pet_identifier, new_nickname)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class PetBotDebug:
|
||||||
print("✅ Background validation started")
|
print("✅ Background validation started")
|
||||||
|
|
||||||
print("🔄 Starting web server...")
|
print("🔄 Starting web server...")
|
||||||
self.web_server = PetBotWebServer(self.database, port=8080, bot=self)
|
self.web_server = PetBotWebServer(self.database, port=8080)
|
||||||
self.web_server.start_in_thread()
|
self.web_server.start_in_thread()
|
||||||
print("✅ Web server started")
|
print("✅ Web server started")
|
||||||
|
|
||||||
|
|
@ -303,14 +303,12 @@ class PetBotDebug:
|
||||||
self.handle_command(channel, nickname, message)
|
self.handle_command(channel, nickname, message)
|
||||||
|
|
||||||
def handle_command(self, channel, nickname, message):
|
def handle_command(self, channel, nickname, message):
|
||||||
from modules.base_module import BaseModule
|
|
||||||
|
|
||||||
command_parts = message[1:].split()
|
command_parts = message[1:].split()
|
||||||
if not command_parts:
|
if not command_parts:
|
||||||
return
|
return
|
||||||
|
|
||||||
command = BaseModule.normalize_input(command_parts[0])
|
command = command_parts[0].lower()
|
||||||
args = BaseModule.normalize_input(command_parts[1:])
|
args = command_parts[1:]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if command in self.command_map:
|
if command in self.command_map:
|
||||||
|
|
|
||||||
238
src/database.py
238
src/database.py
|
|
@ -120,46 +120,6 @@ class Database:
|
||||||
except:
|
except:
|
||||||
pass # Column already exists
|
pass # Column already exists
|
||||||
|
|
||||||
# Add team_order column if it doesn't exist
|
|
||||||
try:
|
|
||||||
await db.execute("ALTER TABLE pets ADD COLUMN team_order INTEGER DEFAULT NULL")
|
|
||||||
await db.commit()
|
|
||||||
print("Added team_order column to pets table")
|
|
||||||
except:
|
|
||||||
pass # Column already exists
|
|
||||||
|
|
||||||
# Migrate existing active pets to have team_order values
|
|
||||||
try:
|
|
||||||
# Find active pets without team_order
|
|
||||||
cursor = await db.execute("""
|
|
||||||
SELECT id, player_id FROM pets
|
|
||||||
WHERE is_active = TRUE AND team_order IS NULL
|
|
||||||
ORDER BY player_id, id
|
|
||||||
""")
|
|
||||||
pets_to_migrate = await cursor.fetchall()
|
|
||||||
|
|
||||||
if pets_to_migrate:
|
|
||||||
print(f"Migrating {len(pets_to_migrate)} active pets to have team_order values...")
|
|
||||||
|
|
||||||
# Group pets by player
|
|
||||||
from collections import defaultdict
|
|
||||||
pets_by_player = defaultdict(list)
|
|
||||||
for pet in pets_to_migrate:
|
|
||||||
pets_by_player[pet[1]].append(pet[0])
|
|
||||||
|
|
||||||
# Assign team_order values for each player
|
|
||||||
for player_id, pet_ids in pets_by_player.items():
|
|
||||||
for i, pet_id in enumerate(pet_ids[:6]): # Max 6 pets per team
|
|
||||||
await db.execute("""
|
|
||||||
UPDATE pets SET team_order = ? WHERE id = ?
|
|
||||||
""", (i + 1, pet_id))
|
|
||||||
|
|
||||||
await db.commit()
|
|
||||||
print("Migration completed successfully")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Migration warning: {e}")
|
|
||||||
pass # Don't fail if migration has issues
|
|
||||||
|
|
||||||
await db.execute("""
|
await db.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS location_spawns (
|
CREATE TABLE IF NOT EXISTS location_spawns (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
@ -448,9 +408,6 @@ class Database:
|
||||||
if active_only:
|
if active_only:
|
||||||
query += " AND p.is_active = TRUE"
|
query += " AND p.is_active = TRUE"
|
||||||
|
|
||||||
# Order by team position for active pets, then by id for storage pets
|
|
||||||
query += " ORDER BY CASE WHEN p.is_active THEN COALESCE(p.team_order, 999) ELSE 999 END ASC, p.id ASC"
|
|
||||||
|
|
||||||
cursor = await db.execute(query, params)
|
cursor = await db.execute(query, params)
|
||||||
rows = await cursor.fetchall()
|
rows = await cursor.fetchall()
|
||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
@ -681,16 +638,11 @@ class Database:
|
||||||
if not pet:
|
if not pet:
|
||||||
return {"success": False, "error": f"No inactive pet found named '{pet_identifier}'"}
|
return {"success": False, "error": f"No inactive pet found named '{pet_identifier}'"}
|
||||||
|
|
||||||
# Get next available team slot
|
# Activate the pet
|
||||||
next_slot = await self.get_next_available_team_slot(player_id)
|
await db.execute("UPDATE pets SET is_active = TRUE WHERE id = ?", (pet["id"],))
|
||||||
if next_slot is None:
|
|
||||||
return {"success": False, "error": "Team is full (maximum 6 pets)"}
|
|
||||||
|
|
||||||
# Activate the pet and assign team position
|
|
||||||
await db.execute("UPDATE pets SET is_active = TRUE, team_order = ? WHERE id = ?", (next_slot, pet["id"]))
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return {"success": True, "pet": dict(pet), "team_position": next_slot}
|
return {"success": True, "pet": dict(pet)}
|
||||||
|
|
||||||
async def deactivate_pet(self, player_id: int, pet_identifier: str) -> Dict:
|
async def deactivate_pet(self, player_id: int, pet_identifier: str) -> Dict:
|
||||||
"""Deactivate a pet by name or species name. Returns result dict."""
|
"""Deactivate a pet by name or species name. Returns result dict."""
|
||||||
|
|
@ -718,122 +670,58 @@ class Database:
|
||||||
if active_count["count"] <= 1:
|
if active_count["count"] <= 1:
|
||||||
return {"success": False, "error": "You must have at least one active pet!"}
|
return {"success": False, "error": "You must have at least one active pet!"}
|
||||||
|
|
||||||
# Deactivate the pet and clear team order
|
# Deactivate the pet
|
||||||
await db.execute("UPDATE pets SET is_active = FALSE, team_order = NULL WHERE id = ?", (pet["id"],))
|
await db.execute("UPDATE pets SET is_active = FALSE WHERE id = ?", (pet["id"],))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return {"success": True, "pet": dict(pet)}
|
return {"success": True, "pet": dict(pet)}
|
||||||
|
|
||||||
# Team Order Methods
|
async def swap_pets(self, player_id: int, pet1_identifier: str, pet2_identifier: str) -> Dict:
|
||||||
async def get_next_available_team_slot(self, player_id: int) -> int:
|
"""Swap the active status of two pets. Returns result dict."""
|
||||||
"""Get the next available team slot (1-6)"""
|
|
||||||
async with aiosqlite.connect(self.db_path) as db:
|
async with aiosqlite.connect(self.db_path) as db:
|
||||||
|
db.row_factory = aiosqlite.Row
|
||||||
|
|
||||||
|
# Find both pets
|
||||||
cursor = await db.execute("""
|
cursor = await db.execute("""
|
||||||
SELECT team_order FROM pets
|
SELECT p.*, ps.name as species_name
|
||||||
WHERE player_id = ? AND is_active = TRUE AND team_order IS NOT NULL
|
FROM pets p
|
||||||
ORDER BY team_order ASC
|
JOIN pet_species ps ON p.species_id = ps.id
|
||||||
""", (player_id,))
|
WHERE p.player_id = ?
|
||||||
used_slots = [row[0] for row in await cursor.fetchall()]
|
AND (p.nickname = ? OR ps.name = ?)
|
||||||
|
LIMIT 1
|
||||||
|
""", (player_id, pet1_identifier, pet1_identifier))
|
||||||
|
pet1 = await cursor.fetchone()
|
||||||
|
|
||||||
# Find first available slot (1-6)
|
|
||||||
for slot in range(1, 7):
|
|
||||||
if slot not in used_slots:
|
|
||||||
return slot
|
|
||||||
return None # Team is full
|
|
||||||
|
|
||||||
async def set_pet_team_order(self, player_id: int, pet_id: int, position: int) -> Dict:
|
|
||||||
"""Set a pet's team order position (1-6)"""
|
|
||||||
if position < 1 or position > 6:
|
|
||||||
return {"success": False, "error": "Team position must be between 1-6"}
|
|
||||||
|
|
||||||
async with aiosqlite.connect(self.db_path) as db:
|
|
||||||
# Check if pet belongs to player
|
|
||||||
cursor = await db.execute("SELECT * FROM pets WHERE id = ? AND player_id = ?", (pet_id, player_id))
|
|
||||||
pet = await cursor.fetchone()
|
|
||||||
if not pet:
|
|
||||||
return {"success": False, "error": "Pet not found"}
|
|
||||||
|
|
||||||
# Check if position is already taken
|
|
||||||
cursor = await db.execute("""
|
cursor = await db.execute("""
|
||||||
SELECT id FROM pets
|
SELECT p.*, ps.name as species_name
|
||||||
WHERE player_id = ? AND team_order = ? AND is_active = TRUE AND id != ?
|
FROM pets p
|
||||||
""", (player_id, position, pet_id))
|
JOIN pet_species ps ON p.species_id = ps.id
|
||||||
existing_pet = await cursor.fetchone()
|
WHERE p.player_id = ?
|
||||||
|
AND (p.nickname = ? OR ps.name = ?)
|
||||||
|
LIMIT 1
|
||||||
|
""", (player_id, pet2_identifier, pet2_identifier))
|
||||||
|
pet2 = await cursor.fetchone()
|
||||||
|
|
||||||
if existing_pet:
|
if not pet1:
|
||||||
return {"success": False, "error": f"Position {position} is already taken"}
|
return {"success": False, "error": f"Pet '{pet1_identifier}' not found"}
|
||||||
|
if not pet2:
|
||||||
|
return {"success": False, "error": f"Pet '{pet2_identifier}' not found"}
|
||||||
|
|
||||||
# Update pet's team order and make it active
|
if pet1["id"] == pet2["id"]:
|
||||||
await db.execute("""
|
return {"success": False, "error": "Cannot swap a pet with itself"}
|
||||||
UPDATE pets SET team_order = ?, is_active = TRUE
|
|
||||||
WHERE id = ? AND player_id = ?
|
|
||||||
""", (position, pet_id, player_id))
|
|
||||||
|
|
||||||
|
# Swap their active status
|
||||||
|
await db.execute("UPDATE pets SET is_active = ? WHERE id = ?", (not pet1["is_active"], pet1["id"]))
|
||||||
|
await db.execute("UPDATE pets SET is_active = ? WHERE id = ?", (not pet2["is_active"], pet2["id"]))
|
||||||
await db.commit()
|
await db.commit()
|
||||||
return {"success": True, "position": position}
|
|
||||||
|
|
||||||
async def reorder_team_positions(self, player_id: int, new_positions: List[Dict]) -> Dict:
|
|
||||||
"""Reorder team positions based on new arrangement"""
|
|
||||||
async with aiosqlite.connect(self.db_path) as db:
|
|
||||||
try:
|
|
||||||
# Validate all positions are 1-6 and no duplicates
|
|
||||||
positions = [pos["position"] for pos in new_positions]
|
|
||||||
if len(set(positions)) != len(positions):
|
|
||||||
return {"success": False, "error": "Duplicate positions detected"}
|
|
||||||
|
|
||||||
for pos_data in new_positions:
|
|
||||||
position = pos_data["position"]
|
|
||||||
pet_id = pos_data["pet_id"]
|
|
||||||
|
|
||||||
if position < 1 or position > 6:
|
|
||||||
return {"success": False, "error": f"Invalid position {position}"}
|
|
||||||
|
|
||||||
# Verify pet belongs to player
|
|
||||||
cursor = await db.execute("SELECT id FROM pets WHERE id = ? AND player_id = ?", (pet_id, player_id))
|
|
||||||
if not await cursor.fetchone():
|
|
||||||
return {"success": False, "error": f"Pet {pet_id} not found"}
|
|
||||||
|
|
||||||
# Clear all team orders first
|
|
||||||
await db.execute("UPDATE pets SET team_order = NULL WHERE player_id = ?", (player_id,))
|
|
||||||
|
|
||||||
# Set new positions
|
|
||||||
for pos_data in new_positions:
|
|
||||||
await db.execute("""
|
|
||||||
UPDATE pets SET team_order = ?, is_active = TRUE
|
|
||||||
WHERE id = ? AND player_id = ?
|
|
||||||
""", (pos_data["position"], pos_data["pet_id"], player_id))
|
|
||||||
|
|
||||||
await db.commit()
|
|
||||||
return {"success": True, "message": "Team order updated successfully"}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
await db.rollback()
|
|
||||||
return {"success": False, "error": str(e)}
|
|
||||||
|
|
||||||
async def remove_from_team_position(self, player_id: int, pet_id: int) -> Dict:
|
|
||||||
"""Remove a pet from team (set to inactive and clear team_order)"""
|
|
||||||
async with aiosqlite.connect(self.db_path) as db:
|
|
||||||
# Check if pet belongs to player
|
|
||||||
cursor = await db.execute("SELECT * FROM pets WHERE id = ? AND player_id = ?", (pet_id, player_id))
|
|
||||||
pet = await cursor.fetchone()
|
|
||||||
if not pet:
|
|
||||||
return {"success": False, "error": "Pet not found"}
|
|
||||||
|
|
||||||
# Check if this is the only active pet
|
return {
|
||||||
cursor = await db.execute("SELECT COUNT(*) as count FROM pets WHERE player_id = ? AND is_active = TRUE", (player_id,))
|
"success": True,
|
||||||
active_count = await cursor.fetchone()
|
"pet1": dict(pet1),
|
||||||
|
"pet2": dict(pet2),
|
||||||
if active_count["count"] <= 1:
|
"pet1_now": "active" if not pet1["is_active"] else "storage",
|
||||||
return {"success": False, "error": "Cannot deactivate your only active pet"}
|
"pet2_now": "active" if not pet2["is_active"] else "storage"
|
||||||
|
}
|
||||||
# Remove from team
|
|
||||||
await db.execute("""
|
|
||||||
UPDATE pets SET is_active = FALSE, team_order = NULL
|
|
||||||
WHERE id = ? AND player_id = ?
|
|
||||||
""", (pet_id, player_id))
|
|
||||||
|
|
||||||
await db.commit()
|
|
||||||
return {"success": True, "message": "Pet removed from team"}
|
|
||||||
|
|
||||||
# Item and Inventory Methods
|
# Item and Inventory Methods
|
||||||
async def add_item_to_inventory(self, player_id: int, item_name: str, quantity: int = 1) -> bool:
|
async def add_item_to_inventory(self, player_id: int, item_name: str, quantity: int = 1) -> bool:
|
||||||
|
|
@ -985,7 +873,7 @@ class Database:
|
||||||
FROM pets p
|
FROM pets p
|
||||||
JOIN pet_species ps ON p.species_id = ps.id
|
JOIN pet_species ps ON p.species_id = ps.id
|
||||||
WHERE p.player_id = ? AND p.is_active = 1
|
WHERE p.player_id = ? AND p.is_active = 1
|
||||||
ORDER BY p.team_order ASC, p.id ASC
|
ORDER BY p.id ASC
|
||||||
""", (player_id,))
|
""", (player_id,))
|
||||||
rows = await cursor.fetchall()
|
rows = await cursor.fetchall()
|
||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
@ -1713,18 +1601,12 @@ class Database:
|
||||||
# Begin transaction
|
# Begin transaction
|
||||||
await db.execute("BEGIN TRANSACTION")
|
await db.execute("BEGIN TRANSACTION")
|
||||||
|
|
||||||
# Update pet active status and team_order based on new team
|
# Update pet active status based on new team
|
||||||
for pet_id, position in team_changes.items():
|
for pet_id, is_active in team_changes.items():
|
||||||
if position: # If position is a number (1-6), pet is active
|
await db.execute("""
|
||||||
await db.execute("""
|
UPDATE pets SET is_active = ?
|
||||||
UPDATE pets SET is_active = TRUE, team_order = ?
|
WHERE id = ? AND player_id = ?
|
||||||
WHERE id = ? AND player_id = ?
|
""", (is_active, int(pet_id), player_id))
|
||||||
""", (position, int(pet_id), player_id))
|
|
||||||
else: # If position is False, pet is inactive
|
|
||||||
await db.execute("""
|
|
||||||
UPDATE pets SET is_active = FALSE, team_order = NULL
|
|
||||||
WHERE id = ? AND player_id = ?
|
|
||||||
""", (int(pet_id), player_id))
|
|
||||||
|
|
||||||
# Mark any pending change as verified
|
# Mark any pending change as verified
|
||||||
await db.execute("""
|
await db.execute("""
|
||||||
|
|
@ -1831,28 +1713,18 @@ class Database:
|
||||||
|
|
||||||
# Get current pet states
|
# Get current pet states
|
||||||
cursor = await db.execute("""
|
cursor = await db.execute("""
|
||||||
SELECT id, is_active, team_order FROM pets WHERE player_id = ?
|
SELECT id, is_active FROM pets WHERE player_id = ?
|
||||||
""", (player_id,))
|
""", (player_id,))
|
||||||
current_pets = {str(row["id"]): row["team_order"] if row["is_active"] else False for row in await cursor.fetchall()}
|
current_pets = {str(row["id"]): bool(row["is_active"]) for row in await cursor.fetchall()}
|
||||||
|
|
||||||
# Apply proposed changes to current state
|
# Apply proposed changes to current state
|
||||||
new_state = current_pets.copy()
|
new_state = current_pets.copy()
|
||||||
for pet_id, new_position in proposed_changes.items():
|
for pet_id, new_active_state in proposed_changes.items():
|
||||||
if pet_id in new_state:
|
if pet_id in new_state:
|
||||||
new_state[pet_id] = new_position
|
new_state[pet_id] = new_active_state
|
||||||
|
|
||||||
# Count active pets and validate positions
|
# Count active pets in new state
|
||||||
active_positions = [pos for pos in new_state.values() if pos]
|
active_count = sum(1 for is_active in new_state.values() if is_active)
|
||||||
active_count = len(active_positions)
|
|
||||||
|
|
||||||
# Check for valid positions (1-6)
|
|
||||||
for pos in active_positions:
|
|
||||||
if not isinstance(pos, int) or pos < 1 or pos > 6:
|
|
||||||
return {"valid": False, "error": f"Invalid team position: {pos}"}
|
|
||||||
|
|
||||||
# Check for duplicate positions
|
|
||||||
if len(active_positions) != len(set(active_positions)):
|
|
||||||
return {"valid": False, "error": "Duplicate team positions detected"}
|
|
||||||
|
|
||||||
# Validate constraints
|
# Validate constraints
|
||||||
if active_count < 1:
|
if active_count < 1:
|
||||||
|
|
|
||||||
|
|
@ -196,11 +196,11 @@ class GameEngine:
|
||||||
|
|
||||||
cursor = await db.execute("""
|
cursor = await db.execute("""
|
||||||
INSERT INTO pets (player_id, species_id, level, experience, hp, max_hp,
|
INSERT INTO pets (player_id, species_id, level, experience, hp, max_hp,
|
||||||
attack, defense, speed, is_active, team_order)
|
attack, defense, speed, is_active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""", (player_id, species["id"], pet_data["level"], 0,
|
""", (player_id, species["id"], pet_data["level"], 0,
|
||||||
pet_data["hp"], pet_data["hp"], pet_data["attack"],
|
pet_data["hp"], pet_data["hp"], pet_data["attack"],
|
||||||
pet_data["defense"], pet_data["speed"], True, 1))
|
pet_data["defense"], pet_data["speed"], True))
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
|
|
|
||||||
4037
webserver.py
4037
webserver.py
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue