Made vulnerable and spawn code not depend on the enemy type. Hopefully nothing broke.

This commit is contained in:
Storm Dragon
2025-02-17 12:52:53 -05:00
parent fdb8381603
commit 5b0615db7f
6 changed files with 153 additions and 70 deletions

View File

@@ -80,7 +80,7 @@ Zombie spawn chance is 0-100, higher means more zombies. Item is also optional,
{ {
"x_range": [20, 35], // patrol or hunting range "x_range": [20, 35], // patrol or hunting range
"y": 0, "y": 0,
"enemy_type": "goblin", // goblin, witch, ghoul, boogie_man, ghost, revenant "enemy_type": "goblin",
"health": 4, "health": 4,
"damage": 2, "damage": 2,
"attack_range": 1.5, "attack_range": 1.5,
@@ -93,6 +93,58 @@ Zombie spawn chance is 0-100, higher means more zombies. Item is also optional,
Attacks can be "hunter" or "patrol". The "patrol" option does not use the "turn_threshold" option. The "turn_threshold" option is how quickly the hunting enemy will turn around to attack the player. Hunters will leave their area to pursue the player once he has entered the enemy's range. Attacks can be "hunter" or "patrol". The "patrol" option does not use the "turn_threshold" option. The "turn_threshold" option is how quickly the hunting enemy will turn around to attack the player. Hunters will leave their area to pursue the player once he has entered the enemy's range.
#### Special Enemy Behaviors
Enemies can have special behaviors regardless of their type. Here are some examples:
##### incorporeal Goblin
``` json
{
"x_range": [400, 415],
"y": 0,
"enemy_type": "goblin",
"health": 30,
"damage": 2,
"attack_range": 1,
"has_vulnerability": true,
"is_vulnerable": false,
"vulnerability_duration": 1000,
"invulnerability_duration": 5000,
"speed_multiplier": 0.8,
"attack_cooldown": 1200,
"attack_pattern": {
"type": "hunter",
"turn_threshold": 2
}
}
```
##### Spawning Other Enemies (like revenants)
You can mix and match these behaviors. For an example of a witch who spawns black cats, see "Wicked Quest/13.json"
## Sound Requirements for Special Behaviors
When adding special behaviors to enemies, you'll need corresponding sound files:
For vulnerability system:
- enemy_is_vulnerable.ogg - Sound when enemy becomes vulnerable
For spawning behavior:
- enemy_spawn.ogg (optional) - Sound when spawning new enemies
## Tips for Custom Enemies
- Balance special behaviors carefully
- Test enemy combinations thoroughly
- Consider providing audio cues for special behaviors
- Remember faster enemies should generally do less damage
- Vulnerability systems work best with higher health values
- Spawning enemies should have lower health to compensate
### Hazards ### Hazards
#### Skull Storm #### Skull Storm

View File

@@ -257,16 +257,25 @@
"static": true "static": true
}, },
{ {
"x_range": [400, 420], "type": "spider_web",
"x": 395,
"y": 0
},
{
"x_range": [400, 415],
"y": 0, "y": 0,
"enemy_type": "revenant", "enemy_type": "revenant",
"health": 40, "health": 40,
"damage": 1, "damage": 1,
"attack_range": 1, "attack_range": 1,
"zombie_spawn_cooldown": 2500,
"attack_pattern": { "attack_pattern": {
"type": "patrol" "type": "patrol"
} },
"can_spawn": true,
"spawn_type": "zombie",
"spawn_cooldown": 2500,
"spawn_chance": 100,
"spawn_distance": 5
} }
], ],
"boundaries": { "boundaries": {

View File

@@ -334,7 +334,12 @@
"attack_range": 2, "attack_range": 2,
"attack_pattern": { "attack_pattern": {
"type": "patrol" "type": "patrol"
} },
"can_spawn": true,
"spawn_type": "black_cat",
"spawn_cooldown": 5000,
"spawn_chance": 75,
"spawn_distance": 4
}, },
{ {
"x_range": [405, 495], "x_range": [405, 495],

View File

@@ -327,14 +327,17 @@
"health": 30, "health": 30,
"damage": 2, "damage": 2,
"attack_range": 1, "attack_range": 1,
"has_vulnerability": true,
"is_vulnerable": false,
"vulnerability_duration": 1000, "vulnerability_duration": 1000,
"invulnerability_duration": 5000, "invulnerability_duration": 5000,
"speed_multiplier": 0.8, "speed_multiplier": 0.8,
"attack_cooldown": 1200, "attack_cooldown": 1200,
"attack_pattern": { "attack_pattern": {
"type": "patrol" "type": "hunter",
"turn_threshold": 2
} }
} }
], ],
"boundaries": { "boundaries": {
"left": 0, "left": 0,

View File

@@ -33,39 +33,35 @@ class Enemy(Object):
self.attackCooldown = 1000 # 1 second between attacks self.attackCooldown = 1000 # 1 second between attacks
self._currentX = self.xRange[0] # Initialize current position self._currentX = self.xRange[0] # Initialize current position
# Add spawn configuration
self.canSpawn = kwargs.get('can_spawn', False)
if self.canSpawn:
self.spawnCooldown = kwargs.get('spawn_cooldown', 2000)
self.spawnChance = kwargs.get('spawn_chance', 25)
self.spawnType = kwargs.get('spawn_type', 'zombie') # Default to zombie for backward compatibility
self.spawnDistance = kwargs.get('spawn_distance', 5)
self.lastSpawnTime = 0
# Attack pattern configuration # Attack pattern configuration
self.attackPattern = kwargs.get('attack_pattern', {'type': 'patrol'}) self.attackPattern = kwargs.get('attack_pattern', {'type': 'patrol'})
self.turnThreshold = self.attackPattern.get('turn_threshold', 5) self.turnThreshold = self.attackPattern.get('turn_threshold', 5)
# Initialize vulnerability system
self.hasVulnerabilitySystem = kwargs.get('has_vulnerability', False)
if self.hasVulnerabilitySystem:
self.isVulnerable = kwargs.get('is_vulnerable', False) # For enemies with vulnerability, default to invulnerable
self.vulnerabilityTimer = 0
self.vulnerabilityDuration = kwargs.get('vulnerability_duration', 1000)
self.invulnerabilityDuration = kwargs.get('invulnerability_duration', 5000)
else:
self.isVulnerable = True
# Enemy type specific adjustments # Enemy type specific adjustments
if enemyType == "zombie": if enemyType == "zombie":
self.movementSpeed *= 0.6 # Zombies are slower self.movementSpeed *= 0.6 # Zombies are slower
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 == "ghost":
self.isVulnerable = False
self.vulnerabilityTimer = 0
self.vulnerabilityDuration = kwargs.get('vulnerability_duration', 3000) # Default 3 seconds
self.invulnerabilityDuration = kwargs.get('invulnerability_duration', 5000) # Default 5 seconds
self.movementSpeed *= kwargs.get('speed_multiplier', 0.8) # Default 80% speed
self.health = kwargs.get('health', 3) # Default 3 HP
self.damage = kwargs.get('damage', 2) # Default 2 damage
self.attackRange = kwargs.get('attack_range', 1) # Use provided or default 1
self.attackCooldown = kwargs.get('attack_cooldown', 1200) # Default 1.2 seconds
self.attackPattern = kwargs.get('attack_pattern', {
'type': 'hunter',
'turn_threshold': 2
})
elif enemyType == "revenant":
self.movementSpeed *= 0.7 # Slower than normal
self.damage = 1
self.health = kwargs.get('health', 40)
self.attackCooldown = 1500 # Slower direct attacks
self.zombieSpawnCooldown = kwargs.get('zombie_spawn_cooldown', 2000) # 2 seconds between spawns
self.lastZombieSpawn = 0
self.zombieSpawnDistance = 5
self.attackPattern = kwargs.get('attack_pattern', {'type': 'patrol'})
elif enemyType == "spider": elif enemyType == "spider":
speedMultiplier = kwargs.get('speed_multiplier', 2.0) speedMultiplier = kwargs.get('speed_multiplier', 2.0)
self.movementSpeed *= speedMultiplier # Spiders are faster self.movementSpeed *= speedMultiplier # Spiders are faster
@@ -99,16 +95,21 @@ class Enemy(Object):
if not self.isActive or self.health <= 0: if not self.isActive or self.health <= 0:
return return
# Ghost vulnerability state management # Set initial sound for enemies with vulnerability system
if self.enemyType == "ghost": if self.hasVulnerabilitySystem and self.channel is None:
soundName = f"{self.enemyType}_is_vulnerable" if self.isVulnerable else self.enemyType
self.channel = obj_play(self.sounds, soundName, player.xPos, self.xPos)
# Enemy vulnerability state management
if self.hasVulnerabilitySystem:
if self.isVulnerable and (currentTime - self.vulnerabilityTimer > self.vulnerabilityDuration): if self.isVulnerable and (currentTime - self.vulnerabilityTimer > self.vulnerabilityDuration):
# Switch to invulnerable # Switch to invulnerable
self.isVulnerable = False self.isVulnerable = False
self.vulnerabilityTimer = currentTime self.vulnerabilityTimer = currentTime
# Change sound back to base ghost sound # Change sound back to base enemy sound
if self.channel: if self.channel:
obj_stop(self.channel) obj_stop(self.channel)
self.channel = obj_play(self.sounds, "ghost", player.xPos, self.xPos) self.channel = obj_play(self.sounds, self.enemyType, player.xPos, self.xPos)
elif not self.isVulnerable and (currentTime - self.vulnerabilityTimer > self.invulnerabilityDuration): elif not self.isVulnerable and (currentTime - self.vulnerabilityTimer > self.invulnerabilityDuration):
# Switch to vulnerable # Switch to vulnerable
self.isVulnerable = True self.isVulnerable = True
@@ -116,7 +117,7 @@ class Enemy(Object):
# Change to vulnerable sound # Change to vulnerable sound
if self.channel: if self.channel:
obj_stop(self.channel) obj_stop(self.channel)
self.channel = obj_play(self.sounds, "ghost_is_vulnerable", player.xPos, self.xPos) self.channel = obj_play(self.sounds, f"{self.enemyType}_is_vulnerable", player.xPos, self.xPos)
# Check if player has entered territory # Check if player has entered territory
if not self.hunting: if not self.hunting:
@@ -151,33 +152,40 @@ class Enemy(Object):
if self.can_attack(currentTime, player): if self.can_attack(currentTime, player):
self.attack(currentTime, player) self.attack(currentTime, player)
if self.enemyType == "revenant" and self.hunting: # Only spawn when player enters territory if self.canSpawn:
# Check if it's time to spawn a zombie if currentTime - self.lastSpawnTime >= self.spawnCooldown:
if currentTime - self.lastZombieSpawn >= self.zombieSpawnCooldown: distanceToPlayer = abs(player.xPos - self.xPos)
# Spawn zombies relative to player position, not revenant if distanceToPlayer <= 12: # Within audible range
spawnDirection = random.choice([-1, 1]) # Random chance to spawn
spawnX = player.xPos + (spawnDirection * self.zombieSpawnDistance) if random.randint(1, 100) <= self.spawnChance:
# Spawn relative to player position
# Ensure spawn point is within level boundaries spawnDirection = random.choice([-1, 1])
spawnX = max(self.level.leftBoundary, min(spawnX, self.level.rightBoundary)) spawnX = player.xPos + (spawnDirection * self.spawnDistance)
# Create new zombie # Ensure spawn point is within level boundaries
zombie = Enemy( spawnX = max(self.level.leftBoundary, min(spawnX, self.level.rightBoundary))
[spawnX, spawnX], # Single point range for spawn
self.yPos, # Create new enemy of specified type
"zombie", spawned = Enemy(
self.sounds, [spawnX, spawnX], # Single point range for spawn
self.level self.yPos,
) self.spawnType,
self.sounds,
# Add to level's enemies self.level,
self.level.enemies.append(zombie) health=4, # Default health for spawned enemies
self.lastZombieSpawn = currentTime damage=2, # Default damage for spawned enemies
attack_range=1 # Default range for spawned enemies
# Play spawn sound and speak message )
if 'revenant_spawn_zombie' in self.sounds:
self.sounds['revenant_spawn_zombie'].play() # Add to level's enemies
speak("Zombie spawned") self.level.enemies.append(spawned)
self.lastSpawnTime = currentTime
# Play spawn sound if available
spawnSound = f"{self.enemyType}_spawn_{self.spawnType}"
if spawnSound in self.sounds:
self.sounds[spawnSound].play()
speak(f"{self.spawnType} spawned")
# Check for attack opportunity # Check for attack opportunity
if self.can_attack(currentTime, player): if self.can_attack(currentTime, player):
@@ -218,8 +226,8 @@ class Enemy(Object):
def take_damage(self, amount): def take_damage(self, amount):
"""Handle enemy taking damage""" """Handle enemy taking damage"""
# Ghost can only take damage when vulnerable # Enemy can only take damage when vulnerable
if self.enemyType == "ghost" and not self.isVulnerable: if not self.isVulnerable:
return return
self.health -= amount self.health -= amount

View File

@@ -147,7 +147,12 @@ class Level:
damage=obj.get("damage", 1), damage=obj.get("damage", 1),
attack_range=obj.get("attack_range", 1), attack_range=obj.get("attack_range", 1),
movement_range=obj.get("movement_range", 5), movement_range=obj.get("movement_range", 5),
attack_pattern=obj.get("attack_pattern", {'type': 'patrol'}) # Add this line attack_pattern=obj.get("attack_pattern", {'type': 'patrol'}),
can_spawn=obj.get("can_spawn", False),
spawn_type=obj.get("spawn_type", "zombie"),
spawn_cooldown=obj.get("spawn_cooldown", 2000),
spawn_chance=obj.get("spawn_chance", 25),
spawn_distance=obj.get("spawn_distance", 5)
) )
self.enemies.append(enemy) self.enemies.append(enemy)
else: else:
@@ -215,11 +220,12 @@ class Level:
continue continue
enemy.update(currentTime, self.player) enemy.update(currentTime, self.player)
if enemy.channel is None or not enemy.channel.get_busy(): if not enemy.hasVulnerabilitySystem:
enemy.channel = obj_play(self.sounds, enemy.enemyType, self.player.xPos, enemy.xPos) if enemy.channel is None or not enemy.channel.get_busy():
if enemy.channel is not None: enemy.channel = obj_play(self.sounds, enemy.enemyType, self.player.xPos, enemy.xPos)
enemy.channel = obj_update(enemy.channel, self.player.xPos, enemy.xPos) if enemy.channel is not None:
enemy.channel = obj_update(enemy.channel, self.player.xPos, enemy.xPos)
# Update catapults # Update catapults
for obj in self.objects: for obj in self.objects: