diff --git a/components/ai.py b/components/ai.py index e77c061..51d7828 100644 --- a/components/ai.py +++ b/components/ai.py @@ -6,14 +6,12 @@ import numpy as np # type: ignore import tcod 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 +class BaseAI(Action): def get_path_to(self, dest_x: int, dest_y: int) -> List[Tuple[int, int]]: """Compute and return a path to the target position. diff --git a/components/base_component.py b/components/base_component.py index 58a8b99..b3876d0 100644 --- a/components/base_component.py +++ b/components/base_component.py @@ -5,11 +5,16 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from engine import Engine from entity import Entity + from game_map import GameMap class BaseComponent: - entity: Entity # Owning entity instance + parent: Entity # Owning entity instance + + @property + def gamemap(self) -> GameMap: + return self.parent.gamemap @property def engine(self) -> Engine: - return self.entity.gamemap.engine + return self.gamemap.engine diff --git a/components/fighter.py b/components/fighter.py index acb4fed..17785fe 100644 --- a/components/fighter.py +++ b/components/fighter.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: class Fighter(BaseComponent): - entity: Actor + parent: Actor def __init__(self, hp: int, defense: int, power: int): self.max_hp = hp @@ -27,23 +27,23 @@ class Fighter(BaseComponent): @hp.setter def hp(self, value: int) -> None: self._hp = max(0, min(value, self.max_hp)) - if self._hp == 0 and self.entity.ai: + if self._hp == 0 and self.parent.ai: self.die() def die(self) -> None: - if self.engine.player is self.entity: + if self.engine.player is self.parent: death_message = "You died!" death_message_color = color.player_die self.engine.event_handler = GameOverEventHandler(self.engine) else: - death_message = f"{self.entity.name} is dead!" + death_message = f"{self.parent.name} is dead!" death_message_color = color.enemy_die - self.entity.char = "%" - self.entity.color = (191, 0, 0) - self.entity.blocks_movement = False - self.entity.ai = None - self.entity.name = f"remains of {self.entity.name}" - self.entity.render_order = RenderOrder.CORPSE + self.parent.char = "%" + self.parent.color = (191, 0, 0) + self.parent.blocks_movement = False + self.parent.ai = None + self.parent.name = f"remains of {self.parent.name}" + self.parent.render_order = RenderOrder.CORPSE self.engine.message_log.add_message(death_message, death_message_color) diff --git a/entity.py b/entity.py index 30b7a23..6b8c823 100644 --- a/entity.py +++ b/entity.py @@ -18,11 +18,11 @@ class Entity: A generic object to represent players, enemies, items, etc. """ - gamemap: GameMap + parent: GameMap def __init__( self, - gamemap: Optional[GameMap] = None, + parent: Optional[GameMap] = None, x: int = 0, y: int = 0, char: str = "?", @@ -38,17 +38,21 @@ class Entity: self.name = name self.blocks_movement = blocks_movement self.render_order = render_order - if gamemap: + if parent: # If gamemap isn't provided now, it will be later. - self.gamemap = gamemap - gamemap.entities.add(self) + self.parent = parent + parent.entities.add(self) + + @property + def gamemap(self) -> GameMap: + return self.parent.gamemap def spawn(self: T, gamemap: GameMap, x: int, y: int) -> T: """Spawn a copy of this instance at the given location.""" clone = copy.deepcopy(self) clone.x = x clone.y = y - clone.gamemap = gamemap + clone.parent = gamemap gamemap.entities.add(clone) return clone @@ -57,10 +61,11 @@ class Entity: self.x = x self.y = y if gamemap: - if hasattr(self, "gamemap"): # Possibly uninitialized - self.gamemap.entities.remove(self) + if hasattr(self, "parent"): # Possibly uninitialized + if self.parent is self.gamemap: + self.gamemap.entities.remove(self) - self.gamemap = gamemap + self.parent = gamemap gamemap.entities.add(self) def move(self, dx: int, dy: int): @@ -94,7 +99,7 @@ class Actor(Entity): self.ai: Optional[BaseAI] = ai_cls(self) self.fighter = fighter - self.fighter.entity = self + self.fighter.parent = self @property def is_alive(self) -> bool: diff --git a/game_map.py b/game_map.py index d87c1a1..dd4b841 100644 --- a/game_map.py +++ b/game_map.py @@ -37,6 +37,10 @@ class GameMap: order="F" ) # Tiles the player has seen before + @property + def gamemap(self) -> GameMap: + return self + @property def actors(self) -> Iterator[Actor]: """Iterate over this map's living actors.""" diff --git a/message_log.py b/message_log.py index d9aff5d..3c3138c 100644 --- a/message_log.py +++ b/message_log.py @@ -1,4 +1,4 @@ -from typing import List, Reversible, Tuple +from typing import Iterable, List, Reversible, Tuple import textwrap import tcod @@ -57,7 +57,18 @@ class MessageLog: self.render_messages(console, x, y, width, height, self.messages) @staticmethod + def wrap(string: str, width: int) -> Iterable[str]: + """Return a wrapped text message.""" + for line in string.splitlines(): # Handle newlines in messages. + yield from textwrap.wrap( + line, + width, + expand_tabs=True + ) + + @classmethod def render_messages( + cls, console: tcod.Console, x: int, y: int, @@ -72,7 +83,7 @@ class MessageLog: y_offset = height - 1 for message in reversed(messages): - for line in reversed(textwrap.wrap(message.full_text, width)): + for line in reversed(list(cls.wrap(message.full_text, width))): console.print(x=x, y=y + y_offset, string=line, fg=message.fg) y_offset -= 1 if y_offset < 0: