Extended override system to include spider webs. They can now be altered to be on the ground and require jumping, the name and the sponned monster can be changed as well. Fixed various bugs.

This commit is contained in:
Storm Dragon
2025-09-21 19:25:09 -04:00
parent bd7df4804d
commit 948d0c32f7
4 changed files with 202 additions and 18 deletions

View File

@@ -279,11 +279,76 @@ The `maximum_skulls` setting controls how many skulls can be falling simultaneou
{
"type": "spider_web",
"x": 15,
"y": 0
"y": 3,
"sound_overrides": {
"base": "floating_trap",
"hit_sound": "trap_trigger",
"spawn_enemy": "elf"
}
}
```
Slows player movement and attack speed temporarily when walked over. Automatically spawns a spider enemy at the web location.
**Spider Web Mechanics:**
Spider webs create interactive traps that can slow player movement and attack speed temporarily when triggered. They automatically spawn an enemy at the web location. When triggered, the player bounces back 3 tiles and may receive a 15-second penalty (half movement speed, double attack time) depending on the slowdown setting.
**Y-Position Based Behavior:**
- **`"y": 0` (Ground Level)** - Ground trap that triggers when walking/running. **Avoid by jumping over it.**
- **`"y": > 0` (Air Level)** - Air trap that triggers when walking or jumping. **Avoid by ducking under it.**
**Sound Override Properties:**
- `"base"`: Override the ambient web sound (e.g., "floating_trap", "bear_trap")
- `"hit_sound"`: Override the trigger sound (e.g., "trap_trigger", "snap")
- `"spawn_enemy"`: Override spawned enemy type ("spider", "elf", "witch")
- `"enemy_stats"`: Override spawned enemy stats (health, damage, speed_multiplier, attack_range)
- `"slowdown"`: Override slowdown effect (true/false, defaults to true)
**Level Design Guidelines:**
- **For traditional spider webs, always use `"y": > 0`** (air placement) unless you're specifically creating a themed ground trap
- **Ground webs** (`"y": 0`) work well for: bear traps, tripwires, pressure plates, snowdrifts, present piles
- **Air webs** (`"y": > 0`) work well for: hanging webs, floating pods, magical traps, dangling ornaments
- **Slowdown considerations**: Use `"slowdown": false` for traps where the slowdown effect doesn't make thematic sense (stepping on a snowdrift shouldn't entangle you like a web would)
**Example Themed Variations:**
```json
// Christmas snowdrift (ground trap - jump to avoid)
{
"type": "spider_web",
"x": 50,
"y": 0,
"sound_overrides": {
"base": "snow_drift",
"hit_sound": "snow_drift_hit",
"spawn_enemy": "abominable_snowman",
"enemy_stats": {
"health": 12,
"damage": 3,
"speed_multiplier": 0.8,
"attack_range": 1
},
"slowdown": false
}
}
// Floating snow cloud (air trap - duck to avoid)
{
"type": "spider_web",
"x": 75,
"y": 4,
"sound_overrides": {
"base": "floating_snow_cloud",
"hit_sound": "cloud_burst",
"spawn_enemy": "witch",
"slowdown": true
}
}
// Traditional spider web (default behavior)
{
"type": "spider_web",
"x": 100,
"y": 2
}
```
#### Grasping Hands
```json
@@ -315,17 +380,26 @@ While not directly supported, you can create the illusion of coffins that releas
{
"type": "spider_web",
"x": 25,
"y": 3
"y": 3,
"sound_overrides": {
"base": "coffin",
"hit_sound": "coffin_shatter",
"spawn_enemy": "elf"
}
}
```
**Setup in your level pack's sound directory:**
**Modern Approach (Recommended):**
Use sound overrides directly in your JSON for cleaner, more maintainable theming. The above creates a "coffin" that shatters and spawns an enemy when touched.
**Legacy Approach:**
You can still replace sounds in your level pack's sound directory:
- Replace `spiderweb.ogg` with `coffin.ogg` (ambient coffin sound)
- Replace `hit_spiderweb.ogg` with `coffin_shatter.ogg` (break sound)
- Replace `spider.ogg` with `mummy.ogg` (spawned enemy sound)
- Replace `spider_dies.ogg` with `mummy_dies.ogg` (death sound)
**Result:** Players hear a coffin, when they touch it they hear it shatter, get slowed down (representing the enemy emerging), and a "mummy" automatically spawns from the coffin. The enemy is internally still a spider but sounds and feels like a mummy emerging from a sarcophagus. The slow down for the player can be because the skeleton is tangled in the mummy wrappings, or perhapse he's tangled in the shattered wood. It also does not float, so players would quickly learn to be war, maybe this coffin is one to just be passed under (low hanging).
**Result:** Players hear a coffin, when they touch it they hear it shatter, get slowed down (representing the enemy emerging), and a "mummy" automatically spawns from the coffin. Since this uses `"y": 3` (air level), players must duck to avoid triggering it - creating the "low hanging coffin" effect you described.
### Complex Enemy Combinations
- **Spawner + Vulnerability:** Create a tough ghost that spawns minions while alternating between vulnerable states

View File

@@ -213,6 +213,9 @@ class Level:
isStatic=True,
isCollectible=False,
)
# Apply sound overrides if specified
if "sound_overrides" in obj:
self._apply_object_sound_overrides(web, obj["sound_overrides"])
self.objects.append(web)
# Check if this is an enemy
elif "enemy_type" in obj:
@@ -403,6 +406,40 @@ class Level:
)
self.enemies.append(spider)
def spawn_web_enemy(self, xPos, yPos, enemyType="spider", enemyStats=None):
"""Spawn an enemy from a web trigger (supports any enemy type)"""
# Use provided stats or defaults based on enemy type
if enemyStats:
health = enemyStats.get("health", 8)
damage = enemyStats.get("damage", 2)
speed_multiplier = enemyStats.get("speed_multiplier", 1.0)
attack_range = enemyStats.get("attack_range", 1)
else:
# Default stats for common enemy types (backward compatibility)
if enemyType == "spider":
health, damage, speed_multiplier = 8, 8, 2.0
elif enemyType == "elf":
health, damage, speed_multiplier = 2, 1, 1.0
elif enemyType == "witch":
health, damage, speed_multiplier = 6, 2, 1.0
else:
# For any other enemy type, use reasonable defaults
health, damage, speed_multiplier = 6, 2, 1.0
attack_range = 1
enemy = Enemy(
[xPos - 5, xPos + 5], # Give enemy a patrol range
yPos,
enemyType,
self.sounds,
self,
health=health,
damage=damage,
attack_range=attack_range,
speed_multiplier=speed_multiplier,
)
self.enemies.append(enemy)
def handle_collisions(self):
"""Handle all collision checks and return True if level is complete."""
# Add a pump here so it gets called reasonably often.
@@ -474,14 +511,28 @@ class Level:
) # Use survivor_bonus sound if available, fallback to bone_dust
continue
# Handle spiderweb - this should trigger for both walking and jumping if not ducking
if obj.originalSoundName == "spiderweb" and not self.player.isDucking:
# Handle spiderweb - collision depends on Y position
# Y = 0 (ground): hit if walking (avoid by jumping)
# Y > 0 (air): hit if not ducking (avoid by ducking)
webTriggered = False
if obj.originalSoundName == "spiderweb":
if obj.yPos == 0:
# Ground web: triggers if player is walking (not jumping)
webTriggered = not self.player.isJumping
else:
# Air web: triggers if player is not ducking
webTriggered = not self.player.isDucking
if webTriggered:
# 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.webObject = obj # Pass web object for override information
# Use override hit sound if available, otherwise default
hitSound = getattr(obj, 'hitSoundOverride', 'hit_spiderweb')
play_sound(self.sounds[hitSound])
webEffect.apply_effect(self.player, self)
# Deactivate web
@@ -675,3 +726,45 @@ class Level:
# Store the override for later use when coffin is broken
if not hasattr(obj, 'itemSoundOverride'):
obj.itemSoundOverride = soundOverrides["item"]
# Handle skull storm sound overrides and message overrides
if hasattr(obj, 'sounds') and isinstance(obj.sounds, dict):
# Create a copy of the original sounds dict for this object
overriddenSounds = dict(obj.sounds)
# Apply any overrides that exist in both the override dict and the sound system
for soundKey, overrideKey in soundOverrides.items():
if soundKey in overriddenSounds and overrideKey in self.sounds:
overriddenSounds[soundKey] = self.sounds[overrideKey]
# Handle numbered sound overrides (e.g., falling_skull1, falling_skull2)
elif soundKey.endswith('_skull') and overrideKey in self.sounds:
# Find all numbered variants and override them
for i in range(1, 10): # Support up to 9 numbered variants
numberedKey = f"{soundKey}{i}"
if numberedKey in overriddenSounds:
# Use falling_poop1, falling_poop2 etc. if they exist, otherwise use base sound
numberedOverride = f"{overrideKey}{i}" if f"{overrideKey}{i}" in self.sounds else overrideKey
if numberedOverride in self.sounds:
overriddenSounds[numberedKey] = self.sounds[numberedOverride]
# Replace the object's sounds with the overridden version
obj.sounds = overriddenSounds
# Handle skull storm TTS message overrides
if hasattr(obj, 'endMessage') and "end_message" in soundOverrides:
obj.endMessage = soundOverrides["end_message"]
# Handle spider web sound overrides
if hasattr(obj, 'originalSoundName') and obj.originalSoundName == "spiderweb":
# Store hit sound override for use in collision detection
if "hit_sound" in soundOverrides and soundOverrides["hit_sound"] in self.sounds:
obj.hitSoundOverride = soundOverrides["hit_sound"]
# Store enemy type override for spawning
if "spawn_enemy" in soundOverrides:
obj.spawnEnemyOverride = soundOverrides["spawn_enemy"]
# Store enemy stats override for spawning
if "enemy_stats" in soundOverrides:
obj.enemyStatsOverride = soundOverrides["enemy_stats"]
# Store slowdown override (defaults to True if not specified)
if "slowdown" in soundOverrides:
obj.slowdownOverride = soundOverrides["slowdown"]
else:
obj.slowdownOverride = True # Default behavior is to slow down

View File

@@ -110,18 +110,32 @@ class PowerUp(Object):
# Bounce player back (happens even if invincible)
player.xPos -= 3 if player.xPos > self.xPos else -3
# Only apply debuffs if not invincible
# Only apply debuffs if not invincible and slowdown is enabled
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
# Check if slowdown is enabled (default is True)
shouldSlowDown = True
if hasattr(self, "webObject") and hasattr(self.webObject, "slowdownOverride"):
shouldSlowDown = self.webObject.slowdownOverride
# Tell level to spawn a spider
if shouldSlowDown:
# 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 an enemy (spider by default, or override type)
if hasattr(self, "level"):
self.level.spawn_spider(self.xPos, self.yPos)
# Check for enemy spawn override from web object
enemyType = "spider" # Default
enemyStats = None
if hasattr(self, "webObject"):
if hasattr(self.webObject, "spawnEnemyOverride"):
enemyType = self.webObject.spawnEnemyOverride
if hasattr(self.webObject, "enemyStatsOverride"):
enemyStats = self.webObject.enemyStatsOverride
self.level.spawn_web_enemy(self.xPos, self.yPos, enemyType, enemyStats)
# Stop movement sound when collected
if self.channel:

View File

@@ -24,6 +24,9 @@ class SkullStorm(Object):
self.nextSkullDelay = random.randint(self.minFreq, self.maxFreq)
self.playerInRange = False
# Default TTS message (can be overridden)
self.endMessage = "Skull storm ended."
def update(self, currentTime, player):
"""Update all active skulls and potentially spawn new ones."""
if not self.isActive:
@@ -39,7 +42,7 @@ class SkullStorm(Object):
# Player just left range
self.playerInRange = False
play_sound(self.sounds["skull_storm_ends"])
speak("Skull storm ended.")
speak(self.endMessage)
# Clear any active skulls when player leaves the range
for skull in self.activeSkulls[:]: