diff --git a/.gitignore b/.gitignore deleted file mode 100644 index eb83e99..0000000 --- a/.gitignore +++ /dev/null @@ -1,74 +0,0 @@ -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# Virtual Environment -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# IDE -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# Logs -*.log -logs/ -duckhunt.log* - -# Database and Runtime Data -duckhunt.json -duckhunt.db -*.db -*.sqlite -*.sqlite3 - -# Configuration (sensitive) -config_local.json -config_backup.json - -# Backups -backup/ -*.backup -*.bak - -# OS -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Temporary files -*.tmp -*.temp -.cache/ \ No newline at end of file diff --git a/__pycache__/duckhunt.cpython-312.pyc b/__pycache__/duckhunt.cpython-312.pyc new file mode 100644 index 0000000..2229791 Binary files /dev/null and b/__pycache__/duckhunt.cpython-312.pyc differ diff --git a/config.json b/config.json index b8aaab0..73a8e3a 100644 --- a/config.json +++ b/config.json @@ -1,30 +1,77 @@ { - "server": "irc.rizon.net", - "port": 6697, - "nick": "DickHunt", - "channels": ["#ct"], - "ssl": true, + "connection": { + "server": "irc.rizon.net", + "port": 6697, + "nick": "DickHunt", + "channels": ["#ct"], + "ssl": true, + "password": "your_iline_password_here", + "max_retries": 3, + "retry_delay": 5, + "timeout": 30 + }, + "sasl": { "enabled": false, "username": "duckhunt", "password": "duckhunt//789//" }, - "password": "your_iline_password_here", + "admins": ["peorth", "computertech", "colby"], - "duck_spawn_min": 10, - "duck_spawn_max": 30, - "duck_timeout": 60, - "befriend_success_rate": 75, - "rearm_on_duck_shot": true, + "duck_spawning": { + "spawn_min": 10, + "spawn_max": 30, + "timeout": 60, + "rearm_on_duck_shot": true + }, - "golden_duck_chance": 0.15, - "golden_duck_min_hp": 3, - "golden_duck_max_hp": 5, - "golden_duck_xp": 15, + "duck_types": { + "normal": { + "xp": 10, + "timeout": 60 + }, + "golden": { + "chance": 0.15, + "min_hp": 3, + "max_hp": 5, + "xp": 15, + "timeout": 50 + }, + "fast": { + "chance": 0.25, + "timeout": 20, + "xp": 20 + } + }, - "fast_duck_chance": 0.25, - "fast_duck_timeout": 30, - "fast_duck_xp": 12 + "player_defaults": { + "accuracy": 75, + "magazines": 3, + "bullets_per_magazine": 6, + "jam_chance": 5, + "xp": 0 + }, + "gameplay": { + "befriend_success_rate": 75, + "befriend_xp": 5, + "accuracy_gain_on_hit": 1, + "accuracy_loss_on_miss": 2, + "min_accuracy": 10, + "max_accuracy": 100, + "min_befriend_success_rate": 5, + "max_befriend_success_rate": 95 + }, + + "features": { + "shop_enabled": true, + "inventory_enabled": true, + "auto_rearm_enabled": true + }, + + "limits": { + "max_inventory_items": 20, + "max_temp_effects": 5 + } } \ No newline at end of file diff --git a/duckhunt.log b/duckhunt.log index b3178b7..f06dd90 100644 --- a/duckhunt.log +++ b/duckhunt.log @@ -640,3 +640,14 @@ 2025-09-23 20:12:39,664 [INFO ] DuckHuntBot - run:535: 💾 Database saved 2025-09-23 20:12:39,766 [INFO ] DuckHuntBot - _close_connection:559: 🔌 IRC connection closed 2025-09-23 20:12:39,766 [INFO ] DuckHuntBot - run:542: ✅ Bot shutdown complete +2025-09-24 16:45:23,678 [INFO ] DuckHuntBot - setup_logger:61: Enhanced logging system initialized with file rotation +2025-09-24 16:45:23,679 [INFO ] DuckHuntBot.DB - load_database:47: Loaded 1 players from duckhunt.json +2025-09-24 16:45:23,681 [INFO ] SASL - setup_logger:61: Enhanced logging system initialized with file rotation +2025-09-24 16:45:23,682 [INFO ] DuckHuntBot.Shop - load_items:30: Loaded 4 shop items from /home/colby/duckhunt/shop.json +2025-09-24 16:45:23,683 [INFO ] DuckHuntBot.Levels - load_levels:28: Loaded 8 levels from /home/colby/duckhunt/levels.json +2025-09-24 18:56:53,426 [INFO ] DuckHuntBot - setup_logger:61: Enhanced logging system initialized with file rotation +2025-09-24 18:56:53,428 [INFO ] DuckHuntBot.DB - load_database:47: Loaded 1 players from duckhunt.json +2025-09-24 18:56:53,431 [INFO ] SASL - setup_logger:61: Enhanced logging system initialized with file rotation +2025-09-24 18:56:53,434 [INFO ] DuckHuntBot.Shop - load_items:30: Loaded 5 shop items from /home/colby/duckhunt/shop.json +2025-09-24 18:56:53,436 [INFO ] DuckHuntBot.Levels - load_levels:28: Loaded 8 levels from /home/colby/duckhunt/levels.json +2025-09-24 18:56:53,439 [INFO ] DuckHuntBot.DB - load_database:47: Loaded 1 players from duckhunt.json diff --git a/levels.json b/levels.json index f4b46a1..2868f16 100644 --- a/levels.json +++ b/levels.json @@ -8,8 +8,9 @@ "name": "Duck Novice", "min_xp": 0, "max_xp": 49, - "befriend_success_rate": 85, - "accuracy_modifier": 5, + "befriend_success_rate": 95, + "accuracy_modifier": 25, + "jam_chance": 0, "duck_spawn_speed_modifier": 1.0, "magazines": 3, "bullets_per_magazine": 6, @@ -19,8 +20,9 @@ "name": "Pond Visitor", "min_xp": 50, "max_xp": 149, - "befriend_success_rate": 80, - "accuracy_modifier": 0, + "befriend_success_rate": 85, + "accuracy_modifier": 15, + "jam_chance": 1, "duck_spawn_speed_modifier": 1.0, "magazines": 3, "bullets_per_magazine": 6, @@ -30,10 +32,11 @@ "name": "Duck Hunter", "min_xp": 150, "max_xp": 299, - "befriend_success_rate": 75, - "accuracy_modifier": -5, + "befriend_success_rate": 80, + "accuracy_modifier": 5, + "jam_chance": 2, "duck_spawn_speed_modifier": 0.9, - "magazines": 2, + "magazines": 3, "bullets_per_magazine": 6, "description": "Your reputation precedes you, ducks are more cautious" }, @@ -41,8 +44,9 @@ "name": "Wetland Stalker", "min_xp": 300, "max_xp": 599, - "befriend_success_rate": 70, - "accuracy_modifier": -10, + "befriend_success_rate": 75, + "accuracy_modifier": -5, + "jam_chance": 3, "duck_spawn_speed_modifier": 0.8, "magazines": 2, "bullets_per_magazine": 6, @@ -52,8 +56,9 @@ "name": "Apex Predator", "min_xp": 600, "max_xp": 999, - "befriend_success_rate": 65, + "befriend_success_rate": 70, "accuracy_modifier": -15, + "jam_chance": 4, "duck_spawn_speed_modifier": 0.7, "magazines": 2, "bullets_per_magazine": 6, @@ -63,8 +68,9 @@ "name": "Duck Whisperer", "min_xp": 1000, "max_xp": 1999, - "befriend_success_rate": 60, + "befriend_success_rate": 65, "accuracy_modifier": -20, + "jam_chance": 5, "duck_spawn_speed_modifier": 0.6, "magazines": 1, "bullets_per_magazine": 6, @@ -74,8 +80,9 @@ "name": "Legendary Hunter", "min_xp": 2000, "max_xp": 4999, - "befriend_success_rate": 55, + "befriend_success_rate": 60, "accuracy_modifier": -25, + "jam_chance": 6, "duck_spawn_speed_modifier": 0.5, "magazines": 1, "bullets_per_magazine": 6, @@ -85,8 +92,9 @@ "name": "Duck Deity", "min_xp": 5000, "max_xp": 999999, - "befriend_success_rate": 50, + "befriend_success_rate": 55, "accuracy_modifier": -30, + "jam_chance": 8, "duck_spawn_speed_modifier": 0.4, "magazines": 1, "bullets_per_magazine": 6, diff --git a/messages.json b/messages.json index 0e00990..a3d65cf 100644 --- a/messages.json +++ b/messages.json @@ -1,51 +1,52 @@ { "duck_spawn": [ - "・゜゜・。。・゜゜\\_O< {bold}QUACK!{reset}", - "・゜゜・。。・゜゜\\_o< {light_grey}quack~{reset}", - "・゜゜・。。・゜゜\\_O> {bold}*flap flap*{reset}" + "・゜゜・。。・゜゜\\_O< QUACK!", + "・゜゜・。。・゜゜\\_o< quack~", + "・゜゜・。。・゜゜\\_O> *flap flap*" ], - "duck_flies_away": "The {bold}duck{reset} flies away. ·°'`'°-.,¸¸.·°'`", - "fast_duck_flies_away": "The {cyan}fast duck{reset} quickly flies away! ·°'`'°-.,¸¸.·°'`", - "golden_duck_flies_away": "The {yellow}{bold}golden duck{reset} flies away majestically. ·°'`'°-.,¸¸.·°'`", - "bang_hit": "{nick} > {green}*BANG*{reset} You shot the duck! [{green}+{xp_gained} xp{reset}] [Total ducks: {bold}{ducks_shot}{reset}]", - "bang_hit_golden": "{nick} > {green}*BANG*{reset} You shot a {yellow}{bold}GOLDEN DUCK{reset}! [{yellow}{hp_remaining} HP remaining{reset}] [{green}+{xp_gained} xp{reset}]", - "bang_hit_golden_killed": "{nick} > {green}*BANG*{reset} You killed the {yellow}{bold}GOLDEN DUCK{reset}! [{green}+{xp_gained} xp{reset}] [Total ducks: {bold}{ducks_shot}{reset}]", - "bang_hit_fast": "{nick} > {green}*BANG*{reset} You shot a {cyan}{bold}FAST DUCK{reset}! [{green}+{xp_gained} xp{reset}] [Total ducks: {bold}{ducks_shot}{reset}]", - "bang_miss": "{nick} > {red}*BANG*{reset} You missed the {cyan}duck{reset}!", - "bang_no_duck": "{nick} > {red}*BANG*{reset} What did you shoot at? There is {red}no duck{reset} in the area... [{red}GUN CONFISCATED{reset}]", - "bang_no_ammo": "{nick} > {orange}*click*{reset} You're out of ammo! Use {blue}!reload{reset}", - "bang_gun_jammed": "{nick} > {red}*click*{reset} Your gun jammed! [{red}AMMO WASTED{reset}]", - "bang_not_armed": "{nick} > You are {red}not armed{reset}.", - "bef_success": "{nick} > {orange}*befriend*{reset} You befriended the duck! [{green}+{xp_gained} xp{reset}] [Ducks befriended: {bold}{ducks_befriended}{reset}]", - "bef_failed": "{nick} > {pink}*gentle approach*{reset} The {cyan}duck{reset} doesn't trust you and {yellow}flies away{reset}...", - "bef_no_duck": "{nick} > {pink}*gentle approach*{reset} There is {red}no duck{reset} to befriend in the area...", - "bef_duck_shot": "{nick} > {pink}*gentle approach*{reset} The {cyan}duck{reset} is {red}already dead{reset}! You can't befriend it now...", - "reload_success": "{nick} > {orange}*click*{reset} New magazine loaded! [Ammo: {green}{ammo}{reset}/{green}{max_ammo}{reset}] [Spare magazines: {blue}{chargers}{reset}]", - "reload_already_loaded": "{nick} > Your gun is {green}already loaded{reset}!", - "reload_no_chargers": "{nick} > You're out of {red}spare magazines{reset}!", - "reload_not_armed": "{nick} > You are {red}not armed{reset}.", - "shop_display": "DuckHunt Shop: {items} | You have {green}{xp} XP{reset}", - "shop_item_format": "({blue}{id}{reset}) {cyan}{name}{reset} - {green}{price} XP{reset}", - "help_header": "{blue}DuckHunt Commands:{reset}", - "help_user_commands": "{blue}!bang{reset} - Shoot at ducks | {blue}!bef{reset} - Befriend ducks | {blue}!reload{reset} - Reload your gun | {blue}!shop{reset} - View/buy from shop | {blue}!duckstats{reset} - View your stats and items | {blue}!use{reset} - Use inventory items", - "help_help_command": "{blue}!duckhelp{reset} - Show this help", - "help_admin_commands": "{red}Admin:{reset} {blue}!rearm {reset} | {blue}!disarm {reset} | {blue}!ignore {reset} | {blue}!unignore {reset} | {blue}!ducklaunch{reset}", - "admin_rearm_player": "[{red}ADMIN{reset}] {cyan}{target}{reset} has been rearmed by {blue}{admin}{reset}", - "admin_rearm_all": "[{red}ADMIN{reset}] All players have been rearmed by {blue}{admin}{reset}", - "admin_rearm_self": "[{red}ADMIN{reset}] {blue}{admin}{reset} has rearmed themselves", - "admin_disarm": "[{red}ADMIN{reset}] {cyan}{target}{reset} has been disarmed by {blue}{admin}{reset}", - "admin_ignore": "[{red}ADMIN{reset}] {cyan}{target}{reset} is now ignored by {blue}{admin}{reset}", - "admin_unignore": "[{red}ADMIN{reset}] {cyan}{target}{reset} is no longer ignored by {blue}{admin}{reset}", - "admin_ducklaunch": "[{red}ADMIN{reset}] A {cyan}duck{reset} has been launched by {blue}{admin}{reset}", - "admin_ducklaunch_not_enabled": "[{red}ADMIN{reset}] This channel is {red}not enabled{reset} for duckhunt", - "usage_rearm": "Usage: {blue}!rearm {reset}", - "usage_disarm": "Usage: {blue}!disarm {reset}", - "usage_ignore": "Usage: {blue}!ignore {reset}", - "usage_unignore": "Usage: {blue}!unignore {reset}", - "shop_buy_success": "{nick} > You bought {cyan}{item_name}{reset}! [-{red}{price} XP{reset}] [Remaining: {green}{remaining_xp} XP{reset}]", - "shop_buy_insufficient_xp": "{nick} > You don't have enough {red}XP{reset} to buy {cyan}{item_name}{reset}. Need {red}{price} XP{reset}, you have {green}{current_xp} XP{reset}.", - "shop_buy_invalid_id": "{nick} > {red}Invalid item ID{reset}. Use {blue}!shop{reset} to see available items.", - "shop_buy_usage": "Usage: {blue}!shop buy {reset}", + "duck_flies_away": "The duck flies away. ·°'`'°-.,¸¸.·°'`", + "fast_duck_flies_away": "The fast duck quickly flies away! ·°'`'°-.,¸¸.·°'`", + "golden_duck_flies_away": "The golden duck 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_no_duck": "{nick} > *BANG* What did you shoot at? There is no duck in the area... [GUN CONFISCATED]", + "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} > You are not armed.", + "bef_success": "{nick} > *befriend* You befriended the duck! [+{xp_gained} xp] [Ducks befriended: {ducks_befriended}]", + "bef_failed": "{nick} > *gentle approach* The duck doesn't trust you and flies away...", + "bef_no_duck": "{nick} > *gentle approach* There is no duck to befriend in the area...", + "bef_duck_shot": "{nick} > *gentle approach* The duck is already dead! You can't befriend it now...", + "reload_success": "{nick} > *click* New magazine loaded! [Ammo: {ammo}/{max_ammo}] [Spare magazines: {chargers}]", + "reload_already_loaded": "{nick} > Your gun is already loaded!", + "reload_no_chargers": "{nick} > You're out of spare magazines!", + "reload_not_armed": "{nick} > You are not armed.", + "shop_display": "DuckHunt Shop: {items} | You have {xp} XP", + "shop_item_format": "({id}) {name} - {price} XP", + "help_header": "DuckHunt Commands:", + "help_user_commands": "!bang - Shoot at ducks | !bef - Befriend ducks | !reload - Reload your gun | !shop - View/buy from shop | !duckstats - View your stats and items | !use - Use inventory items", + "help_help_command": "!duckhelp - Show this help", + "help_admin_commands": "Admin: !rearm | !disarm | !ignore | !unignore | !ducklaunch", + "admin_rearm_player": "[ADMIN] {target} has been rearmed by {admin}", + "admin_rearm_all": "[ADMIN] All players have been rearmed by {admin}", + "admin_rearm_self": "[ADMIN] {admin} has rearmed themselves", + "admin_disarm": "[ADMIN] {target} has been disarmed by {admin}", + "admin_ignore": "[ADMIN] {target} is now ignored by {admin}", + "admin_unignore": "[ADMIN] {target} is no longer ignored by {admin}", + "admin_ducklaunch": "[ADMIN] A duck has been launched by {admin}", + "admin_ducklaunch_not_enabled": "[ADMIN] This channel is not enabled for duckhunt", + "usage_rearm": "Usage: !rearm ", + "usage_disarm": "Usage: !disarm ", + "usage_ignore": "Usage: !ignore ", + "usage_unignore": "Usage: !unignore ", + "shop_buy_success": "{nick} > You bought {item_name}! [-{price} XP] [Remaining: {remaining_xp} XP]", + "shop_buy_insufficient_xp": "{nick} > You don't have enough XP to buy {item_name}. Need {price} XP, you have {current_xp} XP.", + "shop_buy_invalid_id": "{nick} > Invalid item ID. Use !shop to see available items.", + "shop_buy_usage": "Usage: !shop buy ", + "use_attract_ducks": "{nick} > You scattered bread around the pond! Ducks will spawn {spawn_multiplier}x faster for {duration} minutes.", "colours": { "white": "\u00030", diff --git a/shop.json b/shop.json index 75facc3..9cc4319 100644 --- a/shop.json +++ b/shop.json @@ -28,6 +28,14 @@ "description": "Clean your gun - decreases jam chance by 10%", "type": "clean_gun", "amount": -10 + }, + "5": { + "name": "Bread", + "price": 50, + "description": "Attract ducks - increases duck spawn rate for 20 minutes", + "type": "attract_ducks", + "duration": 1200, + "spawn_multiplier": 2.0 } } } \ No newline at end of file diff --git a/src/__pycache__/db.cpython-312.pyc b/src/__pycache__/db.cpython-312.pyc index 6dd9be3..1f8efe8 100644 Binary files a/src/__pycache__/db.cpython-312.pyc and b/src/__pycache__/db.cpython-312.pyc differ diff --git a/src/__pycache__/duckhuntbot.cpython-312.pyc b/src/__pycache__/duckhuntbot.cpython-312.pyc index b7c3112..b9f6ff8 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 bc0d237..6b86410 100644 Binary files a/src/__pycache__/game.cpython-312.pyc and b/src/__pycache__/game.cpython-312.pyc differ diff --git a/src/__pycache__/levels.cpython-312.pyc b/src/__pycache__/levels.cpython-312.pyc new file mode 100644 index 0000000..3f20233 Binary files /dev/null and b/src/__pycache__/levels.cpython-312.pyc differ diff --git a/src/__pycache__/shop.cpython-312.pyc b/src/__pycache__/shop.cpython-312.pyc new file mode 100644 index 0000000..3a2baf3 Binary files /dev/null and b/src/__pycache__/shop.cpython-312.pyc differ diff --git a/src/__pycache__/utils.cpython-312.pyc b/src/__pycache__/utils.cpython-312.pyc index 73f5132..ff7e9d9 100644 Binary files a/src/__pycache__/utils.cpython-312.pyc and b/src/__pycache__/utils.cpython-312.pyc differ diff --git a/src/db.py b/src/db.py index d97eb9a..47daebd 100644 --- a/src/db.py +++ b/src/db.py @@ -12,8 +12,9 @@ import os class DuckDB: """Simplified database management""" - def __init__(self, db_file="duckhunt.json"): + def __init__(self, db_file="duckhunt.json", bot=None): self.db_file = db_file + self.bot = bot self.players = {} self.logger = logging.getLogger('DuckHuntBot.DB') self.load_database() @@ -67,7 +68,9 @@ class DuckDB: sanitized['xp'] = max(0, int(player_data.get('xp', 0))) # Non-negative XP sanitized['ducks_shot'] = max(0, int(player_data.get('ducks_shot', 0))) sanitized['ducks_befriended'] = max(0, int(player_data.get('ducks_befriended', 0))) - sanitized['accuracy'] = max(0, min(100, int(player_data.get('accuracy', 65)))) # 0-100 range + default_accuracy = self.bot.get_config('default_accuracy', 75) if self.bot else 75 + max_accuracy = self.bot.get_config('max_accuracy', 100) if self.bot else 100 + sanitized['accuracy'] = max(0, min(max_accuracy, int(player_data.get('accuracy', default_accuracy)))) # 0-max_accuracy range sanitized['gun_confiscated'] = bool(player_data.get('gun_confiscated', False)) # Ammo system with validation @@ -209,23 +212,38 @@ class DuckDB: return self.create_player(nick) def create_player(self, nick): - """Create a new player with basic stats and validation""" + """Create a new player with configurable starting stats and inventory""" try: # Sanitize nick safe_nick = str(nick)[:50] if nick else 'Unknown' + # Get configurable defaults from bot config + if self.bot: + accuracy = self.bot.get_config('player_defaults.accuracy', 75) + magazines = self.bot.get_config('player_defaults.magazines', 3) + bullets_per_mag = self.bot.get_config('player_defaults.bullets_per_magazine', 6) + jam_chance = self.bot.get_config('player_defaults.jam_chance', 5) + xp = self.bot.get_config('player_defaults.xp', 0) + else: + # Fallback defaults if no bot config available + accuracy = 75 + magazines = 3 + bullets_per_mag = 6 + jam_chance = 5 + xp = 0 + return { 'nick': safe_nick, - 'xp': 0, + 'xp': xp, 'ducks_shot': 0, 'ducks_befriended': 0, - 'current_ammo': 6, # Bullets in current magazine - 'magazines': 3, # Total magazines (including current) - 'bullets_per_magazine': 6, # Bullets per magazine - 'accuracy': 65, - 'jam_chance': 5, # 5% base gun jamming chance + 'current_ammo': bullets_per_mag, # Bullets in current magazine + 'magazines': magazines, # Total magazines (including current) + 'bullets_per_magazine': bullets_per_mag, # Bullets per magazine + 'accuracy': accuracy, # Starting accuracy from config + 'jam_chance': jam_chance, # Base gun jamming chance from config 'gun_confiscated': False, - 'inventory': {}, # {item_id: quantity} + 'inventory': {}, # Empty starting inventory 'temporary_effects': [] # List of temporary effects } except Exception as e: @@ -238,7 +256,7 @@ class DuckDB: 'current_ammo': 6, 'magazines': 3, 'bullets_per_magazine': 6, - 'accuracy': 65, + 'accuracy': 75, 'jam_chance': 5, 'gun_confiscated': False, 'inventory': {}, diff --git a/src/duckhuntbot.py b/src/duckhuntbot.py index 6aa1bf1..4dca482 100644 --- a/src/duckhuntbot.py +++ b/src/duckhuntbot.py @@ -26,7 +26,7 @@ class DuckHuntBot: self.channels_joined = set() self.shutdown_requested = False - self.db = DuckDB() + self.db = DuckDB(bot=self) self.game = DuckGame(self, self.db) self.messages = MessageManager() @@ -97,8 +97,8 @@ class DuckHuntBot: async def connect(self): """Connect to IRC server with comprehensive error handling""" - max_retries = 3 - retry_delay = 5 + max_retries = self.get_config('connection.max_retries', 3) or 3 + retry_delay = self.get_config('connection.retry_delay', 5) or 5 for attempt in range(max_retries): try: @@ -117,7 +117,7 @@ class DuckHuntBot: self.config['port'], ssl=ssl_context ), - timeout=30.0 # 30 second connection timeout + timeout=self.get_config('connection.timeout', 30) or 30.0 # Connection timeout from config ) self.logger.info(f"✅ Successfully connected to {self.config['server']}:{self.config['port']}") @@ -454,7 +454,7 @@ class DuckHuntBot: xp = player.get('xp', 0) ducks_shot = player.get('ducks_shot', 0) ducks_befriended = player.get('ducks_befriended', 0) - accuracy = player.get('accuracy', 65) + accuracy = player.get('accuracy', self.get_config('player_defaults.accuracy', 75)) # Ammo info current_ammo = player.get('current_ammo', 0) @@ -531,13 +531,24 @@ class DuckHuntBot: if not result["success"]: message = f"{nick} > {result['message']}" else: - if result.get("target_affected"): + # Handle specific item effect messages + effect = result.get('effect', {}) + effect_type = effect.get('type', '') + + if effect_type == 'attract_ducks': + # Use specific message for bread + message = self.messages.get('use_attract_ducks', + nick=nick, + spawn_multiplier=effect.get('spawn_multiplier', 2.0), + duration=effect.get('duration', 10) + ) + elif result.get("target_affected"): message = f"{nick} > Used {result['item_name']} on {target_nick}!" else: message = f"{nick} > Used {result['item_name']}!" - # Add remaining count if any - if result.get("remaining_in_inventory", 0) > 0: + # Add remaining count if any (not for bread message which has its own format) + if effect_type != 'attract_ducks' and result.get("remaining_in_inventory", 0) > 0: message += f" ({result['remaining_in_inventory']} remaining)" self.send_message(channel, message) diff --git a/src/game.py b/src/game.py index 3e94ea3..06c3f5f 100644 --- a/src/game.py +++ b/src/game.py @@ -35,8 +35,16 @@ class DuckGame: try: while True: # Wait random time between spawns, but in small chunks for responsiveness - min_wait = self.bot.get_config('duck_spawn_min', 300) # 5 minutes - max_wait = self.bot.get_config('duck_spawn_max', 900) # 15 minutes + min_wait = self.bot.get_config('duck_spawning.spawn_min', 300) # 5 minutes + max_wait = self.bot.get_config('duck_spawning.spawn_max', 900) # 15 minutes + + # Check for active bread effects to modify spawn timing + spawn_multiplier = self._get_active_spawn_multiplier() + if spawn_multiplier > 1.0: + # Reduce wait time when bread is active + min_wait = int(min_wait / spawn_multiplier) + max_wait = int(max_wait / spawn_multiplier) + wait_time = random.randint(min_wait, max_wait) # Sleep in 1-second intervals to allow for quick cancellation @@ -65,12 +73,9 @@ class DuckGame: for channel, ducks in self.ducks.items(): ducks_to_remove = [] for duck in ducks: - # Different timeouts for different duck types + # Get timeout for each duck type from config duck_type = duck.get('duck_type', 'normal') - if duck_type == 'fast': - timeout = self.bot.get_config('fast_duck_timeout', 30) - else: - timeout = self.bot.get_config('duck_timeout', 60) + timeout = self.bot.get_config(f'duck_types.{duck_type}.timeout', 60) if current_time - duck['spawn_time'] > timeout: ducks_to_remove.append(duck) @@ -94,6 +99,9 @@ class DuckGame: for channel in channels_to_clear: if channel in self.ducks and not self.ducks[channel]: del self.ducks[channel] + + # Clean expired effects every loop iteration + self._clean_expired_effects() except asyncio.CancelledError: self.logger.info("Duck timeout loop cancelled") @@ -175,8 +183,8 @@ class DuckGame: 'message_args': {'nick': nick} } - # Check for gun jamming - jam_chance = player.get('jam_chance', 5) / 100.0 # Convert percentage to decimal + # Check for gun jamming using level-based jam chance + jam_chance = self.bot.levels.get_jam_chance(player) / 100.0 # Convert percentage to decimal if random.random() < jam_chance: # Gun jammed! Use ammo but don't shoot player['current_ammo'] = player.get('current_ammo', 1) - 1 @@ -216,7 +224,9 @@ class DuckGame: if duck['current_hp'] > 0: # Still alive, reveal it's golden but don't remove - player['accuracy'] = min(player.get('accuracy', 65) + 1, 100) + accuracy_gain = self.bot.get_config('accuracy_gain_on_hit', 1) + max_accuracy = self.bot.get_config('max_accuracy', 100) + player['accuracy'] = min(player.get('accuracy', self.bot.get_config('default_accuracy', 75)) + accuracy_gain, max_accuracy) self.db.save_database() return { 'success': True, @@ -241,14 +251,16 @@ class DuckGame: else: # Normal duck self.ducks[channel].pop(0) - xp_gained = 10 + xp_gained = self.bot.get_config('normal_duck_xp', 10) message_key = 'bang_hit' # Apply XP and level changes old_level = self.bot.levels.calculate_player_level(player) player['xp'] = player.get('xp', 0) + xp_gained player['ducks_shot'] = player.get('ducks_shot', 0) + 1 - player['accuracy'] = min(player.get('accuracy', 65) + 1, 100) + accuracy_gain = self.bot.get_config('accuracy_gain_on_hit', 1) + max_accuracy = self.bot.get_config('max_accuracy', 100) + player['accuracy'] = min(player.get('accuracy', self.bot.get_config('default_accuracy', 75)) + accuracy_gain, max_accuracy) # Check if player leveled up and update magazines if needed new_level = self.bot.levels.calculate_player_level(player) @@ -272,7 +284,9 @@ class DuckGame: } else: # Miss! Duck stays in the channel - player['accuracy'] = max(player.get('accuracy', 65) - 2, 10) + accuracy_loss = self.bot.get_config('accuracy_loss_on_miss', 2) + min_accuracy = self.bot.get_config('min_accuracy', 10) + player['accuracy'] = max(player.get('accuracy', self.bot.get_config('default_accuracy', 75)) - accuracy_loss, min_accuracy) self.db.save_database() return { 'success': True, @@ -309,8 +323,8 @@ class DuckGame: # Success - befriend the duck duck = self.ducks[channel].pop(0) - # Lower XP gain than shooting (5 instead of 10) - xp_gained = 5 + # Lower XP gain than shooting + xp_gained = self.bot.get_config('befriend_duck_xp', 5) old_level = self.bot.levels.calculate_player_level(player) player['xp'] = player.get('xp', 0) + xp_gained player['ducks_befriended'] = player.get('ducks_befriended', 0) + 1 @@ -407,4 +421,45 @@ class DuckGame: if rearmed_count > 0: self.logger.info(f"Auto-rearmed {rearmed_count} disarmed players after duck shot") except Exception as e: - self.logger.error(f"Error in _rearm_all_disarmed_players: {e}") \ No newline at end of file + self.logger.error(f"Error in _rearm_all_disarmed_players: {e}") + + def _get_active_spawn_multiplier(self): + """Get the current spawn rate multiplier from active bread effects""" + import time + max_multiplier = 1.0 + current_time = time.time() + + try: + for player_name, player_data in self.db.players.items(): + effects = player_data.get('temporary_effects', []) + for effect in effects: + if (effect.get('type') == 'attract_ducks' and + effect.get('expires_at', 0) > current_time): + multiplier = effect.get('spawn_multiplier', 1.0) + max_multiplier = max(max_multiplier, multiplier) + + return max_multiplier + except Exception as e: + self.logger.error(f"Error getting spawn multiplier: {e}") + return 1.0 + + def _clean_expired_effects(self): + """Remove expired temporary effects from all players""" + import time + current_time = time.time() + + try: + for player_name, player_data in self.db.players.items(): + effects = player_data.get('temporary_effects', []) + active_effects = [] + + for effect in effects: + if effect.get('expires_at', 0) > current_time: + active_effects.append(effect) + + if len(active_effects) != len(effects): + player_data['temporary_effects'] = active_effects + 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 diff --git a/src/levels.py b/src/levels.py index d1c1432..ed55b97 100644 --- a/src/levels.py +++ b/src/levels.py @@ -141,7 +141,7 @@ class LevelManager: def get_modified_accuracy(self, player: Dict[str, Any]) -> int: """Get player's accuracy modified by their level""" - base_accuracy = player.get('accuracy', 65) + base_accuracy = player.get('accuracy', 75) # This will be updated by bot config in create_player level_info = self.get_player_level_info(player) modifier = level_info.get('accuracy_modifier', 0) @@ -154,9 +154,20 @@ class LevelManager: level_info = self.get_player_level_info(player) level_rate = level_info.get('befriend_success_rate', base_rate) - # Return as percentage (0-100) + # Return as percentage (0-100) - these will be configurable later if bot reference is available return max(5.0, min(95.0, level_rate)) + def get_jam_chance(self, player: Dict[str, Any]) -> float: + """Get player's gun jam chance based on their level""" + level_info = self.get_player_level_info(player) + level_data = self.get_level_data(level_info['level']) + + if level_data and 'jam_chance' in level_data: + return level_data['jam_chance'] + + # Fallback to old system if no level-specific jam chance + return player.get('jam_chance', 5) + 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: diff --git a/src/shop.py b/src/shop.py index a6163c5..f5e7626 100644 --- a/src/shop.py +++ b/src/shop.py @@ -178,7 +178,7 @@ class ShopManager: elif item_type == 'accuracy': # Increase accuracy up to 100% - current_accuracy = player.get('accuracy', 65) + current_accuracy = player.get('accuracy', 75) new_accuracy = min(current_accuracy + amount, 100) player['accuracy'] = new_accuracy return { @@ -279,7 +279,7 @@ class ShopManager: elif item_type == 'sabotage_accuracy': # Reduce target's accuracy temporarily - current_acc = player.get('accuracy', 65) + current_acc = player.get('accuracy', 75) new_acc = max(current_acc + amount, 10) # Min 10% accuracy (amount is negative) player['accuracy'] = new_acc @@ -325,6 +325,27 @@ class ShopManager: "new_total": new_jam } + elif item_type == 'attract_ducks': + # Add bread effect to increase duck spawn rate + if 'temporary_effects' not in player: + player['temporary_effects'] = [] + + duration = item.get('duration', 600) # 10 minutes default + spawn_multiplier = item.get('spawn_multiplier', 2.0) # 2x spawn rate default + + effect = { + 'type': 'attract_ducks', + 'spawn_multiplier': spawn_multiplier, + 'expires_at': time.time() + duration + } + player['temporary_effects'].append(effect) + + return { + "type": "attract_ducks", + "spawn_multiplier": spawn_multiplier, + "duration": duration // 60 # return duration in minutes + } + else: self.logger.warning(f"Unknown item type: {item_type}") return {"type": "unknown", "message": f"Unknown effect type: {item_type}"}