Implement comprehensive pet healing system with revive items and database support
- Add Revive (50% HP) and Max Revive (100% HP) items to config/items.json - Extend database schema with fainted_at timestamp for pets table - Add last_heal_time column to players table for heal command cooldown - Implement comprehensive pet healing database methods: - get_fainted_pets(): Retrieve all fainted pets for a player - revive_pet(): Restore fainted pet with specified HP - faint_pet(): Mark pet as fainted with timestamp - get_pets_for_auto_recovery(): Find pets eligible for 30-minute auto-recovery - auto_recover_pet(): Automatically restore pet to 1 HP - get_active_pets(): Get active pets excluding fainted ones - get_player_pets(): Enhanced to filter out fainted pets when active_only=True - Update inventory module to handle revive and max_revive effects - Add proper error handling for cases where no fainted pets exist - Maintain case-insensitive item usage compatibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
fca0423c84
commit
72c1098a22
3 changed files with 440 additions and 3 deletions
|
|
@ -48,6 +48,28 @@
|
|||
"effect_value": 15,
|
||||
"locations": ["mystic_forest", "enchanted_grove"],
|
||||
"spawn_rate": 0.03
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"name": "Revive",
|
||||
"description": "Revives a fainted pet and restores 50% of its HP",
|
||||
"rarity": "rare",
|
||||
"category": "healing",
|
||||
"effect": "revive",
|
||||
"effect_value": 50,
|
||||
"locations": ["all"],
|
||||
"spawn_rate": 0.005
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"name": "Max Revive",
|
||||
"description": "Revives a fainted pet and fully restores its HP",
|
||||
"rarity": "epic",
|
||||
"category": "healing",
|
||||
"effect": "max_revive",
|
||||
"effect_value": 100,
|
||||
"locations": ["all"],
|
||||
"spawn_rate": 0.002
|
||||
}
|
||||
],
|
||||
"battle_items": [
|
||||
|
|
|
|||
|
|
@ -99,6 +99,41 @@ class Inventory(BaseModule):
|
|||
self.send_message(channel,
|
||||
f"🍀 {nickname}: Used {item['name']}! Rare pet encounter rate increased by {effect_value}% for 1 hour!")
|
||||
|
||||
elif effect == "revive":
|
||||
# Handle revive items for fainted pets
|
||||
fainted_pets = await self.database.get_fainted_pets(player["id"])
|
||||
if not fainted_pets:
|
||||
self.send_message(channel, f"❌ {nickname}: You don't have any fainted pets to revive!")
|
||||
return
|
||||
|
||||
# Use the first fainted pet (can be expanded to choose specific pet)
|
||||
pet = fainted_pets[0]
|
||||
new_hp = int(pet["max_hp"] * (effect_value / 100.0)) # Convert percentage to HP
|
||||
|
||||
# Revive the pet and restore HP
|
||||
await self.database.revive_pet(pet["id"], new_hp)
|
||||
|
||||
self.send_message(channel,
|
||||
f"💫 {nickname}: Used {item['name']} on {pet['nickname'] or pet['species_name']}! "
|
||||
f"Revived and restored {new_hp}/{pet['max_hp']} HP!")
|
||||
|
||||
elif effect == "max_revive":
|
||||
# Handle max revive items for fainted pets
|
||||
fainted_pets = await self.database.get_fainted_pets(player["id"])
|
||||
if not fainted_pets:
|
||||
self.send_message(channel, f"❌ {nickname}: You don't have any fainted pets to revive!")
|
||||
return
|
||||
|
||||
# Use the first fainted pet (can be expanded to choose specific pet)
|
||||
pet = fainted_pets[0]
|
||||
|
||||
# Revive the pet and fully restore HP
|
||||
await self.database.revive_pet(pet["id"], pet["max_hp"])
|
||||
|
||||
self.send_message(channel,
|
||||
f"✨ {nickname}: Used {item['name']} on {pet['nickname'] or pet['species_name']}! "
|
||||
f"Revived and fully restored HP! ({pet['max_hp']}/{pet['max_hp']})")
|
||||
|
||||
elif effect == "money":
|
||||
# Handle money items (like Coin Pouch)
|
||||
import random
|
||||
|
|
|
|||
386
src/database.py
386
src/database.py
|
|
@ -169,6 +169,22 @@ class Database:
|
|||
print(f"Migration warning: {e}")
|
||||
pass # Don't fail if migration has issues
|
||||
|
||||
# Add fainted_at column for tracking when pets faint
|
||||
try:
|
||||
await db.execute("ALTER TABLE pets ADD COLUMN fainted_at TIMESTAMP DEFAULT NULL")
|
||||
await db.commit()
|
||||
print("Added fainted_at column to pets table")
|
||||
except:
|
||||
pass # Column already exists
|
||||
|
||||
# Add last_heal_time column for heal command cooldown
|
||||
try:
|
||||
await db.execute("ALTER TABLE players ADD COLUMN last_heal_time TIMESTAMP DEFAULT NULL")
|
||||
await db.commit()
|
||||
print("Added last_heal_time column to players table")
|
||||
except:
|
||||
pass # Column already exists
|
||||
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS location_spawns (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
|
@ -349,6 +365,38 @@ class Database:
|
|||
)
|
||||
""")
|
||||
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS npc_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
difficulty INTEGER DEFAULT 1,
|
||||
target_contributions INTEGER NOT NULL,
|
||||
current_contributions INTEGER DEFAULT 0,
|
||||
reward_experience INTEGER DEFAULT 0,
|
||||
reward_money INTEGER DEFAULT 0,
|
||||
reward_items TEXT,
|
||||
status TEXT DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
completion_message TEXT
|
||||
)
|
||||
""")
|
||||
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS npc_event_contributions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
event_id INTEGER NOT NULL,
|
||||
player_id INTEGER NOT NULL,
|
||||
contributions INTEGER DEFAULT 0,
|
||||
last_contribution_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (event_id) REFERENCES npc_events (id),
|
||||
FOREIGN KEY (player_id) REFERENCES players (id),
|
||||
UNIQUE(event_id, player_id)
|
||||
)
|
||||
""")
|
||||
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS verification_pins (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
|
@ -863,7 +911,7 @@ class Database:
|
|||
"""Add an item to player's inventory"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
# Get item ID
|
||||
cursor = await db.execute("SELECT id FROM items WHERE name = ?", (item_name,))
|
||||
cursor = await db.execute("SELECT id FROM items WHERE LOWER(name) = LOWER(?)", (item_name,))
|
||||
item = await cursor.fetchone()
|
||||
if not item:
|
||||
return False
|
||||
|
|
@ -918,7 +966,7 @@ class Database:
|
|||
SELECT i.*, pi.quantity
|
||||
FROM items i
|
||||
JOIN player_inventory pi ON i.id = pi.item_id
|
||||
WHERE pi.player_id = ? AND i.name = ?
|
||||
WHERE pi.player_id = ? AND LOWER(i.name) = LOWER(?)
|
||||
""", (player_id, item_name))
|
||||
item = await cursor.fetchone()
|
||||
|
||||
|
|
@ -2065,4 +2113,336 @@ class Database:
|
|||
return cursor.rowcount > 0
|
||||
except Exception as e:
|
||||
print(f"Error renaming team configuration: {e}")
|
||||
return False
|
||||
return False
|
||||
|
||||
# NPC Events System Methods
|
||||
async def create_npc_event(self, event_data: Dict) -> int:
|
||||
"""Create a new NPC event"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
INSERT INTO npc_events
|
||||
(event_type, title, description, difficulty, target_contributions,
|
||||
reward_experience, reward_money, reward_items, expires_at, completion_message)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
event_data['event_type'],
|
||||
event_data['title'],
|
||||
event_data['description'],
|
||||
event_data['difficulty'],
|
||||
event_data['target_contributions'],
|
||||
event_data['reward_experience'],
|
||||
event_data['reward_money'],
|
||||
event_data.get('reward_items', ''),
|
||||
event_data['expires_at'],
|
||||
event_data.get('completion_message', '')
|
||||
))
|
||||
|
||||
await db.commit()
|
||||
return cursor.lastrowid
|
||||
|
||||
async def get_active_npc_events(self) -> List[Dict]:
|
||||
"""Get all active NPC events"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT * FROM npc_events
|
||||
WHERE status = 'active' AND expires_at > datetime('now')
|
||||
ORDER BY created_at DESC
|
||||
""")
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def get_npc_event_by_id(self, event_id: int) -> Optional[Dict]:
|
||||
"""Get a specific NPC event by ID"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT * FROM npc_events WHERE id = ?
|
||||
""", (event_id,))
|
||||
|
||||
row = await cursor.fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
async def contribute_to_npc_event(self, event_id: int, player_id: int, contribution: int) -> Dict:
|
||||
"""Add player contribution to an NPC event"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
try:
|
||||
await db.execute("BEGIN TRANSACTION")
|
||||
|
||||
# Insert or update player contribution
|
||||
await db.execute("""
|
||||
INSERT OR REPLACE INTO npc_event_contributions
|
||||
(event_id, player_id, contributions, last_contribution_at)
|
||||
VALUES (?, ?,
|
||||
COALESCE((SELECT contributions FROM npc_event_contributions
|
||||
WHERE event_id = ? AND player_id = ?), 0) + ?,
|
||||
CURRENT_TIMESTAMP)
|
||||
""", (event_id, player_id, event_id, player_id, contribution))
|
||||
|
||||
# Update total contributions for the event
|
||||
await db.execute("""
|
||||
UPDATE npc_events
|
||||
SET current_contributions = current_contributions + ?
|
||||
WHERE id = ?
|
||||
""", (contribution, event_id))
|
||||
|
||||
# Check if event is completed
|
||||
cursor = await db.execute("""
|
||||
SELECT current_contributions, target_contributions
|
||||
FROM npc_events WHERE id = ?
|
||||
""", (event_id,))
|
||||
|
||||
row = await cursor.fetchone()
|
||||
if row and row[0] >= row[1]:
|
||||
# Mark event as completed
|
||||
await db.execute("""
|
||||
UPDATE npc_events
|
||||
SET status = 'completed'
|
||||
WHERE id = ?
|
||||
""", (event_id,))
|
||||
|
||||
await db.commit()
|
||||
return {"success": True, "event_completed": True}
|
||||
|
||||
await db.commit()
|
||||
return {"success": True, "event_completed": False}
|
||||
|
||||
except Exception as e:
|
||||
await db.execute("ROLLBACK")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
async def get_player_event_contributions(self, player_id: int, event_id: int) -> int:
|
||||
"""Get player's contributions to a specific event"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT contributions FROM npc_event_contributions
|
||||
WHERE event_id = ? AND player_id = ?
|
||||
""", (event_id, player_id))
|
||||
|
||||
row = await cursor.fetchone()
|
||||
return row[0] if row else 0
|
||||
|
||||
async def get_event_leaderboard(self, event_id: int) -> List[Dict]:
|
||||
"""Get leaderboard for an event"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT p.nickname, c.contributions, c.last_contribution_at
|
||||
FROM npc_event_contributions c
|
||||
JOIN players p ON c.player_id = p.id
|
||||
WHERE c.event_id = ?
|
||||
ORDER BY c.contributions DESC
|
||||
""", (event_id,))
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def expire_npc_events(self) -> int:
|
||||
"""Mark expired events as expired and return count"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
UPDATE npc_events
|
||||
SET status = 'expired'
|
||||
WHERE status = 'active' AND expires_at <= datetime('now')
|
||||
""")
|
||||
|
||||
await db.commit()
|
||||
return cursor.rowcount
|
||||
|
||||
async def distribute_event_rewards(self, event_id: int) -> Dict:
|
||||
"""Distribute rewards to all participants of a completed event"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
try:
|
||||
await db.execute("BEGIN TRANSACTION")
|
||||
|
||||
# Get event details
|
||||
cursor = await db.execute("""
|
||||
SELECT reward_experience, reward_money, reward_items
|
||||
FROM npc_events WHERE id = ? AND status = 'completed'
|
||||
""", (event_id,))
|
||||
|
||||
event_row = await cursor.fetchone()
|
||||
if not event_row:
|
||||
await db.execute("ROLLBACK")
|
||||
return {"success": False, "error": "Event not found or not completed"}
|
||||
|
||||
reward_exp, reward_money, reward_items = event_row
|
||||
|
||||
# Get all participants
|
||||
cursor = await db.execute("""
|
||||
SELECT player_id, contributions
|
||||
FROM npc_event_contributions
|
||||
WHERE event_id = ?
|
||||
""", (event_id,))
|
||||
|
||||
participants = await cursor.fetchall()
|
||||
|
||||
# Distribute rewards
|
||||
for player_id, contributions in participants:
|
||||
# Scale rewards based on contribution (minimum 50% of full reward)
|
||||
contribution_multiplier = max(0.5, min(1.0, contributions / 10))
|
||||
|
||||
final_exp = int(reward_exp * contribution_multiplier)
|
||||
final_money = int(reward_money * contribution_multiplier)
|
||||
|
||||
# Update player rewards
|
||||
await db.execute("""
|
||||
UPDATE players
|
||||
SET experience = experience + ?, money = money + ?
|
||||
WHERE id = ?
|
||||
""", (final_exp, final_money, player_id))
|
||||
|
||||
await db.commit()
|
||||
return {"success": True, "participants_rewarded": len(participants)}
|
||||
|
||||
except Exception as e:
|
||||
await db.execute("ROLLBACK")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
# Pet Healing System Methods
|
||||
async def get_fainted_pets(self, player_id: int) -> List[Dict]:
|
||||
"""Get all fainted pets for a player"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT p.*, ps.name as species_name, ps.emoji
|
||||
FROM pets p
|
||||
JOIN pet_species ps ON p.species_id = ps.id
|
||||
WHERE p.player_id = ? AND p.fainted_at IS NOT NULL
|
||||
ORDER BY p.fainted_at DESC
|
||||
""", (player_id,))
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def revive_pet(self, pet_id: int, new_hp: int) -> bool:
|
||||
"""Revive a fainted pet and restore HP"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
await db.execute("""
|
||||
UPDATE pets
|
||||
SET hp = ?, fainted_at = NULL
|
||||
WHERE id = ?
|
||||
""", (new_hp, pet_id))
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error reviving pet: {e}")
|
||||
return False
|
||||
|
||||
async def faint_pet(self, pet_id: int) -> bool:
|
||||
"""Mark a pet as fainted"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
await db.execute("""
|
||||
UPDATE pets
|
||||
SET hp = 0, fainted_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
""", (pet_id,))
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error fainting pet: {e}")
|
||||
return False
|
||||
|
||||
async def get_pets_for_auto_recovery(self) -> List[Dict]:
|
||||
"""Get pets that are eligible for auto-recovery (fainted for 30+ minutes)"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT p.*, ps.name as species_name
|
||||
FROM pets p
|
||||
JOIN pet_species ps ON p.species_id = ps.id
|
||||
WHERE p.fainted_at IS NOT NULL
|
||||
AND p.fainted_at <= datetime('now', '-30 minutes')
|
||||
""")
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def auto_recover_pet(self, pet_id: int) -> bool:
|
||||
"""Auto-recover a pet to 1 HP after 30 minutes"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
await db.execute("""
|
||||
UPDATE pets
|
||||
SET hp = 1, fainted_at = NULL
|
||||
WHERE id = ?
|
||||
""", (pet_id,))
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error auto-recovering pet: {e}")
|
||||
return False
|
||||
|
||||
async def get_last_heal_time(self, player_id: int) -> Optional[datetime]:
|
||||
"""Get the last time a player used the heal command"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT last_heal_time FROM players WHERE id = ?
|
||||
""", (player_id,))
|
||||
|
||||
row = await cursor.fetchone()
|
||||
if row and row[0]:
|
||||
return datetime.fromisoformat(row[0])
|
||||
return None
|
||||
|
||||
async def update_last_heal_time(self, player_id: int) -> bool:
|
||||
"""Update the last heal time for a player"""
|
||||
try:
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
await db.execute("""
|
||||
UPDATE players
|
||||
SET last_heal_time = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
""", (player_id,))
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error updating last heal time: {e}")
|
||||
return False
|
||||
|
||||
async def get_active_pets(self, player_id: int) -> List[Dict]:
|
||||
"""Get all active pets for a player (excluding fainted pets)"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT p.*, ps.name as species_name, ps.emoji
|
||||
FROM pets p
|
||||
JOIN pet_species ps ON p.species_id = ps.id
|
||||
WHERE p.player_id = ? AND p.is_active = 1 AND p.fainted_at IS NULL
|
||||
ORDER BY p.team_order
|
||||
""", (player_id,))
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def get_player_pets(self, player_id: int, active_only: bool = False) -> List[Dict]:
|
||||
"""Get all pets for a player, optionally filtering to active only"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
|
||||
if active_only:
|
||||
cursor = await db.execute("""
|
||||
SELECT p.*, ps.name as species_name, ps.emoji
|
||||
FROM pets p
|
||||
JOIN pet_species ps ON p.species_id = ps.id
|
||||
WHERE p.player_id = ? AND p.is_active = 1 AND p.fainted_at IS NULL
|
||||
ORDER BY p.team_order
|
||||
""", (player_id,))
|
||||
else:
|
||||
cursor = await db.execute("""
|
||||
SELECT p.*, ps.name as species_name, ps.emoji
|
||||
FROM pets p
|
||||
JOIN pet_species ps ON p.species_id = ps.id
|
||||
WHERE p.player_id = ?
|
||||
ORDER BY p.is_active DESC, p.team_order, p.level DESC
|
||||
""", (player_id,))
|
||||
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
Loading…
Add table
Add a link
Reference in a new issue