Spiderweb obstacles added. Latest libstormgames module update. Level 4 created, needs adjustment, currently way too hard.
This commit is contained in:
@@ -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"
|
||||||
|
}
|
||||||
+1
-1
Submodule libstormgames updated: b479811a98...7cbbc64d27
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user