- Implement visual HP/Stamina/XP bars using Unicode characters (██░) - Refactor handlers.py (1308 → 377 lines) into specialized modules: * action_handlers.py - World interaction and status display * inventory_handlers.py - Inventory management * combat_handlers.py - Combat actions * profile_handlers.py - Character stats with visual bars * corpse_handlers.py - Looting system * pickup_handlers.py - Item collection - Add utility functions: create_progress_bar(), format_stat_bar() - Organize all documentation into docs/ structure - Create comprehensive documentation index with navigation - Add UI examples showing before/after visual improvements
356 lines
13 KiB
Python
356 lines
13 KiB
Python
"""
|
|
Inventory-related action handlers.
|
|
"""
|
|
import logging
|
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
|
from . import database, keyboards, logic
|
|
from data.world_loader import game_world
|
|
from data.items import ITEMS
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def handle_inventory_menu(query, user_id: int, player: dict):
|
|
"""Show player inventory."""
|
|
await query.answer()
|
|
inventory_items = await database.get_inventory(user_id)
|
|
|
|
# Calculate inventory summary
|
|
current_weight, current_volume = logic.calculate_inventory_load(inventory_items)
|
|
max_weight, max_volume = logic.get_player_capacity(inventory_items, player)
|
|
|
|
text = "<b>🎒 Your Inventory:</b>\n"
|
|
text += f"📊 Weight: {current_weight}/{max_weight} kg\n"
|
|
text += f"📦 Volume: {current_volume}/{max_volume} vol\n\n"
|
|
|
|
if not inventory_items:
|
|
text += "It's empty."
|
|
|
|
# Keep current location image for context
|
|
location = game_world.get_location(player['location_id'])
|
|
location_image = location.image_path if location else None
|
|
|
|
from .handlers import send_or_edit_with_image
|
|
await send_or_edit_with_image(
|
|
query,
|
|
text=text,
|
|
reply_markup=keyboards.inventory_keyboard(inventory_items),
|
|
image_path=location_image
|
|
)
|
|
|
|
|
|
async def handle_inventory_item(query, user_id: int, player: dict, data: list):
|
|
"""Show details for a specific inventory item."""
|
|
await query.answer()
|
|
item_db_id = int(data[1])
|
|
item = await database.get_inventory_item(item_db_id)
|
|
item_def = ITEMS.get(item['item_id'], {})
|
|
emoji = item_def.get('emoji', '❔')
|
|
|
|
# Build item details text
|
|
text = f"{emoji} <b>{item_def.get('name', 'Unknown')}</b>\n"
|
|
|
|
description = item_def.get('description')
|
|
if description:
|
|
text += f"<i>{description}</i>\n\n"
|
|
else:
|
|
text += "\n"
|
|
|
|
text += f"<b>Weight:</b> {item_def.get('weight', 0)} kg | <b>Volume:</b> {item_def.get('volume', 0)} vol\n"
|
|
|
|
# Add weapon stats if applicable
|
|
if item_def.get('type') == 'weapon':
|
|
text += f"<b>Damage:</b> {item_def.get('damage_min', 0)}-{item_def.get('damage_max', 0)}\n"
|
|
|
|
# Add consumable effects if applicable
|
|
if item_def.get('type') == 'consumable':
|
|
effects = []
|
|
if item_def.get('hp_restore'):
|
|
effects.append(f"❤️ +{item_def.get('hp_restore')} HP")
|
|
if item_def.get('stamina_restore'):
|
|
effects.append(f"⚡ +{item_def.get('stamina_restore')} Stamina")
|
|
if effects:
|
|
text += f"<b>Effects:</b> {', '.join(effects)}\n"
|
|
|
|
# Add equipped status
|
|
if item.get('is_equipped'):
|
|
text += "\n✅ <b>Currently Equipped</b>"
|
|
|
|
location = game_world.get_location(player['location_id'])
|
|
location_image = location.image_path if location else None
|
|
|
|
from .handlers import send_or_edit_with_image
|
|
await send_or_edit_with_image(
|
|
query,
|
|
text=text,
|
|
reply_markup=keyboards.inventory_item_actions_keyboard(
|
|
item_db_id, item_def, item.get('is_equipped', False), item['quantity']
|
|
),
|
|
image_path=location_image
|
|
)
|
|
|
|
|
|
async def handle_inventory_use(query, user_id: int, player: dict, data: list):
|
|
"""Use a consumable item from inventory."""
|
|
item_db_id = int(data[1])
|
|
item = await database.get_inventory_item(item_db_id)
|
|
|
|
if not item:
|
|
await query.answer("Item not found.", show_alert=False)
|
|
return
|
|
|
|
item_def = ITEMS.get(item['item_id'], {})
|
|
|
|
if item_def.get('type') != 'consumable':
|
|
await query.answer("This item cannot be used.", show_alert=False)
|
|
return
|
|
|
|
await query.answer()
|
|
|
|
# Apply item effects
|
|
result_parts = []
|
|
updates = {}
|
|
|
|
if 'hp_restore' in item_def:
|
|
hp_gain = item_def['hp_restore']
|
|
new_hp = min(player['max_hp'], player['hp'] + hp_gain)
|
|
actual_gain = new_hp - player['hp']
|
|
updates['hp'] = new_hp
|
|
if actual_gain > 0:
|
|
result_parts.append(f"❤️ HP: +{actual_gain}")
|
|
else:
|
|
result_parts.append(f"❤️ HP: Already at maximum!")
|
|
|
|
if 'stamina_restore' in item_def:
|
|
stamina_gain = item_def['stamina_restore']
|
|
new_stamina = min(player['max_stamina'], player['stamina'] + stamina_gain)
|
|
actual_gain = new_stamina - player['stamina']
|
|
updates['stamina'] = new_stamina
|
|
if actual_gain > 0:
|
|
result_parts.append(f"⚡️ Stamina: +{actual_gain}")
|
|
else:
|
|
result_parts.append(f"⚡️ Stamina: Already at maximum!")
|
|
|
|
if updates:
|
|
await database.update_player(user_id, updates)
|
|
|
|
# Remove one item from inventory
|
|
if item['quantity'] > 1:
|
|
await database.update_inventory_item(item['id'], quantity=item['quantity'] - 1)
|
|
else:
|
|
await database.remove_item_from_inventory(item['id'])
|
|
|
|
# Build result message
|
|
emoji = item_def.get('emoji', '❔')
|
|
result_text = f"<b>Used {emoji} {item_def.get('name')}</b>\n\n"
|
|
if result_parts:
|
|
result_text += "\n".join(result_parts)
|
|
else:
|
|
result_text += "No effect."
|
|
|
|
# Show updated inventory
|
|
inventory_items = await database.get_inventory(user_id)
|
|
current_weight, current_volume = logic.calculate_inventory_load(inventory_items)
|
|
max_weight, max_volume = logic.get_player_capacity(inventory_items, player)
|
|
|
|
text = "<b>🎒 Your Inventory:</b>\n"
|
|
text += f"📊 Weight: {current_weight}/{max_weight} kg\n"
|
|
text += f"📦 Volume: {current_volume}/{max_volume} vol\n\n"
|
|
|
|
if not inventory_items:
|
|
text += "It's empty."
|
|
else:
|
|
text += f"{result_text}"
|
|
|
|
location = game_world.get_location(player['location_id'])
|
|
location_image = location.image_path if location else None
|
|
|
|
from .handlers import send_or_edit_with_image
|
|
await send_or_edit_with_image(
|
|
query,
|
|
text=text,
|
|
reply_markup=keyboards.inventory_keyboard(inventory_items),
|
|
image_path=location_image
|
|
)
|
|
|
|
|
|
async def handle_inventory_drop(query, user_id: int, player: dict, data: list):
|
|
"""Drop an item from inventory to the world."""
|
|
item_db_id = int(data[1])
|
|
drop_amount_str = data[2] if len(data) > 2 else None
|
|
|
|
item = await database.get_inventory_item(item_db_id)
|
|
if not item:
|
|
await query.answer("Item not found.", show_alert=False)
|
|
return
|
|
|
|
item_def = ITEMS.get(item['item_id'], {})
|
|
|
|
# Determine how much to drop
|
|
if drop_amount_str is None or drop_amount_str == "all":
|
|
await database.drop_item_to_world(item['item_id'], item['quantity'], player['location_id'])
|
|
await database.remove_item_from_inventory(item['id'], quantity=item['quantity'])
|
|
await query.answer(f"You dropped all {item['quantity']}x {item_def.get('name')}.", show_alert=False)
|
|
else:
|
|
drop_amount = int(drop_amount_str)
|
|
if drop_amount >= item['quantity']:
|
|
await database.drop_item_to_world(item['item_id'], item['quantity'], player['location_id'])
|
|
await database.remove_item_from_inventory(item['id'], quantity=item['quantity'])
|
|
await query.answer(f"You dropped all {item['quantity']}x {item_def.get('name')}.", show_alert=False)
|
|
else:
|
|
await database.drop_item_to_world(item['item_id'], drop_amount, player['location_id'])
|
|
await database.update_inventory_item(item['id'], quantity=item['quantity'] - drop_amount)
|
|
await query.answer(f"You dropped {drop_amount}x {item_def.get('name')}.", show_alert=False)
|
|
|
|
inventory_items = await database.get_inventory(user_id)
|
|
current_weight, current_volume = logic.calculate_inventory_load(inventory_items)
|
|
max_weight, max_volume = logic.get_player_capacity(inventory_items, player)
|
|
|
|
text = "<b>🎒 Your Inventory:</b>\n"
|
|
text += f"📊 Weight: {current_weight}/{max_weight} kg\n"
|
|
text += f"📦 Volume: {current_volume}/{max_volume} vol\n\n"
|
|
|
|
if not inventory_items:
|
|
text += "It's empty."
|
|
|
|
location = game_world.get_location(player['location_id'])
|
|
location_image = location.image_path if location else None
|
|
|
|
from .handlers import send_or_edit_with_image
|
|
await send_or_edit_with_image(
|
|
query,
|
|
text=text,
|
|
reply_markup=keyboards.inventory_keyboard(inventory_items),
|
|
image_path=location_image
|
|
)
|
|
|
|
|
|
async def handle_inventory_equip(query, user_id: int, player: dict, data: list):
|
|
"""Equip an item from inventory."""
|
|
item_db_id = int(data[1])
|
|
item = await database.get_inventory_item(item_db_id)
|
|
|
|
if not item:
|
|
await query.answer("Item not found.", show_alert=False)
|
|
return
|
|
|
|
item_def = ITEMS.get(item['item_id'], {})
|
|
item_slot = item_def.get('slot')
|
|
|
|
if not item_slot:
|
|
await query.answer("This item cannot be equipped.", show_alert=False)
|
|
return
|
|
|
|
# Unequip any item in the same slot
|
|
inventory_items = await database.get_inventory(user_id)
|
|
for inv_item in inventory_items:
|
|
if inv_item.get('is_equipped'):
|
|
inv_item_def = ITEMS.get(inv_item['item_id'], {})
|
|
if inv_item_def.get('slot') == item_slot:
|
|
await database.update_inventory_item(inv_item['id'], is_equipped=False)
|
|
|
|
# If equipping from a stack, split the stack
|
|
if item['quantity'] > 1:
|
|
await database.update_inventory_item(item_db_id, quantity=item['quantity'] - 1)
|
|
new_item_id = await database.add_equipped_item_to_inventory(user_id, item['item_id'])
|
|
await query.answer(f"Equipped {item_def.get('name')}!", show_alert=False)
|
|
item = await database.get_inventory_item(new_item_id)
|
|
item_db_id = new_item_id
|
|
else:
|
|
await database.update_inventory_item(item_db_id, is_equipped=True)
|
|
await query.answer(f"Equipped {item_def.get('name')}!", show_alert=False)
|
|
item = await database.get_inventory_item(item_db_id)
|
|
|
|
# Refresh the item view
|
|
emoji = item_def.get('emoji', '❔')
|
|
text = f"{emoji} <b>{item_def.get('name', 'Unknown')}</b>\n"
|
|
|
|
description = item_def.get('description')
|
|
if description:
|
|
text += f"<i>{description}</i>\n\n"
|
|
else:
|
|
text += "\n"
|
|
|
|
text += f"<b>Weight:</b> {item_def.get('weight', 0)} kg | <b>Volume:</b> {item_def.get('volume', 0)} vol\n"
|
|
|
|
if item_def.get('type') == 'weapon':
|
|
text += f"<b>Damage:</b> {item_def.get('damage_min', 0)}-{item_def.get('damage_max', 0)}\n"
|
|
|
|
text += "\n✅ <b>Currently Equipped</b>"
|
|
|
|
location = game_world.get_location(player['location_id'])
|
|
location_image = location.image_path if location else None
|
|
|
|
from .handlers import send_or_edit_with_image
|
|
await send_or_edit_with_image(
|
|
query,
|
|
text=text,
|
|
reply_markup=keyboards.inventory_item_actions_keyboard(
|
|
item_db_id, item_def, True, item['quantity']
|
|
),
|
|
image_path=location_image
|
|
)
|
|
|
|
|
|
async def handle_inventory_unequip(query, user_id: int, player: dict, data: list):
|
|
"""Unequip an item."""
|
|
item_db_id = int(data[1])
|
|
item = await database.get_inventory_item(item_db_id)
|
|
|
|
if not item:
|
|
await query.answer("Item not found.", show_alert=False)
|
|
return
|
|
|
|
item_def = ITEMS.get(item['item_id'], {})
|
|
|
|
# Check if there's an existing unequipped stack
|
|
inventory_items = await database.get_inventory(user_id)
|
|
existing_stack = None
|
|
for inv_item in inventory_items:
|
|
if (inv_item['item_id'] == item['item_id'] and
|
|
not inv_item.get('is_equipped') and
|
|
inv_item['id'] != item_db_id):
|
|
existing_stack = inv_item
|
|
break
|
|
|
|
if existing_stack:
|
|
# Merge into existing stack
|
|
await database.update_inventory_item(existing_stack['id'], quantity=existing_stack['quantity'] + 1)
|
|
await database.remove_item_from_inventory(item_db_id)
|
|
await query.answer(f"Unequipped {item_def.get('name')}.", show_alert=False)
|
|
item = await database.get_inventory_item(existing_stack['id'])
|
|
item_db_id = existing_stack['id']
|
|
else:
|
|
# Just unequip
|
|
await database.update_inventory_item(item_db_id, is_equipped=False)
|
|
await query.answer(f"Unequipped {item_def.get('name')}.", show_alert=False)
|
|
item = await database.get_inventory_item(item_db_id)
|
|
|
|
# Refresh the item view
|
|
emoji = item_def.get('emoji', '❔')
|
|
text = f"{emoji} <b>{item_def.get('name', 'Unknown')}</b>\n"
|
|
|
|
description = item_def.get('description')
|
|
if description:
|
|
text += f"<i>{description}</i>\n\n"
|
|
else:
|
|
text += "\n"
|
|
|
|
text += f"<b>Weight:</b> {item_def.get('weight', 0)} kg | <b>Volume:</b> {item_def.get('volume', 0)} vol\n"
|
|
|
|
if item_def.get('type') == 'weapon':
|
|
text += f"<b>Damage:</b> {item_def.get('damage_min', 0)}-{item_def.get('damage_max', 0)}\n"
|
|
|
|
location = game_world.get_location(player['location_id'])
|
|
location_image = location.image_path if location else None
|
|
|
|
from .handlers import send_or_edit_with_image
|
|
await send_or_edit_with_image(
|
|
query,
|
|
text=text,
|
|
reply_markup=keyboards.inventory_item_actions_keyboard(
|
|
item_db_id, item_def, False, item['quantity']
|
|
),
|
|
image_path=location_image
|
|
)
|