Simplified DuckHunt bot with customizable messages and colors
This commit is contained in:
351
src/game.py
351
src/game.py
@@ -1,310 +1,105 @@
|
||||
"""
|
||||
Game mechanics for DuckHunt Bot
|
||||
Simplified Game mechanics for DuckHunt Bot
|
||||
Basic duck spawning and timeout only
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import random
|
||||
import time
|
||||
import uuid
|
||||
import logging
|
||||
from typing import Dict, Any, Optional, List
|
||||
|
||||
|
||||
class DuckGame:
|
||||
"""Game mechanics and duck management"""
|
||||
"""Simplified game mechanics - just duck spawning"""
|
||||
|
||||
def __init__(self, bot, db):
|
||||
self.bot = bot
|
||||
self.db = db
|
||||
self.ducks = {}
|
||||
self.ducks = {} # {channel: [duck1, duck2, ...]}
|
||||
self.logger = logging.getLogger('DuckHuntBot.Game')
|
||||
|
||||
self.colors = {
|
||||
'red': '\x0304',
|
||||
'green': '\x0303',
|
||||
'yellow': '\x0308',
|
||||
'blue': '\x0302',
|
||||
'cyan': '\x0311',
|
||||
'magenta': '\x0306',
|
||||
'white': '\x0300',
|
||||
'bold': '\x02',
|
||||
'reset': '\x03',
|
||||
'underline': '\x1f'
|
||||
}
|
||||
|
||||
def get_config(self, path, default=None):
|
||||
"""Helper method to get config values"""
|
||||
return self.bot.get_config(path, default)
|
||||
|
||||
def get_player_level(self, xp):
|
||||
"""Calculate player level from XP"""
|
||||
if xp < 0:
|
||||
return 0
|
||||
return int((xp ** 0.5) / 2) + 1
|
||||
self.spawn_task = None
|
||||
self.timeout_task = None
|
||||
|
||||
def get_xp_for_next_level(self, xp):
|
||||
"""Calculate XP needed for next level"""
|
||||
level = self.get_player_level(xp)
|
||||
return ((level * 2) ** 2) - xp
|
||||
|
||||
def calculate_penalty_by_level(self, base_penalty, xp):
|
||||
"""Reduce penalties for higher level players"""
|
||||
level = self.get_player_level(xp)
|
||||
return max(1, base_penalty - (level - 1))
|
||||
|
||||
def update_karma(self, player, event):
|
||||
"""Update player karma based on events"""
|
||||
if not self.get_config('karma.enabled', True):
|
||||
return
|
||||
|
||||
karma_changes = {
|
||||
'hit': self.get_config('karma.hit_bonus', 2),
|
||||
'golden_hit': self.get_config('karma.golden_hit_bonus', 5),
|
||||
'teamkill': -self.get_config('karma.teamkill_penalty', 10),
|
||||
'wild_shot': -self.get_config('karma.wild_shot_penalty', 3),
|
||||
'miss': -self.get_config('karma.miss_penalty', 1),
|
||||
'befriend_success': self.get_config('karma.befriend_success_bonus', 2),
|
||||
'befriend_fail': -self.get_config('karma.befriend_fail_penalty', 1)
|
||||
}
|
||||
async def start_game_loops(self):
|
||||
"""Start the game loops"""
|
||||
self.spawn_task = asyncio.create_task(self.duck_spawn_loop())
|
||||
self.timeout_task = asyncio.create_task(self.duck_timeout_loop())
|
||||
|
||||
if event in karma_changes:
|
||||
player['karma'] = player.get('karma', 0) + karma_changes[event]
|
||||
try:
|
||||
await asyncio.gather(self.spawn_task, self.timeout_task)
|
||||
except asyncio.CancelledError:
|
||||
self.logger.info("Game loops cancelled")
|
||||
|
||||
def is_sleep_time(self):
|
||||
"""Check if ducks should not spawn due to sleep hours"""
|
||||
sleep_hours = self.get_config('sleep_hours', [])
|
||||
if not sleep_hours or len(sleep_hours) != 2:
|
||||
return False
|
||||
|
||||
import datetime
|
||||
current_hour = datetime.datetime.now().hour
|
||||
start_hour, end_hour = sleep_hours
|
||||
|
||||
if start_hour <= end_hour:
|
||||
return start_hour <= current_hour <= end_hour
|
||||
else:
|
||||
return current_hour >= start_hour or current_hour <= end_hour
|
||||
|
||||
def calculate_gun_reliability(self, player):
|
||||
"""Calculate gun reliability with modifiers"""
|
||||
base_reliability = player.get('reliability', 70)
|
||||
return min(100, max(0, base_reliability))
|
||||
|
||||
def gun_jams(self, player):
|
||||
"""Check if gun jams (eggdrop style)"""
|
||||
reliability = player.get('reliability', 70)
|
||||
jam_chance = max(1, 101 - reliability)
|
||||
|
||||
if player.get('total_ammo_used', 0) > 100:
|
||||
jam_chance += 2
|
||||
|
||||
if player.get('jammed_count', 0) > 5:
|
||||
jam_chance += 1
|
||||
|
||||
return random.randint(1, 100) <= jam_chance
|
||||
async def duck_spawn_loop(self):
|
||||
"""Simple duck spawning loop"""
|
||||
try:
|
||||
while True:
|
||||
# Wait random time between spawns
|
||||
min_wait = self.bot.get_config('duck_spawn_min', 300) # 5 minutes
|
||||
max_wait = self.bot.get_config('duck_spawn_max', 900) # 15 minutes
|
||||
wait_time = random.randint(min_wait, max_wait)
|
||||
|
||||
async def scare_other_ducks(self, channel, shot_duck_id):
|
||||
"""Scare other ducks when one is shot"""
|
||||
if channel not in self.ducks:
|
||||
return
|
||||
|
||||
for duck in self.ducks[channel][:]:
|
||||
if duck['id'] != shot_duck_id and duck['alive']:
|
||||
if random.random() < 0.3:
|
||||
duck['alive'] = False
|
||||
self.ducks[channel].remove(duck)
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
# Spawn duck in random channel
|
||||
channels = list(self.bot.channels_joined)
|
||||
if channels:
|
||||
channel = random.choice(channels)
|
||||
await self.spawn_duck(channel)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
self.logger.info("Duck spawning loop cancelled")
|
||||
|
||||
async def duck_timeout_loop(self):
|
||||
"""Simple duck timeout loop"""
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(10) # Check every 10 seconds
|
||||
|
||||
current_time = time.time()
|
||||
channels_to_clear = []
|
||||
|
||||
for channel, ducks in self.ducks.items():
|
||||
ducks_to_remove = []
|
||||
for duck in ducks:
|
||||
if current_time - duck['spawn_time'] > self.bot.get_config('duck_timeout', 60):
|
||||
ducks_to_remove.append(duck)
|
||||
|
||||
async def scare_duck_on_miss(self, channel, target_duck):
|
||||
"""Scare duck when someone misses"""
|
||||
if target_duck and random.random() < 0.15:
|
||||
target_duck['alive'] = False
|
||||
if channel in self.ducks and target_duck in self.ducks[channel]:
|
||||
self.ducks[channel].remove(target_duck)
|
||||
for duck in ducks_to_remove:
|
||||
ducks.remove(duck)
|
||||
message = self.bot.messages.get('duck_flies_away')
|
||||
self.bot.send_message(channel, message)
|
||||
|
||||
if not ducks:
|
||||
channels_to_clear.append(channel)
|
||||
|
||||
async def find_bushes_items(self, nick, channel, player):
|
||||
"""Find random items in bushes"""
|
||||
if not self.get_config('items.enabled', True):
|
||||
return
|
||||
|
||||
if random.random() < 0.1:
|
||||
items = [
|
||||
("a mirror", "mirror", "You can now deflect shots!"),
|
||||
("some sand", "sand", "Throw this to blind opponents!"),
|
||||
("a rusty bullet", None, "It's too rusty to use..."),
|
||||
("some bread crumbs", "bread", "Feed ducks to make them friendly!"),
|
||||
]
|
||||
|
||||
found_item, item_key, message = random.choice(items)
|
||||
|
||||
if item_key and item_key in player:
|
||||
player[item_key] = player.get(item_key, 0) + 1
|
||||
elif item_key in player:
|
||||
player[item_key] = player.get(item_key, 0) + 1
|
||||
|
||||
await self.bot.send_user_message(nick, channel,
|
||||
f"You found {found_item} in the bushes! {message}")
|
||||
# Clean up empty channels
|
||||
for channel in channels_to_clear:
|
||||
if channel in self.ducks and not self.ducks[channel]:
|
||||
del self.ducks[channel]
|
||||
|
||||
except asyncio.CancelledError:
|
||||
self.logger.info("Duck timeout loop cancelled")
|
||||
|
||||
def get_duck_spawn_message(self):
|
||||
"""Get random duck spawn message (eggdrop style)"""
|
||||
messages = [
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_O< QUACK",
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_o< QUACK!",
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_O< QUAAACK!",
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_ö< Quack?",
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_O< *QUACK*"
|
||||
]
|
||||
return random.choice(messages)
|
||||
|
||||
async def spawn_duck_now(self, channel, force_golden=False):
|
||||
"""Spawn a duck immediately in the specified channel"""
|
||||
async def spawn_duck(self, channel):
|
||||
"""Spawn a duck in the channel"""
|
||||
if channel not in self.ducks:
|
||||
self.ducks[channel] = []
|
||||
|
||||
max_ducks = self.get_config('max_ducks_per_channel', 3)
|
||||
if len([d for d in self.ducks[channel] if d['alive']]) >= max_ducks:
|
||||
self.logger.debug(f"Max ducks already in {channel}")
|
||||
|
||||
# Don't spawn if there's already a duck
|
||||
if self.ducks[channel]:
|
||||
return
|
||||
|
||||
if force_golden:
|
||||
duck_type = "golden"
|
||||
else:
|
||||
rand = random.random()
|
||||
if rand < 0.02:
|
||||
duck_type = "armored"
|
||||
elif rand < 0.10:
|
||||
duck_type = "golden"
|
||||
elif rand < 0.30:
|
||||
duck_type = "rare"
|
||||
elif rand < 0.40:
|
||||
duck_type = "fast"
|
||||
else:
|
||||
duck_type = "normal"
|
||||
|
||||
duck_config = self.get_config(f'duck_types.{duck_type}', {})
|
||||
if not duck_config.get('enabled', True):
|
||||
duck_type = "normal"
|
||||
duck_config = self.get_config('duck_types.normal', {})
|
||||
|
||||
|
||||
duck = {
|
||||
'id': str(uuid.uuid4())[:8],
|
||||
'type': duck_type,
|
||||
'alive': True,
|
||||
'id': f"duck_{int(time.time())}_{random.randint(1000, 9999)}",
|
||||
'spawn_time': time.time(),
|
||||
'health': duck_config.get('health', 1),
|
||||
'max_health': duck_config.get('health', 1)
|
||||
'channel': channel
|
||||
}
|
||||
|
||||
self.ducks[channel].append(duck)
|
||||
|
||||
messages = duck_config.get('messages', [self.get_duck_spawn_message()])
|
||||
spawn_message = random.choice(messages)
|
||||
# Send spawn message
|
||||
message = self.bot.messages.get('duck_spawn')
|
||||
self.bot.send_message(channel, message)
|
||||
|
||||
self.bot.send_message(channel, spawn_message)
|
||||
self.logger.info(f"Spawned {duck_type} duck in {channel}")
|
||||
|
||||
await self.send_duck_alerts(channel, duck_type)
|
||||
|
||||
return duck
|
||||
|
||||
async def send_duck_alerts(self, channel, duck_type):
|
||||
"""Send alerts to users who have them enabled"""
|
||||
if not self.get_config('social.duck_alerts_enabled', True):
|
||||
return
|
||||
|
||||
self.logger.debug(f"Duck alerts for {duck_type} duck in {channel}")
|
||||
|
||||
async def spawn_ducks(self):
|
||||
"""Main duck spawning loop"""
|
||||
while not self.bot.shutdown_requested:
|
||||
try:
|
||||
if self.is_sleep_time():
|
||||
await asyncio.sleep(300)
|
||||
continue
|
||||
|
||||
for channel in self.bot.channels_joined:
|
||||
if self.bot.shutdown_requested:
|
||||
break
|
||||
|
||||
if channel not in self.ducks:
|
||||
self.ducks[channel] = []
|
||||
|
||||
self.ducks[channel] = [d for d in self.ducks[channel] if d['alive']]
|
||||
|
||||
max_ducks = self.get_config('max_ducks_per_channel', 3)
|
||||
alive_ducks = len([d for d in self.ducks[channel] if d['alive']])
|
||||
|
||||
if alive_ducks < max_ducks:
|
||||
min_spawn_time = self.get_config('duck_spawn_min', 1800)
|
||||
max_spawn_time = self.get_config('duck_spawn_max', 5400)
|
||||
|
||||
if random.random() < 0.1:
|
||||
await self.spawn_duck_now(channel)
|
||||
|
||||
await asyncio.sleep(random.randint(60, 300))
|
||||
|
||||
except asyncio.CancelledError:
|
||||
self.logger.info("Duck spawning loop cancelled")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in duck spawning: {e}")
|
||||
await asyncio.sleep(60)
|
||||
|
||||
async def duck_timeout_checker(self):
|
||||
"""Check for ducks that should timeout"""
|
||||
while not self.bot.shutdown_requested:
|
||||
try:
|
||||
current_time = time.time()
|
||||
|
||||
for channel in list(self.ducks.keys()):
|
||||
if self.bot.shutdown_requested:
|
||||
break
|
||||
|
||||
if channel not in self.ducks:
|
||||
continue
|
||||
|
||||
for duck in self.ducks[channel][:]:
|
||||
if not duck['alive']:
|
||||
continue
|
||||
|
||||
age = current_time - duck['spawn_time']
|
||||
min_timeout = self.get_config('duck_timeout_min', 45)
|
||||
max_timeout = self.get_config('duck_timeout_max', 75)
|
||||
|
||||
timeout = random.randint(min_timeout, max_timeout)
|
||||
|
||||
if age > timeout:
|
||||
duck['alive'] = False
|
||||
self.ducks[channel].remove(duck)
|
||||
|
||||
timeout_messages = [
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_o> The duck flew away!",
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_O> *FLAP FLAP FLAP*",
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_o> The duck got tired of waiting and left!",
|
||||
"-.,¸¸.-·°'`'°·-.,¸¸.-·°'`'°· \\_O> *KWAK* The duck escaped!"
|
||||
]
|
||||
self.bot.send_message(channel, random.choice(timeout_messages))
|
||||
self.logger.debug(f"Duck timed out in {channel}")
|
||||
|
||||
await asyncio.sleep(10)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
self.logger.info("Duck timeout checker cancelled")
|
||||
break
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in duck timeout checker: {e}")
|
||||
await asyncio.sleep(30)
|
||||
|
||||
def get_alive_ducks(self, channel):
|
||||
"""Get list of alive ducks in channel"""
|
||||
if channel not in self.ducks:
|
||||
return []
|
||||
return [d for d in self.ducks[channel] if d['alive']]
|
||||
|
||||
def get_duck_by_id(self, channel, duck_id):
|
||||
"""Get duck by ID"""
|
||||
if channel not in self.ducks:
|
||||
return None
|
||||
for duck in self.ducks[channel]:
|
||||
if duck['id'] == duck_id and duck['alive']:
|
||||
return duck
|
||||
return None
|
||||
self.logger.info(f"Duck spawned in {channel}")
|
||||
Reference in New Issue
Block a user