Some minor cleanup.
This commit is contained in:
		| @@ -20,15 +20,15 @@ class Pumpkin: | |||||||
|         """Update pumpkin position and sound""" |         """Update pumpkin position and sound""" | ||||||
|         if not self.isActive: |         if not self.isActive: | ||||||
|             return False |             return False | ||||||
|          |  | ||||||
|         self.x += self.direction * self.speed |         self.x += self.direction * self.speed | ||||||
|          |  | ||||||
|         # Update or start positional audio |         # Update or start positional audio | ||||||
|         if self.soundChannel is None or not self.soundChannel.get_busy(): |         if self.soundChannel is None or not self.soundChannel.get_busy(): | ||||||
|             self.soundChannel = obj_play(sounds, self.soundName, playerX, self.x) |             self.soundChannel = obj_play(sounds, self.soundName, playerX, self.x) | ||||||
|         else: |         else: | ||||||
|             self.soundChannel = obj_update(self.soundChannel, playerX, self.x) |             self.soundChannel = obj_update(self.soundChannel, playerX, self.x) | ||||||
|              |  | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|     def stop_sound(self, sounds, playerX): |     def stop_sound(self, sounds, playerX): | ||||||
| @@ -45,7 +45,7 @@ class Pumpkin: | |||||||
|         """Check if pumpkin hits player""" |         """Check if pumpkin hits player""" | ||||||
|         if not self.isActive: |         if not self.isActive: | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         distance = abs(player.xPos - self.x) |         distance = abs(player.xPos - self.x) | ||||||
|         if distance < 1:  # Within 1 tile |         if distance < 1:  # Within 1 tile | ||||||
|             if self.isHigh and not player.isJumping: |             if self.isHigh and not player.isJumping: | ||||||
| @@ -75,21 +75,21 @@ class Catapult(Object): | |||||||
|     def fire(self, currentTime, player): |     def fire(self, currentTime, player): | ||||||
|         """Start the firing sequence""" |         """Start the firing sequence""" | ||||||
|         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, |             'isHigh': isHigh, | ||||||
|             'direction': fireDirection, |             'direction': fireDirection, | ||||||
|             'playerMaxHealth': player.get_max_health() |             '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 | ||||||
|  |  | ||||||
| @@ -97,11 +97,11 @@ class Catapult(Object): | |||||||
|         """Update catapult and its pumpkins""" |         """Update catapult and its pumpkins""" | ||||||
|         if not self.isActive: |         if not self.isActive: | ||||||
|             return |             return | ||||||
|              |  | ||||||
|         # Check if player is in range |         # Check if player is in range | ||||||
|         distance = abs(player.xPos - self.xPos) |         distance = abs(player.xPos - self.xPos) | ||||||
|         inRange = distance <= self.firingRange |         inRange = distance <= self.firingRange | ||||||
|          |  | ||||||
|         # Handle entering/leaving range |         # Handle entering/leaving range | ||||||
|         if inRange and not self.isFiring: |         if inRange and not self.isFiring: | ||||||
|             self.isFiring = True |             self.isFiring = True | ||||||
| @@ -110,7 +110,7 @@ class Catapult(Object): | |||||||
|         elif not inRange and self.isFiring: |         elif not inRange and self.isFiring: | ||||||
|             self.isFiring = False |             self.isFiring = False | ||||||
|             speak("Out of pumpkin catapult range.") |             speak("Out of pumpkin catapult range.") | ||||||
|              |  | ||||||
|         # Check for pending pumpkin launch |         # Check for pending pumpkin launch | ||||||
|         if self.pendingPumpkin and currentTime >= self.pumpkinLaunchTime: |         if self.pendingPumpkin and currentTime >= self.pumpkinLaunchTime: | ||||||
|             # Create and fire the pending pumpkin |             # Create and fire the pending pumpkin | ||||||
| @@ -122,18 +122,18 @@ class Catapult(Object): | |||||||
|             ) |             ) | ||||||
|             self.activePumpkins.append(pumpkin) |             self.activePumpkins.append(pumpkin) | ||||||
|             self.pendingPumpkin = None |             self.pendingPumpkin = None | ||||||
|              |  | ||||||
|         # Only start new fire sequence if in range and enough time has passed |         # Only start new fire sequence if in range and enough time has passed | ||||||
|         if self.isFiring and currentTime - self.lastFireTime >= self.fireInterval: |         if self.isFiring and currentTime - self.lastFireTime >= self.fireInterval: | ||||||
|             self.fire(currentTime, player) |             self.fire(currentTime, player) | ||||||
|              |  | ||||||
|         # Always update existing pumpkins |         # Always update existing pumpkins | ||||||
|         for pumpkin in self.activePumpkins[:]:  # Copy list to allow removal |         for pumpkin in self.activePumpkins[:]:  # Copy list to allow removal | ||||||
|             if not pumpkin.update(self.sounds, player.xPos): |             if not pumpkin.update(self.sounds, player.xPos): | ||||||
|                 pumpkin.stop_sound(self.sounds, player.xPos) |                 pumpkin.stop_sound(self.sounds, player.xPos) | ||||||
|                 self.activePumpkins.remove(pumpkin) |                 self.activePumpkins.remove(pumpkin) | ||||||
|                 continue |                 continue | ||||||
|                  |  | ||||||
|             if pumpkin.check_collision(player): |             if pumpkin.check_collision(player): | ||||||
|                 player.set_health(player.get_health() - pumpkin.damage) |                 player.set_health(player.get_health() - pumpkin.damage) | ||||||
|                 pumpkin.stop_sound(self.sounds, player.xPos) |                 pumpkin.stop_sound(self.sounds, player.xPos) | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class CoffinObject(Object): | |||||||
|         self.isBroken = False |         self.isBroken = False | ||||||
|         self.dropped_item = None |         self.dropped_item = None | ||||||
|         self.specified_item = item |         self.specified_item = item | ||||||
|          |  | ||||||
|     def hit(self, player_pos): |     def hit(self, player_pos): | ||||||
|         """Handle being hit by the player's weapon""" |         """Handle being hit by the player's weapon""" | ||||||
|         if not self.isBroken: |         if not self.isBroken: | ||||||
| @@ -28,15 +28,15 @@ class CoffinObject(Object): | |||||||
|             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: | ||||||
|                 obj_stop(self.channel) |                 obj_stop(self.channel) | ||||||
|                 self.channel = None |                 self.channel = None | ||||||
|                  |  | ||||||
|             # Mark coffin as inactive since it's broken |             # Mark coffin as inactive since it's broken | ||||||
|             self.isActive = False |             self.isActive = False | ||||||
|              |  | ||||||
|             # Determine item to drop |             # Determine item to drop | ||||||
|             if self.specified_item == "random": |             if self.specified_item == "random": | ||||||
|                 item_type = ItemProperties.get_random_item() |                 item_type = ItemProperties.get_random_item() | ||||||
| @@ -47,12 +47,12 @@ class CoffinObject(Object): | |||||||
|                 else: |                 else: | ||||||
|                     # Fall back to random if invalid item specified |                     # Fall back to random if invalid item specified | ||||||
|                     item_type = ItemProperties.get_random_item() |                     item_type = ItemProperties.get_random_item() | ||||||
|              |  | ||||||
|             # Create item 1-2 tiles away in random direction |             # Create item 1-2 tiles away in random direction | ||||||
|             direction = random.choice([-1, 1]) |             direction = random.choice([-1, 1]) | ||||||
|             drop_distance = random.randint(1, 2) |             drop_distance = random.randint(1, 2) | ||||||
|             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, |                 self.yPos, | ||||||
|   | |||||||
							
								
								
									
										50
									
								
								src/enemy.py
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								src/enemy.py
									
									
									
									
									
								
							| @@ -18,7 +18,7 @@ class Enemy(Object): | |||||||
|             isStatic=False, |             isStatic=False, | ||||||
|             isHazard=True |             isHazard=True | ||||||
|         ) |         ) | ||||||
|          |  | ||||||
|         # Enemy specific properties |         # Enemy specific properties | ||||||
|         self.enemyType = enemyType |         self.enemyType = enemyType | ||||||
|         self.level = level |         self.level = level | ||||||
| @@ -26,7 +26,7 @@ class Enemy(Object): | |||||||
|         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 | ||||||
|         self.movingRight = True  # Initial direction |         self.movingRight = True  # Initial direction | ||||||
|         self.movementSpeed = 0.03  # Base speed |         self.movementSpeed = 0.03  # Base speed | ||||||
| @@ -35,7 +35,7 @@ class Enemy(Object): | |||||||
|         self.lastAttackTime = 0 |         self.lastAttackTime = 0 | ||||||
|         self.attackCooldown = 1000  # 1 second between attacks |         self.attackCooldown = 1000  # 1 second between attacks | ||||||
|         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: | ||||||
| @@ -48,7 +48,7 @@ class Enemy(Object): | |||||||
|         # 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: | ||||||
| @@ -73,18 +73,18 @@ class Enemy(Object): | |||||||
|             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 | ||||||
|              |  | ||||||
|     def patrol_movement(self): |     def patrol_movement(self): | ||||||
|         """Standard back-and-forth patrol movement""" |         """Standard back-and-forth patrol movement""" | ||||||
|         if self.movingRight: |         if self.movingRight: | ||||||
| @@ -100,7 +100,7 @@ class Enemy(Object): | |||||||
|         """Update enemy position and handle attacks""" |         """Update enemy position and handle attacks""" | ||||||
|         if not self.isActive or self.health <= 0: |         if not self.isActive or self.health <= 0: | ||||||
|             return |             return | ||||||
|              |  | ||||||
|         # Initialize sound for enemies with vulnerability system immediately upon creation |         # Initialize sound for enemies with vulnerability system immediately upon creation | ||||||
|         if self.hasVulnerabilitySystem: |         if self.hasVulnerabilitySystem: | ||||||
|             if self.channel is None: |             if self.channel is None: | ||||||
| @@ -109,12 +109,12 @@ class Enemy(Object): | |||||||
|             # Update existing channel position |             # Update existing channel position | ||||||
|             else: |             else: | ||||||
|                 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 | ||||||
|                  |  | ||||||
|                 if self.channel: |                 if self.channel: | ||||||
|                     obj_stop(self.channel) |                     obj_stop(self.channel) | ||||||
|                 soundName = f"{self.enemyType}_is_vulnerable" if self.isVulnerable else self.enemyType |                 soundName = f"{self.enemyType}_is_vulnerable" if self.isVulnerable else self.enemyType | ||||||
| @@ -124,17 +124,17 @@ class Enemy(Object): | |||||||
|         if not self.hunting: |         if not self.hunting: | ||||||
|             if self.patrolStart <= player.xPos <= self.patrolEnd: |             if self.patrolStart <= player.xPos <= self.patrolEnd: | ||||||
|                 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 | ||||||
|              |  | ||||||
|             # If we've moved past the player by more than the turn threshold, turn around |             # If we've moved past the player by more than the turn threshold, turn around | ||||||
|             if abs(distanceToPlayer) >= self.turnThreshold: |             if abs(distanceToPlayer) >= self.turnThreshold: | ||||||
|                 self.movingRight = distanceToPlayer > 0 |                 self.movingRight = distanceToPlayer > 0 | ||||||
|              |  | ||||||
|             # Otherwise keep moving in current direction |             # Otherwise keep moving in current direction | ||||||
|             self.xPos += self.movementSpeed if self.movingRight else -self.movementSpeed |             self.xPos += self.movementSpeed if self.movingRight else -self.movementSpeed | ||||||
|  |  | ||||||
| @@ -152,7 +152,7 @@ class Enemy(Object): | |||||||
|         # Check for attack opportunity |         # Check for attack opportunity | ||||||
|         if self.can_attack(currentTime, player): |         if self.can_attack(currentTime, player): | ||||||
|             self.attack(currentTime, player) |             self.attack(currentTime, player) | ||||||
|              |  | ||||||
|         if self.canSpawn: |         if self.canSpawn: | ||||||
|             if currentTime - self.lastSpawnTime >= self.spawnCooldown: |             if currentTime - self.lastSpawnTime >= self.spawnCooldown: | ||||||
|                 distanceToPlayer = abs(player.xPos - self.xPos) |                 distanceToPlayer = abs(player.xPos - self.xPos) | ||||||
| @@ -197,22 +197,22 @@ class Enemy(Object): | |||||||
|         # Must have cooled down from last attack |         # Must have cooled down from last attack | ||||||
|         if currentTime - self.lastAttackTime < self.attackCooldown: |         if currentTime - self.lastAttackTime < self.attackCooldown: | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         # Don't attack if player is jumping |         # Don't attack if player is jumping | ||||||
|         if player.isJumping: |         if player.isJumping: | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         # Check if player is in range and on same side we're facing |         # Check if player is in range and on same side we're facing | ||||||
|         distance = abs(player.xPos - self.xPos) |         distance = abs(player.xPos - self.xPos) | ||||||
|         tolerance = 0.5  # Same tolerance as we used for the grave |         tolerance = 0.5  # Same tolerance as we used for the grave | ||||||
|          |  | ||||||
|         if distance <= (self.attackRange + tolerance): |         if distance <= (self.attackRange + tolerance): | ||||||
|             # Only attack if we're facing the right way |             # Only attack if we're facing the right way | ||||||
|             playerOnRight = player.xPos > self.xPos |             playerOnRight = player.xPos > self.xPos | ||||||
|             return playerOnRight == self.movingRight |             return playerOnRight == self.movingRight | ||||||
|              |  | ||||||
|         return False |         return False | ||||||
|          |  | ||||||
|     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 | ||||||
| @@ -224,7 +224,7 @@ class Enemy(Object): | |||||||
|         # 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""" | ||||||
|         if self.hasVulnerabilitySystem and not self.isVulnerable: |         if self.hasVulnerabilitySystem and not self.isVulnerable: | ||||||
| @@ -233,7 +233,7 @@ class Enemy(Object): | |||||||
|         self.health -= amount |         self.health -= amount | ||||||
|         if self.health <= 0: |         if self.health <= 0: | ||||||
|             self.die() |             self.die() | ||||||
|              |  | ||||||
|     def die(self): |     def die(self): | ||||||
|         """Handle enemy death""" |         """Handle enemy death""" | ||||||
|         self.isActive = False |         self.isActive = False | ||||||
| @@ -247,7 +247,7 @@ class Enemy(Object): | |||||||
|         rangeModifier = self.attackRange * 250 |         rangeModifier = self.attackRange * 250 | ||||||
|         speedModifier = int(self.movementSpeed * 1000) |         speedModifier = int(self.movementSpeed * 1000) | ||||||
|         totalPoints = max(basePoints + damageModifier + rangeModifier + speedModifier, 1000) |         totalPoints = max(basePoints + damageModifier + rangeModifier + speedModifier, 1000) | ||||||
|      |  | ||||||
|         # Award points |         # Award points | ||||||
|         self.level.levelScore += totalPoints |         self.level.levelScore += totalPoints | ||||||
|  |  | ||||||
| @@ -263,12 +263,12 @@ class Enemy(Object): | |||||||
|             hasNunchucks = any(weapon.name == "nunchucks" for weapon in self.level.player.weapons) |             hasNunchucks = any(weapon.name == "nunchucks" for weapon in self.level.player.weapons) | ||||||
|             # Drop witch_broom only if player has neither broom nor nunchucks |             # Drop witch_broom only if player has neither broom nor nunchucks | ||||||
|             itemType = "witch_broom" if not (hasBroom or hasNunchucks) else "cauldron" |             itemType = "witch_broom" if not (hasBroom or hasNunchucks) else "cauldron" | ||||||
|              |  | ||||||
|             # Create drop 1-2 tiles away in random direction |             # Create drop 1-2 tiles away in random direction | ||||||
|             direction = random.choice([-1, 1]) |             direction = random.choice([-1, 1]) | ||||||
|             dropDistance = random.randint(1, 2) |             dropDistance = random.randint(1, 2) | ||||||
|             dropX = self.xPos + (direction * dropDistance) |             dropX = self.xPos + (direction * dropDistance) | ||||||
|              |  | ||||||
|             droppedItem = PowerUp( |             droppedItem = PowerUp( | ||||||
|                 dropX, |                 dropX, | ||||||
|                 self.yPos, |                 self.yPos, | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ from libstormgames import speak | |||||||
|  |  | ||||||
| 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. | ||||||
|      |  | ||||||
|     Returns: |     Returns: | ||||||
|         list: List of game directory names |         list: List of game directory names | ||||||
|     """ |     """ | ||||||
| @@ -19,11 +19,11 @@ def get_available_games(): | |||||||
|  |  | ||||||
| def selection_menu(sounds, *options): | def selection_menu(sounds, *options): | ||||||
|     """Display level selection menu. |     """Display level selection menu. | ||||||
|      |  | ||||||
|     Args: |     Args: | ||||||
|         sounds (dict): Dictionary of loaded sound effects |         sounds (dict): Dictionary of loaded sound effects | ||||||
|         *options: Variable number of menu options |         *options: Variable number of menu options | ||||||
|          |  | ||||||
|     Returns: |     Returns: | ||||||
|         str: Selected option or None if cancelled |         str: Selected option or None if cancelled | ||||||
|     """ |     """ | ||||||
| @@ -31,53 +31,53 @@ def selection_menu(sounds, *options): | |||||||
|     pygame.mixer.stop() |     pygame.mixer.stop() | ||||||
|     i = 0 |     i = 0 | ||||||
|     j = -1 |     j = -1 | ||||||
|      |  | ||||||
|     # Clear any pending events |     # Clear any pending events | ||||||
|     pygame.event.clear() |     pygame.event.clear() | ||||||
|      |  | ||||||
|     speak("Select an adventure") |     speak("Select an adventure") | ||||||
|     time.sleep(1.0) |     time.sleep(1.0) | ||||||
|      |  | ||||||
|     while loop: |     while loop: | ||||||
|         if i != j: |         if i != j: | ||||||
|             speak(options[i]) |             speak(options[i]) | ||||||
|             j = i |             j = i | ||||||
|              |  | ||||||
|         pygame.event.pump() |         pygame.event.pump() | ||||||
|         event = pygame.event.wait() |         event = pygame.event.wait() | ||||||
|          |  | ||||||
|         if event.type == pygame.KEYDOWN: |         if event.type == pygame.KEYDOWN: | ||||||
|             if event.key == pygame.K_ESCAPE: |             if event.key == pygame.K_ESCAPE: | ||||||
|                 return None |                 return None | ||||||
|                  |  | ||||||
|             if event.key == pygame.K_DOWN and i < len(options) - 1: |             if event.key == pygame.K_DOWN and i < len(options) - 1: | ||||||
|                 i = i + 1 |                 i = i + 1 | ||||||
|                 try: |                 try: | ||||||
|                     sounds['menu-move'].play() |                     sounds['menu-move'].play() | ||||||
|                 except: |                 except: | ||||||
|                     pass |                     pass | ||||||
|                      |  | ||||||
|             if event.key == pygame.K_UP and i > 0: |             if event.key == pygame.K_UP and i > 0: | ||||||
|                 i = i - 1 |                 i = i - 1 | ||||||
|                 try: |                 try: | ||||||
|                     sounds['menu-move'].play() |                     sounds['menu-move'].play() | ||||||
|                 except: |                 except: | ||||||
|                     pass |                     pass | ||||||
|                      |  | ||||||
|             if event.key == pygame.K_HOME and i != 0: |             if event.key == pygame.K_HOME and i != 0: | ||||||
|                 i = 0 |                 i = 0 | ||||||
|                 try: |                 try: | ||||||
|                     sounds['menu-move'].play() |                     sounds['menu-move'].play() | ||||||
|                 except: |                 except: | ||||||
|                     pass |                     pass | ||||||
|                      |  | ||||||
|             if event.key == pygame.K_END and i != len(options) - 1: |             if event.key == pygame.K_END and i != len(options) - 1: | ||||||
|                 i = len(options) - 1 |                 i = len(options) - 1 | ||||||
|                 try: |                 try: | ||||||
|                     sounds['menu-move'].play() |                     sounds['menu-move'].play() | ||||||
|                 except: |                 except: | ||||||
|                     pass |                     pass | ||||||
|                      |  | ||||||
|             if event.key == pygame.K_RETURN: |             if event.key == pygame.K_RETURN: | ||||||
|                 try: |                 try: | ||||||
|                     sounds['menu-select'].play() |                     sounds['menu-select'].play() | ||||||
| @@ -87,48 +87,48 @@ def selection_menu(sounds, *options): | |||||||
|                 return options[i] |                 return options[i] | ||||||
|         elif event.type == pygame.QUIT: |         elif event.type == pygame.QUIT: | ||||||
|             return None |             return None | ||||||
|              |  | ||||||
|         pygame.event.pump() |         pygame.event.pump() | ||||||
|         event = pygame.event.clear() |         event = pygame.event.clear() | ||||||
|         time.sleep(0.001) |         time.sleep(0.001) | ||||||
|  |  | ||||||
| def select_game(sounds): | def select_game(sounds): | ||||||
|     """Display game selection menu and return chosen game. |     """Display game selection menu and return chosen game. | ||||||
|      |  | ||||||
|     Args: |     Args: | ||||||
|         sounds (dict): Dictionary of loaded sound effects |         sounds (dict): Dictionary of loaded sound effects | ||||||
|          |  | ||||||
|     Returns: |     Returns: | ||||||
|         str: Selected game directory name or None if cancelled |         str: Selected game directory name or None if cancelled | ||||||
|     """ |     """ | ||||||
|     availableGames = get_available_games() |     availableGames = get_available_games() | ||||||
|      |  | ||||||
|     if not availableGames: |     if not availableGames: | ||||||
|         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] |     menuOptions = [game.replace("_", " ") for game in availableGames] | ||||||
|      |  | ||||||
|     choice = selection_menu(sounds, *menuOptions) |     choice = selection_menu(sounds, *menuOptions) | ||||||
|      |  | ||||||
|     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(" ", "_") |     gameDir = choice.replace(" ", "_") | ||||||
|     if gameDir not in availableGames: |     if gameDir not in availableGames: | ||||||
|         gameDir = choice  # Use original if conversion doesn't match |         gameDir = choice  # Use original if conversion doesn't match | ||||||
|          |  | ||||||
|     return gameDir |     return gameDir | ||||||
|  |  | ||||||
| def get_level_path(gameDir, levelNum): | def get_level_path(gameDir, levelNum): | ||||||
|     """Get full path to level JSON file. |     """Get full path to level JSON file. | ||||||
|      |  | ||||||
|     Args: |     Args: | ||||||
|         gameDir (str): Game directory name |         gameDir (str): Game directory name | ||||||
|         levelNum (int): Level number |         levelNum (int): Level number | ||||||
|          |  | ||||||
|     Returns: |     Returns: | ||||||
|         str: Full path to level JSON file |         str: Full path to level JSON file | ||||||
|     """ |     """ | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ 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.""" | ||||||
|      |  | ||||||
|     def __init__(self, xRange, y, sounds, delay=1000, crumble_speed=0.065): |     def __init__(self, xRange, y, sounds, delay=1000, crumble_speed=0.065): | ||||||
|         super().__init__( |         super().__init__( | ||||||
|             xRange, |             xRange, | ||||||
| @@ -19,7 +19,7 @@ class GraspingHands(Object): | |||||||
|         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 | ||||||
|         self.crumble_speed = crumble_speed  # How fast the crumbling catches up (tiles per frame) |         self.crumble_speed = crumble_speed  # How fast the crumbling catches up (tiles per frame) | ||||||
|          |  | ||||||
|         # State tracking |         # State tracking | ||||||
|         self.isTriggered = False  # Has the player entered the zone? |         self.isTriggered = False  # Has the player entered the zone? | ||||||
|         self.triggerTime = 0  # When did the player enter the zone? |         self.triggerTime = 0  # When did the player enter the zone? | ||||||
| @@ -29,13 +29,13 @@ class GraspingHands(Object): | |||||||
|         self.crumbleChannel = None  # Channel for the looping crumble sound |         self.crumbleChannel = None  # Channel for the looping crumble sound | ||||||
|         self.entryFromRight = False  # Which side did player enter from |         self.entryFromRight = False  # Which side did player enter from | ||||||
|         self.crumbleDirection = 1  # Direction the crumbling moves (1=right, -1=left) |         self.crumbleDirection = 1  # Direction the crumbling moves (1=right, -1=left) | ||||||
|          |  | ||||||
|     def trigger(self, currentTime, playerX): |     def trigger(self, currentTime, playerX): | ||||||
|         """Trigger the grasping hands when player enters range""" |         """Trigger the grasping hands when player enters range""" | ||||||
|         if not self.isTriggered: |         if not self.isTriggered: | ||||||
|             self.isTriggered = True |             self.isTriggered = True | ||||||
|             self.triggerTime = currentTime |             self.triggerTime = currentTime | ||||||
|              |  | ||||||
|             # Determine which side player entered from |             # Determine which side player entered from | ||||||
|             if playerX > (self.xRange[0] + self.xRange[1]) / 2: |             if playerX > (self.xRange[0] + self.xRange[1]) / 2: | ||||||
|                 # Player entered from right side |                 # Player entered from right side | ||||||
| @@ -47,48 +47,48 @@ class GraspingHands(Object): | |||||||
|                 self.entryFromRight = False |                 self.entryFromRight = False | ||||||
|                 self.crumblePosition = self.xRange[0]  # Start crumbling from left boundary |                 self.crumblePosition = self.xRange[0]  # Start crumbling from left boundary | ||||||
|                 self.crumbleDirection = 1  # Crumble moves right |                 self.crumbleDirection = 1  # Crumble moves right | ||||||
|                  |  | ||||||
|             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): | ||||||
|         """Reset the trap when player leaves the range""" |         """Reset the trap when player leaves the range""" | ||||||
|         if not self.isReset: |         if not self.isReset: | ||||||
|             self.isTriggered = False |             self.isTriggered = False | ||||||
|             self.crumblePosition = 0 |             self.crumblePosition = 0 | ||||||
|             self.isReset = True |             self.isReset = True | ||||||
|              |  | ||||||
|             # Stop the looping crumble sound if it's playing |             # Stop the looping crumble sound if it's playing | ||||||
|             if self.crumbleChannel: |             if self.crumbleChannel: | ||||||
|                 obj_stop(self.crumbleChannel) |                 obj_stop(self.crumbleChannel) | ||||||
|                 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""" | ||||||
|         if not self.isActive: |         if not self.isActive: | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         # Check if player is in range |         # Check if player is in range | ||||||
|         isInRange = self.xRange[0] <= player.xPos <= self.xRange[1] |         isInRange = self.xRange[0] <= player.xPos <= self.xRange[1] | ||||||
|          |  | ||||||
|         # Handle player entering/exiting range |         # Handle player entering/exiting range | ||||||
|         if isInRange and not self.isTriggered: |         if isInRange and not self.isTriggered: | ||||||
|             self.trigger(currentTime, player.xPos) |             self.trigger(currentTime, player.xPos) | ||||||
|         elif not isInRange and self.isTriggered: |         elif not isInRange and self.isTriggered: | ||||||
|             self.reset() |             self.reset() | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         # If triggered and delay has passed, start crumbling |         # If triggered and delay has passed, start crumbling | ||||||
|         if self.isTriggered and currentTime - self.triggerTime >= self.delay: |         if self.isTriggered and currentTime - self.triggerTime >= self.delay: | ||||||
|             # Update crumble position based on direction |             # Update crumble position based on direction | ||||||
|             self.crumblePosition += self.crumble_speed * self.crumbleDirection |             self.crumblePosition += self.crumble_speed * self.crumbleDirection | ||||||
|              |  | ||||||
|             # 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 | ||||||
| @@ -96,14 +96,14 @@ class GraspingHands(Object): | |||||||
|             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) | ||||||
|                  |  | ||||||
|             # 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: | ||||||
|                 if not player.isInvincible: |                 if not player.isInvincible: | ||||||
|                     # Player is caught - instant death |                     # Player is caught - instant death | ||||||
| @@ -111,14 +111,14 @@ class GraspingHands(Object): | |||||||
|                     if self.crumbleChannel: |                     if self.crumbleChannel: | ||||||
|                         obj_stop(self.crumbleChannel) |                         obj_stop(self.crumbleChannel) | ||||||
|                         self.crumbleChannel = None |                         self.crumbleChannel = None | ||||||
|                      |  | ||||||
|                     speak("The hands of the dead drag you down!") |                     speak("The hands of the dead drag you down!") | ||||||
|                     player.set_health(0) |                     player.set_health(0) | ||||||
|                     return True |                     return True | ||||||
|                 # Player is invincible - no warning needed |                 # Player is invincible - no warning needed | ||||||
|              |  | ||||||
|         return False |         return False | ||||||
|          |  | ||||||
|     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 | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class GraveObject(Object): | |||||||
|  |  | ||||||
|     def collect_grave_item(self, player): |     def collect_grave_item(self, player): | ||||||
|         """Handle collection of items from graves via ducking. |         """Handle collection of items from graves via ducking. | ||||||
|      |  | ||||||
|         Returns: |         Returns: | ||||||
|             bool: True if item was collected, False if player should die |             bool: True if item was collected, False if player should die | ||||||
|         """ |         """ | ||||||
| @@ -32,5 +32,5 @@ class GraveObject(Object): | |||||||
|         if player.isDucking: |         if player.isDucking: | ||||||
|             self.isCollected = True  # Mark as collected when collection succeeds |             self.isCollected = True  # Mark as collected when collection succeeds | ||||||
|             return True |             return True | ||||||
|          |  | ||||||
|         return False |         return False | ||||||
|   | |||||||
| @@ -15,13 +15,13 @@ class ItemType(Enum): | |||||||
|  |  | ||||||
| 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.HAND_OF_GLORY: "hand_of_glory", | ||||||
|         ItemType.JACK_O_LANTERN: "jack_o_lantern" |         ItemType.JACK_O_LANTERN: "jack_o_lantern" | ||||||
|     } |     } | ||||||
|      |  | ||||||
|     # All possible items (including special ones) |     # All possible items (including special ones) | ||||||
|     ALL_ITEMS = { |     ALL_ITEMS = { | ||||||
|         ItemType.GUTS: "guts", |         ItemType.GUTS: "guts", | ||||||
| @@ -31,23 +31,23 @@ class ItemProperties: | |||||||
|         ItemType.CAULDRON: "cauldron", |         ItemType.CAULDRON: "cauldron", | ||||||
|         ItemType.WITCH_BROOM: "witch_broom" |         ItemType.WITCH_BROOM: "witch_broom" | ||||||
|     } |     } | ||||||
|      |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_sound_name(item_type): |     def get_sound_name(item_type): | ||||||
|         """Convert enum to sound/asset name""" |         """Convert enum to sound/asset name""" | ||||||
|         return ItemProperties.ALL_ITEMS.get(item_type) |         return ItemProperties.ALL_ITEMS.get(item_type) | ||||||
|      |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_random_item(): |     def get_random_item(): | ||||||
|         """Get a random item from eligible items""" |         """Get a random item from eligible items""" | ||||||
|         item_type = random.choice(list(ItemProperties.RANDOM_ELIGIBLE.keys())) |         item_type = random.choice(list(ItemProperties.RANDOM_ELIGIBLE.keys())) | ||||||
|         return ItemProperties.get_sound_name(item_type) |         return ItemProperties.get_sound_name(item_type) | ||||||
|      |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def is_valid_item(item_name): |     def is_valid_item(item_name): | ||||||
|         """Check if an item name is valid""" |         """Check if an item name is valid""" | ||||||
|         return item_name in [v for v in ItemProperties.ALL_ITEMS.values()] |         return item_name in [v for v in ItemProperties.ALL_ITEMS.values()] | ||||||
|      |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_item_type(item_name): |     def get_item_type(item_name): | ||||||
|         """Get ItemType enum from string name""" |         """Get ItemType enum from string name""" | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								src/level.py
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/level.py
									
									
									
									
									
								
							| @@ -36,7 +36,7 @@ class Level: | |||||||
|  |  | ||||||
|         # Get footstep sound for this level, default to 'footstep' if not specified |         # Get footstep sound for this level, default to 'footstep' if not specified | ||||||
|         self.footstepSound = levelData.get("footstep_sound", "footstep") |         self.footstepSound = levelData.get("footstep_sound", "footstep") | ||||||
|          |  | ||||||
|         # Pass footstep sound to player |         # Pass footstep sound to player | ||||||
|         self.player.set_footstep_sound(self.footstepSound) |         self.player.set_footstep_sound(self.footstepSound) | ||||||
|  |  | ||||||
| @@ -77,7 +77,7 @@ class Level: | |||||||
|                 xPos = obj["x_range"] |                 xPos = obj["x_range"] | ||||||
|             else: |             else: | ||||||
|                 xPos = [obj["x"], obj["x"]]  # Single position as range |                 xPos = [obj["x"], obj["x"]]  # Single position as range | ||||||
|      |  | ||||||
|             # Check if this is a catapult |             # Check if this is a catapult | ||||||
|             if obj.get("type") == "catapult": |             if obj.get("type") == "catapult": | ||||||
|                 catapult = Catapult( |                 catapult = Catapult( | ||||||
| @@ -194,22 +194,22 @@ class Level: | |||||||
|     def update_audio(self): |     def update_audio(self): | ||||||
|         """Update all audio and entity state.""" |         """Update all audio and entity state.""" | ||||||
|         currentTime = pygame.time.get_ticks() |         currentTime = pygame.time.get_ticks() | ||||||
|          |  | ||||||
|         # Update regular objects and check for zombie spawning |         # Update regular objects and check for zombie spawning | ||||||
|         for obj in self.objects: |         for obj in self.objects: | ||||||
|             if not obj.isActive: |             if not obj.isActive: | ||||||
|                 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  |                 obj.zombieSpawnChance > 0 and  | ||||||
|                 not obj.hasSpawned): |                 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 | ||||||
|                     # Mark as checked before doing anything else to prevent multiple checks |                     # Mark as checked before doing anything else to prevent multiple checks | ||||||
|                     obj.hasSpawned = True |                     obj.hasSpawned = True | ||||||
|                      |  | ||||||
|                     roll = random.randint(1, 100) |                     roll = random.randint(1, 100) | ||||||
|                     if roll <= obj.zombieSpawnChance: |                     if roll <= obj.zombieSpawnChance: | ||||||
|                         zombie = Enemy( |                         zombie = Enemy( | ||||||
| @@ -224,7 +224,7 @@ class Level: | |||||||
|                         ) |                         ) | ||||||
|                         self.enemies.append(zombie) |                         self.enemies.append(zombie) | ||||||
|                         speak("A zombie emerges from the grave!") |                         speak("A zombie emerges from the grave!") | ||||||
|              |  | ||||||
|             # Handle object audio |             # Handle object audio | ||||||
|             if obj.channel is not None: |             if obj.channel is not None: | ||||||
|                 obj.channel = obj_update(obj.channel, self.player.xPos, obj.xPos) |                 obj.channel = obj_update(obj.channel, self.player.xPos, obj.xPos) | ||||||
| @@ -233,12 +233,12 @@ class Level: | |||||||
|                     obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos) |                     obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos) | ||||||
|                 else: |                 else: | ||||||
|                     obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos) |                     obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos) | ||||||
|                  |  | ||||||
|         # Update enemies |         # Update enemies | ||||||
|         for enemy in self.enemies: |         for enemy in self.enemies: | ||||||
|             if not enemy.isActive: |             if not enemy.isActive: | ||||||
|                 continue |                 continue | ||||||
|                  |  | ||||||
|             enemy.update(currentTime, self.player) |             enemy.update(currentTime, self.player) | ||||||
|  |  | ||||||
|             # Only handle audio for non-vulnerability enemies |             # Only handle audio for non-vulnerability enemies | ||||||
| @@ -247,7 +247,7 @@ class Level: | |||||||
|                     enemy.channel = obj_play(self.sounds, enemy.enemyType, self.player.xPos, enemy.xPos) |                     enemy.channel = obj_play(self.sounds, enemy.enemyType, self.player.xPos, enemy.xPos) | ||||||
|                 if enemy.channel is not None: |                 if enemy.channel is not None: | ||||||
|                     enemy.channel = obj_update(enemy.channel, self.player.xPos, enemy.xPos) |                     enemy.channel = obj_update(enemy.channel, self.player.xPos, enemy.xPos) | ||||||
|                  |  | ||||||
|         # Update catapults |         # Update catapults | ||||||
|         for obj in self.objects: |         for obj in self.objects: | ||||||
|             if isinstance(obj, Catapult): |             if isinstance(obj, Catapult): | ||||||
| @@ -272,7 +272,7 @@ class Level: | |||||||
|                 if not item.isActive: |                 if not item.isActive: | ||||||
|                     speak(f"{item.soundName} got away!") |                     speak(f"{item.soundName} got away!") | ||||||
|                 continue |                 continue | ||||||
|                  |  | ||||||
|             # 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}']) | ||||||
| @@ -302,7 +302,7 @@ class Level: | |||||||
|                         obj.xPos >= attackRange[0] and  |                         obj.xPos >= attackRange[0] and  | ||||||
|                         obj.xPos <= attackRange[1] and |                         obj.xPos <= attackRange[1] and | ||||||
|                         self.player.isJumping):  # Must be jumping to hit floating coffins |                         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) | ||||||
|  |  | ||||||
| @@ -443,7 +443,7 @@ class Level: | |||||||
|                             # 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']) | ||||||
| @@ -460,7 +460,7 @@ class Level: | |||||||
|             if not proj.update(): |             if not proj.update(): | ||||||
|                 self.projectiles.remove(proj) |                 self.projectiles.remove(proj) | ||||||
|                 continue |                 continue | ||||||
|              |  | ||||||
|             # Check for enemy hits |             # Check for enemy hits | ||||||
|             for enemy in self.enemies: |             for enemy in self.enemies: | ||||||
|                 if enemy.isActive and abs(proj.x - enemy.xPos) < 1: |                 if enemy.isActive and abs(proj.x - enemy.xPos) < 1: | ||||||
| @@ -471,14 +471,14 @@ class Level: | |||||||
|                     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): | ||||||
|         """Have player throw a projectile""" |         """Have player throw a projectile""" | ||||||
|         proj_info = self.player.throw_projectile() |         proj_info = self.player.throw_projectile() | ||||||
|         if proj_info is None: |         if proj_info is None: | ||||||
|             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['type'], | ||||||
|             proj_info['start_x'], |             proj_info['start_x'], | ||||||
|   | |||||||
| @@ -17,17 +17,17 @@ class Object: | |||||||
|         self.isActive = True |         self.isActive = True | ||||||
|         # For collectibles in a range, track which positions have been collected |         # For collectibles in a range, track which positions have been collected | ||||||
|         self.collectedPositions = set() |         self.collectedPositions = set() | ||||||
|          |  | ||||||
|     @property |     @property | ||||||
|     def xPos(self): |     def xPos(self): | ||||||
|         """Return center of range for audio positioning""" |         """Return center of range for audio positioning""" | ||||||
|         return (self.xRange[0] + self.xRange[1]) / 2 |         return (self.xRange[0] + self.xRange[1]) / 2 | ||||||
|      |  | ||||||
|     def is_in_range(self, x): |     def is_in_range(self, x): | ||||||
|         """Check if a given x position is within this object's range""" |         """Check if a given x position is within this object's range""" | ||||||
|         tolerance = 0.5  # Half a unit tolerance |         tolerance = 0.5  # Half a unit tolerance | ||||||
|         return (self.xRange[0] - tolerance) <= x <= (self.xRange[1] + tolerance) |         return (self.xRange[0] - tolerance) <= x <= (self.xRange[1] + tolerance) | ||||||
|      |  | ||||||
|     def collect_at_position(self, x): |     def collect_at_position(self, x): | ||||||
|         """Mark a specific position in the range as collected""" |         """Mark a specific position in the range as collected""" | ||||||
|         self.collectedPositions.add(x) |         self.collectedPositions.add(x) | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class Player: | |||||||
|         self.isRunning = False |         self.isRunning = False | ||||||
|         self.runMultiplier = 1.5  # Same multiplier as jumping |         self.runMultiplier = 1.5  # Same multiplier as jumping | ||||||
|         self.facingRight = True |         self.facingRight = True | ||||||
|          |  | ||||||
|         # Stats and tracking |         # Stats and tracking | ||||||
|         self._health = 10 |         self._health = 10 | ||||||
|         self._maxHealth = 10 |         self._maxHealth = 10 | ||||||
| @@ -39,25 +39,25 @@ class Player: | |||||||
|         self.lastStepTime = 0 |         self.lastStepTime = 0 | ||||||
|         self.isRunning = False |         self.isRunning = False | ||||||
|         self.runMultiplier = 1.5 |         self.runMultiplier = 1.5 | ||||||
|          |  | ||||||
|         # Inventory system |         # Inventory system | ||||||
|         self.inventory = [] |         self.inventory = [] | ||||||
|         self.collectedItems = [] |         self.collectedItems = [] | ||||||
|         self._coins = 0 |         self._coins = 0 | ||||||
|         self._jack_o_lantern_count = 0 |         self._jack_o_lantern_count = 0 | ||||||
|         self.shinBoneCount = 0 |         self.shinBoneCount = 0 | ||||||
|          |  | ||||||
|         # Combat related attributes |         # Combat related attributes | ||||||
|         self.weapons = [] |         self.weapons = [] | ||||||
|         self.currentWeapon = None |         self.currentWeapon = None | ||||||
|         self.isAttacking = False |         self.isAttacking = False | ||||||
|         self.lastAttackTime = 0 |         self.lastAttackTime = 0 | ||||||
|          |  | ||||||
|         # Power-up states |         # Power-up states | ||||||
|         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 | ||||||
|          |  | ||||||
|         # Initialize starting weapon (rusty shovel) |         # Initialize starting weapon (rusty shovel) | ||||||
|         self.add_weapon(Weapon( |         self.add_weapon(Weapon( | ||||||
|             name="rusty_shovel", |             name="rusty_shovel", | ||||||
| @@ -97,64 +97,64 @@ class Player: | |||||||
|                 if self.currentWeapon: |                 if self.currentWeapon: | ||||||
|                     self.currentWeapon.attackDuration *= 0.5  # Restore attack speed |                     self.currentWeapon.attackDuration *= 0.5  # Restore attack speed | ||||||
|                 del self.webPenaltyEndTime |                 del self.webPenaltyEndTime | ||||||
|      |  | ||||||
|         # 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 | ||||||
|             if currentTime - self.invincibilityStartTime >= self.invincibilityDuration: |             if currentTime - self.invincibilityStartTime >= self.invincibilityDuration: | ||||||
|                 self.isInvincible = False |                 self.isInvincible = False | ||||||
|                 speak("Invincibility wore off!") |                 speak("Invincibility wore off!") | ||||||
|                 del self._last_countdown  # Clean up countdown tracker |                 del self._last_countdown  # Clean up countdown tracker | ||||||
|          |  | ||||||
|     def start_invincibility(self): |     def start_invincibility(self): | ||||||
|         """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): | ||||||
|         """Increment lives by 1""" |         """Increment lives by 1""" | ||||||
|         self._lives += 1 |         self._lives += 1 | ||||||
|          |  | ||||||
|     def get_jack_o_lanterns(self): |     def get_jack_o_lanterns(self): | ||||||
|         """Get number of jack o'lanterns""" |         """Get number of jack o'lanterns""" | ||||||
|         return self._jack_o_lantern_count |         return self._jack_o_lantern_count | ||||||
|          |  | ||||||
|     def add_jack_o_lantern(self): |     def add_jack_o_lantern(self): | ||||||
|         """Add a jack o'lantern""" |         """Add a jack o'lantern""" | ||||||
|         self._jack_o_lantern_count += 1 |         self._jack_o_lantern_count += 1 | ||||||
|          |  | ||||||
|     def add_guts(self): |     def add_guts(self): | ||||||
|         """Apply guts, increase max_health by 2 if less than 20 else restore health""" |         """Apply guts, increase max_health by 2 if less than 20 else restore health""" | ||||||
|         if self._maxHealth < 20: |         if self._maxHealth < 20: | ||||||
|             self._maxHealth += 2 |             self._maxHealth += 2 | ||||||
|         else: |         else: | ||||||
|             self._health = self._maxHealth |             self._health = self._maxHealth | ||||||
|          |  | ||||||
|     def throw_projectile(self): |     def throw_projectile(self): | ||||||
|         """Throw a jack o'lantern if we have any""" |         """Throw a jack o'lantern if we have any""" | ||||||
|         if self.get_jack_o_lanterns() <= 0: |         if self.get_jack_o_lanterns() <= 0: | ||||||
|             return None |             return None | ||||||
|          |  | ||||||
|         self._jack_o_lantern_count -= 1 |         self._jack_o_lantern_count -= 1 | ||||||
|         return { |         return { | ||||||
|             'type': 'jack_o_lantern', |             'type': 'jack_o_lantern', | ||||||
|             'start_x': self.xPos, |             'start_x': self.xPos, | ||||||
|             'direction': 1 if self.facingRight else -1 |             '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""" | ||||||
|         if self.isRunning or self.isJumping: |         if self.isRunning or self.isJumping: | ||||||
| @@ -170,11 +170,11 @@ class Player: | |||||||
|     def get_health(self): |     def get_health(self): | ||||||
|         """Get current health""" |         """Get current health""" | ||||||
|         return self._health |         return self._health | ||||||
|          |  | ||||||
|     def get_max_health(self): |     def get_max_health(self): | ||||||
|         """Get current max health""" |         """Get current max health""" | ||||||
|         return self._maxHealth |         return self._maxHealth | ||||||
|          |  | ||||||
|     def restore_health(self): |     def restore_health(self): | ||||||
|         """Restore health to maximum""" |         """Restore health to maximum""" | ||||||
|         self._health = self._maxHealth |         self._health = self._maxHealth | ||||||
| @@ -192,13 +192,13 @@ class Player: | |||||||
|     def set_health(self, value): |     def set_health(self, value): | ||||||
|         """Set health and handle death if needed.""" |         """Set health and handle death if needed.""" | ||||||
|         old_health = self._health |         old_health = self._health | ||||||
|      |  | ||||||
|         # 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 | ||||||
|      |  | ||||||
|         if self._health == 0 and old_health > 0: |         if self._health == 0 and old_health > 0: | ||||||
|             self._lives -= 1 |             self._lives -= 1 | ||||||
|             # Stop all current sounds before playing death sound |             # Stop all current sounds before playing death sound | ||||||
| @@ -209,7 +209,7 @@ class Player: | |||||||
|                 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""" | ||||||
|         self._maxHealth = value |         self._maxHealth = value | ||||||
| @@ -217,22 +217,22 @@ class Player: | |||||||
|     def get_coins(self): |     def get_coins(self): | ||||||
|         """Get remaining coins""" |         """Get remaining coins""" | ||||||
|         return self._coins |         return self._coins | ||||||
|              |  | ||||||
|     def get_lives(self): |     def get_lives(self): | ||||||
|         """Get remaining lives""" |         """Get remaining lives""" | ||||||
|         return self._lives |         return self._lives | ||||||
|              |  | ||||||
|     def add_weapon(self, weapon): |     def add_weapon(self, weapon): | ||||||
|         """Add a new weapon to inventory and equip if first weapon""" |         """Add a new weapon to inventory and equip if first weapon""" | ||||||
|         self.weapons.append(weapon) |         self.weapons.append(weapon) | ||||||
|         if len(self.weapons) == 1:  # If this is our first weapon, equip it |         if len(self.weapons) == 1:  # If this is our first weapon, equip it | ||||||
|             self.equip_weapon(weapon) |             self.equip_weapon(weapon) | ||||||
|              |  | ||||||
|     def equip_weapon(self, weapon): |     def equip_weapon(self, weapon): | ||||||
|         """Equip a specific weapon""" |         """Equip a specific weapon""" | ||||||
|         if weapon in self.weapons: |         if weapon in self.weapons: | ||||||
|             self.currentWeapon = weapon |             self.currentWeapon = weapon | ||||||
|              |  | ||||||
|     def add_item(self, item): |     def add_item(self, item): | ||||||
|         """Add an item to inventory""" |         """Add an item to inventory""" | ||||||
|         self.inventory.append(item) |         self.inventory.append(item) | ||||||
| @@ -245,7 +245,7 @@ class Player: | |||||||
|             self.lastAttackTime = currentTime |             self.lastAttackTime = currentTime | ||||||
|             return True |             return True | ||||||
|         return False |         return False | ||||||
|              |  | ||||||
|     def get_attack_range(self, currentTime): |     def get_attack_range(self, currentTime): | ||||||
|         """Get the current attack's range based on position and facing direction""" |         """Get the current attack's range based on position and facing direction""" | ||||||
|         if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime): |         if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime): | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ class PowerUp(Object): | |||||||
|  |  | ||||||
|         # Update position |         # Update position | ||||||
|         new_x = self._currentX + self.direction * self.speed |         new_x = self._currentX + self.direction * self.speed | ||||||
|      |  | ||||||
|         # Check boundaries and bounce if needed |         # Check boundaries and bounce if needed | ||||||
|         if new_x < self.left_boundary: |         if new_x < self.left_boundary: | ||||||
|             self._currentX = self.left_boundary |             self._currentX = self.left_boundary | ||||||
| @@ -55,7 +55,7 @@ class PowerUp(Object): | |||||||
|             return False |             return False | ||||||
|  |  | ||||||
|         return True |         return True | ||||||
|          |  | ||||||
|     def apply_effect(self, player): |     def apply_effect(self, player): | ||||||
|         """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': | ||||||
| @@ -99,7 +99,7 @@ class PowerUp(Object): | |||||||
|             # 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 | ||||||
|         if self.channel: |         if self.channel: | ||||||
|             self.channel.stop() |             self.channel.stop() | ||||||
|   | |||||||
| @@ -10,21 +10,21 @@ class Projectile: | |||||||
|         self.damage = 5  # All projectiles do same damage for now |         self.damage = 5  # All projectiles do same damage for now | ||||||
|         self.range = 12  # Maximum travel distance in tiles |         self.range = 12  # Maximum travel distance in tiles | ||||||
|         self.start_x = start_x |         self.start_x = start_x | ||||||
|          |  | ||||||
|     def update(self): |     def update(self): | ||||||
|         """Update projectile position and check if it should still exist""" |         """Update projectile position and check if it should still exist""" | ||||||
|         if not self.isActive: |         if not self.isActive: | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         self.x += self.direction * self.speed |         self.x += self.direction * self.speed | ||||||
|          |  | ||||||
|         # Check if projectile has gone too far |         # Check if projectile has gone too far | ||||||
|         if abs(self.x - self.start_x) > self.range: |         if abs(self.x - self.start_x) > self.range: | ||||||
|             self.isActive = False |             self.isActive = False | ||||||
|             return False |             return False | ||||||
|              |  | ||||||
|         return True |         return True | ||||||
|          |  | ||||||
|     def hit_enemy(self, enemy): |     def hit_enemy(self, enemy): | ||||||
|         """Handle hitting an enemy""" |         """Handle hitting an enemy""" | ||||||
|         enemy.take_damage(self.damage) |         enemy.take_damage(self.damage) | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ 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, | ||||||
| @@ -22,7 +22,7 @@ class SkullStorm(Object): | |||||||
|         self.maxSkulls = maxSkulls |         self.maxSkulls = maxSkulls | ||||||
|         self.minFreq = minFreq * 1000  # Convert to milliseconds |         self.minFreq = minFreq * 1000  # Convert to milliseconds | ||||||
|         self.maxFreq = maxFreq * 1000 |         self.maxFreq = maxFreq * 1000 | ||||||
|          |  | ||||||
|         self.activeSkulls = []  # List of currently falling skulls |         self.activeSkulls = []  # List of currently falling skulls | ||||||
|         self.lastSkullTime = 0 |         self.lastSkullTime = 0 | ||||||
|         self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq) |         self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq) | ||||||
| @@ -32,7 +32,7 @@ class SkullStorm(Object): | |||||||
|         """Update all active skulls and potentially spawn new ones.""" |         """Update all active skulls and potentially spawn new ones.""" | ||||||
|         if not self.isActive: |         if not self.isActive: | ||||||
|             return |             return | ||||||
|      |  | ||||||
|         # Check if player has entered range |         # Check if player has entered range | ||||||
|         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: | ||||||
| @@ -43,16 +43,16 @@ class SkullStorm(Object): | |||||||
|             # Player just left range |             # Player just left range | ||||||
|             self.playerInRange = False |             self.playerInRange = False | ||||||
|             speak("Skull storm ended.") |             speak("Skull storm ended.") | ||||||
|          |  | ||||||
|             # 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: | ||||||
|             return |             return | ||||||
|      |  | ||||||
|         # 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']: | ||||||
| @@ -64,7 +64,7 @@ class SkullStorm(Object): | |||||||
|                 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', | ||||||
| @@ -74,21 +74,21 @@ class SkullStorm(Object): | |||||||
|                     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): | ||||||
|         """Spawn a new falling skull at a random position within range.""" |         """Spawn a new falling skull at a random position within range.""" | ||||||
|         # Reset timing |         # Reset timing | ||||||
|         self.lastSkullTime = currentTime |         self.lastSkullTime = currentTime | ||||||
|         self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq) |         self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq) | ||||||
|          |  | ||||||
|         # Calculate fall duration based on height (higher = longer fall) |         # Calculate fall duration based on height (higher = longer fall) | ||||||
|         fallDuration = self.yPos * 100  # 100ms per unit of height |         fallDuration = self.yPos * 100  # 100ms per unit of height | ||||||
|          |  | ||||||
|         # 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]), | ||||||
|   | |||||||
| @@ -10,10 +10,10 @@ class StatTracker: | |||||||
|             'Items collected': 0, |             'Items collected': 0, | ||||||
|             'Total time': 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): | ||||||
| @@ -27,18 +27,18 @@ class StatTracker: | |||||||
|                 self.level[key] = [] |                 self.level[key] = [] | ||||||
|             elif self.level[key] is None: |             elif self.level[key] is None: | ||||||
|                 self.level[key] = None |                 self.level[key] = None | ||||||
|      |  | ||||||
|     def update_stat(self, statName, value=1, levelOnly=False): |     def update_stat(self, statName, value=1, levelOnly=False): | ||||||
|         """Update a stat in both level and total (unless levelOnly is True)""" |         """Update a stat in both level and total (unless levelOnly is True)""" | ||||||
|         if statName in self.level: |         if statName in self.level: | ||||||
|             self.level[statName] += value |             self.level[statName] += value | ||||||
|         if not levelOnly and statName in self.total: |         if not levelOnly and statName in self.total: | ||||||
|             self.total[statName] += value |             self.total[statName] += value | ||||||
|              |  | ||||||
|     def get_level_stat(self, statName): |     def get_level_stat(self, statName): | ||||||
|         """Get a level stat""" |         """Get a level stat""" | ||||||
|         return self.level.get(statName, 0) |         return self.level.get(statName, 0) | ||||||
|          |  | ||||||
|     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) | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ class Weapon: | |||||||
|         self.attackDuration = attackDuration  # Milliseconds the attack is active |         self.attackDuration = attackDuration  # Milliseconds the attack is active | ||||||
|         self.lastAttackTime = 0 |         self.lastAttackTime = 0 | ||||||
|         self.hitEnemies = set() |         self.hitEnemies = set() | ||||||
|          |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def create_nunchucks(cls): |     def create_nunchucks(cls): | ||||||
|         """Create the nunchucks weapon""" |         """Create the nunchucks weapon""" | ||||||
| @@ -41,7 +41,7 @@ class Weapon: | |||||||
|     def can_attack(self, currentTime): |     def can_attack(self, currentTime): | ||||||
|         """Check if enough time has passed since last attack""" |         """Check if enough time has passed since last attack""" | ||||||
|         return currentTime - self.lastAttackTime >= self.cooldown |         return currentTime - self.lastAttackTime >= self.cooldown | ||||||
|          |  | ||||||
|     def get_attack_range(self, playerPos, facingRight): |     def get_attack_range(self, playerPos, facingRight): | ||||||
|         """Calculate the area that this attack would hit""" |         """Calculate the area that this attack would hit""" | ||||||
|         if facingRight: |         if facingRight: | ||||||
| @@ -56,7 +56,7 @@ class Weapon: | |||||||
|             self.hitEnemies.clear()  # Clear hit enemies for new attack |             self.hitEnemies.clear()  # Clear hit enemies for new attack | ||||||
|             return True |             return True | ||||||
|         return False |         return False | ||||||
|          |  | ||||||
|     def is_attack_active(self, currentTime): |     def is_attack_active(self, currentTime): | ||||||
|         """Check if the attack is still in its active frames""" |         """Check if the attack is still in its active frames""" | ||||||
|         timeSinceAttack = currentTime - self.lastAttackTime |         timeSinceAttack = currentTime - self.lastAttackTime | ||||||
|   | |||||||
| @@ -22,14 +22,14 @@ class WickedQuest: | |||||||
|         self.player = None |         self.player = None | ||||||
|         self.currentGame = None |         self.currentGame = None | ||||||
|         self.runLock = False  # Toggle behavior of the run keys |         self.runLock = False  # Toggle behavior of the run keys | ||||||
|          |  | ||||||
|     def load_level(self, levelNumber): |     def load_level(self, levelNumber): | ||||||
|         """Load a level from its JSON file.""" |         """Load a level from its JSON file.""" | ||||||
|         levelFile = get_level_path(self.currentGame, levelNumber) |         levelFile = get_level_path(self.currentGame, levelNumber) | ||||||
|         try: |         try: | ||||||
|             with open(levelFile, 'r') as f: |             with open(levelFile, 'r') as f: | ||||||
|                 levelData = json.load(f) |                 levelData = json.load(f) | ||||||
|                  |  | ||||||
|             # Create player if this is the first level |             # Create player if this is the first level | ||||||
|             if self.player is None: |             if self.player is None: | ||||||
|                 self.player = Player(levelData["player_start"]["x"],  |                 self.player = Player(levelData["player_start"]["x"],  | ||||||
| @@ -51,11 +51,11 @@ class WickedQuest: | |||||||
|                     self.player.moveSpeed *= 2  # Restore normal speed |                     self.player.moveSpeed *= 2  # Restore normal speed | ||||||
|                     if self.player.currentWeapon: |                     if self.player.currentWeapon: | ||||||
|                         self.player.currentWeapon.attackDuration *= 0.5  # Restore normal attack speed |                         self.player.currentWeapon.attackDuration *= 0.5  # Restore normal attack speed | ||||||
|                  |  | ||||||
|             # Pass existing player to new level |             # Pass existing player to new level | ||||||
|             pygame.event.clear() |             pygame.event.clear() | ||||||
|             self.currentLevel = Level(levelData, self.sounds, self.player) |             self.currentLevel = Level(levelData, self.sounds, self.player) | ||||||
|          |  | ||||||
|             return True |             return True | ||||||
|         except FileNotFoundError: |         except FileNotFoundError: | ||||||
|             return False |             return False | ||||||
| @@ -63,14 +63,14 @@ class WickedQuest: | |||||||
|     def validate_levels(self): |     def validate_levels(self): | ||||||
|         """Check if level files have valid JSON.""" |         """Check if level files have valid JSON.""" | ||||||
|         errors = [] |         errors = [] | ||||||
|      |  | ||||||
|         # Check levels from 1 until no more files are found |         # Check levels from 1 until no more files are found | ||||||
|         levelNumber = 1 |         levelNumber = 1 | ||||||
|         while True: |         while True: | ||||||
|             levelPath = get_level_path(self.currentGame, levelNumber) |             levelPath = get_level_path(self.currentGame, levelNumber) | ||||||
|             if not os.path.exists(levelPath): |             if not os.path.exists(levelPath): | ||||||
|                 break |                 break | ||||||
|              |  | ||||||
|             try: |             try: | ||||||
|                 with open(levelPath, 'r') as f: |                 with open(levelPath, 'r') as f: | ||||||
|                     # This will raise an exception if JSON is invalid |                     # This will raise an exception if JSON is invalid | ||||||
| @@ -79,9 +79,9 @@ class WickedQuest: | |||||||
|                 errors.append(f"Level {levelNumber}: Invalid JSON format - {str(e)}") |                 errors.append(f"Level {levelNumber}: Invalid JSON format - {str(e)}") | ||||||
|             except Exception as e: |             except Exception as e: | ||||||
|                 errors.append(f"Level {levelNumber}: Error reading file - {str(e)}") |                 errors.append(f"Level {levelNumber}: Error reading file - {str(e)}") | ||||||
|              |  | ||||||
|             levelNumber += 1 |             levelNumber += 1 | ||||||
|      |  | ||||||
|         return errors |         return errors | ||||||
|  |  | ||||||
|     def handle_input(self): |     def handle_input(self): | ||||||
| @@ -89,7 +89,7 @@ class WickedQuest: | |||||||
|         keys = pygame.key.get_pressed() |         keys = pygame.key.get_pressed() | ||||||
|         player = self.currentLevel.player |         player = self.currentLevel.player | ||||||
|         currentTime = pygame.time.get_ticks() |         currentTime = pygame.time.get_ticks() | ||||||
|      |  | ||||||
|         # Update running and ducking states |         # Update running and ducking states | ||||||
|         if (keys[pygame.K_s] or keys[pygame.K_DOWN]) and not player.isDucking: |         if (keys[pygame.K_s] or keys[pygame.K_DOWN]) and not player.isDucking: | ||||||
|             player.duck() |             player.duck() | ||||||
| @@ -106,7 +106,7 @@ class WickedQuest: | |||||||
|  |  | ||||||
|         # Track movement distance for this frame |         # Track movement distance for this frame | ||||||
|         movementDistance = 0 |         movementDistance = 0 | ||||||
|      |  | ||||||
|         # Horizontal movement |         # Horizontal movement | ||||||
|         if keys[pygame.K_a] or keys[pygame.K_LEFT]:  # Left |         if keys[pygame.K_a] or keys[pygame.K_LEFT]:  # Left | ||||||
|             movementDistance = currentSpeed |             movementDistance = currentSpeed | ||||||
| @@ -116,7 +116,7 @@ class WickedQuest: | |||||||
|             movementDistance = currentSpeed |             movementDistance = currentSpeed | ||||||
|             player.xPos += currentSpeed |             player.xPos += currentSpeed | ||||||
|             player.facingRight = True |             player.facingRight = True | ||||||
|          |  | ||||||
|         # Handle footsteps |         # Handle footsteps | ||||||
|         if movementDistance > 0 and not player.isJumping: |         if movementDistance > 0 and not player.isJumping: | ||||||
|             player.distanceSinceLastStep += movementDistance |             player.distanceSinceLastStep += movementDistance | ||||||
| @@ -147,13 +147,13 @@ class WickedQuest: | |||||||
|         # Handle attack with either CTRL key |         # Handle attack with either CTRL key | ||||||
|         if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime): |         if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime): | ||||||
|             play_sound(self.sounds[player.currentWeapon.attackSound]) |             play_sound(self.sounds[player.currentWeapon.attackSound]) | ||||||
|              |  | ||||||
|         # Handle jumping |         # Handle jumping | ||||||
|         if (keys[pygame.K_w] or keys[pygame.K_UP]) and not player.isJumping: |         if (keys[pygame.K_w] or keys[pygame.K_UP]) and not player.isJumping: | ||||||
|             player.isJumping = True |             player.isJumping = True | ||||||
|             player.jumpStartTime = currentTime |             player.jumpStartTime = currentTime | ||||||
|             play_sound(self.sounds['jump']) |             play_sound(self.sounds['jump']) | ||||||
|      |  | ||||||
|         # Check if jump should end |         # Check if jump should end | ||||||
|         if player.isJumping and currentTime - player.jumpStartTime >= player.jumpDuration: |         if player.isJumping and currentTime - player.jumpStartTime >= player.jumpDuration: | ||||||
|             player.isJumping = False |             player.isJumping = False | ||||||
| @@ -167,12 +167,12 @@ class WickedQuest: | |||||||
|         # Convert time from milliseconds to minutes:seconds |         # Convert time from milliseconds to minutes:seconds | ||||||
|         minutes = timeTaken // 60000 |         minutes = timeTaken // 60000 | ||||||
|         seconds = (timeTaken % 60000) // 1000 |         seconds = (timeTaken % 60000) // 1000 | ||||||
|      |  | ||||||
|         # Update time in stats |         # Update time in stats | ||||||
|         self.currentLevel.player.stats.update_stat('Total time', timeTaken, levelOnly=True) |         self.currentLevel.player.stats.update_stat('Total time', timeTaken, levelOnly=True) | ||||||
|  |  | ||||||
|         report = [f"Time taken: {minutes} minutes and {seconds} seconds"] |         report = [f"Time taken: {minutes} minutes and {seconds} seconds"] | ||||||
|      |  | ||||||
|         # Add all level stats |         # Add all level stats | ||||||
|         for key in self.currentLevel.player.stats.level: |         for key in self.currentLevel.player.stats.level: | ||||||
|             if key != 'Total time':  # Skip time since we already displayed it |             if key != 'Total time':  # Skip time since we already displayed it | ||||||
| @@ -195,10 +195,10 @@ class WickedQuest: | |||||||
|         """Display game over screen with statistics.""" |         """Display game over screen with statistics.""" | ||||||
|         minutes = timeTaken // 60000 |         minutes = timeTaken // 60000 | ||||||
|         seconds = (timeTaken % 60000) // 1000 |         seconds = (timeTaken % 60000) // 1000 | ||||||
|      |  | ||||||
|         report = ["Game Over!"] |         report = ["Game Over!"] | ||||||
|         report.append(f"Time taken: {minutes} minutes and {seconds} seconds") |         report.append(f"Time taken: {minutes} minutes and {seconds} seconds") | ||||||
|      |  | ||||||
|         # Add all total stats |         # Add all total stats | ||||||
|         for key in self.currentLevel.player.stats.total: |         for key in self.currentLevel.player.stats.total: | ||||||
|             if key not in ['Total time', 'levelsCompleted']:  # Skip these |             if key not in ['Total time', 'levelsCompleted']:  # Skip these | ||||||
| @@ -222,14 +222,14 @@ class WickedQuest: | |||||||
|         while True: |         while True: | ||||||
|             currentTime = pygame.time.get_ticks() |             currentTime = pygame.time.get_ticks() | ||||||
|             pygame.event.pump() |             pygame.event.pump() | ||||||
|      |  | ||||||
|             # Game volume controls |             # Game volume controls | ||||||
|             for event in pygame.event.get(): |             for event in pygame.event.get(): | ||||||
|                 if event.type == pygame.KEYDOWN: |                 if event.type == pygame.KEYDOWN: | ||||||
|                     # Check for Alt modifier |                     # Check for Alt modifier | ||||||
|                     mods = pygame.key.get_mods() |                     mods = pygame.key.get_mods() | ||||||
|                     altPressed = mods & pygame.KMOD_ALT |                     altPressed = mods & pygame.KMOD_ALT | ||||||
|                      |  | ||||||
|                     if event.key == pygame.K_ESCAPE: |                     if event.key == pygame.K_ESCAPE: | ||||||
|                         try: |                         try: | ||||||
|                             pygame.mixer.music.stop() |                             pygame.mixer.music.stop() | ||||||
| @@ -260,11 +260,11 @@ class WickedQuest: | |||||||
|             self.currentLevel.player.update(currentTime) |             self.currentLevel.player.update(currentTime) | ||||||
|             self.handle_input() |             self.handle_input() | ||||||
|             self.currentLevel.update_audio() |             self.currentLevel.update_audio() | ||||||
|      |  | ||||||
|             # Handle combat and projectiles |             # Handle combat and projectiles | ||||||
|             self.currentLevel.handle_combat(currentTime) |             self.currentLevel.handle_combat(currentTime) | ||||||
|             self.currentLevel.handle_projectiles(currentTime) |             self.currentLevel.handle_projectiles(currentTime) | ||||||
|      |  | ||||||
|             # Check for death first |             # Check for death first | ||||||
|             if self.currentLevel.player.get_health() <= 0: |             if self.currentLevel.player.get_health() <= 0: | ||||||
|                 if self.currentLevel.player.get_lives() <= 0: |                 if self.currentLevel.player.get_lives() <= 0: | ||||||
| @@ -307,7 +307,7 @@ class WickedQuest: | |||||||
|  |  | ||||||
|                     self.display_game_over(totalTime) |                     self.display_game_over(totalTime) | ||||||
|                     return |                     return | ||||||
|      |  | ||||||
|             clock.tick(60)  # 60 FPS |             clock.tick(60)  # 60 FPS | ||||||
|  |  | ||||||
|     def run(self): |     def run(self): | ||||||
| @@ -345,7 +345,7 @@ class WickedQuest: | |||||||
|                 for i, entry in enumerate(scores, 1): |                 for i, entry in enumerate(scores, 1): | ||||||
|                     scoreStr = f"{i}. {entry['name']}: {entry['score']}" |                     scoreStr = f"{i}. {entry['name']}: {entry['score']}" | ||||||
|                     lines.append(scoreStr) |                     lines.append(scoreStr) | ||||||
|      |  | ||||||
|                 pygame.event.clear() |                 pygame.event.clear() | ||||||
|                 display_text(lines) |                 display_text(lines) | ||||||
|             elif choice == "learn_sounds": |             elif choice == "learn_sounds": | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user