Skip to content

Commit 50be271

Browse files
committed
Create Dark Knight enemy.
It uses a new AI that patrols the map until it finds the player.
1 parent 7e87c8a commit 50be271

File tree

4 files changed

+75
-4
lines changed

4 files changed

+75
-4
lines changed

actor_factories.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,19 @@
186186
level=Level(xp_given=70),
187187
status=Status(status_effects=[(Confused(duration=3), 0.3)]),
188188
)
189+
190+
dark_knight = Actor(
191+
char="K",
192+
color=(0, 0, 0), # Black color for the "dark" knight
193+
name="Dark Knight",
194+
ai_cls=components.ai.DarkKnightAI, # You'll need to define this AI class
195+
equipment=Equipment(), # Add any specific equipment the Dark Knight should have
196+
fighter=Fighter(
197+
hp=25, base_defense=40, base_power=6, base_damage=(2, 6), base_speed=90
198+
), # Adjust these values as needed for your game balance
199+
inventory=Inventory(
200+
capacity=0
201+
), # Adjust capacity if the Dark Knight should be able to carry items
202+
level=Level(xp_given=120), # Adjust XP given upon defeat
203+
status=Status(), # Add any status effects the Dark Knight should have
204+
)

components/ai.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from exceptions import Impossible
2525

2626
if TYPE_CHECKING:
27+
from game_map import GameMap
2728
from entity import Actor
2829

2930

@@ -132,6 +133,38 @@ def get_action(self) -> Action:
132133
return WaitAction(self.entity)
133134

134135

136+
class PatrollingMeleeEnemyAI(BasicMeleeEnemyAI):
137+
def __init__(self, *args, **kwargs):
138+
super().__init__(*args, **kwargs)
139+
self.target = None
140+
141+
def get_action(self) -> Action:
142+
143+
if self.engine.game_map.visible[self.entity.x, self.entity.y]:
144+
# If the player is in sight, behave like the BasicMeleeEnemyAI
145+
return super().get_action()
146+
else:
147+
# Otherwise, patrol the map
148+
if self.target is None or (self.entity.x, self.entity.y) == self.target:
149+
# If there is no current target or the current target has been reached, find a new target
150+
walkable_tiles = self.engine.game_map.get_walkable_tiles_from_position(
151+
(self.entity.x, self.entity.y), self.engine.game_map
152+
)
153+
self.target = random.choice(walkable_tiles)
154+
155+
# Move towards the target
156+
self.path = self.get_path_to(self.target[0], self.target[1])
157+
158+
if self.path:
159+
dest_x, dest_y = self.path.pop(0)
160+
161+
return MovementAction(
162+
self.entity,
163+
dest_x - self.entity.x,
164+
dest_y - self.entity.y,
165+
)
166+
167+
135168
class MoveToTile(BaseAI):
136169
"""AI that moves to a specific tile."""
137170

@@ -343,3 +376,8 @@ def is_tile_empty(self, tile: Tuple[int, int]) -> bool:
343376
for entity in self.engine.game_map.entities
344377
if entity.blocks_movement and entity.x == x and entity.y == y
345378
)
379+
380+
381+
class DarkKnightAI(PatrollingMeleeEnemyAI):
382+
def __init__(self, entity: Actor):
383+
super().__init__(entity)

game_map.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
import itertools
33

44
import random
5-
from re import S
6-
from typing import Iterable, Iterator, Optional, TYPE_CHECKING
7-
from unittest import skip
5+
from typing import Iterable, Iterator, List, Optional, TYPE_CHECKING, Tuple
86

97
import numpy as np
108
import tcod # type: ignore
@@ -149,6 +147,21 @@ def in_bounds(self, x: int, y: int) -> bool:
149147
"""Return True if x and y are inside of the bounds of this map."""
150148
return 0 <= x < self.width and 0 <= y < self.height
151149

150+
def get_walkable_tiles_from_position(
151+
self, origin: Tuple[int, int], game_map: GameMap
152+
) -> List[Tuple[int, int]]:
153+
"""Perform a flood-fill to find which areas the current position has access to."""
154+
walkable_tiles = []
155+
stack = [(origin[0], origin[1])]
156+
while stack:
157+
x, y = stack.pop()
158+
if (x, y) not in walkable_tiles and game_map.tiles["walkable"][x, y]:
159+
walkable_tiles.append((x, y))
160+
stack.extend(
161+
[(x + dx, y + dy) for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]]
162+
)
163+
return walkable_tiles
164+
152165
def get_walkable_adjacent_tiles(self, x: int, y: int) -> Iterator[tuple[int, int]]:
153166
"""Get all walkable adjacent tiles to the given (x, y) coordinate."""
154167
for x_offset, y_offset in itertools.product((-1, 0, 1), (-1, 0, 1)):

map_gen/parameters.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,11 @@
114114
(actor_factories.wolf, 20),
115115
(actor_factories.werewolf, 5),
116116
],
117-
6: [(actor_factories.vampire, 10), (actor_factories.hound, 40)],
117+
6: [
118+
(actor_factories.vampire, 10),
119+
(actor_factories.hound, 40),
120+
(actor_factories.dark_knight, 30),
121+
],
118122
7: [
119123
(actor_factories.ghoul, 40),
120124
(actor_factories.brute_zombie, 35),

0 commit comments

Comments
 (0)