diff --git a/levels/1.json b/levels/1.json index 4838f3e..317c676 100644 --- a/levels/1.json +++ b/levels/1.json @@ -8,7 +8,14 @@ }, "objects": [ { - "x": 5, + "x": 2, + "y": 3, + "item": "guts", + "sound": "coffin", + "type": "coffin" + }, + { + "x_range": [5, 8], "y": 3, "sound": "coin", "collectible": true, @@ -85,7 +92,7 @@ "zombie_spawn_chance": 15 }, { - "x_range": [95, 97], + "x_range": [95, 100], "y": 3, "sound": "coin", "collectible": true, @@ -98,7 +105,7 @@ "type": "coffin" }, { - "x_range": [120, 122], + "x_range": [120, 123], "y": 3, "sound": "coin", "collectible": true, @@ -122,7 +129,7 @@ ], "boundaries": { "left": 0, - "right": 160 + "right": 165 }, "footstep_sound": "footstep_stone" } diff --git a/levels/2.json b/levels/2.json index 11db37c..46cc81b 100644 --- a/levels/2.json +++ b/levels/2.json @@ -14,7 +14,7 @@ "type": "coffin" }, { - "x_range": [15, 17], + "x_range": [15, 18], "y": 3, "sound": "coin", "collectible": true, @@ -39,6 +39,13 @@ "static": true, "zombie_spawn_chance": 15 }, + { + "x_range": [33, 38], + "y": 3, + "sound": "coin", + "collectible": true, + "static": true + }, { "x_range": [45, 60], "y": 12, @@ -57,7 +64,7 @@ "type": "coffin" }, { - "x_range": [65, 67], + "x_range": [65, 68], "y": 3, "sound": "coin", "collectible": true, @@ -89,7 +96,7 @@ "zombie_spawn_chance": 20 }, { - "x_range": [105, 107], + "x_range": [105, 108], "y": 3, "sound": "coin", "collectible": true, @@ -104,7 +111,7 @@ "range": 15 }, { - "x_range": [130, 160], + "x_range": [130, 165], "y": 15, "type": "skull_storm", "damage": 4, diff --git a/levels/3.json b/levels/3.json index 97b43f0..68d32e8 100644 --- a/levels/3.json +++ b/levels/3.json @@ -36,13 +36,7 @@ "zombie_spawn_chance": 25 }, { - "x": 15, - "y": 3, - "sound": "coffin", - "type": "coffin" - }, - { - "x_range": [25, 27], + "x_range": [25, 28], "y": 3, "sound": "coin", "collectible": true, @@ -71,7 +65,7 @@ } }, { - "x_range": [42, 44], + "x_range": [40, 44], "y": 3, "sound": "coin", "collectible": true, @@ -97,7 +91,7 @@ } }, { - "x_range": [75, 77], + "x_range": [75, 78], "y": 3, "sound": "coin", "collectible": true, @@ -140,7 +134,7 @@ } }, { - "x_range": [105, 107], + "x_range": [105, 115], "y": 3, "sound": "coin", "collectible": true, @@ -185,12 +179,19 @@ } }, { - "x_range": [155, 157], + "x_range": [155, 158], "y": 3, "sound": "coin", "collectible": true, "static": true }, + { + "x": 145, + "y": 3, + "item": "hand_of_glory", + "sound": "coffin", + "type": "coffin" + }, { "x_range": [146, 166], "y": 0, @@ -228,6 +229,20 @@ "sound": "coin", "collectible": true, "static": true + }, + { + "x": 185, + "y": 3, + "item": "extra_life", + "sound": "coffin", + "type": "coffin" + }, + { + "x_range": [190, 195], + "y": 3, + "sound": "coin", + "collectible": true, + "static": true } ], "boundaries": { diff --git a/sounds/extra_life.ogg b/sounds/extra_life.ogg deleted file mode 100644 index 2a821a8..0000000 --- a/sounds/extra_life.ogg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c4a1c06d15d8ea2c2ca638fdc97892bf3ebe5fb9e8ab757f8b8e1c93bef954a0 -size 6243 diff --git a/sounds/life_up.ogg b/sounds/get_extra_life.ogg similarity index 100% rename from sounds/life_up.ogg rename to sounds/get_extra_life.ogg diff --git a/sounds/get_guts.ogg b/sounds/get_guts.ogg new file mode 100644 index 0000000..dd845a8 --- /dev/null +++ b/sounds/get_guts.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a61aa20920a924efefada3ad8b23e84720aca90ef0be42f2fc44c0d0534e0184 +size 43261 diff --git a/src/coffin.py b/src/coffin.py index d99cbbb..c6a53ed 100644 --- a/src/coffin.py +++ b/src/coffin.py @@ -1,11 +1,12 @@ +import random from libstormgames import * +from src.item_types import ItemProperties from src.object import Object from src.powerup import PowerUp -import random class CoffinObject(Object): - def __init__(self, x, y, sounds, level): + def __init__(self, x, y, sounds, level, item="random"): super().__init__( x, y, "coffin", isStatic=True, @@ -13,9 +14,10 @@ class CoffinObject(Object): isHazard=False ) self.sounds = sounds - self.level = level # Store level reference + self.level = level self.is_broken = False self.dropped_item = None + self.specified_item = item def hit(self, player_pos): """Handle being hit by the player's weapon""" @@ -23,23 +25,31 @@ class CoffinObject(Object): self.is_broken = True play_sound(self.sounds['coffin_shatter']) self.level.player.stats.update_stat('Coffins broken', 1) - + # 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']) + + # Determine item to drop + if self.specified_item == "random": + item_type = ItemProperties.get_random_item() + else: + # Validate specified item + if ItemProperties.is_valid_item(self.specified_item): + item_type = self.specified_item + else: + # Fall back to random if invalid item specified + item_type = ItemProperties.get_random_item() # Create item 1-2 tiles away in random direction 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/item.py b/src/item.py deleted file mode 100644 index a01ba94..0000000 --- a/src/item.py +++ /dev/null @@ -1,89 +0,0 @@ -from libstormgames import * -from src.object import Object -import random - -class CoffinObject(Object): - def __init__(self, x, y, sounds): - super().__init__( - x, y, "coffin", - isStatic=True, - isCollectible=False, - isHazard=False - ) - self.sounds = sounds - self.is_broken = False - self.dropped_item = None - - def hit(self, player_pos): - """Handle being hit by the player's weapon""" - if not self.is_broken: - self.is_broken = True - self.sounds['coffin_shatter'].play() - - # Randomly choose item type - item_type = random.choice(['hand_of_glory', 'jack_o_lantern']) - - # Create item 1-2 tiles away in random direction - direction = random.choice([-1, 1]) - drop_distance = random.randint(1, 2) - drop_x = self.xPos + (direction * drop_distance) - - self.dropped_item = Item( - drop_x, - self.yPos, - item_type, - self.sounds, - direction - ) - return True - return False - -class Item(Object): - def __init__(self, x, y, item_type, sounds, direction): - super().__init__( - x, y, item_type, - isStatic=False, - isCollectible=True, - isHazard=False - ) - self.sounds = sounds - self.direction = direction - self.speed = 0.05 # Base movement speed - self.item_type = item_type - self.channel = None - - 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 - if self.channel is None or not self.channel.get_busy(): - self.channel = self.sounds['item_bounce'].play(-1) - - # Check if item has gone too far (20 tiles) - if abs(self._currentX - self.xRange[0]) > 20: - self.isActive = False - if self.channel: - self.channel.stop() - self.channel = None - return False - - return True - - def apply_effect(self, player): - """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_projectile('jack_o_lantern') - speak("Gained a Jack-o'-lantern projectile!") - - # Stop movement sound when collected - if self.channel: - self.channel.stop() - self.channel = None diff --git a/src/item_types.py b/src/item_types.py new file mode 100644 index 0000000..d317966 --- /dev/null +++ b/src/item_types.py @@ -0,0 +1,51 @@ +import random +from enum import Enum, auto + + +class ItemType(Enum): + """Defines available item types and their properties""" + GUTS = auto() + HAND_OF_GLORY = auto() + JACK_O_LANTERN = auto() + EXTRA_LIFE = auto() + +class ItemProperties: + """Manages item properties and availability""" + + # Items that can appear in random drops + RANDOM_ELIGIBLE = { + ItemType.HAND_OF_GLORY: "hand_of_glory", + ItemType.JACK_O_LANTERN: "jack_o_lantern" + } + + # All possible items (including special ones) + ALL_ITEMS = { + ItemType.GUTS: "guts", + ItemType.HAND_OF_GLORY: "hand_of_glory", + ItemType.JACK_O_LANTERN: "jack_o_lantern", + ItemType.EXTRA_LIFE: "extra_life" + } + + @staticmethod + def get_sound_name(item_type): + """Convert enum to sound/asset name""" + return ItemProperties.ALL_ITEMS.get(item_type) + + @staticmethod + def get_random_item(): + """Get a random item from eligible items""" + item_type = random.choice(list(ItemProperties.RANDOM_ELIGIBLE.keys())) + return ItemProperties.get_sound_name(item_type) + + @staticmethod + def is_valid_item(item_name): + """Check if an item name is valid""" + return item_name in [v for v in ItemProperties.ALL_ITEMS.values()] + + @staticmethod + def get_item_type(item_name): + """Get ItemType enum from string name""" + for item_type, name in ItemProperties.ALL_ITEMS.items(): + if name == item_name: + return item_type + return None diff --git a/src/level.py b/src/level.py index 101182b..776a668 100644 --- a/src/level.py +++ b/src/level.py @@ -79,7 +79,8 @@ class Level: xPos[0], obj["y"], self.sounds, - self # Pass level reference + self, # Pass level reference + item=obj.get("item", "random") # Get item type or default to random ) self.objects.append(coffin) # Check if this is an enemy @@ -221,7 +222,7 @@ class Level: if obj.hit(self.player.xPos): self.bouncing_items.append(obj.dropped_item) - speak(f"{obj.dropped_item.item_type} falls out!") + #speak(f"{obj.dropped_item.soundName} falls out!") def handle_collisions(self): """Handle all collision checks and return True if level is complete.""" @@ -265,7 +266,6 @@ class Level: 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']) @@ -273,7 +273,7 @@ class Level: elif obj.isHazard and not self.player.isJumping: if not self.player.isInvincible: play_sound(self.sounds[obj.soundName]) - speak("You fell in an open grave!") + speak("You fell in an open grave! Now, it's yours!") self.player.set_health(0) return False else: diff --git a/src/player.py b/src/player.py index ab7d9a2..4f5c21f 100644 --- a/src/player.py +++ b/src/player.py @@ -14,6 +14,8 @@ class Player: self.jumpDuration = 1000 # Jump duration in milliseconds self.jumpStartTime = 0 self.isJumping = False + self.isRunning = False + self.runMultiplier = 1.5 # Same multiplier as jumping self.facingRight = True # Stats and tracking @@ -26,11 +28,14 @@ class Player: self.sounds = sounds # Footstep tracking + self.baseStepDistance = 0.8 + self.baseStepInterval = 250 + self.stepDistance = self.baseStepDistance + self.minStepInterval = self.baseStepInterval self.distanceSinceLastStep = 0 - self.stepDistance = 0.8 self.lastStepTime = 0 - self.minStepInterval = 250 # Minimum milliseconds between steps - self.footstepSound = "footstep" + self.isRunning = False + self.runMultiplier = 1.5 # Inventory system self.inventory = [] @@ -61,8 +66,8 @@ class Player: def should_play_footstep(self, currentTime): """Check if it's time to play a footstep sound""" - return (self.distanceSinceLastStep >= self.stepDistance and - currentTime - self.lastStepTime >= self.minStepInterval) + return (self.distanceSinceLastStep >= self.get_step_distance() and + currentTime - self.lastStepTime >= self.get_step_interval()) def update(self, currentTime): """Update player state""" @@ -76,6 +81,10 @@ class Player: self.isInvincible = True self.invincibilityStartTime = pygame.time.get_ticks() + def extra_life(self): + """Increment lives by 1""" + self._lives += 1 + def get_jack_o_lanterns(self): """Get number of jack o'lanterns""" return self._jack_o_lantern_count @@ -84,6 +93,10 @@ class Player: """Add a jack o'lantern""" self._jack_o_lantern_count += 1 + def add_guts(self): + """Apply guts, increase max_health by 2""" + self._maxHealth += 2 + def throw_projectile(self): """Throw a jack o'lantern if we have any""" if self.get_jack_o_lanterns() <= 0: @@ -96,6 +109,18 @@ class Player: 'direction': 1 if self.facingRight else -1 } + def get_step_distance(self): + """Get step distance based on current speed""" + if self.isRunning or self.isJumping: + return self.baseStepDistance / self.runMultiplier + return self.baseStepDistance + + def get_step_interval(self): + """Get minimum time between steps based on current speed""" + if self.isRunning or self.isJumping: + return self.baseStepInterval / self.runMultiplier + return self.baseStepInterval + def get_health(self): """Get current health""" return self._health @@ -104,6 +129,12 @@ class Player: """Get current max health""" return self._maxHealth + def get_current_speed(self): + """Calculate current speed based on state""" + baseSpeed = self.moveSpeed + if self.isJumping or self.isRunning: return baseSpeed * self.runMultiplier + return baseSpeed + def set_footstep_sound(self, soundName): """Set the current footstep sound""" self.footstepSound = soundName @@ -122,8 +153,7 @@ class Player: pygame.mixer.stop() cut_scene(self.sounds, 'lose_a_life') if self._lives > 0: - self._health = self._maxHealth # Reset health if we still have lives - speak(f"{self._lives} lives remaining") + self.reset_on_death() def set_max_health(self, value): """Set max health""" @@ -137,6 +167,13 @@ class Player: """Get remaining lives""" return self._lives + def reset_on_death(self): + """Reset player state after death""" + self._health = self._maxHealth + self.isJumping = False + self.isRunning = False + self.xPos = 0 + def add_weapon(self, weapon): """Add a new weapon to inventory and equip if first weapon""" self.weapons.append(weapon) diff --git a/src/powerup.py b/src/powerup.py index 71336c6..177db49 100644 --- a/src/powerup.py +++ b/src/powerup.py @@ -44,8 +44,12 @@ class PowerUp(Object): """Apply the item's effect when collected""" if self.item_type == 'hand_of_glory': player.start_invincibility() + elif self.item_type == 'guts': + player.add_guts() elif self.item_type == 'jack_o_lantern': player.add_jack_o_lantern() + elif self.item_type == 'extra_life': + player.extra_life() # Stop movement sound when collected if self.channel: diff --git a/wicked_quest.py b/wicked_quest.py index 34dc07b..ace5a1e 100644 --- a/wicked_quest.py +++ b/wicked_quest.py @@ -49,9 +49,12 @@ class WickedQuest: player = self.currentLevel.player currentTime = pygame.time.get_ticks() - # Calculate current speed based on jumping state - currentSpeed = player.moveSpeed * 1.5 if player.isJumping else player.moveSpeed + # Update running state + player.isRunning = keys[pygame.K_SPACE] + # Get current speed (handles both running and jumping) + currentSpeed = player.get_current_speed() + # Track movement distance for this frame movementDistance = 0