From 0a7052094d510e49d64b9ad89073d6185f83736d Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sat, 1 Feb 2025 19:50:13 -0500 Subject: [PATCH] Got end of level working. Added floating coffins, shatter them and collect what comes out. --- levels/1.json | 71 +++++++++++++++-------------------------- libstormgames | 2 +- sounds/end_of_level.ogg | 3 ++ sounds/lose_a_life.ogg | 3 ++ src/coffin.py | 12 +++++-- src/level.py | 71 +++++++++++++++++++++++++++++++++-------- src/player.py | 21 +++++++++--- src/powerup.py | 15 +++++---- 8 files changed, 124 insertions(+), 74 deletions(-) create mode 100644 sounds/end_of_level.ogg create mode 100644 sounds/lose_a_life.ogg diff --git a/levels/1.json b/levels/1.json index 0acd193..9354900 100644 --- a/levels/1.json +++ b/levels/1.json @@ -1,7 +1,7 @@ { "level_id": 1, "name": "The Mausoleum", - "description": "After years of existing as a pile of bones, someone was crazy enough to assemble your skeleton. Time to wreak some havoc!", + "description": "After years of existing as a pile of bones, someone was crazy enough to assemble your skeleton. Time to wreak some havoc! Use W to jump, A/D to move, and CTRL to attack with your shovel.", "player_start": { "x": 0, "y": 0 @@ -14,6 +14,12 @@ "collectible": true, "static": true }, + { + "x": 10, + "y": 3, + "sound": "coffin", + "type": "coffin" + }, { "x_range": [15, 17], "y": 3, @@ -25,20 +31,19 @@ "x": 25, "y": 0, "enemy_type": "goblin", - "health": 5, - "damage": 2, + "health": 3, + "damage": 1, "attack_range": 1, "movement_range": 5 }, { - "x": 30, + "x": 35, "y": 3, - "sound": "coin", - "collectible": true, - "static": true + "sound": "coffin", + "type": "coffin" }, { - "x": 40, + "x": 45, "y": 0, "hazard": true, "sound": "grave", @@ -46,21 +51,20 @@ "zombie_spawn_chance": 10 }, { - "x_range": [45, 47], + "x_range": [55, 57], "y": 3, "sound": "coin", "collectible": true, "static": true }, { - "x": 55, + "x": 65, "y": 3, - "sound": "coin", - "collectible": true, - "static": true + "sound": "coffin", + "type": "coffin" }, { - "x": 60, + "x": 75, "y": 0, "enemy_type": "goblin", "health": 5, @@ -69,7 +73,7 @@ "movement_range": 5 }, { - "x": 70, + "x": 85, "y": 0, "hazard": true, "sound": "grave", @@ -77,22 +81,7 @@ "zombie_spawn_chance": 15 }, { - "x_range": [80, 82], - "y": 3, - "sound": "coin", - "collectible": true, - "static": true - }, - { - "x": 90, - "y": 0, - "hazard": true, - "sound": "grave", - "static": true, - "zombie_spawn_chance": 20 - }, - { - "x": 100, + "x_range": [95, 97], "y": 3, "sound": "coin", "collectible": true, @@ -100,27 +89,17 @@ }, { "x": 110, - "y": 0, - "hazard": true, - "sound": "grave", - "static": true, - "zombie_spawn_chance": 25 + "y": 3, + "sound": "coffin", + "type": "coffin" }, { - "x_range": [120, 123], + "x_range": [120, 122], "y": 3, "sound": "coin", "collectible": true, "static": true }, - { - "x": 130, - "y": 0, - "hazard": true, - "sound": "grave", - "static": true, - "zombie_spawn_chance": 30 - }, { "x": 145, "y": 0, @@ -132,6 +111,6 @@ ], "boundaries": { "left": 0, - "right": 150 + "right": 160 } } diff --git a/libstormgames b/libstormgames index b5b472e..d5c79c0 160000 --- a/libstormgames +++ b/libstormgames @@ -1 +1 @@ -Subproject commit b5b472eebe6c3da28499bc468ff608a5ac46e159 +Subproject commit d5c79c0770867c21418d10d3dff7c60b595b6547 diff --git a/sounds/end_of_level.ogg b/sounds/end_of_level.ogg new file mode 100644 index 0000000..276cf3d --- /dev/null +++ b/sounds/end_of_level.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5dd5796ef5e620f18e64883f8990f43be74c08c16534e2a6e6ac6c9f0b4c05e +size 13224 diff --git a/sounds/lose_a_life.ogg b/sounds/lose_a_life.ogg new file mode 100644 index 0000000..0f6e8c7 --- /dev/null +++ b/sounds/lose_a_life.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0047dc3de0de84e7bd85b10aac711dd9ae736796e307f67085dcaf738a2adb17 +size 16395 diff --git a/src/coffin.py b/src/coffin.py index 452327e..4aeffdd 100644 --- a/src/coffin.py +++ b/src/coffin.py @@ -20,7 +20,15 @@ class CoffinObject(Object): if not self.is_broken: self.is_broken = True self.sounds['coffin_shatter'].play() - + + # Stop the ongoing coffin sound + if self.channel: + obj_stop(self.channel) + self.channel = None + + # Mark coffin as inactive since it's broken + self.isActive = False + # Randomly choose item type item_type = random.choice(['hand_of_glory', 'jack_o_lantern']) @@ -28,7 +36,7 @@ class CoffinObject(Object): direction = random.choice([-1, 1]) drop_distance = random.randint(1, 2) drop_x = self.xPos + (direction * drop_distance) - + self.dropped_item = PowerUp( drop_x, self.yPos, diff --git a/src/level.py b/src/level.py index 9a795d9..ba94c43 100644 --- a/src/level.py +++ b/src/level.py @@ -7,7 +7,6 @@ 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: @@ -17,10 +16,24 @@ class Level: self.enemies = [] self.bouncing_items = [] self.projectiles = [] # Track active projectiles - self.player = Player(levelData["player_start"]["x"], levelData["player_start"]["y"]) + self.player = Player(levelData["player_start"]["x"], levelData["player_start"]["y"], sounds) self.edge_warning_channel = None self.weapon_hit_channel = None - + self.leftBoundary = levelData["boundaries"]["left"] + self.rightBoundary = levelData["boundaries"]["right"] + self.levelId = levelData["level_id"] + + # Create end of level object at right boundary + endLevel = Object( + self.rightBoundary, + 0, # Same y-level as player start + "end_of_level", + isStatic=True, + isCollectible=False, + isHazard=False + ) + self.objects.append(endLevel) + # Load objects and enemies from level data for obj in levelData["objects"]: # Handle x position or range @@ -28,7 +41,7 @@ class Level: xPos = obj["x_range"] else: xPos = [obj["x"], obj["x"]] # Single position as range - + # Check if this is a catapult if obj.get("type") == "catapult": catapult = Catapult( @@ -40,6 +53,14 @@ class Level: firingRange=obj.get("range", 20) ) self.objects.append(catapult) + # Check if this is a coffin + elif obj.get("type") == "coffin": + coffin = CoffinObject( + xPos[0], + obj["y"], + self.sounds + ) + self.objects.append(coffin) # Check if this is an enemy elif "enemy_type" in obj: enemy = Enemy( @@ -66,6 +87,7 @@ class Level: self.objects.append(gameObject) def update_audio(self): + """Update all audio and entity state.""" currentTime = pygame.time.get_ticks() # Update regular objects and check for zombie spawning @@ -156,7 +178,7 @@ class Level: if self.weapon_hit_channel is not None and not self.weapon_hit_channel.get_busy(): self.weapon_hit_channel = None - # Check for coffin hits - only if we have any coffins + # Check for coffin hits 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 @@ -164,25 +186,27 @@ class Level: 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!") + 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): + """Handle all collision checks and return True if level is complete.""" + # First check if player is dead + if self.player.get_health() <= 0: + return False + + # Process object collisions for hazards and collectibles for obj in self.objects: if not obj.isActive: continue - + # Handle grave edge warnings if obj.isHazard: distance = abs(self.player.xPos - obj.xPos) - # Check if within 1 tile and moving - if distance <= 2 and not self.player.isJumping: - # Only play edge warning if not already playing if self.edge_warning_channel is None or not self.edge_warning_channel.get_busy(): self.edge_warning_channel = self.sounds['edge'].play() - else: if self.edge_warning_channel is not None and not self.edge_warning_channel.get_busy(): self.edge_warning_channel = None @@ -200,7 +224,26 @@ class Level: self.sounds[obj.soundName].play() speak("You fell in an open grave!") self.player.set_health(0) - + return False + + # Handle boundaries + if self.player.xPos < self.leftBoundary: + self.player.xPos = self.leftBoundary + speak("Start of level!") + + # Check for level completion - takes precedence over everything except death + if self.player.get_health() > 0: + for obj in self.objects: + if obj.soundName == "end_of_level": + # Check if player has reached or passed the end marker + if self.player.xPos >= obj.xPos: + # Stop all current sounds and play end level sound + pygame.mixer.stop() + self.sounds["end_of_level"].play() + return True + + return False + def handle_projectiles(self, currentTime): """Update projectiles and check for collisions""" for proj in self.projectiles[:]: # Copy list to allow removal diff --git a/src/player.py b/src/player.py index 04374a0..32ce21b 100644 --- a/src/player.py +++ b/src/player.py @@ -1,6 +1,11 @@ +import pygame +from libstormgames import * from src.weapon import Weapon + + class Player: - def __init__(self, xPos, yPos): + def __init__(self, xPos, yPos, sounds): + self.sounds = sounds # Movement attributes self.xPos = xPos self.yPos = yPos @@ -98,15 +103,21 @@ class Player: return self._maxHealth def set_health(self, value): - """Set health and handle death if needed""" + """Set health and handle death if needed.""" if self.isInvincible: return # No damage while invincible - + + old_health = self._health self._health = max(0, value) # Health can't go below 0 - if self._health == 0: + + if self._health == 0 and old_health > 0: self._lives -= 1 + # Stop all current sounds before playing death sound + pygame.mixer.stop() + cut_scene(self.sounds, 'lose_a_life') if self._lives > 0: - self._health = 10 # Reset health if we still have lives + self._health = self._maxHealth # Reset health if we still have lives + speak(f"{self._lives} lives remaining") def set_max_health(self, value): """Set max health""" diff --git a/src/powerup.py b/src/powerup.py index f9cf272..04d392b 100644 --- a/src/powerup.py +++ b/src/powerup.py @@ -14,19 +14,22 @@ class PowerUp(Object): self.speed = 0.05 # Base movement speed self.item_type = item_type self.channel = None + self._currentX = x # Initialize the current x position def update(self, current_time): """Update item position""" if not self.isActive: return False - + # Update position self._currentX += self.direction * self.speed - - # Keep bounce sound playing while moving + + # Update positional audio if self.channel is None or not self.channel.get_busy(): - self.channel = self.sounds['item_bounce'].play(-1) - + self.channel = obj_play(self.sounds, "item_bounce", self.xPos, self._currentX) + else: + self.channel = obj_update(self.channel, self.xPos, self._currentX) + # Check if item has gone too far (20 tiles) if abs(self._currentX - self.xRange[0]) > 20: self.isActive = False @@ -34,7 +37,7 @@ class PowerUp(Object): self.channel.stop() self.channel = None return False - + return True def apply_effect(self, player):