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.
73
config.json
73
config.json
@@ -1,30 +1,77 @@
|
|||||||
{
|
{
|
||||||
|
"connection": {
|
||||||
"server": "irc.rizon.net",
|
"server": "irc.rizon.net",
|
||||||
"port": 6697,
|
"port": 6697,
|
||||||
"nick": "DickHunt",
|
"nick": "DickHunt",
|
||||||
"channels": ["#ct"],
|
"channels": ["#ct"],
|
||||||
"ssl": true,
|
"ssl": true,
|
||||||
|
"password": "your_iline_password_here",
|
||||||
|
"max_retries": 3,
|
||||||
|
"retry_delay": 5,
|
||||||
|
"timeout": 30
|
||||||
|
},
|
||||||
|
|
||||||
"sasl": {
|
"sasl": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"username": "duckhunt",
|
"username": "duckhunt",
|
||||||
"password": "duckhunt//789//"
|
"password": "duckhunt//789//"
|
||||||
},
|
},
|
||||||
"password": "your_iline_password_here",
|
|
||||||
"admins": ["peorth", "computertech", "colby"],
|
"admins": ["peorth", "computertech", "colby"],
|
||||||
|
|
||||||
"duck_spawn_min": 10,
|
"duck_spawning": {
|
||||||
"duck_spawn_max": 30,
|
"spawn_min": 10,
|
||||||
"duck_timeout": 60,
|
"spawn_max": 30,
|
||||||
|
"timeout": 60,
|
||||||
|
"rearm_on_duck_shot": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"player_defaults": {
|
||||||
|
"accuracy": 75,
|
||||||
|
"magazines": 3,
|
||||||
|
"bullets_per_magazine": 6,
|
||||||
|
"jam_chance": 5,
|
||||||
|
"xp": 0
|
||||||
|
},
|
||||||
|
|
||||||
|
"gameplay": {
|
||||||
"befriend_success_rate": 75,
|
"befriend_success_rate": 75,
|
||||||
"rearm_on_duck_shot": true,
|
"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
|
||||||
|
},
|
||||||
|
|
||||||
"golden_duck_chance": 0.15,
|
"features": {
|
||||||
"golden_duck_min_hp": 3,
|
"shop_enabled": true,
|
||||||
"golden_duck_max_hp": 5,
|
"inventory_enabled": true,
|
||||||
"golden_duck_xp": 15,
|
"auto_rearm_enabled": true
|
||||||
|
},
|
||||||
"fast_duck_chance": 0.25,
|
|
||||||
"fast_duck_timeout": 30,
|
|
||||||
"fast_duck_xp": 12
|
|
||||||
|
|
||||||
|
"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,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 - _close_connection:559: 🔌 IRC connection closed
|
||||||
2025-09-23 20:12:39,766 [INFO ] DuckHuntBot - run:542: ✅ Bot shutdown complete
|
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",
|
"name": "Duck Novice",
|
||||||
"min_xp": 0,
|
"min_xp": 0,
|
||||||
"max_xp": 49,
|
"max_xp": 49,
|
||||||
"befriend_success_rate": 85,
|
"befriend_success_rate": 95,
|
||||||
"accuracy_modifier": 5,
|
"accuracy_modifier": 25,
|
||||||
|
"jam_chance": 0,
|
||||||
"duck_spawn_speed_modifier": 1.0,
|
"duck_spawn_speed_modifier": 1.0,
|
||||||
"magazines": 3,
|
"magazines": 3,
|
||||||
"bullets_per_magazine": 6,
|
"bullets_per_magazine": 6,
|
||||||
@@ -19,8 +20,9 @@
|
|||||||
"name": "Pond Visitor",
|
"name": "Pond Visitor",
|
||||||
"min_xp": 50,
|
"min_xp": 50,
|
||||||
"max_xp": 149,
|
"max_xp": 149,
|
||||||
"befriend_success_rate": 80,
|
"befriend_success_rate": 85,
|
||||||
"accuracy_modifier": 0,
|
"accuracy_modifier": 15,
|
||||||
|
"jam_chance": 1,
|
||||||
"duck_spawn_speed_modifier": 1.0,
|
"duck_spawn_speed_modifier": 1.0,
|
||||||
"magazines": 3,
|
"magazines": 3,
|
||||||
"bullets_per_magazine": 6,
|
"bullets_per_magazine": 6,
|
||||||
@@ -30,10 +32,11 @@
|
|||||||
"name": "Duck Hunter",
|
"name": "Duck Hunter",
|
||||||
"min_xp": 150,
|
"min_xp": 150,
|
||||||
"max_xp": 299,
|
"max_xp": 299,
|
||||||
"befriend_success_rate": 75,
|
"befriend_success_rate": 80,
|
||||||
"accuracy_modifier": -5,
|
"accuracy_modifier": 5,
|
||||||
|
"jam_chance": 2,
|
||||||
"duck_spawn_speed_modifier": 0.9,
|
"duck_spawn_speed_modifier": 0.9,
|
||||||
"magazines": 2,
|
"magazines": 3,
|
||||||
"bullets_per_magazine": 6,
|
"bullets_per_magazine": 6,
|
||||||
"description": "Your reputation precedes you, ducks are more cautious"
|
"description": "Your reputation precedes you, ducks are more cautious"
|
||||||
},
|
},
|
||||||
@@ -41,8 +44,9 @@
|
|||||||
"name": "Wetland Stalker",
|
"name": "Wetland Stalker",
|
||||||
"min_xp": 300,
|
"min_xp": 300,
|
||||||
"max_xp": 599,
|
"max_xp": 599,
|
||||||
"befriend_success_rate": 70,
|
"befriend_success_rate": 75,
|
||||||
"accuracy_modifier": -10,
|
"accuracy_modifier": -5,
|
||||||
|
"jam_chance": 3,
|
||||||
"duck_spawn_speed_modifier": 0.8,
|
"duck_spawn_speed_modifier": 0.8,
|
||||||
"magazines": 2,
|
"magazines": 2,
|
||||||
"bullets_per_magazine": 6,
|
"bullets_per_magazine": 6,
|
||||||
@@ -52,8 +56,9 @@
|
|||||||
"name": "Apex Predator",
|
"name": "Apex Predator",
|
||||||
"min_xp": 600,
|
"min_xp": 600,
|
||||||
"max_xp": 999,
|
"max_xp": 999,
|
||||||
"befriend_success_rate": 65,
|
"befriend_success_rate": 70,
|
||||||
"accuracy_modifier": -15,
|
"accuracy_modifier": -15,
|
||||||
|
"jam_chance": 4,
|
||||||
"duck_spawn_speed_modifier": 0.7,
|
"duck_spawn_speed_modifier": 0.7,
|
||||||
"magazines": 2,
|
"magazines": 2,
|
||||||
"bullets_per_magazine": 6,
|
"bullets_per_magazine": 6,
|
||||||
@@ -63,8 +68,9 @@
|
|||||||
"name": "Duck Whisperer",
|
"name": "Duck Whisperer",
|
||||||
"min_xp": 1000,
|
"min_xp": 1000,
|
||||||
"max_xp": 1999,
|
"max_xp": 1999,
|
||||||
"befriend_success_rate": 60,
|
"befriend_success_rate": 65,
|
||||||
"accuracy_modifier": -20,
|
"accuracy_modifier": -20,
|
||||||
|
"jam_chance": 5,
|
||||||
"duck_spawn_speed_modifier": 0.6,
|
"duck_spawn_speed_modifier": 0.6,
|
||||||
"magazines": 1,
|
"magazines": 1,
|
||||||
"bullets_per_magazine": 6,
|
"bullets_per_magazine": 6,
|
||||||
@@ -74,8 +80,9 @@
|
|||||||
"name": "Legendary Hunter",
|
"name": "Legendary Hunter",
|
||||||
"min_xp": 2000,
|
"min_xp": 2000,
|
||||||
"max_xp": 4999,
|
"max_xp": 4999,
|
||||||
"befriend_success_rate": 55,
|
"befriend_success_rate": 60,
|
||||||
"accuracy_modifier": -25,
|
"accuracy_modifier": -25,
|
||||||
|
"jam_chance": 6,
|
||||||
"duck_spawn_speed_modifier": 0.5,
|
"duck_spawn_speed_modifier": 0.5,
|
||||||
"magazines": 1,
|
"magazines": 1,
|
||||||
"bullets_per_magazine": 6,
|
"bullets_per_magazine": 6,
|
||||||
@@ -85,8 +92,9 @@
|
|||||||
"name": "Duck Deity",
|
"name": "Duck Deity",
|
||||||
"min_xp": 5000,
|
"min_xp": 5000,
|
||||||
"max_xp": 999999,
|
"max_xp": 999999,
|
||||||
"befriend_success_rate": 50,
|
"befriend_success_rate": 55,
|
||||||
"accuracy_modifier": -30,
|
"accuracy_modifier": -30,
|
||||||
|
"jam_chance": 8,
|
||||||
"duck_spawn_speed_modifier": 0.4,
|
"duck_spawn_speed_modifier": 0.4,
|
||||||
"magazines": 1,
|
"magazines": 1,
|
||||||
"bullets_per_magazine": 6,
|
"bullets_per_magazine": 6,
|
||||||
|
|||||||
@@ -1,51 +1,52 @@
|
|||||||
{
|
{
|
||||||
"duck_spawn": [
|
"duck_spawn": [
|
||||||
"・゜゜・。。・゜゜\\_O< {bold}QUACK!{reset}",
|
"・゜゜・。。・゜゜\\_O< QUACK!",
|
||||||
"・゜゜・。。・゜゜\\_o< {light_grey}quack~{reset}",
|
"・゜゜・。。・゜゜\\_o< quack~",
|
||||||
"・゜゜・。。・゜゜\\_O> {bold}*flap flap*{reset}"
|
"・゜゜・。。・゜゜\\_O> *flap flap*"
|
||||||
],
|
],
|
||||||
"duck_flies_away": "The {bold}duck{reset} flies away. ·°'`'°-.,¸¸.·°'`",
|
"duck_flies_away": "The duck flies away. ·°'`'°-.,¸¸.·°'`",
|
||||||
"fast_duck_flies_away": "The {cyan}fast duck{reset} quickly flies away! ·°'`'°-.,¸¸.·°'`",
|
"fast_duck_flies_away": "The fast duck quickly flies away! ·°'`'°-.,¸¸.·°'`",
|
||||||
"golden_duck_flies_away": "The {yellow}{bold}golden duck{reset} flies away majestically. ·°'`'°-.,¸¸.·°'`",
|
"golden_duck_flies_away": "The golden duck 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": "{nick} > *BANG* You shot the duck! [+{xp_gained} xp] [Total ducks: {ducks_shot}]",
|
||||||
"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": "{nick} > *BANG* You shot a GOLDEN DUCK! [{hp_remaining} HP remaining] [+{xp_gained} xp]",
|
||||||
"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_golden_killed": "{nick} > *BANG* You killed the GOLDEN DUCK! [+{xp_gained} xp] [Total ducks: {ducks_shot}]",
|
||||||
"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_hit_fast": "{nick} > *BANG* You shot a FAST DUCK! [+{xp_gained} xp] [Total ducks: {ducks_shot}]",
|
||||||
"bang_miss": "{nick} > {red}*BANG*{reset} You missed the {cyan}duck{reset}!",
|
"bang_miss": "{nick} > *BANG* You missed the duck!",
|
||||||
"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_duck": "{nick} > *BANG* What did you shoot at? There is no duck in the area... [GUN CONFISCATED]",
|
||||||
"bang_no_ammo": "{nick} > {orange}*click*{reset} You're out of ammo! Use {blue}!reload{reset}",
|
"bang_no_ammo": "{nick} > *click* You're out of ammo! Use !reload",
|
||||||
"bang_gun_jammed": "{nick} > {red}*click*{reset} Your gun jammed! [{red}AMMO WASTED{reset}]",
|
"bang_gun_jammed": "{nick} > *click* Your gun jammed! [AMMO WASTED]",
|
||||||
"bang_not_armed": "{nick} > You are {red}not armed{reset}.",
|
"bang_not_armed": "{nick} > You are not armed.",
|
||||||
"bef_success": "{nick} > {orange}*befriend*{reset} You befriended the duck! [{green}+{xp_gained} xp{reset}] [Ducks befriended: {bold}{ducks_befriended}{reset}]",
|
"bef_success": "{nick} > *befriend* You befriended the duck! [+{xp_gained} xp] [Ducks befriended: {ducks_befriended}]",
|
||||||
"bef_failed": "{nick} > {pink}*gentle approach*{reset} The {cyan}duck{reset} doesn't trust you and {yellow}flies away{reset}...",
|
"bef_failed": "{nick} > *gentle approach* The duck doesn't trust you and flies away...",
|
||||||
"bef_no_duck": "{nick} > {pink}*gentle approach*{reset} There is {red}no duck{reset} to befriend in the area...",
|
"bef_no_duck": "{nick} > *gentle approach* There is no duck 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...",
|
"bef_duck_shot": "{nick} > *gentle approach* The duck is already dead! 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_success": "{nick} > *click* New magazine loaded! [Ammo: {ammo}/{max_ammo}] [Spare magazines: {chargers}]",
|
||||||
"reload_already_loaded": "{nick} > Your gun is {green}already loaded{reset}!",
|
"reload_already_loaded": "{nick} > Your gun is already loaded!",
|
||||||
"reload_no_chargers": "{nick} > You're out of {red}spare magazines{reset}!",
|
"reload_no_chargers": "{nick} > You're out of spare magazines!",
|
||||||
"reload_not_armed": "{nick} > You are {red}not armed{reset}.",
|
"reload_not_armed": "{nick} > You are not armed.",
|
||||||
"shop_display": "DuckHunt Shop: {items} | You have {green}{xp} XP{reset}",
|
"shop_display": "DuckHunt Shop: {items} | You have {xp} XP",
|
||||||
"shop_item_format": "({blue}{id}{reset}) {cyan}{name}{reset} - {green}{price} XP{reset}",
|
"shop_item_format": "({id}) {name} - {price} XP",
|
||||||
"help_header": "{blue}DuckHunt Commands:{reset}",
|
"help_header": "DuckHunt Commands:",
|
||||||
"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_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": "{blue}!duckhelp{reset} - Show this help",
|
"help_help_command": "!duckhelp - 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}",
|
"help_admin_commands": "Admin: !rearm <player> | !disarm <player> | !ignore <player> | !unignore <player> | !ducklaunch",
|
||||||
"admin_rearm_player": "[{red}ADMIN{reset}] {cyan}{target}{reset} has been rearmed by {blue}{admin}{reset}",
|
"admin_rearm_player": "[ADMIN] {target} has been rearmed by {admin}",
|
||||||
"admin_rearm_all": "[{red}ADMIN{reset}] All players have been rearmed by {blue}{admin}{reset}",
|
"admin_rearm_all": "[ADMIN] All players have been rearmed by {admin}",
|
||||||
"admin_rearm_self": "[{red}ADMIN{reset}] {blue}{admin}{reset} has rearmed themselves",
|
"admin_rearm_self": "[ADMIN] {admin} has rearmed themselves",
|
||||||
"admin_disarm": "[{red}ADMIN{reset}] {cyan}{target}{reset} has been disarmed by {blue}{admin}{reset}",
|
"admin_disarm": "[ADMIN] {target} has been disarmed by {admin}",
|
||||||
"admin_ignore": "[{red}ADMIN{reset}] {cyan}{target}{reset} is now ignored by {blue}{admin}{reset}",
|
"admin_ignore": "[ADMIN] {target} is now ignored by {admin}",
|
||||||
"admin_unignore": "[{red}ADMIN{reset}] {cyan}{target}{reset} is no longer ignored by {blue}{admin}{reset}",
|
"admin_unignore": "[ADMIN] {target} is no longer ignored by {admin}",
|
||||||
"admin_ducklaunch": "[{red}ADMIN{reset}] A {cyan}duck{reset} has been launched by {blue}{admin}{reset}",
|
"admin_ducklaunch": "[ADMIN] A duck has been launched by {admin}",
|
||||||
"admin_ducklaunch_not_enabled": "[{red}ADMIN{reset}] This channel is {red}not enabled{reset} for duckhunt",
|
"admin_ducklaunch_not_enabled": "[ADMIN] This channel is not enabled for duckhunt",
|
||||||
"usage_rearm": "Usage: {blue}!rearm <player>{reset}",
|
"usage_rearm": "Usage: !rearm <player>",
|
||||||
"usage_disarm": "Usage: {blue}!disarm <player>{reset}",
|
"usage_disarm": "Usage: !disarm <player>",
|
||||||
"usage_ignore": "Usage: {blue}!ignore <player>{reset}",
|
"usage_ignore": "Usage: !ignore <player>",
|
||||||
"usage_unignore": "Usage: {blue}!unignore <player>{reset}",
|
"usage_unignore": "Usage: !unignore <player>",
|
||||||
"shop_buy_success": "{nick} > You bought {cyan}{item_name}{reset}! [-{red}{price} XP{reset}] [Remaining: {green}{remaining_xp} XP{reset}]",
|
"shop_buy_success": "{nick} > You bought {item_name}! [-{price} XP] [Remaining: {remaining_xp} XP]",
|
||||||
"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_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} > {red}Invalid item ID{reset}. Use {blue}!shop{reset} to see available items.",
|
"shop_buy_invalid_id": "{nick} > Invalid item ID. Use !shop to see available items.",
|
||||||
"shop_buy_usage": "Usage: {blue}!shop buy <item_id>{reset}",
|
"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": {
|
"colours": {
|
||||||
"white": "\u00030",
|
"white": "\u00030",
|
||||||
|
|||||||
@@ -28,6 +28,14 @@
|
|||||||
"description": "Clean your gun - decreases jam chance by 10%",
|
"description": "Clean your gun - decreases jam chance by 10%",
|
||||||
"type": "clean_gun",
|
"type": "clean_gun",
|
||||||
"amount": -10
|
"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:
|
class DuckDB:
|
||||||
"""Simplified database management"""
|
"""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.db_file = db_file
|
||||||
|
self.bot = bot
|
||||||
self.players = {}
|
self.players = {}
|
||||||
self.logger = logging.getLogger('DuckHuntBot.DB')
|
self.logger = logging.getLogger('DuckHuntBot.DB')
|
||||||
self.load_database()
|
self.load_database()
|
||||||
@@ -67,7 +68,9 @@ class DuckDB:
|
|||||||
sanitized['xp'] = max(0, int(player_data.get('xp', 0))) # Non-negative XP
|
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_shot'] = max(0, int(player_data.get('ducks_shot', 0)))
|
||||||
sanitized['ducks_befriended'] = max(0, int(player_data.get('ducks_befriended', 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))
|
sanitized['gun_confiscated'] = bool(player_data.get('gun_confiscated', False))
|
||||||
|
|
||||||
# Ammo system with validation
|
# Ammo system with validation
|
||||||
@@ -209,23 +212,38 @@ class DuckDB:
|
|||||||
return self.create_player(nick)
|
return self.create_player(nick)
|
||||||
|
|
||||||
def create_player(self, 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:
|
try:
|
||||||
# Sanitize nick
|
# Sanitize nick
|
||||||
safe_nick = str(nick)[:50] if nick else 'Unknown'
|
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 {
|
return {
|
||||||
'nick': safe_nick,
|
'nick': safe_nick,
|
||||||
'xp': 0,
|
'xp': xp,
|
||||||
'ducks_shot': 0,
|
'ducks_shot': 0,
|
||||||
'ducks_befriended': 0,
|
'ducks_befriended': 0,
|
||||||
'current_ammo': 6, # Bullets in current magazine
|
'current_ammo': bullets_per_mag, # Bullets in current magazine
|
||||||
'magazines': 3, # Total magazines (including current)
|
'magazines': magazines, # Total magazines (including current)
|
||||||
'bullets_per_magazine': 6, # Bullets per magazine
|
'bullets_per_magazine': bullets_per_mag, # Bullets per magazine
|
||||||
'accuracy': 65,
|
'accuracy': accuracy, # Starting accuracy from config
|
||||||
'jam_chance': 5, # 5% base gun jamming chance
|
'jam_chance': jam_chance, # Base gun jamming chance from config
|
||||||
'gun_confiscated': False,
|
'gun_confiscated': False,
|
||||||
'inventory': {}, # {item_id: quantity}
|
'inventory': {}, # Empty starting inventory
|
||||||
'temporary_effects': [] # List of temporary effects
|
'temporary_effects': [] # List of temporary effects
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -238,7 +256,7 @@ class DuckDB:
|
|||||||
'current_ammo': 6,
|
'current_ammo': 6,
|
||||||
'magazines': 3,
|
'magazines': 3,
|
||||||
'bullets_per_magazine': 6,
|
'bullets_per_magazine': 6,
|
||||||
'accuracy': 65,
|
'accuracy': 75,
|
||||||
'jam_chance': 5,
|
'jam_chance': 5,
|
||||||
'gun_confiscated': False,
|
'gun_confiscated': False,
|
||||||
'inventory': {},
|
'inventory': {},
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class DuckHuntBot:
|
|||||||
self.channels_joined = set()
|
self.channels_joined = set()
|
||||||
self.shutdown_requested = False
|
self.shutdown_requested = False
|
||||||
|
|
||||||
self.db = DuckDB()
|
self.db = DuckDB(bot=self)
|
||||||
self.game = DuckGame(self, self.db)
|
self.game = DuckGame(self, self.db)
|
||||||
self.messages = MessageManager()
|
self.messages = MessageManager()
|
||||||
|
|
||||||
@@ -97,8 +97,8 @@ class DuckHuntBot:
|
|||||||
|
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
"""Connect to IRC server with comprehensive error handling"""
|
"""Connect to IRC server with comprehensive error handling"""
|
||||||
max_retries = 3
|
max_retries = self.get_config('connection.max_retries', 3) or 3
|
||||||
retry_delay = 5
|
retry_delay = self.get_config('connection.retry_delay', 5) or 5
|
||||||
|
|
||||||
for attempt in range(max_retries):
|
for attempt in range(max_retries):
|
||||||
try:
|
try:
|
||||||
@@ -117,7 +117,7 @@ class DuckHuntBot:
|
|||||||
self.config['port'],
|
self.config['port'],
|
||||||
ssl=ssl_context
|
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']}")
|
self.logger.info(f"✅ Successfully connected to {self.config['server']}:{self.config['port']}")
|
||||||
@@ -454,7 +454,7 @@ class DuckHuntBot:
|
|||||||
xp = player.get('xp', 0)
|
xp = player.get('xp', 0)
|
||||||
ducks_shot = player.get('ducks_shot', 0)
|
ducks_shot = player.get('ducks_shot', 0)
|
||||||
ducks_befriended = player.get('ducks_befriended', 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
|
# Ammo info
|
||||||
current_ammo = player.get('current_ammo', 0)
|
current_ammo = player.get('current_ammo', 0)
|
||||||
@@ -531,13 +531,24 @@ class DuckHuntBot:
|
|||||||
if not result["success"]:
|
if not result["success"]:
|
||||||
message = f"{nick} > {result['message']}"
|
message = f"{nick} > {result['message']}"
|
||||||
else:
|
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}!"
|
message = f"{nick} > Used {result['item_name']} on {target_nick}!"
|
||||||
else:
|
else:
|
||||||
message = f"{nick} > Used {result['item_name']}!"
|
message = f"{nick} > Used {result['item_name']}!"
|
||||||
|
|
||||||
# Add remaining count if any
|
# Add remaining count if any (not for bread message which has its own format)
|
||||||
if result.get("remaining_in_inventory", 0) > 0:
|
if effect_type != 'attract_ducks' and result.get("remaining_in_inventory", 0) > 0:
|
||||||
message += f" ({result['remaining_in_inventory']} remaining)"
|
message += f" ({result['remaining_in_inventory']} remaining)"
|
||||||
|
|
||||||
self.send_message(channel, message)
|
self.send_message(channel, message)
|
||||||
|
|||||||
85
src/game.py
85
src/game.py
@@ -35,8 +35,16 @@ class DuckGame:
|
|||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
# Wait random time between spawns, but in small chunks for responsiveness
|
# Wait random time between spawns, but in small chunks for responsiveness
|
||||||
min_wait = self.bot.get_config('duck_spawn_min', 300) # 5 minutes
|
min_wait = self.bot.get_config('duck_spawning.spawn_min', 300) # 5 minutes
|
||||||
max_wait = self.bot.get_config('duck_spawn_max', 900) # 15 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)
|
wait_time = random.randint(min_wait, max_wait)
|
||||||
|
|
||||||
# Sleep in 1-second intervals to allow for quick cancellation
|
# Sleep in 1-second intervals to allow for quick cancellation
|
||||||
@@ -65,12 +73,9 @@ class DuckGame:
|
|||||||
for channel, ducks in self.ducks.items():
|
for channel, ducks in self.ducks.items():
|
||||||
ducks_to_remove = []
|
ducks_to_remove = []
|
||||||
for duck in ducks:
|
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')
|
duck_type = duck.get('duck_type', 'normal')
|
||||||
if duck_type == 'fast':
|
timeout = self.bot.get_config(f'duck_types.{duck_type}.timeout', 60)
|
||||||
timeout = self.bot.get_config('fast_duck_timeout', 30)
|
|
||||||
else:
|
|
||||||
timeout = self.bot.get_config('duck_timeout', 60)
|
|
||||||
|
|
||||||
if current_time - duck['spawn_time'] > timeout:
|
if current_time - duck['spawn_time'] > timeout:
|
||||||
ducks_to_remove.append(duck)
|
ducks_to_remove.append(duck)
|
||||||
@@ -95,6 +100,9 @@ class DuckGame:
|
|||||||
if channel in self.ducks and not self.ducks[channel]:
|
if channel in self.ducks and not self.ducks[channel]:
|
||||||
del self.ducks[channel]
|
del self.ducks[channel]
|
||||||
|
|
||||||
|
# Clean expired effects every loop iteration
|
||||||
|
self._clean_expired_effects()
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
self.logger.info("Duck timeout loop cancelled")
|
self.logger.info("Duck timeout loop cancelled")
|
||||||
|
|
||||||
@@ -175,8 +183,8 @@ class DuckGame:
|
|||||||
'message_args': {'nick': nick}
|
'message_args': {'nick': nick}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for gun jamming
|
# Check for gun jamming using level-based jam chance
|
||||||
jam_chance = player.get('jam_chance', 5) / 100.0 # Convert percentage to decimal
|
jam_chance = self.bot.levels.get_jam_chance(player) / 100.0 # Convert percentage to decimal
|
||||||
if random.random() < jam_chance:
|
if random.random() < jam_chance:
|
||||||
# Gun jammed! Use ammo but don't shoot
|
# Gun jammed! Use ammo but don't shoot
|
||||||
player['current_ammo'] = player.get('current_ammo', 1) - 1
|
player['current_ammo'] = player.get('current_ammo', 1) - 1
|
||||||
@@ -216,7 +224,9 @@ class DuckGame:
|
|||||||
|
|
||||||
if duck['current_hp'] > 0:
|
if duck['current_hp'] > 0:
|
||||||
# Still alive, reveal it's golden but don't remove
|
# 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()
|
self.db.save_database()
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
@@ -241,14 +251,16 @@ class DuckGame:
|
|||||||
else:
|
else:
|
||||||
# Normal duck
|
# Normal duck
|
||||||
self.ducks[channel].pop(0)
|
self.ducks[channel].pop(0)
|
||||||
xp_gained = 10
|
xp_gained = self.bot.get_config('normal_duck_xp', 10)
|
||||||
message_key = 'bang_hit'
|
message_key = 'bang_hit'
|
||||||
|
|
||||||
# Apply XP and level changes
|
# Apply XP and level changes
|
||||||
old_level = self.bot.levels.calculate_player_level(player)
|
old_level = self.bot.levels.calculate_player_level(player)
|
||||||
player['xp'] = player.get('xp', 0) + xp_gained
|
player['xp'] = player.get('xp', 0) + xp_gained
|
||||||
player['ducks_shot'] = player.get('ducks_shot', 0) + 1
|
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
|
# Check if player leveled up and update magazines if needed
|
||||||
new_level = self.bot.levels.calculate_player_level(player)
|
new_level = self.bot.levels.calculate_player_level(player)
|
||||||
@@ -272,7 +284,9 @@ class DuckGame:
|
|||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# Miss! Duck stays in the channel
|
# 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()
|
self.db.save_database()
|
||||||
return {
|
return {
|
||||||
'success': True,
|
'success': True,
|
||||||
@@ -309,8 +323,8 @@ class DuckGame:
|
|||||||
# Success - befriend the duck
|
# Success - befriend the duck
|
||||||
duck = self.ducks[channel].pop(0)
|
duck = self.ducks[channel].pop(0)
|
||||||
|
|
||||||
# Lower XP gain than shooting (5 instead of 10)
|
# Lower XP gain than shooting
|
||||||
xp_gained = 5
|
xp_gained = self.bot.get_config('befriend_duck_xp', 5)
|
||||||
old_level = self.bot.levels.calculate_player_level(player)
|
old_level = self.bot.levels.calculate_player_level(player)
|
||||||
player['xp'] = player.get('xp', 0) + xp_gained
|
player['xp'] = player.get('xp', 0) + xp_gained
|
||||||
player['ducks_befriended'] = player.get('ducks_befriended', 0) + 1
|
player['ducks_befriended'] = player.get('ducks_befriended', 0) + 1
|
||||||
@@ -408,3 +422,44 @@ class DuckGame:
|
|||||||
self.logger.info(f"Auto-rearmed {rearmed_count} disarmed players after duck shot")
|
self.logger.info(f"Auto-rearmed {rearmed_count} disarmed players after duck shot")
|
||||||
except Exception as e:
|
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:
|
def get_modified_accuracy(self, player: Dict[str, Any]) -> int:
|
||||||
"""Get player's accuracy modified by their level"""
|
"""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)
|
level_info = self.get_player_level_info(player)
|
||||||
modifier = level_info.get('accuracy_modifier', 0)
|
modifier = level_info.get('accuracy_modifier', 0)
|
||||||
|
|
||||||
@@ -154,9 +154,20 @@ class LevelManager:
|
|||||||
level_info = self.get_player_level_info(player)
|
level_info = self.get_player_level_info(player)
|
||||||
level_rate = level_info.get('befriend_success_rate', base_rate)
|
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))
|
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:
|
def get_duck_spawn_modifier(self, player_levels: list) -> float:
|
||||||
"""Get duck spawn speed modifier based on highest level player in channel"""
|
"""Get duck spawn speed modifier based on highest level player in channel"""
|
||||||
if not player_levels:
|
if not player_levels:
|
||||||
|
|||||||
25
src/shop.py
25
src/shop.py
@@ -178,7 +178,7 @@ class ShopManager:
|
|||||||
|
|
||||||
elif item_type == 'accuracy':
|
elif item_type == 'accuracy':
|
||||||
# Increase accuracy up to 100%
|
# Increase accuracy up to 100%
|
||||||
current_accuracy = player.get('accuracy', 65)
|
current_accuracy = player.get('accuracy', 75)
|
||||||
new_accuracy = min(current_accuracy + amount, 100)
|
new_accuracy = min(current_accuracy + amount, 100)
|
||||||
player['accuracy'] = new_accuracy
|
player['accuracy'] = new_accuracy
|
||||||
return {
|
return {
|
||||||
@@ -279,7 +279,7 @@ class ShopManager:
|
|||||||
|
|
||||||
elif item_type == 'sabotage_accuracy':
|
elif item_type == 'sabotage_accuracy':
|
||||||
# Reduce target's accuracy temporarily
|
# 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)
|
new_acc = max(current_acc + amount, 10) # Min 10% accuracy (amount is negative)
|
||||||
player['accuracy'] = new_acc
|
player['accuracy'] = new_acc
|
||||||
|
|
||||||
@@ -325,6 +325,27 @@ class ShopManager:
|
|||||||
"new_total": new_jam
|
"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:
|
else:
|
||||||
self.logger.warning(f"Unknown item type: {item_type}")
|
self.logger.warning(f"Unknown item type: {item_type}")
|
||||||
return {"type": "unknown", "message": f"Unknown effect type: {item_type}"}
|
return {"type": "unknown", "message": f"Unknown effect type: {item_type}"}
|
||||||
|
|||||||
Reference in New Issue
Block a user