import pygame import random from libstormgames import * from src.enemy import Enemy from src.object import Object from src.player import Player from src.projectile import Projectile from src.coffin import CoffinObject from src.powerup import PowerUp class Level: def __init__(self, levelData, sounds): 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"]) # Load objects and enemies from level data for obj in levelData["objects"]: # Handle x position or range if "x_range" in obj: xPos = obj["x_range"] else: xPos = [obj["x"], obj["x"]] # Single position as range # Check if this is an enemy if "enemy_type" in obj: enemy = Enemy( xPos, obj["y"], obj["enemy_type"], self.sounds, health=obj.get("health", 5), damage=obj.get("damage", 1), attack_range=obj.get("attack_range", 1), movement_range=obj.get("movement_range", 5) ) self.enemies.append(enemy) else: gameObject = Object( xPos, obj["y"], obj["sound"], isStatic=obj.get("static", True), isCollectible=obj.get("collectible", False), isHazard=obj.get("hazard", False), zombie_spawn_chance=obj.get("zombie_spawn_chance", 0) ) self.objects.append(gameObject) def update_audio(self): currentTime = pygame.time.get_ticks() # Update regular objects and check for zombie spawning for obj in self.objects: if not obj.isActive: continue # Check for potential zombie spawn from graves if (obj.soundName == "grave" and obj.zombie_spawn_chance > 0 and not obj.has_spawned): distance = abs(self.player.xPos - obj.xPos) if distance < 6: # Within 6 tiles # Mark as checked before doing anything else to prevent multiple checks obj.has_spawned = True roll = random.randint(1, 100) speak(f"Near grave, chance to spawn zombie") if roll <= obj.zombie_spawn_chance: zombie = Enemy( [obj.xPos, obj.xPos], obj.yPos, "zombie", self.sounds, health=3, damage=10, attack_range=1 ) self.enemies.append(zombie) 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) # Update enemies for enemy in self.enemies: if not enemy.isActive: continue enemy.update(currentTime, self.player) if enemy.channel is None or not enemy.channel.get_busy(): enemy.channel = obj_play(self.sounds, enemy.enemyType, self.player.xPos, enemy.xPos) if enemy.channel is not None: enemy.channel = obj_update(enemy.channel, self.player.xPos, enemy.xPos) # Update bouncing items for item in self.bouncing_items[:]: # Copy list to allow removal if not item.update(currentTime): self.bouncing_items.remove(item) if not item.isActive: speak(f"{item.soundName} got away!") continue # Check for item collection if abs(item.xPos - self.player.xPos) < 1 and self.player.isJumping: self.sounds[f'get_{item.soundName}'].play() item.apply_effect(self.player) item.isActive = False self.bouncing_items.remove(item) def handle_combat(self, currentTime): """Handle combat interactions between player and enemies""" attackRange = self.player.get_attack_range(currentTime) if attackRange: # Check for enemy hits for enemy in self.enemies: if enemy.isActive and enemy.xPos >= attackRange[0] and enemy.xPos <= attackRange[1]: self.sounds[self.player.currentWeapon.hitSound].play() enemy.take_damage(self.player.currentWeapon.damage) # Check for coffin hits - only if we have any coffins for obj in self.objects: if hasattr(obj, 'is_broken'): # Check if it's a coffin without using isinstance if (not obj.is_broken and obj.xPos >= attackRange[0] and obj.xPos <= attackRange[1] and self.player.isJumping): # Must be jumping to hit floating coffins if obj.hit(self.player.xPos): self.bouncing_items.append(obj.dropped_item) speak(f"{obj.dropped_item.item_type} falls out!") def handle_collisions(self): for obj in self.objects: if not obj.isActive: continue if obj.is_in_range(self.player.xPos): if obj.isCollectible and self.player.isJumping: currentPos = round(self.player.xPos) if currentPos not in obj.collectedPositions: self.sounds[f'get_{obj.soundName}'].play() speak(f"Collected {obj.soundName}") obj.collect_at_position(currentPos) self.player.collectedItems.append(obj.soundName) elif obj.isHazard and not self.player.isJumping: self.sounds[obj.soundName].play() speak("You fell in an open grave!") self.player.set_health(0) def handle_projectiles(self, currentTime): """Update projectiles and check for collisions""" for proj in self.projectiles[:]: # Copy list to allow removal if not proj.update(): self.projectiles.remove(proj) continue # Check for enemy hits for enemy in self.enemies: if enemy.isActive and abs(proj.x - enemy.xPos) < 1: proj.hit_enemy(enemy) self.projectiles.remove(proj) break def throw_projectile(self): """Have player throw a projectile""" proj_info = self.player.throw_projectile() if proj_info: 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()