diff --git a/README.md b/README.md index a2ecee6..e7c31e3 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -# πŸ¦† DuckHunt IRC Bot +# DuckHunt IRC Bot A feature-rich IRC bot that brings the classic duck hunting game to your IRC channels. Players can shoot, befriend, and collect various types of ducks while managing their equipment and competing for high scores. ## ✨ Features -- πŸ¦† **Multiple Duck Types**: Normal, Golden (high HP), and Fast (quick timeout) ducks +- **Multiple Duck Types**: Normal, Golden (high HP), and Fast (quick timeout) ducks - 🎯 **Accuracy System**: Dynamic accuracy that improves with hits and degrades with misses -- πŸ”« **Weapon Management**: Magazines, bullets, and gun jamming mechanics +- **Weapon Management**: Magazines, bullets, and gun jamming mechanics - πŸ›’ **Shop System**: Buy equipment and items with XP (currency) - πŸŽ’ **Inventory System**: Collect and use various items (bread, grease, sights, etc.) - πŸ‘₯ **Player Statistics**: Track shots, hits, misses, and best times -- πŸ”§ **Fully Configurable**: Every game parameter can be customized via config +- **Fully Configurable**: Every game parameter can be customized via config - πŸ” **Authentication**: Support for both server passwords and SASL/NickServ auth - πŸ“Š **Admin Commands**: Comprehensive bot management and player administration @@ -100,7 +100,7 @@ The bot uses a nested JSON configuration system. Key settings include: - `!setstat ` - Modify player stats - `!reload_config` - Reload configuration without restart -## πŸ¦† Duck Types +## Duck Types | Type | Spawn Rate | HP | Timeout | XP Reward | |------|------------|----|---------|-----------| @@ -139,7 +139,7 @@ duckhunt/ └── duckhunt.json # Player database ``` -## πŸ”§ Development +## Development ### Adding New Features @@ -198,4 +198,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file --- -**Happy Duck Hunting!** πŸ¦†πŸ”« \ No newline at end of file +**Happy Duck Hunting!** \ No newline at end of file diff --git a/config.json b/config.json index 947d82a..4dd4013 100644 --- a/config.json +++ b/config.json @@ -32,21 +32,47 @@ "duck_types": { "normal": { "xp": 10, - "timeout": 60 + "timeout": 60, + "drop_chance": 0.15 }, "golden": { "chance": 0.15, "min_hp": 3, "max_hp": 5, "xp": 15, - "timeout": 60 + "timeout": 60, + "drop_chance": 0.50 }, "fast": { "chance": 0.25, "timeout": 20, - "xp": 12 + "xp": 12, + "drop_chance": 0.25 } }, + "item_drops": { + "normal_duck_drops": [ + {"item_id": 1, "weight": 40}, + {"item_id": 2, "weight": 25}, + {"item_id": 4, "weight": 20}, + {"item_id": 3, "weight": 15} + ], + "fast_duck_drops": [ + {"item_id": 1, "weight": 30}, + {"item_id": 2, "weight": 25}, + {"item_id": 4, "weight": 20}, + {"item_id": 8, "weight": 15}, + {"item_id": 3, "weight": 10} + ], + "golden_duck_drops": [ + {"item_id": 5, "weight": 25}, + {"item_id": 6, "weight": 20}, + {"item_id": 7, "weight": 20}, + {"item_id": 2, "weight": 15}, + {"item_id": 9, "weight": 10}, + {"item_id": 1, "weight": 10} + ] + }, "player_defaults": { "accuracy": 75, "magazines": 3, diff --git a/duckhunt.json b/duckhunt.json index 5f986f3..ec7f4eb 100644 --- a/duckhunt.json +++ b/duckhunt.json @@ -35,5 +35,5 @@ "temporary_effects": [] } }, - "last_save": "1758910428.1765292" + "last_save": "1758912506.1474519" } \ No newline at end of file diff --git a/duckhunt.py b/duckhunt.py index 28eaf81..3f69fa8 100644 --- a/duckhunt.py +++ b/duckhunt.py @@ -25,7 +25,7 @@ def main(): config = json.load(f) bot = DuckHuntBot(config) - bot.logger.info("πŸ¦† Starting DuckHunt Bot...") + bot.logger.info("Starting DuckHunt Bot...") # Run the bot asyncio.run(bot.run()) diff --git a/logs/duckhunt.log b/logs/duckhunt.log index fbb3629..4f54ee2 100644 --- a/logs/duckhunt.log +++ b/logs/duckhunt.log @@ -512,3 +512,76 @@ 19:13:48.194 πŸ“˜ INFO DuckHuntBot πŸ’Ύ Database saved 19:13:48.395 πŸ“˜ INFO DuckHuntBot πŸ”Œ IRC connection closed 19:13:48.396 πŸ“˜ INFO DuckHuntBot βœ… Bot shutdown complete +19:34:31.269 πŸ“˜ INFO DuckHuntBot Unified logging system initialized: all logs in duckhunt.log +19:34:31.269 πŸ“˜ INFO DuckHuntBot Debug mode: ON +19:34:31.269 πŸ“˜ INFO DuckHuntBot Log everything: YES +19:34:31.270 πŸ“˜ INFO DuckHuntBot Unified format: YES +19:34:31.270 πŸ“˜ INFO DuckHuntBot Console level: INFO +19:34:31.270 πŸ“˜ INFO DuckHuntBot File level: DEBUG +19:34:31.270 πŸ“˜ INFO DuckHuntBot Main log: /home/colby/duckhunt/logs/duckhunt.log +19:34:31.270 πŸ“˜ INFO DuckHuntBot πŸ€– Initializing DuckHunt Bot components... +19:34:31.271 πŸ“˜ INFO DuckHuntBot.DB Loaded 2 players from duckhunt.json +19:34:31.272 πŸ“˜ INFO SASL Unified logging system initialized: all logs in duckhunt.log +19:34:31.272 πŸ“˜ INFO SASL Debug mode: ON +19:34:31.272 πŸ“˜ INFO SASL Log everything: YES +19:34:31.272 πŸ“˜ INFO SASL Unified format: YES +19:34:31.272 πŸ“˜ INFO SASL Console level: INFO +19:34:31.272 πŸ“˜ INFO SASL File level: DEBUG +19:34:31.273 πŸ“˜ INFO SASL Main log: /home/colby/duckhunt/logs/duckhunt.log +19:34:31.273 πŸ“˜ INFO DuckHuntBot πŸ‘‘ Configured 3 admin(s): peorth, computertech, colby +19:34:31.277 πŸ“˜ INFO DuckHuntBot.Levels Loaded 8 levels from /home/colby/duckhunt/levels.json +19:34:31.277 πŸ“˜ INFO DuckHuntBot.Shop Loaded 9 shop items from /home/colby/duckhunt/shop.json +19:34:31.278 πŸ“˜ INFO DuckHuntBot πŸ¦† Starting DuckHunt Bot... +19:34:31.359 πŸ“˜ INFO DuckHuntBot Attempting to connect to irc.rizon.net:6697 (attempt 1/3) +19:34:31.587 πŸ“˜ INFO DuckHuntBot βœ… Successfully connected to irc.rizon.net:6697 +19:34:31.588 πŸ“˜ INFO DuckHuntBot πŸ” Sending server password +19:34:31.589 πŸ“˜ INFO DuckHuntBot πŸ¦† Bot is now running! Press Ctrl+C to stop. +19:34:33.131 πŸ“˜ INFO DuckHuntBot Successfully registered with IRC server +19:45:52.686 πŸ“˜ INFO DuckHuntBot πŸ›‘ Received SIGINT (Ctrl+C), shutting down immediately... +19:45:52.735 πŸ“˜ INFO DuckHuntBot πŸ”„ Cancelled 5 running tasks +19:45:52.741 πŸ“˜ INFO DuckHuntBot Message loop cancelled +19:45:52.743 πŸ“˜ INFO DuckHuntBot Message loop ended +19:45:52.747 πŸ“˜ INFO DuckHuntBot πŸ›‘ Main loop cancelled +19:45:52.752 πŸ“˜ INFO DuckHuntBot.Game Duck spawning loop cancelled +19:45:52.755 πŸ“˜ INFO DuckHuntBot.Game Duck timeout loop cancelled +19:45:52.761 πŸ“˜ INFO DuckHuntBot.Game Game loops cancelled +19:45:52.793 πŸ” DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142] +19:45:52.795 πŸ“˜ INFO DuckHuntBot πŸ’Ύ Database saved +19:45:53.041 πŸ“˜ INFO DuckHuntBot πŸ”Œ IRC connection closed +19:45:53.043 πŸ“˜ INFO DuckHuntBot βœ… Bot shutdown complete +19:48:23.828 πŸ“˜ INFO DuckHuntBot Unified logging system initialized: all logs in duckhunt.log +19:48:23.828 πŸ“˜ INFO DuckHuntBot Debug mode: ON +19:48:23.829 πŸ“˜ INFO DuckHuntBot Log everything: YES +19:48:23.829 πŸ“˜ INFO DuckHuntBot Unified format: YES +19:48:23.829 πŸ“˜ INFO DuckHuntBot Console level: INFO +19:48:23.829 πŸ“˜ INFO DuckHuntBot File level: DEBUG +19:48:23.830 πŸ“˜ INFO DuckHuntBot Main log: /home/colby/duckhunt/logs/duckhunt.log +19:48:23.830 πŸ“˜ INFO DuckHuntBot πŸ€– Initializing DuckHunt Bot components... +19:48:23.830 πŸ“˜ INFO DuckHuntBot.DB Loaded 2 players from duckhunt.json +19:48:23.832 πŸ“˜ INFO SASL Unified logging system initialized: all logs in duckhunt.log +19:48:23.832 πŸ“˜ INFO SASL Debug mode: ON +19:48:23.832 πŸ“˜ INFO SASL Log everything: YES +19:48:23.832 πŸ“˜ INFO SASL Unified format: YES +19:48:23.832 πŸ“˜ INFO SASL Console level: INFO +19:48:23.833 πŸ“˜ INFO SASL File level: DEBUG +19:48:23.833 πŸ“˜ INFO SASL Main log: /home/colby/duckhunt/logs/duckhunt.log +19:48:23.833 πŸ“˜ INFO DuckHuntBot Configured 3 admin(s): peorth, computertech, colby +19:48:23.841 πŸ“˜ INFO DuckHuntBot.Levels Loaded 8 levels from /home/colby/duckhunt/levels.json +19:48:23.845 πŸ“˜ INFO DuckHuntBot.Shop Loaded 9 shop items from /home/colby/duckhunt/shop.json +19:48:23.845 πŸ“˜ INFO DuckHuntBot Starting DuckHunt Bot... +19:48:23.946 πŸ“˜ INFO DuckHuntBot Attempting to connect to irc.rizon.net:6697 (attempt 1/3) +19:48:24.268 πŸ“˜ INFO DuckHuntBot βœ… Successfully connected to irc.rizon.net:6697 +19:48:24.268 πŸ“˜ INFO DuckHuntBot πŸ” Sending server password +19:48:24.269 πŸ“˜ INFO DuckHuntBot Bot is now running! Press Ctrl+C to stop. +19:48:26.087 πŸ“˜ INFO DuckHuntBot πŸ›‘ Received SIGINT (Ctrl+C), shutting down immediately... +19:48:26.087 πŸ“˜ INFO DuckHuntBot πŸ”„ Cancelled 5 running tasks +19:48:26.144 πŸ“˜ INFO DuckHuntBot.Game Duck timeout loop cancelled +19:48:26.145 πŸ“˜ INFO DuckHuntBot Message loop cancelled +19:48:26.145 πŸ“˜ INFO DuckHuntBot Message loop ended +19:48:26.145 πŸ“˜ INFO DuckHuntBot πŸ›‘ Main loop cancelled +19:48:26.146 πŸ“˜ INFO DuckHuntBot.Game Duck spawning loop cancelled +19:48:26.147 πŸ“˜ INFO DuckHuntBot.Game Game loops cancelled +19:48:26.214 πŸ” DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142] +19:48:26.215 πŸ“˜ INFO DuckHuntBot πŸ’Ύ Database saved +19:48:26.422 πŸ“˜ INFO DuckHuntBot πŸ”Œ IRC connection closed +19:48:26.422 πŸ“˜ INFO DuckHuntBot βœ… Bot shutdown complete diff --git a/messages.json b/messages.json index 1d5dd72..57c2b9a 100644 --- a/messages.json +++ b/messages.json @@ -7,14 +7,14 @@ "duck_flies_away": "The duck flies away. Β·Β°'`'Β°-.,ΒΈΒΈ.Β·Β°'`", "fast_duck_flies_away": "The fast duck quickly flies away! Β·Β°'`'Β°-.,ΒΈΒΈ.Β·Β°'`", "golden_duck_flies_away": "The {gold}golden duck{reset} flies away majestically. Β·Β°'`'Β°-.,ΒΈΒΈ.Β·Β°'`", - "bang_hit": "{nick} > *BANG* You shot the duck! [+{xp_gained} xp] [Total ducks: {ducks_shot}]", - "bang_hit_golden": "{nick} > *BANG* You shot a GOLDEN DUCK! [{hp_remaining} HP remaining] [+{xp_gained} xp]", - "bang_hit_golden_killed": "{nick} > *BANG* You killed the GOLDEN DUCK! [+{xp_gained} xp] [Total ducks: {ducks_shot}]", - "bang_hit_fast": "{nick} > *BANG* You shot a FAST DUCK! [+{xp_gained} xp] [Total ducks: {ducks_shot}]", - "bang_miss": "{nick} > *BANG* You missed the duck!", - "bang_friendly_fire_penalty": "{nick} > *BANG* You missed and hit {victim}! [GUN CONFISCATED] [LOST {xp_lost} XP]", - "bang_friendly_fire_insured": "{nick} > *BANG* You missed and hit {victim}! [INSURANCE PROTECTED - No penalties]", - "bang_no_duck": "{nick} > *BANG* What did you shoot at? There is no duck in the area... [GUN CONFISCATED]", + "bang_hit": "{nick} > {red}*BANG*{reset} You shot the duck! [+{xp_gained} xp] [Total ducks: {ducks_shot}]", + "bang_hit_golden": "{nick} > {red}*BANG*{reset} You shot a {gold}GOLDEN DUCK!{reset} [{hp_remaining} HP remaining] [+{xp_gained} xp]", + "bang_hit_golden_killed": "{nick} > {red}*BANG*{reset} You killed the GOLDEN DUCK! [+{xp_gained} xp] [Total ducks: {ducks_shot}]", + "bang_hit_fast": "{nick} > {red}*BANG*{reset} You shot a FAST DUCK! [+{xp_gained} xp] [Total ducks: {ducks_shot}]", + "bang_miss": "{nick} > {red}*BANG*{reset} You missed the duck!", + "bang_friendly_fire_penalty": "{nick} > {red}*BANG*{reset} You missed and hit {victim}! {red}[GUN CONFISCATED]{reset} [LOST {xp_lost} XP]", + "bang_friendly_fire_insured": "{nick} > *BANG* You missed and hit {victim}! {green}[INSURANCE PROTECTED - No penalties]{reset}", + "bang_no_duck": "{nick} > *BANG* What did you shoot at? There is no duck in the area... {red}[GUN CONFISCATED]{reset}", "bang_no_ammo": "{nick} > *click* You're out of ammo! Use !reload", "bang_gun_jammed": "{nick} > *click* Your gun jammed! [AMMO WASTED]", "bang_not_armed": "{nick} > Your gun has been confiscated. Buy it back from the shop.", @@ -62,6 +62,9 @@ "gift_insurance": "{nick} > Gave Hunter's Insurance to {target_nick} - protecting them from friendly fire!", "gift_dry_clothes": "{nick} > Gave dry clothes to {target_nick} - now they can stay dry!", "gift_buy_gun_back": "{nick} > Gave a gun license to {target_nick} - helping them get their gun back!", + "duck_drop_normal": "{nick} > The duck dropped a {green}{item_name}{reset}! [Added to inventory]", + "duck_drop_fast": "{nick} > The {cyan}fast duck{reset} dropped a {green}{item_name}{reset}! [Added to inventory]", + "duck_drop_golden": "{nick} > The {gold}golden duck{reset} dropped a {green}{item_name}{reset}! [Added to inventory]", "colours": { "white": "\u00030", diff --git a/src/__pycache__/duckhuntbot.cpython-312.pyc b/src/__pycache__/duckhuntbot.cpython-312.pyc index 5475b54..acee0d2 100644 Binary files a/src/__pycache__/duckhuntbot.cpython-312.pyc and b/src/__pycache__/duckhuntbot.cpython-312.pyc differ diff --git a/src/__pycache__/game.cpython-312.pyc b/src/__pycache__/game.cpython-312.pyc index e410544..8784b87 100644 Binary files a/src/__pycache__/game.cpython-312.pyc and b/src/__pycache__/game.cpython-312.pyc differ diff --git a/src/__pycache__/utils.cpython-312.pyc b/src/__pycache__/utils.cpython-312.pyc index 7983bd0..b599126 100644 Binary files a/src/__pycache__/utils.cpython-312.pyc and b/src/__pycache__/utils.cpython-312.pyc differ diff --git a/src/duckhuntbot.py b/src/duckhuntbot.py index 54ce104..2aade77 100644 --- a/src/duckhuntbot.py +++ b/src/duckhuntbot.py @@ -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 diff --git a/src/game.py b/src/game.py index f58be43..ddeea88 100644 --- a/src/game.py +++ b/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}") \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/logging_utils.py b/src/logging_utils.py index 2afd148..c926add 100644 --- a/src/logging_utils.py +++ b/src/logging_utils.py @@ -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}") diff --git a/src/utils.py b/src/utils.py index b5d3b3f..de3ffd1 100644 --- a/src/utils.py +++ b/src/utils.py @@ -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!"