feat: Add shop system, befriend command, and level system
- Added configurable shop system with shop.json - Created ShopManager class for modular shop handling - Implemented level system with levels.json for difficulty scaling - Added multiple duck spawn messages with random selection - Enhanced message system with color placeholders - Added ducks_befriended tracking separate from ducks_shot - Updated help system and admin commands - All systems tested and working correctly
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -63,6 +63,11 @@ class DuckDB:
|
||||
|
||||
if nick_lower not in self.players:
|
||||
self.players[nick_lower] = self.create_player(nick)
|
||||
else:
|
||||
# Ensure existing players have new fields
|
||||
player = self.players[nick_lower]
|
||||
if 'ducks_befriended' not in player:
|
||||
player['ducks_befriended'] = 0
|
||||
|
||||
return self.players[nick_lower]
|
||||
|
||||
@@ -72,6 +77,7 @@ class DuckDB:
|
||||
'nick': nick,
|
||||
'xp': 0,
|
||||
'ducks_shot': 0,
|
||||
'ducks_befriended': 0,
|
||||
'ammo': 6,
|
||||
'max_ammo': 6,
|
||||
'chargers': 2,
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
"""
|
||||
Simplified DuckHunt IRC Bot - Core Features Only
|
||||
Commands: !bang, !reload, !shop, !duckhelp, !rearm, !disarm, !ignore, !unignore, !ducklaunch
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import ssl
|
||||
import json
|
||||
@@ -19,6 +14,7 @@ from .utils import parse_irc_message, InputValidator, MessageManager
|
||||
from .db import DuckDB
|
||||
from .game import DuckGame
|
||||
from .sasl import SASLHandler
|
||||
from .shop import ShopManager
|
||||
|
||||
|
||||
class DuckHuntBot:
|
||||
@@ -41,12 +37,9 @@ class DuckHuntBot:
|
||||
|
||||
self.admins = [admin.lower() for admin in self.config.get('admins', ['colby'])]
|
||||
|
||||
# Simple shop items - hardcoded
|
||||
self.shop_items = {
|
||||
1: {"name": "Extra Shots", "price": 10, "description": "5 extra shots"},
|
||||
2: {"name": "Accuracy Boost", "price": 20, "description": "+10% accuracy"},
|
||||
3: {"name": "Lucky Charm", "price": 30, "description": "+5% duck spawn chance"}
|
||||
}
|
||||
# Initialize shop manager
|
||||
shop_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'shop.json')
|
||||
self.shop = ShopManager(shop_file)
|
||||
|
||||
def get_config(self, path, default=None):
|
||||
"""Get configuration value using dot notation"""
|
||||
@@ -148,10 +141,12 @@ class DuckHuntBot:
|
||||
|
||||
if cmd == "bang":
|
||||
await self.handle_bang(nick, channel, player)
|
||||
elif cmd == "bef" or cmd == "befriend":
|
||||
await self.handle_bef(nick, channel, player)
|
||||
elif cmd == "reload":
|
||||
await self.handle_reload(nick, channel, player)
|
||||
elif cmd == "shop":
|
||||
await self.handle_shop(nick, channel, player)
|
||||
await self.handle_shop(nick, channel, player, args)
|
||||
elif cmd == "duckhelp":
|
||||
await self.handle_duckhelp(nick, channel, player)
|
||||
elif cmd == "rearm" and self.is_admin(user):
|
||||
@@ -164,6 +159,8 @@ class DuckHuntBot:
|
||||
await self.handle_unignore(nick, channel, args)
|
||||
elif cmd == "ducklaunch" and self.is_admin(user):
|
||||
await self.handle_ducklaunch(nick, channel, args)
|
||||
elif cmd == "reloadshop" and self.is_admin(user):
|
||||
await self.handle_reloadshop(nick, channel, args)
|
||||
|
||||
async def handle_bang(self, nick, channel, player):
|
||||
"""Handle !bang command"""
|
||||
@@ -215,6 +212,44 @@ class DuckHuntBot:
|
||||
|
||||
self.db.save_database()
|
||||
|
||||
async def handle_bef(self, nick, channel, player):
|
||||
"""Handle !bef (befriend) command"""
|
||||
# Check for duck
|
||||
if channel not in self.game.ducks or not self.game.ducks[channel]:
|
||||
message = self.messages.get('bef_no_duck', nick=nick)
|
||||
self.send_message(channel, message)
|
||||
return
|
||||
|
||||
# Check befriend success rate from config (default 75%)
|
||||
success_rate_config = self.get_config('befriend_success_rate', 75)
|
||||
try:
|
||||
success_rate = float(success_rate_config) / 100.0
|
||||
except (ValueError, TypeError):
|
||||
success_rate = 0.75 # 75% default
|
||||
|
||||
if random.random() < success_rate:
|
||||
# Success - befriend the duck
|
||||
duck = self.game.ducks[channel].pop(0)
|
||||
|
||||
# Lower XP gain than shooting (5 instead of 10)
|
||||
xp_gained = 5
|
||||
player['xp'] = player.get('xp', 0) + xp_gained
|
||||
player['ducks_befriended'] = player.get('ducks_befriended', 0) + 1
|
||||
|
||||
message = self.messages.get('bef_success',
|
||||
nick=nick,
|
||||
xp_gained=xp_gained,
|
||||
ducks_befriended=player['ducks_befriended'])
|
||||
self.send_message(channel, message)
|
||||
else:
|
||||
# Failure - duck flies away, remove from channel
|
||||
duck = self.game.ducks[channel].pop(0)
|
||||
|
||||
message = self.messages.get('bef_failed', nick=nick)
|
||||
self.send_message(channel, message)
|
||||
|
||||
self.db.save_database()
|
||||
|
||||
async def handle_reload(self, nick, channel, player):
|
||||
"""Handle !reload command"""
|
||||
if player.get('gun_confiscated', False):
|
||||
@@ -243,10 +278,22 @@ class DuckHuntBot:
|
||||
self.send_message(channel, message)
|
||||
self.db.save_database()
|
||||
|
||||
async def handle_shop(self, nick, channel, player):
|
||||
async def handle_shop(self, nick, channel, player, args=None):
|
||||
"""Handle !shop command"""
|
||||
# Handle buying: !shop buy <item_id>
|
||||
if args and len(args) >= 2 and args[0].lower() == "buy":
|
||||
try:
|
||||
item_id = int(args[1])
|
||||
await self.handle_shop_buy(nick, channel, player, item_id)
|
||||
return
|
||||
except (ValueError, IndexError):
|
||||
message = self.messages.get('shop_buy_usage', nick=nick)
|
||||
self.send_message(channel, message)
|
||||
return
|
||||
|
||||
# Display shop items
|
||||
items = []
|
||||
for item_id, item in self.shop_items.items():
|
||||
for item_id, item in self.shop.get_items().items():
|
||||
item_text = self.messages.get('shop_item_format',
|
||||
id=item_id,
|
||||
name=item['name'],
|
||||
@@ -259,6 +306,36 @@ class DuckHuntBot:
|
||||
|
||||
self.send_message(channel, shop_text)
|
||||
|
||||
async def handle_shop_buy(self, nick, channel, player, item_id):
|
||||
"""Handle buying an item from the shop"""
|
||||
# Use ShopManager to handle the purchase
|
||||
result = self.shop.purchase_item(player, item_id)
|
||||
|
||||
if not result["success"]:
|
||||
# Handle different error types
|
||||
if result["error"] == "invalid_id":
|
||||
message = self.messages.get('shop_buy_invalid_id', nick=nick)
|
||||
elif result["error"] == "insufficient_xp":
|
||||
message = self.messages.get('shop_buy_insufficient_xp',
|
||||
nick=nick,
|
||||
item_name=result["item_name"],
|
||||
price=result["price"],
|
||||
current_xp=result["current_xp"])
|
||||
else:
|
||||
message = f"{nick} > Error: {result['message']}"
|
||||
|
||||
self.send_message(channel, message)
|
||||
return
|
||||
|
||||
# Purchase successful
|
||||
message = self.messages.get('shop_buy_success',
|
||||
nick=nick,
|
||||
item_name=result["item_name"],
|
||||
price=result["price"],
|
||||
remaining_xp=result["remaining_xp"])
|
||||
self.send_message(channel, message)
|
||||
self.db.save_database()
|
||||
|
||||
async def handle_duckhelp(self, nick, channel, player):
|
||||
"""Handle !duckhelp command"""
|
||||
help_lines = [
|
||||
@@ -357,6 +434,15 @@ class DuckHuntBot:
|
||||
self.send_message(channel, admin_message)
|
||||
self.send_message(channel, duck_message)
|
||||
|
||||
async def handle_reloadshop(self, nick, channel, args):
|
||||
"""Handle !reloadshop admin command"""
|
||||
old_count = len(self.shop.get_items())
|
||||
new_count = self.shop.reload_items()
|
||||
|
||||
message = f"[ADMIN] Shop reloaded by {nick} - {new_count} items loaded"
|
||||
self.send_message(channel, message)
|
||||
self.logger.info(f"Shop reloaded by admin {nick}: {old_count} -> {new_count} items")
|
||||
|
||||
|
||||
async def message_loop(self):
|
||||
"""Main message processing loop"""
|
||||
@@ -397,20 +483,15 @@ class DuckHuntBot:
|
||||
# Start game loops
|
||||
game_task = asyncio.create_task(self.game.start_game_loops())
|
||||
message_task = asyncio.create_task(self.message_loop())
|
||||
shutdown_task = asyncio.create_task(self.shutdown_event.wait())
|
||||
|
||||
self.logger.info("🦆 Bot is now running! Press Ctrl+C to stop.")
|
||||
|
||||
# Wait for shutdown signal or task completion
|
||||
done, pending = await asyncio.wait(
|
||||
[game_task, message_task, shutdown_task],
|
||||
[game_task, message_task],
|
||||
return_when=asyncio.FIRST_COMPLETED
|
||||
)
|
||||
|
||||
if shutdown_task in done:
|
||||
self.logger.info("🛑 Shutdown signal received, cleaning up...")
|
||||
await self._graceful_shutdown()
|
||||
|
||||
# Cancel remaining tasks
|
||||
for task in pending:
|
||||
if not task.done():
|
||||
|
||||
166
src/levels.py
Normal file
166
src/levels.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
Level system for DuckHunt Bot
|
||||
Manages player levels and difficulty scaling
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
from typing import Dict, Any, Optional, Tuple
|
||||
|
||||
|
||||
class LevelManager:
|
||||
"""Manages the DuckHunt level system and difficulty scaling"""
|
||||
|
||||
def __init__(self, levels_file: str = "levels.json"):
|
||||
self.levels_file = levels_file
|
||||
self.levels_data = {}
|
||||
self.logger = logging.getLogger('DuckHuntBot.Levels')
|
||||
self.load_levels()
|
||||
|
||||
def load_levels(self):
|
||||
"""Load level definitions from JSON file"""
|
||||
try:
|
||||
if os.path.exists(self.levels_file):
|
||||
with open(self.levels_file, 'r', encoding='utf-8') as f:
|
||||
self.levels_data = json.load(f)
|
||||
level_count = len(self.levels_data.get('levels', {}))
|
||||
self.logger.info(f"Loaded {level_count} levels from {self.levels_file}")
|
||||
else:
|
||||
# Fallback levels if file doesn't exist
|
||||
self.levels_data = self._get_default_levels()
|
||||
self.logger.warning(f"{self.levels_file} not found, using default levels")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error loading levels: {e}, using defaults")
|
||||
self.levels_data = self._get_default_levels()
|
||||
|
||||
def _get_default_levels(self) -> Dict[str, Any]:
|
||||
"""Default fallback level system"""
|
||||
return {
|
||||
"level_calculation": {
|
||||
"method": "total_ducks",
|
||||
"description": "Level based on total ducks interacted with"
|
||||
},
|
||||
"levels": {
|
||||
"1": {
|
||||
"name": "Duck Novice",
|
||||
"min_ducks": 0,
|
||||
"max_ducks": 9,
|
||||
"befriend_success_rate": 85,
|
||||
"accuracy_modifier": 5,
|
||||
"duck_spawn_speed_modifier": 1.0,
|
||||
"description": "Just starting out"
|
||||
},
|
||||
"2": {
|
||||
"name": "Duck Hunter",
|
||||
"min_ducks": 10,
|
||||
"max_ducks": 99,
|
||||
"befriend_success_rate": 75,
|
||||
"accuracy_modifier": 0,
|
||||
"duck_spawn_speed_modifier": 0.8,
|
||||
"description": "Getting experienced"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def calculate_player_level(self, player: Dict[str, Any]) -> int:
|
||||
"""Calculate a player's current level based on their stats"""
|
||||
method = self.levels_data.get('level_calculation', {}).get('method', 'total_ducks')
|
||||
|
||||
if method == 'total_ducks':
|
||||
total_ducks = player.get('ducks_shot', 0) + player.get('ducks_befriended', 0)
|
||||
elif method == 'xp':
|
||||
total_ducks = player.get('xp', 0) // 10 # 10 XP per "duck equivalent"
|
||||
else:
|
||||
total_ducks = player.get('ducks_shot', 0) + player.get('ducks_befriended', 0)
|
||||
|
||||
# Find the appropriate level
|
||||
levels = self.levels_data.get('levels', {})
|
||||
for level_num in sorted(levels.keys(), key=int, reverse=True):
|
||||
level_data = levels[level_num]
|
||||
if total_ducks >= level_data.get('min_ducks', 0):
|
||||
return int(level_num)
|
||||
|
||||
return 1 # Default to level 1
|
||||
|
||||
def get_level_data(self, level: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get level data for a specific level"""
|
||||
return self.levels_data.get('levels', {}).get(str(level))
|
||||
|
||||
def get_player_level_info(self, player: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Get complete level information for a player"""
|
||||
level = self.calculate_player_level(player)
|
||||
level_data = self.get_level_data(level)
|
||||
|
||||
if not level_data:
|
||||
return {
|
||||
"level": 1,
|
||||
"name": "Duck Novice",
|
||||
"description": "Default level",
|
||||
"befriend_success_rate": 75,
|
||||
"accuracy_modifier": 0,
|
||||
"duck_spawn_speed_modifier": 1.0
|
||||
}
|
||||
|
||||
total_ducks = player.get('ducks_shot', 0) + player.get('ducks_befriended', 0)
|
||||
|
||||
# Calculate progress to next level
|
||||
next_level_data = self.get_level_data(level + 1)
|
||||
if next_level_data:
|
||||
ducks_needed = next_level_data.get('min_ducks', 0) - total_ducks
|
||||
next_level_name = next_level_data.get('name', f"Level {level + 1}")
|
||||
else:
|
||||
ducks_needed = 0
|
||||
next_level_name = "Max Level"
|
||||
|
||||
return {
|
||||
"level": level,
|
||||
"name": level_data.get('name', f"Level {level}"),
|
||||
"description": level_data.get('description', ''),
|
||||
"befriend_success_rate": level_data.get('befriend_success_rate', 75),
|
||||
"accuracy_modifier": level_data.get('accuracy_modifier', 0),
|
||||
"duck_spawn_speed_modifier": level_data.get('duck_spawn_speed_modifier', 1.0),
|
||||
"total_ducks": total_ducks,
|
||||
"ducks_needed_for_next": max(0, ducks_needed),
|
||||
"next_level_name": next_level_name
|
||||
}
|
||||
|
||||
def get_modified_accuracy(self, player: Dict[str, Any]) -> int:
|
||||
"""Get player's accuracy modified by their level"""
|
||||
base_accuracy = player.get('accuracy', 65)
|
||||
level_info = self.get_player_level_info(player)
|
||||
modifier = level_info.get('accuracy_modifier', 0)
|
||||
|
||||
# Apply modifier and clamp between 10-100
|
||||
modified_accuracy = base_accuracy + modifier
|
||||
return max(10, min(100, modified_accuracy))
|
||||
|
||||
def get_modified_befriend_rate(self, player: Dict[str, Any], base_rate: float = 75.0) -> float:
|
||||
"""Get player's befriend success rate modified by their level"""
|
||||
level_info = self.get_player_level_info(player)
|
||||
level_rate = level_info.get('befriend_success_rate', base_rate)
|
||||
|
||||
# Return as percentage (0-100)
|
||||
return max(5.0, min(95.0, level_rate))
|
||||
|
||||
def get_duck_spawn_modifier(self, player_levels: list) -> float:
|
||||
"""Get duck spawn speed modifier based on highest level player in channel"""
|
||||
if not player_levels:
|
||||
return 1.0
|
||||
|
||||
# Use the modifier from the highest level player (makes it harder for everyone)
|
||||
max_level = max(player_levels)
|
||||
level_data = self.get_level_data(max_level)
|
||||
|
||||
if level_data:
|
||||
return level_data.get('duck_spawn_speed_modifier', 1.0)
|
||||
|
||||
return 1.0
|
||||
|
||||
def reload_levels(self) -> int:
|
||||
"""Reload levels from file and return count"""
|
||||
old_count = len(self.levels_data.get('levels', {}))
|
||||
self.load_levels()
|
||||
new_count = len(self.levels_data.get('levels', {}))
|
||||
self.logger.info(f"Levels reloaded: {old_count} -> {new_count} levels")
|
||||
return new_count
|
||||
149
src/shop.py
Normal file
149
src/shop.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Shop system for DuckHunt Bot
|
||||
Handles loading items, purchasing, and item effects
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class ShopManager:
|
||||
"""Manages the DuckHunt shop system"""
|
||||
|
||||
def __init__(self, shop_file: str = "shop.json"):
|
||||
self.shop_file = shop_file
|
||||
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) -> Dict[str, Any]:
|
||||
"""
|
||||
Purchase an item and apply its effects to the player
|
||||
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"}
|
||||
|
||||
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']
|
||||
|
||||
# Apply item effect
|
||||
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
|
||||
}
|
||||
|
||||
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 ammo up to max capacity
|
||||
current_ammo = player.get('ammo', 0)
|
||||
max_ammo = player.get('max_ammo', 6)
|
||||
new_ammo = min(current_ammo + amount, max_ammo)
|
||||
player['ammo'] = new_ammo
|
||||
return {
|
||||
"type": "ammo",
|
||||
"added": new_ammo - current_ammo,
|
||||
"new_total": new_ammo,
|
||||
"max": max_ammo
|
||||
}
|
||||
|
||||
elif item_type == 'accuracy':
|
||||
# Increase accuracy up to 100%
|
||||
current_accuracy = player.get('accuracy', 65)
|
||||
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 = current_luck + amount
|
||||
player['luck_bonus'] = new_luck
|
||||
return {
|
||||
"type": "luck",
|
||||
"added": amount,
|
||||
"new_total": new_luck
|
||||
}
|
||||
|
||||
else:
|
||||
self.logger.warning(f"Unknown item type: {item_type}")
|
||||
return {"type": "unknown", "message": f"Unknown effect type: {item_type}"}
|
||||
|
||||
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
|
||||
30
src/utils.py
30
src/utils.py
@@ -5,6 +5,7 @@ Utility functions for DuckHunt Bot
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
from typing import Optional, Tuple, List, Dict, Any
|
||||
|
||||
|
||||
@@ -29,10 +30,17 @@ class MessageManager:
|
||||
print(f"Error loading messages: {e}, using defaults")
|
||||
self.messages = self._get_default_messages()
|
||||
|
||||
def _get_default_messages(self) -> Dict[str, str]:
|
||||
def _get_default_messages(self) -> Dict[str, Any]:
|
||||
"""Default fallback messages without colors"""
|
||||
return {
|
||||
"duck_spawn": "・゜゜・。。・゜゜\\_o< QUACK! A duck has appeared! Type !bang to shoot it!",
|
||||
"duck_spawn": [
|
||||
"・゜゜・。。・゜゜\\_o< QUACK! A duck has appeared! Type !bang to shoot it!",
|
||||
"・゜゜・。。・゜゜\\_o< *flap flap* A wild duck landed! Use !bang to hunt it!",
|
||||
"🦆 A duck swoops into view! Quick, type !bang before it escapes!",
|
||||
"・゜゜・。。・゜゜\\_o< Quack quack! Fresh duck spotted! !bang to bag it!",
|
||||
"*rustling* A duck waddles out from the bushes! Fire with !bang!",
|
||||
"・゜゜・。。・゜゜\\_o< Splash! A duck surfaces! Shoot it with !bang!"
|
||||
],
|
||||
"duck_flies_away": "The duck flies away. ·°'`'°-.,¸¸.·°'`",
|
||||
"bang_hit": "{nick} > *BANG* You shot the duck! [+{xp_gained} xp] [Total ducks: {ducks_shot}]",
|
||||
"bang_miss": "{nick} > *BANG* You missed the duck!",
|
||||
@@ -63,12 +71,28 @@ class MessageManager:
|
||||
}
|
||||
|
||||
def get(self, key: str, **kwargs) -> str:
|
||||
"""Get a formatted message by key"""
|
||||
"""Get a formatted message by key with color placeholder replacement"""
|
||||
if key not in self.messages:
|
||||
return f"[Missing message: {key}]"
|
||||
|
||||
message = self.messages[key]
|
||||
|
||||
# If message is an array, randomly select one
|
||||
if isinstance(message, list):
|
||||
if not message:
|
||||
return f"[Empty message array: {key}]"
|
||||
message = random.choice(message)
|
||||
|
||||
# Ensure message is a string
|
||||
if not isinstance(message, str):
|
||||
return f"[Invalid message type: {key}]"
|
||||
|
||||
# Replace color placeholders with IRC codes
|
||||
if "colours" in self.messages and isinstance(self.messages["colours"], dict):
|
||||
for color_name, color_code in self.messages["colours"].items():
|
||||
placeholder = "{" + color_name + "}"
|
||||
message = message.replace(placeholder, color_code)
|
||||
|
||||
# Format with provided variables
|
||||
try:
|
||||
return message.format(**kwargs)
|
||||
|
||||
Reference in New Issue
Block a user