diff --git a/libstormgames b/libstormgames index 80fe2ca..68e72f5 160000 --- a/libstormgames +++ b/libstormgames @@ -1 +1 @@ -Subproject commit 80fe2caff3921ef2073e9f145e51a29cbdf80f1b +Subproject commit 68e72f5d81e24223fabf21f4591ab472d8356891 diff --git a/sounds/extra_life.ogg b/sounds/extra_life.ogg new file mode 100644 index 0000000..2a821a8 --- /dev/null +++ b/sounds/extra_life.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c4a1c06d15d8ea2c2ca638fdc97892bf3ebe5fb9e8ab757f8b8e1c93bef954a0 +size 6243 diff --git a/src/catapult.py b/src/catapult.py index 7448c46..fca0764 100644 --- a/src/catapult.py +++ b/src/catapult.py @@ -37,9 +37,7 @@ class Pumpkin: # Calculate volume and pan for splat sound based on final position volume, left, right = calculate_volume_and_pan(playerX, self.x) if volume > 0: # Only play if within audible range - channel = sounds["pumpkin_splat"].play() - if channel: - channel.set_volume(volume * left, volume * right) + obj_play(sounds, 'pumpkin_splat', playerX, self.x, loop=False) def check_collision(self, player): """Check if pumpkin hits player""" @@ -78,7 +76,7 @@ class Catapult(Object): self.lastFireTime = currentTime # Play launch sound - self.sounds['catapult_launch'].play() + play_sound(self.sounds['catapult_launch']) # Set up pending pumpkin isHigh = random.choice([True, False]) diff --git a/src/coffin.py b/src/coffin.py index 928c4ea..23cc8b9 100644 --- a/src/coffin.py +++ b/src/coffin.py @@ -21,7 +21,7 @@ class CoffinObject(Object): """Handle being hit by the player's weapon""" if not self.is_broken: self.is_broken = True - self.sounds['coffin_shatter'].play() + play_sound(self.sounds['coffin_shatter']) self.level.player.stats.update_stat('Coffins broken', 1) self.level.player.stats.update_stat('Coffins remaining', -1) diff --git a/src/level.py b/src/level.py index a78fc3f..3ab1e97 100644 --- a/src/level.py +++ b/src/level.py @@ -181,20 +181,23 @@ class Level: # Check for item collection if abs(item.xPos - self.player.xPos) < 1 and self.player.isJumping: - self.sounds[f'get_{item.soundName}'].play() + play_sound(self.sounds[f'get_{item.soundName}']) 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: + # Only get attack range if attack is active + if self.player.currentWeapon and self.player.currentWeapon.is_attack_active(currentTime): + attackRange = self.player.currentWeapon.get_attack_range(self.player.xPos, self.player.facingRight) + # Check for enemy hits for enemy in self.enemies: if enemy.isActive and enemy.xPos >= attackRange[0] and enemy.xPos <= attackRange[1]: if self.weapon_hit_channel is None or not self.weapon_hit_channel.get_busy(): self.weapon_hit_channel = self.sounds[self.player.currentWeapon.hitSound].play() + self.weapon_hit_channel = play_sound(self.sounds[self.player.currentWeapon.hitSound]) enemy.take_damage(self.player.currentWeapon.damage) else: @@ -230,7 +233,7 @@ class Level: distance = abs(self.player.xPos - obj.xPos) if distance <= 2 and not self.player.isJumping: 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 = play_sound(self.sounds['edge']) else: if self.edge_warning_channel is not None and not self.edge_warning_channel.get_busy(): self.edge_warning_channel = None @@ -239,16 +242,31 @@ class Level: 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() + play_sound(self.sounds[f'get_{obj.soundName}']) obj.collect_at_position(currentPos) self.player.collectedItems.append(obj.soundName) self.player.stats.update_stat('Items collected', 1) if obj.soundName == "coin": self.player._coins += 1 self.player.stats.update_stat('Bone dust', 1) + if self.player._coins % 5 == 0: + # Only heal if below max health + if self.player.get_health() < self.player.get_max_health(): + self.player.set_health(min( + self.player.get_health() + 1, + self.player.get_max_health() + )) + + if self.player._coins % 100 == 0: + # Extra life + speak("Extra life") + self.player._coins = 0 + self.player._lives += 1 + play_sound(self.sounds['extra_life']) + elif obj.isHazard and not self.player.isJumping: if not self.player.isInvincible: - self.sounds[obj.soundName].play() + play_sound(self.sounds[obj.soundName]) speak("You fell in an open grave!") self.player.set_health(0) return False @@ -269,7 +287,7 @@ class Level: if self.player.xPos >= obj.xPos: # Stop all current sounds and play end level sound pygame.mixer.stop() - self.sounds["end_of_level"].play() + play_sound(self.sounds['end_of_level']) return True return False @@ -289,9 +307,7 @@ class Level: # Calculate volume and pan for splat sound based on final position volume, left, right = calculate_volume_and_pan(self.player.xPos, proj.x) if volume > 0: # Only play if within audible range - channel = self.sounds["pumpkin_splat"].play() - if channel: - channel.set_volume(volume * left, volume * right) + obj_play(self.sounds, 'pumpkin_splat', self.player.xPos, proj.x, loop=False) break def throw_projectile(self): @@ -307,4 +323,4 @@ class Level: proj_info['direction'] )) # Play throw sound - self.sounds['throw_jack_o_lantern'].play() + play_sound(self.sounds['throw_jack_o_lantern']) diff --git a/src/player.py b/src/player.py index 1ec4327..1594df9 100644 --- a/src/player.py +++ b/src/player.py @@ -39,7 +39,7 @@ class Player: # Power-up states self.isInvincible = False self.invincibilityStartTime = 0 - self.invincibilityDuration = 5000 # 5 seconds of invincibility + self.invincibilityDuration = 10000 # 10 seconds of invincibility # Initialize starting weapon (rusty shovel) self.add_weapon(Weapon( diff --git a/src/powerup.py b/src/powerup.py index d9bfe2f..71336c6 100644 --- a/src/powerup.py +++ b/src/powerup.py @@ -44,10 +44,8 @@ class PowerUp(Object): """Apply the item's effect when collected""" if self.item_type == 'hand_of_glory': player.start_invincibility() - speak("Hand of Glory makes you invincible!") elif self.item_type == 'jack_o_lantern': player.add_jack_o_lantern() - speak("Gained a Jack-o'-lantern!") # Stop movement sound when collected if self.channel: diff --git a/src/skull_storm.py b/src/skull_storm.py index f133553..9f71272 100644 --- a/src/skull_storm.py +++ b/src/skull_storm.py @@ -33,7 +33,7 @@ class SkullStorm(Object): inRange = self.xRange[0] <= player.xPos <= self.xRange[1] if inRange and not self.playerInRange: # Player just entered range - play the warning sound - self.sounds['skull_storm'].play() + 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 @@ -62,7 +62,7 @@ class SkullStorm(Object): skull['x'], self.yPos, currentY, - existingChannel=skull['channel'] + existing_channel=skull['channel'] ) # Check if we should spawn a new skull @@ -100,10 +100,7 @@ class SkullStorm(Object): channel = pygame.mixer.find_channel(True) # Find an available channel if channel: soundObj = self.sounds['skull_lands'] - channel.play(soundObj, 0) # Play once (0 = no loops) - # Apply positional audio - volume, left, right = calculate_volume_and_pan(player.xPos, skull['x']) - channel.set_volume(volume * left, volume * right) + 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 diff --git a/wicked_quest.py b/wicked_quest.py index 2bcdf84..e9c59e5 100644 --- a/wicked_quest.py +++ b/wicked_quest.py @@ -67,7 +67,7 @@ class WickedQuest: # Status queries if keys[pygame.K_c]: - speak(f"{player.get_coins()} coins") + speak(f"{player.get_coins()} gbone dust") if keys[pygame.K_h]: speak(f"{player.get_health()} HP") if keys[pygame.K_l]: @@ -82,25 +82,25 @@ class WickedQuest: # Handle attack with either CTRL key if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime): - self.sounds[player.currentWeapon.attackSound].play() + play_sound(self.sounds[player.currentWeapon.attackSound]) # Play footstep sounds if moving and not jumping if movementDistance > 0 and not player.isJumping: player.distanceSinceLastStep += movementDistance if player.distanceSinceLastStep >= player.stepDistance: - self.sounds['footstep'].play() + play_sound(self.sounds['footstep']) player.distanceSinceLastStep = 0 # Handle jumping if keys[pygame.K_w] and not player.isJumping: player.isJumping = True player.jumpStartTime = currentTime - self.sounds['jump'].play() + play_sound(self.sounds['jump']) # Check if jump should end if player.isJumping and currentTime - player.jumpStartTime >= player.jumpDuration: player.isJumping = False - self.sounds['footstep'].play() + play_sound(self.sounds['footstep']) # Reset step distance tracking after landing player.distanceSinceLastStep = 0 @@ -149,9 +149,24 @@ class WickedQuest: while True: currentTime = pygame.time.get_ticks() - if check_for_exit(): - return - + # Game volume controls + for event in pygame.event.get(): + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + return + if event.key == pygame.K_PAGEUP: + adjust_master_volume(0.1) + elif event.key == pygame.K_PAGEDOWN: + adjust_master_volume(-0.1) + elif event.key == pygame.K_HOME: + adjust_bgm_volume(0.1) + elif event.key == pygame.K_END: + adjust_bgm_volume(-0.1) + elif event.key == pygame.K_INSERT: + adjust_sfx_volume(0.1) + elif event.key == pygame.K_DELETE: + adjust_sfx_volume(-0.1) + # Update game state self.currentLevel.player.update(currentTime) self.handle_input()