Move the renderable part of Entity to a Renderable component

Move symbol, render order, foreground, and background properties on Entity
to a new Component called Renderable.
This commit is contained in:
Eryn Wells 2023-03-11 00:58:58 -08:00
parent 01b549bc6e
commit def79386d8
5 changed files with 80 additions and 71 deletions

View file

@ -1,7 +1,8 @@
# Eryn Wells <eryn@erynwells.me>
import random
from typing import Optional
from enum import Enum
from typing import Optional, Tuple
class Component:
@ -75,3 +76,42 @@ class Fighter(Component):
def _reset_passive_heal_clock(self) -> None:
self.__ticks_since_last_passive_heal = 0
self.__ticks_for_next_passive_heal = random.randint(30, 70)
class Renderable(Component):
class Order(Enum):
'''
These values indicate the order that an entity with a Renderable
component should be rendered. Higher values are rendered later and
therefore on top of items with lower orderings.
'''
ITEM = 1000
ACTOR = 2000
HERO = 3000
def __init__(
self,
symbol: str,
order: Order = Order.ACTOR,
fg: Optional[Tuple[int, int, int]] = None,
bg: Optional[Tuple[int, int, int]] = None):
if len(symbol) != 1:
raise ValueError(f'Symbol string "{symbol}" must be of length 1')
self.symbol = symbol
'''The symbol that represents this renderable on the map'''
self.order = order
'''
Specifies the layer at which this entity is rendered. Higher values are
rendered later, and thus on top of lower values.
'''
self.foreground = fg
'''The foreground color of the entity'''
self.background = bg
'''The background color of the entity'''
def __repr__(self):
return f'{self.__class__.__name__}("{self.symbol}", {self.order}, {self.foreground}, {self.background})'

View file

