UI/UX: Improve visual clarity and consistency
- Align status bars with label padding (HP, Stamina, XP) - Add clear combat turn indicators (YOUR TURN / ENEMY TURN) - Display HP/Stamina bars in inventory menu and usage - Add consistent separators between sections - Improve feedback for item usage with visible stat changes Files modified: - bot/utils.py: Added label_width parameter to format_stat_bar() - bot/combat.py: Turn headers and separators - bot/inventory_handlers.py: HP/Stamina display - bot/action_handlers.py: Separator placement fix See docs/development/UI_UX_IMPROVEMENTS.md for details
This commit is contained in:
@@ -50,7 +50,8 @@ async def get_player_status_text(telegram_id: int) -> str:
|
||||
if equipped_items:
|
||||
status += f"⚔️ <b>Equipped:</b> {', '.join(equipped_items)}\n"
|
||||
|
||||
status += f"━━━━━━━━━━━━━━━━━━━━\n<i>{location.description}</i>"
|
||||
status += "━━━━━━━━━━━━━━━━━━━━\n"
|
||||
status += f"<i>{location.description}</i>"
|
||||
return status
|
||||
|
||||
|
||||
|
||||
@@ -128,7 +128,8 @@ async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
|
||||
actual_damage = int(actual_damage * 1.5)
|
||||
new_npc_hp = max(0, combat['npc_hp'] - actual_damage)
|
||||
|
||||
message = f"⚔️ You attack the {npc_def.name} for {actual_damage} damage!"
|
||||
message = "━━━ YOUR TURN ━━━\n"
|
||||
message += f"⚔️ You attack the {npc_def.name} for {actual_damage} damage!"
|
||||
if is_crit:
|
||||
message += " 💥 CRITICAL HIT!"
|
||||
|
||||
@@ -174,11 +175,13 @@ async def player_attack(player_id: int) -> Tuple[str, bool, bool]:
|
||||
'player_status_effects': json.dumps(player_effects)
|
||||
})
|
||||
|
||||
message += "\n" + format_stat_bar(f"{npc_def.emoji} {npc_def.name}", "", new_npc_hp, combat['npc_max_hp'])
|
||||
message += "\n━━━━━━━━━━━━━━━━━━━━\n"
|
||||
message += format_stat_bar(f"{npc_def.emoji} {npc_def.name}", "", new_npc_hp, combat['npc_max_hp'])
|
||||
|
||||
return (message, False, True)
|
||||
|
||||
|
||||
|
||||
async def npc_attack(player_id: int) -> Tuple[str, bool]:
|
||||
"""
|
||||
NPC attacks the player.
|
||||
@@ -214,7 +217,8 @@ async def npc_attack(player_id: int) -> Tuple[str, bool]:
|
||||
new_player_hp = max(0, player['hp'] - damage)
|
||||
await database.update_player(player_id, {'hp': new_player_hp})
|
||||
|
||||
message = f"💥 The {npc_def.name} attacks you for {damage} damage!"
|
||||
message = "━━━ ENEMY TURN ━━━\n"
|
||||
message += f"💥 The {npc_def.name} attacks you for {damage} damage!"
|
||||
|
||||
# Check for status effect infliction
|
||||
player_effects = json.loads(combat['player_status_effects'])
|
||||
@@ -251,8 +255,9 @@ async def npc_attack(player_id: int) -> Tuple[str, bool]:
|
||||
'npc_status_effects': json.dumps(npc_effects)
|
||||
})
|
||||
|
||||
message += "\n" + format_stat_bar("Your HP", "❤️", new_player_hp, player['max_hp'])
|
||||
message += "\n" + format_stat_bar(f"{npc_def.emoji} {npc_def.name}", "", combat['npc_hp'], combat['npc_max_hp'])
|
||||
message += "\n━━━━━━━━━━━━━━━━━━━━\n"
|
||||
message += format_stat_bar("Your HP", "❤️", new_player_hp, player['max_hp']) + "\n"
|
||||
message += format_stat_bar(f"{npc_def.emoji} {npc_def.name}", "", combat['npc_hp'], combat['npc_max_hp'])
|
||||
|
||||
return (message, False)
|
||||
|
||||
|
||||
@@ -12,21 +12,22 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
async def handle_inventory_menu(query, user_id: int, player: dict, data: list = None):
|
||||
"""Display player inventory with item management options."""
|
||||
from .utils import format_stat_bar
|
||||
await query.answer()
|
||||
inventory_items = await database.get_inventory(user_id)
|
||||
|
||||
# Calculate inventory summary
|
||||
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"{format_stat_bar('HP', '❤️', player['hp'], player['max_hp'])}\n"
|
||||
text += f"{format_stat_bar('Stamina', '⚡', player['stamina'], player['max_stamina'])}\n"
|
||||
text += f"📊 Weight: {current_weight}/{max_weight} kg\n"
|
||||
text += f"📦 Volume: {current_volume}/{max_volume} vol\n\n"
|
||||
text += f"📦 Volume: {current_volume}/{max_volume} vol\n"
|
||||
|
||||
if not inventory_items:
|
||||
text += "It's empty."
|
||||
text += "\n<i>Your inventory is empty.</i>"
|
||||
|
||||
# Keep current location image for context
|
||||
location = game_world.get_location(player['location_id'])
|
||||
location_image = location.image_path if location else None
|
||||
|
||||
@@ -92,6 +93,8 @@ async def handle_inventory_item(query, user_id: int, player: dict, data: list):
|
||||
|
||||
async def handle_inventory_use(query, user_id: int, player: dict, data: list):
|
||||
"""Use a consumable item from inventory."""
|
||||
from .utils import format_stat_bar
|
||||
|
||||
item_db_id = int(data[1])
|
||||
item = await database.get_inventory_item(item_db_id)
|
||||
|
||||
@@ -127,40 +130,42 @@ async def handle_inventory_use(query, user_id: int, player: dict, data: list):
|
||||
actual_gain = new_stamina - player['stamina']
|
||||
updates['stamina'] = new_stamina
|
||||
if actual_gain > 0:
|
||||
result_parts.append(f"⚡️ Stamina: +{actual_gain}")
|
||||
result_parts.append(f"⚡ Stamina: +{actual_gain}")
|
||||
else:
|
||||
result_parts.append(f"⚡️ Stamina: Already at maximum!")
|
||||
result_parts.append(f"⚡ Stamina: Already at maximum!")
|
||||
|
||||
if updates:
|
||||
await database.update_player(user_id, updates)
|
||||
|
||||
# Refresh player data to get updated stats
|
||||
player = await database.get_player(user_id)
|
||||
|
||||
# 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)
|
||||
|
||||
# Build status section with HP/Stamina bars
|
||||
text = "<b>🎒 Your Inventory:</b>\n"
|
||||
text += f"{format_stat_bar('HP', '❤️', player['hp'], player['max_hp'])}\n"
|
||||
text += f"{format_stat_bar('Stamina', '⚡', player['stamina'], player['max_stamina'])}\n"
|
||||
text += f"📊 Weight: {current_weight}/{max_weight} kg\n"
|
||||
text += f"📦 Volume: {current_volume}/{max_volume} vol\n\n"
|
||||
text += f"📦 Volume: {current_volume}/{max_volume} vol\n"
|
||||
text += "━━━━━━━━━━━━━━━━━━━━\n"
|
||||
|
||||
if not inventory_items:
|
||||
text += "It's empty."
|
||||
# Build result message
|
||||
emoji = item_def.get('emoji', '❔')
|
||||
text += f"<b>✨ Used {emoji} {item_def.get('name')}</b>\n"
|
||||
if result_parts:
|
||||
text += "\n".join(result_parts)
|
||||
else:
|
||||
text += f"{result_text}"
|
||||
text += "No effect."
|
||||
|
||||
location = game_world.get_location(player['location_id'])
|
||||
location_image = location.image_path if location else None
|
||||
|
||||
11
bot/utils.py
11
bot/utils.py
@@ -43,7 +43,7 @@ def create_progress_bar(current: int, maximum: int, length: int = 10, filled_cha
|
||||
return filled_char * filled_length + empty_char * empty_length
|
||||
|
||||
|
||||
def format_stat_bar(label: str, emoji: str, current: int, maximum: int, bar_length: int = 10) -> str:
|
||||
def format_stat_bar(label: str, emoji: str, current: int, maximum: int, bar_length: int = 10, label_width: int = 7) -> str:
|
||||
"""
|
||||
Format a stat (HP, Stamina, etc.) with visual progress bar.
|
||||
|
||||
@@ -53,20 +53,25 @@ def format_stat_bar(label: str, emoji: str, current: int, maximum: int, bar_leng
|
||||
current: Current value
|
||||
maximum: Maximum value
|
||||
bar_length: Length of the progress bar
|
||||
label_width: Width to pad label to for alignment (default 7)
|
||||
|
||||
Returns:
|
||||
Formatted string with bar and percentage
|
||||
|
||||
Examples:
|
||||
>>> format_stat_bar("HP", "❤️", 75, 100)
|
||||
"❤️ HP: ███████░░░ 75% (75/100)"
|
||||
"❤️ HP: ███████░░░ 75% (75/100)"
|
||||
>>> format_stat_bar("Stamina", "⚡", 50, 100)
|
||||
"⚡ Stamina: █████░░░░░ 50% (50/100)"
|
||||
"""
|
||||
bar = create_progress_bar(current, maximum, bar_length)
|
||||
percentage = int((current / maximum * 100)) if maximum > 0 else 0
|
||||
|
||||
return f"{emoji} {label}: {bar} {percentage}% ({current}/{maximum})"
|
||||
# Pad label for alignment
|
||||
padded_label = f"{label}:".ljust(label_width + 1)
|
||||
|
||||
return f"{emoji} {padded_label} {bar} {percentage}% ({current}/{maximum})"
|
||||
|
||||
|
||||
|
||||
def get_admin_ids():
|
||||
|
||||
Reference in New Issue
Block a user