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:
2025-09-23 20:13:01 +01:00
parent 3aaf0d0bb4
commit 0c8b4f9543
8 changed files with 892 additions and 339 deletions

View File

@@ -38,14 +38,14 @@ class LevelManager:
"""Default fallback level system"""
return {
"level_calculation": {
"method": "total_ducks",
"description": "Level based on total ducks interacted with"
"method": "xp",
"description": "Level based on XP earned"
},
"levels": {
"1": {
"name": "Duck Novice",
"min_ducks": 0,
"max_ducks": 9,
"min_xp": 0,
"max_xp": 49,
"befriend_success_rate": 85,
"accuracy_modifier": 5,
"duck_spawn_speed_modifier": 1.0,
@@ -53,8 +53,8 @@ class LevelManager:
},
"2": {
"name": "Duck Hunter",
"min_ducks": 10,
"max_ducks": 99,
"min_xp": 50,
"max_xp": 299,
"befriend_success_rate": 75,
"accuracy_modifier": 0,
"duck_spawn_speed_modifier": 0.8,
@@ -65,20 +65,24 @@ class LevelManager:
def calculate_player_level(self, player: Dict[str, Any]) -> int:
"""Calculate a player's current level based on their stats"""
method = self.levels_data.get('level_calculation', {}).get('method', 'total_ducks')
method = self.levels_data.get('level_calculation', {}).get('method', 'xp')
if method == 'total_ducks':
if method == 'xp':
player_xp = player.get('xp', 0)
elif method == 'total_ducks':
# Fallback to duck-based calculation if specified
total_ducks = player.get('ducks_shot', 0) + player.get('ducks_befriended', 0)
elif method == 'xp':
total_ducks = player.get('xp', 0) // 10 # 10 XP per "duck equivalent"
player_xp = total_ducks # Use duck count as if it were XP
else:
total_ducks = player.get('ducks_shot', 0) + player.get('ducks_befriended', 0)
player_xp = player.get('xp', 0)
# Find the appropriate level
levels = self.levels_data.get('levels', {})
for level_num in sorted(levels.keys(), key=int, reverse=True):
level_data = levels[level_num]
if total_ducks >= level_data.get('min_ducks', 0):
# Check for XP-based thresholds first, fallback to duck-based
min_threshold = level_data.get('min_xp', level_data.get('min_ducks', 0))
if player_xp >= min_threshold:
return int(level_num)
return 1 # Default to level 1
@@ -102,15 +106,23 @@ class LevelManager:
"duck_spawn_speed_modifier": 1.0
}
total_ducks = player.get('ducks_shot', 0) + player.get('ducks_befriended', 0)
method = self.levels_data.get('level_calculation', {}).get('method', 'xp')
if method == 'xp':
current_value = player.get('xp', 0)
value_type = "xp"
else:
current_value = player.get('ducks_shot', 0) + player.get('ducks_befriended', 0)
value_type = "ducks"
# Calculate progress to next level
next_level_data = self.get_level_data(level + 1)
if next_level_data:
ducks_needed = next_level_data.get('min_ducks', 0) - total_ducks
threshold_key = f'min_{value_type}' if value_type == 'xp' else 'min_ducks'
next_threshold = next_level_data.get(threshold_key, 0)
needed_for_next = next_threshold - current_value
next_level_name = next_level_data.get('name', f"Level {level + 1}")
else:
ducks_needed = 0
needed_for_next = 0
next_level_name = "Max Level"
return {
@@ -120,9 +132,11 @@ class LevelManager:
"befriend_success_rate": level_data.get('befriend_success_rate', 75),
"accuracy_modifier": level_data.get('accuracy_modifier', 0),
"duck_spawn_speed_modifier": level_data.get('duck_spawn_speed_modifier', 1.0),
"total_ducks": total_ducks,
"ducks_needed_for_next": max(0, ducks_needed),
"next_level_name": next_level_name
"current_xp": player.get('xp', 0),
"total_ducks": player.get('ducks_shot', 0) + player.get('ducks_befriended', 0),
"needed_for_next": max(0, needed_for_next),
"next_level_name": next_level_name,
"value_type": value_type
}
def get_modified_accuracy(self, player: Dict[str, Any]) -> int:
@@ -163,4 +177,44 @@ class LevelManager:
self.load_levels()
new_count = len(self.levels_data.get('levels', {}))
self.logger.info(f"Levels reloaded: {old_count} -> {new_count} levels")
return new_count
return new_count
def update_player_magazines(self, player: Dict[str, Any]) -> Dict[str, Any]:
"""Update player's magazine count based on their current level"""
level_info = self.get_player_level_info(player)
level_magazines = level_info.get('magazines', 3)
level_bullets_per_mag = level_info.get('bullets_per_magazine', 6)
# Get current magazine status
current_magazines = player.get('magazines', 3)
current_ammo = player.get('current_ammo', 6)
current_bullets_per_mag = player.get('bullets_per_magazine', 6)
# Calculate total bullets they currently have
total_current_bullets = current_ammo + (current_magazines - 1) * current_bullets_per_mag
# Update magazine system to level requirements
player['magazines'] = level_magazines
player['bullets_per_magazine'] = level_bullets_per_mag
# Redistribute bullets across new magazine system
max_total_bullets = level_magazines * level_bullets_per_mag
new_total_bullets = min(total_current_bullets, max_total_bullets)
# Calculate how to distribute bullets
if new_total_bullets <= 0:
player['current_ammo'] = 0
elif new_total_bullets <= level_bullets_per_mag:
# All bullets fit in current magazine
player['current_ammo'] = new_total_bullets
else:
# Fill current magazine, save rest for other magazines
player['current_ammo'] = level_bullets_per_mag
return {
'old_magazines': current_magazines,
'new_magazines': level_magazines,
'old_total_bullets': total_current_bullets,
'new_total_bullets': new_total_bullets,
'current_ammo': player['current_ammo']
}