diff --git a/erynrl/events.py b/erynrl/events.py index 7c68133..f06634b 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -87,7 +87,7 @@ class MainGameEventHandler(EventHandler): case tcod.event.KeySym.ESCAPE: action = ExitAction() case tcod.event.KeySym.SPACE: - action = RegenerateRoomsAction(hero) + action = RegenerateRoomsAction() case tcod.event.KeySym.PERIOD: if not is_shift_pressed: action = WaitAction(hero) diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index dac7fac..b8f06d3 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -23,11 +23,15 @@ class Interface: self.message_window = MessageLogWindow(Rect(Point(28, size.height - 5), Size(size.width - 28, 5)), message_log) def update(self, turn_count: int, hero: Hero, entities: List[Entity]): + '''Update game state that the interface needs to render''' self.info_window.turn_count = turn_count self.info_window.update_hero(hero) + + self.map_window.update_drawable_map_bounds(hero) self.map_window.entities = entities def draw(self, console: Console): + '''Draw the UI to the console''' self.map_window.draw(console) self.info_window.draw(console) self.message_window.draw(console) @@ -48,6 +52,7 @@ class InfoWindow(Window): colors=list(HealthBar.bar_colors())) def update_hero(self, hero: Hero): + '''Update internal state for the hero''' assert hero.fighter fighter = hero.fighter diff --git a/erynrl/interface/window.py b/erynrl/interface/window.py index d9a68d1..f707359 100644 --- a/erynrl/interface/window.py +++ b/erynrl/interface/window.py @@ -5,9 +5,10 @@ from typing import List import numpy as np from tcod.console import Console -from ..object import Entity -from ..geometry import Rect, Vector +from .. import log +from ..geometry import Point, Rect, Vector from ..map import Map +from ..object import Entity, Hero class Window: @@ -19,11 +20,13 @@ class Window: @property def drawable_bounds(self) -> Rect: + '''The bounds of the window that is drawable, inset by any frame''' if self.is_framed: return self.bounds.inset_rect(1, 1, 1, 1) return self.bounds def draw(self, console: Console): + '''Draw the window to the conole''' if self.is_framed: console.draw_frame( self.bounds.origin.x, @@ -35,41 +38,67 @@ class Window: class MapWindow(Window): '''A Window that displays a game map''' - def __init__(self, bounds: Rect, map_: Map, **kwargs): + # pylint: disable=redefined-builtin + def __init__(self, bounds: Rect, map: Map, **kwargs): super().__init__(bounds, **kwargs) - self.map = map_ + self.map = map + self.drawable_map_bounds = map.bounds + self.offset = Vector() self.entities: List[Entity] = [] + def update_drawable_map_bounds(self, hero: Hero): + bounds = self.drawable_bounds + map_bounds = self.map.bounds + + if map_bounds.width < bounds.width and map_bounds.height < bounds.height: + # We can draw the whole map in the drawable bounds + self.drawable_map_bounds = map_bounds + + # Attempt to keep the player centered in the viewport. + + hero_point = hero.position + + x = min(max(0, hero_point.x - bounds.mid_x), map_bounds.max_x - bounds.width) + y = min(max(0, hero_point.y - bounds.mid_y), map_bounds.max_y - bounds.height) + origin = Point(x, y) + + self.drawable_map_bounds = Rect(origin, bounds.size) + def draw(self, console: Console): super().draw(console) self._draw_map(console) self._draw_entities(console) def _draw_map(self, console: Console): - map_ = self.map - map_size = map_.size - + drawable_map_bounds = self.drawable_map_bounds drawable_bounds = self.drawable_bounds - width = min(map_size.width, drawable_bounds.width) - height = min(map_size.height, drawable_bounds.height) + log.UI.info('Drawing map') + log.UI.info('|- map bounds: %s', drawable_map_bounds) + log.UI.info('|- window bounds: %s', drawable_bounds) - # TODO: Adjust the slice according to where the hero is. - map_slice = np.s_[0:width, 0:height] + map_slice = np.s_[ + drawable_map_bounds.min_x:drawable_map_bounds.max_x + 1, + drawable_map_bounds.min_y:drawable_map_bounds.max_y + 1] - min_x = drawable_bounds.min_x - max_x = min_x + width - min_y = drawable_bounds.min_y - max_y = min_y + height + console_slice = np.s_[ + drawable_bounds.min_x:drawable_bounds.max_x + 1, + drawable_bounds.min_y:drawable_bounds.max_y + 1] - console.tiles_rgb[min_x:max_x, min_y:max_y] = self.map.composited_tiles[map_slice] + log.UI.info('|- map slice: %s', map_slice) + log.UI.info('`- console slice: %s', console_slice) + + console.tiles_rgb[console_slice] = self.map.composited_tiles[map_slice] def _draw_entities(self, console): + map_bounds_vector = Vector.from_point(self.drawable_map_bounds.origin) drawable_bounds_vector = Vector.from_point(self.drawable_bounds.origin) + log.UI.info('Drawing entities') + for ent in self.entities: - # Only process entities that are in the field of view + # Only draw entities that are in the field of view if not self.map.visible[tuple(ent.position)]: continue @@ -78,16 +107,15 @@ class MapWindow(Window): entity_position = ent.position map_tile_at_entity_position = self.map.composited_tiles[entity_position.x, entity_position.y] - position = ent.position + drawable_bounds_vector + position = ent.position - map_bounds_vector + drawable_bounds_vector + + if isinstance(ent, Hero): + log.UI.info('|- hero position on map %s', entity_position) + log.UI.info('`- position in window %s', position) + console.print( x=position.x, y=position.y, string=ent.symbol, fg=ent.foreground, bg=tuple(map_tile_at_entity_position['bg'][:3])) - - # if ent.position == self.__current_mouse_point: - # entities_at_mouse_position.append(ent) - - # if len(entities_at_mouse_position) > 0: - # console.print(x=1, y=43, string=', '.join(e.name for e in entities_at_mouse_position))