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.health = 1 # Easy to kill
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
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
)
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
elif "enemy_type" in obj:
enemy = Enemy(
@@ -229,7 +250,21 @@ class Level:
if obj.hit(self.player.xPos):
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):
"""Handle all collision checks and return True if level is complete."""
@@ -243,7 +278,7 @@ class Level:
continue
# Handle grave edge warnings
if obj.isHazard:
if obj.isHazard and obj.soundName != "spiderweb": # Explicitly exclude spiderwebs
distance = abs(self.player.xPos - obj.xPos)
currentTime = pygame.time.get_ticks()
if (distance <= 2 and not self.player.isJumping and not self.player.isInvincible
@@ -254,56 +289,80 @@ class Level:
play_sound(self.sounds['edge'])
self.lastWarningTime = currentTime
if obj.is_in_range(self.player.xPos):
if obj.isCollectible and self.player.isJumping:
currentPos = round(self.player.xPos)
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)
if not obj.is_in_range(self.player.xPos):
continue
# Handle collectibles
if obj.isCollectible and self.player.isJumping:
currentPos = round(self.player.xPos)
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)
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'])
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
# 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
if self.player.xPos < self.leftBoundary:
+7
View File
@@ -86,6 +86,13 @@ class Player:
def update(self, currentTime):
"""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
if self.isInvincible and currentTime - self.invincibilityStartTime >= self.invincibilityDuration:
self.isInvincible = False
+16
View File
@@ -57,6 +57,22 @@ class PowerUp(Object):
broomWeapon = Weapon.create_witch_broom()
player.add_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
if self.channel: