Remove the old map.

This commit is contained in:
Dan Albert 2021-05-30 19:45:50 -07:00
parent acd3e87996
commit 30f6220c3e
15 changed files with 16 additions and 2286 deletions

View File

@ -23,7 +23,7 @@ Saves from 2.5 are not compatible with 3.0.
* **[Flight Planner]** Automatic ATO generation for the player's coalition can now be disabled in the settings.
* **[Payloads]** AI flights for most air to ground mission types (CAS excluded) will have their guns emptied to prevent strafing fully armed and operational battle stations. Gun-reliant airframes like A-10s and warbirds will keep their bullets.
* **[Kneeboard]** ATC table overflow alleviated by wrapping long airfield names and splitting ATC frequency and channel into separate rows.
* **[UI]** Added new web based map UI. This is mostly functional but many of the old display options are a WIP. Revert to the old map with --old-map.
* **[UI]** Overhauled the map implementation. Now uses satellite imagery instead of low res map images. Display options have moved from the toolbar to panels in the map.
* **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present.
* **[UI]** DCS loadouts are now selectable in the loadout setup menu.
* **[UI]** Added global aircraft inventory view under Air Wing dialog.

View File

@ -1,128 +0,0 @@
"""Visibility options for the game map."""
from dataclasses import dataclass, field
from typing import Iterator, Optional, Union
@dataclass
class DisplayRule:
name: str
_value: bool
debug_only: bool = field(default=False)
@property
def menu_text(self) -> str:
return self.name
@property
def value(self) -> bool:
return self._value
@value.setter
def value(self, value: bool) -> None:
from qt_ui.widgets.map.QLiberationMap import QLiberationMap
self._value = value
if QLiberationMap.instance is not None:
QLiberationMap.instance.reload_scene()
QLiberationMap.instance.update()
def __bool__(self) -> bool:
return self.value
class DisplayGroup:
def __init__(self, name: Optional[str], debug_only: bool = False) -> None:
self.name = name
self.debug_only = debug_only
def __iter__(self) -> Iterator[DisplayRule]:
# Python 3.6 enforces that __dict__ is order preserving by default.
for value in self.__dict__.values():
if isinstance(value, DisplayRule):
yield value
class FlightPathOptions(DisplayGroup):
def __init__(self) -> None:
super().__init__("Flight Paths")
self.hide = DisplayRule("Hide Flight Paths", False)
self.only_selected = DisplayRule("Show Selected Flight Path", False)
self.all = DisplayRule("Show All Flight Paths", True)
class ThreatZoneOptions(DisplayGroup):
def __init__(self, coalition_name: str) -> None:
super().__init__(f"{coalition_name} Threat Zones")
self.none = DisplayRule(f"Hide {coalition_name.lower()} threat zones", True)
self.all = DisplayRule(
f"Show full {coalition_name.lower()} threat zones", False
)
self.aircraft = DisplayRule(
f"Show {coalition_name.lower()} aircraft threat tones", False
)
self.air_defenses = DisplayRule(
f"Show {coalition_name.lower()} air defenses threat zones", False
)
class NavMeshOptions(DisplayGroup):
def __init__(self) -> None:
super().__init__("Navmeshes", debug_only=True)
self.hide = DisplayRule("DEBUG Hide Navmeshes", True)
self.blue_navmesh = DisplayRule("DEBUG Show blue navmesh", False)
self.red_navmesh = DisplayRule("DEBUG Show red navmesh", False)
class PathDebugFactionOptions(DisplayGroup):
def __init__(self) -> None:
super().__init__("Faction for path debugging", debug_only=True)
self.blue = DisplayRule("Debug blue paths", True)
self.red = DisplayRule("Debug red paths", False)
class PathDebugOptions(DisplayGroup):
def __init__(self) -> None:
super().__init__("Shortest paths", debug_only=True)
self.hide = DisplayRule("DEBUG Hide paths", True)
self.shortest_path = DisplayRule("DEBUG Show shortest path", False)
self.barcap = DisplayRule("DEBUG Show BARCAP plan", False)
self.cas = DisplayRule("DEBUG Show CAS plan", False)
self.sweep = DisplayRule("DEBUG Show fighter sweep plan", False)
self.strike = DisplayRule("DEBUG Show strike plan", False)
self.tarcap = DisplayRule("DEBUG Show TARCAP plan", False)
class DisplayOptions:
ground_objects = DisplayRule("Ground Objects", True)
control_points = DisplayRule("Control Points", True)
lines = DisplayRule("Lines", True)
sam_ranges = DisplayRule("Ally SAM Threat Range", False)
enemy_sam_ranges = DisplayRule("Enemy SAM Threat Range", True)
detection_range = DisplayRule("SAM Detection Range", False)
map_poly = DisplayRule("Map Polygon Debug Mode", False)
waypoint_info = DisplayRule("Waypoint Information", True)
culling = DisplayRule("Display Culling Zones", False)
actual_frontline_pos = DisplayRule("Display Actual Frontline Location", False)
patrol_engagement_range = DisplayRule(
"Display selected patrol engagement range", True
)
flight_paths = FlightPathOptions()
blue_threat_zones = ThreatZoneOptions("Blue")
red_threat_zones = ThreatZoneOptions("Red")
navmeshes = NavMeshOptions()
path_debug_faction = PathDebugFactionOptions()
path_debug = PathDebugOptions()
@classmethod
def menu_items(cls) -> Iterator[Union[DisplayGroup, DisplayRule]]:
debug = False # Set to True to enable debug options.
# Python 3.6 enforces that __dict__ is order preserving by default.
for value in cls.__dict__.values():
if isinstance(value, DisplayRule):
if value.debug_only and not debug:
continue
yield value
elif isinstance(value, DisplayGroup):
if value.debug_only and not debug:
continue
yield value

