Draw a path from the hero to the current mouse point
Add a highlight grid to Map, with locations set to True if that point should be drawn with a "highlight" treatment. Add the highlight graphic_dtype to all Tiles.
This commit is contained in:
		
							parent
							
								
									f05dfdef55
								
							
						
					
					
						commit
						6c9d01771f
					
				
					 4 changed files with 80 additions and 29 deletions
				
			
		| 
						 | 
				
			
			@ -68,7 +68,9 @@ class Engine:
 | 
			
		|||
        self.map = Map(map_size, map_generator)
 | 
			
		||||
 | 
			
		||||
        self.event_handler: 'EventHandler' = MainGameEventHandler(self)
 | 
			
		||||
        self.current_mouse_point: Optional[Point] = None
 | 
			
		||||
 | 
			
		||||
        self.__current_mouse_point: Optional[Point] = None
 | 
			
		||||
        self.__mouse_path_points: Optional[List[Point]] = None
 | 
			
		||||
 | 
			
		||||
        self.entities: MutableSet[Entity] = set()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -103,6 +105,8 @@ class Engine:
 | 
			
		|||
 | 
			
		||||
    def print_to_console(self, console):
 | 
			
		||||
        '''Print the whole game to the given console.'''
 | 
			
		||||
        self.map.highlight_points(self.__mouse_path_points or [])
 | 
			
		||||
 | 
			
		||||
        self.map.print_to_console(console)
 | 
			
		||||
 | 
			
		||||
        console.print(x=1, y=45, string='HP:')
 | 
			
		||||
| 
						 | 
				
			
			@ -124,7 +128,7 @@ class Engine:
 | 
			
		|||
 | 
			
		||||
            ent.print_to_console(console)
 | 
			
		||||
 | 
			
		||||
            if ent.position == self.current_mouse_point:
 | 
			
		||||
            if ent.position == self.__current_mouse_point:
 | 
			
		||||
                entities_at_mouse_position.append(ent)
 | 
			
		||||
 | 
			
		||||
        if len(entities_at_mouse_position) > 0:
 | 
			
		||||
| 
						 | 
				
			
			@ -141,12 +145,15 @@ class Engine:
 | 
			
		|||
            self.event_handler.handle_events(context)
 | 
			
		||||
            self.finish_turn()
 | 
			
		||||
 | 
			
		||||
    def process_input_action(self, action: Action) -> ActionResult:
 | 
			
		||||
    def process_input_action(self, action: Action):
 | 
			
		||||
        '''Process an Action from player input'''
 | 
			
		||||
 | 
			
		||||
        log.ACTIONS_TREE.info('Processing Hero Actions')
 | 
			
		||||
        log.ACTIONS_TREE.info('|-> %s', action.actor)
 | 
			
		||||
 | 
			
		||||
        # Clear the mouse path highlight before handling actions.
 | 
			
		||||
        self.__mouse_path_points = None
 | 
			
		||||
 | 
			
		||||
        result = self._perform_action_until_done(action)
 | 
			
		||||
 | 
			
		||||
        # Player's action failed, don't proceed with turn.
 | 
			
		||||
