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