Implement magazine system and inventory management
- Add level-based magazine system (3 mags at L1, 2 at L3-5, 1 at L6-8) - Replace ammo/chargers with current_ammo/magazines/bullets_per_magazine - Add inventory system for storing and using shop items - Add Magazine item to shop (15 XP, adds 1 magazine) - Auto-migrate existing players from old ammo system - Auto-update magazines when players level up - Fix method name bugs (get_player_level -> calculate_player_level)
This commit is contained in:
201
src/game.py
201
src/game.py
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
Simplified Game mechanics for DuckHunt Bot
|
||||
Basic duck spawning and timeout only
|
||||
Game mechanics for DuckHunt Bot
|
||||
Handles duck spawning, shooting, befriending, and other game actions
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -10,7 +10,7 @@ import logging
|
||||
|
||||
|
||||
class DuckGame:
|
||||
"""Simplified game mechanics - just duck spawning"""
|
||||
"""Game mechanics for DuckHunt - shooting, befriending, reloading"""
|
||||
|
||||
def __init__(self, bot, db):
|
||||
self.bot = bot
|
||||
@@ -31,15 +31,17 @@ class DuckGame:
|
||||
self.logger.info("Game loops cancelled")
|
||||
|
||||
async def duck_spawn_loop(self):
|
||||
"""Simple duck spawning loop"""
|
||||
"""Duck spawning loop with responsive shutdown"""
|
||||
try:
|
||||
while True:
|
||||
# Wait random time between spawns
|
||||
# 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
|
||||
wait_time = random.randint(min_wait, max_wait)
|
||||
|
||||
await asyncio.sleep(wait_time)
|
||||
# Sleep in 1-second intervals to allow for quick cancellation
|
||||
for _ in range(wait_time):
|
||||
await asyncio.sleep(1)
|
||||
|
||||
# Spawn duck in random channel
|
||||
channels = list(self.bot.channels_joined)
|
||||
@@ -51,10 +53,11 @@ class DuckGame:
|
||||
self.logger.info("Duck spawning loop cancelled")
|
||||
|
||||
async def duck_timeout_loop(self):
|
||||
"""Simple duck timeout loop"""
|
||||
"""Duck timeout loop with responsive shutdown"""
|
||||
try:
|
||||
while True:
|
||||
await asyncio.sleep(10) # Check every 10 seconds
|
||||
# Check every 2 seconds instead of 10 for more responsiveness
|
||||
await asyncio.sleep(2)
|
||||
|
||||
current_time = time.time()
|
||||
channels_to_clear = []
|
||||
@@ -102,4 +105,184 @@ class DuckGame:
|
||||
message = self.bot.messages.get('duck_spawn')
|
||||
self.bot.send_message(channel, message)
|
||||
|
||||
self.logger.info(f"Duck spawned in {channel}")
|
||||
self.logger.info(f"Duck spawned in {channel}")
|
||||
|
||||
def shoot_duck(self, nick, channel, player):
|
||||
"""Handle shooting at a duck"""
|
||||
# Check if gun is confiscated
|
||||
if player.get('gun_confiscated', False):
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'bang_not_armed',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
# Check ammo
|
||||
if player.get('current_ammo', 0) <= 0:
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'bang_no_ammo',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
# Check for duck
|
||||
if channel not in self.ducks or not self.ducks[channel]:
|
||||
# Wild shot - gun confiscated
|
||||
player['current_ammo'] = player.get('current_ammo', 1) - 1
|
||||
player['gun_confiscated'] = True
|
||||
self.db.save_database()
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'bang_no_duck',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
# Shoot at duck
|
||||
player['current_ammo'] = player.get('current_ammo', 1) - 1
|
||||
|
||||
# Calculate hit chance using level-modified accuracy
|
||||
modified_accuracy = self.bot.levels.get_modified_accuracy(player)
|
||||
hit_chance = modified_accuracy / 100.0
|
||||
|
||||
if random.random() < hit_chance:
|
||||
# Hit! Remove the duck
|
||||
duck = self.ducks[channel].pop(0)
|
||||
xp_gained = 10
|
||||
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)
|
||||
|
||||
# Check if player leveled up and update magazines if needed
|
||||
new_level = self.bot.levels.calculate_player_level(player)
|
||||
if new_level != old_level:
|
||||
self.bot.levels.update_player_magazines(player)
|
||||
|
||||
self.db.save_database()
|
||||
return {
|
||||
'success': True,
|
||||
'hit': True,
|
||||
'message_key': 'bang_hit',
|
||||
'message_args': {
|
||||
'nick': nick,
|
||||
'xp_gained': xp_gained,
|
||||
'ducks_shot': player['ducks_shot']
|
||||
}
|
||||
}
|
||||
else:
|
||||
# Miss! Duck stays in the channel
|
||||
player['accuracy'] = max(player.get('accuracy', 65) - 2, 10)
|
||||
self.db.save_database()
|
||||
return {
|
||||
'success': True,
|
||||
'hit': False,
|
||||
'message_key': 'bang_miss',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
def befriend_duck(self, nick, channel, player):
|
||||
"""Handle befriending a duck"""
|
||||
# Check for duck
|
||||
if channel not in self.ducks or not self.ducks[channel]:
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'bef_no_duck',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
# Check befriend success rate from config and level modifiers
|
||||
base_rate = self.bot.get_config('befriend_success_rate', 75)
|
||||
try:
|
||||
if base_rate is not None:
|
||||
base_rate = float(base_rate)
|
||||
else:
|
||||
base_rate = 75.0
|
||||
except (ValueError, TypeError):
|
||||
base_rate = 75.0
|
||||
|
||||
# Apply level-based modification to befriend rate
|
||||
level_modified_rate = self.bot.levels.get_modified_befriend_rate(player, base_rate)
|
||||
success_rate = level_modified_rate / 100.0
|
||||
|
||||
if random.random() < success_rate:
|
||||
# Success - befriend the duck
|
||||
duck = self.ducks[channel].pop(0)
|
||||
|
||||
# Lower XP gain than shooting (5 instead of 10)
|
||||
xp_gained = 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
|
||||
|
||||
# Check if player leveled up and update magazines if needed
|
||||
new_level = self.bot.levels.calculate_player_level(player)
|
||||
if new_level != old_level:
|
||||
self.bot.levels.update_player_magazines(player)
|
||||
|
||||
self.db.save_database()
|
||||
return {
|
||||
'success': True,
|
||||
'befriended': True,
|
||||
'message_key': 'bef_success',
|
||||
'message_args': {
|
||||
'nick': nick,
|
||||
'xp_gained': xp_gained,
|
||||
'ducks_befriended': player['ducks_befriended']
|
||||
}
|
||||
}
|
||||
else:
|
||||
# Failure - duck flies away, remove from channel
|
||||
duck = self.ducks[channel].pop(0)
|
||||
|
||||
self.db.save_database()
|
||||
return {
|
||||
'success': True,
|
||||
'befriended': False,
|
||||
'message_key': 'bef_failed',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
def reload_gun(self, nick, channel, player):
|
||||
"""Handle reloading a gun (switching to a new magazine)"""
|
||||
if player.get('gun_confiscated', False):
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'reload_not_armed',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
current_ammo = player.get('current_ammo', 0)
|
||||
bullets_per_mag = player.get('bullets_per_magazine', 6)
|
||||
|
||||
# Check if current magazine is already full
|
||||
if current_ammo >= bullets_per_mag:
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'reload_already_loaded',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
# Check if they have spare magazines
|
||||
total_magazines = player.get('magazines', 1)
|
||||
if total_magazines <= 1: # Only the current magazine
|
||||
return {
|
||||
'success': False,
|
||||
'message_key': 'reload_no_chargers',
|
||||
'message_args': {'nick': nick}
|
||||
}
|
||||
|
||||
# Reload: discard current magazine and load a new full one
|
||||
player['current_ammo'] = bullets_per_mag
|
||||
player['magazines'] = total_magazines - 1
|
||||
|
||||
self.db.save_database()
|
||||
return {
|
||||
'success': True,
|
||||
'message_key': 'reload_success',
|
||||
'message_args': {
|
||||
'nick': nick,
|
||||
'ammo': player['current_ammo'],
|
||||
'max_ammo': bullets_per_mag,
|
||||
'chargers': player['magazines'] - 1 # Spare magazines (excluding current)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user