| 
						 | 
				
			
			@ -225,7 +232,7 @@ class Engine:
 | 
			
		|||
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    def update_field_of_view(self) -> None:
 | 
			
		||||
    def update_field_of_view(self):
 | 
			
		||||
        '''Compute visible area of the map based on the player's position and point of view.'''
 | 
			
		||||
        # FIXME: Move this to the Map class
 | 
			
		||||
        self.map.visible[:] = tcod.map.compute_fov(
 | 
			
		||||
| 
						 | 
				
			
			@ -233,9 +240,37 @@ class Engine:
 | 
			
		|||
            tuple(self.hero.position),
 | 
			
		||||
            radius=8)
 | 
			
		||||
 | 
			
		||||
        # Visible tiles should be added to the explored list
 | 
			
		||||
        # Add visible tiles to the explored grid
 | 
			
		||||
        self.map.explored |= self.map.visible
 | 
			
		||||
 | 
			
		||||
    def update_mouse_point(self, mouse_point: Optional[Point]):
 | 
			
		||||
        if mouse_point == self.__current_mouse_point:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        should_render_mouse_path = (
 | 
			
		||||
            mouse_point
 | 
			
		||||
            and self.map.tile_is_in_bounds(mouse_point)
 | 
			
		||||
            and self.map.tile_is_walkable(mouse_point))
 | 
			
		||||
 | 
			
		||||
        if not should_render_mouse_path:
 | 
			
		||||
            self.__current_mouse_point = None
 | 
			
		||||
            self.__mouse_path_points = None
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self.__current_mouse_point = mouse_point
 | 
			
		||||
 | 
			
		||||
        path_from_hero_to_mouse_point = tcod.los.bresenham(tuple(self.hero.position), tuple(self.__current_mouse_point))
 | 
			
		||||
        mouse_path_points = [Point(x, y) for x, y in path_from_hero_to_mouse_point.tolist()]
 | 
			
		||||
 | 
			
		||||
        all_mouse_path_points_are_walkable = all(
 | 
			
		||||
            self.map.tile_is_walkable(pt) and self.map.point_is_explored(pt) for pt in mouse_path_points)
 | 
			
		||||
        if not all_mouse_path_points_are_walkable:
 | 
			
		||||
            self.__current_mouse_point = None
 | 
			
		||||
            self.__mouse_path_points = None
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        self.__mouse_path_points = mouse_path_points
 | 
			
		||||
 | 
			
		||||
    def begin_turn(self) -> None:
 | 
			
		||||
        '''Begin the current turn'''
 | 
			
		||||
        if self.did_begin_turn:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,7 +28,7 @@ class EventHandler(tcod.event.EventDispatch[Action]):
 | 
			
		|||
            context.convert_event(event)
 | 
			
		||||
            self.handle_event(event)
 | 
			
		||||
 | 
			
		||||
    def handle_event(self, event: tcod.event.Event) -> None:
 | 
			
		||||
    def handle_event(self, event: tcod.event.Event):
 | 
			
		||||
        '''
 | 
			
		||||
        Handle an event by transforming it into an Action and processing it until it is completed. If the Action
 | 
			
		||||
        succeeds, also process actions from other Entities.
 | 
			
		||||
| 
						 | 
				
			
			@ -95,7 +95,7 @@ class MainGameEventHandler(EventHandler):
 | 
			
		|||
        mouse_point = Point(event.tile.x, event.tile.y)
 | 
			
		||||
        if not self.engine.map.tile_is_in_bounds(mouse_point):
 | 
			
		||||
            mouse_point = None
 | 
			
		||||
        self.engine.current_mouse_point = mouse_point
 | 
			
		||||
        self.engine.update_mouse_point(mouse_point)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GameOverEventHandler(EventHandler):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ parts of a map.
 | 
			
		|||
'''
 | 
			
		||||
 | 
			
		||||
import random
 | 
			
		||||
from typing import Iterable
 | 
			
		||||
 | 
			
		||||
import numpy as np
 | 
			
		||||
import numpy.typing as npt
 | 
			
		||||
