Implement duck item drop system and remove all emojis
Duck Item Drop System: - Added configurable drop rates per duck type (15%/25%/50%) - Created weighted drop tables for different items - Normal ducks: basic items (bullets, magazines, gun brush, sand) - Fast ducks: useful items including bucket of water - Golden ducks: rare items (bread, insurance, gun buyback, dry clothes) - Items automatically added to player inventory - Added drop notification messages for each duck type - Integrated seamlessly with existing combat mechanics Emoji Removal: - Removed all emojis from source code files - Updated logging system to use clean text prefixes - Replaced trophy/medal emojis with #1/#2/#3 rankings - Updated README.md to remove all emojis - Professional clean appearance throughout codebase
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -36,7 +36,7 @@ class DuckHuntBot:
|
||||
|
||||
admins_list = self.get_config('admins', ['colby']) or ['colby']
|
||||
self.admins = [admin.lower() for admin in admins_list]
|
||||
self.logger.info(f"👑 Configured {len(self.admins)} admin(s): {', '.join(self.admins)}")
|
||||
self.logger.info(f"Configured {len(self.admins)} admin(s): {', '.join(self.admins)}")
|
||||
|
||||
# Initialize level manager first
|
||||
levels_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'levels.json')
|
||||
@@ -125,7 +125,7 @@ class DuckHuntBot:
|
||||
tasks = [t for t in asyncio.all_tasks(loop) if not t.done()]
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
self.logger.info(f"🔄 Cancelled {len(tasks)} running tasks")
|
||||
self.logger.info(f"Cancelled {len(tasks)} running tasks")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error cancelling tasks: {e}")
|
||||
|
||||
@@ -159,7 +159,7 @@ class DuckHuntBot:
|
||||
timeout=self.get_config('connection.timeout', 30) or 30.0 # Connection timeout from config
|
||||
)
|
||||
|
||||
self.logger.info(f"✅ Successfully connected to {server}:{port}")
|
||||
self.logger.info(f"Successfully connected to {server}:{port}")
|
||||
return
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
@@ -474,6 +474,28 @@ class DuckHuntBot:
|
||||
result = self.game.shoot_duck(nick, channel, player)
|
||||
message = self.messages.get(result['message_key'], **result['message_args'])
|
||||
self.send_message(channel, message)
|
||||
|
||||
# Check if an item was dropped
|
||||
if result.get('success') and result.get('dropped_item'):
|
||||
dropped_item = result['dropped_item']
|
||||
duck_type = dropped_item['duck_type']
|
||||
item_name = dropped_item['item_name']
|
||||
|
||||
# Send drop notification message
|
||||
drop_message_key = f'duck_drop_{duck_type}'
|
||||
drop_message = self.messages.get(drop_message_key,
|
||||
nick=nick,
|
||||
item_name=item_name
|
||||
)
|
||||
self.send_message(channel, drop_message)
|
||||
|
||||
# Send drop notification message
|
||||
drop_message_key = f'duck_drop_{duck_type}'
|
||||
drop_message = self.messages.get(drop_message_key,
|
||||
nick=nick,
|
||||
item_name=dropped_item['item_name']
|
||||
)
|
||||
self.send_message(channel, drop_message)
|
||||
|
||||
async def handle_bef(self, nick, channel, player):
|
||||
"""Handle !bef (befriend) command"""
|
||||
@@ -676,23 +698,23 @@ class DuckHuntBot:
|
||||
if top_xp:
|
||||
xp_rankings = []
|
||||
for i, (player_nick, xp) in enumerate(top_xp, 1):
|
||||
medal = "🥇" if i == 1 else "🥈" if i == 2 else "🥉"
|
||||
xp_rankings.append(f"{medal}{player_nick}:{xp}XP")
|
||||
xp_line = f"🏆 {bold}Top XP{reset} " + " | ".join(xp_rankings)
|
||||
medal = "#1" if i == 1 else "#2" if i == 2 else "#3"
|
||||
xp_rankings.append(f"{medal} {player_nick}:{xp}XP")
|
||||
xp_line = f"Top XP: {bold}{reset} " + " | ".join(xp_rankings)
|
||||
self.send_message(channel, xp_line)
|
||||
else:
|
||||
self.send_message(channel, "🏆 No XP data available yet!")
|
||||
self.send_message(channel, "No XP data available yet!")
|
||||
|
||||
# Format ducks shot leaderboard as single line
|
||||
if top_ducks:
|
||||
duck_rankings = []
|
||||
for i, (player_nick, ducks) in enumerate(top_ducks, 1):
|
||||
medal = "🥇" if i == 1 else "🥈" if i == 2 else "🥉"
|
||||
duck_rankings.append(f"{medal}{player_nick}:{ducks}")
|
||||
duck_line = f"🦆 {bold}Top Hunters{reset} " + " | ".join(duck_rankings)
|
||||
medal = "#1" if i == 1 else "#2" if i == 2 else "#3"
|
||||
duck_rankings.append(f"{medal} {player_nick}:{ducks}")
|
||||
duck_line = f"Top Hunters: {bold}{reset} " + " | ".join(duck_rankings)
|
||||
self.send_message(channel, duck_line)
|
||||
else:
|
||||
self.send_message(channel, "🦆 No duck hunting data available yet!")
|
||||
self.send_message(channel, "No duck hunting data available yet!")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in handle_topduck: {e}")
|
||||
@@ -1167,7 +1189,7 @@ class DuckHuntBot:
|
||||
game_task = asyncio.create_task(self.game.start_game_loops())
|
||||
message_task = asyncio.create_task(self.message_loop())
|
||||
|
||||
self.logger.info("🦆 Bot is now running! Press Ctrl+C to stop.")
|
||||
self.logger.info("Bot is now running! Press Ctrl+C to stop.")
|
||||
# Wait for shutdown signal or task completion with frequent checks
|
||||
while not self.shutdown_requested:
|
||||
done, _pending = await asyncio.wait(
|
||||
@@ -1181,12 +1203,12 @@ class DuckHuntBot:
|
||||
break
|
||||
break
|
||||
|
||||
self.logger.info("🔄 Shutdown initiated, cleaning up...")
|
||||
self.logger.info("Shutdown initiated, cleaning up...")
|
||||
|
||||
except asyncio.CancelledError:
|
||||
self.logger.info("🛑 Main loop cancelled")
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Bot error: {e}")
|
||||
self.logger.error(f"Bot error: {e}")
|
||||
finally:
|
||||
# Fast cleanup - cancel tasks immediately with short timeout
|
||||
tasks_to_cancel = [task for task in [game_task, message_task] if task and not task.done()]
|
||||
@@ -1201,19 +1223,19 @@ class DuckHuntBot:
|
||||
timeout=1.0
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
self.logger.warning("⚠️ Task cancellation timed out")
|
||||
self.logger.warning("Task cancellation timed out")
|
||||
|
||||
# Quick database save
|
||||
try:
|
||||
self.db.save_database()
|
||||
self.logger.info("💾 Database saved")
|
||||
self.logger.info("Database saved")
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Error saving database: {e}")
|
||||
self.logger.error(f"Error saving database: {e}")
|
||||
|
||||
# Fast connection close
|
||||
await self._close_connection()
|
||||
|
||||
self.logger.info("✅ Bot shutdown complete")
|
||||
self.logger.info("Bot shutdown complete")
|
||||
|
||||
async def _close_connection(self):
|
||||
"""Close IRC connection with comprehensive error handling"""
|
||||
@@ -1235,14 +1257,14 @@ class DuckHuntBot:
|
||||
self.writer.close()
|
||||
await asyncio.wait_for(self.writer.wait_closed(), timeout=2.0)
|
||||
except asyncio.TimeoutError:
|
||||
self.logger.warning("⚠️ Connection close timed out - forcing close")
|
||||
self.logger.warning("Connection close timed out - forcing close")
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Error during connection close: {e}")
|
||||
|
||||
self.logger.info("🔌 IRC connection closed")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Critical error closing connection: {e}")
|
||||
self.logger.error(f"Critical error closing connection: {e}")
|
||||
finally:
|
||||
# Ensure writer is cleared regardless of errors
|
||||
self.writer = None
|
||||
|
||||
76
src/game.py
76
src/game.py
@@ -286,8 +286,13 @@ class DuckGame:
|
||||
if self.bot.get_config('duck_spawning.rearm_on_duck_shot', False):
|
||||
self._rearm_all_disarmed_players()
|
||||
|
||||
# Check for item drops
|
||||
dropped_item = self._check_item_drop(player, duck_type)
|
||||
|
||||
self.db.save_database()
|
||||
return {
|
||||
|
||||
# Include drop info in the return
|
||||
result = {
|
||||
'success': True,
|
||||
'hit': True,
|
||||
'message_key': message_key,
|
||||
@@ -297,6 +302,12 @@ class DuckGame:
|
||||
'ducks_shot': player['ducks_shot']
|
||||
}
|
||||
}
|
||||
|
||||
# Add drop info if an item was dropped
|
||||
if dropped_item:
|
||||
result['dropped_item'] = dropped_item
|
||||
|
||||
return result
|
||||
else:
|
||||
# Miss! Duck stays in the channel
|
||||
player['shots_missed'] = player.get('shots_missed', 0) + 1 # Track missed shots
|
||||
@@ -564,4 +575,65 @@ class DuckGame:
|
||||
self.logger.debug(f"Cleaned expired effects for {player_name}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error cleaning expired effects: {e}")
|
||||
self.logger.error(f"Error cleaning expired effects: {e}")
|
||||
|
||||
def _check_item_drop(self, player, duck_type):
|
||||
"""
|
||||
Check if the duck drops an item and add it to player's inventory
|
||||
Returns the dropped item info or None
|
||||
"""
|
||||
import random
|
||||
|
||||
try:
|
||||
# Get drop chance for this duck type
|
||||
drop_chance = self.bot.get_config(f'duck_types.{duck_type}.drop_chance', 0.0)
|
||||
|
||||
# Roll for drop
|
||||
if random.random() > drop_chance:
|
||||
return None # No drop
|
||||
|
||||
# Get drop table for this duck type
|
||||
drop_table_key = f'{duck_type}_duck_drops'
|
||||
drop_table = self.bot.get_config(f'item_drops.{drop_table_key}', [])
|
||||
|
||||
if not drop_table:
|
||||
self.logger.warning(f"No drop table found for {duck_type} duck")
|
||||
return None
|
||||
|
||||
# Weighted random selection
|
||||
total_weight = sum(item.get('weight', 1) for item in drop_table)
|
||||
if total_weight <= 0:
|
||||
return None
|
||||
|
||||
random_weight = random.randint(1, total_weight)
|
||||
current_weight = 0
|
||||
|
||||
for drop_item in drop_table:
|
||||
current_weight += drop_item.get('weight', 1)
|
||||
if random_weight <= current_weight:
|
||||
item_id = drop_item.get('item_id')
|
||||
if item_id:
|
||||
# Add item to player's inventory
|
||||
inventory = player.get('inventory', {})
|
||||
item_key = str(item_id)
|
||||
inventory[item_key] = inventory.get(item_key, 0) + 1
|
||||
player['inventory'] = inventory
|
||||
|
||||
# Get item info from shop
|
||||
item_info = self.bot.shop.get_item(item_id)
|
||||
item_name = item_info.get('name', f'Item {item_id}') if item_info else f'Item {item_id}'
|
||||
|
||||
self.logger.info(f"Duck dropped {item_name} for player {player.get('nick', 'Unknown')}")
|
||||
|
||||
return {
|
||||
'item_id': item_id,
|
||||
'item_name': item_name,
|
||||
'duck_type': duck_type
|
||||
}
|
||||
break
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in _check_item_drop: {e}")
|
||||
return None
|
||||
@@ -33,9 +33,9 @@ def load_config():
|
||||
|
||||
|
||||
class EnhancedColourFormatter(logging.Formatter):
|
||||
"""Enhanced console formatter with colors, emojis, and better formatting"""
|
||||
"""Enhanced colour formatter for different log levels"""
|
||||
|
||||
# ANSI color codes with styles
|
||||
# ANSI color codes
|
||||
COLORS = {
|
||||
'DEBUG': '\033[36m', # Cyan
|
||||
'INFO': '\033[32m', # Green
|
||||
@@ -48,13 +48,13 @@ class EnhancedColourFormatter(logging.Formatter):
|
||||
'UNDERLINE': '\033[4m', # Underline
|
||||
}
|
||||
|
||||
# Emojis for different log levels
|
||||
# Log level prefixes
|
||||
EMOJIS = {
|
||||
'DEBUG': '🔍',
|
||||
'INFO': '📘',
|
||||
'WARNING': '⚠️',
|
||||
'ERROR': '❌',
|
||||
'CRITICAL': '💥',
|
||||
'DEBUG': 'DEBUG',
|
||||
'INFO': 'INFO',
|
||||
'WARNING': 'WARNING',
|
||||
'ERROR': 'ERROR',
|
||||
'CRITICAL': 'CRITICAL',
|
||||
}
|
||||
|
||||
# Component colors
|
||||
@@ -75,8 +75,8 @@ class EnhancedColourFormatter(logging.Formatter):
|
||||
bold = self.COLORS['BOLD']
|
||||
dim = self.COLORS['DIM']
|
||||
|
||||
# Get emoji
|
||||
emoji = self.EMOJIS.get(record.levelname, '📝')
|
||||
# Get level prefix
|
||||
emoji = self.EMOJIS.get(record.levelname, 'LOG')
|
||||
|
||||
# Format timestamp
|
||||
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
|
||||
@@ -109,18 +109,18 @@ class EnhancedColourFormatter(logging.Formatter):
|
||||
class EnhancedFileFormatter(logging.Formatter):
|
||||
"""Enhanced file formatter matching console format (no colors)"""
|
||||
|
||||
# Emojis for different log levels (same as console)
|
||||
# Log level prefixes (same as console)
|
||||
EMOJIS = {
|
||||
'DEBUG': '🔍',
|
||||
'INFO': '📘',
|
||||
'WARNING': '⚠️',
|
||||
'ERROR': '❌',
|
||||
'CRITICAL': '💥',
|
||||
'DEBUG': 'DEBUG',
|
||||
'INFO': 'INFO',
|
||||
'WARNING': 'WARNING',
|
||||
'ERROR': 'ERROR',
|
||||
'CRITICAL': 'CRITICAL',
|
||||
}
|
||||
|
||||
def format(self, record):
|
||||
# Get emoji (same as console)
|
||||
emoji = self.EMOJIS.get(record.levelname, '📝')
|
||||
# Get level prefix (same as console)
|
||||
emoji = self.EMOJIS.get(record.levelname, 'LOG')
|
||||
|
||||
# Format timestamp (same as console - just time, not date)
|
||||
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
|
||||
@@ -169,13 +169,13 @@ class UnifiedFormatter(logging.Formatter):
|
||||
'DIM': '\033[2m', # Dim
|
||||
}
|
||||
|
||||
# Emojis for different log levels
|
||||
# Log level prefixes
|
||||
EMOJIS = {
|
||||
'DEBUG': '🔍',
|
||||
'INFO': '📘',
|
||||
'WARNING': '⚠️',
|
||||
'ERROR': '❌',
|
||||
'CRITICAL': '💥',
|
||||
'DEBUG': 'DEBUG',
|
||||
'INFO': 'INFO',
|
||||
'WARNING': 'WARNING',
|
||||
'ERROR': 'ERROR',
|
||||
'CRITICAL': 'CRITICAL',
|
||||
}
|
||||
|
||||
# Component colors
|
||||
@@ -193,8 +193,8 @@ class UnifiedFormatter(logging.Formatter):
|
||||
self.use_colors = use_colors
|
||||
|
||||
def format(self, record):
|
||||
# Get emoji
|
||||
emoji = self.EMOJIS.get(record.levelname, '📝')
|
||||
# Get level prefix
|
||||
emoji = self.EMOJIS.get(record.levelname, 'LOG')
|
||||
|
||||
# Format timestamp (same for both)
|
||||
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
|
||||
@@ -350,7 +350,7 @@ def setup_logger(name="DuckHuntBot", console_level=None, file_level=None):
|
||||
simple_handler.setFormatter(simple_formatter)
|
||||
logger.addHandler(simple_handler)
|
||||
logger.error(f"❌ Failed to setup enhanced file logging: {e}")
|
||||
logger.info("📝 Using fallback file logging")
|
||||
logger.info("Using fallback file logging")
|
||||
except Exception as fallback_error:
|
||||
logger.error(f"💥 Complete logging setup failure: {fallback_error}")
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class MessageManager:
|
||||
"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!",
|
||||
"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!"
|
||||
|
||||
Reference in New Issue
Block a user