View File

@ -57,7 +57,7 @@ def inject_custom_payloads(user_path: Path) -> None:
PayloadDirectories.set_preferred(user_path / "MissionEditor" / "UnitPayloads")
def run_ui(game: Optional[Game], new_map: bool) -> None:
def run_ui(game: Optional[Game]) -> None:
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" # Potential fix for 4K screens
app = QApplication(sys.argv)
@ -111,7 +111,7 @@ def run_ui(game: Optional[Game], new_map: bool) -> None:
GameUpdateSignal()
# Start window
window = QLiberationWindow(game, new_map)
window = QLiberationWindow(game)
window.showMaximized()
splash.finish(window)
qt_execution_code = app.exec_()
@ -139,16 +139,8 @@ def parse_args() -> argparse.Namespace:
help="Emits a warning for weapons without date or fallback information.",
)
parser.add_argument(
"--new-map",
action="store_true",
default=True,
help="Use the new map. Functional but missing many display options.",
)
parser.add_argument(
"--old-map", dest="new_map", action="store_false", help="Use the old map."
)
parser.add_argument("--new-map", help="Deprecated. Does nothing.")
parser.add_argument("--old-map", help="Deprecated. Does nothing.")
new_game = subparsers.add_parser("new-game")
@ -267,7 +259,7 @@ def main():
args.cheats,
)
run_ui(game, args.new_map)
run_ui(game)
if __name__ == "__main__":

View File

