diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index da1c7afb..f95aa479 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -229,6 +229,8 @@ class ControlPoint(MissionTarget, ABC): # TODO: Should be Airbase specific. self.stances: Dict[int, CombatStance] = {} self.pending_unit_deliveries: Optional[UnitsDeliveryEvent] = None + + self.target_position = None def __repr__(self): return f"<{__class__}: {self.name}>" @@ -271,6 +273,13 @@ class ControlPoint(MissionTarget, ABC): """ return False + @property + def moveable(self) -> bool: + """ + :return: Whether this control point can be moved around + """ + return False + @property @abstractmethod def total_aircraft_parking(self): @@ -491,6 +500,7 @@ class Airfield(ControlPoint): class NavalControlPoint(ControlPoint, ABC): + @property def is_fleet(self) -> bool: return True @@ -539,6 +549,10 @@ class NavalControlPoint(ControlPoint, ABC): def runway_can_be_repaired(self) -> bool: return False + @property + def moveable(self) -> bool: + return True + class Carrier(NavalControlPoint): @@ -546,8 +560,7 @@ class Carrier(NavalControlPoint): import game.theater.conflicttheater super().__init__(cp_id, name, at, at, game.theater.conflicttheater.SIZE_SMALL, 1, - has_frontline=False, - cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP) + has_frontline=False, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP) def capture(self, game: Game, for_player: bool) -> None: raise RuntimeError("Carriers cannot be captured") diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index d628a0b6..3032a35c 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -5,7 +5,8 @@ import logging import math from typing import Iterable, Iterator, List, Optional, Tuple -from PySide2.QtCore import QPointF, Qt +from PySide2 import QtWidgets, QtCore +from PySide2.QtCore import QPointF, Qt, QLineF from PySide2.QtGui import ( QBrush, QColor, @@ -13,21 +14,20 @@ from PySide2.QtGui import ( QPen, QPixmap, QPolygonF, - QWheelEvent, -) + QWheelEvent, ) from PySide2.QtWidgets import ( QFrame, QGraphicsItem, QGraphicsOpacityEffect, QGraphicsScene, - QGraphicsView, + QGraphicsView, QGraphicsSceneMouseEvent, ) from dcs import Point from dcs.mapping import point_from_heading import qt_ui.uiconstants as CONST from game import Game, db -from game.theater import ControlPoint +from game.theater import ControlPoint, Enum from game.theater.conflicttheater import FrontLine from game.theater.theatergroundobject import ( EwrGroundObject, @@ -76,6 +76,12 @@ def bezier_curve_range(n: int, points: Iterable[Tuple[float, float]]) -> Iterato t = i / float(n - 1) yield bezier(t, points) + +class QLiberationMapState(Enum): + NORMAL = 0 + MOVING_UNIT = 1 + + class QLiberationMap(QGraphicsView): WAYPOINT_SIZE = 4 @@ -86,6 +92,7 @@ class QLiberationMap(QGraphicsView): QLiberationMap.instance = self self.game_model = game_model self.game: Optional[Game] = game_model.game + self.state = QLiberationMapState.NORMAL self.waypoint_info_font = QFont() self.waypoint_info_font.setPointSize(12) @@ -102,6 +109,11 @@ class QLiberationMap(QGraphicsView): self.init_scene() self.setGame(game_model.game) + # Object displayed when unit is selected + self.movement_line = QtWidgets.QGraphicsLineItem(QtCore.QLineF(QPointF(0,0),QPointF(0,0))) + self.movement_line.setPen(QPen(CONST.COLORS["orange"], width=10.0)) + self.selected_cp: QMapControlPoint = None + GameUpdateSignal.get_instance().flight_paths_changed.connect( lambda: self.draw_flight_plans(self.scene()) ) @@ -316,6 +328,12 @@ class QLiberationMap(QGraphicsView): self.draw_ground_objects(scene, cp) + if cp.target_position is not None: + tpos = cp.target_position + scene.addLine(QLineF(QPointF(pos[0], pos[1]), QPointF(tpos[0], tpos[1])), + QPen(CONST.COLORS["green"], width=10, s=Qt.DashDotLine)) + + for cp in self.game.theater.enemy_points(): if DisplayOptions.lines: self.scene_create_lines_for_cp(cp, playerColor, enemyColor) @@ -572,6 +590,10 @@ class QLiberationMap(QGraphicsView): return X > treshold and X or treshold, Y > treshold and Y or treshold + + def _scene_to_dcs_coords(self, x, y): + pass + def highlight_color(self, transparent: Optional[bool] = False) -> QColor: return QColor(255, 255, 0, 20 if transparent else 255) @@ -657,6 +679,32 @@ class QLiberationMap(QGraphicsView): poly = QPolygonF([QPointF(*self._transform_point(Point(point[0], point[1]))) for point in exclusion_zone]) scene.addPolygon(poly, CONST.COLORS["grey"], CONST.COLORS["dark_dark_grey"]) + def setSelectedUnit(self, selected_cp: QMapControlPoint): + self.state = QLiberationMapState.MOVING_UNIT + self.selected_cp = selected_cp + position = self._transform_point(selected_cp.control_point.position) + self.movement_line = QtWidgets.QGraphicsLineItem(QLineF(QPointF(*position), QPointF(*position))) + self.scene().addItem(self.movement_line) + def sceneMouseMovedEvent(self, event: QGraphicsSceneMouseEvent): + if self.state == QLiberationMapState.MOVING_UNIT: + self.setCursor(Qt.PointingHandCursor) + p1 = self.movement_line.line().p1() + self.movement_line.setLine(QLineF(p1, event.scenePos())) - + def sceneMousePressEvent(self, event: QGraphicsSceneMouseEvent): + if self.state == QLiberationMapState.MOVING_UNIT: + if event.buttons() == Qt.RightButton: + pass + elif event.buttons() == Qt.LeftButton: + if self.selected_cp is not None: + # Set movement position for the cp + pos = event.scenePos() + point = Point(int(pos.x()), int(pos.y())) + self.selected_cp.control_point.target_position = point.x, point.y # TODO : convert to DCS coords ! + GameUpdateSignal.get_instance().updateGame(self.game_model.game) + else: + return + self.state = QLiberationMapState.NORMAL + self.scene().removeItem(self.movement_line) + self.selected_cp = None \ No newline at end of file diff --git a/qt_ui/widgets/map/QLiberationScene.py b/qt_ui/widgets/map/QLiberationScene.py index 30f0b56a..df77a6f8 100644 --- a/qt_ui/widgets/map/QLiberationScene.py +++ b/qt_ui/widgets/map/QLiberationScene.py @@ -1,5 +1,4 @@ -from PySide2.QtGui import QFont -from PySide2.QtWidgets import QGraphicsScene +from PySide2.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent import qt_ui.uiconstants as CONST @@ -11,3 +10,9 @@ class QLiberationScene(QGraphicsScene): item = self.addText("Go to \"File/New Game\" to setup a new campaign or go to \"File/Open\" to load an existing save game.", CONST.FONT_PRIMARY) item.setDefaultTextColor(CONST.COLORS["white"]) + + def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): + self.parent().sceneMouseMovedEvent(event) + + def mousePressEvent(self, event:QGraphicsSceneMouseEvent): + self.parent().sceneMousePressEvent(event) diff --git a/qt_ui/widgets/map/QMapControlPoint.py b/qt_ui/widgets/map/QMapControlPoint.py index ef2cc0ca..8249d33a 100644 --- a/qt_ui/widgets/map/QMapControlPoint.py +++ b/qt_ui/widgets/map/QMapControlPoint.py @@ -26,6 +26,12 @@ class QMapControlPoint(QMapObject): f"CHEAT: Capture {self.control_point.name}") self.capture_action.triggered.connect(self.cheat_capture) + self.move_action = QAction("Move") + self.move_action.triggered.connect(self.move) + + self.cancel_move_action = QAction("Cancel Move") + self.cancel_move_action.triggered.connect(self.cancel_move) + def paint(self, painter, option, widget=None) -> None: if DisplayOptions.control_points: painter.save() @@ -70,6 +76,12 @@ class QMapControlPoint(QMapObject): self.base_details_dialog.show() def add_context_menu_actions(self, menu: QMenu) -> None: + + if self.control_point.moveable and self.control_point.captured: + menu.addAction(self.move_action) + if self.control_point.target_position is not None: + menu.addAction(self.cancel_move_action) + if self.control_point.is_fleet: return @@ -86,6 +98,13 @@ class QMapControlPoint(QMapObject): # Reinitialized ground planners and the like. self.game_model.game.initialize_turn() GameUpdateSignal.get_instance().updateGame(self.game_model.game) + + def move(self): + self.parent.setSelectedUnit(self) + + def cancel_move(self): + self.control_point.target_position = None + GameUpdateSignal.get_instance().updateGame(self.game_model.game) def open_new_package_dialog(self) -> None: """Extends the default packagedialog to redirect to base menu for red air base."""