Got end of level working. Added floating coffins, shatter them and collect what comes out.

This commit is contained in:
Storm Dragon
2025-02-01 19:50:13 -05:00
parent 6fda41f57b
commit 0a7052094d
8 changed files with 124 additions and 74 deletions

View File

@@ -1,7 +1,7 @@
{ {
"level_id": 1, "level_id": 1,
"name": "The Mausoleum", "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": { "player_start": {
"x": 0, "x": 0,
"y": 0 "y": 0
@@ -14,6 +14,12 @@
"collectible": true, "collectible": true,
"static": true "static": true
}, },
{
"x": 10,
"y": 3,
"sound": "coffin",
"type": "coffin"
},
{ {
"x_range": [15, 17], "x_range": [15, 17],
"y": 3, "y": 3,
@@ -25,20 +31,19 @@
"x": 25, "x": 25,
"y": 0, "y": 0,
"enemy_type": "goblin", "enemy_type": "goblin",
"health": 5, "health": 3,
"damage": 2, "damage": 1,
"attack_range": 1, "attack_range": 1,
"movement_range": 5 "movement_range": 5
}, },
{ {
"x": 30, "x": 35,
"y": 3, "y": 3,
"sound": "coin", "sound": "coffin",
"collectible": true, "type": "coffin"
"static": true
}, },
{ {
"x": 40, "x": 45,
"y": 0, "y": 0,
"hazard": true, "hazard": true,
"sound": "grave", "sound": "grave",
@@ -46,21 +51,20 @@
"zombie_spawn_chance": 10 "zombie_spawn_chance": 10
}, },
{ {
"x_range": [45, 47], "x_range": [55, 57],
"y": 3, "y": 3,
"sound": "coin", "sound": "coin",
"collectible": true, "collectible": true,
"static": true "static": true
}, },
{ {
"x": 55, "x": 65,
"y": 3, "y": 3,
"sound": "coin", "sound": "coffin",
"collectible": true, "type": "coffin"
"static": true
}, },
{ {
"x": 60, "x": 75,
"y": 0, "y": 0,
"enemy_type": "goblin", "enemy_type": "goblin",
"health": 5, "health": 5,
@@ -69,7 +73,7 @@
"movement_range": 5 "movement_range": 5
}, },
{ {
"x": 70, "x": 85,
"y": 0, "y": 0,
"hazard": true, "hazard": true,
"sound": "grave", "sound": "grave",
@@ -77,22 +81,7 @@
"zombie_spawn_chance": 15 "zombie_spawn_chance": 15
}, },
{ {
"x_range": [80, 82], "x_range": [95, 97],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"x": 90,
"y": 0,
"hazard": true,
"sound": "grave",
"static": true,
"zombie_spawn_chance": 20
},
{
"x": 100,
"y": 3, "y": 3,
"sound": "coin", "sound": "coin",
"collectible": true, "collectible": true,
@@ -100,27 +89,17 @@
}, },
{ {
"x": 110, "x": 110,
"y": 0, "y": 3,
"hazard": true, "sound": "coffin",
"sound": "grave", "type": "coffin"
"static": true,
"zombie_spawn_chance": 25
}, },
{ {
"x_range": [120, 123], "x_range": [120, 122],
"y": 3, "y": 3,
"sound": "coin", "sound": "coin",
"collectible": true, "collectible": true,
"static": true "static": true
}, },
{
"x": 130,
"y": 0,
"hazard": true,
"sound": "grave",
"static": true,
"zombie_spawn_chance": 30
},
{ {
"x": 145, "x": 145,
"y": 0, "y": 0,
@@ -132,6 +111,6 @@
], ],
"boundaries": { "boundaries": {
"left": 0, "left": 0,
"right": 150 "right": 160
} }
} }

