from __future__ import annotations from typing import List, Tuple, TYPE_CHECKING import random import numpy as np import tcod from actions import ( BaseAction, MeleeAction, MovementAction, BumpAction, WaitAction, ) if TYPE_CHECKING: from entity import Entity class BaseAI: """Base type for creature AI, with various utilities.""" entity: Entity def __init__(self, entity: Entity): #grab the entity self.entity = entity #utilities self.path: List[Tuple[int, int]] = [] def process(self) -> BaseAction: """Allow the AI to think.""" raise NotImplementedError() #utils def generate_path_to(self, dest_x: int, dest_y: int) -> List[Tuple[int, int]]: #copy the walkables cost = np.array(self.entity.floor_map.tiles["walkable"], dtype=np.int8) #higher numbers deter path-finding this way for entity in self.entity.floor_map.entities: if not entity.walkable and cost[entity.x, entity.y]: cost[entity.x, entity.y] += 100 graph = tcod.path.SimpleGraph(cost=cost, cardinal=10, diagonal=14) pathfinder = tcod.path.Pathfinder(graph) pathfinder.add_root((self.entity.x, self.entity.y)) #start pos #make the path, omitting the start pos path: List[List[int]] = pathfinder.path_to((dest_x, dest_y))[1:].tolist() #return the path, after mapping it to a list of tuples return [(index[0], index[1]) for index in path] class AggressiveWhenSeen(BaseAI): """ If I'm close enough to the player, attack. If I see where the player is, approach that point. If I know where the player has been, approach that point. If all else fails, idle. """ def process(self) -> BaseAction: target = self.entity.floor_map.player #TODO: friendly fire? xdir = target.x - self.entity.x ydir = target.y - self.entity.y distance = max(abs(xdir), abs(ydir)) #if the player can see me, create or update my path if self.entity.floor_map.visible[self.entity.x, self.entity.y]: #if I'm close enough to attack if distance <= 1: return MeleeAction(self.entity, xdir, ydir) self.path = self.generate_path_to(target.x, target.y) #if I have a path to follow, regardless of current visability if self.path: dest_x, dest_y = self.path.pop(0) return MovementAction( entity = self.entity, xdir = dest_x - self.entity.x, ydir = dest_y - self.entity.y, ) #idle return WaitAction(self.entity) class ConfusedAI(BaseAI): """ """ parent_ai: BaseAI duration: int def __init__(self, entity: Entity, duration: int): super().__init__(entity) self.parent_ai = entity.ai self.duration = duration def process(self) -> BaseAction: if self.duration <= 0: self.entity.ai = self.parent_ai return self.entity.ai.process() self.duration -= 1 xdir, ydir = random.choice( [ [ -1, -1], [ -1, 0], [ -1, 1], [ 0, -1], #[ 0, 0], [ 0, 1], [ 1, -1], [ 1, 0], [ 1, 1], ] ) #Oh, this allows for friendly fire! return BumpAction(self.entity, xdir, ydir)