710 lines
28 KiB
Python
710 lines
28 KiB
Python
"""
|
|
Shop system for DuckHunt Bot
|
|
Handles loading items, purchasing, and item effects including player-vs-player actions
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import time
|
|
import logging
|
|
from typing import Dict, Any, Optional
|
|
|
|
|
|
class ShopManager:
|
|
"""Manages the DuckHunt shop system"""
|
|
|
|
def __init__(self, shop_file: str = "shop.json", levels_manager=None):
|
|
self.shop_file = shop_file
|
|
self.levels = levels_manager
|
|
self.items = {}
|
|
self.logger = logging.getLogger('DuckHuntBot.Shop')
|
|
self.load_items()
|
|
|
|
def load_items(self):
|
|
"""Load shop items from JSON file"""
|
|
try:
|
|
if os.path.exists(self.shop_file):
|
|
with open(self.shop_file, 'r', encoding='utf-8') as f:
|
|
shop_data = json.load(f)
|
|
# Convert string keys to integers for easier handling
|
|
self.items = {int(k): v for k, v in shop_data.get('items', {}).items()}
|
|
self.logger.info(f"Loaded {len(self.items)} shop items from {self.shop_file}")
|
|
else:
|
|
# Fallback items if file doesn't exist
|
|
self.items = self._get_default_items()
|
|
self.logger.warning(f"{self.shop_file} not found, using default items")
|
|
except Exception as e:
|
|
self.logger.error(f"Error loading shop items: {e}, using defaults")
|
|
self.items = self._get_default_items()
|
|
|
|
def _get_default_items(self) -> Dict[int, Dict[str, Any]]:
|
|
"""Default fallback shop items"""
|
|
return {
|
|
1: {"name": "Single Bullet", "price": 5, "description": "1 extra bullet", "type": "ammo", "amount": 1},
|
|
2: {"name": "Accuracy Boost", "price": 20, "description": "+10% accuracy", "type": "accuracy", "amount": 10},
|
|
3: {"name": "Lucky Charm", "price": 30, "description": "+5% duck spawn chance", "type": "luck", "amount": 5}
|
|
}
|
|
|
|
def get_items(self) -> Dict[int, Dict[str, Any]]:
|
|
"""Get all shop items"""
|
|
return self.items.copy()
|
|
|
|
def get_item(self, item_id: int) -> Optional[Dict[str, Any]]:
|
|
"""Get a specific shop item by ID"""
|
|
return self.items.get(item_id)
|
|
|
|
def is_valid_item(self, item_id: int) -> bool:
|
|
"""Check if item ID exists"""
|
|
return item_id in self.items
|
|
|
|
def can_afford(self, player_xp: int, item_id: int) -> bool:
|
|
"""Check if player can afford an item"""
|
|
item = self.get_item(item_id)
|
|
if not item:
|
|
return False
|
|
return player_xp >= item['price']
|
|
|
|
def purchase_item(self, player: Dict[str, Any], item_id: int, target_player: Optional[Dict[str, Any]] = None, store_in_inventory: bool = False) -> Dict[str, Any]:
|
|
"""
|
|
Purchase an item and either store in inventory or apply immediately
|
|
Returns a result dictionary with success status and details
|
|
"""
|
|
item = self.get_item(item_id)
|
|
if not item:
|
|
return {"success": False, "error": "invalid_id", "message": "Invalid item ID"}
|
|
|
|
# If storing in inventory and item requires a target, that's invalid
|
|
if store_in_inventory and item.get('target_required', False):
|
|
return {
|
|
"success": False,
|
|
"error": "invalid_storage",
|
|
"message": f"{item['name']} cannot be stored - it targets other players",
|
|
"item_name": item['name']
|
|
}
|
|
|
|
# Check if item requires a target (only when not storing)
|
|
if not store_in_inventory and item.get('target_required', False) and not target_player:
|
|
return {
|
|
"success": False,
|
|
"error": "target_required",
|
|
"message": f"{item['name']} requires a target player",
|
|
"item_name": item['name']
|
|
}
|
|
|
|
player_xp = player.get('xp', 0)
|
|
if player_xp < item['price']:
|
|
return {
|
|
"success": False,
|
|
"error": "insufficient_xp",
|
|
"message": f"Need {item['price']} XP, have {player_xp} XP",
|
|
"item_name": item['name'],
|
|
"price": item['price'],
|
|
"current_xp": player_xp
|
|
}
|
|
|
|
# Deduct XP
|
|
player['xp'] = player_xp - item['price']
|
|
|
|
if store_in_inventory:
|
|
# Add to inventory with bounds checking
|
|
inventory = player.get('inventory', {})
|
|
item_id_str = str(item_id)
|
|
current_count = inventory.get(item_id_str, 0)
|
|
|
|
# Load inventory limits from config
|
|
config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config.json')
|
|
max_per_item = 99 # Default limit per item type
|
|
max_total_items = 20 # Default total items limit
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
config = json.load(f)
|
|
max_total_items = config.get('gameplay', {}).get('max_inventory_items', 20)
|
|
max_per_item = config.get('gameplay', {}).get('max_per_item_type', 99)
|
|
except:
|
|
pass # Use defaults
|
|
|
|
# Check individual item limit
|
|
if current_count >= max_per_item:
|
|
return {
|
|
"success": False,
|
|
"error": "item_limit_reached",
|
|
"message": f"Cannot hold more than {max_per_item} {item['name']}s",
|
|
"item_name": item['name']
|
|
}
|
|
|
|
# Check total inventory size limit
|
|
total_items = sum(inventory.values())
|
|
if total_items >= max_total_items:
|
|
return {
|
|
"success": False,
|
|
"error": "inventory_full",
|
|
"message": f"Inventory full! (max {max_total_items} items)",
|
|
"item_name": item['name']
|
|
}
|
|
|
|
inventory[item_id_str] = current_count + 1
|
|
player['inventory'] = inventory
|
|
|
|
return {
|
|
"success": True,
|
|
"item_name": item['name'],
|
|
"price": item['price'],
|
|
"remaining_xp": player['xp'],
|
|
"stored_in_inventory": True,
|
|
"inventory_count": inventory[item_id_str]
|
|
}
|
|
else:
|
|
# Apply effect immediately
|
|
if item.get('target_required', False) and target_player:
|
|
effect_result = self._apply_item_effect(target_player, item)
|
|
|
|
return {
|
|
"success": True,
|
|
"item_name": item['name'],
|
|
"price": item['price'],
|
|
"remaining_xp": player['xp'],
|
|
"effect": effect_result,
|
|
"target_affected": True
|
|
}
|
|
else:
|
|
# Apply effect to purchaser
|
|
effect_result = self._apply_item_effect(player, item)
|
|
|
|
return {
|
|
"success": True,
|
|
"item_name": item['name'],
|
|
"price": item['price'],
|
|
"remaining_xp": player['xp'],
|
|
"effect": effect_result,
|
|
"target_affected": False
|
|
}
|
|
|
|
def _apply_item_effect(self, player: Dict[str, Any], item: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Apply the effect of an item to a player"""
|
|
item_type = item.get('type', 'unknown')
|
|
amount = item.get('amount', 0)
|
|
|
|
if item_type == 'ammo':
|
|
# Add bullets to current magazine
|
|
current_ammo = player.get('current_ammo', 0)
|
|
bullets_per_mag = player.get('bullets_per_magazine', 6)
|
|
new_ammo = min(current_ammo + amount, bullets_per_mag)
|
|
added_bullets = new_ammo - current_ammo
|
|
player['current_ammo'] = new_ammo
|
|
return {
|
|
"type": "ammo",
|
|
"added": added_bullets,
|
|
"new_total": new_ammo,
|
|
"max": bullets_per_mag
|
|
}
|
|
|
|
elif item_type == 'magazine':
|
|
# Add magazines (limit checking is done before this function is called)
|
|
current_magazines = player.get('magazines', 1)
|
|
|
|
if self.levels:
|
|
level_info = self.levels.get_player_level_info(player)
|
|
max_magazines = level_info.get('magazines', 3)
|
|
# Don't exceed maximum magazines for level
|
|
magazines_to_add = min(amount, max_magazines - current_magazines)
|
|
else:
|
|
# Fallback if levels not available
|
|
magazines_to_add = amount
|
|
|
|
new_magazines = current_magazines + magazines_to_add
|
|
player['magazines'] = new_magazines
|
|
return {
|
|
"type": "magazine",
|
|
"added": magazines_to_add,
|
|
"new_total": new_magazines
|
|
}
|
|
|
|
elif item_type == 'accuracy':
|
|
# Increase accuracy up to 100%
|
|
current_accuracy = player.get('accuracy', 75)
|
|
new_accuracy = min(current_accuracy + amount, 100)
|
|
player['accuracy'] = new_accuracy
|
|
return {
|
|
"type": "accuracy",
|
|
"added": new_accuracy - current_accuracy,
|
|
"new_total": new_accuracy
|
|
}
|
|
|
|
elif item_type == 'luck':
|
|
# Store luck bonus (would be used in duck spawning logic)
|
|
current_luck = player.get('luck_bonus', 0)
|
|
new_luck = min(max(current_luck + amount, -50), 100) # Bounded between -50 and +100
|
|
player['luck_bonus'] = new_luck
|
|
return {
|
|
"type": "luck",
|
|
"added": new_luck - current_luck,
|
|
"new_total": new_luck
|
|
}
|
|
|
|
elif item_type == 'jam_resistance':
|
|
# Reduce gun jamming chance (lower is better)
|
|
current_jam = player.get('jam_chance', 5) # Default 5% jam chance
|
|
new_jam = max(current_jam - amount, 0) # Can't go below 0%
|
|
player['jam_chance'] = new_jam
|
|
return {
|
|
"type": "jam_resistance",
|
|
"reduced": current_jam - new_jam,
|
|
"new_total": new_jam
|
|
}
|
|
|
|
elif item_type == 'max_ammo':
|
|
# Increase maximum ammo capacity
|
|
current_max = player.get('max_ammo', 6)
|
|
new_max = current_max + amount
|
|
player['max_ammo'] = new_max
|
|
return {
|
|
"type": "max_ammo",
|
|
"added": amount,
|
|
"new_total": new_max
|
|
}
|
|
|
|
elif item_type == 'chargers':
|
|
# Add reload chargers
|
|
current_chargers = player.get('chargers', 2)
|
|
new_chargers = current_chargers + amount
|
|
player['chargers'] = new_chargers
|
|
return {
|
|
"type": "chargers",
|
|
"added": amount,
|
|
"new_total": new_chargers
|
|
}
|
|
|
|
elif item_type == 'duck_attraction':
|
|
# Increase chance of ducks appearing when this player is online
|
|
current_attraction = player.get('duck_attraction', 0)
|
|
new_attraction = current_attraction + amount
|
|
player['duck_attraction'] = new_attraction
|
|
return {
|
|
"type": "duck_attraction",
|
|
"added": amount,
|
|
"new_total": new_attraction
|
|
}
|
|
|
|
elif item_type == 'critical_hit':
|
|
# Chance for critical hits (double XP)
|
|
current_crit = player.get('critical_chance', 0)
|
|
new_crit = min(current_crit + amount, 25) # Max 25% crit chance
|
|
player['critical_chance'] = new_crit
|
|
return {
|
|
"type": "critical_hit",
|
|
"added": new_crit - current_crit,
|
|
"new_total": new_crit
|
|
}
|
|
|
|
elif item_type == 'sabotage_jam':
|
|
# Increase target's gun jamming chance temporarily
|
|
current_jam = player.get('jam_chance', 5)
|
|
new_jam = min(current_jam + amount, 50) # Max 50% jam chance
|
|
player['jam_chance'] = new_jam
|
|
|
|
# Add temporary effect tracking
|
|
if 'temporary_effects' not in player:
|
|
player['temporary_effects'] = []
|
|
|
|
effect = {
|
|
'type': 'jam_increase',
|
|
'amount': amount,
|
|
'expires_at': time.time() + item.get('duration', 5) * 60 # duration in minutes
|
|
}
|
|
player['temporary_effects'].append(effect)
|
|
|
|
return {
|
|
"type": "sabotage_jam",
|
|
"added": new_jam - current_jam,
|
|
"new_total": new_jam,
|
|
"duration": item.get('duration', 5)
|
|
}
|
|
|
|
elif item_type == 'sabotage_accuracy':
|
|
# Reduce target's accuracy temporarily
|
|
current_acc = player.get('accuracy', 75)
|
|
new_acc = max(current_acc + amount, 10) # Min 10% accuracy (amount is negative)
|
|
player['accuracy'] = new_acc
|
|
|
|
# Add temporary effect tracking
|
|
if 'temporary_effects' not in player:
|
|
player['temporary_effects'] = []
|
|
|
|
effect = {
|
|
'type': 'accuracy_reduction',
|
|
'amount': amount,
|
|
'expires_at': time.time() + item.get('duration', 3) * 60
|
|
}
|
|
player['temporary_effects'].append(effect)
|
|
|
|
return {
|
|
"type": "sabotage_accuracy",
|
|
"reduced": current_acc - new_acc,
|
|
"new_total": new_acc,
|
|
"duration": item.get('duration', 3)
|
|
}
|
|
|
|
elif item_type == 'steal_ammo':
|
|
# Steal ammo from target player
|
|
current_ammo = player.get('ammo', 0)
|
|
stolen = min(amount, current_ammo)
|
|
player['ammo'] = max(current_ammo - stolen, 0)
|
|
|
|
return {
|
|
"type": "steal_ammo",
|
|
"stolen": stolen,
|
|
"remaining": player['ammo']
|
|
}
|
|
|
|
elif item_type == 'clean_gun':
|
|
# Clean gun to reduce jamming chance (positive amount reduces jam chance)
|
|
current_jam = player.get('jam_chance', 5) # Default 5% jam chance
|
|
new_jam = min(max(current_jam + amount, 0), 100) # Bounded between 0% and 100%
|
|
player['jam_chance'] = new_jam
|
|
|
|
return {
|
|
"type": "clean_gun",
|
|
"reduced": current_jam - new_jam,
|
|
"new_total": new_jam
|
|
}
|
|
|
|
elif item_type == 'attract_ducks':
|
|
# Add bread effect to increase duck spawn rate
|
|
if 'temporary_effects' not in player:
|
|
player['temporary_effects'] = []
|
|
|
|
duration = item.get('duration', 600) # 10 minutes default
|
|
spawn_multiplier = item.get('spawn_multiplier', 2.0) # 2x spawn rate default
|
|
|
|
effect = {
|
|
'type': 'attract_ducks',
|
|
'spawn_multiplier': spawn_multiplier,
|
|
'expires_at': time.time() + duration
|
|
}
|
|
player['temporary_effects'].append(effect)
|
|
|
|
return {
|
|
"type": "attract_ducks",
|
|
"spawn_multiplier": spawn_multiplier,
|
|
"duration": duration // 60 # return duration in minutes
|
|
}
|
|
|
|
elif item_type == 'clover_luck':
|
|
# Temporarily boost hit + befriend success rates
|
|
if 'temporary_effects' not in player or not isinstance(player.get('temporary_effects'), list):
|
|
player['temporary_effects'] = []
|
|
|
|
duration = item.get('duration', 600) # seconds
|
|
try:
|
|
duration = int(duration)
|
|
except (ValueError, TypeError):
|
|
duration = 600
|
|
duration = max(30, min(duration, 86400))
|
|
|
|
try:
|
|
min_hit = float(item.get('min_hit_chance', 0.95))
|
|
except (ValueError, TypeError):
|
|
min_hit = 0.95
|
|
try:
|
|
min_bef = float(item.get('min_befriend_chance', 0.95))
|
|
except (ValueError, TypeError):
|
|
min_bef = 0.95
|
|
min_hit = max(0.0, min(min_hit, 1.0))
|
|
min_bef = max(0.0, min(min_bef, 1.0))
|
|
|
|
now = time.time()
|
|
expires_at = now + duration
|
|
|
|
# If an existing clover effect is active, extend it instead of stacking.
|
|
for effect in player['temporary_effects']:
|
|
if isinstance(effect, dict) and effect.get('type') == 'clover_luck' and effect.get('expires_at', 0) > now:
|
|
effect['expires_at'] = max(effect.get('expires_at', now), now) + duration
|
|
effect['min_hit_chance'] = max(float(effect.get('min_hit_chance', 0.0) or 0.0), min_hit)
|
|
effect['min_befriend_chance'] = max(float(effect.get('min_befriend_chance', 0.0) or 0.0), min_bef)
|
|
return {
|
|
"type": "clover_luck",
|
|
"duration": duration // 60,
|
|
"min_hit_chance": min_hit,
|
|
"min_befriend_chance": min_bef,
|
|
"extended": True
|
|
}
|
|
|
|
effect = {
|
|
'type': 'clover_luck',
|
|
'min_hit_chance': min_hit,
|
|
'min_befriend_chance': min_bef,
|
|
'expires_at': expires_at
|
|
}
|
|
player['temporary_effects'].append(effect)
|
|
|
|
return {
|
|
"type": "clover_luck",
|
|
"duration": duration // 60,
|
|
"min_hit_chance": min_hit,
|
|
"min_befriend_chance": min_bef,
|
|
"extended": False
|
|
}
|
|
|
|
elif item_type == 'insurance':
|
|
# Add insurance protection against friendly fire
|
|
if 'temporary_effects' not in player:
|
|
player['temporary_effects'] = []
|
|
|
|
duration = item.get('duration', 86400) # 24 hours default
|
|
protection_type = item.get('protection', 'friendly_fire')
|
|
|
|
effect = {
|
|
'type': 'insurance',
|
|
'protection': protection_type,
|
|
'expires_at': time.time() + duration,
|
|
'name': 'Hunter\'s Insurance'
|
|
}
|
|
player['temporary_effects'].append(effect)
|
|
|
|
return {
|
|
"type": "insurance",
|
|
"protection": protection_type,
|
|
"duration": duration // 3600 # return duration in hours
|
|
}
|
|
|
|
elif item_type == 'buy_gun_back':
|
|
# Restore confiscated gun with original ammo
|
|
was_confiscated = player.get('gun_confiscated', False)
|
|
|
|
if was_confiscated:
|
|
player['gun_confiscated'] = False
|
|
# Restore original ammo and magazines from when gun was confiscated
|
|
restored_ammo = player.get('confiscated_ammo', 0)
|
|
restored_magazines = player.get('confiscated_magazines', 1)
|
|
player['current_ammo'] = restored_ammo
|
|
player['magazines'] = restored_magazines
|
|
# Clean up the stored values
|
|
player.pop('confiscated_ammo', None)
|
|
player.pop('confiscated_magazines', None)
|
|
|
|
return {
|
|
"type": "buy_gun_back",
|
|
"restored": True,
|
|
"ammo_restored": restored_ammo
|
|
}
|
|
else:
|
|
return {
|
|
"type": "buy_gun_back",
|
|
"restored": False,
|
|
"message": "Your gun is not confiscated"
|
|
}
|
|
|
|
|
|
|
|
elif item_type == 'dry_clothes':
|
|
# Remove wet clothes effect
|
|
|
|
# Remove any wet clothes effects
|
|
if 'temporary_effects' in player:
|
|
original_count = len(player['temporary_effects'])
|
|
player['temporary_effects'] = [
|
|
effect for effect in player['temporary_effects']
|
|
if effect.get('type') != 'wet_clothes'
|
|
]
|
|
new_count = len(player['temporary_effects'])
|
|
was_wet = original_count > new_count
|
|
else:
|
|
was_wet = False
|
|
|
|
return {
|
|
"type": "dry_clothes",
|
|
"was_wet": was_wet,
|
|
"message": "You changed into dry clothes!" if was_wet else "You weren't wet!"
|
|
}
|
|
|
|
else:
|
|
self.logger.warning(f"Unknown item type: {item_type}")
|
|
return {"type": "unknown", "message": f"Unknown effect type: {item_type}"}
|
|
|
|
def _apply_splash_water_effect(self, target_player: Dict[str, Any], item: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Apply splash water effect to target player"""
|
|
# Load config directly without import issues
|
|
config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'config.json')
|
|
try:
|
|
with open(config_path, 'r') as f:
|
|
config = json.load(f)
|
|
wet_duration = config.get('gameplay', {}).get('wet_clothes_duration', 300) # 5 minutes default
|
|
except:
|
|
wet_duration = 300 # Default 5 minutes
|
|
|
|
if 'temporary_effects' not in target_player:
|
|
target_player['temporary_effects'] = []
|
|
|
|
# Add wet clothes effect
|
|
wet_effect = {
|
|
'type': 'wet_clothes',
|
|
'expires_at': time.time() + wet_duration
|
|
}
|
|
target_player['temporary_effects'].append(wet_effect)
|
|
|
|
return {
|
|
"type": "splash_water",
|
|
"target_soaked": True,
|
|
"duration": wet_duration // 60 # return duration in minutes
|
|
}
|
|
|
|
def use_inventory_item(self, player: Dict[str, Any], item_id: int, target_player: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
"""
|
|
Use an item from player's inventory
|
|
Returns a result dictionary with success status and details
|
|
"""
|
|
item = self.get_item(item_id)
|
|
if not item:
|
|
return {"success": False, "error": "invalid_id", "message": "Invalid item ID"}
|
|
|
|
inventory = player.get('inventory', {})
|
|
item_id_str = str(item_id)
|
|
|
|
if item_id_str not in inventory or inventory[item_id_str] <= 0:
|
|
return {
|
|
"success": False,
|
|
"error": "not_in_inventory",
|
|
"message": f"You don't have any {item['name']} in your inventory",
|
|
"item_name": item['name']
|
|
}
|
|
|
|
# Special restrictions: Some items require targets, bread cannot have targets
|
|
if item['type'] == 'attract_ducks' and target_player:
|
|
return {
|
|
"success": False,
|
|
"error": "bread_no_target",
|
|
"message": "Bread affects everyone in the channel - you cannot target a specific player",
|
|
"item_name": item['name']
|
|
}
|
|
|
|
# Items that must have targets when used (but can be stored in inventory)
|
|
target_required_items = ['sabotage_jam', 'splash_water']
|
|
if item['type'] in target_required_items and not target_player:
|
|
return {
|
|
"success": False,
|
|
"error": "target_required",
|
|
"message": f"{item['name']} requires a target player to use",
|
|
"item_name": item['name']
|
|
}
|
|
|
|
# Special checks for ammo/magazine limits
|
|
if item['type'] == 'magazine' and self.levels:
|
|
affected_player = target_player if target_player else player
|
|
current_magazines = affected_player.get('magazines', 1)
|
|
level_info = self.levels.get_player_level_info(affected_player)
|
|
max_magazines = level_info.get('magazines', 3)
|
|
|
|
if current_magazines >= max_magazines:
|
|
return {
|
|
"success": False,
|
|
"error": "max_magazines_reached",
|
|
"message": f"Already at maximum magazines ({max_magazines}) for current level!",
|
|
"item_name": item['name']
|
|
}
|
|
elif item['type'] == 'ammo':
|
|
affected_player = target_player if target_player else player
|
|
current_ammo = affected_player.get('current_ammo', 0)
|
|
bullets_per_mag = affected_player.get('bullets_per_magazine', 6)
|
|
|
|
if current_ammo >= bullets_per_mag:
|
|
return {
|
|
"success": False,
|
|
"error": "magazine_full",
|
|
"message": f"Current magazine is already full ({bullets_per_mag}/{bullets_per_mag})!",
|
|
"item_name": item['name']
|
|
}
|
|
|
|
# Remove item from inventory
|
|
inventory[item_id_str] -= 1
|
|
if inventory[item_id_str] <= 0:
|
|
del inventory[item_id_str]
|
|
player['inventory'] = inventory
|
|
|
|
# Determine who gets the effect
|
|
if target_player:
|
|
# Special handling for harmful effects
|
|
if item['type'] == 'splash_water':
|
|
effect_result = self._apply_splash_water_effect(target_player, item)
|
|
target_affected = True
|
|
elif item['type'] == 'sabotage_jam':
|
|
effect_result = self._apply_item_effect(target_player, item)
|
|
target_affected = True
|
|
else:
|
|
# Beneficial items - give to target (gifting)
|
|
effect_result = self._apply_item_effect(target_player, item)
|
|
target_affected = True
|
|
# Mark as gift in the result
|
|
effect_result['is_gift'] = True
|
|
|
|
return {
|
|
"success": True,
|
|
"item_name": item['name'],
|
|
"effect": effect_result,
|
|
"target_affected": target_affected,
|
|
"remaining_in_inventory": inventory.get(item_id_str, 0)
|
|
}
|
|
else:
|
|
# Apply effect to user (no target specified)
|
|
effect_result = self._apply_item_effect(player, item)
|
|
|
|
return {
|
|
"success": True,
|
|
"item_name": item['name'],
|
|
"effect": effect_result,
|
|
"target_affected": False,
|
|
"remaining_in_inventory": inventory.get(item_id_str, 0)
|
|
}
|
|
|
|
def get_inventory_display(self, player: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Get formatted inventory display for a player
|
|
Returns dict with inventory info
|
|
"""
|
|
inventory = player.get('inventory', {})
|
|
if not inventory:
|
|
return {
|
|
"empty": True,
|
|
"message": "Your inventory is empty"
|
|
}
|
|
|
|
items = []
|
|
for item_id_str, quantity in inventory.items():
|
|
item_id = int(item_id_str)
|
|
item = self.get_item(item_id)
|
|
if item:
|
|
items.append({
|
|
"id": item_id,
|
|
"name": item['name'],
|
|
"quantity": quantity,
|
|
"description": item.get('description', 'No description')
|
|
})
|
|
|
|
return {
|
|
"empty": False,
|
|
"items": items,
|
|
"total_items": len(items)
|
|
}
|
|
|
|
def reload_items(self) -> int:
|
|
"""Reload items from file and return count"""
|
|
old_count = len(self.items)
|
|
self.load_items()
|
|
new_count = len(self.items)
|
|
self.logger.info(f"Shop reloaded: {old_count} -> {new_count} items")
|
|
return new_count
|
|
|
|
def get_shop_display(self, player, message_manager):
|
|
"""Get formatted shop display"""
|
|
items = []
|
|
for item_id, item in self.get_items().items():
|
|
item_text = message_manager.get('shop_item_format',
|
|
id=item_id,
|
|
name=item['name'],
|
|
price=item['price'])
|
|
items.append(item_text)
|
|
|
|
shop_text = message_manager.get('shop_display',
|
|
items=" | ".join(items),
|
|
xp=player.get('xp', 0))
|
|
|
|
return shop_text |