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:
14
README.md
14
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.
|
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
|
## ✨ 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
|
- 🎯 **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)
|
- 🛒 **Shop System**: Buy equipment and items with XP (currency)
|
||||||
- 🎒 **Inventory System**: Collect and use various items (bread, grease, sights, etc.)
|
- 🎒 **Inventory System**: Collect and use various items (bread, grease, sights, etc.)
|
||||||
- 👥 **Player Statistics**: Track shots, hits, misses, and best times
|
- 👥 **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
|
- 🔐 **Authentication**: Support for both server passwords and SASL/NickServ auth
|
||||||
- 📊 **Admin Commands**: Comprehensive bot management and player administration
|
- 📊 **Admin Commands**: Comprehensive bot management and player administration
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ The bot uses a nested JSON configuration system. Key settings include:
|
|||||||
- `!setstat <player> <stat> <value>` - Modify player stats
|
- `!setstat <player> <stat> <value>` - Modify player stats
|
||||||
- `!reload_config` - Reload configuration without restart
|
- `!reload_config` - Reload configuration without restart
|
||||||
|
|
||||||
## 🦆 Duck Types
|
## Duck Types
|
||||||
|
|
||||||
| Type | Spawn Rate | HP | Timeout | XP Reward |
|
| Type | Spawn Rate | HP | Timeout | XP Reward |
|
||||||
|------|------------|----|---------|-----------|
|
|------|------------|----|---------|-----------|
|
||||||
@@ -139,7 +139,7 @@ duckhunt/
|
|||||||
└── duckhunt.json # Player database
|
└── duckhunt.json # Player database
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔧 Development
|
## Development
|
||||||
|
|
||||||
### Adding New Features
|
### Adding New Features
|
||||||
|
|
||||||
@@ -198,4 +198,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Happy Duck Hunting!** 🦆🔫
|
**Happy Duck Hunting!**
|
||||||
32
config.json
32
config.json
@@ -32,21 +32,47 @@
|
|||||||
"duck_types": {
|
"duck_types": {
|
||||||
"normal": {
|
"normal": {
|
||||||
"xp": 10,
|
"xp": 10,
|
||||||
"timeout": 60
|
"timeout": 60,
|
||||||
|
"drop_chance": 0.15
|
||||||
},
|
},
|
||||||
"golden": {
|
"golden": {
|
||||||
"chance": 0.15,
|
"chance": 0.15,
|
||||||
"min_hp": 3,
|
"min_hp": 3,
|
||||||
"max_hp": 5,
|
"max_hp": 5,
|
||||||
"xp": 15,
|
"xp": 15,
|
||||||
"timeout": 60
|
"timeout": 60,
|
||||||
|
"drop_chance": 0.50
|
||||||
},
|
},
|
||||||
"fast": {
|
"fast": {
|
||||||
"chance": 0.25,
|
"chance": 0.25,
|
||||||
"timeout": 20,
|
"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": {
|
"player_defaults": {
|
||||||
"accuracy": 75,
|
"accuracy": 75,
|
||||||
"magazines": 3,
|
"magazines": 3,
|
||||||
|
|||||||
@@ -35,5 +35,5 @@
|
|||||||
"temporary_effects": []
|
"temporary_effects": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"last_save": "1758910428.1765292"
|
"last_save": "1758912506.1474519"
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ def main():
|
|||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
||||||
bot = DuckHuntBot(config)
|
bot = DuckHuntBot(config)
|
||||||
bot.logger.info("🦆 Starting DuckHunt Bot...")
|
bot.logger.info("Starting DuckHunt Bot...")
|
||||||
|
|
||||||
# Run the bot
|
# Run the bot
|
||||||
asyncio.run(bot.run())
|
asyncio.run(bot.run())
|
||||||
|
|||||||
@@ -512,3 +512,76 @@
|
|||||||
19:13:48.194 📘 INFO DuckHuntBot 💾 Database saved
|
19:13:48.194 📘 INFO DuckHuntBot 💾 Database saved
|
||||||
19:13:48.395 📘 INFO DuckHuntBot 🔌 IRC connection closed
|
19:13:48.395 📘 INFO DuckHuntBot 🔌 IRC connection closed
|
||||||
19:13:48.396 📘 INFO DuckHuntBot ✅ Bot shutdown complete
|
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
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
"duck_flies_away": "The duck flies away. ·°'`'°-.,¸¸.·°'`",
|
"duck_flies_away": "The duck flies away. ·°'`'°-.,¸¸.·°'`",
|
||||||
"fast_duck_flies_away": "The fast duck quickly flies away! ·°'`'°-.,¸¸.·°'`",
|
"fast_duck_flies_away": "The fast duck quickly flies away! ·°'`'°-.,¸¸.·°'`",
|
||||||
"golden_duck_flies_away": "The {gold}golden duck{reset} flies away majestically. ·°'`'°-.,¸¸.·°'`",
|
"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": "{nick} > {red}*BANG*{reset} 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": "{nick} > {red}*BANG*{reset} You shot a {gold}GOLDEN DUCK!{reset} [{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_golden_killed": "{nick} > {red}*BANG*{reset} 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_hit_fast": "{nick} > {red}*BANG*{reset} You shot a FAST DUCK! [+{xp_gained} xp] [Total ducks: {ducks_shot}]",
|
||||||
"bang_miss": "{nick} > *BANG* You missed the duck!",
|
"bang_miss": "{nick} > {red}*BANG*{reset} You missed the duck!",
|
||||||
"bang_friendly_fire_penalty": "{nick} > *BANG* You missed and hit {victim}! [GUN CONFISCATED] [LOST {xp_lost} XP]",
|
"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}! [INSURANCE PROTECTED - No penalties]",
|
"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... [GUN CONFISCATED]",
|
"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_no_ammo": "{nick} > *click* You're out of ammo! Use !reload",
|
||||||
"bang_gun_jammed": "{nick} > *click* Your gun jammed! [AMMO WASTED]",
|
"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.",
|
"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_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_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!",
|
"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": {
|
"colours": {
|
||||||
"white": "\u00030",
|
"white": "\u00030",
|
||||||
|
|||||||
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']
|
admins_list = self.get_config('admins', ['colby']) or ['colby']
|
||||||
self.admins = [admin.lower() for admin in admins_list]
|
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
|
# Initialize level manager first
|
||||||
levels_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'levels.json')
|
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()]
|
tasks = [t for t in asyncio.all_tasks(loop) if not t.done()]
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
self.logger.info(f"🔄 Cancelled {len(tasks)} running tasks")
|
self.logger.info(f"Cancelled {len(tasks)} running tasks")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error cancelling tasks: {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
|
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
|
return
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
@@ -475,6 +475,28 @@ class DuckHuntBot:
|
|||||||
message = self.messages.get(result['message_key'], **result['message_args'])
|
message = self.messages.get(result['message_key'], **result['message_args'])
|
||||||
self.send_message(channel, message)
|
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):
|
async def handle_bef(self, nick, channel, player):
|
||||||
"""Handle !bef (befriend) command"""
|
"""Handle !bef (befriend) command"""
|
||||||
result = self.game.befriend_duck(nick, channel, player)
|
result = self.game.befriend_duck(nick, channel, player)
|
||||||
@@ -676,23 +698,23 @@ class DuckHuntBot:
|
|||||||
if top_xp:
|
if top_xp:
|
||||||
xp_rankings = []
|
xp_rankings = []
|
||||||
for i, (player_nick, xp) in enumerate(top_xp, 1):
|
for i, (player_nick, xp) in enumerate(top_xp, 1):
|
||||||
medal = "🥇" if i == 1 else "🥈" if i == 2 else "🥉"
|
medal = "#1" if i == 1 else "#2" if i == 2 else "#3"
|
||||||
xp_rankings.append(f"{medal}{player_nick}:{xp}XP")
|
xp_rankings.append(f"{medal} {player_nick}:{xp}XP")
|
||||||
xp_line = f"🏆 {bold}Top XP{reset} " + " | ".join(xp_rankings)
|
xp_line = f"Top XP: {bold}{reset} " + " | ".join(xp_rankings)
|
||||||
self.send_message(channel, xp_line)
|
self.send_message(channel, xp_line)
|
||||||
else:
|
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
|
# Format ducks shot leaderboard as single line
|
||||||
if top_ducks:
|
if top_ducks:
|
||||||
duck_rankings = []
|
duck_rankings = []
|
||||||
for i, (player_nick, ducks) in enumerate(top_ducks, 1):
|
for i, (player_nick, ducks) in enumerate(top_ducks, 1):
|
||||||
medal = "🥇" if i == 1 else "🥈" if i == 2 else "🥉"
|
medal = "#1" if i == 1 else "#2" if i == 2 else "#3"
|
||||||
duck_rankings.append(f"{medal}{player_nick}:{ducks}")
|
duck_rankings.append(f"{medal} {player_nick}:{ducks}")
|
||||||
duck_line = f"🦆 {bold}Top Hunters{reset} " + " | ".join(duck_rankings)
|
duck_line = f"Top Hunters: {bold}{reset} " + " | ".join(duck_rankings)
|
||||||
self.send_message(channel, duck_line)
|
self.send_message(channel, duck_line)
|
||||||
else:
|
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:
|
except Exception as e:
|
||||||
self.logger.error(f"Error in handle_topduck: {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())
|
game_task = asyncio.create_task(self.game.start_game_loops())
|
||||||
message_task = asyncio.create_task(self.message_loop())
|
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
|
# Wait for shutdown signal or task completion with frequent checks
|
||||||
while not self.shutdown_requested:
|
while not self.shutdown_requested:
|
||||||
done, _pending = await asyncio.wait(
|
done, _pending = await asyncio.wait(
|
||||||
@@ -1181,12 +1203,12 @@ class DuckHuntBot:
|
|||||||
break
|
break
|
||||||
break
|
break
|
||||||
|
|
||||||
self.logger.info("🔄 Shutdown initiated, cleaning up...")
|
self.logger.info("Shutdown initiated, cleaning up...")
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
self.logger.info("🛑 Main loop cancelled")
|
self.logger.info("🛑 Main loop cancelled")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"❌ Bot error: {e}")
|
self.logger.error(f"Bot error: {e}")
|
||||||
finally:
|
finally:
|
||||||
# Fast cleanup - cancel tasks immediately with short timeout
|
# 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()]
|
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
|
timeout=1.0
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
self.logger.warning("⚠️ Task cancellation timed out")
|
self.logger.warning("Task cancellation timed out")
|
||||||
|
|
||||||
# Quick database save
|
# Quick database save
|
||||||
try:
|
try:
|
||||||
self.db.save_database()
|
self.db.save_database()
|
||||||
self.logger.info("💾 Database saved")
|
self.logger.info("Database saved")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"❌ Error saving database: {e}")
|
self.logger.error(f"Error saving database: {e}")
|
||||||
|
|
||||||
# Fast connection close
|
# Fast connection close
|
||||||
await self._close_connection()
|
await self._close_connection()
|
||||||
|
|
||||||
self.logger.info("✅ Bot shutdown complete")
|
self.logger.info("Bot shutdown complete")
|
||||||
|
|
||||||
async def _close_connection(self):
|
async def _close_connection(self):
|
||||||
"""Close IRC connection with comprehensive error handling"""
|
"""Close IRC connection with comprehensive error handling"""
|
||||||
@@ -1235,14 +1257,14 @@ class DuckHuntBot:
|
|||||||
self.writer.close()
|
self.writer.close()
|
||||||
await asyncio.wait_for(self.writer.wait_closed(), timeout=2.0)
|
await asyncio.wait_for(self.writer.wait_closed(), timeout=2.0)
|
||||||
except asyncio.TimeoutError:
|
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:
|
except Exception as e:
|
||||||
self.logger.debug(f"Error during connection close: {e}")
|
self.logger.debug(f"Error during connection close: {e}")
|
||||||
|
|
||||||
self.logger.info("🔌 IRC connection closed")
|
self.logger.info("🔌 IRC connection closed")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"❌ Critical error closing connection: {e}")
|
self.logger.error(f"Critical error closing connection: {e}")
|
||||||
finally:
|
finally:
|
||||||
# Ensure writer is cleared regardless of errors
|
# Ensure writer is cleared regardless of errors
|
||||||
self.writer = None
|
self.writer = None
|
||||||
|
|||||||
74
src/game.py
74
src/game.py
@@ -286,8 +286,13 @@ class DuckGame:
|
|||||||
if self.bot.get_config('duck_spawning.rearm_on_duck_shot', False):
|
if self.bot.get_config('duck_spawning.rearm_on_duck_shot', False):
|
||||||
self._rearm_all_disarmed_players()
|
self._rearm_all_disarmed_players()
|
||||||
|
|
||||||
|
# Check for item drops
|
||||||
|
dropped_item = self._check_item_drop(player, duck_type)
|
||||||
|
|
||||||
self.db.save_database()
|
self.db.save_database()
|
||||||
return {
|
|
||||||
|
# Include drop info in the return
|
||||||
|
result = {
|
||||||
'success': True,
|
'success': True,
|
||||||
'hit': True,
|
'hit': True,
|
||||||
'message_key': message_key,
|
'message_key': message_key,
|
||||||
@@ -297,6 +302,12 @@ class DuckGame:
|
|||||||
'ducks_shot': player['ducks_shot']
|
'ducks_shot': player['ducks_shot']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add drop info if an item was dropped
|
||||||
|
if dropped_item:
|
||||||
|
result['dropped_item'] = dropped_item
|
||||||
|
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
# Miss! Duck stays in the channel
|
# Miss! Duck stays in the channel
|
||||||
player['shots_missed'] = player.get('shots_missed', 0) + 1 # Track missed shots
|
player['shots_missed'] = player.get('shots_missed', 0) + 1 # Track missed shots
|
||||||
@@ -565,3 +576,64 @@ class DuckGame:
|
|||||||
|
|
||||||
except Exception as e:
|
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):
|
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 = {
|
COLORS = {
|
||||||
'DEBUG': '\033[36m', # Cyan
|
'DEBUG': '\033[36m', # Cyan
|
||||||
'INFO': '\033[32m', # Green
|
'INFO': '\033[32m', # Green
|
||||||
@@ -48,13 +48,13 @@ class EnhancedColourFormatter(logging.Formatter):
|
|||||||
'UNDERLINE': '\033[4m', # Underline
|
'UNDERLINE': '\033[4m', # Underline
|
||||||
}
|
}
|
||||||
|
|
||||||
# Emojis for different log levels
|
# Log level prefixes
|
||||||
EMOJIS = {
|
EMOJIS = {
|
||||||
'DEBUG': '🔍',
|
'DEBUG': 'DEBUG',
|
||||||
'INFO': '📘',
|
'INFO': 'INFO',
|
||||||
'WARNING': '⚠️',
|
'WARNING': 'WARNING',
|
||||||
'ERROR': '❌',
|
'ERROR': 'ERROR',
|
||||||
'CRITICAL': '💥',
|
'CRITICAL': 'CRITICAL',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Component colors
|
# Component colors
|
||||||
@@ -75,8 +75,8 @@ class EnhancedColourFormatter(logging.Formatter):
|
|||||||
bold = self.COLORS['BOLD']
|
bold = self.COLORS['BOLD']
|
||||||
dim = self.COLORS['DIM']
|
dim = self.COLORS['DIM']
|
||||||
|
|
||||||
# Get emoji
|
# Get level prefix
|
||||||
emoji = self.EMOJIS.get(record.levelname, '📝')
|
emoji = self.EMOJIS.get(record.levelname, 'LOG')
|
||||||
|
|
||||||
# Format timestamp
|
# Format timestamp
|
||||||
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
|
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
|
||||||
@@ -109,18 +109,18 @@ class EnhancedColourFormatter(logging.Formatter):
|
|||||||
class EnhancedFileFormatter(logging.Formatter):
|
class EnhancedFileFormatter(logging.Formatter):
|
||||||
"""Enhanced file formatter matching console format (no colors)"""
|
"""Enhanced file formatter matching console format (no colors)"""
|
||||||
|
|
||||||
# Emojis for different log levels (same as console)
|
# Log level prefixes (same as console)
|
||||||
EMOJIS = {
|
EMOJIS = {
|
||||||
'DEBUG': '🔍',
|
'DEBUG': 'DEBUG',
|
||||||
'INFO': '📘',
|
'INFO': 'INFO',
|
||||||
'WARNING': '⚠️',
|
'WARNING': 'WARNING',
|
||||||
'ERROR': '❌',
|
'ERROR': 'ERROR',
|
||||||
'CRITICAL': '💥',
|
'CRITICAL': 'CRITICAL',
|
||||||
}
|
}
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
# Get emoji (same as console)
|
# Get level prefix (same as console)
|
||||||
emoji = self.EMOJIS.get(record.levelname, '📝')
|
emoji = self.EMOJIS.get(record.levelname, 'LOG')
|
||||||
|
|
||||||
# Format timestamp (same as console - just time, not date)
|
# Format timestamp (same as console - just time, not date)
|
||||||
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
|
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
|
||||||
@@ -169,13 +169,13 @@ class UnifiedFormatter(logging.Formatter):
|
|||||||
'DIM': '\033[2m', # Dim
|
'DIM': '\033[2m', # Dim
|
||||||
}
|
}
|
||||||
|
|
||||||
# Emojis for different log levels
|
# Log level prefixes
|
||||||
EMOJIS = {
|
EMOJIS = {
|
||||||
'DEBUG': '🔍',
|
'DEBUG': 'DEBUG',
|
||||||
'INFO': '📘',
|
'INFO': 'INFO',
|
||||||
'WARNING': '⚠️',
|
'WARNING': 'WARNING',
|
||||||
'ERROR': '❌',
|
'ERROR': 'ERROR',
|
||||||
'CRITICAL': '💥',
|
'CRITICAL': 'CRITICAL',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Component colors
|
# Component colors
|
||||||
@@ -193,8 +193,8 @@ class UnifiedFormatter(logging.Formatter):
|
|||||||
self.use_colors = use_colors
|
self.use_colors = use_colors
|
||||||
|
|
||||||
def format(self, record):
|
def format(self, record):
|
||||||
# Get emoji
|
# Get level prefix
|
||||||
emoji = self.EMOJIS.get(record.levelname, '📝')
|
emoji = self.EMOJIS.get(record.levelname, 'LOG')
|
||||||
|
|
||||||
# Format timestamp (same for both)
|
# Format timestamp (same for both)
|
||||||
timestamp = datetime.fromtimestamp(record.created).strftime('%H:%M:%S.%f')[:-3]
|
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)
|
simple_handler.setFormatter(simple_formatter)
|
||||||
logger.addHandler(simple_handler)
|
logger.addHandler(simple_handler)
|
||||||
logger.error(f"❌ Failed to setup enhanced file logging: {e}")
|
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:
|
except Exception as fallback_error:
|
||||||
logger.error(f"💥 Complete logging setup failure: {fallback_error}")
|
logger.error(f"💥 Complete logging setup failure: {fallback_error}")
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class MessageManager:
|
|||||||
"duck_spawn": [
|
"duck_spawn": [
|
||||||
"・゜゜・。。・゜゜\\_o< QUACK! A duck has appeared! Type !bang to shoot it!",
|
"・゜゜・。。・゜゜\\_o< QUACK! A duck has appeared! Type !bang to shoot it!",
|
||||||
"・゜゜・。。・゜゜\\_o< *flap flap* A wild duck landed! Use !bang to hunt 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!",
|
"・゜゜・。。・゜゜\\_o< Quack quack! Fresh duck spotted! !bang to bag it!",
|
||||||
"*rustling* A duck waddles out from the bushes! Fire with !bang!",
|
"*rustling* A duck waddles out from the bushes! Fire with !bang!",
|
||||||
"・゜゜・。。・゜゜\\_o< Splash! A duck surfaces! Shoot it with !bang!"
|
"・゜゜・。。・゜゜\\_o< Splash! A duck surfaces! Shoot it with !bang!"
|
||||||
|
|||||||
Reference in New Issue
Block a user