BIN
sounds/end_of_level.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
sounds/lose_a_life.ogg (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -21,6 +21,14 @@ class CoffinObject(Object):
self.is_broken = True self.is_broken = True
self.sounds['coffin_shatter'].play() 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 # Randomly choose item type
item_type = random.choice(['hand_of_glory', 'jack_o_lantern']) item_type = random.choice(['hand_of_glory', 'jack_o_lantern'])

View File

@@ -7,7 +7,6 @@ from src.enemy import Enemy
from src.object import Object from src.object import Object
from src.player import Player from src.player import Player
from src.projectile import Projectile from src.projectile import Projectile
from src.coffin import CoffinObject
from src.powerup import PowerUp from src.powerup import PowerUp
class Level: class Level:
@@ -17,9 +16,23 @@ class Level:
self.enemies = [] self.enemies = []
self.bouncing_items = [] self.bouncing_items = []
self.projectiles = [] # Track active projectiles 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.edge_warning_channel = None
self.weapon_hit_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 # Load objects and enemies from level data
for obj in levelData["objects"]: for obj in levelData["objects"]:
@@ -40,6 +53,14 @@ class Level:
firingRange=obj.get("range", 20) firingRange=obj.get("range", 20)
) )
self.objects.append(catapult) 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 # Check if this is an enemy
elif "enemy_type" in obj: elif "enemy_type" in obj:
enemy = Enemy( enemy = Enemy(
@@ -66,6 +87,7 @@ class Level:
self.objects.append(gameObject) self.objects.append(gameObject)
def update_audio(self): def update_audio(self):
"""Update all audio and entity state."""
currentTime = pygame.time.get_ticks() currentTime = pygame.time.get_ticks()
# Update regular objects and check for zombie spawning # 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(): if self.weapon_hit_channel is not None and not self.weapon_hit_channel.get_busy():
self.weapon_hit_channel = None self.weapon_hit_channel = None
# Check for coffin hits - only if we have any coffins # Check for coffin hits
for obj in self.objects: for obj in self.objects:
if hasattr(obj, 'is_broken'): # Check if it's a coffin without using isinstance if hasattr(obj, 'is_broken'): # Check if it's a coffin without using isinstance
if (not obj.is_broken and if (not obj.is_broken and
@@ -164,11 +186,17 @@ class Level:
obj.xPos <= attackRange[1] and obj.xPos <= attackRange[1] and
self.player.isJumping): # Must be jumping to hit floating coffins self.player.isJumping): # Must be jumping to hit floating coffins
if obj.hit(self.player.xPos): if obj.hit(self.player.xPos):
self.bouncing_items.append(obj.dropped_item) self.bouncing_items.append(obj.dropped_item)
speak(f"{obj.dropped_item.item_type} falls out!") speak(f"{obj.dropped_item.item_type} falls out!")
def handle_collisions(self): 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: for obj in self.objects:
if not obj.isActive: if not obj.isActive:
continue continue
@@ -176,13 +204,9 @@ class Level:
# Handle grave edge warnings # Handle grave edge warnings
if obj.isHazard: if obj.isHazard:
distance = abs(self.player.xPos - obj.xPos) distance = abs(self.player.xPos - obj.xPos)
# Check if within 1 tile and moving
if distance <= 2 and not self.player.isJumping: 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(): if self.edge_warning_channel is None or not self.edge_warning_channel.get_busy():
self.edge_warning_channel = self.sounds['edge'].play() self.edge_warning_channel = self.sounds['edge'].play()
else: else:
if self.edge_warning_channel is not None and not self.edge_warning_channel.get_busy(): if self.edge_warning_channel is not None and not self.edge_warning_channel.get_busy():
self.edge_warning_channel = None self.edge_warning_channel = None
@@ -200,6 +224,25 @@ class Level:
self.sounds[obj.soundName].play() self.sounds[obj.soundName].play()
speak("You fell in an open grave!") speak("You fell in an open grave!")
self.player.set_health(0) 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): def handle_projectiles(self, currentTime):
"""Update projectiles and check for collisions""" """Update projectiles and check for collisions"""

View File

@@ -1,6 +1,11 @@
import pygame
from libstormgames import *
from src.weapon import Weapon from src.weapon import Weapon
class Player: class Player:
def __init__(self, xPos, yPos): def __init__(self, xPos, yPos, sounds):
self.sounds = sounds
# Movement attributes # Movement attributes
self.xPos = xPos self.xPos = xPos
self.yPos = yPos self.yPos = yPos
@@ -98,15 +103,21 @@ class Player:
return self._maxHealth return self._maxHealth
def set_health(self, value): def set_health(self, value):
"""Set health and handle death if needed""" """Set health and handle death if needed."""
if self.isInvincible: if self.isInvincible:
return # No damage while invincible return # No damage while invincible
old_health = self._health
self._health = max(0, value) # Health can't go below 0 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 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: 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): def set_max_health(self, value):
"""Set max health""" """Set max health"""

View File

@@ -14,6 +14,7 @@ class PowerUp(Object):
self.speed = 0.05 # Base movement speed self.speed = 0.05 # Base movement speed
self.item_type = item_type self.item_type = item_type
self.channel = None self.channel = None
self._currentX = x # Initialize the current x position
def update(self, current_time): def update(self, current_time):
"""Update item position""" """Update item position"""
@@ -23,9 +24,11 @@ class PowerUp(Object):
# Update position # Update position
self._currentX += self.direction * self.speed 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(): 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) # Check if item has gone too far (20 tiles)
if abs(self._currentX - self.xRange[0]) > 20: if abs(self._currentX - self.xRange[0]) > 20: