Revert to simple version: Remove new ducks and items
- Removed new duck types (concrete, diamond, holy_grail, explosive, poisonous, etc.) - Removed new shop items (sniper rifle, duck radar, bread, splash water, etc.) - Removed status effects system (eliminated, poisoned, wet, etc.) - Removed item drops and temporary effects - Kept only original 3 duck types: normal, golden, fast - Kept original simple shop - KEPT BUG FIX: Golden duck XP now awarded on each hit - KEPT BUG FIX: Message sanitization preserves IRC codes - Simpler, more stable bot with core improvements
This commit is contained in:
412
src/game.py
412
src/game.py
@@ -19,17 +19,6 @@ class DuckGame:
|
||||
self.logger = logging.getLogger('DuckHuntBot.Game')
|
||||
self.spawn_task = None
|
||||
self.timeout_task = None
|
||||
|
||||
def _get_effects_list(self, player: dict):
|
||||
"""Return a sanitized list of temporary effect dicts for a player."""
|
||||
effects = player.get('temporary_effects', [])
|
||||
if not isinstance(effects, list):
|
||||
player['temporary_effects'] = []
|
||||
return []
|
||||
cleaned = [e for e in effects if isinstance(e, dict)]
|
||||
if len(cleaned) != len(effects):
|
||||
player['temporary_effects'] = cleaned
|
||||
return cleaned
|
||||
|
||||
async def start_game_loops(self):
|
||||
"""Start the game loops"""
|
||||
@@ -45,20 +34,12 @@ class DuckGame:
|
||||
"""Duck spawning loop with responsive shutdown"""
|
||||
try:
|
||||
while True:
|
||||
# Pick a target channel first so spawn multipliers are per-channel
|
||||
channels = list(self.bot.channels_joined)
|
||||
if not channels:
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
|
||||
channel = random.choice(channels)
|
||||
|
||||
# Wait random time between spawns, but in small chunks for responsiveness
|
||||
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(channel)
|
||||
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)
|
||||
@@ -70,8 +51,10 @@ class DuckGame:
|
||||
for _ in range(wait_time):
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Spawn duck in the chosen channel (if still joined)
|
||||
if channel in self.bot.channels_joined:
|
||||
# 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:
|
||||
@@ -128,164 +111,62 @@ class DuckGame:
|
||||
if channel not in self.ducks:
|
||||
self.ducks[channel] = []
|
||||
|
||||
# Don't spawn if there are already ducks present
|
||||
# Don't spawn if there's already a duck
|
||||
if self.ducks[channel]:
|
||||
return
|
||||
|
||||
duck_type = self._choose_duck_type()
|
||||
|
||||
# Special spawns that create multiple normal ducks.
|
||||
if duck_type in ('couple', 'family'):
|
||||
count = 2 if duck_type == 'couple' else random.randint(3, 4)
|
||||
for _ in range(count):
|
||||
duck = self._create_duck(channel, 'normal')
|
||||
self.ducks[channel].append(duck)
|
||||
self.logger.info(f"{duck_type} spawned {count} ducks in {channel}")
|
||||
|
||||
# Determine duck type randomly
|
||||
golden_chance = self.bot.get_config('golden_duck_chance', 0.15)
|
||||
fast_chance = self.bot.get_config('fast_duck_chance', 0.25)
|
||||
|
||||
rand = random.random()
|
||||
if rand < golden_chance:
|
||||
# Golden duck - high HP, high XP
|
||||
min_hp = self.bot.get_config('golden_duck_min_hp', 3)
|
||||
max_hp = self.bot.get_config('golden_duck_max_hp', 5)
|
||||
hp = random.randint(min_hp, max_hp)
|
||||
duck_type = 'golden'
|
||||
duck = {
|
||||
'id': f"golden_duck_{int(time.time())}_{random.randint(1000, 9999)}",
|
||||
'spawn_time': time.time(),
|
||||
'channel': channel,
|
||||
'duck_type': duck_type,
|
||||
'max_hp': hp,
|
||||
'current_hp': hp
|
||||
}
|
||||
self.logger.info(f"Golden duck (hidden) spawned in {channel} with {hp} HP")
|
||||
elif rand < golden_chance + fast_chance:
|
||||
# Fast duck - normal HP, flies away faster
|
||||
duck_type = 'fast'
|
||||
duck = {
|
||||
'id': f"fast_duck_{int(time.time())}_{random.randint(1000, 9999)}",
|
||||
'spawn_time': time.time(),
|
||||
'channel': channel,
|
||||
'duck_type': duck_type,
|
||||
'max_hp': 1,
|
||||
'current_hp': 1
|
||||
}
|
||||
self.logger.info(f"Fast duck (hidden) spawned in {channel}")
|
||||
else:
|
||||
duck = self._create_duck(channel, duck_type)
|
||||
self.ducks[channel].append(duck)
|
||||
hp = duck.get('max_hp', 1)
|
||||
if duck_type != 'normal':
|
||||
self.logger.info(f"{duck_type} duck (hidden) spawned in {channel} with {hp} HP")
|
||||
else:
|
||||
self.logger.info(f"Normal duck spawned in {channel}")
|
||||
|
||||
# Notify players with Duck Radar
|
||||
try:
|
||||
for player_name, player_data in self.db.get_players_for_channel(channel).items():
|
||||
if self._has_active_effect(player_data, 'duck_radar'):
|
||||
self.bot.send_message(player_name, self.bot.messages.get('radar_alert', channel=channel))
|
||||
except Exception:
|
||||
pass
|
||||
# Normal duck
|
||||
duck_type = 'normal'
|
||||
duck = {
|
||||
'id': f"duck_{int(time.time())}_{random.randint(1000, 9999)}",
|
||||
'spawn_time': time.time(),
|
||||
'channel': channel,
|
||||
'duck_type': duck_type,
|
||||
'max_hp': 1,
|
||||
'current_hp': 1
|
||||
}
|
||||
self.logger.info(f"Normal duck spawned in {channel}")
|
||||
|
||||
# All duck types use the same spawn message - type is hidden!
|
||||
message = self.bot.messages.get('duck_spawn')
|
||||
if not self.bot.send_message(channel, message):
|
||||
self.logger.warning(f"Failed to send duck spawn message to {channel}")
|
||||
|
||||
async def force_spawn_duck(self, channel, duck_type):
|
||||
"""Force spawn a specific duck type in a channel (admin/items), even if ducks already exist."""
|
||||
if channel not in self.ducks:
|
||||
self.ducks[channel] = []
|
||||
|
||||
duck_type = (duck_type or 'normal').lower()
|
||||
|
||||
if duck_type in ('couple', 'family'):
|
||||
count = 2 if duck_type == 'couple' else random.randint(3, 4)
|
||||
for _ in range(count):
|
||||
self.ducks[channel].append(self._create_duck(channel, 'normal'))
|
||||
else:
|
||||
self.ducks[channel].append(self._create_duck(channel, duck_type))
|
||||
|
||||
# Notify players with Duck Radar
|
||||
try:
|
||||
for player_name, player_data in self.db.get_players_for_channel(channel).items():
|
||||
if self._has_active_effect(player_data, 'duck_radar'):
|
||||
self.bot.send_message(player_name, self.bot.messages.get('radar_alert', channel=channel))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not self.bot.send_message(channel, self.bot.messages.get('duck_spawn')):
|
||||
self.logger.warning(f"Failed to send forced duck spawn message to {channel}")
|
||||
|
||||
def _choose_duck_type(self):
|
||||
"""Choose a duck type using duck_types.*.chance (with legacy fallbacks)."""
|
||||
try:
|
||||
duck_types = self.bot.get_config('duck_types', {}) or {}
|
||||
if not isinstance(duck_types, dict):
|
||||
duck_types = {}
|
||||
|
||||
weighted = []
|
||||
total = 0.0
|
||||
|
||||
for dtype, cfg in duck_types.items():
|
||||
if dtype == 'normal':
|
||||
continue
|
||||
chance = None
|
||||
if isinstance(cfg, dict):
|
||||
chance = cfg.get('chance')
|
||||
|
||||
# Legacy fallbacks
|
||||
if chance is None and dtype == 'golden':
|
||||
chance = self.bot.get_config('golden_duck_chance', None)
|
||||
if chance is None and dtype == 'fast':
|
||||
chance = self.bot.get_config('fast_duck_chance', None)
|
||||
|
||||
try:
|
||||
chance = float(chance)
|
||||
except (TypeError, ValueError):
|
||||
chance = 0.0
|
||||
|
||||
if chance > 0:
|
||||
weighted.append((dtype, chance))
|
||||
total += chance
|
||||
|
||||
if total <= 0:
|
||||
return 'normal'
|
||||
|
||||
r = random.random()
|
||||
if r >= min(1.0, total):
|
||||
return 'normal'
|
||||
|
||||
pick = random.random() * total
|
||||
cumulative = 0.0
|
||||
for dtype, weight in weighted:
|
||||
cumulative += weight
|
||||
if pick <= cumulative:
|
||||
return dtype
|
||||
return weighted[-1][0]
|
||||
except Exception:
|
||||
return 'normal'
|
||||
|
||||
def _create_duck(self, channel, duck_type):
|
||||
"""Create a duck dict for a given type."""
|
||||
cfg = self.bot.get_config(f'duck_types.{duck_type}', {}) or {}
|
||||
if not isinstance(cfg, dict):
|
||||
cfg = {}
|
||||
|
||||
# Legacy golden HP keys
|
||||
if duck_type == 'golden' and ('min_hp' not in cfg and 'max_hp' not in cfg):
|
||||
cfg = dict(cfg)
|
||||
cfg['min_hp'] = self.bot.get_config('golden_duck_min_hp', 3)
|
||||
cfg['max_hp'] = self.bot.get_config('golden_duck_max_hp', 5)
|
||||
|
||||
min_hp = cfg.get('min_hp', cfg.get('hp', 1))
|
||||
max_hp = cfg.get('max_hp', cfg.get('hp', 1))
|
||||
try:
|
||||
min_hp = int(min_hp)
|
||||
max_hp = int(max_hp)
|
||||
except (TypeError, ValueError):
|
||||
min_hp = 1
|
||||
max_hp = 1
|
||||
min_hp = max(1, min_hp)
|
||||
max_hp = max(min_hp, max_hp)
|
||||
hp = random.randint(min_hp, max_hp)
|
||||
|
||||
return {
|
||||
'id': f"{duck_type}_duck_{int(time.time())}_{random.randint(1000, 9999)}",
|
||||
'spawn_time': time.time(),
|
||||
'channel': channel,
|
||||
'duck_type': duck_type,
|
||||
'max_hp': hp,
|
||||
'current_hp': hp
|
||||
}
|
||||
self.ducks[channel].append(duck)
|
||||
self.bot.send_message(channel, message)
|
||||
|
||||
def shoot_duck(self, nick, channel, player):
|
||||
"""Handle shooting at a duck"""
|
||||
# Status effects
|
||||
if self._has_active_effect(player, 'eliminated'):
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'player_eliminated',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
if self._has_active_effect(player, 'poisoned'):
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'player_poisoned',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
# Check if gun is confiscated
|
||||
if player.get('gun_confiscated', False):
|
||||
return {
|
||||
@@ -346,76 +227,58 @@ class DuckGame:
|
||||
# Calculate hit chance using level-modified accuracy
|
||||
modified_accuracy = self.bot.levels.get_modified_accuracy(player)
|
||||
hit_chance = modified_accuracy / 100.0
|
||||
if self._has_active_effect(player, 'perfect_aim'):
|
||||
hit_chance = 1.0
|
||||
if random.random() < hit_chance:
|
||||
# Hit! Get the duck and reveal its type
|
||||
duck = self.ducks[channel][0]
|
||||
duck_type = duck.get('duck_type', 'normal')
|
||||
|
||||
# Multi-HP ducks: treat as "boss" style.
|
||||
if duck.get('max_hp', 1) > 1:
|
||||
|
||||
if duck_type == 'golden':
|
||||
# Golden duck - multi-hit with high XP
|
||||
duck['current_hp'] -= 1
|
||||
per_hit_xp = self._get_duck_xp_per_hit(duck_type)
|
||||
|
||||
xp_gained = self.bot.get_config('golden_duck_xp', 15)
|
||||
|
||||
if duck['current_hp'] > 0:
|
||||
# Award XP for hitting (but not killing) the duck
|
||||
player['xp'] = player.get('xp', 0) + per_hit_xp
|
||||
# Still alive, reveal it's golden but don't remove
|
||||
# Award XP for hitting (but not killing) the golden duck
|
||||
player['xp'] = player.get('xp', 0) + xp_gained
|
||||
|
||||
accuracy_gain = self.bot.get_config('gameplay.accuracy_gain_on_hit', self.bot.get_config('accuracy_gain_on_hit', 1))
|
||||
max_accuracy = self.bot.get_config('gameplay.max_accuracy', self.bot.get_config('max_accuracy', 100))
|
||||
player['accuracy'] = min(player.get('accuracy', self.bot.get_config('player_defaults.accuracy', 75)) + accuracy_gain, max_accuracy)
|
||||
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()
|
||||
|
||||
# Choose appropriate message for multi-HP duck types
|
||||
message_key = {
|
||||
'golden': 'bang_hit_golden',
|
||||
'concrete': 'bang_hit_concrete',
|
||||
'holy_grail': 'bang_hit_holy_grail',
|
||||
'diamond': 'bang_hit_diamond'
|
||||
}.get(duck_type, 'bang_hit_golden') # Default to golden format for unknown types
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'hit': True,
|
||||
'message_key': message_key,
|
||||
'message_key': 'bang_hit_golden',
|
||||
'message_args': {
|
||||
'nick': nick,
|
||||
'hp_remaining': duck['current_hp'],
|
||||
'xp_gained': per_hit_xp,
|
||||
'ducks_shot': player.get('ducks_shot', 0)
|
||||
'xp_gained': xp_gained
|
||||
}
|
||||
}
|
||||
|
||||
# Killed!
|
||||
else:
|
||||
# Golden duck killed!
|
||||
self.ducks[channel].pop(0)
|
||||
xp_gained = xp_gained * duck['max_hp'] # Bonus XP for killing
|
||||
message_key = 'bang_hit_golden_killed'
|
||||
elif duck_type == 'fast':
|
||||
# Fast duck - normal HP but higher XP
|
||||
self.ducks[channel].pop(0)
|
||||
xp_gained = per_hit_xp * int(duck.get('max_hp', 1))
|
||||
message_key = {
|
||||
'golden': 'bang_hit_golden_killed',
|
||||
'concrete': 'bang_hit_concrete_killed',
|
||||
'holy_grail': 'bang_hit_holy_grail_killed',
|
||||
'diamond': 'bang_hit_diamond_killed'
|
||||
}.get(duck_type, 'bang_hit_golden_killed')
|
||||
xp_gained = self.bot.get_config('fast_duck_xp', 12)
|
||||
message_key = 'bang_hit_fast'
|
||||
else:
|
||||
# Single-HP ducks
|
||||
# Normal duck
|
||||
self.ducks[channel].pop(0)
|
||||
xp_gained = self._get_duck_xp_per_hit(duck_type)
|
||||
message_key = {
|
||||
'normal': 'bang_hit',
|
||||
'fast': 'bang_hit_fast',
|
||||
'explosive': 'bang_hit_explosive'
|
||||
}.get(duck_type, 'bang_hit')
|
||||
|
||||
if duck_type == 'explosive':
|
||||
self._add_temporary_effect(player, 'eliminated', 2 * 3600)
|
||||
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
|
||||
accuracy_gain = self.bot.get_config('gameplay.accuracy_gain_on_hit', self.bot.get_config('accuracy_gain_on_hit', 1))
|
||||
max_accuracy = self.bot.get_config('gameplay.max_accuracy', self.bot.get_config('max_accuracy', 100))
|
||||
player['accuracy'] = min(player.get('accuracy', self.bot.get_config('player_defaults.accuracy', 75)) + accuracy_gain, max_accuracy)
|
||||
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)
|
||||
@@ -424,7 +287,7 @@ class DuckGame:
|
||||
|
||||
# If config option enabled, rearm all disarmed players when duck is shot
|
||||
if self.bot.get_config('duck_spawning.rearm_on_duck_shot', False):
|
||||
self._rearm_all_disarmed_players(channel)
|
||||
self._rearm_all_disarmed_players()
|
||||
|
||||
# Check for item drops
|
||||
dropped_item = self._check_item_drop(player, duck_type)
|
||||
@@ -464,7 +327,7 @@ class DuckGame:
|
||||
if random.random() < friendly_fire_chance:
|
||||
# Get other armed players in the same channel
|
||||
armed_players = []
|
||||
for other_nick, other_player in self.db.get_players_for_channel(channel).items():
|
||||
for other_nick, other_player in self.db.players.items():
|
||||
if (other_nick.lower() != nick.lower() and
|
||||
not other_player.get('gun_confiscated', False) and
|
||||
other_player.get('current_ammo', 0) > 0):
|
||||
@@ -525,20 +388,6 @@ class DuckGame:
|
||||
|
||||
def befriend_duck(self, nick, channel, player):
|
||||
"""Handle befriending a duck"""
|
||||
# Status effects
|
||||
if self._has_active_effect(player, 'eliminated'):
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'player_eliminated',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
if self._has_active_effect(player, 'poisoned'):
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'player_poisoned',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
# Check for duck
|
||||
if channel not in self.ducks or not self.ducks[channel]:
|
||||
return {
|
||||
@@ -564,34 +413,6 @@ class DuckGame:
|
||||
if random.random() < success_rate:
|
||||
# Success - befriend the duck
|
||||
duck = self.ducks[channel].pop(0)
|
||||
|
||||
duck_type = duck.get('duck_type', 'normal')
|
||||
|
||||
# Poison effects
|
||||
if duck_type == 'poisonous':
|
||||
self._add_temporary_effect(player, 'poisoned', 2 * 3600)
|
||||
self.db.save_database()
|
||||
return {
|
||||
'success': True,
|
||||
'befriended': True,
|
||||
'message_key': 'bef_poisoned',
|
||||
'message_args': {
|
||||
'nick': nick,
|
||||
'duration_hours': 2
|
||||
}
|
||||
}
|
||||
if duck_type == 'radioactive':
|
||||
self._add_temporary_effect(player, 'poisoned', 8 * 3600)
|
||||
self.db.save_database()
|
||||
return {
|
||||
'success': True,
|
||||
'befriended': True,
|
||||
'message_key': 'bef_poisoned',
|
||||
'message_args': {
|
||||
'nick': nick,
|
||||
'duration_hours': 8
|
||||
}
|
||||
}
|
||||
|
||||
# Lower XP gain than shooting
|
||||
xp_gained = self.bot.get_config('gameplay.befriend_xp', 5)
|
||||
@@ -606,7 +427,7 @@ class DuckGame:
|
||||
|
||||
# If config option enabled, rearm all disarmed players when duck is befriended
|
||||
if self.bot.get_config('rearm_on_duck_shot', False):
|
||||
self._rearm_all_disarmed_players(channel)
|
||||
self._rearm_all_disarmed_players()
|
||||
|
||||
self.db.save_database()
|
||||
return {
|
||||
@@ -676,11 +497,11 @@ class DuckGame:
|
||||
}
|
||||
}
|
||||
|
||||
def _rearm_all_disarmed_players(self, channel):
|
||||
"""Rearm all players who have been disarmed (gun confiscated) in a channel"""
|
||||
def _rearm_all_disarmed_players(self):
|
||||
"""Rearm all players who have been disarmed (gun confiscated)"""
|
||||
try:
|
||||
rearmed_count = 0
|
||||
for player_name, player_data in self.db.get_players_for_channel(channel).items():
|
||||
for player_name, player_data in self.db.players.items():
|
||||
if player_data.get('gun_confiscated', False):
|
||||
player_data['gun_confiscated'] = False
|
||||
# Update magazines based on player level
|
||||
@@ -693,15 +514,15 @@ class DuckGame:
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in _rearm_all_disarmed_players: {e}")
|
||||
|
||||
def _get_active_spawn_multiplier(self, channel):
|
||||
"""Get the current spawn rate multiplier from active bread effects in a channel"""
|
||||
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.get_players_for_channel(channel).items():
|
||||
effects = self._get_effects_list(player_data)
|
||||
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):
|
||||
@@ -717,8 +538,8 @@ class DuckGame:
|
||||
"""Check if player has wet clothes that prevent shooting"""
|
||||
import time
|
||||
current_time = time.time()
|
||||
|
||||
effects = self._get_effects_list(player)
|
||||
|
||||
effects = player.get('temporary_effects', [])
|
||||
for effect in effects:
|
||||
if (effect.get('type') == 'wet_clothes' and
|
||||
effect.get('expires_at', 0) > current_time):
|
||||
@@ -731,7 +552,7 @@ class DuckGame:
|
||||
current_time = time.time()
|
||||
|
||||
try:
|
||||
effects = self._get_effects_list(player)
|
||||
effects = player.get('temporary_effects', [])
|
||||
for effect in effects:
|
||||
if (effect.get('type') == 'insurance' and
|
||||
effect.get('protection') == protection_type and
|
||||
@@ -748,8 +569,8 @@ class DuckGame:
|
||||
current_time = time.time()
|
||||
|
||||
try:
|
||||
for _channel, player_name, player_data in self.db.iter_all_players():
|
||||
effects = self._get_effects_list(player_data)
|
||||
for player_name, player_data in self.db.players.items():
|
||||
effects = player_data.get('temporary_effects', [])
|
||||
active_effects = []
|
||||
|
||||
for effect in effects:
|
||||
@@ -778,13 +599,12 @@ class DuckGame:
|
||||
if random.random() > drop_chance:
|
||||
return None # No drop
|
||||
|
||||
# Get drop table for this duck type (fallback to normal)
|
||||
# Get drop table for this duck type
|
||||
drop_table_key = f'{duck_type}_duck_drops'
|
||||
drop_table = self.bot.get_config(f'item_drops.{drop_table_key}', [])
|
||||
if not drop_table:
|
||||
drop_table = self.bot.get_config('item_drops.normal_duck_drops', [])
|
||||
|
||||
if not drop_table:
|
||||
self.logger.warning(f"No drop table found for {duck_type} duck")
|
||||
return None
|
||||
|
||||
# Weighted random selection
|
||||
@@ -823,40 +643,4 @@ class DuckGame:
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in _check_item_drop: {e}")
|
||||
return None
|
||||
|
||||
def _has_active_effect(self, player, effect_type):
|
||||
import time
|
||||
current_time = time.time()
|
||||
effects = self._get_effects_list(player)
|
||||
for effect in effects:
|
||||
if effect.get('type') == effect_type and effect.get('expires_at', 0) > current_time:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _add_temporary_effect(self, player, effect_type, duration_seconds):
|
||||
import time
|
||||
# Normalize effects storage in case legacy data stored it as a dict/string.
|
||||
self._get_effects_list(player)
|
||||
duration_seconds = int(duration_seconds)
|
||||
duration_seconds = max(1, min(duration_seconds, 7 * 24 * 3600)) # cap 7 days
|
||||
player['temporary_effects'].append({
|
||||
'type': effect_type,
|
||||
'expires_at': time.time() + duration_seconds
|
||||
})
|
||||
|
||||
def _get_duck_xp_per_hit(self, duck_type):
|
||||
"""Get XP value for a duck type (supports duck_types.*.xp and legacy keys)."""
|
||||
xp = self.bot.get_config(f'duck_types.{duck_type}.xp', None)
|
||||
if xp is None:
|
||||
# Legacy keys
|
||||
if duck_type == 'golden':
|
||||
xp = self.bot.get_config('golden_duck_xp', 15)
|
||||
elif duck_type == 'fast':
|
||||
xp = self.bot.get_config('fast_duck_xp', 12)
|
||||
else:
|
||||
xp = self.bot.get_config('normal_duck_xp', 10)
|
||||
try:
|
||||
return int(xp)
|
||||
except (TypeError, ValueError):
|
||||
return 10
|
||||
return None
|
||||
Reference in New Issue
Block a user