| 
						 | 
				
			
			@ -20,17 +21,17 @@ class Map:
 | 
			
		|||
    def __init__(self, size: Size, generator: MapGenerator):
 | 
			
		||||
        self.size = size
 | 
			
		||||
 | 
			
		||||
        self.generator = generator
 | 
			
		||||
        self.tiles = np.full(tuple(size), fill_value=Empty, order='F')
 | 
			
		||||
        generator.generate(self.tiles)
 | 
			
		||||
 | 
			
		||||
        self.up_stairs = generator.up_stairs
 | 
			
		||||
        self.down_stairs = generator.down_stairs
 | 
			
		||||
 | 
			
		||||
        self.highlighted = np.full(tuple(self.size), fill_value=False, order='F')
 | 
			
		||||
        # Map tiles that are currently visible to the player
 | 
			
		||||
        self.visible = np.full(tuple(self.size), fill_value=True, order='F')
 | 
			
		||||
        self.visible = np.full(tuple(self.size), fill_value=False, order='F')
 | 
			
		||||
        # Map tiles that the player has explored
 | 
			
		||||
        self.explored = np.full(tuple(self.size), fill_value=True, order='F')
 | 
			
		||||
        self.explored = np.full(tuple(self.size), fill_value=False, order='F')
 | 
			
		||||
 | 
			
		||||
        self.__walkable_points = None
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +48,14 @@ class Map:
 | 
			
		|||
 | 
			
		||||
    def tile_is_walkable(self, point: Point) -> bool:
 | 
			
		||||
        '''Return True if the tile at the given point is walkable'''
 | 
			
		||||
        return self.tiles[point.x, point.y]['walkable']
 | 
			
		||||
        return self.tile_is_in_bounds(point) and self.tiles[point.x, point.y]['walkable']
 | 
			
		||||
 | 
			
		||||
    def highlight_points(self, points: Iterable[Point]):
 | 
			
		||||
        '''Update the highlight graph with the list of points to highlight.'''
 | 
			
		||||
        self.highlighted.fill(False)
 | 
			
		||||
 | 
			
		||||
        for pt in points if points:
 | 
			
		||||
            self.highlighted[pt.x, pt.y] = True
 | 
			
		||||
 | 
			
		||||
    def print_to_console(self, console: tcod.Console) -> None:
 | 
			
		||||
        '''Render the map to the console.'''
 | 
			
		||||
