lint and spacing fixes. Also fixed problem with levels not showing up in compiled version (hopefully).
This commit is contained in:
		| @@ -1,2 +0,0 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ class Pumpkin: | |||||||
|         self.isActive = True |         self.isActive = True | ||||||
|         self.damage = playerMaxHealth // 2  # Half of player's max health |         self.damage = playerMaxHealth // 2  # Half of player's max health | ||||||
|         self.soundChannel = None |         self.soundChannel = None | ||||||
|         self.soundName = 'pumpkin_low' if isHigh else 'pumpkin_high'  # Inverted mapping |         self.soundName = "pumpkin_low" if isHigh else "pumpkin_high"  # Inverted mapping | ||||||
|  |  | ||||||
|     def update(self, sounds, playerX): |     def update(self, sounds, playerX): | ||||||
|         """Update pumpkin position and sound""" |         """Update pumpkin position and sound""" | ||||||
| @@ -39,7 +39,7 @@ class Pumpkin: | |||||||
|             # Calculate volume and pan for splat sound based on final position |             # Calculate volume and pan for splat sound based on final position | ||||||
|             volume, left, right = calculate_volume_and_pan(playerX, self.x) |             volume, left, right = calculate_volume_and_pan(playerX, self.x) | ||||||
|             if volume > 0:  # Only play if within audible range |             if volume > 0:  # Only play if within audible range | ||||||
|                 obj_play(sounds, 'pumpkin_splat', playerX, self.x, loop=False) |                 obj_play(sounds, "pumpkin_splat", playerX, self.x, loop=False) | ||||||
|  |  | ||||||
|     def check_collision(self, player): |     def check_collision(self, player): | ||||||
|         """Check if pumpkin hits player""" |         """Check if pumpkin hits player""" | ||||||
| @@ -58,7 +58,9 @@ class Pumpkin: | |||||||
| class Catapult(Object): | class Catapult(Object): | ||||||
|     def __init__(self, x, y, sounds, direction=1, fireInterval=5000, firingRange=20): |     def __init__(self, x, y, sounds, direction=1, fireInterval=5000, firingRange=20): | ||||||
|         super().__init__( |         super().__init__( | ||||||
|             x, y, "catapult", |             x, | ||||||
|  |             y, | ||||||
|  |             "catapult", | ||||||
|             isStatic=True, |             isStatic=True, | ||||||
|             isCollectible=False, |             isCollectible=False, | ||||||
|         ) |         ) | ||||||
| @@ -77,18 +79,14 @@ class Catapult(Object): | |||||||
|         self.lastFireTime = currentTime |         self.lastFireTime = currentTime | ||||||
|  |  | ||||||
|         # Play launch sound using directional audio |         # Play launch sound using directional audio | ||||||
|         play_directional_sound(self.sounds, 'catapult_launch', player.xPos, self.xPos) |         play_directional_sound(self.sounds, "catapult_launch", player.xPos, self.xPos) | ||||||
|  |  | ||||||
|         # Set up pending pumpkin |         # Set up pending pumpkin | ||||||
|         isHigh = random.choice([True, False]) |         isHigh = random.choice([True, False]) | ||||||
|         fireDirection = 1 if player.xPos > self.xPos else -1 |         fireDirection = 1 if player.xPos > self.xPos else -1 | ||||||
|  |  | ||||||
|         # Store pumpkin data for later creation |         # Store pumpkin data for later creation | ||||||
|         self.pendingPumpkin = { |         self.pendingPumpkin = {"isHigh": isHigh, "direction": fireDirection, "playerMaxHealth": player.get_max_health()} | ||||||
|             'isHigh': isHigh, |  | ||||||
|             'direction': fireDirection, |  | ||||||
|             'playerMaxHealth': player.get_max_health() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         # Set when to actually launch the pumpkin |         # Set when to actually launch the pumpkin | ||||||
|         self.pumpkinLaunchTime = currentTime + self.launchDelay |         self.pumpkinLaunchTime = currentTime + self.launchDelay | ||||||
| @@ -116,9 +114,9 @@ class Catapult(Object): | |||||||
|             # Create and fire the pending pumpkin |             # Create and fire the pending pumpkin | ||||||
|             pumpkin = Pumpkin( |             pumpkin = Pumpkin( | ||||||
|                 self.xPos, |                 self.xPos, | ||||||
|                 self.pendingPumpkin['isHigh'], |                 self.pendingPumpkin["isHigh"], | ||||||
|                 self.pendingPumpkin['direction'], |                 self.pendingPumpkin["direction"], | ||||||
|                 self.pendingPumpkin['playerMaxHealth'] |                 self.pendingPumpkin["playerMaxHealth"], | ||||||
|             ) |             ) | ||||||
|             self.activePumpkins.append(pumpkin) |             self.activePumpkins.append(pumpkin) | ||||||
|             self.pendingPumpkin = None |             self.pendingPumpkin = None | ||||||
| @@ -140,6 +138,4 @@ class Catapult(Object): | |||||||
|                 pumpkin.isActive = False |                 pumpkin.isActive = False | ||||||
|                 self.activePumpkins.remove(pumpkin) |                 self.activePumpkins.remove(pumpkin) | ||||||
|                 if not player.isInvincible: |                 if not player.isInvincible: | ||||||
|                     self.sounds['player_takes_damage'].play() |                     self.sounds["player_takes_damage"].play() | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,12 +9,7 @@ from src.powerup import PowerUp | |||||||
|  |  | ||||||
| class CoffinObject(Object): | class CoffinObject(Object): | ||||||
|     def __init__(self, x, y, sounds, level, item="random"): |     def __init__(self, x, y, sounds, level, item="random"): | ||||||
|         super().__init__( |         super().__init__(x, y, "coffin", isStatic=True, isCollectible=False, isHazard=False) | ||||||
|             x, y, "coffin", |  | ||||||
|             isStatic=True, |  | ||||||
|             isCollectible=False, |  | ||||||
|             isHazard=False |  | ||||||
|         ) |  | ||||||
|         self.sounds = sounds |         self.sounds = sounds | ||||||
|         self.level = level |         self.level = level | ||||||
|         self.isBroken = False |         self.isBroken = False | ||||||
| @@ -25,9 +20,9 @@ class CoffinObject(Object): | |||||||
|         """Handle being hit by the player's weapon""" |         """Handle being hit by the player's weapon""" | ||||||
|         if not self.isBroken: |         if not self.isBroken: | ||||||
|             self.isBroken = True |             self.isBroken = True | ||||||
|             play_sound(self.sounds['coffin_shatter']) |             play_sound(self.sounds["coffin_shatter"]) | ||||||
|             self.level.levelScore += 500 |             self.level.levelScore += 500 | ||||||
|             self.level.player.stats.update_stat('Coffins broken', 1) |             self.level.player.stats.update_stat("Coffins broken", 1) | ||||||
|  |  | ||||||
|             # Stop the ongoing coffin sound |             # Stop the ongoing coffin sound | ||||||
|             if self.channel: |             if self.channel: | ||||||
| @@ -54,16 +49,8 @@ class CoffinObject(Object): | |||||||
|             drop_x = self.xPos + (direction * drop_distance) |             drop_x = self.xPos + (direction * drop_distance) | ||||||
|  |  | ||||||
|             self.dropped_item = PowerUp( |             self.dropped_item = PowerUp( | ||||||
|                 drop_x, |                 drop_x, self.yPos, item_type, self.sounds, direction, self.level.leftBoundary, self.level.rightBoundary | ||||||
|                 self.yPos, |  | ||||||
|                 item_type, |  | ||||||
|                 self.sounds, |  | ||||||
|                 direction, |  | ||||||
|                 self.level.leftBoundary, |  | ||||||
|                 self.level.rightBoundary |  | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|             return True |             return True | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,31 +11,25 @@ import pygame | |||||||
| from libstormgames import * | from libstormgames import * | ||||||
| from src.object import Object | from src.object import Object | ||||||
|  |  | ||||||
|  |  | ||||||
| class DeathSound(Object): | class DeathSound(Object): | ||||||
|     """Special object that plays enemy death sounds at fixed positions and self-removes.""" |     """Special object that plays enemy death sounds at fixed positions and self-removes.""" | ||||||
|      |  | ||||||
|     def __init__(self, x, y, enemyType, sounds): |     def __init__(self, x, y, enemyType, sounds): | ||||||
|         # Initialize as a static object with the death sound name |         # Initialize as a static object with the death sound name | ||||||
|         deathSoundName = f"{enemyType}_dies" |         deathSoundName = f"{enemyType}_dies" | ||||||
|         super().__init__( |         super().__init__(x, y, deathSoundName, isStatic=True, isCollectible=False, isHazard=False) | ||||||
|             x,  |  | ||||||
|             y,  |  | ||||||
|             deathSoundName, |  | ||||||
|             isStatic=True, |  | ||||||
|             isCollectible=False, |  | ||||||
|             isHazard=False |  | ||||||
|         ) |  | ||||||
|          |  | ||||||
|         self.sounds = sounds |         self.sounds = sounds | ||||||
|         self.enemyType = enemyType |         self.enemyType = enemyType | ||||||
|         self.startTime = pygame.time.get_ticks() |         self.startTime = pygame.time.get_ticks() | ||||||
|          |  | ||||||
|         # Get the duration of the death sound if it exists |         # Get the duration of the death sound if it exists | ||||||
|         if deathSoundName in sounds: |         if deathSoundName in sounds: | ||||||
|             self.soundDuration = sounds[deathSoundName].get_length() * 1000  # Convert to milliseconds |             self.soundDuration = sounds[deathSoundName].get_length() * 1000  # Convert to milliseconds | ||||||
|         else: |         else: | ||||||
|             self.soundDuration = 1000  # Default 1 second if sound doesn't exist |             self.soundDuration = 1000  # Default 1 second if sound doesn't exist | ||||||
|      |  | ||||||
|     def update(self, currentTime): |     def update(self, currentTime): | ||||||
|         """Check if sound has finished playing and mark for removal.""" |         """Check if sound has finished playing and mark for removal.""" | ||||||
|         if currentTime - self.startTime >= self.soundDuration: |         if currentTime - self.startTime >= self.soundDuration: | ||||||
|   | |||||||
							
								
								
									
										74
									
								
								src/enemy.py
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								src/enemy.py
									
									
									
									
									
								
							| @@ -6,25 +6,20 @@ from libstormgames import * | |||||||
| from src.object import Object | from src.object import Object | ||||||
| from src.powerup import PowerUp | from src.powerup import PowerUp | ||||||
|  |  | ||||||
|  |  | ||||||
| class Enemy(Object): | class Enemy(Object): | ||||||
|     def __init__(self, xRange, y, enemyType, sounds, level, **kwargs): |     def __init__(self, xRange, y, enemyType, sounds, level, **kwargs): | ||||||
|         # Track when critters should start hunting |         # Track when critters should start hunting | ||||||
|         self.hunting = False |         self.hunting = False | ||||||
|         # Initialize base object properties |         # Initialize base object properties | ||||||
|         super().__init__( |         super().__init__(xRange, y, f"{enemyType}", isStatic=False, isHazard=True)  # Base sound | ||||||
|             xRange,  |  | ||||||
|             y,  |  | ||||||
|             f"{enemyType}",  # Base sound |  | ||||||
|             isStatic=False, |  | ||||||
|             isHazard=True |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|         # Enemy specific properties |         # Enemy specific properties | ||||||
|         self.enemyType = enemyType |         self.enemyType = enemyType | ||||||
|         self.level = level |         self.level = level | ||||||
|         self.health = kwargs.get('health', 5)  # Default 5 HP |         self.health = kwargs.get("health", 5)  # Default 5 HP | ||||||
|         self.damage = kwargs.get('damage', 1)  # Default 1 damage |         self.damage = kwargs.get("damage", 1)  # Default 1 damage | ||||||
|         self.attackRange = kwargs.get('attack_range', 1)  # Default 1 tile range |         self.attackRange = kwargs.get("attack_range", 1)  # Default 1 tile range | ||||||
|         self.sounds = sounds  # Store reference to game sounds |         self.sounds = sounds  # Store reference to game sounds | ||||||
|  |  | ||||||
|         # Movement and behavior properties |         # Movement and behavior properties | ||||||
| @@ -37,25 +32,25 @@ class Enemy(Object): | |||||||
|         self._currentX = self.xRange[0]  # Initialize current position |         self._currentX = self.xRange[0]  # Initialize current position | ||||||
|  |  | ||||||
|         # Add spawn configuration |         # Add spawn configuration | ||||||
|         self.canSpawn = kwargs.get('can_spawn', False) |         self.canSpawn = kwargs.get("can_spawn", False) | ||||||
|         if self.canSpawn: |         if self.canSpawn: | ||||||
|             self.spawnCooldown = kwargs.get('spawn_cooldown', 2000) |             self.spawnCooldown = kwargs.get("spawn_cooldown", 2000) | ||||||
|             self.spawnChance = kwargs.get('spawn_chance', 25) |             self.spawnChance = kwargs.get("spawn_chance", 25) | ||||||
|             self.spawnType = kwargs.get('spawn_type', 'zombie')  # Default to zombie for backward compatibility |             self.spawnType = kwargs.get("spawn_type", "zombie")  # Default to zombie for backward compatibility | ||||||
|             self.spawnDistance = kwargs.get('spawn_distance', 5) |             self.spawnDistance = kwargs.get("spawn_distance", 5) | ||||||
|             self.lastSpawnTime = 0 |             self.lastSpawnTime = 0 | ||||||
|  |  | ||||||
|         # Attack pattern configuration |         # Attack pattern configuration | ||||||
|         self.attackPattern = kwargs.get('attack_pattern', {'type': 'patrol'}) |         self.attackPattern = kwargs.get("attack_pattern", {"type": "patrol"}) | ||||||
|         self.turnThreshold = self.attackPattern.get('turn_threshold', 5) |         self.turnThreshold = self.attackPattern.get("turn_threshold", 5) | ||||||
|  |  | ||||||
|         # Initialize vulnerability system |         # Initialize vulnerability system | ||||||
|         self.hasVulnerabilitySystem = kwargs.get('has_vulnerability', False) |         self.hasVulnerabilitySystem = kwargs.get("has_vulnerability", False) | ||||||
|         if self.hasVulnerabilitySystem: |         if self.hasVulnerabilitySystem: | ||||||
|             self.isVulnerable = False  # Start invulnerable |             self.isVulnerable = False  # Start invulnerable | ||||||
|             self.vulnerabilityTimer = pygame.time.get_ticks() |             self.vulnerabilityTimer = pygame.time.get_ticks() | ||||||
|             self.vulnerabilityDuration = kwargs.get('vulnerability_duration', 2000) |             self.vulnerabilityDuration = kwargs.get("vulnerability_duration", 2000) | ||||||
|             self.invulnerabilityDuration = kwargs.get('invulnerability_duration', 5000) |             self.invulnerabilityDuration = kwargs.get("invulnerability_duration", 5000) | ||||||
|             soundName = f"{self.enemyType}_is_vulnerable" if self.isVulnerable else self.enemyType |             soundName = f"{self.enemyType}_is_vulnerable" if self.isVulnerable else self.enemyType | ||||||
|             self.channel = obj_play(self.sounds, soundName, self.level.player.xPos, self.xPos) |             self.channel = obj_play(self.sounds, soundName, self.level.player.xPos, self.xPos) | ||||||
|         else: |         else: | ||||||
| @@ -68,19 +63,17 @@ class Enemy(Object): | |||||||
|             self.health = 1  # Easy to kill |             self.health = 1  # Easy to kill | ||||||
|             self.attackCooldown = 1500  # Slower attack rate |             self.attackCooldown = 1500  # Slower attack rate | ||||||
|         elif enemyType == "spider": |         elif enemyType == "spider": | ||||||
|             speedMultiplier = kwargs.get('speed_multiplier', 2.0) |             speedMultiplier = kwargs.get("speed_multiplier", 2.0) | ||||||
|             self.movementSpeed *= speedMultiplier  # Spiders are faster |             self.movementSpeed *= speedMultiplier  # Spiders are faster | ||||||
|             self.attackPattern = {'type': 'hunter'}  # Spiders actively hunt the player |             self.attackPattern = {"type": "hunter"}  # Spiders actively hunt the player | ||||||
|             self.turnThreshold = 3  # Spiders turn around quickly to chase player |             self.turnThreshold = 3  # Spiders turn around quickly to chase player | ||||||
|  |  | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def xPos(self): |     def xPos(self): | ||||||
|         """Current x position""" |         """Current x position""" | ||||||
|         return self._currentX |         return self._currentX | ||||||
|  |  | ||||||
|     @xPos.setter |     @xPos.setter | ||||||
|  |  | ||||||
|     def xPos(self, value): |     def xPos(self, value): | ||||||
|         """Set current x position""" |         """Set current x position""" | ||||||
|         self._currentX = value |         self._currentX = value | ||||||
| @@ -111,7 +104,9 @@ class Enemy(Object): | |||||||
|                 self.channel = obj_update(self.channel, player.xPos, self.xPos) |                 self.channel = obj_update(self.channel, player.xPos, self.xPos) | ||||||
|  |  | ||||||
|             # Check for vulnerability state change |             # Check for vulnerability state change | ||||||
|             if currentTime - self.vulnerabilityTimer >= (self.vulnerabilityDuration if self.isVulnerable else self.invulnerabilityDuration): |             if currentTime - self.vulnerabilityTimer >= ( | ||||||
|  |                 self.vulnerabilityDuration if self.isVulnerable else self.invulnerabilityDuration | ||||||
|  |             ): | ||||||
|                 self.isVulnerable = not self.isVulnerable |                 self.isVulnerable = not self.isVulnerable | ||||||
|                 self.vulnerabilityTimer = currentTime |                 self.vulnerabilityTimer = currentTime | ||||||
|  |  | ||||||
| @@ -126,8 +121,7 @@ class Enemy(Object): | |||||||
|                 self.hunting = True |                 self.hunting = True | ||||||
|  |  | ||||||
|         # Handle movement based on enemy type and pattern |         # Handle movement based on enemy type and pattern | ||||||
|         if (self.enemyType == "zombie" or  |         if self.enemyType == "zombie" or (self.attackPattern["type"] == "hunter" and self.hunting): | ||||||
|             (self.attackPattern['type'] == 'hunter' and self.hunting)): |  | ||||||
|  |  | ||||||
|             distanceToPlayer = player.xPos - self.xPos |             distanceToPlayer = player.xPos - self.xPos | ||||||
|  |  | ||||||
| @@ -167,9 +161,9 @@ class Enemy(Object): | |||||||
|                         spawnX = max(self.level.leftBoundary, min(spawnX, self.level.rightBoundary)) |                         spawnX = max(self.level.leftBoundary, min(spawnX, self.level.rightBoundary)) | ||||||
|  |  | ||||||
|                         # Set behavior based on game mode |                         # Set behavior based on game mode | ||||||
|                         behavior = 'hunter' if self.level.levelId == 999 else 'patrol' |                         behavior = "hunter" if self.level.levelId == 999 else "patrol" | ||||||
|                         turn_rate = 2 if self.level.levelId == 999 else 8  # Faster turn rate for survival |                         turn_rate = 2 if self.level.levelId == 999 else 8  # Faster turn rate for survival | ||||||
|                          |  | ||||||
|                         # Create new enemy of specified type |                         # Create new enemy of specified type | ||||||
|                         spawned = Enemy( |                         spawned = Enemy( | ||||||
|                             [spawnX, spawnX],  # Single point range for spawn |                             [spawnX, spawnX],  # Single point range for spawn | ||||||
| @@ -180,8 +174,8 @@ class Enemy(Object): | |||||||
|                             health=4,  # Default health for spawned enemies |                             health=4,  # Default health for spawned enemies | ||||||
|                             damage=2,  # Default damage for spawned enemies |                             damage=2,  # Default damage for spawned enemies | ||||||
|                             attack_range=1,  # Default range for spawned enemies |                             attack_range=1,  # Default range for spawned enemies | ||||||
|                             attack_pattern={'type': behavior}, |                             attack_pattern={"type": behavior}, | ||||||
|                             turn_rate=turn_rate |                             turn_rate=turn_rate, | ||||||
|                         ) |                         ) | ||||||
|  |  | ||||||
|                         # Add to level's enemies |                         # Add to level's enemies | ||||||
| @@ -221,7 +215,8 @@ class Enemy(Object): | |||||||
|  |  | ||||||
|     def attack(self, currentTime, player): |     def attack(self, currentTime, player): | ||||||
|         """Perform attack on player""" |         """Perform attack on player""" | ||||||
|         if player.isInvincible: return |         if player.isInvincible: | ||||||
|  |             return | ||||||
|         self.lastAttackTime = currentTime |         self.lastAttackTime = currentTime | ||||||
|         # Play attack sound |         # Play attack sound | ||||||
|         attackSound = f"{self.enemyType}_attack" |         attackSound = f"{self.enemyType}_attack" | ||||||
| @@ -229,7 +224,7 @@ class Enemy(Object): | |||||||
|             self.sounds[attackSound].play() |             self.sounds[attackSound].play() | ||||||
|         # Deal damage to player |         # Deal damage to player | ||||||
|         player.set_health(player.get_health() - self.damage) |         player.set_health(player.get_health() - self.damage) | ||||||
|         self.sounds['player_takes_damage'].play() |         self.sounds["player_takes_damage"].play() | ||||||
|  |  | ||||||
|     def take_damage(self, amount): |     def take_damage(self, amount): | ||||||
|         """Handle enemy taking damage""" |         """Handle enemy taking damage""" | ||||||
| @@ -259,6 +254,7 @@ class Enemy(Object): | |||||||
|  |  | ||||||
|         # Create a DeathSound object to play death sound at fixed position |         # Create a DeathSound object to play death sound at fixed position | ||||||
|         from src.die_monster_die import DeathSound |         from src.die_monster_die import DeathSound | ||||||
|  |  | ||||||
|         deathSoundObj = DeathSound(self.xPos, self.yPos, self.enemyType, self.sounds) |         deathSoundObj = DeathSound(self.xPos, self.yPos, self.enemyType, self.sounds) | ||||||
|         self.level.objects.append(deathSoundObj) |         self.level.objects.append(deathSoundObj) | ||||||
|  |  | ||||||
| @@ -276,17 +272,9 @@ class Enemy(Object): | |||||||
|             dropX = self.xPos + (direction * dropDistance) |             dropX = self.xPos + (direction * dropDistance) | ||||||
|  |  | ||||||
|             droppedItem = PowerUp( |             droppedItem = PowerUp( | ||||||
|                 dropX, |                 dropX, self.yPos, itemType, self.sounds, direction, self.level.leftBoundary, self.level.rightBoundary | ||||||
|                 self.yPos, |  | ||||||
|                 itemType, |  | ||||||
|                 self.sounds, |  | ||||||
|                 direction, |  | ||||||
|                 self.level.leftBoundary, |  | ||||||
|                 self.level.rightBoundary |  | ||||||
|             ) |             ) | ||||||
|             self.level.bouncing_items.append(droppedItem) |             self.level.bouncing_items.append(droppedItem) | ||||||
|  |  | ||||||
|         # Update stats |         # Update stats | ||||||
|         self.level.player.stats.update_stat('Enemies killed', 1) |         self.level.player.stats.update_stat("Enemies killed", 1) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,11 +2,11 @@ | |||||||
|  |  | ||||||
| import os | import os | ||||||
| import sys | import sys | ||||||
| import time |  | ||||||
| import pygame |  | ||||||
| from os.path import isdir, join | from os.path import isdir, join | ||||||
|  |  | ||||||
| from libstormgames import speak, instruction_menu | from libstormgames import speak, instruction_menu | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_available_games(): | def get_available_games(): | ||||||
|     """Get list of available game directories in levels folder. |     """Get list of available game directories in levels folder. | ||||||
|  |  | ||||||
| @@ -15,18 +15,19 @@ def get_available_games(): | |||||||
|     """ |     """ | ||||||
|     try: |     try: | ||||||
|         # Handle PyInstaller path issues |         # Handle PyInstaller path issues | ||||||
|         if hasattr(sys, '_MEIPASS'): |         if hasattr(sys, "_MEIPASS"): | ||||||
|             # Running as PyInstaller executable |             # Running as PyInstaller executable | ||||||
|             base_path = sys._MEIPASS |             base_path = sys._MEIPASS | ||||||
|         else: |         else: | ||||||
|             # Running as script |             # Running as script | ||||||
|             base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |             base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||||||
|          |  | ||||||
|         levels_path = os.path.join(base_path, "levels") |         levels_path = os.path.join(base_path, "levels") | ||||||
|         return [d for d in os.listdir(levels_path) if isdir(join(levels_path, d))] |         return [d for d in os.listdir(levels_path) if isdir(join(levels_path, d)) and not d.endswith(".md")] | ||||||
|     except FileNotFoundError: |     except FileNotFoundError: | ||||||
|         return [] |         return [] | ||||||
|  |  | ||||||
|  |  | ||||||
| def selection_menu(sounds, *options): | def selection_menu(sounds, *options): | ||||||
|     """Display level selection menu using instruction_menu. |     """Display level selection menu using instruction_menu. | ||||||
|  |  | ||||||
| @@ -39,6 +40,7 @@ def selection_menu(sounds, *options): | |||||||
|     """ |     """ | ||||||
|     return instruction_menu(sounds, "Select an adventure", *options) |     return instruction_menu(sounds, "Select an adventure", *options) | ||||||
|  |  | ||||||
|  |  | ||||||
| def select_game(sounds): | def select_game(sounds): | ||||||
|     """Display game selection menu and return chosen game. |     """Display game selection menu and return chosen game. | ||||||
|  |  | ||||||
| @@ -48,49 +50,48 @@ def select_game(sounds): | |||||||
|     Returns: |     Returns: | ||||||
|         str: Selected game directory name or None if cancelled |         str: Selected game directory name or None if cancelled | ||||||
|     """ |     """ | ||||||
|     availableGames = get_available_games() |     available_games = get_available_games() | ||||||
|  |  | ||||||
|     if not availableGames: |     if not available_games: | ||||||
|         speak("No games found in levels directory!") |         speak("No games found in levels directory!") | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     # Convert directory names to display names (replace underscores with spaces) |     # Convert directory names to display names (replace underscores with spaces) | ||||||
|     menuOptions = [game.replace("_", " ") for game in availableGames] |     menu_options = [game.replace("_", " ") for game in available_games] | ||||||
|  |  | ||||||
|     choice = selection_menu(sounds, *menuOptions) |     choice = selection_menu(sounds, *menu_options) | ||||||
|  |  | ||||||
|     if choice is None: |     if choice is None: | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|     # Convert display name back to directory name if needed |     # Convert display name back to directory name if needed | ||||||
|     gameDir = choice.replace(" ", "_") |     game_dir = choice.replace(" ", "_") | ||||||
|     if gameDir not in availableGames: |     if game_dir not in available_games: | ||||||
|         gameDir = choice  # Use original if conversion doesn't match |         game_dir = choice  # Use original if conversion doesn't match | ||||||
|  |  | ||||||
|     return gameDir |     return game_dir | ||||||
|  |  | ||||||
| def get_level_path(gameDir, levelNum): |  | ||||||
|  | def get_level_path(game_dir, level_num): | ||||||
|     """Get full path to level JSON file. |     """Get full path to level JSON file. | ||||||
|  |  | ||||||
|     Args: |     Args: | ||||||
|         gameDir (str): Game directory name |         game_dir (str): Game directory name | ||||||
|         levelNum (int): Level number |         level_num (int): Level number | ||||||
|  |  | ||||||
|     Returns: |     Returns: | ||||||
|         str: Full path to level JSON file |         str: Full path to level JSON file | ||||||
|     """ |     """ | ||||||
|     if gameDir is None: |     if game_dir is None: | ||||||
|         raise ValueError("gameDir cannot be None") |         raise ValueError("game_dir cannot be None") | ||||||
|      |  | ||||||
|     # Handle PyInstaller path issues |     # Handle PyInstaller path issues | ||||||
|     if hasattr(sys, '_MEIPASS'): |     if hasattr(sys, "_MEIPASS"): | ||||||
|         # Running as PyInstaller executable |         # Running as PyInstaller executable | ||||||
|         base_path = sys._MEIPASS |         base_path = sys._MEIPASS | ||||||
|     else: |     else: | ||||||
|         # Running as script |         # Running as script | ||||||
|         base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |         base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||||||
|      |  | ||||||
|     level_path = os.path.join(base_path, "levels", gameDir, f"{levelNum}.json") |     level_path = os.path.join(base_path, "levels", game_dir, f"{level_num}.json") | ||||||
|     return level_path |     return level_path | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import pygame | |||||||
| from libstormgames import * | from libstormgames import * | ||||||
| from src.object import Object | from src.object import Object | ||||||
|  |  | ||||||
|  |  | ||||||
| class GraspingHands(Object): | class GraspingHands(Object): | ||||||
|     """A hazard where the ground crumbles beneath the player as undead hands reach up.""" |     """A hazard where the ground crumbles beneath the player as undead hands reach up.""" | ||||||
|  |  | ||||||
| @@ -14,7 +15,7 @@ class GraspingHands(Object): | |||||||
|             "",  # Empty string so no regular object sound plays |             "",  # Empty string so no regular object sound plays | ||||||
|             isStatic=True, |             isStatic=True, | ||||||
|             isCollectible=False, |             isCollectible=False, | ||||||
|             isHazard=True |             isHazard=True, | ||||||
|         ) |         ) | ||||||
|         self.sounds = sounds |         self.sounds = sounds | ||||||
|         self.delay = delay  # Delay in milliseconds before ground starts crumbling |         self.delay = delay  # Delay in milliseconds before ground starts crumbling | ||||||
| @@ -51,7 +52,7 @@ class GraspingHands(Object): | |||||||
|             self.isReset = False |             self.isReset = False | ||||||
|  |  | ||||||
|             # Play initial warning sound |             # Play initial warning sound | ||||||
|             play_sound(self.sounds['grasping_hands_start']) |             play_sound(self.sounds["grasping_hands_start"]) | ||||||
|             speak("The ground crumbles as the dead reach for you.") |             speak("The ground crumbles as the dead reach for you.") | ||||||
|  |  | ||||||
|     def reset(self): |     def reset(self): | ||||||
| @@ -67,7 +68,7 @@ class GraspingHands(Object): | |||||||
|                 self.crumbleChannel = None |                 self.crumbleChannel = None | ||||||
|  |  | ||||||
|             # Play the end sound |             # Play the end sound | ||||||
|             play_sound(self.sounds['grasping_hands_end']) |             play_sound(self.sounds["grasping_hands_end"]) | ||||||
|  |  | ||||||
|     def update(self, currentTime, player): |     def update(self, currentTime, player): | ||||||
|         """Update the grasping hands trap state""" |         """Update the grasping hands trap state""" | ||||||
| @@ -92,7 +93,7 @@ class GraspingHands(Object): | |||||||
|             # Manage the looping positional audio for the crumbling ground |             # Manage the looping positional audio for the crumbling ground | ||||||
|             if self.crumbleChannel is None or not self.crumbleChannel.get_busy(): |             if self.crumbleChannel is None or not self.crumbleChannel.get_busy(): | ||||||
|                 # Start the sound if it's not playing |                 # Start the sound if it's not playing | ||||||
|                 self.crumbleChannel = obj_play(self.sounds, 'grasping_hands', player.xPos, self.crumblePosition) |                 self.crumbleChannel = obj_play(self.sounds, "grasping_hands", player.xPos, self.crumblePosition) | ||||||
|             else: |             else: | ||||||
|                 # Update the sound position |                 # Update the sound position | ||||||
|                 self.crumbleChannel = obj_update(self.crumbleChannel, player.xPos, self.crumblePosition) |                 self.crumbleChannel = obj_update(self.crumbleChannel, player.xPos, self.crumblePosition) | ||||||
| @@ -100,8 +101,9 @@ class GraspingHands(Object): | |||||||
|             # Check if player is caught by crumbling |             # Check if player is caught by crumbling | ||||||
|             playerCaught = False |             playerCaught = False | ||||||
|             if not player.isJumping: |             if not player.isJumping: | ||||||
|                 if (self.crumbleDirection > 0 and player.xPos <= self.crumblePosition) or \ |                 if (self.crumbleDirection > 0 and player.xPos <= self.crumblePosition) or ( | ||||||
|                    (self.crumbleDirection < 0 and player.xPos >= self.crumblePosition): |                     self.crumbleDirection < 0 and player.xPos >= self.crumblePosition | ||||||
|  |                 ): | ||||||
|                     playerCaught = True |                     playerCaught = True | ||||||
|  |  | ||||||
|             if playerCaught: |             if playerCaught: | ||||||
| @@ -122,8 +124,6 @@ class GraspingHands(Object): | |||||||
|     def __del__(self): |     def __del__(self): | ||||||
|         """Cleanup when object is destroyed""" |         """Cleanup when object is destroyed""" | ||||||
|         # Ensure sound is stopped when object is destroyed |         # Ensure sound is stopped when object is destroyed | ||||||
|         if hasattr(self, 'crumbleChannel') and self.crumbleChannel: |         if hasattr(self, "crumbleChannel") and self.crumbleChannel: | ||||||
|             obj_stop(self.crumbleChannel) |             obj_stop(self.crumbleChannel) | ||||||
|             self.crumbleChannel = None |             self.crumbleChannel = None | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								src/grave.py
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/grave.py
									
									
									
									
									
								
							| @@ -8,13 +8,9 @@ from src.powerup import PowerUp | |||||||
| class GraveObject(Object): | class GraveObject(Object): | ||||||
|     def __init__(self, x, y, sounds, item=None, zombieSpawnChance=0): |     def __init__(self, x, y, sounds, item=None, zombieSpawnChance=0): | ||||||
|         super().__init__( |         super().__init__( | ||||||
|             x, y, "grave", |             x, y, "grave", isStatic=True, isCollectible=False, isHazard=True, zombieSpawnChance=zombieSpawnChance | ||||||
|             isStatic=True, |  | ||||||
|             isCollectible=False, |  | ||||||
|             isHazard=True, |  | ||||||
|             zombieSpawnChance=zombieSpawnChance |  | ||||||
|         ) |         ) | ||||||
|         self.graveItem = item   |         self.graveItem = item | ||||||
|         self.isCollected = False  # Renamed to match style of isHazard, isStatic etc |         self.isCollected = False  # Renamed to match style of isHazard, isStatic etc | ||||||
|         self.isFilled = False  # Track if grave has been filled with shovel |         self.isFilled = False  # Track if grave has been filled with shovel | ||||||
|         self.sounds = sounds |         self.sounds = sounds | ||||||
| @@ -30,8 +26,12 @@ class GraveObject(Object): | |||||||
|             return False |             return False | ||||||
|  |  | ||||||
|         # Collect the item if player is ducking, walking (not running), and wielding shovel |         # Collect the item if player is ducking, walking (not running), and wielding shovel | ||||||
|         if (player.isDucking and not player.isRunning and  |         if ( | ||||||
|             player.currentWeapon and player.currentWeapon.name == "rusty_shovel"): |             player.isDucking | ||||||
|  |             and not player.isRunning | ||||||
|  |             and player.currentWeapon | ||||||
|  |             and player.currentWeapon.name == "rusty_shovel" | ||||||
|  |         ): | ||||||
|             self.isCollected = True  # Mark as collected when collection succeeds |             self.isCollected = True  # Mark as collected when collection succeeds | ||||||
|             return True |             return True | ||||||
|  |  | ||||||
| @@ -39,21 +39,25 @@ class GraveObject(Object): | |||||||
|  |  | ||||||
|     def can_fill_grave(self, player): |     def can_fill_grave(self, player): | ||||||
|         """Check if grave can be filled with shovel. |         """Check if grave can be filled with shovel. | ||||||
|          |  | ||||||
|         Returns: |         Returns: | ||||||
|             bool: True if grave can be filled, False otherwise |             bool: True if grave can be filled, False otherwise | ||||||
|         """ |         """ | ||||||
|         # Can only fill empty graves (no item) that haven't been filled yet |         # Can only fill empty graves (no item) that haven't been filled yet | ||||||
|         if self.graveItem or self.isFilled: |         if self.graveItem or self.isFilled: | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         # Must be ducking, walking (not running), and wielding shovel |         # Must be ducking, walking (not running), and wielding shovel | ||||||
|         return (player.isDucking and not player.isRunning and  |         return ( | ||||||
|                 player.currentWeapon and player.currentWeapon.name == "rusty_shovel") |             player.isDucking | ||||||
|  |             and not player.isRunning | ||||||
|  |             and player.currentWeapon | ||||||
|  |             and player.currentWeapon.name == "rusty_shovel" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def fill_grave(self, player): |     def fill_grave(self, player): | ||||||
|         """Fill the grave with dirt using shovel. |         """Fill the grave with dirt using shovel. | ||||||
|          |  | ||||||
|         Returns: |         Returns: | ||||||
|             bool: True if grave was filled successfully |             bool: True if grave was filled successfully | ||||||
|         """ |         """ | ||||||
| @@ -62,5 +66,3 @@ class GraveObject(Object): | |||||||
|             self.isHazard = False  # No longer a hazard once filled |             self.isHazard = False  # No longer a hazard once filled | ||||||
|             return True |             return True | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ from enum import Enum, auto | |||||||
|  |  | ||||||
| class ItemType(Enum): | class ItemType(Enum): | ||||||
|     """Defines available item types and their properties""" |     """Defines available item types and their properties""" | ||||||
|  |  | ||||||
|     GUTS = auto() |     GUTS = auto() | ||||||
|     HAND_OF_GLORY = auto() |     HAND_OF_GLORY = auto() | ||||||
|     JACK_O_LANTERN = auto() |     JACK_O_LANTERN = auto() | ||||||
| @@ -13,14 +14,12 @@ class ItemType(Enum): | |||||||
|     CAULDRON = auto() |     CAULDRON = auto() | ||||||
|     WITCH_BROOM = auto() |     WITCH_BROOM = auto() | ||||||
|  |  | ||||||
|  |  | ||||||
| class ItemProperties: | class ItemProperties: | ||||||
|     """Manages item properties and availability""" |     """Manages item properties and availability""" | ||||||
|  |  | ||||||
|     # Items that can appear in random drops |     # Items that can appear in random drops | ||||||
|     RANDOM_ELIGIBLE = { |     RANDOM_ELIGIBLE = {ItemType.HAND_OF_GLORY: "hand_of_glory", ItemType.JACK_O_LANTERN: "jack_o_lantern"} | ||||||
|         ItemType.HAND_OF_GLORY: "hand_of_glory", |  | ||||||
|         ItemType.JACK_O_LANTERN: "jack_o_lantern" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     # All possible items (including special ones) |     # All possible items (including special ones) | ||||||
|     ALL_ITEMS = { |     ALL_ITEMS = { | ||||||
| @@ -29,7 +28,7 @@ class ItemProperties: | |||||||
|         ItemType.JACK_O_LANTERN: "jack_o_lantern", |         ItemType.JACK_O_LANTERN: "jack_o_lantern", | ||||||
|         ItemType.EXTRA_LIFE: "extra_life", |         ItemType.EXTRA_LIFE: "extra_life", | ||||||
|         ItemType.CAULDRON: "cauldron", |         ItemType.CAULDRON: "cauldron", | ||||||
|         ItemType.WITCH_BROOM: "witch_broom" |         ItemType.WITCH_BROOM: "witch_broom", | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
| @@ -55,5 +54,3 @@ class ItemProperties: | |||||||
|             if name == item_name: |             if name == item_name: | ||||||
|                 return item_type |                 return item_type | ||||||
|         return None |         return None | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										150
									
								
								src/level.py
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								src/level.py
									
									
									
									
									
								
							| @@ -25,7 +25,7 @@ class Level: | |||||||
|         self.projectiles = []  # Track active projectiles |         self.projectiles = []  # Track active projectiles | ||||||
|         self.player = player |         self.player = player | ||||||
|         self.lastWarningTime = 0 |         self.lastWarningTime = 0 | ||||||
|         self.warningInterval = int(self.sounds['edge'].get_length() * 1000)  # Convert seconds to milliseconds |         self.warningInterval = int(self.sounds["edge"].get_length() * 1000)  # Convert seconds to milliseconds | ||||||
|         self.weapon_hit_channel = None |         self.weapon_hit_channel = None | ||||||
|  |  | ||||||
|         self.leftBoundary = levelData["boundaries"]["left"] |         self.leftBoundary = levelData["boundaries"]["left"] | ||||||
| @@ -42,34 +42,34 @@ class Level: | |||||||
|         self.player.set_footstep_sound(self.footstepSound) |         self.player.set_footstep_sound(self.footstepSound) | ||||||
|  |  | ||||||
|         # Level intro message (skip for survival mode) |         # Level intro message (skip for survival mode) | ||||||
|         if levelData['level_id'] != 999:  # 999 is survival mode |         if levelData["level_id"] != 999:  # 999 is survival mode | ||||||
|             levelIntro = f"Level {levelData['level_id']}, {levelData['name']}. " |             levelIntro = f"Level {levelData['level_id']}, {levelData['name']}. " | ||||||
|             if self.isLocked: |             if self.isLocked: | ||||||
|                 levelIntro += "This is a boss level. You must defeat all enemies before you can advance. " |                 levelIntro += "This is a boss level. You must defeat all enemies before you can advance. " | ||||||
|             levelIntro += levelData['description'] |             levelIntro += levelData["description"] | ||||||
|             messagebox(levelIntro) |             messagebox(levelIntro) | ||||||
|  |  | ||||||
|         # Handle level music |         # Handle level music | ||||||
|         try: |         try: | ||||||
|             pygame.mixer.music.stop() |             pygame.mixer.music.stop() | ||||||
|             if "ambience" in levelData: |             if "ambience" in levelData: | ||||||
|                 ambientFile = levelData['ambience'] |                 ambientFile = levelData["ambience"] | ||||||
|                  |  | ||||||
|                 # Build list of paths to try (pack-specific first, then generic) |                 # Build list of paths to try (pack-specific first, then generic) | ||||||
|                 ambiencePaths = [] |                 ambiencePaths = [] | ||||||
|                 if self.levelPackName: |                 if self.levelPackName: | ||||||
|                     ambiencePaths.append(f"sounds/{self.levelPackName}/ambience/{ambientFile}") |                     ambiencePaths.append(f"sounds/{self.levelPackName}/ambience/{ambientFile}") | ||||||
|                 ambiencePaths.append(f"sounds/ambience/{ambientFile}") |                 ambiencePaths.append(f"sounds/ambience/{ambientFile}") | ||||||
|                  |  | ||||||
|                 # Try each path until one works |                 # Try each path until one works | ||||||
|                 for ambiencePath in ambiencePaths: |                 for ambiencePath in ambiencePaths: | ||||||
|                     try: |                     try: | ||||||
|                         pygame.mixer.music.load(ambiencePath) |                         pygame.mixer.music.load(ambiencePath) | ||||||
|                         pygame.mixer.music.play(-1)  # Loop indefinitely |                         pygame.mixer.music.play(-1)  # Loop indefinitely | ||||||
|                         break |                         break | ||||||
|                     except: |                     except Exception: | ||||||
|                         continue |                         continue | ||||||
|         except: |         except Exception: | ||||||
|             pass |             pass | ||||||
|  |  | ||||||
|         # Create end of level object at right boundary |         # Create end of level object at right boundary | ||||||
| @@ -79,7 +79,7 @@ class Level: | |||||||
|             "end_of_level", |             "end_of_level", | ||||||
|             isStatic=True, |             isStatic=True, | ||||||
|             isCollectible=False, |             isCollectible=False, | ||||||
|             isHazard=False |             isHazard=False, | ||||||
|         ) |         ) | ||||||
|         self.objects.append(endLevel) |         self.objects.append(endLevel) | ||||||
|  |  | ||||||
| @@ -98,7 +98,7 @@ class Level: | |||||||
|                     obj["y"], |                     obj["y"], | ||||||
|                     self.sounds, |                     self.sounds, | ||||||
|                     fireInterval=obj.get("fireInterval", 5000), |                     fireInterval=obj.get("fireInterval", 5000), | ||||||
|                     firingRange=obj.get("range", 20) |                     firingRange=obj.get("range", 20), | ||||||
|                 ) |                 ) | ||||||
|                 self.objects.append(catapult) |                 self.objects.append(catapult) | ||||||
|             # Check if this is grasping hands |             # Check if this is grasping hands | ||||||
| @@ -108,7 +108,7 @@ class Level: | |||||||
|                     obj["y"], |                     obj["y"], | ||||||
|                     self.sounds, |                     self.sounds, | ||||||
|                     delay=obj.get("delay", 1000), |                     delay=obj.get("delay", 1000), | ||||||
|                     crumble_speed=obj.get("crumble_speed", 0.03) |                     crumble_speed=obj.get("crumble_speed", 0.03), | ||||||
|                 ) |                 ) | ||||||
|                 self.objects.append(graspingHands) |                 self.objects.append(graspingHands) | ||||||
|             # Check if this is a grave |             # Check if this is a grave | ||||||
| @@ -118,7 +118,7 @@ class Level: | |||||||
|                     obj["y"], |                     obj["y"], | ||||||
|                     self.sounds, |                     self.sounds, | ||||||
|                     item=obj.get("item", None), |                     item=obj.get("item", None), | ||||||
|                     zombieSpawnChance=obj.get("zombie_spawn_chance", 0) |                     zombieSpawnChance=obj.get("zombie_spawn_chance", 0), | ||||||
|                 ) |                 ) | ||||||
|                 self.objects.append(grave) |                 self.objects.append(grave) | ||||||
|             # Check if this is a skull storm |             # Check if this is a skull storm | ||||||
| @@ -130,7 +130,7 @@ class Level: | |||||||
|                     obj.get("damage", 5), |                     obj.get("damage", 5), | ||||||
|                     obj.get("maximum_skulls", 3), |                     obj.get("maximum_skulls", 3), | ||||||
|                     obj.get("frequency", {}).get("min", 2), |                     obj.get("frequency", {}).get("min", 2), | ||||||
|                     obj.get("frequency", {}).get("max", 5) |                     obj.get("frequency", {}).get("max", 5), | ||||||
|                 ) |                 ) | ||||||
|                 self.objects.append(skullStorm) |                 self.objects.append(skullStorm) | ||||||
|             # Check if this is a coffin |             # Check if this is a coffin | ||||||
| @@ -140,7 +140,7 @@ class Level: | |||||||
|                     obj["y"], |                     obj["y"], | ||||||
|                     self.sounds, |                     self.sounds, | ||||||
|                     self,  # Pass level reference |                     self,  # Pass level reference | ||||||
|                     item=obj.get("item", "random")  # Get item type or default to random |                     item=obj.get("item", "random"),  # Get item type or default to random | ||||||
|                 ) |                 ) | ||||||
|                 self.objects.append(coffin) |                 self.objects.append(coffin) | ||||||
|             # Check if this is a spider web |             # Check if this is a spider web | ||||||
| @@ -148,8 +148,7 @@ class Level: | |||||||
|                 # Check distance from graves |                 # Check distance from graves | ||||||
|                 isValidPosition = True |                 isValidPosition = True | ||||||
|                 for existingObj in self.objects: |                 for existingObj in self.objects: | ||||||
|                     if (existingObj.soundName == "grave" and |                     if existingObj.soundName == "grave" and not hasattr(existingObj, "graveItem"): | ||||||
|                         not hasattr(existingObj, 'graveItem')): |  | ||||||
|                         distance = abs(obj["x"] - existingObj.xPos) |                         distance = abs(obj["x"] - existingObj.xPos) | ||||||
|                         if distance < 3: |                         if distance < 3: | ||||||
|                             isValidPosition = False |                             isValidPosition = False | ||||||
| @@ -176,7 +175,7 @@ class Level: | |||||||
|                     damage=obj.get("damage", 1), |                     damage=obj.get("damage", 1), | ||||||
|                     attack_range=obj.get("attack_range", 1), |                     attack_range=obj.get("attack_range", 1), | ||||||
|                     movement_range=obj.get("movement_range", 5), |                     movement_range=obj.get("movement_range", 5), | ||||||
|                     attack_pattern=obj.get("attack_pattern", {'type': 'patrol'}), |                     attack_pattern=obj.get("attack_pattern", {"type": "patrol"}), | ||||||
|                     can_spawn=obj.get("can_spawn", False), |                     can_spawn=obj.get("can_spawn", False), | ||||||
|                     spawn_type=obj.get("spawn_type", "zombie"), |                     spawn_type=obj.get("spawn_type", "zombie"), | ||||||
|                     spawn_cooldown=obj.get("spawn_cooldown", 2000), |                     spawn_cooldown=obj.get("spawn_cooldown", 2000), | ||||||
| @@ -185,7 +184,7 @@ class Level: | |||||||
|                     has_vulnerability=obj.get("has_vulnerability", False), |                     has_vulnerability=obj.get("has_vulnerability", False), | ||||||
|                     is_vulnerable=obj.get("is_vulnerable", False), |                     is_vulnerable=obj.get("is_vulnerable", False), | ||||||
|                     vulnerability_duration=obj.get("vulnerability_duration", 1000), |                     vulnerability_duration=obj.get("vulnerability_duration", 1000), | ||||||
|                     invulnerability_duration=obj.get("invulnerability_duration", 5000) |                     invulnerability_duration=obj.get("invulnerability_duration", 5000), | ||||||
|                 ) |                 ) | ||||||
|                 self.enemies.append(enemy) |                 self.enemies.append(enemy) | ||||||
|             else: |             else: | ||||||
| @@ -196,13 +195,13 @@ class Level: | |||||||
|                     isStatic=obj.get("static", True), |                     isStatic=obj.get("static", True), | ||||||
|                     isCollectible=obj.get("collectible", False), |                     isCollectible=obj.get("collectible", False), | ||||||
|                     isHazard=obj.get("hazard", False), |                     isHazard=obj.get("hazard", False), | ||||||
|                     zombieSpawnChance=obj.get("zombieSpawnChance", 0) |                     zombieSpawnChance=obj.get("zombieSpawnChance", 0), | ||||||
|                 ) |                 ) | ||||||
|                 self.objects.append(gameObject) |                 self.objects.append(gameObject) | ||||||
|         enemyCount = len(self.enemies) |         enemyCount = len(self.enemies) | ||||||
|         coffinCount = sum(1 for obj in self.objects if hasattr(obj, 'isBroken')) |         coffinCount = sum(1 for obj in self.objects if hasattr(obj, "isBroken")) | ||||||
|         player.stats.update_stat('Enemies remaining', enemyCount) |         player.stats.update_stat("Enemies remaining", enemyCount) | ||||||
|         player.stats.update_stat('Coffins remaining', coffinCount) |         player.stats.update_stat("Coffins remaining", coffinCount) | ||||||
|  |  | ||||||
|     def update_audio(self): |     def update_audio(self): | ||||||
|         """Update all audio and entity state.""" |         """Update all audio and entity state.""" | ||||||
| @@ -214,9 +213,7 @@ class Level: | |||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             # Check for potential zombie spawn from graves |             # Check for potential zombie spawn from graves | ||||||
|             if (obj.soundName == "grave" and  |             if obj.soundName == "grave" and obj.zombieSpawnChance > 0 and not obj.hasSpawned: | ||||||
|                 obj.zombieSpawnChance > 0 and  |  | ||||||
|                 not obj.hasSpawned): |  | ||||||
|  |  | ||||||
|                 distance = abs(self.player.xPos - obj.xPos) |                 distance = abs(self.player.xPos - obj.xPos) | ||||||
|                 if distance < 6:  # Within 6 tiles |                 if distance < 6:  # Within 6 tiles | ||||||
| @@ -226,9 +223,9 @@ class Level: | |||||||
|                     roll = random.randint(1, 100) |                     roll = random.randint(1, 100) | ||||||
|                     if roll <= obj.zombieSpawnChance: |                     if roll <= obj.zombieSpawnChance: | ||||||
|                         # Set behavior based on game mode |                         # Set behavior based on game mode | ||||||
|                         behavior = 'hunter' if self.levelId == 999 else 'patrol' |                         behavior = "hunter" if self.levelId == 999 else "patrol" | ||||||
|                         turn_rate = 2 if self.levelId == 999 else 8  # Faster turn rate for survival |                         turn_rate = 2 if self.levelId == 999 else 8  # Faster turn rate for survival | ||||||
|                          |  | ||||||
|                         zombie = Enemy( |                         zombie = Enemy( | ||||||
|                             [obj.xPos, obj.xPos], |                             [obj.xPos, obj.xPos], | ||||||
|                             obj.yPos, |                             obj.yPos, | ||||||
| @@ -238,8 +235,8 @@ class Level: | |||||||
|                             health=3, |                             health=3, | ||||||
|                             damage=10, |                             damage=10, | ||||||
|                             attack_range=1, |                             attack_range=1, | ||||||
|                             attack_pattern={'type': behavior}, |                             attack_pattern={"type": behavior}, | ||||||
|                             turn_rate=turn_rate |                             turn_rate=turn_rate, | ||||||
|                         ) |                         ) | ||||||
|                         self.enemies.append(zombie) |                         self.enemies.append(zombie) | ||||||
|                         speak("A zombie emerges from the grave!") |                         speak("A zombie emerges from the grave!") | ||||||
| @@ -283,13 +280,14 @@ class Level: | |||||||
|                 caught = obj.update(currentTime, self.player) |                 caught = obj.update(currentTime, self.player) | ||||||
|                 if caught: |                 if caught: | ||||||
|                     return  # Stop if player is dead |                     return  # Stop if player is dead | ||||||
|          |  | ||||||
|         # Update death sound objects |         # Update death sound objects | ||||||
|         from src.die_monster_die import DeathSound |         from src.die_monster_die import DeathSound | ||||||
|  |  | ||||||
|         for obj in self.objects: |         for obj in self.objects: | ||||||
|             if isinstance(obj, DeathSound): |             if isinstance(obj, DeathSound): | ||||||
|                 obj.update(currentTime) |                 obj.update(currentTime) | ||||||
|          |  | ||||||
|         # Clean up inactive objects (including finished death sounds) |         # Clean up inactive objects (including finished death sounds) | ||||||
|         self.objects = [obj for obj in self.objects if obj.isActive] |         self.objects = [obj for obj in self.objects if obj.isActive] | ||||||
|  |  | ||||||
| @@ -303,7 +301,7 @@ class Level: | |||||||
|  |  | ||||||
|             # Check for item collection |             # Check for item collection | ||||||
|             if abs(item._currentX - self.player.xPos) < 1 and self.player.isJumping: |             if abs(item._currentX - self.player.xPos) < 1 and self.player.isJumping: | ||||||
|                 play_sound(self.sounds[f'get_{item.soundName}']) |                 play_sound(self.sounds[f"get_{item.soundName}"]) | ||||||
|                 item.apply_effect(self.player, self) |                 item.apply_effect(self.player, self) | ||||||
|                 self.levelScore += 1000  # All items collected points awarded |                 self.levelScore += 1000  # All items collected points awarded | ||||||
|                 item.isActive = False |                 item.isActive = False | ||||||
| @@ -325,11 +323,13 @@ class Level: | |||||||
|  |  | ||||||
|             # Check for coffin hits |             # Check for coffin hits | ||||||
|             for obj in self.objects: |             for obj in self.objects: | ||||||
|                 if hasattr(obj, 'isBroken'):  # Check if it's a coffin without using isinstance |                 if hasattr(obj, "isBroken"):  # Check if it's a coffin without using isinstance | ||||||
|                     if (not obj.isBroken and  |                     if ( | ||||||
|                         obj.xPos >= attackRange[0] and  |                         not obj.isBroken | ||||||
|                         obj.xPos <= attackRange[1] and |                         and obj.xPos >= attackRange[0] | ||||||
|                         self.player.isJumping):  # Must be jumping to hit floating coffins |                         and obj.xPos <= attackRange[1] | ||||||
|  |                         and self.player.isJumping | ||||||
|  |                     ):  # Must be jumping to hit floating coffins | ||||||
|  |  | ||||||
|                         if obj.hit(self.player.xPos): |                         if obj.hit(self.player.xPos): | ||||||
|                             self.bouncing_items.append(obj.dropped_item) |                             self.bouncing_items.append(obj.dropped_item) | ||||||
| @@ -345,7 +345,7 @@ class Level: | |||||||
|             health=8, |             health=8, | ||||||
|             damage=8, |             damage=8, | ||||||
|             attack_range=1, |             attack_range=1, | ||||||
|             speed_multiplier=2.0 |             speed_multiplier=2.0, | ||||||
|         ) |         ) | ||||||
|         self.enemies.append(spider) |         self.enemies.append(spider) | ||||||
|  |  | ||||||
| @@ -364,15 +364,21 @@ class Level: | |||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             # Handle grave edge warnings |             # Handle grave edge warnings | ||||||
|             if obj.isHazard and obj.soundName != "spiderweb" and not isinstance(obj, GraspingHands):  # Exclude spiderwebs and grasping hands |             if ( | ||||||
|  |                 obj.isHazard and obj.soundName != "spiderweb" and not isinstance(obj, GraspingHands) | ||||||
|  |             ):  # Exclude spiderwebs and grasping hands | ||||||
|                 distance = abs(self.player.xPos - obj.xPos) |                 distance = abs(self.player.xPos - obj.xPos) | ||||||
|                 currentTime = pygame.time.get_ticks() |                 currentTime = pygame.time.get_ticks() | ||||||
|                 if (distance <= 2 and not self.player.isJumping and not self.player.isInvincible  |                 if ( | ||||||
|                         and currentTime - self.lastWarningTime >= self.warningInterval): |                     distance <= 2 | ||||||
|  |                     and not self.player.isJumping | ||||||
|  |                     and not self.player.isInvincible | ||||||
|  |                     and currentTime - self.lastWarningTime >= self.warningInterval | ||||||
|  |                 ): | ||||||
|                     if isinstance(obj, GraveObject) and obj.graveItem and not obj.isCollected: |                     if isinstance(obj, GraveObject) and obj.graveItem and not obj.isCollected: | ||||||
|                         play_sound(self.sounds['_edge']) |                         play_sound(self.sounds["_edge"]) | ||||||
|                     else: |                     else: | ||||||
|                         play_sound(self.sounds['edge']) |                         play_sound(self.sounds["edge"]) | ||||||
|                     self.lastWarningTime = currentTime |                     self.lastWarningTime = currentTime | ||||||
|  |  | ||||||
|             if not obj.is_in_range(self.player.xPos): |             if not obj.is_in_range(self.player.xPos): | ||||||
| @@ -382,22 +388,19 @@ class Level: | |||||||
|             if obj.isCollectible and self.player.isJumping and not self.player.diedThisFrame: |             if obj.isCollectible and self.player.isJumping and not self.player.diedThisFrame: | ||||||
|                 currentPos = round(self.player.xPos) |                 currentPos = round(self.player.xPos) | ||||||
|                 if currentPos not in obj.collectedPositions: |                 if currentPos not in obj.collectedPositions: | ||||||
|                     play_sound(self.sounds[f'get_{obj.soundName}']) |                     play_sound(self.sounds[f"get_{obj.soundName}"]) | ||||||
|                     obj.collect_at_position(currentPos) |                     obj.collect_at_position(currentPos) | ||||||
|                     self.player.collectedItems.append(obj.soundName) |                     self.player.collectedItems.append(obj.soundName) | ||||||
|                     self.player.stats.update_stat('Items collected', 1) |                     self.player.stats.update_stat("Items collected", 1) | ||||||
|                     if obj.soundName == "bone_dust": |                     if obj.soundName == "bone_dust": | ||||||
|                         self.player._coins += 1 |                         self.player._coins += 1 | ||||||
|                         self.player.add_save_bone_dust(1)  # Add to save bone dust counter too |                         self.player.add_save_bone_dust(1)  # Add to save bone dust counter too | ||||||
|                         self.levelScore += 100 |                         self.levelScore += 100 | ||||||
|                         self.player.stats.update_stat('Bone dust', 1) |                         self.player.stats.update_stat("Bone dust", 1) | ||||||
|                         if self.player._coins % 5 == 0: |                         if self.player._coins % 5 == 0: | ||||||
|                             # Only heal if below max health |                             # Only heal if below max health | ||||||
|                             if self.player.get_health() < self.player.get_max_health(): |                             if self.player.get_health() < self.player.get_max_health(): | ||||||
|                                 self.player.set_health(min( |                                 self.player.set_health(min(self.player.get_health() + 1, self.player.get_max_health())) | ||||||
|                                     self.player.get_health() + 1, |  | ||||||
|                                     self.player.get_max_health() |  | ||||||
|                                 )) |  | ||||||
|  |  | ||||||
|                             if self.player._coins % 100 == 0: |                             if self.player._coins % 100 == 0: | ||||||
|                                 # Only give extra lives in story mode, not survival mode (level_id 999) |                                 # Only give extra lives in story mode, not survival mode (level_id 999) | ||||||
| @@ -406,27 +409,25 @@ class Level: | |||||||
|                                     self.player._coins = 0 |                                     self.player._coins = 0 | ||||||
|                                     self.player._lives += 1 |                                     self.player._lives += 1 | ||||||
|                                     self.levelScore += 1000 |                                     self.levelScore += 1000 | ||||||
|                                     play_sound(self.sounds['get_extra_life']) |                                     play_sound(self.sounds["get_extra_life"]) | ||||||
|                                 else: |                                 else: | ||||||
|                                     # In survival mode, reset coin counter but give bonus score instead |                                     # In survival mode, reset coin counter but give bonus score instead | ||||||
|                                     self.player._coins = 0 |                                     self.player._coins = 0 | ||||||
|                                     self.levelScore += 2000  # Double score bonus instead of extra life |                                     self.levelScore += 2000  # Double score bonus instead of extra life | ||||||
|                                     speak("100 bone dust collected! Bonus score!") |                                     speak("100 bone dust collected! Bonus score!") | ||||||
|                                     play_sound(self.sounds.get('survivor_bonus', 'bone_dust'))  # Use survivor_bonus sound if available, fallback to bone_dust |                                     play_sound( | ||||||
|  |                                         self.sounds.get("survivor_bonus", "bone_dust") | ||||||
|  |                                     )  # Use survivor_bonus sound if available, fallback to bone_dust | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             # Handle spiderweb - this should trigger for both walking and jumping if not ducking |             # Handle spiderweb - this should trigger for both walking and jumping if not ducking | ||||||
|             if obj.soundName == "spiderweb" and not self.player.isDucking: |             if obj.soundName == "spiderweb" and not self.player.isDucking: | ||||||
|                 # Create and apply web effect |                 # Create and apply web effect | ||||||
|                 webEffect = PowerUp( |                 webEffect = PowerUp( | ||||||
|                     obj.xPos, |                     obj.xPos, obj.yPos, "spiderweb", self.sounds, 0  # No direction needed since it's just for effect | ||||||
|                     obj.yPos, |  | ||||||
|                     'spiderweb', |  | ||||||
|                     self.sounds, |  | ||||||
|                     0  # No direction needed since it's just for effect |  | ||||||
|                 ) |                 ) | ||||||
|                 webEffect.level = self  # Pass level reference for spider spawning |                 webEffect.level = self  # Pass level reference for spider spawning | ||||||
|                 play_sound(self.sounds['hit_spiderweb']) |                 play_sound(self.sounds["hit_spiderweb"]) | ||||||
|                 webEffect.apply_effect(self.player, self) |                 webEffect.apply_effect(self.player, self) | ||||||
|  |  | ||||||
|                 # Deactivate web |                 # Deactivate web | ||||||
| @@ -442,12 +443,13 @@ class Level: | |||||||
|  |  | ||||||
|                     if can_collect: |                     if can_collect: | ||||||
|                         # Successfully collected item while ducking with shovel |                         # Successfully collected item while ducking with shovel | ||||||
|                         play_sound(self.sounds[f'get_{obj.graveItem}']) |                         play_sound(self.sounds[f"get_{obj.graveItem}"]) | ||||||
|                         play_sound(self.sounds.get('fill_in_grave', 'shovel_dig'))  # Also play fill sound |                         play_sound(self.sounds.get("fill_in_grave", "shovel_dig"))  # Also play fill sound | ||||||
|                         self.player.stats.update_stat('Items collected', 1) |                         self.player.stats.update_stat("Items collected", 1) | ||||||
|                         # Create PowerUp to handle the item effect |                         # Create PowerUp to handle the item effect | ||||||
|                         item = PowerUp(obj.xPos, obj.yPos, obj.graveItem, self.sounds, 1, |                         item = PowerUp( | ||||||
|                             self.leftBoundary, self.rightBoundary) |                             obj.xPos, obj.yPos, obj.graveItem, self.sounds, 1, self.leftBoundary, self.rightBoundary | ||||||
|  |                         ) | ||||||
|                         item.apply_effect(self.player, self) |                         item.apply_effect(self.player, self) | ||||||
|                         # Stop grave's current audio channel |                         # Stop grave's current audio channel | ||||||
|                         if obj.channel: |                         if obj.channel: | ||||||
| @@ -459,8 +461,10 @@ class Level: | |||||||
|                         continue |                         continue | ||||||
|                     elif can_fill and obj.fill_grave(self.player): |                     elif can_fill and obj.fill_grave(self.player): | ||||||
|                         # Successfully filled empty grave with shovel |                         # Successfully filled empty grave with shovel | ||||||
|                         play_sound(self.sounds.get('fill_in_grave', 'shovel_dig'))  # Use fill_in_grave sound if available, fallback to shovel_dig |                         play_sound( | ||||||
|                         self.player.stats.update_stat('Graves filled', 1) |                             self.sounds.get("fill_in_grave", "shovel_dig") | ||||||
|  |                         )  # Use fill_in_grave sound if available, fallback to shovel_dig | ||||||
|  |                         self.player.stats.update_stat("Graves filled", 1) | ||||||
|                         # Stop grave's current audio channel |                         # Stop grave's current audio channel | ||||||
|                         if obj.channel: |                         if obj.channel: | ||||||
|                             obj_stop(obj.channel) |                             obj_stop(obj.channel) | ||||||
| @@ -488,14 +492,14 @@ class Level: | |||||||
|                         # If level is locked, check for remaining enemies |                         # If level is locked, check for remaining enemies | ||||||
|                         if self.isLocked and any(enemy.isActive for enemy in self.enemies): |                         if self.isLocked and any(enemy.isActive for enemy in self.enemies): | ||||||
|                             speak("You must defeat all enemies before proceeding!") |                             speak("You must defeat all enemies before proceeding!") | ||||||
|                             play_sound(self.sounds['locked']) |                             play_sound(self.sounds["locked"]) | ||||||
|                             # Push player back a bit |                             # Push player back a bit | ||||||
|                             self.player.xPos -= 5 |                             self.player.xPos -= 5 | ||||||
|                             return False |                             return False | ||||||
|  |  | ||||||
|                         # Level complete |                         # Level complete | ||||||
|                         pygame.mixer.stop() |                         pygame.mixer.stop() | ||||||
|                         play_sound(self.sounds['end_of_level']) |                         play_sound(self.sounds["end_of_level"]) | ||||||
|                         self.levelScore += 10000 |                         self.levelScore += 10000 | ||||||
|                         # Actually update the scoreboard with level completion |                         # Actually update the scoreboard with level completion | ||||||
|                         self.player.scoreboard.increase_score(self.levelScore) |                         self.player.scoreboard.increase_score(self.levelScore) | ||||||
| @@ -518,7 +522,7 @@ class Level: | |||||||
|                     # Calculate volume and pan for splat sound based on final position |                     # Calculate volume and pan for splat sound based on final position | ||||||
|                     volume, left, right = calculate_volume_and_pan(self.player.xPos, proj.x) |                     volume, left, right = calculate_volume_and_pan(self.player.xPos, proj.x) | ||||||
|                     if volume > 0:  # Only play if within audible range |                     if volume > 0:  # Only play if within audible range | ||||||
|                         obj_play(self.sounds, 'pumpkin_splat', self.player.xPos, proj.x, loop=False) |                         obj_play(self.sounds, "pumpkin_splat", self.player.xPos, proj.x, loop=False) | ||||||
|                     break |                     break | ||||||
|  |  | ||||||
|     def throw_projectile(self): |     def throw_projectile(self): | ||||||
| @@ -528,12 +532,6 @@ class Level: | |||||||
|             speak("No jack o'lanterns to throw!") |             speak("No jack o'lanterns to throw!") | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         self.projectiles.append(Projectile( |         self.projectiles.append(Projectile(proj_info["type"], proj_info["start_x"], proj_info["direction"])) | ||||||
|             proj_info['type'], |  | ||||||
|             proj_info['start_x'], |  | ||||||
|             proj_info['direction'] |  | ||||||
|         )) |  | ||||||
|         # Play throw sound |         # Play throw sound | ||||||
|         play_sound(self.sounds['throw_jack_o_lantern']) |         play_sound(self.sounds["throw_jack_o_lantern"]) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
| from libstormgames import * | from libstormgames import * | ||||||
|  |  | ||||||
|  |  | ||||||
| class Object: | class Object: | ||||||
|     def __init__(self, x, yPos, soundName, isStatic=True, isCollectible=False, isHazard=False, zombieSpawnChance=0): |     def __init__(self, x, yPos, soundName, isStatic=True, isCollectible=False, isHazard=False, zombieSpawnChance=0): | ||||||
|         # x can be either a single position or a range [start, end] |         # x can be either a single position or a range [start, end] | ||||||
| @@ -37,5 +38,3 @@ class Object: | |||||||
|             if self.channel: |             if self.channel: | ||||||
|                 obj_stop(self.channel) |                 obj_stop(self.channel) | ||||||
|                 self.channel = None |                 self.channel = None | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,10 +13,10 @@ from libstormgames.sound import Sound | |||||||
|  |  | ||||||
| class PackSoundSystem(dict): | class PackSoundSystem(dict): | ||||||
|     """Sound system with hierarchical pack-specific loading.""" |     """Sound system with hierarchical pack-specific loading.""" | ||||||
|      |  | ||||||
|     def __init__(self, originalSounds, soundDir="sounds/", levelPackName=None): |     def __init__(self, originalSounds, soundDir="sounds/", levelPackName=None): | ||||||
|         """Initialize pack-specific sound system. |         """Initialize pack-specific sound system. | ||||||
|          |  | ||||||
|         Args: |         Args: | ||||||
|             originalSounds (dict): Original sound dictionary from initialize_gui |             originalSounds (dict): Original sound dictionary from initialize_gui | ||||||
|             soundDir (str): Base sound directory |             soundDir (str): Base sound directory | ||||||
| @@ -24,34 +24,33 @@ class PackSoundSystem(dict): | |||||||
|         """ |         """ | ||||||
|         # Initialize dict with original sounds |         # Initialize dict with original sounds | ||||||
|         super().__init__(originalSounds) |         super().__init__(originalSounds) | ||||||
|          |  | ||||||
|         self.soundDir = soundDir |         self.soundDir = soundDir | ||||||
|         self.levelPackName = levelPackName |         self.levelPackName = levelPackName | ||||||
|          |  | ||||||
|         # Load pack-specific sounds if pack name provided |         # Load pack-specific sounds if pack name provided | ||||||
|         if levelPackName: |         if levelPackName: | ||||||
|             self._load_pack_sounds() |             self._load_pack_sounds() | ||||||
|      |  | ||||||
|     def _load_pack_sounds(self): |     def _load_pack_sounds(self): | ||||||
|         """Load pack-specific sounds from sounds/[pack_name]/ directory.""" |         """Load pack-specific sounds from sounds/[pack_name]/ directory.""" | ||||||
|         packSoundDir = os.path.join(self.soundDir, self.levelPackName) |         packSoundDir = os.path.join(self.soundDir, self.levelPackName) | ||||||
|         if not os.path.exists(packSoundDir): |         if not os.path.exists(packSoundDir): | ||||||
|             return |             return | ||||||
|              |  | ||||||
|         try: |         try: | ||||||
|             for dirPath, _, fileNames in os.walk(packSoundDir): |             for dirPath, _, fileNames in os.walk(packSoundDir): | ||||||
|                 relPath = os.path.relpath(dirPath, packSoundDir) |                 relPath = os.path.relpath(dirPath, packSoundDir) | ||||||
|                  |  | ||||||
|                 for fileName in fileNames: |                 for fileName in fileNames: | ||||||
|                     if fileName.lower().endswith(('.ogg', '.wav')): |                     if fileName.lower().endswith((".ogg", ".wav")): | ||||||
|                         fullPath = os.path.join(dirPath, fileName) |                         fullPath = os.path.join(dirPath, fileName) | ||||||
|                         baseName = os.path.splitext(fileName)[0] |                         baseName = os.path.splitext(fileName)[0] | ||||||
|                          |  | ||||||
|                         # Create sound key same as base system |                         # Create sound key same as base system | ||||||
|                         soundKey = baseName if relPath == '.' else os.path.join(relPath, baseName).replace('\\', '/') |                         soundKey = baseName if relPath == "." else os.path.join(relPath, baseName).replace("\\", "/") | ||||||
|                         # Add/override sound in the main dictionary |                         # Add/override sound in the main dictionary | ||||||
|                         self[soundKey] = pygame.mixer.Sound(fullPath) |                         self[soundKey] = pygame.mixer.Sound(fullPath) | ||||||
|                          |  | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             print(f"Error loading pack sounds: {e}") |             print(f"Error loading pack sounds: {e}") | ||||||
|      |  | ||||||
|   | |||||||
| @@ -58,32 +58,36 @@ class Player: | |||||||
|         self.isInvincible = False |         self.isInvincible = False | ||||||
|         self.invincibilityStartTime = 0 |         self.invincibilityStartTime = 0 | ||||||
|         self.invincibilityDuration = 10000  # 10 seconds of invincibility |         self.invincibilityDuration = 10000  # 10 seconds of invincibility | ||||||
|          |  | ||||||
|         # Death state tracking (to prevent revival after death in same frame) |         # Death state tracking (to prevent revival after death in same frame) | ||||||
|         self.diedThisFrame = False |         self.diedThisFrame = False | ||||||
|  |  | ||||||
|         # Initialize starting weapon (rusty shovel) |         # Initialize starting weapon (rusty shovel) | ||||||
|         self.add_weapon(Weapon( |         self.add_weapon( | ||||||
|             name="rusty_shovel", |             Weapon( | ||||||
|             damage=2, |                 name="rusty_shovel", | ||||||
|             range=2, |                 damage=2, | ||||||
|             attackSound="player_shovel_attack", |                 range=2, | ||||||
|             hitSound="player_shovel_hit", |                 attackSound="player_shovel_attack", | ||||||
|             attackDuration=200  # 200ms attack duration |                 hitSound="player_shovel_hit", | ||||||
|         )) |                 attackDuration=200,  # 200ms attack duration | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         self.scoreboard = Scoreboard() |         self.scoreboard = Scoreboard() | ||||||
|  |  | ||||||
|     def should_play_footstep(self, currentTime): |     def should_play_footstep(self, currentTime): | ||||||
|         """Check if it's time to play a footstep sound""" |         """Check if it's time to play a footstep sound""" | ||||||
|         return (self.distanceSinceLastStep >= self.get_step_distance() and  |         return ( | ||||||
|                 currentTime - self.lastStepTime >= self.get_step_interval()) |             self.distanceSinceLastStep >= self.get_step_distance() | ||||||
|  |             and currentTime - self.lastStepTime >= self.get_step_interval() | ||||||
|  |         ) | ||||||
|  |  | ||||||
|     def duck(self): |     def duck(self): | ||||||
|         """Start ducking""" |         """Start ducking""" | ||||||
|         if not self.isDucking and not self.isJumping:  # Can't duck while jumping |         if not self.isDucking and not self.isJumping:  # Can't duck while jumping | ||||||
|             self.isDucking = True |             self.isDucking = True | ||||||
|             play_sound(self.sounds['duck']) |             play_sound(self.sounds["duck"]) | ||||||
|             return True |             return True | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
| @@ -91,14 +95,14 @@ class Player: | |||||||
|         """Stop ducking state and play sound""" |         """Stop ducking state and play sound""" | ||||||
|         if self.isDucking: |         if self.isDucking: | ||||||
|             self.isDucking = False |             self.isDucking = False | ||||||
|             play_sound(self.sounds['stand']) |             play_sound(self.sounds["stand"]) | ||||||
|  |  | ||||||
|     def update(self, currentTime): |     def update(self, currentTime): | ||||||
|         """Update player state""" |         """Update player state""" | ||||||
|         # Reset death flag at start of each frame |         # Reset death flag at start of each frame | ||||||
|         self.diedThisFrame = False |         self.diedThisFrame = False | ||||||
|          |  | ||||||
|         if hasattr(self, 'webPenaltyEndTime'): |         if hasattr(self, "webPenaltyEndTime"): | ||||||
|             if currentTime >= self.webPenaltyEndTime: |             if currentTime >= self.webPenaltyEndTime: | ||||||
|                 self.moveSpeed *= 2  # Restore speed |                 self.moveSpeed *= 2  # Restore speed | ||||||
|                 if self.currentWeapon: |                 if self.currentWeapon: | ||||||
| @@ -107,15 +111,17 @@ class Player: | |||||||
|  |  | ||||||
|         # Check invincibility status |         # Check invincibility status | ||||||
|         if self.isInvincible: |         if self.isInvincible: | ||||||
|             remaining_time = (self.invincibilityStartTime + self.invincibilityDuration - currentTime) / 1000  # Convert to seconds |             remaining_time = ( | ||||||
|  |                 self.invincibilityStartTime + self.invincibilityDuration - currentTime | ||||||
|  |             ) / 1000  # Convert to seconds | ||||||
|  |  | ||||||
|             # Handle countdown sounds |             # Handle countdown sounds | ||||||
|             if not hasattr(self, '_last_countdown'): |             if not hasattr(self, "_last_countdown"): | ||||||
|                 self._last_countdown = 4  # Start counting from 4 to catch 3,2,1 |                 self._last_countdown = 4  # Start counting from 4 to catch 3,2,1 | ||||||
|  |  | ||||||
|             current_second = int(remaining_time) |             current_second = int(remaining_time) | ||||||
|             if current_second < self._last_countdown and current_second <= 3 and current_second > 0: |             if current_second < self._last_countdown and current_second <= 3 and current_second > 0: | ||||||
|                 play_sound(self.sounds['end_of_invincibility_warning']) |                 play_sound(self.sounds["end_of_invincibility_warning"]) | ||||||
|                 self._last_countdown = current_second |                 self._last_countdown = current_second | ||||||
|  |  | ||||||
|             # Check if invincibility has expired |             # Check if invincibility has expired | ||||||
| @@ -128,7 +134,7 @@ class Player: | |||||||
|         """Activate invincibility from Hand of Glory""" |         """Activate invincibility from Hand of Glory""" | ||||||
|         self.isInvincible = True |         self.isInvincible = True | ||||||
|         self.invincibilityStartTime = pygame.time.get_ticks() |         self.invincibilityStartTime = pygame.time.get_ticks() | ||||||
|         if hasattr(self, '_last_countdown'): |         if hasattr(self, "_last_countdown"): | ||||||
|             del self._last_countdown  # Reset countdown if it exists |             del self._last_countdown  # Reset countdown if it exists | ||||||
|  |  | ||||||
|     def extra_life(self): |     def extra_life(self): | ||||||
| @@ -156,30 +162,26 @@ class Player: | |||||||
|             return None |             return None | ||||||
|  |  | ||||||
|         self._jack_o_lantern_count -= 1 |         self._jack_o_lantern_count -= 1 | ||||||
|         return { |         return {"type": "jack_o_lantern", "start_x": self.xPos, "direction": 1 if self.facingRight else -1} | ||||||
|             'type': 'jack_o_lantern', |  | ||||||
|             'start_x': self.xPos, |  | ||||||
|             'direction': 1 if self.facingRight else -1 |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     def get_step_distance(self): |     def get_step_distance(self): | ||||||
|         """Get step distance based on current speed""" |         """Get step distance based on current speed""" | ||||||
|         weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 |         weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 | ||||||
|         totalMultiplier = weaponBonus |         totalMultiplier = weaponBonus | ||||||
|          |  | ||||||
|         if self.isRunning or self.isJumping: |         if self.isRunning or self.isJumping: | ||||||
|             totalMultiplier *= self.runMultiplier |             totalMultiplier *= self.runMultiplier | ||||||
|              |  | ||||||
|         return self.baseStepDistance / totalMultiplier |         return self.baseStepDistance / totalMultiplier | ||||||
|  |  | ||||||
|     def get_step_interval(self): |     def get_step_interval(self): | ||||||
|         """Get minimum time between steps based on current speed""" |         """Get minimum time between steps based on current speed""" | ||||||
|         weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 |         weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 | ||||||
|         totalMultiplier = weaponBonus |         totalMultiplier = weaponBonus | ||||||
|          |  | ||||||
|         if self.isRunning or self.isJumping: |         if self.isRunning or self.isJumping: | ||||||
|             totalMultiplier *= self.runMultiplier |             totalMultiplier *= self.runMultiplier | ||||||
|              |  | ||||||
|         return self.baseStepInterval / totalMultiplier |         return self.baseStepInterval / totalMultiplier | ||||||
|  |  | ||||||
|     def get_health(self): |     def get_health(self): | ||||||
| @@ -198,8 +200,8 @@ class Player: | |||||||
|         """Calculate current speed based on state and weapon""" |         """Calculate current speed based on state and weapon""" | ||||||
|         baseSpeed = self.moveSpeed |         baseSpeed = self.moveSpeed | ||||||
|         weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 |         weaponBonus = self.currentWeapon.speedBonus if self.currentWeapon else 1.0 | ||||||
|          |  | ||||||
|         if self.isJumping or self.isRunning:  |         if self.isJumping or self.isRunning: | ||||||
|             return baseSpeed * self.runMultiplier * weaponBonus |             return baseSpeed * self.runMultiplier * weaponBonus | ||||||
|         return baseSpeed * weaponBonus |         return baseSpeed * weaponBonus | ||||||
|  |  | ||||||
| @@ -218,7 +220,7 @@ class Player: | |||||||
|  |  | ||||||
|         # Oops, allow healing while invincible. |         # Oops, allow healing while invincible. | ||||||
|         if self.isInvincible and value < old_health: |         if self.isInvincible and value < old_health: | ||||||
|             return   |             return | ||||||
|  |  | ||||||
|         self._health = max(0, value)  # Health can't go below 0 |         self._health = max(0, value)  # Health can't go below 0 | ||||||
|  |  | ||||||
| @@ -230,10 +232,10 @@ class Player: | |||||||
|             pygame.mixer.stop() |             pygame.mixer.stop() | ||||||
|             try: |             try: | ||||||
|                 pygame.mixer.music.stop() |                 pygame.mixer.music.stop() | ||||||
|             except: |             except Exception: | ||||||
|                 pass |                 pass | ||||||
|  |  | ||||||
|             cut_scene(self.sounds, 'lose_a_life') |             cut_scene(self.sounds, "lose_a_life") | ||||||
|  |  | ||||||
|     def set_max_health(self, value): |     def set_max_health(self, value): | ||||||
|         """Set max health""" |         """Set max health""" | ||||||
| @@ -279,23 +281,19 @@ class Player: | |||||||
|  |  | ||||||
|     def switch_to_weapon(self, weaponIndex): |     def switch_to_weapon(self, weaponIndex): | ||||||
|         """Switch to weapon by index (1=shovel, 2=broom, 3=nunchucks)""" |         """Switch to weapon by index (1=shovel, 2=broom, 3=nunchucks)""" | ||||||
|         weaponMap = { |         weaponMap = {1: "rusty_shovel", 2: "witch_broom", 3: "nunchucks"} | ||||||
|             1: "rusty_shovel", |  | ||||||
|             2: "witch_broom",  |  | ||||||
|             3: "nunchucks" |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         targetWeaponName = weaponMap.get(weaponIndex) |         targetWeaponName = weaponMap.get(weaponIndex) | ||||||
|         if not targetWeaponName: |         if not targetWeaponName: | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         # Find the weapon in player's inventory |         # Find the weapon in player's inventory | ||||||
|         for weapon in self.weapons: |         for weapon in self.weapons: | ||||||
|             if weapon.name == targetWeaponName: |             if weapon.name == targetWeaponName: | ||||||
|                 self.equip_weapon(weapon) |                 self.equip_weapon(weapon) | ||||||
|                 speak(weapon.name.replace('_', ' ')) |                 speak(weapon.name.replace("_", " ")) | ||||||
|                 return True |                 return True | ||||||
|          |  | ||||||
|         # Weapon not found in inventory |         # Weapon not found in inventory | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
| @@ -317,5 +315,3 @@ class Player: | |||||||
|         if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime): |         if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime): | ||||||
|             return None |             return None | ||||||
|         return self.currentWeapon.get_attack_range(self.xPos, self.facingRight) |         return self.currentWeapon.get_attack_range(self.xPos, self.facingRight) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,14 +5,10 @@ from libstormgames import * | |||||||
| from src.object import Object | from src.object import Object | ||||||
| from src.weapon import Weapon | from src.weapon import Weapon | ||||||
|  |  | ||||||
|  |  | ||||||
| class PowerUp(Object): | class PowerUp(Object): | ||||||
|     def __init__(self, x, y, item_type, sounds, direction, left_boundary=1, right_boundary=100): |     def __init__(self, x, y, item_type, sounds, direction, left_boundary=1, right_boundary=100): | ||||||
|         super().__init__( |         super().__init__(x, y, item_type, isStatic=False, isCollectible=True, isHazard=False) | ||||||
|             x, y, item_type, |  | ||||||
|             isStatic=False, |  | ||||||
|             isCollectible=True, |  | ||||||
|             isHazard=False |  | ||||||
|         ) |  | ||||||
|         self.sounds = sounds |         self.sounds = sounds | ||||||
|         self.direction = direction |         self.direction = direction | ||||||
|         self.speed = 0.049  # Base movement speed |         self.speed = 0.049  # Base movement speed | ||||||
| @@ -58,35 +54,32 @@ class PowerUp(Object): | |||||||
|  |  | ||||||
|     def apply_effect(self, player, level=None): |     def apply_effect(self, player, level=None): | ||||||
|         """Apply the item's effect when collected""" |         """Apply the item's effect when collected""" | ||||||
|         if self.item_type == 'hand_of_glory': |         if self.item_type == "hand_of_glory": | ||||||
|             player.start_invincibility() |             player.start_invincibility() | ||||||
|         elif self.item_type == 'cauldron': |         elif self.item_type == "cauldron": | ||||||
|             player.restore_health() |             player.restore_health() | ||||||
|         elif self.item_type == 'guts': |         elif self.item_type == "guts": | ||||||
|             player.add_guts() |             player.add_guts() | ||||||
|             player.collectedItems.append('guts') |             player.collectedItems.append("guts") | ||||||
|             self.check_for_nunchucks(player) |             self.check_for_nunchucks(player) | ||||||
|         elif self.item_type == 'jack_o_lantern': |         elif self.item_type == "jack_o_lantern": | ||||||
|             player.add_jack_o_lantern() |             player.add_jack_o_lantern() | ||||||
|         elif self.item_type == 'extra_life': |         elif self.item_type == "extra_life": | ||||||
|             # Don't give extra lives in survival mode |             # Don't give extra lives in survival mode | ||||||
|             if level and level.levelId == 999: |             if level and level.levelId == 999: | ||||||
|                 # In survival mode, give bonus score instead |                 # In survival mode, give bonus score instead | ||||||
|                 level.levelScore += 2000 |                 level.levelScore += 2000 | ||||||
|                 speak("Extra life found! Bonus score in survival mode!") |                 speak("Extra life found! Bonus score in survival mode!") | ||||||
|                 play_sound(self.sounds.get('survivor_bonus', 'get_extra_life'))  # Use survivor_bonus sound if available |                 play_sound(self.sounds.get("survivor_bonus", "get_extra_life"))  # Use survivor_bonus sound if available | ||||||
|             else: |             else: | ||||||
|                 player.extra_life() |                 player.extra_life() | ||||||
|         elif self.item_type == 'shin_bone':  # Add shin bone handling |         elif self.item_type == "shin_bone":  # Add shin bone handling | ||||||
|             player.shinBoneCount += 1 |             player.shinBoneCount += 1 | ||||||
|             player._coins += 5 |             player._coins += 5 | ||||||
|             player.add_save_bone_dust(5)  # Add to save bone dust counter too |             player.add_save_bone_dust(5)  # Add to save bone dust counter too | ||||||
|             if player.get_health() < player.get_max_health(): |             if player.get_health() < player.get_max_health(): | ||||||
|                 player.set_health(min( |                 player.set_health(min(player.get_health() + 1, player.get_max_health())) | ||||||
|                     player.get_health() + 1, |  | ||||||
|                     player.get_max_health() |  | ||||||
|                 )) |  | ||||||
|              |  | ||||||
|             # Check for 100 coin bonus after adding shin bone coins |             # Check for 100 coin bonus after adding shin bone coins | ||||||
|             if player._coins >= 100: |             if player._coins >= 100: | ||||||
|                 # Only give extra lives in story mode, not survival mode (level_id 999) |                 # Only give extra lives in story mode, not survival mode (level_id 999) | ||||||
| @@ -95,20 +88,22 @@ class PowerUp(Object): | |||||||
|                     player._coins = 0 |                     player._coins = 0 | ||||||
|                     player._lives += 1 |                     player._lives += 1 | ||||||
|                     level.levelScore += 1000 |                     level.levelScore += 1000 | ||||||
|                     play_sound(self.sounds['get_extra_life']) |                     play_sound(self.sounds["get_extra_life"]) | ||||||
|                 else: |                 else: | ||||||
|                     # In survival mode, reset coin counter but give bonus score instead |                     # In survival mode, reset coin counter but give bonus score instead | ||||||
|                     player._coins = 0 |                     player._coins = 0 | ||||||
|                     level.levelScore += 2000  # Double score bonus instead of extra life |                     level.levelScore += 2000  # Double score bonus instead of extra life | ||||||
|                     speak("100 bone dust collected! Bonus score!") |                     speak("100 bone dust collected! Bonus score!") | ||||||
|                     play_sound(self.sounds.get('survivor_bonus', 'bone_dust'))  # Use survivor_bonus sound if available, fallback to bone_dust |                     play_sound( | ||||||
|              |                         self.sounds.get("survivor_bonus", "bone_dust") | ||||||
|  |                     )  # Use survivor_bonus sound if available, fallback to bone_dust | ||||||
|  |  | ||||||
|             self.check_for_nunchucks(player) |             self.check_for_nunchucks(player) | ||||||
|         elif self.item_type == 'witch_broom': |         elif self.item_type == "witch_broom": | ||||||
|             broomWeapon = Weapon.create_witch_broom() |             broomWeapon = Weapon.create_witch_broom() | ||||||
|             player.add_weapon(broomWeapon) |             player.add_weapon(broomWeapon) | ||||||
|             player.equip_weapon(broomWeapon) |             player.equip_weapon(broomWeapon) | ||||||
|         elif self.item_type == 'spiderweb': |         elif self.item_type == "spiderweb": | ||||||
|             # Bounce player back (happens even if invincible) |             # Bounce player back (happens even if invincible) | ||||||
|             player.xPos -= 3 if player.xPos > self.xPos else -3 |             player.xPos -= 3 if player.xPos > self.xPos else -3 | ||||||
|  |  | ||||||
| @@ -122,7 +117,7 @@ class PowerUp(Object): | |||||||
|                 player.webPenaltyEndTime = pygame.time.get_ticks() + 15000 |                 player.webPenaltyEndTime = pygame.time.get_ticks() + 15000 | ||||||
|  |  | ||||||
|             # Tell level to spawn a spider |             # Tell level to spawn a spider | ||||||
|             if hasattr(self, 'level'): |             if hasattr(self, "level"): | ||||||
|                 self.level.spawn_spider(self.xPos, self.yPos) |                 self.level.spawn_spider(self.xPos, self.yPos) | ||||||
|  |  | ||||||
|         # Stop movement sound when collected |         # Stop movement sound when collected | ||||||
| @@ -131,20 +126,20 @@ class PowerUp(Object): | |||||||
|             self.channel = None |             self.channel = None | ||||||
|  |  | ||||||
|         # Item tracking |         # Item tracking | ||||||
|         player.stats.update_stat('Items collected', 1) |         player.stats.update_stat("Items collected", 1) | ||||||
|  |  | ||||||
|     def check_for_nunchucks(self, player): |     def check_for_nunchucks(self, player): | ||||||
|         """Check if player has materials for nunchucks and create if conditions are met""" |         """Check if player has materials for nunchucks and create if conditions are met""" | ||||||
|         if (player.shinBoneCount >= 2 and  |         if ( | ||||||
|             'guts' in player.collectedItems and  |             player.shinBoneCount >= 2 | ||||||
|             not any(weapon.name == "nunchucks" for weapon in player.weapons)): |             and "guts" in player.collectedItems | ||||||
|  |             and not any(weapon.name == "nunchucks" for weapon in player.weapons) | ||||||
|  |         ): | ||||||
|             nunchucksWeapon = Weapon.create_nunchucks() |             nunchucksWeapon = Weapon.create_nunchucks() | ||||||
|             player.add_weapon(nunchucksWeapon) |             player.add_weapon(nunchucksWeapon) | ||||||
|             player.equip_weapon(nunchucksWeapon) |             player.equip_weapon(nunchucksWeapon) | ||||||
|             basePoints = nunchucksWeapon.damage * 1000 |             basePoints = nunchucksWeapon.damage * 1000 | ||||||
|             rangeModifier = nunchucksWeapon.range * 500 |             rangeModifier = nunchucksWeapon.range * 500 | ||||||
|             player.scoreboard.increase_score(basePoints + rangeModifier) |             player.scoreboard.increase_score(basePoints + rangeModifier) | ||||||
|             play_sound(self.sounds['get_nunchucks']) |             play_sound(self.sounds["get_nunchucks"]) | ||||||
|             player.stats.update_stat('Items collected', 1) |             player.stats.update_stat("Items collected", 1) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  |  | ||||||
| class Projectile: | class Projectile: | ||||||
|     def __init__(self, projectile_type, start_x, direction): |     def __init__(self, projectile_type, start_x, direction): | ||||||
|         self.type = projectile_type |         self.type = projectile_type | ||||||
| @@ -29,5 +30,3 @@ class Projectile: | |||||||
|         """Handle hitting an enemy""" |         """Handle hitting an enemy""" | ||||||
|         enemy.take_damage(self.damage) |         enemy.take_damage(self.damage) | ||||||
|         self.isActive = False  # Projectile is destroyed on hit |         self.isActive = False  # Projectile is destroyed on hit | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -11,8 +11,8 @@ class SaveManager: | |||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         """Initialize save manager with XDG-compliant save directory""" |         """Initialize save manager with XDG-compliant save directory""" | ||||||
|         # Use XDG_CONFIG_HOME or default to ~/.config |         # Use XDG_CONFIG_HOME or default to ~/.config | ||||||
|         config_home = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config')) |         config_home = os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")) | ||||||
|         self.save_dir = Path(config_home) / 'storm-games' / 'wicked-quest' |         self.save_dir = Path(config_home) / "storm-games" / "wicked-quest" | ||||||
|         self.save_dir.mkdir(parents=True, exist_ok=True) |         self.save_dir.mkdir(parents=True, exist_ok=True) | ||||||
|         self.max_saves = 10 |         self.max_saves = 10 | ||||||
|  |  | ||||||
| @@ -24,7 +24,7 @@ class SaveManager: | |||||||
|         # Validate required parameters |         # Validate required parameters | ||||||
|         if current_game is None: |         if current_game is None: | ||||||
|             return False, "No game selected to save" |             return False, "No game selected to save" | ||||||
|          |  | ||||||
|         if current_level is None: |         if current_level is None: | ||||||
|             return False, "No current level to save" |             return False, "No current level to save" | ||||||
|  |  | ||||||
| @@ -34,30 +34,30 @@ class SaveManager: | |||||||
|  |  | ||||||
|         # Create save data |         # Create save data | ||||||
|         save_data = { |         save_data = { | ||||||
|             'player_state': { |             "player_state": { | ||||||
|                 'xPos': player.xPos, |                 "xPos": player.xPos, | ||||||
|                 'yPos': player.yPos, |                 "yPos": player.yPos, | ||||||
|                 'health': player._health, |                 "health": player._health, | ||||||
|                 'maxHealth': player._maxHealth, |                 "maxHealth": player._maxHealth, | ||||||
|                 'lives': player._lives, |                 "lives": player._lives, | ||||||
|                 'coins': player._coins, |                 "coins": player._coins, | ||||||
|                 'saveBoneDust': player._saveBoneDust, |                 "saveBoneDust": player._saveBoneDust, | ||||||
|                 'jackOLanternCount': player._jack_o_lantern_count, |                 "jackOLanternCount": player._jack_o_lantern_count, | ||||||
|                 'shinBoneCount': player.shinBoneCount, |                 "shinBoneCount": player.shinBoneCount, | ||||||
|                 'inventory': player.inventory, |                 "inventory": player.inventory, | ||||||
|                 'collectedItems': player.collectedItems, |                 "collectedItems": player.collectedItems, | ||||||
|                 'weapons': self._serialize_weapons(player.weapons), |                 "weapons": self._serialize_weapons(player.weapons), | ||||||
|                 'currentWeaponName': player.currentWeapon.name if player.currentWeapon else None, |                 "currentWeaponName": player.currentWeapon.name if player.currentWeapon else None, | ||||||
|                 'stats': self._serialize_stats(player.stats), |                 "stats": self._serialize_stats(player.stats), | ||||||
|                 'scoreboard': self._serialize_scoreboard(player.scoreboard) |                 "scoreboard": self._serialize_scoreboard(player.scoreboard), | ||||||
|             }, |             }, | ||||||
|             'game_state': { |             "game_state": { | ||||||
|                 'currentLevel': current_level, |                 "currentLevel": current_level, | ||||||
|                 'currentGame': current_game, |                 "currentGame": current_game, | ||||||
|                 'gameStartTime': game_start_time, |                 "gameStartTime": game_start_time, | ||||||
|                 'saveTime': datetime.now() |                 "saveTime": datetime.now(), | ||||||
|             }, |             }, | ||||||
|             'version': '1.0' |             "version": "1.0", | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         # Generate filename with timestamp |         # Generate filename with timestamp | ||||||
| @@ -67,26 +67,26 @@ class SaveManager: | |||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             # Write to temporary file first, then rename for atomic operation |             # Write to temporary file first, then rename for atomic operation | ||||||
|             temp_filepath = filepath.with_suffix('.tmp') |             temp_filepath = filepath.with_suffix(".tmp") | ||||||
|              |  | ||||||
|             with open(temp_filepath, 'wb') as f: |             with open(temp_filepath, "wb") as f: | ||||||
|                 pickle.dump(save_data, f) |                 pickle.dump(save_data, f) | ||||||
|                 f.flush()  # Ensure data is written to disk |                 f.flush()  # Ensure data is written to disk | ||||||
|                 os.fsync(f.fileno())  # Force write to disk |                 os.fsync(f.fileno())  # Force write to disk | ||||||
|              |  | ||||||
|             # Atomic rename (replaces old file if it exists) |             # Atomic rename (replaces old file if it exists) | ||||||
|             temp_filepath.rename(filepath) |             temp_filepath.rename(filepath) | ||||||
|              |  | ||||||
|             # Clean up old saves if we exceed max_saves |             # Clean up old saves if we exceed max_saves | ||||||
|             self._cleanup_old_saves() |             self._cleanup_old_saves() | ||||||
|              |  | ||||||
|             return True, f"Game saved to {filename}" |             return True, f"Game saved to {filename}" | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
|             # Clean up temp file if it exists |             # Clean up temp file if it exists | ||||||
|             if temp_filepath.exists(): |             if temp_filepath.exists(): | ||||||
|                 try: |                 try: | ||||||
|                     temp_filepath.unlink() |                     temp_filepath.unlink() | ||||||
|                 except: |                 except Exception: | ||||||
|                     pass |                     pass | ||||||
|             return False, f"Failed to save game: {str(e)}" |             return False, f"Failed to save game: {str(e)}" | ||||||
|  |  | ||||||
| @@ -94,132 +94,136 @@ class SaveManager: | |||||||
|         """Serialize weapons for saving""" |         """Serialize weapons for saving""" | ||||||
|         serialized = [] |         serialized = [] | ||||||
|         for weapon in weapons: |         for weapon in weapons: | ||||||
|             serialized.append({ |             serialized.append( | ||||||
|                 'name': weapon.name, |                 { | ||||||
|                 'damage': weapon.damage, |                     "name": weapon.name, | ||||||
|                 'range': weapon.range, |                     "damage": weapon.damage, | ||||||
|                 'attackSound': weapon.attackSound, |                     "range": weapon.range, | ||||||
|                 'hitSound': weapon.hitSound, |                     "attackSound": weapon.attackSound, | ||||||
|                 'attackDuration': weapon.attackDuration, |                     "hitSound": weapon.hitSound, | ||||||
|                 'speedBonus': getattr(weapon, 'speedBonus', 1.0), |                     "attackDuration": weapon.attackDuration, | ||||||
|                 'jumpDurationBonus': getattr(weapon, 'jumpDurationBonus', 1.0) |                     "speedBonus": getattr(weapon, "speedBonus", 1.0), | ||||||
|             }) |                     "jumpDurationBonus": getattr(weapon, "jumpDurationBonus", 1.0), | ||||||
|  |                 } | ||||||
|  |             ) | ||||||
|         return serialized |         return serialized | ||||||
|  |  | ||||||
|     def _deserialize_weapons(self, weapon_data): |     def _deserialize_weapons(self, weapon_data): | ||||||
|         """Deserialize weapons from save data""" |         """Deserialize weapons from save data""" | ||||||
|         from src.weapon import Weapon |         from src.weapon import Weapon | ||||||
|  |  | ||||||
|         weapons = [] |         weapons = [] | ||||||
|         for data in weapon_data: |         for data in weapon_data: | ||||||
|             # Handle backward compatibility for old saves |             # Handle backward compatibility for old saves | ||||||
|             speedBonus = data.get('speedBonus', 1.0) |             speedBonus = data.get("speedBonus", 1.0) | ||||||
|             jumpDurationBonus = data.get('jumpDurationBonus', 1.0) |             jumpDurationBonus = data.get("jumpDurationBonus", 1.0) | ||||||
|              |  | ||||||
|             # For old saves, restore proper bonuses for specific weapons |             # For old saves, restore proper bonuses for specific weapons | ||||||
|             if data['name'] == 'witch_broom' and speedBonus == 1.0: |             if data["name"] == "witch_broom" and speedBonus == 1.0: | ||||||
|                 speedBonus = 1.17 |                 speedBonus = 1.17 | ||||||
|                 jumpDurationBonus = 1.25 |                 jumpDurationBonus = 1.25 | ||||||
|              |  | ||||||
|             weapon = Weapon( |             weapon = Weapon( | ||||||
|                 name=data['name'], |                 name=data["name"], | ||||||
|                 damage=data['damage'], |                 damage=data["damage"], | ||||||
|                 range=data['range'], |                 range=data["range"], | ||||||
|                 attackSound=data['attackSound'], |                 attackSound=data["attackSound"], | ||||||
|                 hitSound=data['hitSound'], |                 hitSound=data["hitSound"], | ||||||
|                 attackDuration=data['attackDuration'], |                 attackDuration=data["attackDuration"], | ||||||
|                 speedBonus=speedBonus, |                 speedBonus=speedBonus, | ||||||
|                 jumpDurationBonus=jumpDurationBonus |                 jumpDurationBonus=jumpDurationBonus, | ||||||
|             ) |             ) | ||||||
|             weapons.append(weapon) |             weapons.append(weapon) | ||||||
|         return weapons |         return weapons | ||||||
|  |  | ||||||
|     def _serialize_stats(self, stats): |     def _serialize_stats(self, stats): | ||||||
|         """Serialize stats for saving""" |         """Serialize stats for saving""" | ||||||
|         return { |         return {"total": stats.total.copy(), "level": stats.level.copy()} | ||||||
|             'total': stats.total.copy(), |  | ||||||
|             'level': stats.level.copy() |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     def _deserialize_stats(self, stats_data): |     def _deserialize_stats(self, stats_data): | ||||||
|         """Deserialize stats from save data""" |         """Deserialize stats from save data""" | ||||||
|         from src.stat_tracker import StatTracker |         from src.stat_tracker import StatTracker | ||||||
|  |  | ||||||
|         stats = StatTracker() |         stats = StatTracker() | ||||||
|         if 'total' in stats_data: |         if "total" in stats_data: | ||||||
|             stats.total.update(stats_data['total']) |             stats.total.update(stats_data["total"]) | ||||||
|         if 'level' in stats_data: |         if "level" in stats_data: | ||||||
|             stats.level.update(stats_data['level']) |             stats.level.update(stats_data["level"]) | ||||||
|         return stats |         return stats | ||||||
|  |  | ||||||
|     def _serialize_scoreboard(self, scoreboard): |     def _serialize_scoreboard(self, scoreboard): | ||||||
|         """Serialize scoreboard for saving""" |         """Serialize scoreboard for saving""" | ||||||
|         return { |         return { | ||||||
|             'currentScore': getattr(scoreboard, 'currentScore', 0), |             "currentScore": getattr(scoreboard, "currentScore", 0), | ||||||
|             'highScores': getattr(scoreboard, 'highScores', []) |             "highScores": getattr(scoreboard, "highScores", []), | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     def _deserialize_scoreboard(self, scoreboard_data): |     def _deserialize_scoreboard(self, scoreboard_data): | ||||||
|         """Deserialize scoreboard from save data""" |         """Deserialize scoreboard from save data""" | ||||||
|         from libstormgames import Scoreboard |         from libstormgames import Scoreboard | ||||||
|  |  | ||||||
|         scoreboard = Scoreboard() |         scoreboard = Scoreboard() | ||||||
|         if 'currentScore' in scoreboard_data: |         if "currentScore" in scoreboard_data: | ||||||
|             scoreboard.currentScore = scoreboard_data['currentScore'] |             scoreboard.currentScore = scoreboard_data["currentScore"] | ||||||
|         if 'highScores' in scoreboard_data: |         if "highScores" in scoreboard_data: | ||||||
|             scoreboard.highScores = scoreboard_data['highScores'] |             scoreboard.highScores = scoreboard_data["highScores"] | ||||||
|         return scoreboard |         return scoreboard | ||||||
|  |  | ||||||
|     def get_save_files(self): |     def get_save_files(self): | ||||||
|         """Get list of save files with metadata""" |         """Get list of save files with metadata""" | ||||||
|         save_files = [] |         save_files = [] | ||||||
|         pattern = str(self.save_dir / "save_*.pickle") |         pattern = str(self.save_dir / "save_*.pickle") | ||||||
|          |  | ||||||
|         for filepath in glob.glob(pattern): |         for filepath in glob.glob(pattern): | ||||||
|             try: |             try: | ||||||
|                 with open(filepath, 'rb') as f: |                 with open(filepath, "rb") as f: | ||||||
|                     save_data = pickle.load(f) |                     save_data = pickle.load(f) | ||||||
|                  |  | ||||||
|                 # Validate save data structure |                 # Validate save data structure | ||||||
|                 if not self._validate_save_data(save_data): |                 if not self._validate_save_data(save_data): | ||||||
|                     print(f"Invalid save file structure: {filepath}") |                     print(f"Invalid save file structure: {filepath}") | ||||||
|                     continue |                     continue | ||||||
|                  |  | ||||||
|                 # Extract save info |                 # Extract save info | ||||||
|                 save_time = save_data['game_state']['saveTime'] |                 save_time = save_data["game_state"]["saveTime"] | ||||||
|                 level = save_data['game_state']['currentLevel'] |                 level = save_data["game_state"]["currentLevel"] | ||||||
|                 game_name = save_data['game_state']['currentGame'] |                 game_name = save_data["game_state"]["currentGame"] | ||||||
|                  |  | ||||||
|                 # Format display name |                 # Format display name | ||||||
|                 formatted_time = save_time.strftime("%B %d %I:%M%p") |                 formatted_time = save_time.strftime("%B %d %I:%M%p") | ||||||
|                 display_name = f"{formatted_time} {game_name} Level {level}" |                 display_name = f"{formatted_time} {game_name} Level {level}" | ||||||
|                  |  | ||||||
|                 save_files.append({ |                 save_files.append( | ||||||
|                     'filepath': filepath, |                     { | ||||||
|                     'display_name': display_name, |                         "filepath": filepath, | ||||||
|                     'save_time': save_time, |                         "display_name": display_name, | ||||||
|                     'level': level, |                         "save_time": save_time, | ||||||
|                     'game_name': game_name, |                         "level": level, | ||||||
|                     'save_data': save_data |                         "game_name": game_name, | ||||||
|                 }) |                         "save_data": save_data, | ||||||
|  |                     } | ||||||
|  |                 ) | ||||||
|             except (pickle.PickleError, EOFError, OSError) as e: |             except (pickle.PickleError, EOFError, OSError) as e: | ||||||
|                 print(f"Corrupted save file {filepath}: {e}") |                 print(f"Corrupted save file {filepath}: {e}") | ||||||
|                 # Try to remove corrupted save file |                 # Try to remove corrupted save file | ||||||
|                 try: |                 try: | ||||||
|                     os.remove(filepath) |                     os.remove(filepath) | ||||||
|                     print(f"Removed corrupted save file: {filepath}") |                     print(f"Removed corrupted save file: {filepath}") | ||||||
|                 except: |                 except Exception: | ||||||
|                     pass |                     pass | ||||||
|                 continue |                 continue | ||||||
|             except Exception as e: |             except Exception as e: | ||||||
|                 print(f"Error reading save file {filepath}: {e}") |                 print(f"Error reading save file {filepath}: {e}") | ||||||
|                 continue |                 continue | ||||||
|          |  | ||||||
|         # Sort by save time (newest first) |         # Sort by save time (newest first) | ||||||
|         save_files.sort(key=lambda x: x['save_time'], reverse=True) |         save_files.sort(key=lambda x: x["save_time"], reverse=True) | ||||||
|         return save_files |         return save_files | ||||||
|  |  | ||||||
|     def load_save(self, filepath): |     def load_save(self, filepath): | ||||||
|         """Load game state from save file""" |         """Load game state from save file""" | ||||||
|         try: |         try: | ||||||
|             with open(filepath, 'rb') as f: |             with open(filepath, "rb") as f: | ||||||
|                 save_data = pickle.load(f) |                 save_data = pickle.load(f) | ||||||
|             return True, save_data |             return True, save_data | ||||||
|         except Exception as e: |         except Exception as e: | ||||||
| @@ -227,55 +231,57 @@ class SaveManager: | |||||||
|  |  | ||||||
|     def restore_player_state(self, player, save_data): |     def restore_player_state(self, player, save_data): | ||||||
|         """Restore player state from save data""" |         """Restore player state from save data""" | ||||||
|         player_state = save_data['player_state'] |         player_state = save_data["player_state"] | ||||||
|          |  | ||||||
|         # Restore basic attributes |         # Restore basic attributes | ||||||
|         player.xPos = player_state['xPos'] |         player.xPos = player_state["xPos"] | ||||||
|         player.yPos = player_state['yPos'] |         player.yPos = player_state["yPos"] | ||||||
|         player._health = player_state['health'] |         player._health = player_state["health"] | ||||||
|         player._maxHealth = player_state['maxHealth'] |         player._maxHealth = player_state["maxHealth"] | ||||||
|         player._lives = player_state['lives'] |         player._lives = player_state["lives"] | ||||||
|         player._coins = player_state['coins'] |         player._coins = player_state["coins"] | ||||||
|         player._saveBoneDust = player_state['saveBoneDust'] |         player._saveBoneDust = player_state["saveBoneDust"] | ||||||
|         player._jack_o_lantern_count = player_state['jackOLanternCount'] |         player._jack_o_lantern_count = player_state["jackOLanternCount"] | ||||||
|         player.shinBoneCount = player_state['shinBoneCount'] |         player.shinBoneCount = player_state["shinBoneCount"] | ||||||
|         player.inventory = player_state['inventory'] |         player.inventory = player_state["inventory"] | ||||||
|         player.collectedItems = player_state['collectedItems'] |         player.collectedItems = player_state["collectedItems"] | ||||||
|          |  | ||||||
|         # Restore weapons |         # Restore weapons | ||||||
|         player.weapons = self._deserialize_weapons(player_state['weapons']) |         player.weapons = self._deserialize_weapons(player_state["weapons"]) | ||||||
|          |  | ||||||
|         # Restore current weapon |         # Restore current weapon | ||||||
|         current_weapon_name = player_state.get('currentWeaponName') |         current_weapon_name = player_state.get("currentWeaponName") | ||||||
|         if current_weapon_name: |         if current_weapon_name: | ||||||
|             for weapon in player.weapons: |             for weapon in player.weapons: | ||||||
|                 if weapon.name == current_weapon_name: |                 if weapon.name == current_weapon_name: | ||||||
|                     player.currentWeapon = weapon |                     player.currentWeapon = weapon | ||||||
|                     break |                     break | ||||||
|          |  | ||||||
|         # Restore stats |         # Restore stats | ||||||
|         if 'stats' in player_state: |         if "stats" in player_state: | ||||||
|             player.stats = self._deserialize_stats(player_state['stats']) |             player.stats = self._deserialize_stats(player_state["stats"]) | ||||||
|         else: |         else: | ||||||
|             from src.stat_tracker import StatTracker |             from src.stat_tracker import StatTracker | ||||||
|  |  | ||||||
|             player.stats = StatTracker() |             player.stats = StatTracker() | ||||||
|          |  | ||||||
|         # Restore scoreboard |         # Restore scoreboard | ||||||
|         if 'scoreboard' in player_state: |         if "scoreboard" in player_state: | ||||||
|             player.scoreboard = self._deserialize_scoreboard(player_state['scoreboard']) |             player.scoreboard = self._deserialize_scoreboard(player_state["scoreboard"]) | ||||||
|         else: |         else: | ||||||
|             from libstormgames import Scoreboard |             from libstormgames import Scoreboard | ||||||
|  |  | ||||||
|             player.scoreboard = Scoreboard() |             player.scoreboard = Scoreboard() | ||||||
|  |  | ||||||
|     def _cleanup_old_saves(self): |     def _cleanup_old_saves(self): | ||||||
|         """Remove old save files if we exceed max_saves""" |         """Remove old save files if we exceed max_saves""" | ||||||
|         save_files = self.get_save_files() |         save_files = self.get_save_files() | ||||||
|          |  | ||||||
|         if len(save_files) > self.max_saves: |         if len(save_files) > self.max_saves: | ||||||
|             # Remove oldest saves |             # Remove oldest saves | ||||||
|             for save_file in save_files[self.max_saves:]: |             for save_file in save_files[self.max_saves:]: | ||||||
|                 try: |                 try: | ||||||
|                     os.remove(save_file['filepath']) |                     os.remove(save_file["filepath"]) | ||||||
|                 except Exception as e: |                 except Exception as e: | ||||||
|                     print(f"Error removing old save {save_file['filepath']}: {e}") |                     print(f"Error removing old save {save_file['filepath']}: {e}") | ||||||
|  |  | ||||||
| @@ -283,25 +289,24 @@ class SaveManager: | |||||||
|         """Validate that save data has required structure""" |         """Validate that save data has required structure""" | ||||||
|         try: |         try: | ||||||
|             # Check for required top-level keys |             # Check for required top-level keys | ||||||
|             required_keys = ['player_state', 'game_state', 'version'] |             required_keys = ["player_state", "game_state", "version"] | ||||||
|             if not all(key in save_data for key in required_keys): |             if not all(key in save_data for key in required_keys): | ||||||
|                 return False |                 return False | ||||||
|              |  | ||||||
|             # Check player_state structure |             # Check player_state structure | ||||||
|             player_required = ['xPos', 'yPos', 'health', 'maxHealth', 'lives', 'coins', 'saveBoneDust'] |             player_required = ["xPos", "yPos", "health", "maxHealth", "lives", "coins", "saveBoneDust"] | ||||||
|             if not all(key in save_data['player_state'] for key in player_required): |             if not all(key in save_data["player_state"] for key in player_required): | ||||||
|                 return False |                 return False | ||||||
|              |  | ||||||
|             # Check game_state structure |             # Check game_state structure | ||||||
|             game_required = ['currentLevel', 'currentGame', 'gameStartTime', 'saveTime'] |             game_required = ["currentLevel", "currentGame", "gameStartTime", "saveTime"] | ||||||
|             if not all(key in save_data['game_state'] for key in game_required): |             if not all(key in save_data["game_state"] for key in game_required): | ||||||
|                 return False |                 return False | ||||||
|              |  | ||||||
|             return True |             return True | ||||||
|         except: |         except Exception: | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|     def has_saves(self): |     def has_saves(self): | ||||||
|         """Check if any save files exist""" |         """Check if any save files exist""" | ||||||
|         return len(self.get_save_files()) > 0 |         return len(self.get_save_files()) > 0 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,17 +5,13 @@ import random | |||||||
| from libstormgames import * | from libstormgames import * | ||||||
| from src.object import Object | from src.object import Object | ||||||
|  |  | ||||||
|  |  | ||||||
| class SkullStorm(Object): | class SkullStorm(Object): | ||||||
|     """Handles falling skulls within a specified range.""" |     """Handles falling skulls within a specified range.""" | ||||||
|  |  | ||||||
|     def __init__(self, xRange, y, sounds, damage, maxSkulls=3, minFreq=2, maxFreq=5): |     def __init__(self, xRange, y, sounds, damage, maxSkulls=3, minFreq=2, maxFreq=5): | ||||||
|         super().__init__( |         super().__init__( | ||||||
|             xRange, |             xRange, y, "", isStatic=True, isCollectible=False, isHazard=False  # No ambient sound for the skull storm | ||||||
|             y, |  | ||||||
|             "",  # No ambient sound for the skull storm |  | ||||||
|             isStatic=True, |  | ||||||
|             isCollectible=False, |  | ||||||
|             isHazard=False |  | ||||||
|         ) |         ) | ||||||
|         self.sounds = sounds |         self.sounds = sounds | ||||||
|         self.damage = damage |         self.damage = damage | ||||||
| @@ -37,7 +33,7 @@ class SkullStorm(Object): | |||||||
|         inRange = self.xRange[0] <= player.xPos <= self.xRange[1] |         inRange = self.xRange[0] <= player.xPos <= self.xRange[1] | ||||||
|         if inRange and not self.playerInRange: |         if inRange and not self.playerInRange: | ||||||
|             # Player just entered range - play the warning sound |             # Player just entered range - play the warning sound | ||||||
|             play_sound(self.sounds['skull_storm']) |             play_sound(self.sounds["skull_storm"]) | ||||||
|             self.playerInRange = True |             self.playerInRange = True | ||||||
|         elif not inRange and self.playerInRange:  # Only speak when actually leaving range |         elif not inRange and self.playerInRange:  # Only speak when actually leaving range | ||||||
|             # Player just left range |             # Player just left range | ||||||
| @@ -46,8 +42,8 @@ class SkullStorm(Object): | |||||||
|  |  | ||||||
|             # Clear any active skulls when player leaves the range |             # Clear any active skulls when player leaves the range | ||||||
|             for skull in self.activeSkulls[:]: |             for skull in self.activeSkulls[:]: | ||||||
|                 if skull['channel']: |                 if skull["channel"]: | ||||||
|                     obj_stop(skull['channel']) |                     obj_stop(skull["channel"]) | ||||||
|             self.activeSkulls = []  # Reset the list of active skulls |             self.activeSkulls = []  # Reset the list of active skulls | ||||||
|  |  | ||||||
|         if not inRange: |         if not inRange: | ||||||
| @@ -55,29 +51,28 @@ class SkullStorm(Object): | |||||||
|  |  | ||||||
|         # Update existing skulls |         # Update existing skulls | ||||||
|         for skull in self.activeSkulls[:]:  # Copy list to allow removal |         for skull in self.activeSkulls[:]:  # Copy list to allow removal | ||||||
|             if currentTime >= skull['land_time']: |             if currentTime >= skull["land_time"]: | ||||||
|                 # Skull has landed |                 # Skull has landed | ||||||
|                 self.handle_landing(skull, player) |                 self.handle_landing(skull, player) | ||||||
|                 self.activeSkulls.remove(skull) |                 self.activeSkulls.remove(skull) | ||||||
|             else: |             else: | ||||||
|                 # Update falling sound |                 # Update falling sound | ||||||
|                 timeElapsed = currentTime - skull['start_time'] |                 timeElapsed = currentTime - skull["start_time"] | ||||||
|                 fallProgress = timeElapsed / skull['fall_duration'] |                 fallProgress = timeElapsed / skull["fall_duration"] | ||||||
|                 currentY = self.yPos * (1 - fallProgress) |                 currentY = self.yPos * (1 - fallProgress) | ||||||
|  |  | ||||||
|                 skull['channel'] = play_random_falling( |                 skull["channel"] = play_random_falling( | ||||||
|                     self.sounds, |                     self.sounds, | ||||||
|                     'falling_skull', |                     "falling_skull", | ||||||
|                     player.xPos, |                     player.xPos, | ||||||
|                     skull['x'], |                     skull["x"], | ||||||
|                     self.yPos, |                     self.yPos, | ||||||
|                     currentY, |                     currentY, | ||||||
|                     existingChannel=skull['channel'] |                     existingChannel=skull["channel"], | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|         # Check if we should spawn a new skull |         # Check if we should spawn a new skull | ||||||
|         if (len(self.activeSkulls) < self.maxSkulls and  |         if len(self.activeSkulls) < self.maxSkulls and currentTime - self.lastSkullTime >= self.nextSkullDelay: | ||||||
|             currentTime - self.lastSkullTime >= self.nextSkullDelay): |  | ||||||
|             self.spawn_skull(currentTime) |             self.spawn_skull(currentTime) | ||||||
|  |  | ||||||
|     def spawn_skull(self, currentTime): |     def spawn_skull(self, currentTime): | ||||||
| @@ -91,11 +86,11 @@ class SkullStorm(Object): | |||||||
|  |  | ||||||
|         # Create new skull |         # Create new skull | ||||||
|         skull = { |         skull = { | ||||||
|             'x': random.uniform(self.xRange[0], self.xRange[1]), |             "x": random.uniform(self.xRange[0], self.xRange[1]), | ||||||
|             'start_time': currentTime, |             "start_time": currentTime, | ||||||
|             'fall_duration': fallDuration, |             "fall_duration": fallDuration, | ||||||
|             'land_time': currentTime + fallDuration, |             "land_time": currentTime + fallDuration, | ||||||
|             'channel': None |             "channel": None, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         self.activeSkulls.append(skull) |         self.activeSkulls.append(skull) | ||||||
| @@ -103,20 +98,18 @@ class SkullStorm(Object): | |||||||
|     def handle_landing(self, skull, player): |     def handle_landing(self, skull, player): | ||||||
|         """Handle a skull landing.""" |         """Handle a skull landing.""" | ||||||
|         # Stop falling sound |         # Stop falling sound | ||||||
|         if skull['channel']: |         if skull["channel"]: | ||||||
|             obj_stop(skull['channel']) |             obj_stop(skull["channel"]) | ||||||
|  |  | ||||||
|         # Play landing sound with positional audio once |         # Play landing sound with positional audio once | ||||||
|         channel = pygame.mixer.find_channel(True)  # Find an available channel |         channel = pygame.mixer.find_channel(True)  # Find an available channel | ||||||
|         if channel: |         if channel: | ||||||
|             soundObj = self.sounds['skull_lands'] |             soundObj = self.sounds["skull_lands"] | ||||||
|             obj_play(self.sounds, 'skull_lands', player.xPos, skull['x'], loop=False) |             obj_play(self.sounds, "skull_lands", player.xPos, skull["x"], loop=False) | ||||||
|  |  | ||||||
|         # Check if player was hit |         # Check if player was hit | ||||||
|         if abs(player.xPos - skull['x']) < 1:  # Within 1 tile |         if abs(player.xPos - skull["x"]) < 1:  # Within 1 tile | ||||||
|             if not player.isInvincible: |             if not player.isInvincible: | ||||||
|                 player.set_health(player.get_health() - self.damage) |                 player.set_health(player.get_health() - self.damage) | ||||||
|                 self.sounds['player_takes_damage'].play() |                 self.sounds["player_takes_damage"].play() | ||||||
|                 speak("Hit by falling skull!") |                 speak("Hit by falling skull!") | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,20 +1,15 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  |  | ||||||
| class StatTracker: | class StatTracker: | ||||||
|     def __init__(self): |     def __init__(self): | ||||||
|         # Base dictionary for tracking stats |         # Base dictionary for tracking stats | ||||||
|         self.total = { |         self.total = {"Bone dust": 0, "Enemies killed": 0, "Coffins broken": 0, "Items collected": 0, "Total time": 0} | ||||||
|             'Bone dust': 0, |  | ||||||
|             'Enemies killed': 0, |  | ||||||
|             'Coffins broken': 0, |  | ||||||
|             'Items collected': 0, |  | ||||||
|             'Total time': 0 |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         # Create level stats from total (shallow copy is fine here) |         # Create level stats from total (shallow copy is fine here) | ||||||
|         self.level = self.total.copy() |         self.level = self.total.copy() | ||||||
|  |  | ||||||
|         self.total['levelsCompleted'] = 0 |         self.total["levelsCompleted"] = 0 | ||||||
|  |  | ||||||
|     def reset_level(self): |     def reset_level(self): | ||||||
|         """Reset level stats based on variable type""" |         """Reset level stats based on variable type""" | ||||||
| @@ -42,5 +37,3 @@ class StatTracker: | |||||||
|     def get_total_stat(self, statName): |     def get_total_stat(self, statName): | ||||||
|         """Get a total stat""" |         """Get a total stat""" | ||||||
|         return self.total.get(statName, 0) |         return self.total.get(statName, 0) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ from src.game_selection import get_level_path | |||||||
| class SurvivalGenerator: | class SurvivalGenerator: | ||||||
|     def __init__(self, gamePack): |     def __init__(self, gamePack): | ||||||
|         """Initialize the survival generator for a specific game pack. |         """Initialize the survival generator for a specific game pack. | ||||||
|          |  | ||||||
|         Args: |         Args: | ||||||
|             gamePack (str): Name of the game pack directory |             gamePack (str): Name of the game pack directory | ||||||
|         """ |         """ | ||||||
| @@ -24,57 +24,57 @@ class SurvivalGenerator: | |||||||
|         self.footstepSounds = [] |         self.footstepSounds = [] | ||||||
|         self.loadLevelData() |         self.loadLevelData() | ||||||
|         self.parseTemplates() |         self.parseTemplates() | ||||||
|      |  | ||||||
|     def loadLevelData(self): |     def loadLevelData(self): | ||||||
|         """Load all level JSON files from the game pack.""" |         """Load all level JSON files from the game pack.""" | ||||||
|         levelFiles = [] |         levelFiles = [] | ||||||
|         packPath = os.path.join("levels", self.gamePack) |         packPath = os.path.join("levels", self.gamePack) | ||||||
|          |  | ||||||
|         if not os.path.exists(packPath): |         if not os.path.exists(packPath): | ||||||
|             raise FileNotFoundError(f"Game pack '{self.gamePack}' not found") |             raise FileNotFoundError(f"Game pack '{self.gamePack}' not found") | ||||||
|          |  | ||||||
|         # Get all JSON files in the pack directory |         # Get all JSON files in the pack directory | ||||||
|         for file in os.listdir(packPath): |         for file in os.listdir(packPath): | ||||||
|             if file.endswith('.json') and file[0].isdigit(): |             if file.endswith(".json") and file[0].isdigit(): | ||||||
|                 levelFiles.append(file) |                 levelFiles.append(file) | ||||||
|          |  | ||||||
|         # Load each level file |         # Load each level file | ||||||
|         for levelFile in levelFiles: |         for levelFile in levelFiles: | ||||||
|             levelPath = os.path.join(packPath, levelFile) |             levelPath = os.path.join(packPath, levelFile) | ||||||
|             with open(levelPath, 'r') as f: |             with open(levelPath, "r") as f: | ||||||
|                 levelNum = int(levelFile.split('.')[0]) |                 levelNum = int(levelFile.split(".")[0]) | ||||||
|                 self.levelData[levelNum] = json.load(f) |                 self.levelData[levelNum] = json.load(f) | ||||||
|      |  | ||||||
|     def parseTemplates(self): |     def parseTemplates(self): | ||||||
|         """Parse all level data to extract object templates by type.""" |         """Parse all level data to extract object templates by type.""" | ||||||
|         for levelNum, data in self.levelData.items(): |         for levelNum, data in self.levelData.items(): | ||||||
|             # Store ambience and footstep sounds (remove duplicates) |             # Store ambience and footstep sounds (remove duplicates) | ||||||
|             if 'ambience' in data and data['ambience'] not in self.ambientSounds: |             if "ambience" in data and data["ambience"] not in self.ambientSounds: | ||||||
|                 self.ambientSounds.append(data['ambience']) |                 self.ambientSounds.append(data["ambience"]) | ||||||
|             if 'footstep_sound' in data and data['footstep_sound'] not in self.footstepSounds: |             if "footstep_sound" in data and data["footstep_sound"] not in self.footstepSounds: | ||||||
|                 self.footstepSounds.append(data['footstep_sound']) |                 self.footstepSounds.append(data["footstep_sound"]) | ||||||
|              |  | ||||||
|             # Parse objects |             # Parse objects | ||||||
|             for obj in data.get('objects', []): |             for obj in data.get("objects", []): | ||||||
|                 objCopy = copy.deepcopy(obj) |                 objCopy = copy.deepcopy(obj) | ||||||
|                  |  | ||||||
|                 # Categorize objects |                 # Categorize objects | ||||||
|                 if 'enemy_type' in obj: |                 if "enemy_type" in obj: | ||||||
|                     self.enemyTemplates.append(objCopy) |                     self.enemyTemplates.append(objCopy) | ||||||
|                 elif obj.get('collectible', False) or obj.get('sound') == 'bone_dust': |                 elif obj.get("collectible", False) or obj.get("sound") == "bone_dust": | ||||||
|                     self.collectibleTemplates.append(objCopy) |                     self.collectibleTemplates.append(objCopy) | ||||||
|                 elif obj.get('type') in ['skull_storm', 'catapult', 'grasping_hands']: |                 elif obj.get("type") in ["skull_storm", "catapult", "grasping_hands"]: | ||||||
|                     self.hazardTemplates.append(objCopy) |                     self.hazardTemplates.append(objCopy) | ||||||
|                 else: |                 else: | ||||||
|                     self.objectTemplates.append(objCopy) |                     self.objectTemplates.append(objCopy) | ||||||
|      |  | ||||||
|     def generate_survival_level(self, difficultyLevel=1, segmentLength=100): |     def generate_survival_level(self, difficultyLevel=1, segmentLength=100): | ||||||
|         """Generate an endless survival level segment. |         """Generate an endless survival level segment. | ||||||
|          |  | ||||||
|         Args: |         Args: | ||||||
|             difficultyLevel (int): Current difficulty level (increases over time) |             difficultyLevel (int): Current difficulty level (increases over time) | ||||||
|             segmentLength (int): Length of this level segment |             segmentLength (int): Length of this level segment | ||||||
|              |  | ||||||
|         Returns: |         Returns: | ||||||
|             dict: Generated level data |             dict: Generated level data | ||||||
|         """ |         """ | ||||||
| @@ -88,30 +88,30 @@ class SurvivalGenerator: | |||||||
|             "boundaries": {"left": 0, "right": segmentLength}, |             "boundaries": {"left": 0, "right": segmentLength}, | ||||||
|             "locked": True,  # Enable lock system for survival mode |             "locked": True,  # Enable lock system for survival mode | ||||||
|             "ambience": "Escaping the Grave.ogg",  # Will be overridden below |             "ambience": "Escaping the Grave.ogg",  # Will be overridden below | ||||||
|             "footstep_sound": "footstep_stone"  # Will be overridden below |             "footstep_sound": "footstep_stone",  # Will be overridden below | ||||||
|         } |         } | ||||||
|          |  | ||||||
|         # Choose random music and footstep from collected unique tracks |         # Choose random music and footstep from collected unique tracks | ||||||
|         if self.ambientSounds: |         if self.ambientSounds: | ||||||
|             levelData["ambience"] = random.choice(self.ambientSounds) |             levelData["ambience"] = random.choice(self.ambientSounds) | ||||||
|         if self.footstepSounds: |         if self.footstepSounds: | ||||||
|             levelData["footstep_sound"] = random.choice(self.footstepSounds) |             levelData["footstep_sound"] = random.choice(self.footstepSounds) | ||||||
|          |  | ||||||
|         # Calculate spawn rates based on difficulty |         # Calculate spawn rates based on difficulty | ||||||
|         collectibleDensity = max(0.1, 0.3 - (difficultyLevel * 0.02))  # Fewer collectibles over time |         collectibleDensity = max(0.1, 0.3 - (difficultyLevel * 0.02))  # Fewer collectibles over time | ||||||
|         enemyDensity = min(0.8, 0.2 + (difficultyLevel * 0.05))  # More enemies over time   |         enemyDensity = min(0.8, 0.2 + (difficultyLevel * 0.05))  # More enemies over time | ||||||
|         hazardDensity = min(0.4, 0.1 + (difficultyLevel * 0.03))  # More hazards over time |         hazardDensity = min(0.4, 0.1 + (difficultyLevel * 0.03))  # More hazards over time | ||||||
|         objectDensity = max(0.1, 0.2 - (difficultyLevel * 0.01))  # Fewer misc objects over time |         objectDensity = max(0.1, 0.2 - (difficultyLevel * 0.01))  # Fewer misc objects over time | ||||||
|          |  | ||||||
|         # Generate objects across the segment with buffer zones |         # Generate objects across the segment with buffer zones | ||||||
|         startBufferZone = 25  # Safe zone at start of each wave |         startBufferZone = 25  # Safe zone at start of each wave | ||||||
|         endBufferZone = 30    # Safe zone at end of each wave |         endBufferZone = 30  # Safe zone at end of each wave | ||||||
|         currentX = startBufferZone  # Start placing objects after start buffer zone |         currentX = startBufferZone  # Start placing objects after start buffer zone | ||||||
|          |  | ||||||
|         while currentX < segmentLength - endBufferZone: |         while currentX < segmentLength - endBufferZone: | ||||||
|             # Determine what to place based on probability |             # Determine what to place based on probability | ||||||
|             rand = random.random() |             rand = random.random() | ||||||
|              |  | ||||||
|             if rand < collectibleDensity and self.collectibleTemplates: |             if rand < collectibleDensity and self.collectibleTemplates: | ||||||
|                 obj = self.place_collectible(currentX, difficultyLevel) |                 obj = self.place_collectible(currentX, difficultyLevel) | ||||||
|                 currentX += random.randint(8, 15) |                 currentX += random.randint(8, 15) | ||||||
| @@ -127,121 +127,118 @@ class SurvivalGenerator: | |||||||
|             else: |             else: | ||||||
|                 currentX += random.randint(5, 15) |                 currentX += random.randint(5, 15) | ||||||
|                 continue |                 continue | ||||||
|              |  | ||||||
|             if obj: |             if obj: | ||||||
|                 levelData["objects"].append(obj) |                 levelData["objects"].append(obj) | ||||||
|          |  | ||||||
|         # Add end-of-level marker at the end, within the end buffer zone |         # Add end-of-level marker at the end, within the end buffer zone | ||||||
|         endMarker = { |         endMarker = { | ||||||
|             "x": segmentLength - (endBufferZone // 2),  # Place marker in middle of end buffer |             "x": segmentLength - (endBufferZone // 2),  # Place marker in middle of end buffer | ||||||
|             "y": 0, |             "y": 0, | ||||||
|             "sound": "end_of_level" |             "sound": "end_of_level", | ||||||
|         } |         } | ||||||
|         levelData["objects"].append(endMarker) |         levelData["objects"].append(endMarker) | ||||||
|          |  | ||||||
|         return levelData |         return levelData | ||||||
|      |  | ||||||
|     def place_collectible(self, xPos, difficultyLevel): |     def place_collectible(self, xPos, difficultyLevel): | ||||||
|         """Place a collectible at the given position.""" |         """Place a collectible at the given position.""" | ||||||
|         template = random.choice(self.collectibleTemplates) |         template = random.choice(self.collectibleTemplates) | ||||||
|         obj = copy.deepcopy(template) |         obj = copy.deepcopy(template) | ||||||
|          |  | ||||||
|         # Handle x_range vs single x |         # Handle x_range vs single x | ||||||
|         if 'x_range' in obj: |         if "x_range" in obj: | ||||||
|             rangeSize = obj['x_range'][1] - obj['x_range'][0] |             rangeSize = obj["x_range"][1] - obj["x_range"][0] | ||||||
|             obj['x_range'] = [xPos, xPos + rangeSize] |             obj["x_range"] = [xPos, xPos + rangeSize] | ||||||
|         else: |         else: | ||||||
|             obj['x'] = xPos |             obj["x"] = xPos | ||||||
|          |  | ||||||
|         return obj |         return obj | ||||||
|      |  | ||||||
|     def place_enemy(self, xPos, difficultyLevel): |     def place_enemy(self, xPos, difficultyLevel): | ||||||
|         """Place an enemy at the given position with scaled difficulty.""" |         """Place an enemy at the given position with scaled difficulty.""" | ||||||
|         # Filter out boss enemies for early waves |         # Filter out boss enemies for early waves | ||||||
|         bossEnemies = ['witch', 'boogie_man', 'revenant', 'ghost', 'headless_horseman'] |         bossEnemies = ["witch", "boogie_man", "revenant", "ghost", "headless_horseman"] | ||||||
|          |  | ||||||
|         if difficultyLevel < 3:  # Waves 1-2: no bosses |         if difficultyLevel < 3:  # Waves 1-2: no bosses | ||||||
|             availableEnemies = [e for e in self.enemyTemplates  |             availableEnemies = [e for e in self.enemyTemplates if e.get("enemy_type") not in bossEnemies] | ||||||
|                               if e.get('enemy_type') not in bossEnemies] |  | ||||||
|         elif difficultyLevel < 5:  # Waves 3-4: exclude the hardest bosses |         elif difficultyLevel < 5:  # Waves 3-4: exclude the hardest bosses | ||||||
|             hardestBosses = ['revenant', 'ghost', 'headless_horseman'] |             hardestBosses = ["revenant", "ghost", "headless_horseman"] | ||||||
|             availableEnemies = [e for e in self.enemyTemplates  |             availableEnemies = [e for e in self.enemyTemplates if e.get("enemy_type") not in hardestBosses] | ||||||
|                               if e.get('enemy_type') not in hardestBosses] |  | ||||||
|         else:  # Wave 5+: all enemies allowed |         else:  # Wave 5+: all enemies allowed | ||||||
|             availableEnemies = self.enemyTemplates |             availableEnemies = self.enemyTemplates | ||||||
|          |  | ||||||
|         # Fallback to all enemies if filtering removed everything |         # Fallback to all enemies if filtering removed everything | ||||||
|         if not availableEnemies: |         if not availableEnemies: | ||||||
|             availableEnemies = self.enemyTemplates |             availableEnemies = self.enemyTemplates | ||||||
|          |  | ||||||
|         template = random.choice(availableEnemies) |         template = random.choice(availableEnemies) | ||||||
|         obj = copy.deepcopy(template) |         obj = copy.deepcopy(template) | ||||||
|          |  | ||||||
|         # Dynamic health scaling: random between wave/2 and wave |         # Dynamic health scaling: random between wave/2 and wave | ||||||
|         minHealth = max(1, difficultyLevel // 2) |         minHealth = max(1, difficultyLevel // 2) | ||||||
|         maxHealth = max(1, difficultyLevel) |         maxHealth = max(1, difficultyLevel) | ||||||
|         obj['health'] = random.randint(minHealth, maxHealth) |         obj["health"] = random.randint(minHealth, maxHealth) | ||||||
|          |  | ||||||
|         # Damage scaling (keep existing logic) |         # Damage scaling (keep existing logic) | ||||||
|         damageMultiplier = 1 + (difficultyLevel * 0.1) |         damageMultiplier = 1 + (difficultyLevel * 0.1) | ||||||
|         obj['damage'] = max(1, int(obj.get('damage', 1) * damageMultiplier)) |         obj["damage"] = max(1, int(obj.get("damage", 1) * damageMultiplier)) | ||||||
|          |  | ||||||
|         # Set all enemies to hunter mode for survival |         # Set all enemies to hunter mode for survival | ||||||
|         obj['behavior'] = 'hunter' |         obj["behavior"] = "hunter" | ||||||
|          |  | ||||||
|         # Progressive turn rate reduction: start at 6, decrease to 1 |         # Progressive turn rate reduction: start at 6, decrease to 1 | ||||||
|         obj['turn_rate'] = max(1, 7 - difficultyLevel) |         obj["turn_rate"] = max(1, 7 - difficultyLevel) | ||||||
|          |  | ||||||
|         # Handle x_range vs single x |         # Handle x_range vs single x | ||||||
|         if 'x_range' in obj: |         if "x_range" in obj: | ||||||
|             rangeSize = obj['x_range'][1] - obj['x_range'][0] |             rangeSize = obj["x_range"][1] - obj["x_range"][0] | ||||||
|             obj['x_range'] = [xPos, xPos + rangeSize] |             obj["x_range"] = [xPos, xPos + rangeSize] | ||||||
|         else: |         else: | ||||||
|             obj['x'] = xPos |             obj["x"] = xPos | ||||||
|          |  | ||||||
|         return obj |         return obj | ||||||
|      |  | ||||||
|     def place_hazard(self, xPos, difficultyLevel): |     def place_hazard(self, xPos, difficultyLevel): | ||||||
|         """Place a hazard at the given position with scaled difficulty.""" |         """Place a hazard at the given position with scaled difficulty.""" | ||||||
|         template = random.choice(self.hazardTemplates) |         template = random.choice(self.hazardTemplates) | ||||||
|         obj = copy.deepcopy(template) |         obj = copy.deepcopy(template) | ||||||
|          |  | ||||||
|         # Scale hazard difficulty |         # Scale hazard difficulty | ||||||
|         if obj.get('type') == 'skull_storm': |         if obj.get("type") == "skull_storm": | ||||||
|             obj['damage'] = max(1, int(obj.get('damage', 1) * (1 + difficultyLevel * 0.1))) |             obj["damage"] = max(1, int(obj.get("damage", 1) * (1 + difficultyLevel * 0.1))) | ||||||
|             obj['maximum_skulls'] = min(6, obj.get('maximum_skulls', 2) + (difficultyLevel // 3)) |             obj["maximum_skulls"] = min(6, obj.get("maximum_skulls", 2) + (difficultyLevel // 3)) | ||||||
|         elif obj.get('type') == 'catapult': |         elif obj.get("type") == "catapult": | ||||||
|             obj['fire_interval'] = max(1000, obj.get('fire_interval', 4000) - (difficultyLevel * 100)) |             obj["fire_interval"] = max(1000, obj.get("fire_interval", 4000) - (difficultyLevel * 100)) | ||||||
|          |  | ||||||
|         # Handle x_range vs single x   |         # Handle x_range vs single x | ||||||
|         if 'x_range' in obj: |         if "x_range" in obj: | ||||||
|             rangeSize = obj['x_range'][1] - obj['x_range'][0] |             rangeSize = obj["x_range"][1] - obj["x_range"][0] | ||||||
|             obj['x_range'] = [xPos, xPos + rangeSize] |             obj["x_range"] = [xPos, xPos + rangeSize] | ||||||
|         else: |         else: | ||||||
|             obj['x'] = xPos |             obj["x"] = xPos | ||||||
|          |  | ||||||
|         return obj |         return obj | ||||||
|      |  | ||||||
|     def place_object(self, xPos, difficultyLevel): |     def place_object(self, xPos, difficultyLevel): | ||||||
|         """Place a misc object at the given position.""" |         """Place a misc object at the given position.""" | ||||||
|         template = random.choice(self.objectTemplates) |         template = random.choice(self.objectTemplates) | ||||||
|         obj = copy.deepcopy(template) |         obj = copy.deepcopy(template) | ||||||
|          |  | ||||||
|         # Handle graves - increase zombie spawn chance with difficulty |  | ||||||
|         if obj.get('type') == 'grave': |  | ||||||
|             baseChance = obj.get('zombie_spawn_chance', 0) |  | ||||||
|             obj['zombie_spawn_chance'] = min(50, baseChance + (difficultyLevel * 2)) |  | ||||||
|          |  | ||||||
|         # Handle coffins - make all items random in survival mode |  | ||||||
|         if obj.get('type') == 'coffin': |  | ||||||
|             obj['item'] = 'random'  # Override any specified item |  | ||||||
|          |  | ||||||
|         # Handle x_range vs single x |  | ||||||
|         if 'x_range' in obj: |  | ||||||
|             rangeSize = obj['x_range'][1] - obj['x_range'][0] |  | ||||||
|             obj['x_range'] = [xPos, xPos + rangeSize] |  | ||||||
|         else: |  | ||||||
|             obj['x'] = xPos |  | ||||||
|          |  | ||||||
|         return obj |  | ||||||
|  |  | ||||||
|  |         # Handle graves - increase zombie spawn chance with difficulty | ||||||
|  |         if obj.get("type") == "grave": | ||||||
|  |             baseChance = obj.get("zombie_spawn_chance", 0) | ||||||
|  |             obj["zombie_spawn_chance"] = min(50, baseChance + (difficultyLevel * 2)) | ||||||
|  |  | ||||||
|  |         # Handle coffins - make all items random in survival mode | ||||||
|  |         if obj.get("type") == "coffin": | ||||||
|  |             obj["item"] = "random"  # Override any specified item | ||||||
|  |  | ||||||
|  |         # Handle x_range vs single x | ||||||
|  |         if "x_range" in obj: | ||||||
|  |             rangeSize = obj["x_range"][1] - obj["x_range"][0] | ||||||
|  |             obj["x_range"] = [xPos, xPos + rangeSize] | ||||||
|  |         else: | ||||||
|  |             obj["x"] = xPos | ||||||
|  |  | ||||||
|  |         return obj | ||||||
|   | |||||||
| @@ -1,7 +1,19 @@ | |||||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||||
|  |  | ||||||
|  |  | ||||||
| class Weapon: | class Weapon: | ||||||
|     def __init__(self, name, damage, range, attackSound, hitSound, cooldown=500, attackDuration=200, speedBonus=1.0, jumpDurationBonus=1.0): |     def __init__( | ||||||
|  |         self, | ||||||
|  |         name, | ||||||
|  |         damage, | ||||||
|  |         range, | ||||||
|  |         attackSound, | ||||||
|  |         hitSound, | ||||||
|  |         cooldown=500, | ||||||
|  |         attackDuration=200, | ||||||
|  |         speedBonus=1.0, | ||||||
|  |         jumpDurationBonus=1.0, | ||||||
|  |     ): | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.damage = damage |         self.damage = damage | ||||||
|         self.range = range  # Range in tiles |         self.range = range  # Range in tiles | ||||||
| @@ -24,7 +36,7 @@ class Weapon: | |||||||
|             attackSound="player_nunchuck_attack", |             attackSound="player_nunchuck_attack", | ||||||
|             hitSound="player_nunchuck_hit", |             hitSound="player_nunchuck_hit", | ||||||
|             cooldown=250, |             cooldown=250, | ||||||
|             attackDuration=100 |             attackDuration=100, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
| @@ -38,8 +50,8 @@ class Weapon: | |||||||
|             hitSound="player_broom_hit", |             hitSound="player_broom_hit", | ||||||
|             cooldown=500, |             cooldown=500, | ||||||
|             attackDuration=200, |             attackDuration=200, | ||||||
|             speedBonus=1.17,  # 17% speed bonus when wielding the broom   |             speedBonus=1.17,  # 17% speed bonus when wielding the broom | ||||||
|             jumpDurationBonus=1.25  # 25% longer jump duration for better traversal |             jumpDurationBonus=1.25,  # 25% longer jump duration for better traversal | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def can_attack(self, currentTime): |     def can_attack(self, currentTime): | ||||||
| @@ -73,5 +85,3 @@ class Weapon: | |||||||
|                 self.hitEnemies.add(enemy) |                 self.hitEnemies.add(enemy) | ||||||
|                 return True |                 return True | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,12 +1,21 @@ | |||||||
| # -*- mode: python ; coding: utf-8 -*- | # -*- mode: python ; coding: utf-8 -*- | ||||||
|  |  | ||||||
|  | import os | ||||||
|  |  | ||||||
|  | # Collect level directories dynamically | ||||||
|  | level_dirs = [] | ||||||
|  | levels_path = 'levels' | ||||||
|  | if os.path.exists(levels_path): | ||||||
|  |     for item in os.listdir(levels_path): | ||||||
|  |         item_path = os.path.join(levels_path, item) | ||||||
|  |         if os.path.isdir(item_path): | ||||||
|  |             level_dirs.append((item_path, item_path)) | ||||||
|  |  | ||||||
| a = Analysis( | a = Analysis( | ||||||
|     ['wicked_quest.py'], |     ['wicked_quest.py'], | ||||||
|     pathex=[], |     pathex=[], | ||||||
|     binaries=[], |     binaries=[], | ||||||
|     datas=[ |     datas=level_dirs + [ | ||||||
|         ('levels', 'levels'), |  | ||||||
|         ('sounds', 'sounds'), |         ('sounds', 'sounds'), | ||||||
|         ('libstormgames', 'libstormgames'), |         ('libstormgames', 'libstormgames'), | ||||||
|     ], |     ], | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user