Some minor cleanup.
This commit is contained in:
+14
-14
@@ -20,15 +20,15 @@ class Pumpkin:
|
|||||||
"""Update pumpkin position and sound"""
|
"""Update pumpkin position and sound"""
|
||||||
if not self.isActive:
|
if not self.isActive:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.x += self.direction * self.speed
|
self.x += self.direction * self.speed
|
||||||
|
|
||||||
# Update or start positional audio
|
# Update or start positional audio
|
||||||
if self.soundChannel is None or not self.soundChannel.get_busy():
|
if self.soundChannel is None or not self.soundChannel.get_busy():
|
||||||
self.soundChannel = obj_play(sounds, self.soundName, playerX, self.x)
|
self.soundChannel = obj_play(sounds, self.soundName, playerX, self.x)
|
||||||
else:
|
else:
|
||||||
self.soundChannel = obj_update(self.soundChannel, playerX, self.x)
|
self.soundChannel = obj_update(self.soundChannel, playerX, self.x)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def stop_sound(self, sounds, playerX):
|
def stop_sound(self, sounds, playerX):
|
||||||
@@ -45,7 +45,7 @@ class Pumpkin:
|
|||||||
"""Check if pumpkin hits player"""
|
"""Check if pumpkin hits player"""
|
||||||
if not self.isActive:
|
if not self.isActive:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
distance = abs(player.xPos - self.x)
|
distance = abs(player.xPos - self.x)
|
||||||
if distance < 1: # Within 1 tile
|
if distance < 1: # Within 1 tile
|
||||||
if self.isHigh and not player.isJumping:
|
if self.isHigh and not player.isJumping:
|
||||||
@@ -75,21 +75,21 @@ class Catapult(Object):
|
|||||||
def fire(self, currentTime, player):
|
def fire(self, currentTime, player):
|
||||||
"""Start the firing sequence"""
|
"""Start the firing sequence"""
|
||||||
self.lastFireTime = currentTime
|
self.lastFireTime = currentTime
|
||||||
|
|
||||||
# Play launch sound using directional audio
|
# Play launch sound using directional audio
|
||||||
play_directional_sound(self.sounds, 'catapult_launch', player.xPos, self.xPos)
|
play_directional_sound(self.sounds, 'catapult_launch', player.xPos, self.xPos)
|
||||||
|
|
||||||
# Set up pending pumpkin
|
# Set up pending pumpkin
|
||||||
isHigh = random.choice([True, False])
|
isHigh = random.choice([True, False])
|
||||||
fireDirection = 1 if player.xPos > self.xPos else -1
|
fireDirection = 1 if player.xPos > self.xPos else -1
|
||||||
|
|
||||||
# Store pumpkin data for later creation
|
# Store pumpkin data for later creation
|
||||||
self.pendingPumpkin = {
|
self.pendingPumpkin = {
|
||||||
'isHigh': isHigh,
|
'isHigh': isHigh,
|
||||||
'direction': fireDirection,
|
'direction': fireDirection,
|
||||||
'playerMaxHealth': player.get_max_health()
|
'playerMaxHealth': player.get_max_health()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Set when to actually launch the pumpkin
|
# Set when to actually launch the pumpkin
|
||||||
self.pumpkinLaunchTime = currentTime + self.launchDelay
|
self.pumpkinLaunchTime = currentTime + self.launchDelay
|
||||||
|
|
||||||
@@ -97,11 +97,11 @@ class Catapult(Object):
|
|||||||
"""Update catapult and its pumpkins"""
|
"""Update catapult and its pumpkins"""
|
||||||
if not self.isActive:
|
if not self.isActive:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if player is in range
|
# Check if player is in range
|
||||||
distance = abs(player.xPos - self.xPos)
|
distance = abs(player.xPos - self.xPos)
|
||||||
inRange = distance <= self.firingRange
|
inRange = distance <= self.firingRange
|
||||||
|
|
||||||
# Handle entering/leaving range
|
# Handle entering/leaving range
|
||||||
if inRange and not self.isFiring:
|
if inRange and not self.isFiring:
|
||||||
self.isFiring = True
|
self.isFiring = True
|
||||||
@@ -110,7 +110,7 @@ class Catapult(Object):
|
|||||||
elif not inRange and self.isFiring:
|
elif not inRange and self.isFiring:
|
||||||
self.isFiring = False
|
self.isFiring = False
|
||||||
speak("Out of pumpkin catapult range.")
|
speak("Out of pumpkin catapult range.")
|
||||||
|
|
||||||
# Check for pending pumpkin launch
|
# Check for pending pumpkin launch
|
||||||
if self.pendingPumpkin and currentTime >= self.pumpkinLaunchTime:
|
if self.pendingPumpkin and currentTime >= self.pumpkinLaunchTime:
|
||||||
# Create and fire the pending pumpkin
|
# Create and fire the pending pumpkin
|
||||||
@@ -122,18 +122,18 @@ class Catapult(Object):
|
|||||||
)
|
)
|
||||||
self.activePumpkins.append(pumpkin)
|
self.activePumpkins.append(pumpkin)
|
||||||
self.pendingPumpkin = None
|
self.pendingPumpkin = None
|
||||||
|
|
||||||
# Only start new fire sequence if in range and enough time has passed
|
# Only start new fire sequence if in range and enough time has passed
|
||||||
if self.isFiring and currentTime - self.lastFireTime >= self.fireInterval:
|
if self.isFiring and currentTime - self.lastFireTime >= self.fireInterval:
|
||||||
self.fire(currentTime, player)
|
self.fire(currentTime, player)
|
||||||
|
|
||||||
# Always update existing pumpkins
|
# Always update existing pumpkins
|
||||||
for pumpkin in self.activePumpkins[:]: # Copy list to allow removal
|
for pumpkin in self.activePumpkins[:]: # Copy list to allow removal
|
||||||
if not pumpkin.update(self.sounds, player.xPos):
|
if not pumpkin.update(self.sounds, player.xPos):
|
||||||
pumpkin.stop_sound(self.sounds, player.xPos)
|
pumpkin.stop_sound(self.sounds, player.xPos)
|
||||||
self.activePumpkins.remove(pumpkin)
|
self.activePumpkins.remove(pumpkin)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if pumpkin.check_collision(player):
|
if pumpkin.check_collision(player):
|
||||||
player.set_health(player.get_health() - pumpkin.damage)
|
player.set_health(player.get_health() - pumpkin.damage)
|
||||||
pumpkin.stop_sound(self.sounds, player.xPos)
|
pumpkin.stop_sound(self.sounds, player.xPos)
|
||||||
|
|||||||
+6
-6
@@ -20,7 +20,7 @@ class CoffinObject(Object):
|
|||||||
self.isBroken = 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.isBroken:
|
if not self.isBroken:
|
||||||
@@ -28,15 +28,15 @@ class CoffinObject(Object):
|
|||||||
play_sound(self.sounds['coffin_shatter'])
|
play_sound(self.sounds['coffin_shatter'])
|
||||||
self.level.levelScore += 500
|
self.level.levelScore += 500
|
||||||
self.level.player.stats.update_stat('Coffins broken', 1)
|
self.level.player.stats.update_stat('Coffins broken', 1)
|
||||||
|
|
||||||
# Stop the ongoing coffin sound
|
# Stop the ongoing coffin sound
|
||||||
if self.channel:
|
if self.channel:
|
||||||
obj_stop(self.channel)
|
obj_stop(self.channel)
|
||||||
self.channel = None
|
self.channel = None
|
||||||
|
|
||||||
# Mark coffin as inactive since it's broken
|
# Mark coffin as inactive since it's broken
|
||||||
self.isActive = False
|
self.isActive = False
|
||||||
|
|
||||||
# Determine item to drop
|
# Determine item to drop
|
||||||
if self.specified_item == "random":
|
if self.specified_item == "random":
|
||||||
item_type = ItemProperties.get_random_item()
|
item_type = ItemProperties.get_random_item()
|
||||||
@@ -47,12 +47,12 @@ class CoffinObject(Object):
|
|||||||
else:
|
else:
|
||||||
# Fall back to random if invalid item specified
|
# Fall back to random if invalid item specified
|
||||||
item_type = ItemProperties.get_random_item()
|
item_type = ItemProperties.get_random_item()
|
||||||
|
|
||||||
# Create item 1-2 tiles away in random direction
|
# Create item 1-2 tiles away in random direction
|
||||||
direction = random.choice([-1, 1])
|
direction = random.choice([-1, 1])
|
||||||
drop_distance = random.randint(1, 2)
|
drop_distance = random.randint(1, 2)
|
||||||
drop_x = self.xPos + (direction * drop_distance)
|
drop_x = self.xPos + (direction * drop_distance)
|
||||||
|
|
||||||
self.dropped_item = PowerUp(
|
self.dropped_item = PowerUp(
|
||||||
drop_x,
|
drop_x,
|
||||||
self.yPos,
|
self.yPos,
|
||||||
|
|||||||
+25
-25
@@ -18,7 +18,7 @@ class Enemy(Object):
|
|||||||
isStatic=False,
|
isStatic=False,
|
||||||
isHazard=True
|
isHazard=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Enemy specific properties
|
# Enemy specific properties
|
||||||
self.enemyType = enemyType
|
self.enemyType = enemyType
|
||||||
self.level = level
|
self.level = level
|
||||||
@@ -26,7 +26,7 @@ class Enemy(Object):
|
|||||||
self.damage = kwargs.get('damage', 1) # Default 1 damage
|
self.damage = kwargs.get('damage', 1) # Default 1 damage
|
||||||
self.attackRange = kwargs.get('attack_range', 1) # Default 1 tile range
|
self.attackRange = kwargs.get('attack_range', 1) # Default 1 tile range
|
||||||
self.sounds = sounds # Store reference to game sounds
|
self.sounds = sounds # Store reference to game sounds
|
||||||
|
|
||||||
# Movement and behavior properties
|
# Movement and behavior properties
|
||||||
self.movingRight = True # Initial direction
|
self.movingRight = True # Initial direction
|
||||||
self.movementSpeed = 0.03 # Base speed
|
self.movementSpeed = 0.03 # Base speed
|
||||||
@@ -35,7 +35,7 @@ class Enemy(Object):
|
|||||||
self.lastAttackTime = 0
|
self.lastAttackTime = 0
|
||||||
self.attackCooldown = 1000 # 1 second between attacks
|
self.attackCooldown = 1000 # 1 second between attacks
|
||||||
self._currentX = self.xRange[0] # Initialize current position
|
self._currentX = self.xRange[0] # Initialize current position
|
||||||
|
|
||||||
# Add spawn configuration
|
# Add spawn configuration
|
||||||
self.canSpawn = kwargs.get('can_spawn', False)
|
self.canSpawn = kwargs.get('can_spawn', False)
|
||||||
if self.canSpawn:
|
if self.canSpawn:
|
||||||
@@ -48,7 +48,7 @@ class Enemy(Object):
|
|||||||
# Attack pattern configuration
|
# Attack pattern configuration
|
||||||
self.attackPattern = kwargs.get('attack_pattern', {'type': 'patrol'})
|
self.attackPattern = kwargs.get('attack_pattern', {'type': 'patrol'})
|
||||||
self.turnThreshold = self.attackPattern.get('turn_threshold', 5)
|
self.turnThreshold = self.attackPattern.get('turn_threshold', 5)
|
||||||
|
|
||||||
# Initialize vulnerability system
|
# Initialize vulnerability system
|
||||||
self.hasVulnerabilitySystem = kwargs.get('has_vulnerability', False)
|
self.hasVulnerabilitySystem = kwargs.get('has_vulnerability', False)
|
||||||
if self.hasVulnerabilitySystem:
|
if self.hasVulnerabilitySystem:
|
||||||
@@ -73,18 +73,18 @@ class Enemy(Object):
|
|||||||
self.attackPattern = {'type': 'hunter'} # Spiders actively hunt the player
|
self.attackPattern = {'type': 'hunter'} # Spiders actively hunt the player
|
||||||
self.turnThreshold = 3 # Spiders turn around quickly to chase player
|
self.turnThreshold = 3 # Spiders turn around quickly to chase player
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def xPos(self):
|
def xPos(self):
|
||||||
"""Current x position"""
|
"""Current x position"""
|
||||||
return self._currentX
|
return self._currentX
|
||||||
|
|
||||||
@xPos.setter
|
@xPos.setter
|
||||||
|
|
||||||
def xPos(self, value):
|
def xPos(self, value):
|
||||||
"""Set current x position"""
|
"""Set current x position"""
|
||||||
self._currentX = value
|
self._currentX = value
|
||||||
|
|
||||||
def patrol_movement(self):
|
def patrol_movement(self):
|
||||||
"""Standard back-and-forth patrol movement"""
|
"""Standard back-and-forth patrol movement"""
|
||||||
if self.movingRight:
|
if self.movingRight:
|
||||||
@@ -100,7 +100,7 @@ class Enemy(Object):
|
|||||||
"""Update enemy position and handle attacks"""
|
"""Update enemy position and handle attacks"""
|
||||||
if not self.isActive or self.health <= 0:
|
if not self.isActive or self.health <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Initialize sound for enemies with vulnerability system immediately upon creation
|
# Initialize sound for enemies with vulnerability system immediately upon creation
|
||||||
if self.hasVulnerabilitySystem:
|
if self.hasVulnerabilitySystem:
|
||||||
if self.channel is None:
|
if self.channel is None:
|
||||||
@@ -109,12 +109,12 @@ class Enemy(Object):
|
|||||||
# Update existing channel position
|
# Update existing channel position
|
||||||
else:
|
else:
|
||||||
self.channel = obj_update(self.channel, player.xPos, self.xPos)
|
self.channel = obj_update(self.channel, player.xPos, self.xPos)
|
||||||
|
|
||||||
# Check for vulnerability state change
|
# Check for vulnerability state change
|
||||||
if currentTime - self.vulnerabilityTimer >= (self.vulnerabilityDuration if self.isVulnerable else self.invulnerabilityDuration):
|
if currentTime - self.vulnerabilityTimer >= (self.vulnerabilityDuration if self.isVulnerable else self.invulnerabilityDuration):
|
||||||
self.isVulnerable = not self.isVulnerable
|
self.isVulnerable = not self.isVulnerable
|
||||||
self.vulnerabilityTimer = currentTime
|
self.vulnerabilityTimer = currentTime
|
||||||
|
|
||||||
if self.channel:
|
if self.channel:
|
||||||
obj_stop(self.channel)
|
obj_stop(self.channel)
|
||||||
soundName = f"{self.enemyType}_is_vulnerable" if self.isVulnerable else self.enemyType
|
soundName = f"{self.enemyType}_is_vulnerable" if self.isVulnerable else self.enemyType
|
||||||
@@ -124,17 +124,17 @@ class Enemy(Object):
|
|||||||
if not self.hunting:
|
if not self.hunting:
|
||||||
if self.patrolStart <= player.xPos <= self.patrolEnd:
|
if self.patrolStart <= player.xPos <= self.patrolEnd:
|
||||||
self.hunting = True
|
self.hunting = True
|
||||||
|
|
||||||
# Handle movement based on enemy type and pattern
|
# Handle movement based on enemy type and pattern
|
||||||
if (self.enemyType == "zombie" or
|
if (self.enemyType == "zombie" or
|
||||||
(self.attackPattern['type'] == 'hunter' and self.hunting)):
|
(self.attackPattern['type'] == 'hunter' and self.hunting)):
|
||||||
|
|
||||||
distanceToPlayer = player.xPos - self.xPos
|
distanceToPlayer = player.xPos - self.xPos
|
||||||
|
|
||||||
# If we've moved past the player by more than the turn threshold, turn around
|
# If we've moved past the player by more than the turn threshold, turn around
|
||||||
if abs(distanceToPlayer) >= self.turnThreshold:
|
if abs(distanceToPlayer) >= self.turnThreshold:
|
||||||
self.movingRight = distanceToPlayer > 0
|
self.movingRight = distanceToPlayer > 0
|
||||||
|
|
||||||
# Otherwise keep moving in current direction
|
# Otherwise keep moving in current direction
|
||||||
self.xPos += self.movementSpeed if self.movingRight else -self.movementSpeed
|
self.xPos += self.movementSpeed if self.movingRight else -self.movementSpeed
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ class Enemy(Object):
|
|||||||
# Check for attack opportunity
|
# Check for attack opportunity
|
||||||
if self.can_attack(currentTime, player):
|
if self.can_attack(currentTime, player):
|
||||||
self.attack(currentTime, player)
|
self.attack(currentTime, player)
|
||||||
|
|
||||||
if self.canSpawn:
|
if self.canSpawn:
|
||||||
if currentTime - self.lastSpawnTime >= self.spawnCooldown:
|
if currentTime - self.lastSpawnTime >= self.spawnCooldown:
|
||||||
distanceToPlayer = abs(player.xPos - self.xPos)
|
distanceToPlayer = abs(player.xPos - self.xPos)
|
||||||
@@ -197,22 +197,22 @@ class Enemy(Object):
|
|||||||
# Must have cooled down from last attack
|
# Must have cooled down from last attack
|
||||||
if currentTime - self.lastAttackTime < self.attackCooldown:
|
if currentTime - self.lastAttackTime < self.attackCooldown:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Don't attack if player is jumping
|
# Don't attack if player is jumping
|
||||||
if player.isJumping:
|
if player.isJumping:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if player is in range and on same side we're facing
|
# Check if player is in range and on same side we're facing
|
||||||
distance = abs(player.xPos - self.xPos)
|
distance = abs(player.xPos - self.xPos)
|
||||||
tolerance = 0.5 # Same tolerance as we used for the grave
|
tolerance = 0.5 # Same tolerance as we used for the grave
|
||||||
|
|
||||||
if distance <= (self.attackRange + tolerance):
|
if distance <= (self.attackRange + tolerance):
|
||||||
# Only attack if we're facing the right way
|
# Only attack if we're facing the right way
|
||||||
playerOnRight = player.xPos > self.xPos
|
playerOnRight = player.xPos > self.xPos
|
||||||
return playerOnRight == self.movingRight
|
return playerOnRight == self.movingRight
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def attack(self, currentTime, player):
|
def attack(self, currentTime, player):
|
||||||
"""Perform attack on player"""
|
"""Perform attack on player"""
|
||||||
if player.isInvincible: return
|
if player.isInvincible: return
|
||||||
@@ -224,7 +224,7 @@ class Enemy(Object):
|
|||||||
# Deal damage to player
|
# Deal damage to player
|
||||||
player.set_health(player.get_health() - self.damage)
|
player.set_health(player.get_health() - self.damage)
|
||||||
self.sounds['player_takes_damage'].play()
|
self.sounds['player_takes_damage'].play()
|
||||||
|
|
||||||
def take_damage(self, amount):
|
def take_damage(self, amount):
|
||||||
"""Handle enemy taking damage"""
|
"""Handle enemy taking damage"""
|
||||||
if self.hasVulnerabilitySystem and not self.isVulnerable:
|
if self.hasVulnerabilitySystem and not self.isVulnerable:
|
||||||
@@ -233,7 +233,7 @@ class Enemy(Object):
|
|||||||
self.health -= amount
|
self.health -= amount
|
||||||
if self.health <= 0:
|
if self.health <= 0:
|
||||||
self.die()
|
self.die()
|
||||||
|
|
||||||
def die(self):
|
def die(self):
|
||||||
"""Handle enemy death"""
|
"""Handle enemy death"""
|
||||||
self.isActive = False
|
self.isActive = False
|
||||||
@@ -247,7 +247,7 @@ class Enemy(Object):
|
|||||||
rangeModifier = self.attackRange * 250
|
rangeModifier = self.attackRange * 250
|
||||||
speedModifier = int(self.movementSpeed * 1000)
|
speedModifier = int(self.movementSpeed * 1000)
|
||||||
totalPoints = max(basePoints + damageModifier + rangeModifier + speedModifier, 1000)
|
totalPoints = max(basePoints + damageModifier + rangeModifier + speedModifier, 1000)
|
||||||
|
|
||||||
# Award points
|
# Award points
|
||||||
self.level.levelScore += totalPoints
|
self.level.levelScore += totalPoints
|
||||||
|
|
||||||
@@ -263,12 +263,12 @@ class Enemy(Object):
|
|||||||
hasNunchucks = any(weapon.name == "nunchucks" for weapon in self.level.player.weapons)
|
hasNunchucks = any(weapon.name == "nunchucks" for weapon in self.level.player.weapons)
|
||||||
# Drop witch_broom only if player has neither broom nor nunchucks
|
# Drop witch_broom only if player has neither broom nor nunchucks
|
||||||
itemType = "witch_broom" if not (hasBroom or hasNunchucks) else "cauldron"
|
itemType = "witch_broom" if not (hasBroom or hasNunchucks) else "cauldron"
|
||||||
|
|
||||||
# Create drop 1-2 tiles away in random direction
|
# Create drop 1-2 tiles away in random direction
|
||||||
direction = random.choice([-1, 1])
|
direction = random.choice([-1, 1])
|
||||||
dropDistance = random.randint(1, 2)
|
dropDistance = random.randint(1, 2)
|
||||||
dropX = self.xPos + (direction * dropDistance)
|
dropX = self.xPos + (direction * dropDistance)
|
||||||
|
|
||||||
droppedItem = PowerUp(
|
droppedItem = PowerUp(
|
||||||
dropX,
|
dropX,
|
||||||
self.yPos,
|
self.yPos,
|
||||||
|
|||||||
+24
-24
@@ -8,7 +8,7 @@ from libstormgames import speak
|
|||||||
|
|
||||||
def get_available_games():
|
def get_available_games():
|
||||||
"""Get list of available game directories in levels folder.
|
"""Get list of available game directories in levels folder.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: List of game directory names
|
list: List of game directory names
|
||||||
"""
|
"""
|
||||||
@@ -19,11 +19,11 @@ def get_available_games():
|
|||||||
|
|
||||||
def selection_menu(sounds, *options):
|
def selection_menu(sounds, *options):
|
||||||
"""Display level selection menu.
|
"""Display level selection menu.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sounds (dict): Dictionary of loaded sound effects
|
sounds (dict): Dictionary of loaded sound effects
|
||||||
*options: Variable number of menu options
|
*options: Variable number of menu options
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Selected option or None if cancelled
|
str: Selected option or None if cancelled
|
||||||
"""
|
"""
|
||||||
@@ -31,53 +31,53 @@ def selection_menu(sounds, *options):
|
|||||||
pygame.mixer.stop()
|
pygame.mixer.stop()
|
||||||
i = 0
|
i = 0
|
||||||
j = -1
|
j = -1
|
||||||
|
|
||||||
# Clear any pending events
|
# Clear any pending events
|
||||||
pygame.event.clear()
|
pygame.event.clear()
|
||||||
|
|
||||||
speak("Select an adventure")
|
speak("Select an adventure")
|
||||||
time.sleep(1.0)
|
time.sleep(1.0)
|
||||||
|
|
||||||
while loop:
|
while loop:
|
||||||
if i != j:
|
if i != j:
|
||||||
speak(options[i])
|
speak(options[i])
|
||||||
j = i
|
j = i
|
||||||
|
|
||||||
pygame.event.pump()
|
pygame.event.pump()
|
||||||
event = pygame.event.wait()
|
event = pygame.event.wait()
|
||||||
|
|
||||||
if event.type == pygame.KEYDOWN:
|
if event.type == pygame.KEYDOWN:
|
||||||
if event.key == pygame.K_ESCAPE:
|
if event.key == pygame.K_ESCAPE:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if event.key == pygame.K_DOWN and i < len(options) - 1:
|
if event.key == pygame.K_DOWN and i < len(options) - 1:
|
||||||
i = i + 1
|
i = i + 1
|
||||||
try:
|
try:
|
||||||
sounds['menu-move'].play()
|
sounds['menu-move'].play()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if event.key == pygame.K_UP and i > 0:
|
if event.key == pygame.K_UP and i > 0:
|
||||||
i = i - 1
|
i = i - 1
|
||||||
try:
|
try:
|
||||||
sounds['menu-move'].play()
|
sounds['menu-move'].play()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if event.key == pygame.K_HOME and i != 0:
|
if event.key == pygame.K_HOME and i != 0:
|
||||||
i = 0
|
i = 0
|
||||||
try:
|
try:
|
||||||
sounds['menu-move'].play()
|
sounds['menu-move'].play()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if event.key == pygame.K_END and i != len(options) - 1:
|
if event.key == pygame.K_END and i != len(options) - 1:
|
||||||
i = len(options) - 1
|
i = len(options) - 1
|
||||||
try:
|
try:
|
||||||
sounds['menu-move'].play()
|
sounds['menu-move'].play()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if event.key == pygame.K_RETURN:
|
if event.key == pygame.K_RETURN:
|
||||||
try:
|
try:
|
||||||
sounds['menu-select'].play()
|
sounds['menu-select'].play()
|
||||||
@@ -87,48 +87,48 @@ def selection_menu(sounds, *options):
|
|||||||
return options[i]
|
return options[i]
|
||||||
elif event.type == pygame.QUIT:
|
elif event.type == pygame.QUIT:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
pygame.event.pump()
|
pygame.event.pump()
|
||||||
event = pygame.event.clear()
|
event = pygame.event.clear()
|
||||||
time.sleep(0.001)
|
time.sleep(0.001)
|
||||||
|
|
||||||
def select_game(sounds):
|
def select_game(sounds):
|
||||||
"""Display game selection menu and return chosen game.
|
"""Display game selection menu and return chosen game.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sounds (dict): Dictionary of loaded sound effects
|
sounds (dict): Dictionary of loaded sound effects
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Selected game directory name or None if cancelled
|
str: Selected game directory name or None if cancelled
|
||||||
"""
|
"""
|
||||||
availableGames = get_available_games()
|
availableGames = get_available_games()
|
||||||
|
|
||||||
if not availableGames:
|
if not availableGames:
|
||||||
speak("No games found in levels directory!")
|
speak("No games found in levels directory!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Convert directory names to display names (replace underscores with spaces)
|
# Convert directory names to display names (replace underscores with spaces)
|
||||||
menuOptions = [game.replace("_", " ") for game in availableGames]
|
menuOptions = [game.replace("_", " ") for game in availableGames]
|
||||||
|
|
||||||
choice = selection_menu(sounds, *menuOptions)
|
choice = selection_menu(sounds, *menuOptions)
|
||||||
|
|
||||||
if choice is None:
|
if choice is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Convert display name back to directory name if needed
|
# Convert display name back to directory name if needed
|
||||||
gameDir = choice.replace(" ", "_")
|
gameDir = choice.replace(" ", "_")
|
||||||
if gameDir not in availableGames:
|
if gameDir not in availableGames:
|
||||||
gameDir = choice # Use original if conversion doesn't match
|
gameDir = choice # Use original if conversion doesn't match
|
||||||
|
|
||||||
return gameDir
|
return gameDir
|
||||||
|
|
||||||
def get_level_path(gameDir, levelNum):
|
def get_level_path(gameDir, levelNum):
|
||||||
"""Get full path to level JSON file.
|
"""Get full path to level JSON file.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gameDir (str): Game directory name
|
gameDir (str): Game directory name
|
||||||
levelNum (int): Level number
|
levelNum (int): Level number
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Full path to level JSON file
|
str: Full path to level JSON file
|
||||||
"""
|
"""
|
||||||
|
|||||||
+19
-19
@@ -6,7 +6,7 @@ from src.object import Object
|
|||||||
|
|
||||||
class GraspingHands(Object):
|
class GraspingHands(Object):
|
||||||
"""A hazard where the ground crumbles beneath the player as undead hands reach up."""
|
"""A hazard where the ground crumbles beneath the player as undead hands reach up."""
|
||||||
|
|
||||||
def __init__(self, xRange, y, sounds, delay=1000, crumble_speed=0.065):
|
def __init__(self, xRange, y, sounds, delay=1000, crumble_speed=0.065):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
xRange,
|
xRange,
|
||||||
@@ -19,7 +19,7 @@ class GraspingHands(Object):
|
|||||||
self.sounds = sounds
|
self.sounds = sounds
|
||||||
self.delay = delay # Delay in milliseconds before ground starts crumbling
|
self.delay = delay # Delay in milliseconds before ground starts crumbling
|
||||||
self.crumble_speed = crumble_speed # How fast the crumbling catches up (tiles per frame)
|
self.crumble_speed = crumble_speed # How fast the crumbling catches up (tiles per frame)
|
||||||
|
|
||||||
# State tracking
|
# State tracking
|
||||||
self.isTriggered = False # Has the player entered the zone?
|
self.isTriggered = False # Has the player entered the zone?
|
||||||
self.triggerTime = 0 # When did the player enter the zone?
|
self.triggerTime = 0 # When did the player enter the zone?
|
||||||
@@ -29,13 +29,13 @@ class GraspingHands(Object):
|
|||||||
self.crumbleChannel = None # Channel for the looping crumble sound
|
self.crumbleChannel = None # Channel for the looping crumble sound
|
||||||
self.entryFromRight = False # Which side did player enter from
|
self.entryFromRight = False # Which side did player enter from
|
||||||
self.crumbleDirection = 1 # Direction the crumbling moves (1=right, -1=left)
|
self.crumbleDirection = 1 # Direction the crumbling moves (1=right, -1=left)
|
||||||
|
|
||||||
def trigger(self, currentTime, playerX):
|
def trigger(self, currentTime, playerX):
|
||||||
"""Trigger the grasping hands when player enters range"""
|
"""Trigger the grasping hands when player enters range"""
|
||||||
if not self.isTriggered:
|
if not self.isTriggered:
|
||||||
self.isTriggered = True
|
self.isTriggered = True
|
||||||
self.triggerTime = currentTime
|
self.triggerTime = currentTime
|
||||||
|
|
||||||
# Determine which side player entered from
|
# Determine which side player entered from
|
||||||
if playerX > (self.xRange[0] + self.xRange[1]) / 2:
|
if playerX > (self.xRange[0] + self.xRange[1]) / 2:
|
||||||
# Player entered from right side
|
# Player entered from right side
|
||||||
@@ -47,48 +47,48 @@ class GraspingHands(Object):
|
|||||||
self.entryFromRight = False
|
self.entryFromRight = False
|
||||||
self.crumblePosition = self.xRange[0] # Start crumbling from left boundary
|
self.crumblePosition = self.xRange[0] # Start crumbling from left boundary
|
||||||
self.crumbleDirection = 1 # Crumble moves right
|
self.crumbleDirection = 1 # Crumble moves right
|
||||||
|
|
||||||
self.isReset = False
|
self.isReset = False
|
||||||
|
|
||||||
# Play initial warning sound
|
# Play initial warning sound
|
||||||
play_sound(self.sounds['grasping_hands_start'])
|
play_sound(self.sounds['grasping_hands_start'])
|
||||||
speak("The ground crumbles as the dead reach for you.")
|
speak("The ground crumbles as the dead reach for you.")
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Reset the trap when player leaves the range"""
|
"""Reset the trap when player leaves the range"""
|
||||||
if not self.isReset:
|
if not self.isReset:
|
||||||
self.isTriggered = False
|
self.isTriggered = False
|
||||||
self.crumblePosition = 0
|
self.crumblePosition = 0
|
||||||
self.isReset = True
|
self.isReset = True
|
||||||
|
|
||||||
# Stop the looping crumble sound if it's playing
|
# Stop the looping crumble sound if it's playing
|
||||||
if self.crumbleChannel:
|
if self.crumbleChannel:
|
||||||
obj_stop(self.crumbleChannel)
|
obj_stop(self.crumbleChannel)
|
||||||
self.crumbleChannel = None
|
self.crumbleChannel = None
|
||||||
|
|
||||||
# Play the end sound
|
# Play the end sound
|
||||||
play_sound(self.sounds['grasping_hands_end'])
|
play_sound(self.sounds['grasping_hands_end'])
|
||||||
|
|
||||||
def update(self, currentTime, player):
|
def update(self, currentTime, player):
|
||||||
"""Update the grasping hands trap state"""
|
"""Update the grasping hands trap state"""
|
||||||
if not self.isActive:
|
if not self.isActive:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if player is in range
|
# Check if player is in range
|
||||||
isInRange = self.xRange[0] <= player.xPos <= self.xRange[1]
|
isInRange = self.xRange[0] <= player.xPos <= self.xRange[1]
|
||||||
|
|
||||||
# Handle player entering/exiting range
|
# Handle player entering/exiting range
|
||||||
if isInRange and not self.isTriggered:
|
if isInRange and not self.isTriggered:
|
||||||
self.trigger(currentTime, player.xPos)
|
self.trigger(currentTime, player.xPos)
|
||||||
elif not isInRange and self.isTriggered:
|
elif not isInRange and self.isTriggered:
|
||||||
self.reset()
|
self.reset()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If triggered and delay has passed, start crumbling
|
# If triggered and delay has passed, start crumbling
|
||||||
if self.isTriggered and currentTime - self.triggerTime >= self.delay:
|
if self.isTriggered and currentTime - self.triggerTime >= self.delay:
|
||||||
# Update crumble position based on direction
|
# Update crumble position based on direction
|
||||||
self.crumblePosition += self.crumble_speed * self.crumbleDirection
|
self.crumblePosition += self.crumble_speed * self.crumbleDirection
|
||||||
|
|
||||||
# Manage the looping positional audio for the crumbling ground
|
# Manage the looping positional audio for the crumbling ground
|
||||||
if self.crumbleChannel is None or not self.crumbleChannel.get_busy():
|
if self.crumbleChannel is None or not self.crumbleChannel.get_busy():
|
||||||
# Start the sound if it's not playing
|
# Start the sound if it's not playing
|
||||||
@@ -96,14 +96,14 @@ class GraspingHands(Object):
|
|||||||
else:
|
else:
|
||||||
# Update the sound position
|
# Update the sound position
|
||||||
self.crumbleChannel = obj_update(self.crumbleChannel, player.xPos, self.crumblePosition)
|
self.crumbleChannel = obj_update(self.crumbleChannel, player.xPos, self.crumblePosition)
|
||||||
|
|
||||||
# Check if player is caught by crumbling
|
# Check if player is caught by crumbling
|
||||||
playerCaught = False
|
playerCaught = False
|
||||||
if not player.isJumping:
|
if not player.isJumping:
|
||||||
if (self.crumbleDirection > 0 and player.xPos <= self.crumblePosition) or \
|
if (self.crumbleDirection > 0 and player.xPos <= self.crumblePosition) or \
|
||||||
(self.crumbleDirection < 0 and player.xPos >= self.crumblePosition):
|
(self.crumbleDirection < 0 and player.xPos >= self.crumblePosition):
|
||||||
playerCaught = True
|
playerCaught = True
|
||||||
|
|
||||||
if playerCaught:
|
if playerCaught:
|
||||||
if not player.isInvincible:
|
if not player.isInvincible:
|
||||||
# Player is caught - instant death
|
# Player is caught - instant death
|
||||||
@@ -111,14 +111,14 @@ class GraspingHands(Object):
|
|||||||
if self.crumbleChannel:
|
if self.crumbleChannel:
|
||||||
obj_stop(self.crumbleChannel)
|
obj_stop(self.crumbleChannel)
|
||||||
self.crumbleChannel = None
|
self.crumbleChannel = None
|
||||||
|
|
||||||
speak("The hands of the dead drag you down!")
|
speak("The hands of the dead drag you down!")
|
||||||
player.set_health(0)
|
player.set_health(0)
|
||||||
return True
|
return True
|
||||||
# Player is invincible - no warning needed
|
# Player is invincible - no warning needed
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
"""Cleanup when object is destroyed"""
|
"""Cleanup when object is destroyed"""
|
||||||
# Ensure sound is stopped when object is destroyed
|
# Ensure sound is stopped when object is destroyed
|
||||||
|
|||||||
+2
-2
@@ -20,7 +20,7 @@ class GraveObject(Object):
|
|||||||
|
|
||||||
def collect_grave_item(self, player):
|
def collect_grave_item(self, player):
|
||||||
"""Handle collection of items from graves via ducking.
|
"""Handle collection of items from graves via ducking.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if item was collected, False if player should die
|
bool: True if item was collected, False if player should die
|
||||||
"""
|
"""
|
||||||
@@ -32,5 +32,5 @@ class GraveObject(Object):
|
|||||||
if player.isDucking:
|
if player.isDucking:
|
||||||
self.isCollected = True # Mark as collected when collection succeeds
|
self.isCollected = True # Mark as collected when collection succeeds
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
+6
-6
@@ -15,13 +15,13 @@ class ItemType(Enum):
|
|||||||
|
|
||||||
class ItemProperties:
|
class ItemProperties:
|
||||||
"""Manages item properties and availability"""
|
"""Manages item properties and availability"""
|
||||||
|
|
||||||
# Items that can appear in random drops
|
# Items that can appear in random drops
|
||||||
RANDOM_ELIGIBLE = {
|
RANDOM_ELIGIBLE = {
|
||||||
ItemType.HAND_OF_GLORY: "hand_of_glory",
|
ItemType.HAND_OF_GLORY: "hand_of_glory",
|
||||||
ItemType.JACK_O_LANTERN: "jack_o_lantern"
|
ItemType.JACK_O_LANTERN: "jack_o_lantern"
|
||||||
}
|
}
|
||||||
|
|
||||||
# All possible items (including special ones)
|
# All possible items (including special ones)
|
||||||
ALL_ITEMS = {
|
ALL_ITEMS = {
|
||||||
ItemType.GUTS: "guts",
|
ItemType.GUTS: "guts",
|
||||||
@@ -31,23 +31,23 @@ class ItemProperties:
|
|||||||
ItemType.CAULDRON: "cauldron",
|
ItemType.CAULDRON: "cauldron",
|
||||||
ItemType.WITCH_BROOM: "witch_broom"
|
ItemType.WITCH_BROOM: "witch_broom"
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_sound_name(item_type):
|
def get_sound_name(item_type):
|
||||||
"""Convert enum to sound/asset name"""
|
"""Convert enum to sound/asset name"""
|
||||||
return ItemProperties.ALL_ITEMS.get(item_type)
|
return ItemProperties.ALL_ITEMS.get(item_type)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_random_item():
|
def get_random_item():
|
||||||
"""Get a random item from eligible items"""
|
"""Get a random item from eligible items"""
|
||||||
item_type = random.choice(list(ItemProperties.RANDOM_ELIGIBLE.keys()))
|
item_type = random.choice(list(ItemProperties.RANDOM_ELIGIBLE.keys()))
|
||||||
return ItemProperties.get_sound_name(item_type)
|
return ItemProperties.get_sound_name(item_type)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_valid_item(item_name):
|
def is_valid_item(item_name):
|
||||||
"""Check if an item name is valid"""
|
"""Check if an item name is valid"""
|
||||||
return item_name in [v for v in ItemProperties.ALL_ITEMS.values()]
|
return item_name in [v for v in ItemProperties.ALL_ITEMS.values()]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_item_type(item_name):
|
def get_item_type(item_name):
|
||||||
"""Get ItemType enum from string name"""
|
"""Get ItemType enum from string name"""
|
||||||
|
|||||||
+16
-16
@@ -36,7 +36,7 @@ class Level:
|
|||||||
|
|
||||||
# Get footstep sound for this level, default to 'footstep' if not specified
|
# Get footstep sound for this level, default to 'footstep' if not specified
|
||||||
self.footstepSound = levelData.get("footstep_sound", "footstep")
|
self.footstepSound = levelData.get("footstep_sound", "footstep")
|
||||||
|
|
||||||
# Pass footstep sound to player
|
# Pass footstep sound to player
|
||||||
self.player.set_footstep_sound(self.footstepSound)
|
self.player.set_footstep_sound(self.footstepSound)
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ class Level:
|
|||||||
xPos = obj["x_range"]
|
xPos = obj["x_range"]
|
||||||
else:
|
else:
|
||||||
xPos = [obj["x"], obj["x"]] # Single position as range
|
xPos = [obj["x"], obj["x"]] # Single position as range
|
||||||
|
|
||||||
# Check if this is a catapult
|
# Check if this is a catapult
|
||||||
if obj.get("type") == "catapult":
|
if obj.get("type") == "catapult":
|
||||||
catapult = Catapult(
|
catapult = Catapult(
|
||||||
@@ -194,22 +194,22 @@ class Level:
|
|||||||
def update_audio(self):
|
def update_audio(self):
|
||||||
"""Update all audio and entity state."""
|
"""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
|
||||||
for obj in self.objects:
|
for obj in self.objects:
|
||||||
if not obj.isActive:
|
if not obj.isActive:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 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.zombieSpawnChance > 0 and
|
obj.zombieSpawnChance > 0 and
|
||||||
not obj.hasSpawned):
|
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.hasSpawned = True
|
obj.hasSpawned = True
|
||||||
|
|
||||||
roll = random.randint(1, 100)
|
roll = random.randint(1, 100)
|
||||||
if roll <= obj.zombieSpawnChance:
|
if roll <= obj.zombieSpawnChance:
|
||||||
zombie = Enemy(
|
zombie = Enemy(
|
||||||
@@ -224,7 +224,7 @@ class Level:
|
|||||||
)
|
)
|
||||||
self.enemies.append(zombie)
|
self.enemies.append(zombie)
|
||||||
speak("A zombie emerges from the grave!")
|
speak("A zombie emerges from the grave!")
|
||||||
|
|
||||||
# Handle object audio
|
# Handle object audio
|
||||||
if obj.channel is not None:
|
if obj.channel is not None:
|
||||||
obj.channel = obj_update(obj.channel, self.player.xPos, obj.xPos)
|
obj.channel = obj_update(obj.channel, self.player.xPos, obj.xPos)
|
||||||
@@ -233,12 +233,12 @@ class Level:
|
|||||||
obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos)
|
obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos)
|
||||||
else:
|
else:
|
||||||
obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos)
|
obj.channel = obj_play(self.sounds, obj.soundName, self.player.xPos, obj.xPos)
|
||||||
|
|
||||||
# Update enemies
|
# Update enemies
|
||||||
for enemy in self.enemies:
|
for enemy in self.enemies:
|
||||||
if not enemy.isActive:
|
if not enemy.isActive:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
enemy.update(currentTime, self.player)
|
enemy.update(currentTime, self.player)
|
||||||
|
|
||||||
# Only handle audio for non-vulnerability enemies
|
# Only handle audio for non-vulnerability enemies
|
||||||
@@ -247,7 +247,7 @@ class Level:
|
|||||||
enemy.channel = obj_play(self.sounds, enemy.enemyType, self.player.xPos, enemy.xPos)
|
enemy.channel = obj_play(self.sounds, enemy.enemyType, self.player.xPos, enemy.xPos)
|
||||||
if enemy.channel is not None:
|
if enemy.channel is not None:
|
||||||
enemy.channel = obj_update(enemy.channel, self.player.xPos, enemy.xPos)
|
enemy.channel = obj_update(enemy.channel, self.player.xPos, enemy.xPos)
|
||||||
|
|
||||||
# Update catapults
|
# Update catapults
|
||||||
for obj in self.objects:
|
for obj in self.objects:
|
||||||
if isinstance(obj, Catapult):
|
if isinstance(obj, Catapult):
|
||||||
@@ -272,7 +272,7 @@ class Level:
|
|||||||
if not item.isActive:
|
if not item.isActive:
|
||||||
speak(f"{item.soundName} got away!")
|
speak(f"{item.soundName} got away!")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check for item collection
|
# Check for item collection
|
||||||
if abs(item._currentX - self.player.xPos) < 1 and self.player.isJumping:
|
if abs(item._currentX - self.player.xPos) < 1 and self.player.isJumping:
|
||||||
play_sound(self.sounds[f'get_{item.soundName}'])
|
play_sound(self.sounds[f'get_{item.soundName}'])
|
||||||
@@ -302,7 +302,7 @@ class Level:
|
|||||||
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
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@@ -443,7 +443,7 @@ class Level:
|
|||||||
# Push player back a bit
|
# Push player back a bit
|
||||||
self.player.xPos -= 5
|
self.player.xPos -= 5
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Level complete
|
# Level complete
|
||||||
pygame.mixer.stop()
|
pygame.mixer.stop()
|
||||||
play_sound(self.sounds['end_of_level'])
|
play_sound(self.sounds['end_of_level'])
|
||||||
@@ -460,7 +460,7 @@ class Level:
|
|||||||
if not proj.update():
|
if not proj.update():
|
||||||
self.projectiles.remove(proj)
|
self.projectiles.remove(proj)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Check for enemy hits
|
# Check for enemy hits
|
||||||
for enemy in self.enemies:
|
for enemy in self.enemies:
|
||||||
if enemy.isActive and abs(proj.x - enemy.xPos) < 1:
|
if enemy.isActive and abs(proj.x - enemy.xPos) < 1:
|
||||||
@@ -471,14 +471,14 @@ class Level:
|
|||||||
if volume > 0: # Only play if within audible range
|
if volume > 0: # Only play if within audible range
|
||||||
obj_play(self.sounds, 'pumpkin_splat', self.player.xPos, proj.x, loop=False)
|
obj_play(self.sounds, 'pumpkin_splat', self.player.xPos, proj.x, loop=False)
|
||||||
break
|
break
|
||||||
|
|
||||||
def throw_projectile(self):
|
def throw_projectile(self):
|
||||||
"""Have player throw a projectile"""
|
"""Have player throw a projectile"""
|
||||||
proj_info = self.player.throw_projectile()
|
proj_info = self.player.throw_projectile()
|
||||||
if proj_info is None:
|
if proj_info is None:
|
||||||
speak("No jack o'lanterns to throw!")
|
speak("No jack o'lanterns to throw!")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.projectiles.append(Projectile(
|
self.projectiles.append(Projectile(
|
||||||
proj_info['type'],
|
proj_info['type'],
|
||||||
proj_info['start_x'],
|
proj_info['start_x'],
|
||||||
|
|||||||
+3
-3
@@ -17,17 +17,17 @@ class Object:
|
|||||||
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
|
||||||
self.collectedPositions = set()
|
self.collectedPositions = set()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def xPos(self):
|
def xPos(self):
|
||||||
"""Return center of range for audio positioning"""
|
"""Return center of range for audio positioning"""
|
||||||
return (self.xRange[0] + self.xRange[1]) / 2
|
return (self.xRange[0] + self.xRange[1]) / 2
|
||||||
|
|
||||||
def is_in_range(self, x):
|
def is_in_range(self, x):
|
||||||
"""Check if a given x position is within this object's range"""
|
"""Check if a given x position is within this object's range"""
|
||||||
tolerance = 0.5 # Half a unit tolerance
|
tolerance = 0.5 # Half a unit tolerance
|
||||||
return (self.xRange[0] - tolerance) <= x <= (self.xRange[1] + tolerance)
|
return (self.xRange[0] - tolerance) <= x <= (self.xRange[1] + tolerance)
|
||||||
|
|
||||||
def collect_at_position(self, x):
|
def collect_at_position(self, x):
|
||||||
"""Mark a specific position in the range as collected"""
|
"""Mark a specific position in the range as collected"""
|
||||||
self.collectedPositions.add(x)
|
self.collectedPositions.add(x)
|
||||||
|
|||||||
+28
-28
@@ -20,7 +20,7 @@ class Player:
|
|||||||
self.isRunning = False
|
self.isRunning = False
|
||||||
self.runMultiplier = 1.5 # Same multiplier as jumping
|
self.runMultiplier = 1.5 # Same multiplier as jumping
|
||||||
self.facingRight = True
|
self.facingRight = True
|
||||||
|
|
||||||
# Stats and tracking
|
# Stats and tracking
|
||||||
self._health = 10
|
self._health = 10
|
||||||
self._maxHealth = 10
|
self._maxHealth = 10
|
||||||
@@ -39,25 +39,25 @@ class Player:
|
|||||||
self.lastStepTime = 0
|
self.lastStepTime = 0
|
||||||
self.isRunning = False
|
self.isRunning = False
|
||||||
self.runMultiplier = 1.5
|
self.runMultiplier = 1.5
|
||||||
|
|
||||||
# Inventory system
|
# Inventory system
|
||||||
self.inventory = []
|
self.inventory = []
|
||||||
self.collectedItems = []
|
self.collectedItems = []
|
||||||
self._coins = 0
|
self._coins = 0
|
||||||
self._jack_o_lantern_count = 0
|
self._jack_o_lantern_count = 0
|
||||||
self.shinBoneCount = 0
|
self.shinBoneCount = 0
|
||||||
|
|
||||||
# Combat related attributes
|
# Combat related attributes
|
||||||
self.weapons = []
|
self.weapons = []
|
||||||
self.currentWeapon = None
|
self.currentWeapon = None
|
||||||
self.isAttacking = False
|
self.isAttacking = False
|
||||||
self.lastAttackTime = 0
|
self.lastAttackTime = 0
|
||||||
|
|
||||||
# Power-up states
|
# Power-up states
|
||||||
self.isInvincible = False
|
self.isInvincible = False
|
||||||
self.invincibilityStartTime = 0
|
self.invincibilityStartTime = 0
|
||||||
self.invincibilityDuration = 10000 # 10 seconds of invincibility
|
self.invincibilityDuration = 10000 # 10 seconds of invincibility
|
||||||
|
|
||||||
# Initialize starting weapon (rusty shovel)
|
# Initialize starting weapon (rusty shovel)
|
||||||
self.add_weapon(Weapon(
|
self.add_weapon(Weapon(
|
||||||
name="rusty_shovel",
|
name="rusty_shovel",
|
||||||
@@ -97,64 +97,64 @@ class Player:
|
|||||||
if self.currentWeapon:
|
if self.currentWeapon:
|
||||||
self.currentWeapon.attackDuration *= 0.5 # Restore attack speed
|
self.currentWeapon.attackDuration *= 0.5 # Restore attack speed
|
||||||
del self.webPenaltyEndTime
|
del self.webPenaltyEndTime
|
||||||
|
|
||||||
# Check invincibility status
|
# Check invincibility status
|
||||||
if self.isInvincible:
|
if self.isInvincible:
|
||||||
remaining_time = (self.invincibilityStartTime + self.invincibilityDuration - currentTime) / 1000 # Convert to seconds
|
remaining_time = (self.invincibilityStartTime + self.invincibilityDuration - currentTime) / 1000 # Convert to seconds
|
||||||
|
|
||||||
# Handle countdown sounds
|
# Handle countdown sounds
|
||||||
if not hasattr(self, '_last_countdown'):
|
if not hasattr(self, '_last_countdown'):
|
||||||
self._last_countdown = 4 # Start counting from 4 to catch 3,2,1
|
self._last_countdown = 4 # Start counting from 4 to catch 3,2,1
|
||||||
|
|
||||||
current_second = int(remaining_time)
|
current_second = int(remaining_time)
|
||||||
if current_second < self._last_countdown and current_second <= 3 and current_second > 0:
|
if current_second < self._last_countdown and current_second <= 3 and current_second > 0:
|
||||||
play_sound(self.sounds['end_of_invincibility_warning'])
|
play_sound(self.sounds['end_of_invincibility_warning'])
|
||||||
self._last_countdown = current_second
|
self._last_countdown = current_second
|
||||||
|
|
||||||
# Check if invincibility has expired
|
# Check if invincibility has expired
|
||||||
if currentTime - self.invincibilityStartTime >= self.invincibilityDuration:
|
if currentTime - self.invincibilityStartTime >= self.invincibilityDuration:
|
||||||
self.isInvincible = False
|
self.isInvincible = False
|
||||||
speak("Invincibility wore off!")
|
speak("Invincibility wore off!")
|
||||||
del self._last_countdown # Clean up countdown tracker
|
del self._last_countdown # Clean up countdown tracker
|
||||||
|
|
||||||
def start_invincibility(self):
|
def start_invincibility(self):
|
||||||
"""Activate invincibility from Hand of Glory"""
|
"""Activate invincibility from Hand of Glory"""
|
||||||
self.isInvincible = True
|
self.isInvincible = True
|
||||||
self.invincibilityStartTime = pygame.time.get_ticks()
|
self.invincibilityStartTime = pygame.time.get_ticks()
|
||||||
if hasattr(self, '_last_countdown'):
|
if hasattr(self, '_last_countdown'):
|
||||||
del self._last_countdown # Reset countdown if it exists
|
del self._last_countdown # Reset countdown if it exists
|
||||||
|
|
||||||
def extra_life(self):
|
def extra_life(self):
|
||||||
"""Increment lives by 1"""
|
"""Increment lives by 1"""
|
||||||
self._lives += 1
|
self._lives += 1
|
||||||
|
|
||||||
def get_jack_o_lanterns(self):
|
def get_jack_o_lanterns(self):
|
||||||
"""Get number of jack o'lanterns"""
|
"""Get number of jack o'lanterns"""
|
||||||
return self._jack_o_lantern_count
|
return self._jack_o_lantern_count
|
||||||
|
|
||||||
def add_jack_o_lantern(self):
|
def add_jack_o_lantern(self):
|
||||||
"""Add a jack o'lantern"""
|
"""Add a jack o'lantern"""
|
||||||
self._jack_o_lantern_count += 1
|
self._jack_o_lantern_count += 1
|
||||||
|
|
||||||
def add_guts(self):
|
def add_guts(self):
|
||||||
"""Apply guts, increase max_health by 2 if less than 20 else restore health"""
|
"""Apply guts, increase max_health by 2 if less than 20 else restore health"""
|
||||||
if self._maxHealth < 20:
|
if self._maxHealth < 20:
|
||||||
self._maxHealth += 2
|
self._maxHealth += 2
|
||||||
else:
|
else:
|
||||||
self._health = self._maxHealth
|
self._health = self._maxHealth
|
||||||
|
|
||||||
def throw_projectile(self):
|
def throw_projectile(self):
|
||||||
"""Throw a jack o'lantern if we have any"""
|
"""Throw a jack o'lantern if we have any"""
|
||||||
if self.get_jack_o_lanterns() <= 0:
|
if self.get_jack_o_lanterns() <= 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self._jack_o_lantern_count -= 1
|
self._jack_o_lantern_count -= 1
|
||||||
return {
|
return {
|
||||||
'type': 'jack_o_lantern',
|
'type': 'jack_o_lantern',
|
||||||
'start_x': self.xPos,
|
'start_x': self.xPos,
|
||||||
'direction': 1 if self.facingRight else -1
|
'direction': 1 if self.facingRight else -1
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_step_distance(self):
|
def get_step_distance(self):
|
||||||
"""Get step distance based on current speed"""
|
"""Get step distance based on current speed"""
|
||||||
if self.isRunning or self.isJumping:
|
if self.isRunning or self.isJumping:
|
||||||
@@ -170,11 +170,11 @@ class Player:
|
|||||||
def get_health(self):
|
def get_health(self):
|
||||||
"""Get current health"""
|
"""Get current health"""
|
||||||
return self._health
|
return self._health
|
||||||
|
|
||||||
def get_max_health(self):
|
def get_max_health(self):
|
||||||
"""Get current max health"""
|
"""Get current max health"""
|
||||||
return self._maxHealth
|
return self._maxHealth
|
||||||
|
|
||||||
def restore_health(self):
|
def restore_health(self):
|
||||||
"""Restore health to maximum"""
|
"""Restore health to maximum"""
|
||||||
self._health = self._maxHealth
|
self._health = self._maxHealth
|
||||||
@@ -192,13 +192,13 @@ class Player:
|
|||||||
def set_health(self, value):
|
def set_health(self, value):
|
||||||
"""Set health and handle death if needed."""
|
"""Set health and handle death if needed."""
|
||||||
old_health = self._health
|
old_health = self._health
|
||||||
|
|
||||||
# Oops, allow healing while invincible.
|
# Oops, allow healing while invincible.
|
||||||
if self.isInvincible and value < old_health:
|
if self.isInvincible and value < old_health:
|
||||||
return
|
return
|
||||||
|
|
||||||
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 and old_health > 0:
|
if self._health == 0 and old_health > 0:
|
||||||
self._lives -= 1
|
self._lives -= 1
|
||||||
# Stop all current sounds before playing death sound
|
# Stop all current sounds before playing death sound
|
||||||
@@ -209,7 +209,7 @@ class Player:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
cut_scene(self.sounds, 'lose_a_life')
|
cut_scene(self.sounds, 'lose_a_life')
|
||||||
|
|
||||||
def set_max_health(self, value):
|
def set_max_health(self, value):
|
||||||
"""Set max health"""
|
"""Set max health"""
|
||||||
self._maxHealth = value
|
self._maxHealth = value
|
||||||
@@ -217,22 +217,22 @@ class Player:
|
|||||||
def get_coins(self):
|
def get_coins(self):
|
||||||
"""Get remaining coins"""
|
"""Get remaining coins"""
|
||||||
return self._coins
|
return self._coins
|
||||||
|
|
||||||
def get_lives(self):
|
def get_lives(self):
|
||||||
"""Get remaining lives"""
|
"""Get remaining lives"""
|
||||||
return self._lives
|
return self._lives
|
||||||
|
|
||||||
def add_weapon(self, weapon):
|
def add_weapon(self, weapon):
|
||||||
"""Add a new weapon to inventory and equip if first weapon"""
|
"""Add a new weapon to inventory and equip if first weapon"""
|
||||||
self.weapons.append(weapon)
|
self.weapons.append(weapon)
|
||||||
if len(self.weapons) == 1: # If this is our first weapon, equip it
|
if len(self.weapons) == 1: # If this is our first weapon, equip it
|
||||||
self.equip_weapon(weapon)
|
self.equip_weapon(weapon)
|
||||||
|
|
||||||
def equip_weapon(self, weapon):
|
def equip_weapon(self, weapon):
|
||||||
"""Equip a specific weapon"""
|
"""Equip a specific weapon"""
|
||||||
if weapon in self.weapons:
|
if weapon in self.weapons:
|
||||||
self.currentWeapon = weapon
|
self.currentWeapon = weapon
|
||||||
|
|
||||||
def add_item(self, item):
|
def add_item(self, item):
|
||||||
"""Add an item to inventory"""
|
"""Add an item to inventory"""
|
||||||
self.inventory.append(item)
|
self.inventory.append(item)
|
||||||
@@ -245,7 +245,7 @@ class Player:
|
|||||||
self.lastAttackTime = currentTime
|
self.lastAttackTime = currentTime
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_attack_range(self, currentTime):
|
def get_attack_range(self, currentTime):
|
||||||
"""Get the current attack's range based on position and facing direction"""
|
"""Get the current attack's range based on position and facing direction"""
|
||||||
if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime):
|
if not self.currentWeapon or not self.currentWeapon.is_attack_active(currentTime):
|
||||||
|
|||||||
+3
-3
@@ -29,7 +29,7 @@ class PowerUp(Object):
|
|||||||
|
|
||||||
# Update position
|
# Update position
|
||||||
new_x = self._currentX + self.direction * self.speed
|
new_x = self._currentX + self.direction * self.speed
|
||||||
|
|
||||||
# Check boundaries and bounce if needed
|
# Check boundaries and bounce if needed
|
||||||
if new_x < self.left_boundary:
|
if new_x < self.left_boundary:
|
||||||
self._currentX = self.left_boundary
|
self._currentX = self.left_boundary
|
||||||
@@ -55,7 +55,7 @@ class PowerUp(Object):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def apply_effect(self, player):
|
def apply_effect(self, player):
|
||||||
"""Apply the item's effect when collected"""
|
"""Apply the item's effect when collected"""
|
||||||
if self.item_type == 'hand_of_glory':
|
if self.item_type == 'hand_of_glory':
|
||||||
@@ -99,7 +99,7 @@ class PowerUp(Object):
|
|||||||
# Tell level to spawn a spider
|
# Tell level to spawn a spider
|
||||||
if hasattr(self, 'level'):
|
if hasattr(self, 'level'):
|
||||||
self.level.spawn_spider(self.xPos, self.yPos)
|
self.level.spawn_spider(self.xPos, self.yPos)
|
||||||
|
|
||||||
# Stop movement sound when collected
|
# Stop movement sound when collected
|
||||||
if self.channel:
|
if self.channel:
|
||||||
self.channel.stop()
|
self.channel.stop()
|
||||||
|
|||||||
+5
-5
@@ -10,21 +10,21 @@ class Projectile:
|
|||||||
self.damage = 5 # All projectiles do same damage for now
|
self.damage = 5 # All projectiles do same damage for now
|
||||||
self.range = 12 # Maximum travel distance in tiles
|
self.range = 12 # Maximum travel distance in tiles
|
||||||
self.start_x = start_x
|
self.start_x = start_x
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update projectile position and check if it should still exist"""
|
"""Update projectile position and check if it should still exist"""
|
||||||
if not self.isActive:
|
if not self.isActive:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.x += self.direction * self.speed
|
self.x += self.direction * self.speed
|
||||||
|
|
||||||
# Check if projectile has gone too far
|
# Check if projectile has gone too far
|
||||||
if abs(self.x - self.start_x) > self.range:
|
if abs(self.x - self.start_x) > self.range:
|
||||||
self.isActive = False
|
self.isActive = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def hit_enemy(self, enemy):
|
def hit_enemy(self, enemy):
|
||||||
"""Handle hitting an enemy"""
|
"""Handle hitting an enemy"""
|
||||||
enemy.take_damage(self.damage)
|
enemy.take_damage(self.damage)
|
||||||
|
|||||||
+11
-11
@@ -7,7 +7,7 @@ from src.object import Object
|
|||||||
|
|
||||||
class SkullStorm(Object):
|
class SkullStorm(Object):
|
||||||
"""Handles falling skulls within a specified range."""
|
"""Handles falling skulls within a specified range."""
|
||||||
|
|
||||||
def __init__(self, xRange, y, sounds, damage, maxSkulls=3, minFreq=2, maxFreq=5):
|
def __init__(self, xRange, y, sounds, damage, maxSkulls=3, minFreq=2, maxFreq=5):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
xRange,
|
xRange,
|
||||||
@@ -22,7 +22,7 @@ class SkullStorm(Object):
|
|||||||
self.maxSkulls = maxSkulls
|
self.maxSkulls = maxSkulls
|
||||||
self.minFreq = minFreq * 1000 # Convert to milliseconds
|
self.minFreq = minFreq * 1000 # Convert to milliseconds
|
||||||
self.maxFreq = maxFreq * 1000
|
self.maxFreq = maxFreq * 1000
|
||||||
|
|
||||||
self.activeSkulls = [] # List of currently falling skulls
|
self.activeSkulls = [] # List of currently falling skulls
|
||||||
self.lastSkullTime = 0
|
self.lastSkullTime = 0
|
||||||
self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq)
|
self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq)
|
||||||
@@ -32,7 +32,7 @@ class SkullStorm(Object):
|
|||||||
"""Update all active skulls and potentially spawn new ones."""
|
"""Update all active skulls and potentially spawn new ones."""
|
||||||
if not self.isActive:
|
if not self.isActive:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if player has entered range
|
# Check if player has entered range
|
||||||
inRange = self.xRange[0] <= player.xPos <= self.xRange[1]
|
inRange = self.xRange[0] <= player.xPos <= self.xRange[1]
|
||||||
if inRange and not self.playerInRange:
|
if inRange and not self.playerInRange:
|
||||||
@@ -43,16 +43,16 @@ class SkullStorm(Object):
|
|||||||
# Player just left range
|
# Player just left range
|
||||||
self.playerInRange = False
|
self.playerInRange = False
|
||||||
speak("Skull storm ended.")
|
speak("Skull storm ended.")
|
||||||
|
|
||||||
# Clear any active skulls when player leaves the range
|
# Clear any active skulls when player leaves the range
|
||||||
for skull in self.activeSkulls[:]:
|
for skull in self.activeSkulls[:]:
|
||||||
if skull['channel']:
|
if skull['channel']:
|
||||||
obj_stop(skull['channel'])
|
obj_stop(skull['channel'])
|
||||||
self.activeSkulls = [] # Reset the list of active skulls
|
self.activeSkulls = [] # Reset the list of active skulls
|
||||||
|
|
||||||
if not inRange:
|
if not inRange:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update existing skulls
|
# Update existing skulls
|
||||||
for skull in self.activeSkulls[:]: # Copy list to allow removal
|
for skull in self.activeSkulls[:]: # Copy list to allow removal
|
||||||
if currentTime >= skull['land_time']:
|
if currentTime >= skull['land_time']:
|
||||||
@@ -64,7 +64,7 @@ class SkullStorm(Object):
|
|||||||
timeElapsed = currentTime - skull['start_time']
|
timeElapsed = currentTime - skull['start_time']
|
||||||
fallProgress = timeElapsed / skull['fall_duration']
|
fallProgress = timeElapsed / skull['fall_duration']
|
||||||
currentY = self.yPos * (1 - fallProgress)
|
currentY = self.yPos * (1 - fallProgress)
|
||||||
|
|
||||||
skull['channel'] = play_random_falling(
|
skull['channel'] = play_random_falling(
|
||||||
self.sounds,
|
self.sounds,
|
||||||
'falling_skull',
|
'falling_skull',
|
||||||
@@ -74,21 +74,21 @@ class SkullStorm(Object):
|
|||||||
currentY,
|
currentY,
|
||||||
existingChannel=skull['channel']
|
existingChannel=skull['channel']
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if we should spawn a new skull
|
# Check if we should spawn a new skull
|
||||||
if (len(self.activeSkulls) < self.maxSkulls and
|
if (len(self.activeSkulls) < self.maxSkulls and
|
||||||
currentTime - self.lastSkullTime >= self.nextSkullDelay):
|
currentTime - self.lastSkullTime >= self.nextSkullDelay):
|
||||||
self.spawn_skull(currentTime)
|
self.spawn_skull(currentTime)
|
||||||
|
|
||||||
def spawn_skull(self, currentTime):
|
def spawn_skull(self, currentTime):
|
||||||
"""Spawn a new falling skull at a random position within range."""
|
"""Spawn a new falling skull at a random position within range."""
|
||||||
# Reset timing
|
# Reset timing
|
||||||
self.lastSkullTime = currentTime
|
self.lastSkullTime = currentTime
|
||||||
self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq)
|
self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq)
|
||||||
|
|
||||||
# Calculate fall duration based on height (higher = longer fall)
|
# Calculate fall duration based on height (higher = longer fall)
|
||||||
fallDuration = self.yPos * 100 # 100ms per unit of height
|
fallDuration = self.yPos * 100 # 100ms per unit of height
|
||||||
|
|
||||||
# Create new skull
|
# Create new skull
|
||||||
skull = {
|
skull = {
|
||||||
'x': random.uniform(self.xRange[0], self.xRange[1]),
|
'x': random.uniform(self.xRange[0], self.xRange[1]),
|
||||||
|
|||||||
+5
-5
@@ -10,10 +10,10 @@ class StatTracker:
|
|||||||
'Items collected': 0,
|
'Items collected': 0,
|
||||||
'Total time': 0
|
'Total time': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create level stats from total (shallow copy is fine here)
|
# Create level stats from total (shallow copy is fine here)
|
||||||
self.level = self.total.copy()
|
self.level = self.total.copy()
|
||||||
|
|
||||||
self.total['levelsCompleted'] = 0
|
self.total['levelsCompleted'] = 0
|
||||||
|
|
||||||
def reset_level(self):
|
def reset_level(self):
|
||||||
@@ -27,18 +27,18 @@ class StatTracker:
|
|||||||
self.level[key] = []
|
self.level[key] = []
|
||||||
elif self.level[key] is None:
|
elif self.level[key] is None:
|
||||||
self.level[key] = None
|
self.level[key] = None
|
||||||
|
|
||||||
def update_stat(self, statName, value=1, levelOnly=False):
|
def update_stat(self, statName, value=1, levelOnly=False):
|
||||||
"""Update a stat in both level and total (unless levelOnly is True)"""
|
"""Update a stat in both level and total (unless levelOnly is True)"""
|
||||||
if statName in self.level:
|
if statName in self.level:
|
||||||
self.level[statName] += value
|
self.level[statName] += value
|
||||||
if not levelOnly and statName in self.total:
|
if not levelOnly and statName in self.total:
|
||||||
self.total[statName] += value
|
self.total[statName] += value
|
||||||
|
|
||||||
def get_level_stat(self, statName):
|
def get_level_stat(self, statName):
|
||||||
"""Get a level stat"""
|
"""Get a level stat"""
|
||||||
return self.level.get(statName, 0)
|
return self.level.get(statName, 0)
|
||||||
|
|
||||||
def get_total_stat(self, statName):
|
def get_total_stat(self, statName):
|
||||||
"""Get a total stat"""
|
"""Get a total stat"""
|
||||||
return self.total.get(statName, 0)
|
return self.total.get(statName, 0)
|
||||||
|
|||||||
+3
-3
@@ -11,7 +11,7 @@ class Weapon:
|
|||||||
self.attackDuration = attackDuration # Milliseconds the attack is active
|
self.attackDuration = attackDuration # Milliseconds the attack is active
|
||||||
self.lastAttackTime = 0
|
self.lastAttackTime = 0
|
||||||
self.hitEnemies = set()
|
self.hitEnemies = set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_nunchucks(cls):
|
def create_nunchucks(cls):
|
||||||
"""Create the nunchucks weapon"""
|
"""Create the nunchucks weapon"""
|
||||||
@@ -41,7 +41,7 @@ class Weapon:
|
|||||||
def can_attack(self, currentTime):
|
def can_attack(self, currentTime):
|
||||||
"""Check if enough time has passed since last attack"""
|
"""Check if enough time has passed since last attack"""
|
||||||
return currentTime - self.lastAttackTime >= self.cooldown
|
return currentTime - self.lastAttackTime >= self.cooldown
|
||||||
|
|
||||||
def get_attack_range(self, playerPos, facingRight):
|
def get_attack_range(self, playerPos, facingRight):
|
||||||
"""Calculate the area that this attack would hit"""
|
"""Calculate the area that this attack would hit"""
|
||||||
if facingRight:
|
if facingRight:
|
||||||
@@ -56,7 +56,7 @@ class Weapon:
|
|||||||
self.hitEnemies.clear() # Clear hit enemies for new attack
|
self.hitEnemies.clear() # Clear hit enemies for new attack
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_attack_active(self, currentTime):
|
def is_attack_active(self, currentTime):
|
||||||
"""Check if the attack is still in its active frames"""
|
"""Check if the attack is still in its active frames"""
|
||||||
timeSinceAttack = currentTime - self.lastAttackTime
|
timeSinceAttack = currentTime - self.lastAttackTime
|
||||||
|
|||||||
+23
-23
@@ -22,14 +22,14 @@ class WickedQuest:
|
|||||||
self.player = None
|
self.player = None
|
||||||
self.currentGame = None
|
self.currentGame = None
|
||||||
self.runLock = False # Toggle behavior of the run keys
|
self.runLock = False # Toggle behavior of the run keys
|
||||||
|
|
||||||
def load_level(self, levelNumber):
|
def load_level(self, levelNumber):
|
||||||
"""Load a level from its JSON file."""
|
"""Load a level from its JSON file."""
|
||||||
levelFile = get_level_path(self.currentGame, levelNumber)
|
levelFile = get_level_path(self.currentGame, levelNumber)
|
||||||
try:
|
try:
|
||||||
with open(levelFile, 'r') as f:
|
with open(levelFile, 'r') as f:
|
||||||
levelData = json.load(f)
|
levelData = json.load(f)
|
||||||
|
|
||||||
# Create player if this is the first level
|
# Create player if this is the first level
|
||||||
if self.player is None:
|
if self.player is None:
|
||||||
self.player = Player(levelData["player_start"]["x"],
|
self.player = Player(levelData["player_start"]["x"],
|
||||||
@@ -51,11 +51,11 @@ class WickedQuest:
|
|||||||
self.player.moveSpeed *= 2 # Restore normal speed
|
self.player.moveSpeed *= 2 # Restore normal speed
|
||||||
if self.player.currentWeapon:
|
if self.player.currentWeapon:
|
||||||
self.player.currentWeapon.attackDuration *= 0.5 # Restore normal attack speed
|
self.player.currentWeapon.attackDuration *= 0.5 # Restore normal attack speed
|
||||||
|
|
||||||
# Pass existing player to new level
|
# Pass existing player to new level
|
||||||
pygame.event.clear()
|
pygame.event.clear()
|
||||||
self.currentLevel = Level(levelData, self.sounds, self.player)
|
self.currentLevel = Level(levelData, self.sounds, self.player)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return False
|
return False
|
||||||
@@ -63,14 +63,14 @@ class WickedQuest:
|
|||||||
def validate_levels(self):
|
def validate_levels(self):
|
||||||
"""Check if level files have valid JSON."""
|
"""Check if level files have valid JSON."""
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
# Check levels from 1 until no more files are found
|
# Check levels from 1 until no more files are found
|
||||||
levelNumber = 1
|
levelNumber = 1
|
||||||
while True:
|
while True:
|
||||||
levelPath = get_level_path(self.currentGame, levelNumber)
|
levelPath = get_level_path(self.currentGame, levelNumber)
|
||||||
if not os.path.exists(levelPath):
|
if not os.path.exists(levelPath):
|
||||||
break
|
break
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(levelPath, 'r') as f:
|
with open(levelPath, 'r') as f:
|
||||||
# This will raise an exception if JSON is invalid
|
# This will raise an exception if JSON is invalid
|
||||||
@@ -79,9 +79,9 @@ class WickedQuest:
|
|||||||
errors.append(f"Level {levelNumber}: Invalid JSON format - {str(e)}")
|
errors.append(f"Level {levelNumber}: Invalid JSON format - {str(e)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append(f"Level {levelNumber}: Error reading file - {str(e)}")
|
errors.append(f"Level {levelNumber}: Error reading file - {str(e)}")
|
||||||
|
|
||||||
levelNumber += 1
|
levelNumber += 1
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
def handle_input(self):
|
def handle_input(self):
|
||||||
@@ -89,7 +89,7 @@ class WickedQuest:
|
|||||||
keys = pygame.key.get_pressed()
|
keys = pygame.key.get_pressed()
|
||||||
player = self.currentLevel.player
|
player = self.currentLevel.player
|
||||||
currentTime = pygame.time.get_ticks()
|
currentTime = pygame.time.get_ticks()
|
||||||
|
|
||||||
# Update running and ducking states
|
# Update running and ducking states
|
||||||
if (keys[pygame.K_s] or keys[pygame.K_DOWN]) and not player.isDucking:
|
if (keys[pygame.K_s] or keys[pygame.K_DOWN]) and not player.isDucking:
|
||||||
player.duck()
|
player.duck()
|
||||||
@@ -106,7 +106,7 @@ class WickedQuest:
|
|||||||
|
|
||||||
# Track movement distance for this frame
|
# Track movement distance for this frame
|
||||||
movementDistance = 0
|
movementDistance = 0
|
||||||
|
|
||||||
# Horizontal movement
|
# Horizontal movement
|
||||||
if keys[pygame.K_a] or keys[pygame.K_LEFT]: # Left
|
if keys[pygame.K_a] or keys[pygame.K_LEFT]: # Left
|
||||||
movementDistance = currentSpeed
|
movementDistance = currentSpeed
|
||||||
@@ -116,7 +116,7 @@ class WickedQuest:
|
|||||||
movementDistance = currentSpeed
|
movementDistance = currentSpeed
|
||||||
player.xPos += currentSpeed
|
player.xPos += currentSpeed
|
||||||
player.facingRight = True
|
player.facingRight = True
|
||||||
|
|
||||||
# Handle footsteps
|
# Handle footsteps
|
||||||
if movementDistance > 0 and not player.isJumping:
|
if movementDistance > 0 and not player.isJumping:
|
||||||
player.distanceSinceLastStep += movementDistance
|
player.distanceSinceLastStep += movementDistance
|
||||||
@@ -147,13 +147,13 @@ class WickedQuest:
|
|||||||
# Handle attack with either CTRL key
|
# Handle attack with either CTRL key
|
||||||
if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime):
|
if (keys[pygame.K_LCTRL] or keys[pygame.K_RCTRL]) and player.start_attack(currentTime):
|
||||||
play_sound(self.sounds[player.currentWeapon.attackSound])
|
play_sound(self.sounds[player.currentWeapon.attackSound])
|
||||||
|
|
||||||
# Handle jumping
|
# Handle jumping
|
||||||
if (keys[pygame.K_w] or keys[pygame.K_UP]) and not player.isJumping:
|
if (keys[pygame.K_w] or keys[pygame.K_UP]) and not player.isJumping:
|
||||||
player.isJumping = True
|
player.isJumping = True
|
||||||
player.jumpStartTime = currentTime
|
player.jumpStartTime = currentTime
|
||||||
play_sound(self.sounds['jump'])
|
play_sound(self.sounds['jump'])
|
||||||
|
|
||||||
# Check if jump should end
|
# Check if jump should end
|
||||||
if player.isJumping and currentTime - player.jumpStartTime >= player.jumpDuration:
|
if player.isJumping and currentTime - player.jumpStartTime >= player.jumpDuration:
|
||||||
player.isJumping = False
|
player.isJumping = False
|
||||||
@@ -167,12 +167,12 @@ class WickedQuest:
|
|||||||
# Convert time from milliseconds to minutes:seconds
|
# Convert time from milliseconds to minutes:seconds
|
||||||
minutes = timeTaken // 60000
|
minutes = timeTaken // 60000
|
||||||
seconds = (timeTaken % 60000) // 1000
|
seconds = (timeTaken % 60000) // 1000
|
||||||
|
|
||||||
# Update time in stats
|
# Update time in stats
|
||||||
self.currentLevel.player.stats.update_stat('Total time', timeTaken, levelOnly=True)
|
self.currentLevel.player.stats.update_stat('Total time', timeTaken, levelOnly=True)
|
||||||
|
|
||||||
report = [f"Time taken: {minutes} minutes and {seconds} seconds"]
|
report = [f"Time taken: {minutes} minutes and {seconds} seconds"]
|
||||||
|
|
||||||
# Add all level stats
|
# Add all level stats
|
||||||
for key in self.currentLevel.player.stats.level:
|
for key in self.currentLevel.player.stats.level:
|
||||||
if key != 'Total time': # Skip time since we already displayed it
|
if key != 'Total time': # Skip time since we already displayed it
|
||||||
@@ -195,10 +195,10 @@ class WickedQuest:
|
|||||||
"""Display game over screen with statistics."""
|
"""Display game over screen with statistics."""
|
||||||
minutes = timeTaken // 60000
|
minutes = timeTaken // 60000
|
||||||
seconds = (timeTaken % 60000) // 1000
|
seconds = (timeTaken % 60000) // 1000
|
||||||
|
|
||||||
report = ["Game Over!"]
|
report = ["Game Over!"]
|
||||||
report.append(f"Time taken: {minutes} minutes and {seconds} seconds")
|
report.append(f"Time taken: {minutes} minutes and {seconds} seconds")
|
||||||
|
|
||||||
# Add all total stats
|
# Add all total stats
|
||||||
for key in self.currentLevel.player.stats.total:
|
for key in self.currentLevel.player.stats.total:
|
||||||
if key not in ['Total time', 'levelsCompleted']: # Skip these
|
if key not in ['Total time', 'levelsCompleted']: # Skip these
|
||||||
@@ -222,14 +222,14 @@ class WickedQuest:
|
|||||||
while True:
|
while True:
|
||||||
currentTime = pygame.time.get_ticks()
|
currentTime = pygame.time.get_ticks()
|
||||||
pygame.event.pump()
|
pygame.event.pump()
|
||||||
|
|
||||||
# Game volume controls
|
# Game volume controls
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
if event.type == pygame.KEYDOWN:
|
if event.type == pygame.KEYDOWN:
|
||||||
# Check for Alt modifier
|
# Check for Alt modifier
|
||||||
mods = pygame.key.get_mods()
|
mods = pygame.key.get_mods()
|
||||||
altPressed = mods & pygame.KMOD_ALT
|
altPressed = mods & pygame.KMOD_ALT
|
||||||
|
|
||||||
if event.key == pygame.K_ESCAPE:
|
if event.key == pygame.K_ESCAPE:
|
||||||
try:
|
try:
|
||||||
pygame.mixer.music.stop()
|
pygame.mixer.music.stop()
|
||||||
@@ -260,11 +260,11 @@ class WickedQuest:
|
|||||||
self.currentLevel.player.update(currentTime)
|
self.currentLevel.player.update(currentTime)
|
||||||
self.handle_input()
|
self.handle_input()
|
||||||
self.currentLevel.update_audio()
|
self.currentLevel.update_audio()
|
||||||
|
|
||||||
# Handle combat and projectiles
|
# Handle combat and projectiles
|
||||||
self.currentLevel.handle_combat(currentTime)
|
self.currentLevel.handle_combat(currentTime)
|
||||||
self.currentLevel.handle_projectiles(currentTime)
|
self.currentLevel.handle_projectiles(currentTime)
|
||||||
|
|
||||||
# Check for death first
|
# Check for death first
|
||||||
if self.currentLevel.player.get_health() <= 0:
|
if self.currentLevel.player.get_health() <= 0:
|
||||||
if self.currentLevel.player.get_lives() <= 0:
|
if self.currentLevel.player.get_lives() <= 0:
|
||||||
@@ -307,7 +307,7 @@ class WickedQuest:
|
|||||||
|
|
||||||
self.display_game_over(totalTime)
|
self.display_game_over(totalTime)
|
||||||
return
|
return
|
||||||
|
|
||||||
clock.tick(60) # 60 FPS
|
clock.tick(60) # 60 FPS
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@@ -345,7 +345,7 @@ class WickedQuest:
|
|||||||
for i, entry in enumerate(scores, 1):
|
for i, entry in enumerate(scores, 1):
|
||||||
scoreStr = f"{i}. {entry['name']}: {entry['score']}"
|
scoreStr = f"{i}. {entry['name']}: {entry['score']}"
|
||||||
lines.append(scoreStr)
|
lines.append(scoreStr)
|
||||||
|
|
||||||
pygame.event.clear()
|
pygame.event.clear()
|
||||||
display_text(lines)
|
display_text(lines)
|
||||||
elif choice == "learn_sounds":
|
elif choice == "learn_sounds":
|
||||||
|
|||||||
Reference in New Issue
Block a user