Catapults working. At this point most of the basic game is set up, now I have to get down to level and enemy creation, and sounds for when things happen.

This commit is contained in:
Storm Dragon
2025-02-01 00:11:36 -05:00
parent ab73ddfdd5
commit 7627543ed8
8 changed files with 187 additions and 4 deletions

View File

@@ -120,6 +120,14 @@
"sound": "grave", "sound": "grave",
"static": true, "static": true,
"zombie_spawn_chance": 30 "zombie_spawn_chance": 30
},
{
"x": 145,
"y": 0,
"type": "catapult",
"direction": -1,
"fire_interval": 5000,
"range": 15
} }
], ],
"boundaries": { "boundaries": {

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

137
src/catapult.py Normal file
View File

@@ -0,0 +1,137 @@
from libstormgames import *
from src.object import Object
import random
class Pumpkin:
def __init__(self, x, isHigh, direction, playerMaxHealth):
self.x = x
self.isHigh = isHigh
self.direction = direction # 1 for right, -1 for left
self.speed = 0.15
self.isActive = True
self.damage = playerMaxHealth // 2 # Half of player's max health
self.soundChannel = None
self.soundName = 'pumpkin_low' if isHigh else 'pumpkin_high' # Inverted mapping
def update(self, sounds, playerX):
"""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):
"""Stop the pumpkin's sound"""
if self.soundChannel:
obj_stop(self.soundChannel)
self.soundChannel = None
def check_collision(self, player):
"""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:
return True # Hit by high pumpkin while on ground
elif not self.isHigh and player.isJumping:
return True # Hit by low pumpkin while jumping
return False
class Catapult(Object):
def __init__(self, x, y, sounds, direction=1, fireInterval=5000, firingRange=20):
super().__init__(
x, y, "catapult",
isStatic=True,
isCollectible=False,
)
self.sounds = sounds
self.direction = direction
self.fireInterval = fireInterval # Time between shots in milliseconds
self.firingRange = firingRange # How close player needs to be to trigger firing
self.lastFireTime = 0
self.activePumpkins = []
self.isFiring = False # Track if we're currently in firing mode
self.launchDelay = 900 # Time between launch sound and pumpkin firing
self.pendingPumpkin = None # Store pending pumpkin data
self.pumpkinLaunchTime = 0 # When to launch the pending pumpkin
def fire(self, currentTime, player):
"""Start the firing sequence"""
self.lastFireTime = currentTime
# Play launch sound
self.sounds['catapult_launch'].play()
# 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
def update(self, currentTime, player):
"""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
self.lastFireTime = currentTime # Reset timer when entering range
speak("Pumpkin catapult activates!")
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
pumpkin = Pumpkin(
self.xPos,
self.pendingPumpkin['isHigh'],
self.pendingPumpkin['direction'],
self.pendingPumpkin['playerMaxHealth']
)
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.activePumpkins.remove(pumpkin)
continue
if pumpkin.check_collision(player):
player.set_health(player.get_health() - pumpkin.damage)
pumpkin.stop_sound()
pumpkin.isActive = False
self.activePumpkins.remove(pumpkin)
speak("Hit by a pumpkin!")

View File

@@ -1,6 +1,8 @@
import pygame import pygame
import random import random
from libstormgames import * from libstormgames import *
from src.catapult import Catapult
from src.coffin import CoffinObject
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
@@ -27,8 +29,19 @@ class Level:
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
if obj.get("type") == "catapult":
catapult = Catapult(
xPos[0],
obj["y"],
self.sounds,
direction=obj.get("direction", 1),
fireInterval=obj.get("fireInterval", 5000),
firingRange=obj.get("range", 20)
)
self.objects.append(catapult)
# Check if this is an enemy # Check if this is an enemy
if "enemy_type" in obj: elif "enemy_type" in obj:
enemy = Enemy( enemy = Enemy(
xPos, xPos,
obj["y"], obj["y"],
@@ -71,7 +84,6 @@ class Level:
obj.has_spawned = True obj.has_spawned = True
roll = random.randint(1, 100) roll = random.randint(1, 100)
speak(f"Near grave, chance to spawn zombie")
if roll <= obj.zombie_spawn_chance: if roll <= obj.zombie_spawn_chance:
zombie = Enemy( zombie = Enemy(
[obj.xPos, obj.xPos], [obj.xPos, obj.xPos],
@@ -108,6 +120,11 @@ class Level:
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
for obj in self.objects:
if isinstance(obj, Catapult):
obj.update(currentTime, self.player)
# Update bouncing items # Update bouncing items
for item in self.bouncing_items[:]: # Copy list to allow removal for item in self.bouncing_items[:]: # Copy list to allow removal
if not item.update(currentTime): if not item.update(currentTime):

View File

@@ -12,6 +12,7 @@ class Player:
# Stats and tracking # Stats and tracking
self._health = 10 self._health = 10
self._maxHealth = 10
self._lives = 1 self._lives = 1
self.distanceSinceLastStep = 0 self.distanceSinceLastStep = 0
self.stepDistance = 0.5 self.stepDistance = 0.5
@@ -92,6 +93,10 @@ class Player:
"""Get current health""" """Get current health"""
return self._health return self._health
def get_max_health(self):
"""Get current max health"""
return self._maxHealth
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: if self.isInvincible:
@@ -103,6 +108,10 @@ class Player:
if self._lives > 0: if self._lives > 0:
self._health = 10 # Reset health if we still have lives self._health = 10 # Reset health if we still have lives
def set_max_health(self, value):
"""Set max health"""
self._maxHealth = value
def get_coins(self): def get_coins(self):
"""Get remaining coins""" """Get remaining coins"""
return self._coins return self._coins