Add movement AI to npcs
This commit is contained in:
parent
50ddf8cc01
commit
be433a9449
@ -34,6 +34,11 @@ class EscapeAction(Action):
|
|||||||
raise SystemExit()
|
raise SystemExit()
|
||||||
|
|
||||||
|
|
||||||
|
class WaitAction(Action):
|
||||||
|
def perform(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ActionWithDirection(Action):
|
class ActionWithDirection(Action):
|
||||||
def __init__(self, entity, dx: int, dy: int):
|
def __init__(self, entity, dx: int, dy: int):
|
||||||
super().__init__(entity)
|
super().__init__(entity)
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple, TYPE_CHECKING
|
||||||
|
|
||||||
import numpy as np # type: ignore
|
import numpy as np # type: ignore
|
||||||
import tcod
|
import tcod
|
||||||
|
|
||||||
from actions import Action
|
from actions import Action, MeleeAction, MovementAction, WaitAction
|
||||||
from components.base_component import BaseComponent
|
from components.base_component import BaseComponent
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from entity import Actor
|
||||||
|
|
||||||
|
|
||||||
class BaseAI(Action, BaseComponent):
|
class BaseAI(Action, BaseComponent):
|
||||||
|
entity: Actor
|
||||||
|
|
||||||
def get_path_to(self, dest_x: int, dest_y: int) -> List[Tuple[int, int]]:
|
def get_path_to(self, dest_x: int, dest_y: int) -> List[Tuple[int, int]]:
|
||||||
"""Compute and return a path to the target position.
|
"""Compute and return a path to the target position.
|
||||||
|
|
||||||
@ -38,3 +43,33 @@ class BaseAI(Action, BaseComponent):
|
|||||||
|
|
||||||
# Convert from List[List[int]] to List[Tuple[int, int]].
|
# Convert from List[List[int]] to List[Tuple[int, int]].
|
||||||
return [(index[0], index[1]) for index in path]
|
return [(index[0], index[1]) for index in path]
|
||||||
|
|
||||||
|
|
||||||
|
class HostileEnemy(BaseAI):
|
||||||
|
def __init__(self, entity: Actor):
|
||||||
|
super().__init__(entity)
|
||||||
|
self.path: List[Tuple[int, int]] = []
|
||||||
|
|
||||||
|
def perform(self) -> None:
|
||||||
|
target = self.engine.player
|
||||||
|
dx = target.x - self.entity.x
|
||||||
|
dy = target.y - self.entity.y
|
||||||
|
|
||||||
|
# Chebyshev distance
|
||||||
|
distance = max(abs(dx), abs(dy))
|
||||||
|
|
||||||
|
if self.engine.game_map.visible[self.entity.x, self.entity.y]:
|
||||||
|
if distance <= 1:
|
||||||
|
return MeleeAction(self.entity, dx, dy).perform()
|
||||||
|
|
||||||
|
self.path = self.get_path_to(target.x, target.y)
|
||||||
|
|
||||||
|
if self.path:
|
||||||
|
dest_x, dest_y = self.path.pop(0)
|
||||||
|
return MovementAction(
|
||||||
|
self.entity,
|
||||||
|
dest_x - self.entity.x,
|
||||||
|
dest_y - self.entity.y
|
||||||
|
).perform()
|
||||||
|
|
||||||
|
return WaitAction(self.entity).perform()
|
@ -24,8 +24,9 @@ class Engine:
|
|||||||
self.player = player
|
self.player = player
|
||||||
|
|
||||||
def handle_enemy_turns(self) -> None:
|
def handle_enemy_turns(self) -> None:
|
||||||
for entity in self.game_map.entities - {self.player}:
|
for entity in set(self.game_map.actors) - {self.player}:
|
||||||
print(f'The {entity.name} wonders when it will get to take a real turn.')
|
if entity.ai:
|
||||||
|
entity.ai.perform()
|
||||||
|
|
||||||
def update_fov(self) -> None:
|
def update_fov(self) -> None:
|
||||||
"""Recompute the visible area based on the player's point of view."""
|
"""Recompute the visible area based on the player's point of view."""
|
||||||
|
36
entity.py
36
entity.py
@ -1,9 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
from typing import Optional, Tuple, TypeVar, TYPE_CHECKING
|
from typing import Optional, Tuple, Type, TypeVar, TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from components.ai import BaseAI
|
||||||
|
from components.fighter import Fighter
|
||||||
from game_map import GameMap
|
from game_map import GameMap
|
||||||
|
|
||||||
T = TypeVar("T", bound="Entity")
|
T = TypeVar("T", bound="Entity")
|
||||||
@ -61,3 +63,35 @@ class Entity:
|
|||||||
# Move the entity by a given amount
|
# Move the entity by a given amount
|
||||||
self.x += dx
|
self.x += dx
|
||||||
self.y += dy
|
self.y += dy
|
||||||
|
|
||||||
|
|
||||||
|
class Actor(Entity):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
x: int = 0,
|
||||||
|
y: int = 0,
|
||||||
|
char: str = "?",
|
||||||
|
color: Tuple[int, int, int] = (255, 255, 255),
|
||||||
|
name: str = "<Unamed>",
|
||||||
|
ai_cls: Type[BaseAI],
|
||||||
|
fighter: Fighter
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
x=x,
|
||||||
|
y=y,
|
||||||
|
char=char,
|
||||||
|
color=color,
|
||||||
|
name=name,
|
||||||
|
blocks_movement=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.ai = Optional[BaseAI] = ai_cls(self)
|
||||||
|
|
||||||
|
self.fighter = fighter
|
||||||
|
self.fighter.entity = self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_alive(self) -> bool:
|
||||||
|
"""Returns True as long as this actor can perform actions."""
|
||||||
|
return bool(self.ai)
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
from entity import Entity
|
from components.ai import HostileEnemy
|
||||||
|
from components.fighter import Fighter
|
||||||
|
from entity import Actor
|
||||||
|
|
||||||
player = Entity(
|
player = Actor(
|
||||||
char="@",
|
char="@",
|
||||||
color=(255, 255, 255),
|
color=(255, 255, 255),
|
||||||
name="PLayer",
|
name="PLayer",
|
||||||
blocks_movement=True
|
ai_cls=HostileEnemy,
|
||||||
|
fighter=Fighter(hp=30, defense=2, power=5),
|
||||||
)
|
)
|
||||||
|
|
||||||
orc = Entity(
|
orc = Actor(
|
||||||
char="o",
|
char="o",
|
||||||
color=(63, 127, 63),
|
color=(63, 127, 63),
|
||||||
name="Orc",
|
name="Orc",
|
||||||
blocks_movement=True
|
ai_cls=HostileEnemy,
|
||||||
|
fighter=Fighter(hp=10, defense=0, power=3),
|
||||||
)
|
)
|
||||||
troll = Entity(
|
troll = Actor(
|
||||||
char="T",
|
char="T",
|
||||||
color=(0, 127, 0),
|
color=(0, 127, 0),
|
||||||
name="Troll",
|
name="Troll",
|
||||||
blocks_movement=True
|
ai_cls=HostileEnemy,
|
||||||
|
fighter=Fighter(hp=16, defense=1, power=4)
|
||||||
)
|
)
|
||||||
|
19
game_map.py
19
game_map.py
@ -1,10 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Iterable, Optional, TYPE_CHECKING
|
from typing import Iterable, Iterator, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
import numpy as np # type: ignore
|
import numpy as np # type: ignore
|
||||||
from tcod.console import Console
|
from tcod.console import Console
|
||||||
|
|
||||||
|
from entity import Actor
|
||||||
import tile_types
|
import tile_types
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -36,6 +37,15 @@ class GameMap:
|
|||||||
order="F"
|
order="F"
|
||||||
) # Tiles the player has seen before
|
) # Tiles the player has seen before
|
||||||
|
|
||||||
|
@property
|
||||||
|
def actors(self) -> Iterator[Actor]:
|
||||||
|
"""Iterate over this map's living actors."""
|
||||||
|
yield from (
|
||||||
|
entity
|
||||||
|
for entity in self.entities
|
||||||
|
if isinstance(entity, Actor) and entity.is_alive
|
||||||
|
)
|
||||||
|
|
||||||
def get_blocking_entity_at_location(
|
def get_blocking_entity_at_location(
|
||||||
self,
|
self,
|
||||||
location_x: int,
|
location_x: int,
|
||||||
@ -51,6 +61,13 @@ class GameMap:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_actor_at_location(self, x: int, y: int) -> Optional[Actor]:
|
||||||
|
for actor in self.actors:
|
||||||
|
if actor.x == x and actor.y == y:
|
||||||
|
return actor
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def in_bounds(self, x: int, y: int) -> bool:
|
def in_bounds(self, x: int, y: int) -> bool:
|
||||||
"""Return True if x and y are inside the bounds of the map."""
|
"""Return True if x and y are inside the bounds of the map."""
|
||||||
return 0 <= x < self.width and 0 <= y < self.height
|
return 0 <= x < self.width and 0 <= y < self.height
|
||||||
|
Loading…
Reference in New Issue
Block a user