Graves basically rewritten.

This commit is contained in:
Storm Dragon
2025-02-08 03:30:45 -05:00
parent e0e097a397
commit ada16cb40f
8 changed files with 172 additions and 52 deletions

View File

@@ -7,13 +7,6 @@
"y": 0 "y": 0
}, },
"objects": [ "objects": [
{
"x": 2,
"y": 3,
"item": "guts",
"sound": "coffin",
"type": "coffin"
},
{ {
"x_range": [5, 8], "x_range": [5, 8],
"y": 3, "y": 3,
@@ -38,7 +31,7 @@
"x_range": [25, 30], "x_range": [25, 30],
"y": 0, "y": 0,
"enemy_type": "goblin", "enemy_type": "goblin",
"health": 3, "health": 1,
"damage": 1, "damage": 1,
"attack_range": 1, "attack_range": 1,
"attack_pattern": { "attack_pattern": {
@@ -54,10 +47,10 @@
{ {
"x": 45, "x": 45,
"y": 0, "y": 0,
"hazard": true, "type": "grave",
"sound": "grave", "sound": "grave",
"static": true, "static": true,
"zombie_spawn_chance": 10 "zombie_spawn_chance": 0
}, },
{ {
"x_range": [55, 57], "x_range": [55, 57],
@@ -76,8 +69,8 @@
"x_range": [75, 80], "x_range": [75, 80],
"y": 0, "y": 0,
"enemy_type": "goblin", "enemy_type": "goblin",
"health": 5, "health": 1,
"damage": 2, "damage": 1,
"attack_range": 1, "attack_range": 1,
"attack_pattern": { "attack_pattern": {
"type": "patrol" "type": "patrol"
@@ -86,10 +79,10 @@
{ {
"x": 85, "x": 85,
"y": 0, "y": 0,
"hazard": true, "type": "grave",
"sound": "grave", "sound": "grave",
"static": true, "static": true,
"zombie_spawn_chance": 15 "zombie_spawn_chance": 0
}, },
{ {
"x_range": [95, 100], "x_range": [95, 100],
@@ -125,11 +118,52 @@
"sound": "coin", "sound": "coin",
"collectible": true, "collectible": true,
"static": true "static": true
},
{
"x": 165,
"y": 0,
"type": "grave",
"sound": "grave",
"static": true,
"zombie_spawn_chance": 0
},
{
"x_range": [170, 172],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"x": 180,
"y": 3,
"sound": "coffin",
"type": "coffin"
},
{
"x_range": [185, 190],
"y": 0,
"enemy_type": "goblin",
"health": 1,
"damage": 1,
"attack_range": 1,
"attack_pattern": {
"type": "patrol"
}
},
{
"x": 195,
"y": 0,
"type": "grave",
"sound": "grave",
"static": true,
"zombie_spawn_chance": 0,
"item": "guts"
} }
], ],
"boundaries": { "boundaries": {
"left": 0, "left": 0,
"right": 165 "right": 200
}, },
"footstep_sound": "footstep_stone" "footstep_sound": "footstep_stone"
} }

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

Binary file not shown.

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

Binary file not shown.

View File

@@ -15,14 +15,14 @@ class CoffinObject(Object):
) )
self.sounds = sounds self.sounds = sounds
self.level = level self.level = level
self.is_broken = False self.isBroken = False
self.dropped_item = None self.dropped_item = None
self.specified_item = item self.specified_item = item
def hit(self, player_pos): def hit(self, player_pos):
"""Handle being hit by the player's weapon""" """Handle being hit by the player's weapon"""
if not self.is_broken: if not self.isBroken:
self.is_broken = True self.isBroken = True
play_sound(self.sounds['coffin_shatter']) play_sound(self.sounds['coffin_shatter'])
self.level.player.stats.update_stat('Coffins broken', 1) self.level.player.stats.update_stat('Coffins broken', 1)

34
src/grave.py Normal file
View File

@@ -0,0 +1,34 @@
from libstormgames import *
from src.object import Object
from src.powerup import PowerUp
class GraveObject(Object):
def __init__(self, x, y, sounds, item=None, zombieSpawnChance=0):
super().__init__(
x, y, "grave",
isStatic=True,
isCollectible=False,
isHazard=True,
zombieSpawnChance=zombieSpawnChance
)
self.graveItem = item
self.isCollected = False # Renamed to match style of isHazard, isStatic etc
self.sounds = sounds
def collect_grave_item(self, player):
"""Handle collection of items from graves via ducking.
Returns:
bool: True if item was collected, False if player should die
"""
# If grave has no item or item was already collected, player dies
if not self.graveItem or self.isCollected:
return False
# Collect the item if player is ducking
if player.isDucking:
self.isCollected = True # Mark as collected when collection succeeds
return True
return False

View File

@@ -4,6 +4,7 @@ from libstormgames import *
from src.catapult import Catapult from src.catapult import Catapult
from src.coffin import CoffinObject from src.coffin import CoffinObject
from src.enemy import Enemy from src.enemy import Enemy
from src.grave import GraveObject
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
@@ -19,7 +20,9 @@ class Level:
self.bouncing_items = [] self.bouncing_items = []
self.projectiles = [] # Track active projectiles self.projectiles = [] # Track active projectiles
self.player = player self.player = player
self.edge_warning_channel = None self.lastWarningTime = 0
self.warningInterval = int(self.sounds['edge'].get_length() * 1000) # Convert seconds to milliseconds
self.weapon_hit_channel = None self.weapon_hit_channel = None
self.leftBoundary = levelData["boundaries"]["left"] self.leftBoundary = levelData["boundaries"]["left"]
self.rightBoundary = levelData["boundaries"]["right"] self.rightBoundary = levelData["boundaries"]["right"]
@@ -61,6 +64,16 @@ 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 grave
elif obj.get("type") == "grave":
grave = GraveObject(
xPos[0],
obj["y"],
self.sounds,
item=obj.get("item", None),
zombieSpawnChance=obj.get("zombie_spawn_chance", 0)
)
self.objects.append(grave)
# Check if this is a skull storm # Check if this is a skull storm
elif obj.get("type") == "skull_storm": elif obj.get("type") == "skull_storm":
skullStorm = SkullStorm( skullStorm = SkullStorm(
@@ -105,11 +118,11 @@ class Level:
isStatic=obj.get("static", True), isStatic=obj.get("static", True),
isCollectible=obj.get("collectible", False), isCollectible=obj.get("collectible", False),
isHazard=obj.get("hazard", False), isHazard=obj.get("hazard", False),
zombie_spawn_chance=obj.get("zombie_spawn_chance", 0) zombieSpawnChance=obj.get("zombieSpawnChance", 0)
) )
self.objects.append(gameObject) self.objects.append(gameObject)
enemyCount = len(self.enemies) enemyCount = len(self.enemies)
coffinCount = sum(1 for obj in self.objects if hasattr(obj, 'is_broken')) coffinCount = sum(1 for obj in self.objects if hasattr(obj, 'isBroken'))
player.stats.update_stat('Enemies remaining', enemyCount) player.stats.update_stat('Enemies remaining', enemyCount)
player.stats.update_stat('Coffins remaining', coffinCount) player.stats.update_stat('Coffins remaining', coffinCount)
@@ -124,16 +137,16 @@ class Level:
# Check for potential zombie spawn from graves # Check for potential zombie spawn from graves
if (obj.soundName == "grave" and if (obj.soundName == "grave" and
obj.zombie_spawn_chance > 0 and obj.zombieSpawnChance > 0 and
not obj.has_spawned): not obj.hasSpawned):
distance = abs(self.player.xPos - obj.xPos) distance = abs(self.player.xPos - obj.xPos)
if distance < 6: # Within 6 tiles if distance < 6: # Within 6 tiles
# Mark as checked before doing anything else to prevent multiple checks # Mark as checked before doing anything else to prevent multiple checks
obj.has_spawned = True obj.hasSpawned = True
roll = random.randint(1, 100) roll = random.randint(1, 100)
if roll <= obj.zombie_spawn_chance: if roll <= obj.zombieSpawnChance:
zombie = Enemy( zombie = Enemy(
[obj.xPos, obj.xPos], [obj.xPos, obj.xPos],
obj.yPos, obj.yPos,
@@ -209,8 +222,8 @@ class Level:
# Check for coffin hits # 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, 'isBroken'): # Check if it's a coffin without using isinstance
if (not obj.is_broken and if (not obj.isBroken and
obj.xPos >= attackRange[0] and obj.xPos >= attackRange[0] and
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
@@ -233,12 +246,14 @@ 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)
if distance <= 2 and not self.player.isJumping and not self.player.isInvincible: currentTime = pygame.time.get_ticks()
if self.edge_warning_channel is None or not self.edge_warning_channel.get_busy(): if (distance <= 2 and not self.player.isJumping and not self.player.isInvincible
self.edge_warning_channel = play_sound(self.sounds['edge']) and currentTime - self.lastWarningTime >= self.warningInterval):
if isinstance(obj, GraveObject) and obj.graveItem and not obj.isCollected:
play_sound(self.sounds['_edge'])
else: else:
if self.edge_warning_channel is not None and not self.edge_warning_channel.get_busy(): play_sound(self.sounds['edge'])
self.edge_warning_channel = None self.lastWarningTime = currentTime
if obj.is_in_range(self.player.xPos): if obj.is_in_range(self.player.xPos):
if obj.isCollectible and self.player.isJumping: if obj.isCollectible and self.player.isJumping:
@@ -266,14 +281,30 @@ class Level:
play_sound(self.sounds['get_extra_life']) play_sound(self.sounds['get_extra_life'])
elif obj.isHazard and not self.player.isJumping: elif obj.isHazard and not self.player.isJumping:
if not self.player.isInvincible: if isinstance(obj, GraveObject):
can_collect = obj.collect_grave_item(self.player)
if can_collect:
# Successfully collected item while ducking
play_sound(self.sounds[f'get_{obj.graveItem}'])
self.player.stats.update_stat('Items collected', 1)
# Create PowerUp to handle the item effect
item = PowerUp(obj.xPos, obj.yPos, obj.graveItem, self.sounds, 1)
item.apply_effect(self.player)
# Stop grave's current audio channel
if obj.channel:
obj_stop(obj.channel)
# Remove the grave
obj.graveItem = None
obj.channel = None
obj.isActive = False # Mark the grave as inactive after collection
continue
elif not self.player.isInvincible:
# Kill player for normal graves or non-ducking collision
play_sound(self.sounds[obj.soundName]) play_sound(self.sounds[obj.soundName])
speak("You fell in an open grave! Now, it's yours!") speak("You fell in an open grave! Now, it's yours!")
self.player.set_health(0) self.player.set_health(0)
return False return False
else:
# When invincible, treat it like a successful jump over the grave
pass
# Handle boundaries # Handle boundaries
if self.player.xPos < self.leftBoundary: if self.player.xPos < self.leftBoundary:

View File

@@ -1,7 +1,7 @@
from libstormgames import * from libstormgames import *
class Object: class Object:
def __init__(self, x, yPos, soundName, isStatic=True, isCollectible=False, isHazard=False, zombie_spawn_chance=0): def __init__(self, x, yPos, soundName, isStatic=True, isCollectible=False, isHazard=False, zombieSpawnChance=0):
# x can be either a single position or a range [start, end] # x can be either a single position or a range [start, end]
self.xRange = [x, x] if isinstance(x, (int, float)) else x self.xRange = [x, x] if isinstance(x, (int, float)) else x
self.yPos = yPos self.yPos = yPos
@@ -9,8 +9,8 @@ class Object:
self.isStatic = isStatic self.isStatic = isStatic
self.isCollectible = isCollectible self.isCollectible = isCollectible
self.isHazard = isHazard self.isHazard = isHazard
self.zombie_spawn_chance = zombie_spawn_chance self.zombieSpawnChance = zombieSpawnChance
self.has_spawned = False # Track if this object has spawned a zombie self.hasSpawned = False
self.channel = None # For tracking the sound channel self.channel = None # For tracking the sound channel
self.isActive = True self.isActive = True
# For collectibles in a range, track which positions have been collected # For collectibles in a range, track which positions have been collected

View File

@@ -13,6 +13,7 @@ class Player:
self.moveSpeed = 0.05 self.moveSpeed = 0.05
self.jumpDuration = 1000 # Jump duration in milliseconds self.jumpDuration = 1000 # Jump duration in milliseconds
self.jumpStartTime = 0 self.jumpStartTime = 0
self.isDucking = False
self.isJumping = False self.isJumping = False
self.isRunning = False self.isRunning = False
self.runMultiplier = 1.5 # Same multiplier as jumping self.runMultiplier = 1.5 # Same multiplier as jumping
@@ -69,6 +70,20 @@ class Player:
return (self.distanceSinceLastStep >= self.get_step_distance() and return (self.distanceSinceLastStep >= self.get_step_distance() and
currentTime - self.lastStepTime >= self.get_step_interval()) currentTime - self.lastStepTime >= self.get_step_interval())
def duck(self):
"""Start ducking"""
if not self.isDucking and not self.isJumping: # Can't duck while jumping
self.isDucking = True
play_sound(self.sounds['duck'])
return True
return False
def stand(self):
"""Stop ducking state and play sound"""
if self.isDucking:
self.isDucking = False
play_sound(self.sounds['stand'])
def update(self, currentTime): def update(self, currentTime):
"""Update player state""" """Update player state"""
# Check if invincibility has expired # Check if invincibility has expired