| 
						 | 
				
			
			@ -56,6 +64,6 @@ class Map:
 | 
			
		|||
        # If a tile is in the visible array, draw it with the "light" color. If it's not, but it's in the explored
 | 
			
		||||
        # array, draw it with the "dark" color. Otherwise, draw it as Empty.
 | 
			
		||||
        console.tiles_rgb[0:size.width, 0:size.height] = np.select(
 | 
			
		||||
            condlist=[self.visible, self.explored],
 | 
			
		||||
            choicelist=[self.tiles['light'], self.tiles['dark']],
 | 
			
		||||
            condlist=[self.highlighted, self.visible, self.explored],
 | 
			
		||||
            choicelist=[self.tiles['highlighted'], self.tiles['light'], self.tiles['dark']],
 | 
			
		||||
            default=Shroud)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,9 +6,9 @@ graphic_datatype = np.dtype([
 | 
			
		|||
    # Character, a Unicode codepoint represented as an int32
 | 
			
		||||
    ('ch', np.int32),
 | 
			
		||||
    # Foreground color, three bytes
 | 
			
		||||
    ('fg', '3B'),
 | 
			
		||||
    ('fg', '4B'),
 | 
			
		||||
    # Background color, three bytes
 | 
			
		||||
    ('bg', '3B'),
 | 
			
		||||
    ('bg', '4B'),
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
tile_datatype = np.dtype([
 | 
			
		||||
| 
						 | 
				
			
			@ -20,37 +20,45 @@ tile_datatype = np.dtype([
 | 
			
		|||
    ('dark', graphic_datatype),
 | 
			
		||||
    # A graphic struct (as above) defining the look of this tile when it's visible
 | 
			
		||||
    ('light', graphic_datatype),
 | 
			
		||||
    # A graphic struct (as above) defining the look of this tile when it's highlighted
 | 
			
		||||
    ('highlighted', graphic_datatype),
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def tile(*,
 | 
			
		||||
         walkable: int,
 | 
			
		||||
         transparent: int,
 | 
			
		||||
         dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
 | 
			
		||||
         light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]]) -> np.ndarray:
 | 
			
		||||
    return np.array((walkable, transparent, dark, light), dtype=tile_datatype)
 | 
			
		||||
         dark: Tuple[int, Tuple[int, int, int, int], Tuple[int, int, int, int]],
 | 
			
		||||
         light: Tuple[int, Tuple[int, int, int, int], Tuple[int, int, int, int]],
 | 
			
		||||
         highlighted: Tuple[int, Tuple[int, int, int, int], Tuple[int, int, int, int]]) -> np.ndarray:
 | 
			
		||||
    return np.array((walkable, transparent, dark, light, highlighted), dtype=tile_datatype)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# An overlay color for tiles that are not visible and have not been explored
 | 
			
		||||
Shroud = np.array((ord(' '), (255, 255, 255), (0, 0, 0)), dtype=graphic_datatype)
 | 
			
		||||
Shroud = np.array((ord(' '), (255, 255, 255, 255), (0, 0, 0, 0)), dtype=graphic_datatype)
 | 
			
		||||
 | 
			
		||||
Empty = tile(
 | 
			
		||||
    walkable=False, transparent=False,
 | 
			
		||||
    dark=(ord(' '), (255, 255, 255), (0, 0, 0)),
 | 
			
		||||
    light=(ord(' '), (255, 255, 255), (0, 0, 0)))
 | 
			
		||||
    dark=(ord('#'), (20, 20, 20, 255), (0, 0, 0, 0)),
 | 
			
		||||
    light=(ord('#'), (20, 20, 20, 255), (0, 0, 0, 0)),
 | 
			
		||||
    highlighted=(ord('#'), (20, 20, 20, 255), (30, 30, 30, 255)))
 | 
			
		||||
Floor = tile(
 | 
			
		||||
    walkable=True, transparent=True,
 | 
			
		||||
    dark=(ord('·'), (80, 80, 100), (50, 50, 50)),
 | 
			
		||||
    light=(ord('·'), (100, 100, 120), (80, 80, 100)))
 | 
			
		||||
    dark=(ord('·'), (80, 80, 100, 255), (50, 50, 50, 255)),
 | 
			
		||||
    light=(ord('·'), (100, 100, 120, 255), (80, 80, 100, 255)),
 | 
			
		||||
    highlighted=(ord('·'), (100, 100, 120, 255), (80, 80, 150, 255)))
 | 
			
		||||
StairsUp = tile(
 | 
			
		||||
    walkable=True, transparent=True,
 | 
			
		||||
    dark=(ord('<'), (80, 80, 100), (50, 50, 50)),
 | 
			
		||||
    light=(ord('<'), (100, 100, 120), (80, 80, 100)))
 | 
			
		||||
    dark=(ord('<'), (80, 80, 100, 255), (50, 50, 50, 255)),
 | 
			
		||||
    light=(ord('<'), (100, 100, 120, 255), (80, 80, 100, 255)),
 | 
			
		||||
    highlighted=(ord('<'), (100, 100, 120, 255), (80, 80, 150, 255)))
 | 
			
		||||
StairsDown = tile(
 | 
			
		||||
    walkable=True, transparent=True,
 | 
			
		||||
    dark=(ord('>'), (80, 80, 100), (50, 50, 50)),
 | 
			
		||||
    light=(ord('>'), (100, 100, 120), (80, 80, 100)))
 | 
			
		||||
    dark=(ord('>'), (80, 80, 100, 255), (50, 50, 50, 255)),
 | 
			
		||||
    light=(ord('>'), (100, 100, 120, 255), (80, 80, 100, 255)),
 | 
			
		||||
    highlighted=(ord('>'), (100, 100, 120, 255), (80, 80, 150, 255)))
 | 
			
		||||
Wall = tile(
 | 
			
		||||
    walkable=False, transparent=False,
 | 
			
		||||
    dark=(ord(' '), (255, 255, 255), (0, 0, 150)),
 | 
			
		||||
    light=(ord(' '), (255, 255, 255), (50, 50, 200)))
 | 
			
		||||
    dark=(ord('#'), (80, 80, 80, 255), (0, 0, 0, 255)),
 | 
			
		||||
    light=(ord('#'), (100, 100, 100, 255), (20, 20, 20, 255)),
 | 
			
		||||
    highlighted=(ord('#'), (100, 100, 100, 255), (20, 20, 20, 255)))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue