Made vulnerable and spawn code not depend on the enemy type. Hopefully nothing broke.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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],
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
122
src/enemy.py
122
src/enemy.py
@@ -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
|
||||||
|
|||||||
18
src/level.py
18
src/level.py
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user