diff --git a/tests/test_game.py b/tests/test_game.py index eb3fb25..0be50e8 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -784,6 +784,40 @@ def test_clicking_a_train_selects_the_train(self, game: Game): class TestSignals: + def test_hovering_over_rail_in_signal_mode_shows_signal_outline(self, game: Game): + create_objects( + game.grid, + """ + .-. + """, + ) + game.gui.mode = Mode.SIGNAL + game.gui.disable() + + assert len(game.drawer.signals_being_built_shape_element_list) == 0 + + game.on_mouse_motion(30, 15, 0, 0) + + assert len(game.drawer.signals_being_built_shape_element_list) == 2 + + def test_remove_signal_outline_when_hovering_elsewhere(self, game: Game): + create_objects( + game.grid, + """ + .-. + """, + ) + game.gui.mode = Mode.SIGNAL + game.gui.disable() + + game.on_mouse_motion(30, 15, 0, 0) + + assert len(game.drawer.signals_being_built_shape_element_list) == 2 + + game.on_mouse_motion(60, 15, 0, 0) + + assert len(game.drawer.signals_being_built_shape_element_list) == 0 + def test_creating_signal_creates_two_signal_blocks(self, game: Game): create_objects( game.grid, diff --git a/trainfinity2/game.py b/trainfinity2/game.py index 03271b7..c38bf81 100644 --- a/trainfinity2/game.py +++ b/trainfinity2/game.py @@ -13,7 +13,12 @@ SECONDS_BETWEEN_CARGO_CREATION, ) from .graphics.drawer import Drawer -from .grid import Grid, RailsBeingBuiltEvent, StationBeingBuiltEvent +from .grid import ( + Grid, + RailsBeingBuiltEvent, + SignalsBeingBuiltEvent, + StationBeingBuiltEvent, +) from .gui import Gui, Mode from .model import Player, Signal, Station from .observer import ChangeEvent, CreateEvent, DestroyEvent, Event @@ -82,6 +87,7 @@ def setup(self, terrain: Terrain): self.grid.add_observer(self.drawer, DestroyEvent) self.grid.add_observer(self.drawer, RailsBeingBuiltEvent) self.grid.add_observer(self.drawer, StationBeingBuiltEvent) + self.grid.add_observer(self.drawer, SignalsBeingBuiltEvent) self.grid.create_buildings() self.player = Player(self.gui, self.level_up) @@ -266,7 +272,13 @@ def on_mouse_motion(self, x: int, y: int, dx: int, dy: int): else: self.drawer.highlight(self._train_placer.session.station.positions) - if self.gui.mode == Mode.DESTROY: + elif self.gui.mode == Mode.SIGNAL: + world_x_float, world_y_float = self.camera.to_world_coordinates_no_rounding( + x, y + ) + self.grid.show_signal_outline(world_x_float, world_y_float) + + elif self.gui.mode == Mode.DESTROY: self.drawer.show_rails_to_be_destroyed( self.grid.rails_at_position(Vec2(world_x, world_y)) ) diff --git a/trainfinity2/graphics/drawer.py b/trainfinity2/graphics/drawer.py index f42935f..f6dabea 100644 --- a/trainfinity2/graphics/drawer.py +++ b/trainfinity2/graphics/drawer.py @@ -3,7 +3,7 @@ from typing import Any, Collection, Iterable import arcade -from arcade import Shape, color +from arcade import Color, Shape, color from pyglet.math import Vec2 from trainfinity2.graphics.cargo import get_cargo_shape @@ -21,7 +21,12 @@ PIXEL_OFFSET_PER_CARGO, RAIL_TO_BE_DESTROYED_COLOR, ) -from ..grid import Grid, RailsBeingBuiltEvent, StationBeingBuiltEvent +from ..grid import ( + Grid, + RailsBeingBuiltEvent, + SignalsBeingBuiltEvent, + StationBeingBuiltEvent, +) from ..model import ( Building, Factory, @@ -90,6 +95,8 @@ def __init__(self): self._station_being_built: Station | None = None self.stations_being_built_shape_element_list = _ShapeElementList() + self.signals_being_built_shape_element_list = _ShapeElementList() + self.cargo_shape_element_list = _ShapeElementList() self.highlight_shape_element_list = _ShapeElementList() @@ -237,8 +244,7 @@ def _add_station(self, station: Station): for shape in self._get_station_shapes(station): self._add_shape(shape, station) - def _update_signal(self, signal: Signal): - self._remove(signal) + def _create_signal_shape(self, signal: Signal, is_being_built: bool = False): positions = list(signal.rail.positions) middle_of_rail = positions[0].lerp(positions[1], 0.5) position = middle_of_rail.lerp(signal.from_position, 0.5) @@ -246,14 +252,23 @@ def _update_signal(self, signal: Signal): position.x * GRID_BOX_SIZE_PIXELS + GRID_BOX_SIZE_PIXELS / 2, position.y * GRID_BOX_SIZE_PIXELS + GRID_BOX_SIZE_PIXELS / 2, ) - shape = arcade.create_ellipse_filled( + alpha = 128 if is_being_built else 255 + color_: Color = ( + color.RED + if signal.signal_color == SignalColor.RED + else color.GREEN + (alpha,) + ) + return arcade.create_ellipse_filled( position.x, position.y, GRID_BOX_SIZE_PIXELS / 6, GRID_BOX_SIZE_PIXELS / 6, - color.RED if signal.signal_color == SignalColor.RED else color.GREEN, + color=color_, ) - self._add_signal_shape(shape, signal) + + def _update_signal(self, signal: Signal): + self._remove(signal) + self._add_signal_shape(self._create_signal_shape(signal), signal) def _add_sprite(self, sprite: arcade.Sprite, object: Any): self._sprite_list.append(sprite) @@ -324,6 +339,9 @@ def on_notify(self, object: Any, event: Event): case Grid(), StationBeingBuiltEvent(): event = typing.cast(StationBeingBuiltEvent, event) self._show_station_being_built(event.station, event.illegal_positions) + case Grid(), SignalsBeingBuiltEvent(): + event = typing.cast(SignalsBeingBuiltEvent, event) + self._show_signals_being_built(event.signals) case Station(), CreateEvent(): self.upsert(object) case Factory(), CreateEvent(): @@ -386,6 +404,13 @@ def _show_station_being_built( self.stations_being_built_shape_element_list.append(red_box_shape) self._station_being_built = station + def _show_signals_being_built(self, signals: set[Signal]): + self.signals_being_built_shape_element_list = _ShapeElementList() + for signal in signals: + self.signals_being_built_shape_element_list.append( + self._create_signal_shape(signal, is_being_built=True) + ) + def _create_rail(self, rail: Rail): for rail_shape in get_rail_shapes(rail, FINISHED_RAIL_COLOR): self._add_rail_shape(rail_shape, rail) @@ -420,6 +445,8 @@ def draw(self): self.rails_being_built_shape_element_list.draw() self.stations_being_built_shape_element_list.draw() + self.signals_being_built_shape_element_list.draw() + self.cargo_shape_element_list.draw() self.highlight_shape_element_list.draw() diff --git a/trainfinity2/grid.py b/trainfinity2/grid.py index 96c15a6..5716aa5 100644 --- a/trainfinity2/grid.py +++ b/trainfinity2/grid.py @@ -25,6 +25,11 @@ from .route_finder import find_route +@dataclass(frozen=True) +class SignalsBeingBuiltEvent(Event): + signals: set[Signal] + + @dataclass class RailsBeingBuiltEvent(Event): rails: set[Rail] @@ -390,3 +395,12 @@ def create_signals_at_grid_position(self, x: float, y: float) -> list[Signal]: signals.append(signal) self._signal_controller.create_signal_blocks(self, list(self.signals.values())) return signals + + def show_signal_outline(self, world_x: float, world_y: float): + x = world_x - 0.5 + y = world_y - 0.5 + if rail := self._closest_rail(x, y): + signals = {Signal(position, rail) for position in rail.positions} + else: + signals = set() + self.notify(SignalsBeingBuiltEvent(signals))