Updated libstormgames submodule. Added skull storms. Fixed no jack-o-lanterns reported when there actually were. Fixed player being recreated on each new level thus resetting all stats.
This commit is contained in:
		 Submodule libstormgames updated: d5c79c0770...658709ebce
									
								
							
							
								
								
									
										
											BIN
										
									
								
								sounds/falling_skull1.ogg
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								sounds/falling_skull1.ogg
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								sounds/skull_lands.ogg
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								sounds/skull_lands.ogg
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								sounds/skull_storm.ogg
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								sounds/skull_storm.ogg
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								sounds/throw_jack_o_lantern.ogg
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								sounds/throw_jack_o_lantern.ogg
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										49
									
								
								src/level.py
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								src/level.py
									
									
									
									
									
								
							| @@ -8,15 +8,17 @@ from src.object import Object | ||||
| from src.player import Player | ||||
| from src.projectile import Projectile | ||||
| from src.powerup import PowerUp | ||||
| from src.skull_storm import SkullStorm | ||||
|  | ||||
|  | ||||
| class Level: | ||||
|     def __init__(self, levelData, sounds): | ||||
|     def __init__(self, levelData, sounds, player): | ||||
|         self.sounds = sounds | ||||
|         self.objects = [] | ||||
|         self.enemies = [] | ||||
|         self.bouncing_items = [] | ||||
|         self.projectiles = []  # Track active projectiles | ||||
|         self.player = Player(levelData["player_start"]["x"], levelData["player_start"]["y"], sounds) | ||||
|         self.player = player | ||||
|         self.edge_warning_channel = None | ||||
|         self.weapon_hit_channel = None | ||||
|         self.leftBoundary = levelData["boundaries"]["left"] | ||||
| @@ -53,6 +55,18 @@ class Level: | ||||
|                     firingRange=obj.get("range", 20) | ||||
|                 ) | ||||
|                 self.objects.append(catapult) | ||||
|             # Check if this is a skull storm | ||||
|             elif obj.get("type") == "skull_storm": | ||||
|                 skullStorm = SkullStorm( | ||||
|                     xPos, | ||||
|                     obj["y"], | ||||
|                     self.sounds, | ||||
|                     obj.get("damage", 5), | ||||
|                     obj.get("maximum_skulls", 3), | ||||
|                     obj.get("frequency", {}).get("min", 2), | ||||
|                     obj.get("frequency", {}).get("max", 5) | ||||
|                 ) | ||||
|                 self.objects.append(skullStorm) | ||||
|             # Check if this is a coffin | ||||
|             elif obj.get("type") == "coffin": | ||||
|                 coffin = CoffinObject( | ||||
| @@ -120,15 +134,13 @@ class Level: | ||||
|                         speak("A zombie emerges from the grave!") | ||||
|              | ||||
|             # Handle object audio | ||||
|             if not obj.isStatic: | ||||
|                 if obj.channel is None or not obj.channel.get_busy(): | ||||
|                     obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos) | ||||
|             else: | ||||
|                 if obj.channel is None: | ||||
|                     obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos) | ||||
|                      | ||||
|             if obj.channel is not None: | ||||
|                 obj.channel = obj_update(obj.channel, self.player.xPos, obj.xPos) | ||||
|             elif obj.soundName:  # Only try to play sound if soundName is not empty | ||||
|                 if not obj.isStatic: | ||||
|                     obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos) | ||||
|                 else: | ||||
|                     obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos) | ||||
|                  | ||||
|         # Update enemies | ||||
|         for enemy in self.enemies: | ||||
| @@ -147,6 +159,11 @@ class Level: | ||||
|             if isinstance(obj, Catapult): | ||||
|                 obj.update(currentTime, self.player) | ||||
|  | ||||
|         # Update skull storms | ||||
|         for obj in self.objects: | ||||
|             if isinstance(obj, SkullStorm): | ||||
|                 obj.update(currentTime, self.player) | ||||
|  | ||||
|         # Update bouncing items | ||||
|         for item in self.bouncing_items[:]:  # Copy list to allow removal | ||||
|             if not item.update(currentTime): | ||||
| @@ -256,17 +273,25 @@ class Level: | ||||
|                 if enemy.isActive and abs(proj.x - enemy.xPos) < 1: | ||||
|                     proj.hit_enemy(enemy) | ||||
|                     self.projectiles.remove(proj) | ||||
|                     # Calculate volume and pan for splat sound based on final position | ||||
|                     volume, left, right = calculate_volume_and_pan(self.player.xPos, proj.x) | ||||
|                     if volume > 0:  # Only play if within audible range | ||||
|                         channel = self.sounds["pumpkin_splat"].play() | ||||
|                         if channel: | ||||
|                             channel.set_volume(volume * left, volume * right) | ||||
|                     break | ||||
|                      | ||||
|     def throw_projectile(self): | ||||
|         """Have player throw a projectile""" | ||||
|         proj_info = self.player.throw_projectile() | ||||
|         if proj_info: | ||||
|         if proj_info is None: | ||||
|             speak("No jack o'lanterns to throw!") | ||||
|             return | ||||
|          | ||||
|         self.projectiles.append(Projectile( | ||||
|             proj_info['type'], | ||||
|             proj_info['start_x'], | ||||
|             proj_info['direction'] | ||||
|         )) | ||||
|         # Play throw sound | ||||
|             if f"{proj_info['type']}_throw" in self.sounds: | ||||
|                 self.sounds[f"{proj_info['type']}_throw"].play() | ||||
|         self.sounds['throw_jack_o_lantern'].play() | ||||
|   | ||||
| @@ -26,6 +26,7 @@ class Player: | ||||
|         self.inventory = [] | ||||
|         self.collectedItems = [] | ||||
|         self._coins = 0 | ||||
|         self._jack_o_lantern_count = 0 | ||||
|          | ||||
|         # Combat related attributes | ||||
|         self.weapons = [] | ||||
| @@ -38,9 +39,6 @@ class Player: | ||||
|         self.invincibilityStartTime = 0 | ||||
|         self.invincibilityDuration = 5000  # 5 seconds of invincibility | ||||
|          | ||||
|         # Projectiles | ||||
|         self.projectiles = []  # List of type and quantity tuples | ||||
|          | ||||
|         # Initialize starting weapon (rusty shovel) | ||||
|         self.add_weapon(Weapon( | ||||
|             name="rusty_shovel", | ||||
| @@ -63,33 +61,22 @@ class Player: | ||||
|         self.isInvincible = True | ||||
|         self.invincibilityStartTime = pygame.time.get_ticks() | ||||
|          | ||||
|     def add_projectile(self, projectile_type): | ||||
|         """Add a projectile to inventory""" | ||||
|         # Find if we already have this type | ||||
|         for proj in self.projectiles: | ||||
|             if proj[0] == projectile_type: | ||||
|                 proj[1] += 1  # Increase quantity | ||||
|                 speak(f"Now have {proj[1]} {projectile_type}s") | ||||
|                 return | ||||
|     def get_jack_o_lanterns(self): | ||||
|         """Get number of jack o'lanterns""" | ||||
|         return self._jack_o_lantern_count | ||||
|          | ||||
|         # If not found, add new type with quantity 1 | ||||
|         self.projectiles.append([projectile_type, 1]) | ||||
|     def add_jack_o_lantern(self): | ||||
|         """Add a jack o'lantern""" | ||||
|         self._jack_o_lantern_count += 1 | ||||
|          | ||||
|     def throw_projectile(self): | ||||
|         """Throw the first available projectile""" | ||||
|         if not self.projectiles: | ||||
|             speak("No projectiles to throw!") | ||||
|         """Throw a jack o'lantern if we have any""" | ||||
|         if self.get_jack_o_lanterns() <= 0: | ||||
|             return None | ||||
|          | ||||
|         # Get the first projectile type | ||||
|         projectile = self.projectiles[0] | ||||
|         projectile[1] -= 1  # Decrease quantity | ||||
|          | ||||
|         if projectile[1] <= 0: | ||||
|             self.projectiles.pop(0)  # Remove if none left | ||||
|              | ||||
|         self._jack_o_lantern_count -= 1 | ||||
|         return { | ||||
|             'type': projectile[0], | ||||
|             'type': 'jack_o_lantern', | ||||
|             'start_x': self.xPos, | ||||
|             'direction': 1 if self.facingRight else -1 | ||||
|         } | ||||
|   | ||||
| @@ -46,8 +46,8 @@ class PowerUp(Object): | ||||
|             player.start_invincibility() | ||||
|             speak("Hand of Glory makes you invincible!") | ||||
|         elif self.item_type == 'jack_o_lantern': | ||||
|             player.add_projectile('jack_o_lantern') | ||||
|             speak("Gained a Jack-o'-lantern projectile!") | ||||
|             player.add_jack_o_lantern() | ||||
|             speak("Gained a Jack-o'-lantern!") | ||||
|          | ||||
|         # Stop movement sound when collected | ||||
|         if self.channel: | ||||
|   | ||||
							
								
								
									
										112
									
								
								src/skull_storm.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/skull_storm.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,112 @@ | ||||
| from libstormgames import * | ||||
| from src.object import Object | ||||
|  | ||||
| class SkullStorm(Object): | ||||
|     """Handles falling skulls within a specified range.""" | ||||
|      | ||||
|     def __init__(self, xRange, y, sounds, damage, maxSkulls=3, minFreq=2, maxFreq=5): | ||||
|         super().__init__( | ||||
|             xRange, | ||||
|             y, | ||||
|             "",  # No ambient sound for the skull storm | ||||
|             isStatic=True, | ||||
|             isCollectible=False, | ||||
|             isHazard=False | ||||
|         ) | ||||
|         self.sounds = sounds | ||||
|         self.damage = damage | ||||
|         self.maxSkulls = maxSkulls | ||||
|         self.minFreq = minFreq * 1000  # Convert to milliseconds | ||||
|         self.maxFreq = maxFreq * 1000 | ||||
|          | ||||
|         self.activeSkulls = []  # List of currently falling skulls | ||||
|         self.lastSkullTime = 0 | ||||
|         self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq) | ||||
|         self.playerInRange = False | ||||
|  | ||||
|     def update(self, currentTime, player): | ||||
|         """Update all active skulls and potentially spawn new ones.""" | ||||
|         if not self.isActive: | ||||
|             return | ||||
|          | ||||
|         # Check if player has entered range | ||||
|         inRange = self.xRange[0] <= player.xPos <= self.xRange[1] | ||||
|         if inRange and not self.playerInRange: | ||||
|             # Player just entered range - play the warning sound | ||||
|             self.sounds['skull_storm'].play() | ||||
|             self.playerInRange = True | ||||
|         elif not inRange and self.playerInRange:  # Only speak when actually leaving range | ||||
|             # Player just left range | ||||
|             self.playerInRange = False | ||||
|             speak("Skull storm ended.") | ||||
|              | ||||
|         if not inRange: | ||||
|             return | ||||
|              | ||||
|         # Update existing skulls | ||||
|         for skull in self.activeSkulls[:]:  # Copy list to allow removal | ||||
|             if currentTime >= skull['land_time']: | ||||
|                 # Skull has landed | ||||
|                 self.handle_landing(skull, player) | ||||
|                 self.activeSkulls.remove(skull) | ||||
|             else: | ||||
|                 # Update falling sound | ||||
|                 timeElapsed = currentTime - skull['start_time'] | ||||
|                 fallProgress = timeElapsed / skull['fall_duration'] | ||||
|                 currentY = self.yPos * (1 - fallProgress) | ||||
|                  | ||||
|                 if skull['channel'] is None or not skull['channel'].get_busy(): | ||||
|                     skull['channel'] = play_random_falling( | ||||
|                         self.sounds, | ||||
|                         'falling_skull', | ||||
|                         player.xPos, | ||||
|                         skull['x'], | ||||
|                         self.yPos, | ||||
|                         currentY | ||||
|                     ) | ||||
|          | ||||
|         # Check if we should spawn a new skull | ||||
|         if (len(self.activeSkulls) < self.maxSkulls and  | ||||
|             currentTime - self.lastSkullTime >= self.nextSkullDelay): | ||||
|             self.spawn_skull(currentTime) | ||||
|              | ||||
|     def spawn_skull(self, currentTime): | ||||
|         """Spawn a new falling skull at a random position within range.""" | ||||
|         # Reset timing | ||||
|         self.lastSkullTime = currentTime | ||||
|         self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq) | ||||
|          | ||||
|         # Calculate fall duration based on height (higher = longer fall) | ||||
|         fallDuration = self.yPos * 100  # 100ms per unit of height | ||||
|          | ||||
|         # Create new skull | ||||
|         skull = { | ||||
|             'x': random.uniform(self.xRange[0], self.xRange[1]), | ||||
|             'start_time': currentTime, | ||||
|             'fall_duration': fallDuration, | ||||
|             'land_time': currentTime + fallDuration, | ||||
|             'channel': None | ||||
|         } | ||||
|  | ||||
|         self.activeSkulls.append(skull) | ||||
|  | ||||
|     def handle_landing(self, skull, player): | ||||
|         """Handle a skull landing.""" | ||||
|         # Stop falling sound | ||||
|         if skull['channel']: | ||||
|             obj_stop(skull['channel']) | ||||
|  | ||||
|         # Play landing sound with positional audio once | ||||
|         channel = pygame.mixer.find_channel(True)  # Find an available channel | ||||
|         if channel: | ||||
|             soundObj = self.sounds['skull_lands'] | ||||
|             channel.play(soundObj, 0)  # Play once (0 = no loops) | ||||
|             # Apply positional audio | ||||
|             volume, left, right = calculate_volume_and_pan(player.xPos, skull['x']) | ||||
|             channel.set_volume(volume * left, volume * right) | ||||
|  | ||||
|         # Check if player was hit | ||||
|         if abs(player.xPos - skull['x']) < 1:  # Within 1 tile | ||||
|             if not player.isJumping:  # Only hit if not jumping | ||||
|                 player.set_health(player.get_health() - self.damage) | ||||
|                 speak("Hit by falling skull!") | ||||
		Reference in New Issue
	
	Block a user