Restructure config.json with nested hierarchy and dot notation access
- Reorganized config.json into logical sections: connection, duck_spawning, duck_types, player_defaults, gameplay, features, limits - Enhanced get_config() method to support dot notation (e.g., 'duck_types.normal.xp') - Added comprehensive configurable parameters for all game mechanics - Updated player creation to use configurable starting values - Added individual timeout settings per duck type - Made XP rewards, accuracy mechanics, and game limits fully configurable - Fixed syntax errors in duck_spawn_loop function
This commit is contained in:
74
.gitignore
vendored
74
.gitignore
vendored
@@ -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/
|
||||
BIN
__pycache__/duckhunt.cpython-312.pyc
Normal file
BIN
__pycache__/duckhunt.cpython-312.pyc
Normal file
Binary file not shown.
83
config.json
83
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
|
||||
}
|
||||
}
|
||||
11
duckhunt.log
11
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
|
||||
|
||||
34
levels.json
34
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,
|
||||
|
||||
@@ -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 <player>{reset} | {blue}!disarm <player>{reset} | {blue}!ignore <player>{reset} | {blue}!unignore <player>{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 <player>{reset}",
|
||||
"usage_disarm": "Usage: {blue}!disarm <player>{reset}",
|
||||
"usage_ignore": "Usage: {blue}!ignore <player>{reset}",
|
||||
"usage_unignore": "Usage: {blue}!unignore <player>{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 <item_id>{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 <player> | !disarm <player> | !ignore <player> | !unignore <player> | !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 <player>",
|
||||
"usage_disarm": "Usage: !disarm <player>",
|
||||
"usage_ignore": "Usage: !ignore <player>",
|
||||
"usage_unignore": "Usage: !unignore <player>",
|
||||
"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 <item_id>",
|
||||
"use_attract_ducks": "{nick} > You scattered bread around the pond! Ducks will spawn {spawn_multiplier}x faster for {duration} minutes.",
|
||||
|
||||
"colours": {
|
||||
"white": "\u00030",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
src/__pycache__/levels.cpython-312.pyc
Normal file
BIN
src/__pycache__/levels.cpython-312.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/shop.cpython-312.pyc
Normal file
BIN
src/__pycache__/shop.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
40
src/db.py
40
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': {},
|
||||
|
||||
@@ -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)
|
||||
|
||||
87
src/game.py
87
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}")
|
||||
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}")
|
||||
@@ -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:
|
||||
|
||||
25
src/shop.py
25
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}"}
|
||||
|
||||
Reference in New Issue
Block a user