From 7627543ed8e74002f467aff4492b57177d5075c5 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Sat, 1 Feb 2025 00:11:36 -0500 Subject: [PATCH] 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. --- levels/1.json | 8 +++ sounds/catapult.ogg | 3 + sounds/catapult_launch.ogg | 3 + sounds/pumpkin_high.ogg | 3 + sounds/pumpkin_low.ogg | 3 + src/catapult.py | 137 +++++++++++++++++++++++++++++++++++++ src/level.py | 25 +++++-- src/player.py | 9 +++ 8 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 sounds/catapult.ogg create mode 100644 sounds/catapult_launch.ogg create mode 100644 sounds/pumpkin_high.ogg create mode 100644 sounds/pumpkin_low.ogg create mode 100644 src/catapult.py diff --git a/levels/1.json b/levels/1.json index 3d37556..0acd193 100644 --- a/levels/1.json +++ b/levels/1.json @@ -120,6 +120,14 @@ "sound": "grave", "static": true, "zombie_spawn_chance": 30 + }, + { + "x": 145, + "y": 0, + "type": "catapult", + "direction": -1, + "fire_interval": 5000, + "range": 15 } ], "boundaries": { diff --git a/sounds/catapult.ogg b/sounds/catapult.ogg new file mode 100644 index 0000000..45636c8 --- /dev/null +++ b/sounds/catapult.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cb72921b0051a5fd59aaf0b12994474c03a2714448b63ba8a6d8ed95ff90c3d +size 28059 diff --git a/sounds/catapult_launch.ogg b/sounds/catapult_launch.ogg new file mode 100644 index 0000000..38d2a43 --- /dev/null +++ b/sounds/catapult_launch.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1bc9f4de07b13af5e462323a42ea8a483645b70521c0be42830bb05f02f22a99 +size 17121 diff --git a/sounds/pumpkin_high.ogg b/sounds/pumpkin_high.ogg new file mode 100644 index 0000000..f719de4 --- /dev/null +++ b/sounds/pumpkin_high.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3be90ac51f70724df24e3a19b13c1b39387b6d38b6221e4d20da03cd9579aeb +size 15753 diff --git a/sounds/pumpkin_low.ogg b/sounds/pumpkin_low.ogg new file mode 100644 index 0000000..f71af83 --- /dev/null +++ b/sounds/pumpkin_low.ogg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e1ff65eb09d14af2c380f2e70f62eb5064e3fcdc568ffaad7df4e675abe1a81f +size 11278 diff --git a/src/catapult.py b/src/catapult.py new file mode 100644 index 0000000..37defba --- /dev/null +++ b/src/catapult.py @@ -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!") diff --git a/src/level.py b/src/level.py index abfc1c4..9a795d9 100644 --- a/src/level.py +++ b/src/level.py @@ -1,6 +1,8 @@ import pygame import random from libstormgames import * +from src.catapult import Catapult +from src.coffin import CoffinObject from src.enemy import Enemy from src.object import Object from src.player import Player @@ -18,7 +20,7 @@ class Level: self.player = Player(levelData["player_start"]["x"], levelData["player_start"]["y"]) self.edge_warning_channel = None self.weapon_hit_channel = None - + # Load objects and enemies from level data for obj in levelData["objects"]: # Handle x position or range @@ -26,9 +28,20 @@ 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( + 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 - if "enemy_type" in obj: + elif "enemy_type" in obj: enemy = Enemy( xPos, obj["y"], @@ -71,7 +84,6 @@ class Level: 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], @@ -108,6 +120,11 @@ class Level: 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): + obj.update(currentTime, self.player) + # Update bouncing items for item in self.bouncing_items[:]: # Copy list to allow removal if not item.update(currentTime): diff --git a/src/player.py b/src/player.py index 2ea56fe..04374a0 100644 --- a/src/player.py +++ b/src/player.py @@ -12,6 +12,7 @@ class Player: # Stats and tracking self._health = 10 + self._maxHealth = 10 self._lives = 1 self.distanceSinceLastStep = 0 self.stepDistance = 0.5 @@ -92,6 +93,10 @@ class Player: """Get current health""" return self._health + def get_max_health(self): + """Get current max health""" + return self._maxHealth + def set_health(self, value): """Set health and handle death if needed""" if self.isInvincible: @@ -103,6 +108,10 @@ class Player: if self._lives > 0: 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): """Get remaining coins""" return self._coins