Add complete item collection system (v0.2.0)
🎒 Item Collection System: - 16 unique items across 5 categories (healing, battle, rare, location, special) - Rarity tiers: Common, Uncommon, Rare, Epic, Legendary with symbols - 30% chance to find items during exploration - Location-specific items (shells, mushrooms, crystals, runes) - Inventory management with \!inventory and \!use commands - Web interface integration showing player inventories - Consumable items: healing potions, battle boosters, lucky charms 🔧 Technical Updates: - Added items and player_inventory database tables - New Inventory module for item management - Updated game engine with item discovery system - Enhanced web interface with inventory display - Item initialization from config/items.json 🆕 New Commands: - \!inventory / \!inv / \!items - View collected items by category - \!use <item name> - Use consumable items on active pets 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e0edcb391a
commit
db144da24f
13 changed files with 952 additions and 22 deletions
169
src/database.py
169
src/database.py
|
|
@ -186,6 +186,33 @@ class Database:
|
|||
)
|
||||
""")
|
||||
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT UNIQUE NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
category TEXT NOT NULL,
|
||||
rarity TEXT NOT NULL,
|
||||
effect TEXT,
|
||||
effect_value INTEGER DEFAULT 0,
|
||||
consumable BOOLEAN DEFAULT TRUE,
|
||||
sell_value INTEGER DEFAULT 0
|
||||
)
|
||||
""")
|
||||
|
||||
await db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS player_inventory (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
player_id INTEGER NOT NULL,
|
||||
item_id INTEGER NOT NULL,
|
||||
quantity INTEGER DEFAULT 1,
|
||||
obtained_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (player_id) REFERENCES players (id),
|
||||
FOREIGN KEY (item_id) REFERENCES items (id),
|
||||
UNIQUE(player_id, item_id)
|
||||
)
|
||||
""")
|
||||
|
||||
await db.commit()
|
||||
|
||||
async def get_player(self, nickname: str) -> Optional[Dict]:
|
||||
|
|
@ -519,4 +546,144 @@ class Database:
|
|||
"pet2": dict(pet2),
|
||||
"pet1_now": "active" if not pet1["is_active"] else "storage",
|
||||
"pet2_now": "active" if not pet2["is_active"] else "storage"
|
||||
}
|
||||
}
|
||||
|
||||
# Item and Inventory Methods
|
||||
async def add_item_to_inventory(self, player_id: int, item_name: str, quantity: int = 1) -> bool:
|
||||
"""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,))
|
||||
item = await cursor.fetchone()
|
||||
if not item:
|
||||
return False
|
||||
|
||||
item_id = item[0]
|
||||
|
||||
# Check if player already has this item
|
||||
cursor = await db.execute(
|
||||
"SELECT quantity FROM player_inventory WHERE player_id = ? AND item_id = ?",
|
||||
(player_id, item_id)
|
||||
)
|
||||
existing = await cursor.fetchone()
|
||||
|
||||
if existing:
|
||||
# Update quantity
|
||||
new_quantity = existing[0] + quantity
|
||||
await db.execute(
|
||||
"UPDATE player_inventory SET quantity = ? WHERE player_id = ? AND item_id = ?",
|
||||
(new_quantity, player_id, item_id)
|
||||
)
|
||||
else:
|
||||
# Insert new item
|
||||
await db.execute(
|
||||
"INSERT INTO player_inventory (player_id, item_id, quantity) VALUES (?, ?, ?)",
|
||||
(player_id, item_id, quantity)
|
||||
)
|
||||
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
async def get_player_inventory(self, player_id: int) -> List[Dict]:
|
||||
"""Get all items in player's inventory"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT i.name, i.description, i.category, i.rarity, i.effect,
|
||||
i.effect_value, pi.quantity, pi.obtained_at
|
||||
FROM player_inventory pi
|
||||
JOIN items i ON pi.item_id = i.id
|
||||
WHERE pi.player_id = ?
|
||||
ORDER BY i.rarity DESC, i.name ASC
|
||||
""", (player_id,))
|
||||
rows = await cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
async def use_item(self, player_id: int, item_name: str) -> Dict:
|
||||
"""Use an item from inventory"""
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
# Get item details
|
||||
db.row_factory = aiosqlite.Row
|
||||
cursor = await db.execute("""
|
||||
SELECT i.*, pi.quantity
|
||||
FROM items i
|
||||
JOIN player_inventory pi ON i.id = pi.item_id
|
||||
WHERE pi.player_id = ? AND i.name = ?
|
||||
""", (player_id, item_name))
|
||||
item = await cursor.fetchone()
|
||||
|
||||
if not item:
|
||||
return {"success": False, "error": "Item not found in inventory"}
|
||||
|
||||
if item["quantity"] <= 0:
|
||||
return {"success": False, "error": "No items of this type available"}
|
||||
|
||||
# Remove one from inventory if consumable
|
||||
if item["consumable"]:
|
||||
if item["quantity"] == 1:
|
||||
await db.execute(
|
||||
"DELETE FROM player_inventory WHERE player_id = ? AND item_id = ?",
|
||||
(player_id, item["id"])
|
||||
)
|
||||
else:
|
||||
await db.execute(
|
||||
"UPDATE player_inventory SET quantity = quantity - 1 WHERE player_id = ? AND item_id = ?",
|
||||
(player_id, item["id"])
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"item": dict(item),
|
||||
"effect": item["effect"],
|
||||
"effect_value": item["effect_value"]
|
||||
}
|
||||
|
||||
async def initialize_items(self):
|
||||
"""Initialize items from config file"""
|
||||
import json
|
||||
|
||||
try:
|
||||
with open("config/items.json", "r") as f:
|
||||
items_data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
print("Items config file not found")
|
||||
return
|
||||
|
||||
async with aiosqlite.connect(self.db_path) as db:
|
||||
# Clear existing items
|
||||
await db.execute("DELETE FROM items")
|
||||
|
||||
# Add all items from config
|
||||
for category_items in items_data.values():
|
||||
if category_items and isinstance(category_items, list):
|
||||
for item in category_items:
|
||||
if "id" in item: # Valid item entry
|
||||
sell_value = 0
|
||||
if item.get("effect") == "sell_value":
|
||||
sell_value = item.get("effect_value", 0)
|
||||
|
||||
await db.execute("""
|
||||
INSERT OR REPLACE INTO items
|
||||
(id, name, description, category, rarity, effect, effect_value, sell_value)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
item["id"],
|
||||
item["name"],
|
||||
item["description"],
|
||||
item["category"],
|
||||
item["rarity"],
|
||||
item.get("effect", "none"),
|
||||
item.get("effect_value", 0),
|
||||
sell_value
|
||||
))
|
||||
|
||||
await db.commit()
|
||||
print("Items initialized from config")
|
||||
|
||||
async def update_pet_hp(self, pet_id: int, new_hp: int) -> bool:
|
||||
"""Update a pet's current HP"""
|
||||
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
|
||||
|
|
@ -24,6 +24,7 @@ class GameEngine:
|
|||
await self.load_moves()
|
||||
await self.load_type_chart()
|
||||
await self.load_achievements()
|
||||
await self.database.initialize_items()
|
||||
await self.init_weather_system()
|
||||
await self.battle_engine.load_battle_data()
|
||||
|
||||
|
|
@ -299,6 +300,11 @@ class GameEngine:
|
|||
if not location:
|
||||
return {"type": "error", "message": "You are not in a valid location!"}
|
||||
|
||||
# Check for item discovery first (30% chance)
|
||||
item_result = await self.check_item_discovery(player_id, location)
|
||||
if item_result:
|
||||
return item_result
|
||||
|
||||
# Get spawns for current location
|
||||
cursor = await db.execute("""
|
||||
SELECT ls.*, ps.name as species_name, ps.*
|
||||
|
|
@ -314,8 +320,8 @@ class GameEngine:
|
|||
# Apply weather modifiers to spawns
|
||||
modified_spawns = await self.get_weather_modified_spawns(location["id"], spawns)
|
||||
|
||||
# Random encounter chance (70% chance of finding something)
|
||||
if random.random() > 0.7:
|
||||
# Random encounter chance (50% chance of finding a pet after item check)
|
||||
if random.random() > 0.5:
|
||||
return {"type": "empty", "message": f"You explore {location['name']} but find nothing this time..."}
|
||||
|
||||
# Choose random spawn with weather-modified rates
|
||||
|
|
@ -347,6 +353,63 @@ class GameEngine:
|
|||
}
|
||||
}
|
||||
|
||||
async def check_item_discovery(self, player_id: int, location: Dict) -> Optional[Dict]:
|
||||
"""Check if player finds an item during exploration"""
|
||||
import json
|
||||
|
||||
# Load items config
|
||||
try:
|
||||
with open("config/items.json", "r") as f:
|
||||
items_data = json.load(f)
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
# Get all possible items for this location
|
||||
available_items = []
|
||||
location_name = location["name"].lower().replace(" ", "_")
|
||||
|
||||
for category_items in items_data.values():
|
||||
if isinstance(category_items, list):
|
||||
for item in category_items:
|
||||
if "locations" in item:
|
||||
item_locations = item["locations"]
|
||||
if "all" in item_locations or location_name in item_locations:
|
||||
available_items.append(item)
|
||||
|
||||
if not available_items:
|
||||
return None
|
||||
|
||||
# Calculate total spawn rates for this location
|
||||
total_rate = sum(item.get("spawn_rate", 0.1) for item in available_items)
|
||||
|
||||
# 30% base chance of finding an item
|
||||
if random.random() > 0.3:
|
||||
return None
|
||||
|
||||
# Choose item based on spawn rates
|
||||
chosen_item = random.choices(
|
||||
available_items,
|
||||
weights=[item.get("spawn_rate", 0.1) for item in available_items]
|
||||
)[0]
|
||||
|
||||
# Add item to player's inventory
|
||||
success = await self.database.add_item_to_inventory(player_id, chosen_item["name"])
|
||||
|
||||
if success:
|
||||
# Get rarity info for display
|
||||
rarity_info = items_data.get("rarity_info", {}).get(chosen_item["rarity"], {})
|
||||
symbol = rarity_info.get("symbol", "○")
|
||||
|
||||
return {
|
||||
"type": "item_found",
|
||||
"location": location["name"],
|
||||
"item": chosen_item,
|
||||
"symbol": symbol,
|
||||
"message": f"🎒 You found a {symbol} {chosen_item['name']} ({chosen_item['rarity']})! {chosen_item['description']}"
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
async def attempt_catch_current_location(self, player_id: int, target_pet: Dict) -> str:
|
||||
"""Attempt to catch a pet during exploration"""
|
||||
catch_rate = 0.5 + (0.3 / target_pet.get("rarity", 1))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue