# -*- coding: utf-8 -*- import pygame import random 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, "", isStatic=True, isCollectible=False, isHazard=False # No ambient sound for the skull storm ) 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 # Default TTS message (can be overridden) self.endMessage = "Skull storm ended." 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 play_sound(self.sounds["skull_storm"]) self.playerInRange = True elif not inRange and self.playerInRange: # Only speak when actually leaving range # Player just left range self.playerInRange = False play_sound(self.sounds["skull_storm_ends"]) speak(self.endMessage) # Clear any active skulls when player leaves the range for skull in self.activeSkulls[:]: if skull["channel"]: obj_stop(skull["channel"]) self.activeSkulls = [] # Reset the list of active skulls 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) skull["channel"] = play_random_falling( self.sounds, "falling_skull", player.xPos, skull["x"], self.yPos, currentY, existingChannel=skull["channel"], ) # 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"] obj_play(self.sounds, "skull_lands", player.xPos, skull["x"], loop=False) # Check if player was hit if abs(player.xPos - skull["x"]) < 1: # Within 1 tile if not player.isInvincible: player.set_health(player.get_health() - self.damage) self.sounds["player_takes_damage"].play() speak("Hit by falling skull!")