@ -1,7 +1,7 @@
import os
from typing import Dict
from PySide2.QtGui import QColor, QFont, QPixmap
from PySide2.QtGui import QPixmap
from game.theater.theatergroundobject import NAME_BY_CATEGORY
from .liberation_theme import get_theme_icons
@ -16,51 +16,6 @@ URLS: Dict[str, str] = {
LABELS_OPTIONS = ["Full", "Abbreviated", "Dot Only", "Off"]
SKILL_OPTIONS = ["Average", "Good", "High", "Excellent"]
FONT_SIZE = 8
FONT_NAME = "Arial"
# FONT = QFont("Arial", 12, weight=5, italic=True)
FONT_PRIMARY = QFont(FONT_NAME, FONT_SIZE, weight=5, italic=False)
FONT_PRIMARY_I = QFont(FONT_NAME, FONT_SIZE, weight=5, italic=True)
FONT_PRIMARY_B = QFont(FONT_NAME, FONT_SIZE, weight=75, italic=False)
FONT_MAP = QFont(FONT_NAME, 10, weight=75, italic=False)
COLORS: Dict[str, QColor] = {
"white": QColor(255, 255, 255),
"white_transparent": QColor(255, 255, 255, 35),
"light_red": QColor(231, 92, 83, 90),
"red": QColor(200, 80, 80),
"dark_red": QColor(140, 20, 20),
"red_transparent": QColor(227, 32, 0, 20),
"transparent": QColor(255, 255, 255, 0),
"light_blue": QColor(105, 182, 240, 90),
"blue": QColor(0, 132, 255),
"dark_blue": QColor(45, 62, 80),
"sea_blue": QColor(52, 68, 85),
"sea_blue_transparent": QColor(52, 68, 85, 150),
"blue_transparent": QColor(0, 132, 255, 20),
"purple": QColor(187, 137, 255),
"yellow": QColor(238, 225, 123),
"bright_red": QColor(150, 80, 80),
"super_red": QColor(227, 32, 0),
"green": QColor(128, 186, 128),
"light_green": QColor(223, 255, 173),
"light_green_transparent": QColor(180, 255, 140, 50),
"bright_green": QColor(64, 200, 64),
"black": QColor(0, 0, 0),
"black_transparent": QColor(0, 0, 0, 5),
"orange": QColor(254, 125, 10),
"night_overlay": QColor(12, 20, 69),
"dawn_dust_overlay": QColor(46, 38, 85),
"grey": QColor(150, 150, 150),
"grey_transparent": QColor(150, 150, 150, 150),
"dark_grey": QColor(75, 75, 75),
"dark_grey_transparent": QColor(75, 75, 75, 150),
"dark_dark_grey": QColor(48, 48, 48),
"dark_dark_grey_transparent": QColor(48, 48, 48, 150),
}
CP_SIZE = 12
AIRCRAFT_BANNERS: Dict[str, QPixmap] = {}
AIRCRAFT_ICONS: Dict[str, QPixmap] = {}
VEHICLE_BANNERS: Dict[str, QPixmap] = {}
@ -138,17 +93,6 @@ def load_icons():
"./resources/ui/misc/" + get_theme_icons() + "/ordnance_icon.png"
)
ICONS["target"] = QPixmap("./resources/ui/ground_assets/target.png")
ICONS["cleared"] = QPixmap("./resources/ui/ground_assets/cleared.png")
for category in NAME_BY_CATEGORY.keys():
ICONS[category] = QPixmap("./resources/ui/ground_assets/" + category + ".png")
ICONS[category + "_blue"] = QPixmap(
"./resources/ui/ground_assets/" + category + "_blue.png"
)
ICONS["destroyed"] = QPixmap("./resources/ui/ground_assets/destroyed.png")
ICONS["nothreat"] = QPixmap("./resources/ui/ground_assets/nothreat.png")
ICONS["nothreat_blue"] = QPixmap("./resources/ui/ground_assets/nothreat_blue.png")
ICONS["Generator"] = QPixmap(
"./resources/ui/misc/" + get_theme_icons() + "/generator.png"
)

View File

@ -1,8 +1,6 @@
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2 import QtCore, QtGui
from PySide2.QtWidgets import QCalendarWidget
from qt_ui.uiconstants import COLORS
class QLiberationCalendar(QCalendarWidget):
def __init__(self, parent=None):
@ -29,7 +27,7 @@ class QLiberationCalendar(QCalendarWidget):
painter.save()
painter.fillRect(rect, QtGui.QColor("#D3D3D3"))
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QtGui.QColor(COLORS["sea_blue"]))
painter.setBrush(QtGui.QColor(52, 68, 85))
r = QtCore.QRect(
QtCore.QPoint(), min(rect.width(), rect.height()) * QtCore.QSize(1, 1)
)

View File

