Spiderweb obstacles added. Latest libstormgames module update. Level 4 created, needs adjustment, currently way too hard.

This commit is contained in:
Storm Dragon
2025-02-09 13:38:11 -05:00
parent 0f0d719578
commit 6303cf93e7
12 changed files with 435 additions and 56 deletions
+280
View File
@@ -0,0 +1,280 @@
{
"level_id": 4,
"name": "Spider's Domain",
"description": "The spiders have claimed this part of the graveyard. Be careful to not disturb them. You know how spiders think, if you can't eat it, kill it!",
"player_start": {
"x": 0,
"y": 0
},
"objects": [
{
"x_range": [5, 8],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"type": "spider_web",
"x": 10,
"y": 0
},
{
"x": 15,
"y": 3,
"sound": "coffin",
"type": "coffin"
},
{
"x_range": [20, 23],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"x_range": [25, 35],
"y": 0,
"enemy_type": "goblin",
"health": 4,
"damage": 2,
"attack_range": 1,
"attack_pattern": {
"type": "patrol"
}
},
{
"x_range": [30, 45],
"y": 12,
"type": "skull_storm",
"damage": 3,
"maximum_skulls": 2,
"frequency": {
"min": 2,
"max": 4
}
},
{
"type": "spider_web",
"x": 40,
"y": 0
},
{
"x": 45,
"y": 0,
"type": "grave",
"sound": "grave",
"static": true,
"zombie_spawn_chance": 0
},
{
"x_range": [50, 54],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"x": 60,
"y": 3,
"sound": "coffin",
"type": "coffin"
},
{
"x_range": [65, 85],
"y": 0,
"enemy_type": "witch",
"health": 8,
"damage": 2,
"attack_range": 1,
"attack_pattern": {
"type": "patrol"
}
},
{
"type": "spider_web",
"x": 75,
"y": 0
},
{
"x_range": [80, 95],
"y": 15,
"type": "skull_storm",
"damage": 4,
"maximum_skulls": 3,
"frequency": {
"min": 1,
"max": 3
}
},
{
"x_range": [90, 93],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"x": 100,
"y": 0,
"type": "catapult",
"fire_interval": 4000,
"range": 20
},
{
"x": 110,
"y": 0,
"type": "grave",
"sound": "grave",
"static": true,
"zombie_spawn_chance": 50
},
{
"x_range": [115, 118],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"type": "spider_web",
"x": 120,
"y": 0
},
{
"x_range": [125, 145],
"y": 0,
"enemy_type": "ghoul",
"health": 12,
"damage": 3,
"attack_range": 2,
"attack_pattern": {
"type": "hunter",
"turn_threshold": 4
}
},
{
"x": 135,
"y": 3,
"sound": "coffin",
"type": "coffin"
},
{
"x_range": [140, 160],
"y": 15,
"type": "skull_storm",
"damage": 4,
"maximum_skulls": 3,
"frequency": {
"min": 1,
"max": 3
}
},
{
"x_range": [150, 153],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"x": 165,
"y": 0,
"type": "grave",
"sound": "grave",
"static": true,
"item": "guts",
"zombie_spawn_chance": 0
},
{
"type": "spider_web",
"x": 175,
"y": 0
},
{
"x_range": [180, 200],
"y": 0,
"enemy_type": "witch",
"health": 8,
"damage": 2,
"attack_range": 1,
"attack_pattern": {
"type": "patrol"
}
},
{
"x_range": [185, 188],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"x": 195,
"y": 3,
"item": "hand_of_glory",
"sound": "coffin",
"type": "coffin"
},
{
"x_range": [205, 225],
"y": 12,
"type": "skull_storm",
"damage": 4,
"maximum_skulls": 3,
"frequency": {
"min": 1,
"max": 3
}
},
{
"x_range": [210, 213],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"type": "spider_web",
"x": 220,
"y": 0
},
{
"x": 225,
"y": 0,
"type": "catapult",
"fire_interval": 4000,
"range": 20
},
{
"x_range": [230, 245],
"y": 0,
"enemy_type": "ghoul",
"health": 12,
"damage": 3,
"attack_range": 2,
"attack_pattern": {
"type": "hunter",
"turn_threshold": 4
}
},
{
"x_range": [235, 238],
"y": 3,
"sound": "coin",
"collectible": true,
"static": true
},
{
"x": 245,
"y": 3,
"sound": "coffin",
"type": "coffin"
}
],
"boundaries": {
"left": 0,
"right": 250
},
"footstep_sound": "footstep_tall_grass"
}
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+5
View File
@@ -41,6 +41,11 @@ class Enemy(Object):
self.damage = level.player.get_max_health() # Instant death self.damage = level.player.get_max_health() # Instant death
self.health = 1 # Easy to kill self.health = 1 # Easy to kill
self.attackCooldown = 1500 # Slower attack rate self.attackCooldown = 1500 # Slower attack rate
elif enemyType == "spider":
speedMultiplier = kwargs.get('speed_multiplier', 2.0)
self.movementSpeed *= speedMultiplier # Spiders are faster
self.attackPattern = {'type': 'hunter'} # Spiders actively hunt the player
self.turnThreshold = 3 # Spiders turn around quickly to chase player
@property @property
def xPos(self): def xPos(self):
+110 -51
View File
@@ -95,6 +95,27 @@ class Level:
item=obj.get("item", "random") # Get item type or default to random item=obj.get("item", "random") # Get item type or default to random
) )
self.objects.append(coffin) self.objects.append(coffin)
# Check if this is a spider web
elif obj.get("type") == "spider_web":
# Check distance from graves
isValidPosition = True
for existingObj in self.objects:
if (existingObj.soundName == "grave" and
not hasattr(existingObj, 'graveItem')):
distance = abs(obj["x"] - existingObj.xPos)
if distance < 3:
isValidPosition = False
break
if isValidPosition:
web = Object(
obj["x"], # Just pass the single x value
obj["y"],
"spiderweb",
isStatic=True,
isCollectible=False,
)
self.objects.append(web)
# Check if this is an enemy # Check if this is an enemy
elif "enemy_type" in obj: elif "enemy_type" in obj:
enemy = Enemy( enemy = Enemy(
@@ -229,7 +250,21 @@ class Level:
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)
#speak(f"{obj.dropped_item.soundName} falls out!")
def spawn_spider(self, xPos, yPos):
"""Spawn a spider at the given position"""
spider = Enemy(
[xPos - 5, xPos + 5], # Give spider a patrol range
yPos,
"spider",
self.sounds,
self,
health=8,
damage=8,
attack_range=1,
speed_multiplier=2.0
)
self.enemies.append(spider)
def handle_collisions(self): def handle_collisions(self):
"""Handle all collision checks and return True if level is complete.""" """Handle all collision checks and return True if level is complete."""
@@ -243,7 +278,7 @@ class Level:
continue continue
# Handle grave edge warnings # Handle grave edge warnings
if obj.isHazard: if obj.isHazard and obj.soundName != "spiderweb": # Explicitly exclude spiderwebs
distance = abs(self.player.xPos - obj.xPos) distance = abs(self.player.xPos - obj.xPos)
currentTime = pygame.time.get_ticks() currentTime = pygame.time.get_ticks()
if (distance <= 2 and not self.player.isJumping and not self.player.isInvincible if (distance <= 2 and not self.player.isJumping and not self.player.isInvincible
@@ -254,56 +289,80 @@ class Level:
play_sound(self.sounds['edge']) play_sound(self.sounds['edge'])
self.lastWarningTime = currentTime self.lastWarningTime = currentTime
if obj.is_in_range(self.player.xPos): if not obj.is_in_range(self.player.xPos):
if obj.isCollectible and self.player.isJumping: continue
currentPos = round(self.player.xPos)
if currentPos not in obj.collectedPositions: # Handle collectibles
play_sound(self.sounds[f'get_{obj.soundName}']) if obj.isCollectible and self.player.isJumping:
obj.collect_at_position(currentPos) currentPos = round(self.player.xPos)
self.player.collectedItems.append(obj.soundName) if currentPos not in obj.collectedPositions:
play_sound(self.sounds[f'get_{obj.soundName}'])
obj.collect_at_position(currentPos)
self.player.collectedItems.append(obj.soundName)
self.player.stats.update_stat('Items collected', 1)
if obj.soundName == "coin":
self.player._coins += 1
self.player.stats.update_stat('Bone dust', 1)
if self.player._coins % 5 == 0:
# Only heal if below max health
if self.player.get_health() < self.player.get_max_health():
self.player.set_health(min(
self.player.get_health() + 1,
self.player.get_max_health()
))
if self.player._coins % 100 == 0:
# Extra life
self.player._coins = 0
self.player._lives += 1
play_sound(self.sounds['get_extra_life'])
continue
# Handle spiderweb - this should trigger for both walking and jumping if not ducking
if obj.soundName == "spiderweb" and not self.player.isDucking:
# Create and apply web effect
webEffect = PowerUp(
obj.xPos,
obj.yPos,
'spiderweb',
self.sounds,
0 # No direction needed since it's just for effect
)
webEffect.level = self # Pass level reference for spider spawning
play_sound(self.sounds['hit_spiderweb'])
webEffect.apply_effect(self.player)
# Deactivate web
obj.isActive = False
obj.channel = obj_stop(obj.channel)
continue
# Handle graves and other hazards
if obj.isHazard and not self.player.isJumping:
if isinstance(obj, GraveObject):
can_collect = obj.collect_grave_item(self.player)
if can_collect:
# Successfully collected item while ducking
play_sound(self.sounds[f'get_{obj.graveItem}'])
self.player.stats.update_stat('Items collected', 1) self.player.stats.update_stat('Items collected', 1)
if obj.soundName == "coin": # Create PowerUp to handle the item effect
self.player._coins += 1 item = PowerUp(obj.xPos, obj.yPos, obj.graveItem, self.sounds, 1)
self.player.stats.update_stat('Bone dust', 1) item.apply_effect(self.player)
if self.player._coins % 5 == 0: # Stop grave's current audio channel
# Only heal if below max health if obj.channel:
if self.player.get_health() < self.player.get_max_health(): obj_stop(obj.channel)
self.player.set_health(min( # Remove the grave
self.player.get_health() + 1, obj.graveItem = None
self.player.get_max_health() obj.channel = None
)) obj.isActive = False # Mark the grave as inactive after collection
continue
if self.player._coins % 100 == 0: elif not self.player.isInvincible:
# Extra life # Kill player for normal graves or non-ducking collision
self.player._coins = 0 play_sound(self.sounds[obj.soundName])
self.player._lives += 1 speak("You fell in an open grave! Now, it's yours!")
play_sound(self.sounds['get_extra_life']) self.player.set_health(0)
return False
elif obj.isHazard and not self.player.isJumping:
if isinstance(obj, GraveObject):
can_collect = obj.collect_grave_item(self.player)
if can_collect:
# Successfully collected item while ducking
play_sound(self.sounds[f'get_{obj.graveItem}'])
self.player.stats.update_stat('Items collected', 1)
# Create PowerUp to handle the item effect
item = PowerUp(obj.xPos, obj.yPos, obj.graveItem, self.sounds, 1)
item.apply_effect(self.player)
# Stop grave's current audio channel
if obj.channel:
obj_stop(obj.channel)
# Remove the grave
obj.graveItem = None
obj.channel = None
obj.isActive = False # Mark the grave as inactive after collection
continue
elif not self.player.isInvincible:
# Kill player for normal graves or non-ducking collision
play_sound(self.sounds[obj.soundName])
speak("You fell in an open grave! Now, it's yours!")
self.player.set_health(0)
return False
# Handle boundaries # Handle boundaries
if self.player.xPos < self.leftBoundary: if self.player.xPos < self.leftBoundary:
+7
View File
@@ -86,6 +86,13 @@ class Player:
def update(self, currentTime): def update(self, currentTime):
"""Update player state""" """Update player state"""
if hasattr(self, 'webPenaltyEndTime'):
if currentTime >= self.webPenaltyEndTime:
self.moveSpeed *= 2 # Restore speed
if self.currentWeapon:
self.currentWeapon.attackDuration *= 0.5 # Restore attack speed
del self.webPenaltyEndTime
# Check if invincibility has expired # Check if invincibility has expired
if self.isInvincible and currentTime - self.invincibilityStartTime >= self.invincibilityDuration: if self.isInvincible and currentTime - self.invincibilityStartTime >= self.invincibilityDuration:
self.isInvincible = False self.isInvincible = False
+16
View File
@@ -57,6 +57,22 @@ class PowerUp(Object):
broomWeapon = Weapon.create_witch_broom() broomWeapon = Weapon.create_witch_broom()
player.add_weapon(broomWeapon) player.add_weapon(broomWeapon)
player.equip_weapon(broomWeapon) player.equip_weapon(broomWeapon)
elif self.item_type == 'spiderweb':
# Bounce player back (happens even if invincible)
player.xPos -= 3 if player.xPos > self.xPos else -3
# Only apply debuffs if not invincible
if not player.isInvincible:
# Half speed and double attack time for 15 seconds
player.moveSpeed *= 0.5
if player.currentWeapon:
player.currentWeapon.attackDuration *= 2
# Set timer for penalty removal
player.webPenaltyEndTime = pygame.time.get_ticks() + 15000
# Tell level to spawn a spider
if hasattr(self, 'level'):
self.level.spawn_spider(self.xPos, self.yPos)
# Stop movement sound when collected # Stop movement sound when collected
if self.channel: if self.channel: