Code cleanup, added more functionality. Floating coffins that spawn items, graves can spawn zombies, etc.
This commit is contained in:
@@ -28,7 +28,8 @@
|
|||||||
"y": 0,
|
"y": 0,
|
||||||
"hazard": true,
|
"hazard": true,
|
||||||
"sound": "grave",
|
"sound": "grave",
|
||||||
"static": true
|
"static": true,
|
||||||
|
"zombie_spawn_chance": 100
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"boundaries": {
|
"boundaries": {
|
||||||
|
40
src/coffin.py
Normal file
40
src/coffin.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from libstormgames import *
|
||||||
|
from src.object import Object
|
||||||
|
from src.powerup import PowerUp
|
||||||
|
import random
|
||||||
|
|
||||||
|
class CoffinObject(Object):
|
||||||
|
def __init__(self, x, y, sounds):
|
||||||
|
super().__init__(
|
||||||
|
x, y, "coffin",
|
||||||
|
isStatic=True,
|
||||||
|
isCollectible=False,
|
||||||
|
isHazard=False
|
||||||
|
)
|
||||||
|
self.sounds = sounds
|
||||||
|
self.is_broken = False
|
||||||
|
self.dropped_item = None
|
||||||
|
|
||||||
|
def hit(self, player_pos):
|
||||||
|
"""Handle being hit by the player's weapon"""
|
||||||
|
if not self.is_broken:
|
||||||
|
self.is_broken = True
|
||||||
|
self.sounds['coffin_shatter'].play()
|
||||||
|
|
||||||
|
# Randomly choose item type
|
||||||
|
item_type = random.choice(['hand_of_glory', 'jack_o_lantern'])
|
||||||
|
|
||||||
|
# Create item 1-2 tiles away in random direction
|
||||||
|
direction = random.choice([-1, 1])
|
||||||
|
drop_distance = random.randint(1, 2)
|
||||||
|
drop_x = self.xPos + (direction * drop_distance)
|
||||||
|
|
||||||
|
self.dropped_item = PowerUp(
|
||||||
|
drop_x,
|
||||||
|
self.yPos,
|
||||||
|
item_type,
|
||||||
|
self.sounds,
|
||||||
|
direction
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
return False
|
41
src/enemy.py
41
src/enemy.py
@@ -24,12 +24,19 @@ class Enemy(Object):
|
|||||||
|
|
||||||
# Movement and behavior properties
|
# Movement and behavior properties
|
||||||
self.movingRight = True # Initial direction
|
self.movingRight = True # Initial direction
|
||||||
self.movementSpeed = 0.03 # Slightly slower than player
|
self.movementSpeed = 0.03 # Base speed
|
||||||
self.patrolStart = self.xRange[0]
|
self.patrolStart = self.xRange[0]
|
||||||
self.patrolEnd = self.xRange[0] + self.movementRange
|
self.patrolEnd = self.xRange[0] + self.movementRange
|
||||||
self.lastAttackTime = 0
|
self.lastAttackTime = 0
|
||||||
self.attackCooldown = 1000 # 1 second between attacks
|
self.attackCooldown = 1000 # 1 second between attacks
|
||||||
|
|
||||||
|
# Enemy type specific adjustments
|
||||||
|
if enemyType == "zombie":
|
||||||
|
self.movementSpeed *= 0.6 # Zombies are slower
|
||||||
|
self.damage = 10 # Zombies do massive damage
|
||||||
|
self.health = 3 # Easier to kill than goblins
|
||||||
|
self.attackCooldown = 1500 # Slower attack rate
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def xPos(self):
|
def xPos(self):
|
||||||
"""Current x position"""
|
"""Current x position"""
|
||||||
@@ -45,16 +52,26 @@ class Enemy(Object):
|
|||||||
if not self.isActive or self.health <= 0:
|
if not self.isActive or self.health <= 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update position based on patrol behavior
|
# Zombie behavior - always chase player
|
||||||
if self.movingRight:
|
if self.enemyType == "zombie":
|
||||||
self.xPos += self.movementSpeed
|
# Determine direction to player
|
||||||
if self.xPos >= self.patrolEnd:
|
if player.xPos > self.xPos:
|
||||||
self.movingRight = False
|
|
||||||
else:
|
|
||||||
self.xPos -= self.movementSpeed
|
|
||||||
if self.xPos <= self.patrolStart:
|
|
||||||
self.movingRight = True
|
self.movingRight = True
|
||||||
|
self.xPos += self.movementSpeed
|
||||||
|
else:
|
||||||
|
self.movingRight = False
|
||||||
|
self.xPos -= self.movementSpeed
|
||||||
|
else:
|
||||||
|
# Normal patrol behavior for other enemies
|
||||||
|
if self.movingRight:
|
||||||
|
self.xPos += self.movementSpeed
|
||||||
|
if self.xPos >= self.patrolEnd:
|
||||||
|
self.movingRight = False
|
||||||
|
else:
|
||||||
|
self.xPos -= self.movementSpeed
|
||||||
|
if self.xPos <= self.patrolStart:
|
||||||
|
self.movingRight = True
|
||||||
|
|
||||||
# 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)
|
||||||
@@ -103,3 +120,7 @@ class Enemy(Object):
|
|||||||
if self.channel:
|
if self.channel:
|
||||||
obj_stop(self.channel)
|
obj_stop(self.channel)
|
||||||
self.channel = None
|
self.channel = None
|
||||||
|
# Play death sound if available
|
||||||
|
deathSound = f"{self.enemyType}_death"
|
||||||
|
if deathSound in self.sounds:
|
||||||
|
self.sounds[deathSound].play()
|
||||||
|
89
src/item.py
Normal file
89
src/item.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
from libstormgames import *
|
||||||
|
from src.object import Object
|
||||||
|
import random
|
||||||
|
|
||||||
|
class CoffinObject(Object):
|
||||||
|
def __init__(self, x, y, sounds):
|
||||||
|
super().__init__(
|
||||||
|
x, y, "coffin",
|
||||||
|
isStatic=True,
|
||||||
|
isCollectible=False,
|
||||||
|
isHazard=False
|
||||||
|
)
|
||||||
|
self.sounds = sounds
|
||||||
|
self.is_broken = False
|
||||||
|
self.dropped_item = None
|
||||||
|
|
||||||
|
def hit(self, player_pos):
|
||||||
|
"""Handle being hit by the player's weapon"""
|
||||||
|
if not self.is_broken:
|
||||||
|
self.is_broken = True
|
||||||
|
self.sounds['coffin_shatter'].play()
|
||||||
|
|
||||||
|
# Randomly choose item type
|
||||||
|
item_type = random.choice(['hand_of_glory', 'jack_o_lantern'])
|
||||||
|
|
||||||
|
# Create item 1-2 tiles away in random direction
|
||||||
|
direction = random.choice([-1, 1])
|
||||||
|
drop_distance = random.randint(1, 2)
|
||||||
|
drop_x = self.xPos + (direction * drop_distance)
|
||||||
|
|
||||||
|
self.dropped_item = Item(
|
||||||
|
drop_x,
|
||||||
|
self.yPos,
|
||||||
|
item_type,
|
||||||
|
self.sounds,
|
||||||
|
direction
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
class Item(Object):
|
||||||
|
def __init__(self, x, y, item_type, sounds, direction):
|
||||||
|
super().__init__(
|
||||||
|
x, y, item_type,
|
||||||
|
isStatic=False,
|
||||||
|
isCollectible=True,
|
||||||
|
isHazard=False
|
||||||
|
)
|
||||||
|
self.sounds = sounds
|
||||||
|
self.direction = direction
|
||||||
|
self.speed = 0.05 # Base movement speed
|
||||||
|
self.item_type = item_type
|
||||||
|
self.channel = None
|
||||||
|
|
||||||
|
def update(self, current_time):
|
||||||
|
"""Update item position"""
|
||||||
|
if not self.isActive:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Update position
|
||||||
|
self._currentX += self.direction * self.speed
|
||||||
|
|
||||||
|
# Keep bounce sound playing while moving
|
||||||
|
if self.channel is None or not self.channel.get_busy():
|
||||||
|
self.channel = self.sounds['item_bounce'].play(-1)
|
||||||
|
|
||||||
|
# Check if item has gone too far (20 tiles)
|
||||||
|
if abs(self._currentX - self.xRange[0]) > 20:
|
||||||
|
self.isActive = False
|
||||||
|
if self.channel:
|
||||||
|
self.channel.stop()
|
||||||
|
self.channel = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def apply_effect(self, player):
|
||||||
|
"""Apply the item's effect when collected"""
|
||||||
|
if self.item_type == 'hand_of_glory':
|
||||||
|
player.start_invincibility()
|
||||||
|
speak("Hand of Glory makes you invincible!")
|
||||||
|
elif self.item_type == 'jack_o_lantern':
|
||||||
|
player.add_projectile('jack_o_lantern')
|
||||||
|
speak("Gained a Jack-o'-lantern projectile!")
|
||||||
|
|
||||||
|
# Stop movement sound when collected
|
||||||
|
if self.channel:
|
||||||
|
self.channel.stop()
|
||||||
|
self.channel = None
|
92
src/level.py
92
src/level.py
@@ -1,14 +1,20 @@
|
|||||||
import pygame
|
import pygame
|
||||||
|
import random
|
||||||
from libstormgames import *
|
from libstormgames import *
|
||||||
from src.enemy import Enemy
|
from src.enemy import Enemy
|
||||||
from src.object import Object
|
from src.object import Object
|
||||||
from src.player import Player
|
from src.player import Player
|
||||||
|
from src.projectile import Projectile
|
||||||
|
from src.coffin import CoffinObject
|
||||||
|
from src.powerup import PowerUp
|
||||||
|
|
||||||
class Level:
|
class Level:
|
||||||
def __init__(self, levelData, sounds):
|
def __init__(self, levelData, sounds):
|
||||||
self.sounds = sounds
|
self.sounds = sounds
|
||||||
self.objects = []
|
self.objects = []
|
||||||
self.enemies = []
|
self.enemies = []
|
||||||
|
self.bouncing_items = []
|
||||||
|
self.projectiles = [] # Track active projectiles
|
||||||
self.player = Player(levelData["player_start"]["x"], levelData["player_start"]["y"])
|
self.player = Player(levelData["player_start"]["x"], levelData["player_start"]["y"])
|
||||||
|
|
||||||
# Load objects and enemies from level data
|
# Load objects and enemies from level data
|
||||||
@@ -39,18 +45,45 @@ class Level:
|
|||||||
obj["sound"],
|
obj["sound"],
|
||||||
isStatic=obj.get("static", True),
|
isStatic=obj.get("static", True),
|
||||||
isCollectible=obj.get("collectible", False),
|
isCollectible=obj.get("collectible", False),
|
||||||
isHazard=obj.get("hazard", False)
|
isHazard=obj.get("hazard", False),
|
||||||
|
zombie_spawn_chance=obj.get("zombie_spawn_chance", 0)
|
||||||
)
|
)
|
||||||
self.objects.append(gameObject)
|
self.objects.append(gameObject)
|
||||||
|
|
||||||
def update_audio(self):
|
def update_audio(self):
|
||||||
currentTime = pygame.time.get_ticks()
|
currentTime = pygame.time.get_ticks()
|
||||||
|
|
||||||
# Update regular objects
|
# 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
|
||||||
|
if (obj.soundName == "grave" and
|
||||||
|
obj.zombie_spawn_chance > 0 and
|
||||||
|
not obj.has_spawned):
|
||||||
|
|
||||||
|
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.has_spawned = True
|
||||||
|
|
||||||
|
roll = random.randint(1, 100)
|
||||||
|
speak(f"Near grave, chance to spawn zombie")
|
||||||
|
if roll <= obj.zombie_spawn_chance:
|
||||||
|
zombie = Enemy(
|
||||||
|
[obj.xPos, obj.xPos],
|
||||||
|
obj.yPos,
|
||||||
|
"zombie",
|
||||||
|
self.sounds,
|
||||||
|
health=3,
|
||||||
|
damage=10,
|
||||||
|
attack_range=1
|
||||||
|
)
|
||||||
|
self.enemies.append(zombie)
|
||||||
|
speak("A zombie emerges from the grave!")
|
||||||
|
|
||||||
|
# Handle object audio
|
||||||
if not obj.isStatic:
|
if not obj.isStatic:
|
||||||
if obj.channel is None or not obj.channel.get_busy():
|
if obj.channel is None or not obj.channel.get_busy():
|
||||||
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)
|
||||||
@@ -72,15 +105,43 @@ 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 bouncing items
|
||||||
|
for item in self.bouncing_items[:]: # Copy list to allow removal
|
||||||
|
if not item.update(currentTime):
|
||||||
|
self.bouncing_items.remove(item)
|
||||||
|
if not item.isActive:
|
||||||
|
speak(f"{item.soundName} got away!")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check for item collection
|
||||||
|
if abs(item.xPos - self.player.xPos) < 1 and self.player.isJumping:
|
||||||
|
self.sounds[f'get_{item.soundName}'].play()
|
||||||
|
item.apply_effect(self.player)
|
||||||
|
item.isActive = False
|
||||||
|
self.bouncing_items.remove(item)
|
||||||
|
|
||||||
def handle_combat(self, currentTime):
|
def handle_combat(self, currentTime):
|
||||||
"""Handle combat interactions between player and enemies"""
|
"""Handle combat interactions between player and enemies"""
|
||||||
attackRange = self.player.get_attack_range(currentTime)
|
attackRange = self.player.get_attack_range(currentTime)
|
||||||
if attackRange:
|
if attackRange:
|
||||||
|
# Check for enemy hits
|
||||||
for enemy in self.enemies:
|
for enemy in self.enemies:
|
||||||
if enemy.isActive and enemy.xPos >= attackRange[0] and enemy.xPos <= attackRange[1]:
|
if enemy.isActive and enemy.xPos >= attackRange[0] and enemy.xPos <= attackRange[1]:
|
||||||
self.sounds[self.player.currentWeapon.hitSound].play()
|
self.sounds[self.player.currentWeapon.hitSound].play()
|
||||||
enemy.take_damage(self.player.currentWeapon.damage)
|
enemy.take_damage(self.player.currentWeapon.damage)
|
||||||
|
|
||||||
|
# Check for coffin hits - only if we have any coffins
|
||||||
|
for obj in self.objects:
|
||||||
|
if hasattr(obj, 'is_broken'): # Check if it's a coffin without using isinstance
|
||||||
|
if (not obj.is_broken and
|
||||||
|
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)
|
||||||
|
speak(f"{obj.dropped_item.item_type} falls out!")
|
||||||
|
|
||||||
def handle_collisions(self):
|
def handle_collisions(self):
|
||||||
for obj in self.objects:
|
for obj in self.objects:
|
||||||
@@ -99,3 +160,30 @@ class Level:
|
|||||||
self.sounds[obj.soundName].play()
|
self.sounds[obj.soundName].play()
|
||||||
speak("You fell in an open grave!")
|
speak("You fell in an open grave!")
|
||||||
self.player.set_health(0)
|
self.player.set_health(0)
|
||||||
|
|
||||||
|
def handle_projectiles(self, currentTime):
|
||||||
|
"""Update projectiles and check for collisions"""
|
||||||
|
for proj in self.projectiles[:]: # Copy list to allow removal
|
||||||
|
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:
|
||||||
|
proj.hit_enemy(enemy)
|
||||||
|
self.projectiles.remove(proj)
|
||||||
|
break
|
||||||
|
|
||||||
|
def throw_projectile(self):
|
||||||
|
"""Have player throw a projectile"""
|
||||||
|
proj_info = self.player.throw_projectile()
|
||||||
|
if proj_info:
|
||||||
|
self.projectiles.append(Projectile(
|
||||||
|
proj_info['type'],
|
||||||
|
proj_info['start_x'],
|
||||||
|
proj_info['direction']
|
||||||
|
))
|
||||||
|
# Play throw sound
|
||||||
|
if f"{proj_info['type']}_throw" in self.sounds:
|
||||||
|
self.sounds[f"{proj_info['type']}_throw"].play()
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
from libstormgames import *
|
from libstormgames import *
|
||||||
|
|
||||||
class Object:
|
class Object:
|
||||||
def __init__(self, x, yPos, soundName, isStatic=True, isCollectible=False, isHazard=False):
|
def __init__(self, x, yPos, soundName, isStatic=True, isCollectible=False, isHazard=False, zombie_spawn_chance=0):
|
||||||
# x can be either a single position or a range [start, end]
|
# x can be either a single position or a range [start, end]
|
||||||
self.xRange = [x, x] if isinstance(x, (int, float)) else x
|
self.xRange = [x, x] if isinstance(x, (int, float)) else x
|
||||||
self.yPos = yPos
|
self.yPos = yPos
|
||||||
@@ -9,6 +9,8 @@ class Object:
|
|||||||
self.isStatic = isStatic
|
self.isStatic = isStatic
|
||||||
self.isCollectible = isCollectible
|
self.isCollectible = isCollectible
|
||||||
self.isHazard = isHazard
|
self.isHazard = isHazard
|
||||||
|
self.zombie_spawn_chance = zombie_spawn_chance
|
||||||
|
self.has_spawned = False # Track if this object has spawned a zombie
|
||||||
self.channel = None # For tracking the sound channel
|
self.channel = None # For tracking the sound channel
|
||||||
self.isActive = True
|
self.isActive = True
|
||||||
# For collectibles in a range, track which positions have been collected
|
# For collectibles in a range, track which positions have been collected
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
from src.weapon import Weapon
|
from src.weapon import Weapon
|
||||||
|
|
||||||
class Player:
|
class Player:
|
||||||
def __init__(self, xPos, yPos):
|
def __init__(self, xPos, yPos):
|
||||||
# Movement attributes
|
# Movement attributes
|
||||||
@@ -28,6 +27,14 @@ class Player:
|
|||||||
self.isAttacking = False
|
self.isAttacking = False
|
||||||
self.lastAttackTime = 0
|
self.lastAttackTime = 0
|
||||||
|
|
||||||
|
# Power-up states
|
||||||
|
self.isInvincible = False
|
||||||
|
self.invincibilityStartTime = 0
|
||||||
|
self.invincibilityDuration = 5000 # 5 seconds of invincibility
|
||||||
|
|
||||||
|
# Projectiles
|
||||||
|
self.projectiles = [] # List of type and quantity tuples
|
||||||
|
|
||||||
# Initialize starting weapon (rusty shovel)
|
# Initialize starting weapon (rusty shovel)
|
||||||
self.add_weapon(Weapon(
|
self.add_weapon(Weapon(
|
||||||
name="rusty_shovel",
|
name="rusty_shovel",
|
||||||
@@ -38,12 +45,58 @@ class Player:
|
|||||||
attackDuration=200 # 200ms attack duration
|
attackDuration=200 # 200ms attack duration
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def update(self, currentTime):
|
||||||
|
"""Update player state"""
|
||||||
|
# Check if invincibility has expired
|
||||||
|
if self.isInvincible and currentTime - self.invincibilityStartTime >= self.invincibilityDuration:
|
||||||
|
self.isInvincible = False
|
||||||
|
speak("Invincibility wore off!")
|
||||||
|
|
||||||
|
def start_invincibility(self):
|
||||||
|
"""Activate invincibility from Hand of Glory"""
|
||||||
|
self.isInvincible = True
|
||||||
|
self.invincibilityStartTime = pygame.time.get_ticks()
|
||||||
|
|
||||||
|
def add_projectile(self, projectile_type):
|
||||||
|
"""Add a projectile to inventory"""
|
||||||
|
# Find if we already have this type
|
||||||
|
for proj in self.projectiles:
|
||||||
|
if proj[0] == projectile_type:
|
||||||
|
proj[1] += 1 # Increase quantity
|
||||||
|
speak(f"Now have {proj[1]} {projectile_type}s")
|
||||||
|
return
|
||||||
|
|
||||||
|
# If not found, add new type with quantity 1
|
||||||
|
self.projectiles.append([projectile_type, 1])
|
||||||
|
|
||||||
|
def throw_projectile(self):
|
||||||
|
"""Throw the first available projectile"""
|
||||||
|
if not self.projectiles:
|
||||||
|
speak("No projectiles to throw!")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get the first projectile type
|
||||||
|
projectile = self.projectiles[0]
|
||||||
|
projectile[1] -= 1 # Decrease quantity
|
||||||
|
|
||||||
|
if projectile[1] <= 0:
|
||||||
|
self.projectiles.pop(0) # Remove if none left
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': projectile[0],
|
||||||
|
'start_x': self.xPos,
|
||||||
|
'direction': 1 if self.facingRight else -1
|
||||||
|
}
|
||||||
|
|
||||||
def get_health(self):
|
def get_health(self):
|
||||||
"""Get current health"""
|
"""Get current health"""
|
||||||
return self._health
|
return self._health
|
||||||
|
|
||||||
def set_health(self, value):
|
def set_health(self, value):
|
||||||
"""Set health and handle death if needed"""
|
"""Set health and handle death if needed"""
|
||||||
|
if self.isInvincible:
|
||||||
|
return # No damage while invincible
|
||||||
|
|
||||||
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:
|
if self._health == 0:
|
||||||
self._lives -= 1
|
self._lives -= 1
|
||||||
|
52
src/powerup.py
Normal file
52
src/powerup.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from libstormgames import *
|
||||||
|
from src.object import Object
|
||||||
|
|
||||||
|
class PowerUp(Object):
|
||||||
|
def __init__(self, x, y, item_type, sounds, direction):
|
||||||
|
super().__init__(
|
||||||
|
x, y, item_type,
|
||||||
|
isStatic=False,
|
||||||
|
isCollectible=True,
|
||||||
|
isHazard=False
|
||||||
|
)
|
||||||
|
self.sounds = sounds
|
||||||
|
self.direction = direction
|
||||||
|
self.speed = 0.05 # Base movement speed
|
||||||
|
self.item_type = item_type
|
||||||
|
self.channel = None
|
||||||
|
|
||||||
|
def update(self, current_time):
|
||||||
|
"""Update item position"""
|
||||||
|
if not self.isActive:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Update position
|
||||||
|
self._currentX += self.direction * self.speed
|
||||||
|
|
||||||
|
# Keep bounce sound playing while moving
|
||||||
|
if self.channel is None or not self.channel.get_busy():
|
||||||
|
self.channel = self.sounds['item_bounce'].play(-1)
|
||||||
|
|
||||||
|
# Check if item has gone too far (20 tiles)
|
||||||
|
if abs(self._currentX - self.xRange[0]) > 20:
|
||||||
|
self.isActive = False
|
||||||
|
if self.channel:
|
||||||
|
self.channel.stop()
|
||||||
|
self.channel = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def apply_effect(self, player):
|
||||||
|
"""Apply the item's effect when collected"""
|
||||||
|
if self.item_type == 'hand_of_glory':
|
||||||
|
player.start_invincibility()
|
||||||
|
speak("Hand of Glory makes you invincible!")
|
||||||
|
elif self.item_type == 'jack_o_lantern':
|
||||||
|
player.add_projectile('jack_o_lantern')
|
||||||
|
speak("Gained a Jack-o'-lantern projectile!")
|
||||||
|
|
||||||
|
# Stop movement sound when collected
|
||||||
|
if self.channel:
|
||||||
|
self.channel.stop()
|
||||||
|
self.channel = None
|
29
src/projectile.py
Normal file
29
src/projectile.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
class Projectile:
|
||||||
|
def __init__(self, projectile_type, start_x, direction):
|
||||||
|
self.type = projectile_type
|
||||||
|
self.x = start_x
|
||||||
|
self.direction = direction
|
||||||
|
self.speed = 0.2 # Projectiles move faster than player
|
||||||
|
self.isActive = True
|
||||||
|
self.damage = 5 # All projectiles do same damage for now
|
||||||
|
self.range = 10 # 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)
|
||||||
|
self.isActive = False # Projectile is destroyed on hit
|
@@ -45,6 +45,12 @@ class WickedQuest:
|
|||||||
player.xPos += currentSpeed
|
player.xPos += currentSpeed
|
||||||
player.facingRight = True
|
player.facingRight = True
|
||||||
|
|
||||||
|
if keys[pygame.K_h]:
|
||||||
|
speak(f"{player.get_health()} HP")
|
||||||
|
|
||||||
|
if keys[pygame.K_f]: # Throw projectile
|
||||||
|
self.currentLevel.throw_projectile()
|
||||||
|
|
||||||
# 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):
|
||||||
self.sounds[player.currentWeapon.attackSound].play()
|
self.sounds[player.currentWeapon.attackSound].play()
|
||||||
@@ -77,6 +83,9 @@ class WickedQuest:
|
|||||||
if check_for_exit():
|
if check_for_exit():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Update player state (including power-ups)
|
||||||
|
self.currentLevel.player.update(currentTime)
|
||||||
|
|
||||||
self.handle_input()
|
self.handle_input()
|
||||||
|
|
||||||
# Update audio positioning and handle collisions
|
# Update audio positioning and handle collisions
|
||||||
@@ -86,6 +95,9 @@ class WickedQuest:
|
|||||||
# Handle combat interactions
|
# Handle combat interactions
|
||||||
self.currentLevel.handle_combat(currentTime)
|
self.currentLevel.handle_combat(currentTime)
|
||||||
|
|
||||||
|
# Update projectiles
|
||||||
|
self.currentLevel.handle_projectiles(currentTime)
|
||||||
|
|
||||||
clock.tick(60) # 60 FPS
|
clock.tick(60) # 60 FPS
|
||||||
|
|
||||||
# Player died or ran out of lives
|
# Player died or ran out of lives
|
||||||
|
Reference in New Issue
Block a user