@ -1,117 +0,0 @@
"""Common base for objects drawn on the game map."""
from typing import Optional
from PySide2.QtCore import Qt
from PySide2.QtGui import QPen
from PySide2.QtWidgets import (
QAction,
QGraphicsLineItem,
QGraphicsSceneContextMenuEvent,
QGraphicsSceneHoverEvent,
QGraphicsSceneMouseEvent,
QMenu,
)
import qt_ui.uiconstants as const
from game.theater import FrontLine
from qt_ui.dialogs import Dialog
from qt_ui.models import GameModel
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.mission.QPackageDialog import QNewPackageDialog
class QFrontLine(QGraphicsLineItem):
"""Base class for objects drawn on the game map.
Game map objects have an on_click behavior that triggers on left click, and
change the mouse cursor on hover.
"""
def __init__(
self,
x1: float,
y1: float,
x2: float,
y2: float,
mission_target: FrontLine,
game_model: GameModel,
) -> None:
super().__init__(x1, y1, x2, y2)
self.mission_target = mission_target
self.game_model = game_model
self.new_package_dialog: Optional[QNewPackageDialog] = None
self.setAcceptHoverEvents(True)
pen = QPen(brush=const.COLORS["bright_red"])
pen.setColor(const.COLORS["orange"])
pen.setWidth(8)
self.setPen(pen)
def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
self.setCursor(Qt.PointingHandCursor)
def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
if event.button() == Qt.LeftButton:
self.on_click()
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None:
menu = QMenu("Menu")
object_details_action = QAction(self.object_dialog_text)
object_details_action.triggered.connect(self.on_click)
menu.addAction(object_details_action)
new_package_action = QAction(f"New package")
new_package_action.triggered.connect(self.open_new_package_dialog)
menu.addAction(new_package_action)
if self.game_model.game.settings.enable_frontline_cheats:
cheat_forward = QAction(f"CHEAT: Advance Frontline")
cheat_forward.triggered.connect(self.cheat_forward)
menu.addAction(cheat_forward)
cheat_backward = QAction(f"CHEAT: Retreat Frontline")
cheat_backward.triggered.connect(self.cheat_backward)
menu.addAction(cheat_backward)
menu.exec_(event.screenPos())
@property
def object_dialog_text(self) -> str:
"""Text to for the object's dialog in the context menu.
Right clicking a map object will open a context menu and the first item
will open the details dialog for this object. This menu action has the
same behavior as the on_click event.
Return:
The text that should be displayed for the menu item.
"""
return "Details"
def on_click(self) -> None:
"""The action to take when this map object is left-clicked.
Typically this should open a details view of the object.
"""
raise NotImplementedError
def open_new_package_dialog(self) -> None:
"""Opens the dialog for planning a new mission package."""
Dialog.open_new_package_dialog(self.mission_target)
def cheat_forward(self) -> None:
self.mission_target.blue_cp.base.affect_strength(0.1)
self.mission_target.red_cp.base.affect_strength(-0.1)
# Clear the ATO to replan missions affected by the front line.
self.game_model.game.reset_ato()
self.game_model.game.initialize_turn()
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
def cheat_backward(self) -> None:
self.mission_target.blue_cp.base.affect_strength(-0.1)
self.mission_target.red_cp.base.affect_strength(0.1)
# Clear the ATO to replan missions affected by the front line.
self.game_model.game.reset_ato()
self.game_model.game.initialize_turn()
GameUpdateSignal.get_instance().updateGame(self.game_model.game)

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +0,0 @@
from PySide2.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent
import qt_ui.uiconstants as CONST
class QLiberationScene(QGraphicsScene):
def __init__(self, parent):
super().__init__(parent)
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):
super(QLiberationScene, self).mouseMoveEvent(event)
self.parent().sceneMouseMovedEvent(event)
def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
super(QLiberationScene, self).mousePressEvent(event)
self.parent().sceneMousePressEvent(event)

View File

@ -1,125 +0,0 @@
from typing import Optional
from PySide2.QtGui import QColor, QPainter
from PySide2.QtWidgets import QAction, QMenu
import qt_ui.uiconstants as const
from game.theater import ControlPoint, NavalControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
from .QMapObject import QMapObject
from ...displayoptions import DisplayOptions
from ...windows.GameUpdateSignal import GameUpdateSignal
class QMapControlPoint(QMapObject):
def __init__(
self,
parent,
x: float,
y: float,
w: float,
h: float,
control_point: ControlPoint,
game_model: GameModel,
) -> None:
super().__init__(x, y, w, h, mission_target=control_point)
self.game_model = game_model
self.control_point = control_point
self.parent = parent
self.setZValue(1)
self.setToolTip(self.control_point.name)
self.base_details_dialog: Optional[QBaseMenu2] = None
self.capture_action = QAction(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()
painter.setRenderHint(QPainter.Antialiasing)
painter.setBrush(self.brush_color)
painter.setPen(self.pen_color)
if not self.control_point.runway_is_operational():
painter.setBrush(const.COLORS["black"])
painter.setPen(self.brush_color)
r = option.rect
painter.drawEllipse(r.x(), r.y(), r.width(), r.height())
# TODO: Draw sunk carriers differently.
# Either don't draw them at all, or perhaps use a sunk ship icon.
painter.restore()
@property
def brush_color(self) -> QColor:
if self.control_point.captured:
return const.COLORS["blue"]
else:
return const.COLORS["super_red"]
@property
def pen_color(self) -> QColor:
return const.COLORS["white"]
@property
def object_dialog_text(self) -> str:
if self.control_point.captured:
return "Open base menu"
else:
return "Open intel menu"
def on_click(self) -> None:
self.base_details_dialog = QBaseMenu2(
self.window(), self.control_point, self.game_model
)
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
if self.control_point.captured:
return
for connected in self.control_point.connected_points:
if (
connected.captured
and self.game_model.game.settings.enable_base_capture_cheat
):
menu.addAction(self.capture_action)
break
def cheat_capture(self) -> None:
self.control_point.capture(self.game_model.game, for_player=True)
# Reinitialized ground planners and the like. The ATO needs to be reset because
# missions planned against the flipped base are no longer valid.
self.game_model.game.reset_ato()
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."""
is_navy = isinstance(self.control_point, NavalControlPoint)
if self.control_point.captured or is_navy:
super().open_new_package_dialog()
return
self.on_click()

View File

@ -1,167 +0,0 @@
from typing import List, Optional
from PySide2.QtCore import QRect
from PySide2.QtGui import QBrush
from PySide2.QtWidgets import QGraphicsItem
import qt_ui.uiconstants as const
from game import Game
from game.data.building_data import FORTIFICATION_BUILDINGS
from game.db import REWARDS
from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import (
MissileSiteGroundObject,
CoastalSiteGroundObject,
)
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
from .QMapObject import QMapObject
from ...displayoptions import DisplayOptions
class QMapGroundObject(QMapObject):
def __init__(
self,
parent,
x: float,
y: float,
w: float,
h: float,
control_point: ControlPoint,
ground_object: TheaterGroundObject,
game: Game,
buildings: Optional[List[TheaterGroundObject]] = None,
) -> None:
super().__init__(x, y, w, h, mission_target=ground_object)
self.ground_object = ground_object
self.control_point = control_point
self.parent = parent
self.game = game
self.setZValue(2)
self.buildings = buildings if buildings is not None else []
self.setFlag(QGraphicsItem.ItemIgnoresTransformations, False)
self.ground_object_dialog: Optional[QGroundObjectMenu] = None
self.setToolTip(self.tooltip)
@property
def tooltip(self) -> str:
lines = [
f"[{self.ground_object.obj_name}]",
f"${self.production_per_turn} per turn",
]
if self.ground_object.groups:
units = {}
for g in self.ground_object.groups:
for u in g.units:
if u.type in units:
units[u.type] = units[u.type] + 1
else:
units[u.type] = 1
for unit in units.keys():
lines.append(f"{unit} x {units[unit]}")
else:
for building in self.buildings:
if not building.is_dead:
lines.append(f"{building.dcs_identifier}")
return "\n".join(lines)
@property
def production_per_turn(self) -> int:
production = 0
for building in self.buildings:
if building.is_dead:
continue
if building.category in REWARDS.keys():
production += REWARDS[building.category]
return production
def paint(self, painter, option, widget=None) -> None:
player_icons = "_blue"
enemy_icons = ""
if DisplayOptions.ground_objects:
painter.save()
cat = self.ground_object.category
rect = QRect(
option.rect.x() + 2,
option.rect.y(),
option.rect.width() - 2,
option.rect.height(),
)
is_dead = self.ground_object.is_dead
for building in self.buildings:
if not building.is_dead:
is_dead = False
break
if cat == "aa":
has_threat = False
for group in self.ground_object.groups:
if self.ground_object.threat_range(group).distance_in_meters > 0:
has_threat = True
if not is_dead and not self.control_point.captured:
if cat == "aa" and not has_threat:
painter.drawPixmap(rect, const.ICONS["nothreat" + enemy_icons])
else:
painter.drawPixmap(rect, const.ICONS[cat + enemy_icons])
elif not is_dead:
if cat == "aa" and not has_threat:
painter.drawPixmap(rect, const.ICONS["nothreat" + player_icons])
else:
painter.drawPixmap(rect, const.ICONS[cat + player_icons])
else:
painter.drawPixmap(rect, const.ICONS["destroyed"])
self.draw_health_gauge(painter, option)
painter.restore()
def draw_health_gauge(self, painter, option) -> None:
units_alive = 0
units_dead = 0
if len(self.ground_object.groups) == 0:
for building in self.buildings:
if building.dcs_identifier in FORTIFICATION_BUILDINGS:
continue
if building.is_dead:
units_dead += 1
else:
units_alive += 1
for g in self.ground_object.groups:
units_alive += len(g.units)
if hasattr(g, "units_losts"):
units_dead += len(g.units_losts)
if units_dead + units_alive > 0:
ratio = float(units_alive) / (float(units_dead) + float(units_alive))
bar_height = ratio * option.rect.height()
painter.fillRect(
option.rect.x(),
option.rect.y(),
2,
option.rect.height(),
QBrush(const.COLORS["dark_red"]),
)
painter.fillRect(
option.rect.x(),
option.rect.y(),
2,
bar_height,
QBrush(const.COLORS["green"]),
)
def on_click(self) -> None:
self.ground_object_dialog = QGroundObjectMenu(
self.window(),
self.ground_object,
self.buildings,
self.control_point,
self.game,
)
self.ground_object_dialog.show()

View File

@ -1,84 +0,0 @@
"""Common base for objects drawn on the game map."""
from typing import Optional
from PySide2.QtCore import Qt
from PySide2.QtWidgets import (
QAction,
QGraphicsRectItem,
QGraphicsSceneContextMenuEvent,
QGraphicsSceneHoverEvent,
QGraphicsSceneMouseEvent,
QMenu,
)
from qt_ui.dialogs import Dialog
from qt_ui.windows.mission.QPackageDialog import QNewPackageDialog
from game.theater.missiontarget import MissionTarget
class QMapObject(QGraphicsRectItem):
"""Base class for objects drawn on the game map.
Game map objects have an on_click behavior that triggers on left click, and
change the mouse cursor on hover.
"""
def __init__(
self, x: float, y: float, w: float, h: float, mission_target: MissionTarget
) -> None:
super().__init__(x, y, w, h)
self.mission_target = mission_target
self.new_package_dialog: Optional[QNewPackageDialog] = None
self.setAcceptHoverEvents(True)
def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent):
self.setCursor(Qt.PointingHandCursor)
def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
if event.button() == Qt.LeftButton:
self.on_click()
def add_context_menu_actions(self, menu: QMenu) -> None:
pass
def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None:
menu = QMenu("Menu", self.parent)
object_details_action = QAction(self.object_dialog_text)
object_details_action.triggered.connect(self.on_click)
menu.addAction(object_details_action)
# Not all locations have valid objectives. Off-map spawns, for example,
# have no mission types.
if list(self.mission_target.mission_types(for_player=True)):
new_package_action = QAction(f"New package")
new_package_action.triggered.connect(self.open_new_package_dialog)
menu.addAction(new_package_action)
self.add_context_menu_actions(menu)
menu.exec_(event.screenPos())
@property
def object_dialog_text(self) -> str:
"""Text to for the object's dialog in the context menu.
Right clicking a map object will open a context menu and the first item
will open the details dialog for this object. This menu action has the
same behavior as the on_click event.
Return:
The text that should be displayed for the menu item.
"""
return "Details"
def on_click(self) -> None:
"""The action to take when this map object is left-clicked.
Typically this should open a details view of the object.
"""
raise NotImplementedError
def open_new_package_dialog(self) -> None:
"""Opens the dialog for planning a new mission package."""
Dialog.open_new_package_dialog(self.mission_target)

View File

@ -1,70 +0,0 @@
from typing import List, Optional
from PySide2.QtCore import Qt
from PySide2.QtGui import QColor, QPen
from PySide2.QtWidgets import (
QGraphicsItem,
QGraphicsLineItem,
)
from game.theater import ControlPoint
from game.transfers import CargoShip
from qt_ui.uiconstants import COLORS
class ShippingLaneSegment(QGraphicsLineItem):
def __init__(
self,
x0: float,
y0: float,
x1: float,
y1: float,
control_point_a: ControlPoint,
control_point_b: ControlPoint,
ships: List[CargoShip],
parent: Optional[QGraphicsItem] = None,
) -> None:
super().__init__(x0, y0, x1, y1, parent)
self.control_point_a = control_point_a
self.control_point_b = control_point_b
self.ships = ships
self.setPen(self.make_pen())
self.setToolTip(self.make_tooltip())
self.setAcceptHoverEvents(True)
@property
def has_ships(self) -> bool:
return bool(self.ships)
def make_tooltip(self) -> str:
if not self.has_ships:
return "No ships present in this shipping lane."
ships = []
for ship in self.ships:
units = "units" if ship.size > 1 else "unit"
ships.append(
f"{ship.size} {units} transferring from {ship.origin} to "
f"{ship.destination}."
)
return "\n".join(ships)
@property
def line_color(self) -> QColor:
if self.control_point_a.captured:
return COLORS["dark_blue"]
else:
return COLORS["dark_red"]
@property
def line_style(self) -> Qt.PenStyle:
if self.has_ships:
return Qt.PenStyle.SolidLine
return Qt.PenStyle.DotLine
def make_pen(self) -> QPen:
pen = QPen(brush=self.line_color)
pen.setColor(self.line_color)
pen.setStyle(self.line_style)
pen.setWidth(2)
return pen

View File

@ -1,76 +0,0 @@
from functools import cached_property
from typing import List, Optional
from PySide2.QtCore import Qt
from PySide2.QtGui import QColor, QPen
from PySide2.QtWidgets import (
QGraphicsItem,
QGraphicsLineItem,
)
from game.theater import ControlPoint
from game.transfers import Convoy
from qt_ui.uiconstants import COLORS
class SupplyRouteSegment(QGraphicsLineItem):
def __init__(
self,
x0: float,
y0: float,
x1: float,
y1: float,
control_point_a: ControlPoint,
control_point_b: ControlPoint,
convoys: List[Convoy],
parent: Optional[QGraphicsItem] = None,
) -> None:
super().__init__(x0, y0, x1, y1, parent)
self.control_point_a = control_point_a
self.control_point_b = control_point_b
self.convoys = convoys
self.setPen(self.make_pen())
self.setToolTip(self.make_tooltip())
self.setAcceptHoverEvents(True)
@property
def has_convoys(self) -> bool:
return bool(self.convoys)
def make_tooltip(self) -> str:
if not self.has_convoys:
return "No convoys present on this supply route."
convoys = []
for convoy in self.convoys:
units = "units" if convoy.size > 1 else "unit"
convoys.append(
f"{convoy.size} {units} transferring from {convoy.origin} to "
f"{convoy.destination}"
)
return "\n".join(convoys)
@property
def line_color(self) -> QColor:
if self.control_point_a.front_is_active(self.control_point_b):
return COLORS["red"]
elif self.control_point_a.captured:
return COLORS["dark_blue"]
else:
return COLORS["dark_red"]
@property
def line_style(self) -> Qt.PenStyle:
if (
self.control_point_a.front_is_active(self.control_point_b)
or self.has_convoys
):
return Qt.PenStyle.SolidLine
return Qt.PenStyle.DotLine
def make_pen(self) -> QPen:
pen = QPen(brush=self.line_color)
pen.setColor(self.line_color)
pen.setStyle(self.line_style)
pen.setWidth(6)
return pen

View File

@ -36,6 +36,8 @@ from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
LeafletLatLon = list[float]
LeafletPoly = list[LeafletLatLon]
MAX_SHIP_DISTANCE = nautical_miles(80)
# **EVERY PROPERTY NEEDS A NOTIFY SIGNAL**
#
# https://bugreports.qt.io/browse/PYSIDE-1426

View File

@ -22,12 +22,11 @@ from game import Game, VERSION, persistency
from game.debriefing import Debriefing
from qt_ui import liberation_install
from qt_ui.dialogs import Dialog
from qt_ui.displayoptions import DisplayGroup, DisplayOptions, DisplayRule
from qt_ui.models import GameModel
from qt_ui.uiconstants import URLS
from qt_ui.widgets.QTopPanel import QTopPanel
from qt_ui.widgets.ato import QAirTaskingOrderPanel
from qt_ui.widgets.map.QLiberationMap import LeafletMap, QLiberationMap, LiberationMap
from qt_ui.widgets.map.QLiberationMap import QLiberationMap
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.QDebriefingWindow import QDebriefingWindow
from qt_ui.windows.infos.QInfoPanel import QInfoPanel
@ -38,7 +37,7 @@ from qt_ui.windows.preferences.QLiberationPreferencesWindow import (
class QLiberationWindow(QMainWindow):
def __init__(self, game: Optional[Game], new_map: bool) -> None:
def __init__(self, game: Optional[Game]) -> None:
super(QLiberationWindow, self).__init__()
self.game = game
@ -46,7 +45,7 @@ class QLiberationWindow(QMainWindow):
Dialog.set_game(self.game_model)
self.ato_panel = QAirTaskingOrderPanel(self.game_model)
self.info_panel = QInfoPanel(self.game)
self.liberation_map: LiberationMap = self.create_map(new_map)
self.liberation_map = QLiberationMap(self.game_model, self)
self.setGeometry(300, 100, 270, 100)
self.setWindowTitle(f"DCS Liberation - v{VERSION}")
@ -174,30 +173,6 @@ class QLiberationWindow(QMainWindow):
file_menu.addSeparator()
file_menu.addAction("E&xit", self.close)
displayMenu = self.menu.addMenu("&Display")
last_was_group = False
for item in DisplayOptions.menu_items():
if isinstance(item, DisplayRule):
if last_was_group:
displayMenu.addSeparator()
self.display_bar.addSeparator()
action = self.make_display_rule_action(item)
displayMenu.addAction(action)
if action.icon():
self.display_bar.addAction(action)
last_was_group = False
elif isinstance(item, DisplayGroup):
displayMenu.addSeparator()
self.display_bar.addSeparator()
group = QActionGroup(displayMenu)
for display_rule in item:
action = self.make_display_rule_action(display_rule, group)
displayMenu.addAction(action)
if action.icon():
self.display_bar.addAction(action)
last_was_group = True
help_menu = self.menu.addMenu("&Help")
help_menu.addAction(self.openDiscordAction)
help_menu.addAction(self.openGithubAction)
@ -284,11 +259,6 @@ class QLiberationWindow(QMainWindow):
self.game = game
GameUpdateSignal.get_instance().game_loaded.emit(self.game)
def create_map(self, new_map: bool) -> LiberationMap:
if new_map:
return LeafletMap(self.game_model, self)
return QLiberationMap(self.game_model)
def setGame(self, game: Optional[Game]):
try:
self.game = game