@ -165,7 +165,7 @@ class Engine:
if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.map.visible[tuple(action.actor.position)]:
if result.alternate:
alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]'
alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor}]'
else:
alternate_string = str(result.alternate)
log.ACTIONS_TREE.info(

View file

@ -45,7 +45,8 @@ class Interface:
hero = self.engine.hero
self.info_window.update_hero(hero)
sorted_entities = sorted(self.engine.entities, key=lambda e: e.render_order.value)
sorted_entities = sorted(filter(lambda e: e.renderable is not None, self.engine.entities),
key=lambda e: e.renderable.order.value)
self.map_window.entities = sorted_entities
def draw(self):

View file

@ -190,6 +190,10 @@ class MapWindow(Window):
if not self.map.point_is_visible(entity_position):
continue
renderable = ent.renderable
if not renderable:
continue
# Entity positions are relative to the (0, 0) point of the Map. In
# order to render them in the correct position in the console, we
# need to transform them into viewport-relative coordinates.
@ -200,6 +204,6 @@ class MapWindow(Window):
console.print(
x=position.x,
y=position.y,
string=ent.symbol,
fg=ent.foreground,
string=renderable.symbol,
fg=renderable.foreground,
bg=tuple(map_tile_at_entity_position['bg'][:3]))

View file

@ -2,13 +2,12 @@
'''Defines a number of high-level game objects. The parent class of all game objects is the Entity class.'''
from enum import Enum
from typing import TYPE_CHECKING, Optional, Tuple, Type
from typing import TYPE_CHECKING, Optional, Type
import tcod
from . import items
from .components import Fighter
from .components import Fighter, Renderable
from .geometry import Point
from .monsters import Species
@ -16,16 +15,6 @@ if TYPE_CHECKING:
from .ai import AI
class RenderOrder(Enum):
'''
These values indicate the order that an Entity should be rendered. Higher values are rendered later and therefore on
top of items with lower orderings.
'''
ITEM = 1000
ACTOR = 2000
HERO = 3000
class Entity:
'''A single-tile drawable entity with a symbol and position
@ -36,19 +25,9 @@ class Entity:
game
position : Point
The Entity's location on the map
foreground : Tuple[int, int, int]
The foreground color used to render this Entity
background : Tuple[int, int, int], optional
The background color used to render this Entity
symbol : str
A single character string that represents this character on the map
blocks_movement : bool
True if this Entity blocks other Entities from moving through its
position
render_order : RenderOrder
One of the RenderOrder values that specifies a layer at which this
entity will be rendered. Higher values are rendered on top of lower
values.
'''
# A monotonically increasing identifier to help differentiate between
@ -57,66 +36,51 @@ class Entity:
def __init__(
self,
symbol: str,
*,
position: Optional[Point] = None,
blocks_movement: Optional[bool] = True,
render_order: RenderOrder = RenderOrder.ITEM,
fg: Optional[Tuple[int, int, int]] = None,
bg: Optional[Tuple[int, int, int]] = None):
renderable: Optional[Renderable] = None):
self.identifier = Entity.__next_identifier
self.position = position if position else Point()
self.foreground = fg if fg else (255, 255, 255)
self.background = bg
self.symbol = symbol
self.renderable = renderable
self.blocks_movement = blocks_movement
self.render_order = render_order
Entity.__next_identifier += 1
def print_to_console(self, console: tcod.Console) -> None:
'''Render this Entity to the console'''
console.print(x=self.position.x, y=self.position.y, string=self.symbol, fg=self.foreground, bg=self.background)
def __str__(self):
return f'{self.__class__.__name__}!{self.identifier}'
def __str__(self) -> str:
return f'{self.symbol}!{self.identifier} at {self.position}'
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fg={self.foreground!r}, bg={self.background!r})'
def __repr__(self):
return f'{self.__class__.__name__}(position={self.position!r}, blocks_movement={self.blocks_movement}, renderable={self.renderable!r})'
class Actor(Entity):
'''
An actor is an abstract class that defines an object that can act in the game world. Entities that are actors will
be allowed an opportunity to perform an action during each game turn.
An actor is an abstract class that defines an object that can act in the
game world. Entities that are actors will be allowed an opportunity to
perform an action during each game turn.
### Attributes
Attributes
----------
ai : AI, optional
If an entity can act on its own behalf, an instance of an AI class
fighter : Fighter, optional
If an entity can fight or take damage, an instance of the Fighter class. This is where hit points, attack power,
defense power, etc live.
If an entity can fight or take damage, an instance of the Fighter class.
This is where hit points, attack power, defense power, etc live.
'''
def __init__(
self,
symbol: str,
*,
position: Optional[Point] = None,
blocks_movement: Optional[bool] = True,
render_order: RenderOrder = RenderOrder.ACTOR,
renderable: Optional[Renderable] = None,
ai: Optional['AI'] = None,
fighter: Optional[Fighter] = None,
fg: Optional[Tuple[int, int, int]] = None,
bg: Optional[Tuple[int, int, int]] = None):
fighter: Optional[Fighter] = None):
super().__init__(
symbol,
position=position,
blocks_movement=blocks_movement,
fg=fg,
bg=bg,
render_order=render_order)
renderable=renderable)
# Components
self.ai = ai
@ -138,7 +102,7 @@ class Actor(Entity):
return False
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fighter={self.fighter!r}, ai={self.ai!r}, fg={self.foreground!r}, bg={self.background!r})'
return f'{self.__class__.__name__}(position={self.position!r}, fighter={self.fighter!r}, ai={self.ai!r}, renderable={self.renderable!r})'
class Hero(Actor):
@ -146,11 +110,9 @@ class Hero(Actor):
def __init__(self, position: Point):
super().__init__(
'@',
position=position,
fighter=Fighter(maximum_hit_points=30, attack_power=5, defense=2),
render_order=RenderOrder.HERO,
fg=tuple(tcod.white))
renderable=Renderable('@', Renderable.Order.HERO, tuple(tcod.white)))
@property
def name(self) -> str:
@ -176,12 +138,13 @@ class Monster(Actor):
defense=species.defense)
super().__init__(
species.symbol,
ai=ai_class(self),
position=position,
fighter=fighter,
fg=species.foreground_color,
bg=species.background_color)
renderable=Renderable(
symbol=species.symbol,
fg=species.foreground_color,
bg=species.background_color))
self.species = species
@ -206,12 +169,13 @@ class Item(Entity):
'''An instance of an Item'''
def __init__(self, kind: items.Item, position: Optional[Point] = None, name: Optional[str] = None):
super().__init__(kind.symbol,
position=position,
super().__init__(position=position,
blocks_movement=False,
render_order=RenderOrder.ITEM,
fg=kind.foreground_color,
bg=kind.background_color)
renderable=Renderable(
symbol=kind.symbol,
order=Renderable.Order.ITEM,
fg=kind.foreground_color,
bg=kind.background_color))
self.kind = kind
self._name = name