Add movement AI to npcs
This commit is contained in:
parent
50ddf8cc01
commit
be433a9449
@ -34,6 +34,11 @@ class EscapeAction(Action):
|
||||
raise SystemExit()
|
||||
|
||||
|
||||
class WaitAction(Action):
|
||||
def perform(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class ActionWithDirection(Action):
|
||||
def __init__(self, entity, dx: int, dy: int):
|
||||
super().__init__(entity)
|
||||
|
@ -1,15 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, TYPE_CHECKING
|
||||
|
||||
import numpy as np # type: ignore
|
||||
import tcod
|
||||
|
||||
from actions import Action
|
||||
from actions import Action, MeleeAction, MovementAction, WaitAction
|
||||
from components.base_component import BaseComponent
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from entity import Actor
|
||||
|
||||
|
||||
class BaseAI(Action, BaseComponent):
|
||||
entity: Actor
|
||||
|
||||
def get_path_to(self, dest_x: int, dest_y: int) -> List[Tuple[int, int]]:
|
||||
"""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]].
|
||||
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
|
||||
|
||||
def handle_enemy_turns(self) -> None:
|
||||
for entity in self.game_map.entities - {self.player}:
|
||||
print(f'The {entity.name} wonders when it will get to take a real turn.')
|
||||
for entity in set(self.game_map.actors) - {self.player}:
|
||||
if entity.ai:
|
||||
entity.ai.perform()
|
||||
|
||||
def update_fov(self) -> None:
|
||||
"""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
|
||||
|
||||
import copy
|
||||
from typing import Optional, Tuple, TypeVar, TYPE_CHECKING
|
||||
from typing import Optional, Tuple, Type, TypeVar, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from components.ai import BaseAI
|
||||
from components.fighter import Fighter
|
||||
from game_map import GameMap
|
||||
|
||||
T = TypeVar("T", bound="Entity")
|
||||
@ -61,3 +63,35 @@ class Entity:
|
||||
# Move the entity by a given amount
|
||||
self.x += dx
|
||||
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="@",
|
||||
color=(255, 255, 255),
|
||||
name="PLayer",
|
||||
blocks_movement=True
|
||||
ai_cls=HostileEnemy,
|
||||
fighter=Fighter(hp=30, defense=2, power=5),
|
||||
)
|
||||
|
||||
orc = Entity(
|
||||
orc = Actor(
|
||||
char="o",
|
||||
color=(63, 127, 63),
|
||||
name="Orc",
|
||||
blocks_movement=True
|
||||
ai_cls=HostileEnemy,
|
||||
fighter=Fighter(hp=10, defense=0, power=3),
|
||||
)
|
||||
troll = Entity(
|
||||
troll = Actor(
|
||||
char="T",
|
||||
color=(0, 127, 0),
|
||||
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 typing import Iterable, Optional, TYPE_CHECKING
|
||||
from typing import Iterable, Iterator, Optional, TYPE_CHECKING
|
||||
|
||||
import numpy as np # type: ignore
|
||||
from tcod.console import Console
|
||||
|
||||
from entity import Actor
|
||||
import tile_types
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -36,6 +37,15 @@ class GameMap:
|
||||
order="F"
|
||||
) # 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(
|
||||
self,
|
||||
location_x: int,
|
||||
@ -51,6 +61,13 @@ class GameMap:
|
||||
|
||||
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:
|
||||
"""Return True if x and y are inside the bounds of the map."""
|
||||
return 0 <= x < self.width and 0 <= y < self.height
|
||||
|
Loading…
Reference in New Issue
Block a user