Add hidden duck types: Normal, Golden, and Fast ducks

Features:
- Three duck types spawn randomly with configurable chances
- All duck types use same spawn message - type is hidden until shot/timeout
- Golden ducks: 3-5 HP, 15 XP base, reveal type when hit but still alive
- Fast ducks: 1 HP, 12 XP, fly away in 30s instead of 60s
- Normal ducks: 1 HP, 10 XP, standard 60s timeout

Configuration options:
- golden_duck_chance: 0.15 (15% spawn rate)
- fast_duck_chance: 0.25 (25% spawn rate)
- golden_duck_xp: 15, golden_duck_min/max_hp: 3-5
- fast_duck_xp: 12, fast_duck_timeout: 30s

Duck type is revealed when:
- Shot (different messages for each type)
- Flies away (type-specific fly away messages)
- Golden ducks reveal immediately when hit (before death)

Maintains backward compatibility with existing game mechanics.
This commit is contained in:
2025-09-24 16:35:45 +01:00
parent f9883758f3
commit 6ca624bd2f
3 changed files with 116 additions and 17 deletions

View File

@@ -16,6 +16,15 @@
"duck_spawn_max": 30, "duck_spawn_max": 30,
"duck_timeout": 60, "duck_timeout": 60,
"befriend_success_rate": 75, "befriend_success_rate": 75,
"rearm_on_duck_shot": true "rearm_on_duck_shot": true,
"golden_duck_chance": 0.15,
"golden_duck_min_hp": 3,
"golden_duck_max_hp": 5,
"golden_duck_xp": 15,
"fast_duck_chance": 0.25,
"fast_duck_timeout": 30,
"fast_duck_xp": 12
} }

View File

@@ -5,7 +5,12 @@
"・゜゜・。。・゜゜\\_O> {bold}*flap flap*{reset}" "・゜゜・。。・゜゜\\_O> {bold}*flap flap*{reset}"
], ],
"duck_flies_away": "The {bold}duck{reset} flies away. ·°'`'°-.,¸¸.·°'`", "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": "{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_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_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_no_ammo": "{nick} > {orange}*click*{reset} You're out of ammo! Use {blue}!reload{reset}",

View File

@@ -65,14 +65,24 @@ 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:
if current_time - duck['spawn_time'] > self.bot.get_config('duck_timeout', 60): # Different timeouts for different duck types
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)
if current_time - duck['spawn_time'] > timeout:
ducks_to_remove.append(duck) ducks_to_remove.append(duck)
for duck in ducks_to_remove: for duck in ducks_to_remove:
ducks.remove(duck) ducks.remove(duck)
# Use appropriate fly away message based on duck type # Use appropriate fly away message based on duck type - revealing the type!
if duck.get('is_golden', False): duck_type = duck.get('duck_type', 'normal')
if duck_type == 'golden':
message = self.bot.messages.get('golden_duck_flies_away') message = self.bot.messages.get('golden_duck_flies_away')
elif duck_type == 'fast':
message = self.bot.messages.get('fast_duck_flies_away')
else: else:
message = self.bot.messages.get('duck_flies_away') message = self.bot.messages.get('duck_flies_away')
self.bot.send_message(channel, message) self.bot.send_message(channel, message)
@@ -97,16 +107,53 @@ class DuckGame:
if self.ducks[channel]: if self.ducks[channel]:
return return
# 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:
# Normal duck
duck_type = 'normal'
duck = { duck = {
'id': f"duck_{int(time.time())}_{random.randint(1000, 9999)}", 'id': f"duck_{int(time.time())}_{random.randint(1000, 9999)}",
'spawn_time': time.time(), 'spawn_time': time.time(),
'channel': channel, 'channel': channel,
'duck_type': duck_type,
'max_hp': 1, 'max_hp': 1,
'current_hp': 1 'current_hp': 1
} }
# Send regular duck spawn message 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') message = self.bot.messages.get('duck_spawn')
self.logger.info(f"Regular duck spawned in {channel}")
self.ducks[channel].append(duck) self.ducks[channel].append(duck)
self.bot.send_message(channel, message) self.bot.send_message(channel, message)
@@ -158,13 +205,51 @@ class DuckGame:
modified_accuracy = self.bot.levels.get_modified_accuracy(player) modified_accuracy = self.bot.levels.get_modified_accuracy(player)
hit_chance = modified_accuracy / 100.0 hit_chance = modified_accuracy / 100.0
if random.random() < hit_chance: if random.random() < hit_chance:
# Hit! Get the duck # Hit! Get the duck and reveal its type
duck = self.ducks[channel][0]
duck_type = duck.get('duck_type', 'normal')
if duck_type == 'golden':
# Golden duck - multi-hit with high XP
duck['current_hp'] -= 1
xp_gained = self.bot.get_config('golden_duck_xp', 15)
if duck['current_hp'] > 0:
# Still alive, reveal it's golden but don't remove
player['accuracy'] = min(player.get('accuracy', 65) + 1, 100)
self.db.save_database()
return {
'success': True,
'hit': True,
'message_key': 'bang_hit_golden',
'message_args': {
'nick': nick,
'hp_remaining': duck['current_hp'],
'xp_gained': xp_gained
}
}
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 = self.bot.get_config('fast_duck_xp', 12)
message_key = 'bang_hit_fast'
else:
# Normal duck
self.ducks[channel].pop(0) self.ducks[channel].pop(0)
xp_gained = 10 xp_gained = 10
message_key = 'bang_hit'
# 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) player['accuracy'] = min(player.get('accuracy', 65) + 1, 100)
# 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)
if new_level != old_level: if new_level != old_level:
@@ -178,7 +263,7 @@ class DuckGame:
return { return {
'success': True, 'success': True,
'hit': True, 'hit': True,
'message_key': 'bang_hit', 'message_key': message_key,
'message_args': { 'message_args': {
'nick': nick, 'nick': nick,
'xp_gained': xp_gained, 'xp_gained': xp_gained,