120 lines
4.5 KiB
Python
120 lines
4.5 KiB
Python
# -*- 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!")
|