Spiderweb obstacles added. Latest libstormgames module update. Level 4 created, needs adjustment, currently way too hard.
This commit is contained in:
280
levels/Wicked Quest/4.json
Normal file
280
levels/Wicked Quest/4.json
Normal 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"
|
||||
}
|
Submodule libstormgames updated: b479811a98...7cbbc64d27
BIN
sounds/get_cauldron.ogg
(Stored with Git LFS)
BIN
sounds/get_cauldron.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
sounds/get_guts.ogg
(Stored with Git LFS)
BIN
sounds/get_guts.ogg
(Stored with Git LFS)
Binary file not shown.
BIN
sounds/hit_spiderweb.ogg
(Stored with Git LFS)
Normal file
BIN
sounds/hit_spiderweb.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
sounds/spider.ogg
(Stored with Git LFS)
Normal file
BIN
sounds/spider.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
sounds/spider_dies.ogg
(Stored with Git LFS)
Normal file
BIN
sounds/spider_dies.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
sounds/spiderweb.ogg
(Stored with Git LFS)
Normal file
BIN
sounds/spiderweb.ogg
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -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):
|
||||
|
161
src/level.py
161
src/level.py
@@ -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:
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
Reference in New Issue
Block a user