From b65d178cf1a416c63cc7992a29b3fb47f3dae2d4 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 15:57:18 -0700 Subject: [PATCH 001/438] Move develop to 2.6. --- changelog.md | 8 ++++++++ game/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 2f5e9d14..11770bcb 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +# 2.6.0 + +Saves from 2.5 are not compatible with 2.6. + +## Features/Improvements + +## Fixes + # 2.5.0 Saves from 2.4 are not compatible with 2.5. diff --git a/game/version.py b/game/version.py index 4d54004e..37a7dcfb 100644 --- a/game/version.py +++ b/game/version.py @@ -2,7 +2,7 @@ from pathlib import Path def _build_version_string() -> str: - components = ["2.5"] + components = ["2.6"] build_number_path = Path("resources/buildnumber") if build_number_path.exists(): with build_number_path.open("r") as build_number_file: From e9ff554f397a3ad8fe61423cdfdff8e0eafc9a5e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 15 Feb 2021 12:49:36 -0800 Subject: [PATCH 002/438] Basic implementation of road based transfers. This adds the models and UIs for creating ground unit transfer orders. Most of the feature is still missing: * The AI doesn't do them. * Transfers can move across the whole map in one turn. * Transfers between disconnected bases are allowed. * Transfers are not modeled in the simulation, so they can't be interdicted. https://github.com/Khopa/dcs_liberation/issues/824 --- game/db.py | 4 +- game/game.py | 13 + game/transfers.py | 76 +++++ qt_ui/delegate_helpers.py | 13 + qt_ui/models.py | 63 +++- qt_ui/widgets/QTopPanel.py | 23 +- qt_ui/widgets/ato.py | 14 +- qt_ui/windows/PendingTransfersDialog.py | 189 +++++++++++ .../windows/basemenu/NewUnitTransferDialog.py | 310 ++++++++++++++++++ qt_ui/windows/basemenu/QBaseMenu2.py | 9 + qt_ui/windows/basemenu/QRecruitBehaviour.py | 17 +- .../airfield/QAircraftRecruitmentMenu.py | 1 - 12 files changed, 702 insertions(+), 30 deletions(-) create mode 100644 game/transfers.py create mode 100644 qt_ui/delegate_helpers.py create mode 100644 qt_ui/windows/PendingTransfersDialog.py create mode 100644 qt_ui/windows/basemenu/NewUnitTransferDialog.py diff --git a/game/db.py b/game/db.py index 809a43b1..c091a399 100644 --- a/game/db.py +++ b/game/db.py @@ -1442,11 +1442,11 @@ def unit_task(unit: UnitType) -> Optional[Task]: return None -def find_unittype(for_task: Task, country_name: str) -> List[Type[UnitType]]: +def find_unittype(for_task: Type[MainTask], country_name: str) -> List[Type[UnitType]]: return [x for x in UNIT_BY_TASK[for_task] if x in FACTIONS[country_name].units] -MANPADS: List[VehicleType] = [ +MANPADS: List[Type[VehicleType]] = [ AirDefence.MANPADS_SA_18_Igla_Grouse, AirDefence.MANPADS_SA_18_Igla_S_Grouse, AirDefence.MANPADS_Stinger, diff --git a/game/game.py b/game/game.py index 22598504..9dcf5006 100644 --- a/game/game.py +++ b/game/game.py @@ -34,6 +34,7 @@ from .settings import Settings from .theater import ConflictTheater, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import MissileSiteGroundObject from .threatzones import ThreatZones +from .transfers import PendingTransfers from .unitmap import UnitMap from .weather import Conditions, TimeOfDay @@ -121,6 +122,8 @@ class Game: self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints) + self._transfers = PendingTransfers() + self.sanitize_sides() self.on_load() @@ -151,6 +154,14 @@ class Game: # Regenerate any state that was not persisted. self.on_load() + @property + def transfers(self) -> PendingTransfers: + try: + return self._transfers + except AttributeError: + self._transfers = PendingTransfers() + return self._transfers + def generate_conditions(self) -> Conditions: return Conditions.generate( self.theater, self.current_day, self.current_turn_time_of_day, self.settings @@ -264,6 +275,8 @@ class Game: for control_point in self.theater.controlpoints: control_point.process_turn(self) + self.transfers.complete_transfers() + self.process_enemy_income() self.process_player_income() diff --git a/game/transfers.py b/game/transfers.py new file mode 100644 index 00000000..cc2e7665 --- /dev/null +++ b/game/transfers.py @@ -0,0 +1,76 @@ +import logging +from dataclasses import dataclass +from typing import Dict, List, Type + +from dcs.unittype import VehicleType +from game.theater import ControlPoint + + +@dataclass +class TransferOrder: + """The base type of all transfer orders. + + A transfer order can transfer multiple units of multiple types. + """ + + #: The location the units are transferring from. + origin: ControlPoint + + #: The location the units are transferring to. + destination: ControlPoint + + #: True if the transfer order belongs to the player. + player: bool + + +@dataclass +class RoadTransferOrder(TransferOrder): + """A transfer order that moves units by road.""" + + #: The units being transferred. + units: Dict[Type[VehicleType], int] + + +class PendingTransfers: + def __init__(self) -> None: + self.pending_transfers: List[RoadTransferOrder] = [] + + @property + def pending_transfer_count(self) -> int: + return len(self.pending_transfers) + + def transfer_at_index(self, index: int) -> RoadTransferOrder: + return self.pending_transfers[index] + + def new_transfer(self, transfer: RoadTransferOrder) -> None: + transfer.origin.base.commit_losses(transfer.units) + self.pending_transfers.append(transfer) + + def cancel_transfer(self, transfer: RoadTransferOrder) -> None: + self.pending_transfers.remove(transfer) + transfer.origin.base.commision_units(transfer.units) + + def complete_transfers(self) -> None: + for transfer in self.pending_transfers: + self.complete_transfer(transfer) + self.pending_transfers.clear() + + @staticmethod + def complete_transfer(transfer: RoadTransferOrder) -> None: + if transfer.player == transfer.destination.captured: + logging.info( + f"Units transferred from {transfer.origin.name} to " + f"{transfer.destination.name}" + ) + transfer.destination.base.commision_units(transfer.units) + elif transfer.player == transfer.origin.captured: + logging.info( + f"{transfer.destination.name} was captured. Transferring units are " + f"returning to {transfer.origin.name}" + ) + transfer.origin.base.commision_units(transfer.units) + else: + logging.info( + f"Both {transfer.origin.name} and {transfer.destination.name} were " + "captured. Units were surrounded and captured during transfer." + ) diff --git a/qt_ui/delegate_helpers.py b/qt_ui/delegate_helpers.py new file mode 100644 index 00000000..0c437310 --- /dev/null +++ b/qt_ui/delegate_helpers.py @@ -0,0 +1,13 @@ +from contextlib import contextmanager +from typing import ContextManager + +from PySide2.QtGui import QPainter + + +@contextmanager +def painter_context(painter: QPainter) -> ContextManager[None]: + try: + painter.save() + yield + finally: + painter.restore() diff --git a/qt_ui/models.py b/qt_ui/models.py index cdc594d6..bd7d2bc8 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -1,4 +1,6 @@ """Qt data models for game objects.""" +from __future__ import annotations + import datetime from typing import Any, Callable, Dict, Iterator, Optional, TypeVar @@ -12,11 +14,12 @@ from PySide2.QtGui import QIcon from game import db from game.game import Game +from game.theater.missiontarget import MissionTarget +from game.transfers import RoadTransferOrder from gen.ato import AirTaskingOrder, Package from gen.flights.flight import Flight from gen.flights.traveltime import TotEstimator from qt_ui.uiconstants import AIRCRAFT_ICONS -from game.theater.missiontarget import MissionTarget class DeletableChildModelManager: @@ -285,6 +288,63 @@ class AtoModel(QAbstractListModel): yield self.package_models.acquire(package) +class TransferModel(QAbstractListModel): + """The model for a ground unit transfer.""" + + TransferRole = Qt.UserRole + + def __init__(self, game_model: GameModel) -> None: + super().__init__() + self.game_model = game_model + + def rowCount(self, parent: QModelIndex = QModelIndex()) -> int: + return self.game_model.game.transfers.pending_transfer_count + + def data(self, index: QModelIndex, role: int = Qt.DisplayRole) -> Any: + if not index.isValid(): + return None + transfer = self.transfer_at_index(index) + if role == Qt.DisplayRole: + return self.text_for_transfer(transfer) + if role == Qt.DecorationRole: + return self.icon_for_transfer(transfer) + elif role == TransferModel.TransferRole: + return transfer + return None + + @staticmethod + def text_for_transfer(transfer: RoadTransferOrder) -> str: + """Returns the text that should be displayed for the transfer.""" + count = sum(transfer.units.values()) + origin = transfer.origin.name + destination = transfer.destination.name + return f"Transfer of {count} units from {origin} to {destination}" + + @staticmethod + def icon_for_transfer(_transfer: RoadTransferOrder) -> Optional[QIcon]: + """Returns the icon that should be displayed for the transfer.""" + return None + + def new_transfer(self, transfer: RoadTransferOrder) -> None: + """Updates the game with the new unit transfer.""" + self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount()) + # TODO: Needs to regenerate base inventory tab. + self.game_model.game.transfers.new_transfer(transfer) + self.endInsertRows() + + def cancel_transfer_at_index(self, index: QModelIndex) -> None: + """Cancels the planned unit transfer at the given index.""" + transfer = self.transfer_at_index(index) + self.beginRemoveRows(QModelIndex(), index.row(), index.row()) + # TODO: Needs to regenerate base inventory tab. + self.game_model.game.transfers.cancel_transfer(transfer) + self.endRemoveRows() + + def transfer_at_index(self, index: QModelIndex) -> RoadTransferOrder: + """Returns the transfer located at the given index.""" + return self.game_model.game.transfers.transfer_at_index(index.row()) + + class GameModel: """A model for the Game object. @@ -294,6 +354,7 @@ class GameModel: def __init__(self, game: Optional[Game]) -> None: self.game: Optional[Game] = game + self.transfer_model = TransferModel(self) if self.game is None: self.ato_model = AtoModel(self.game, AirTaskingOrder()) self.red_ato_model = AtoModel(self.game, AirTaskingOrder()) diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index 68834d28..f43657f7 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -4,6 +4,7 @@ from datetime import timedelta from typing import List, Optional from PySide2.QtWidgets import ( + QDialog, QFrame, QGroupBox, QHBoxLayout, @@ -22,6 +23,7 @@ from qt_ui.widgets.QFactionsInfos import QFactionsInfos from qt_ui.widgets.QIntelBox import QIntelBox from qt_ui.widgets.clientslots import MaxPlayerCount from qt_ui.windows.GameUpdateSignal import GameUpdateSignal +from qt_ui.windows.PendingTransfersDialog import PendingTransfersDialog from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResultWindow from qt_ui.windows.settings.QSettingsWindow import QSettingsWindow from qt_ui.windows.stats.QStatsWindow import QStatsWindow @@ -32,6 +34,8 @@ class QTopPanel(QFrame): def __init__(self, game_model: GameModel): super(QTopPanel, self).__init__() self.game_model = game_model + self.dialog: Optional[QDialog] = None + self.setMaximumHeight(70) self.init_ui() GameUpdateSignal.get_instance().gameupdated.connect(self.setGame) @@ -61,6 +65,11 @@ class QTopPanel(QFrame): self.factionsInfos = QFactionsInfos(self.game) + self.transfers = QPushButton("Transfers") + self.transfers.setDisabled(True) + self.transfers.setProperty("style", "btn-primary") + self.transfers.clicked.connect(self.open_transfers) + self.settings = QPushButton("Settings") self.settings.setDisabled(True) self.settings.setIcon(CONST.ICONS["Settings"]) @@ -77,6 +86,7 @@ class QTopPanel(QFrame): self.buttonBox = QGroupBox("Misc") self.buttonBoxLayout = QHBoxLayout() + self.buttonBoxLayout.addWidget(self.transfers) self.buttonBoxLayout.addWidget(self.settings) self.buttonBoxLayout.addWidget(self.statistics) self.buttonBox.setLayout(self.buttonBoxLayout) @@ -106,6 +116,7 @@ class QTopPanel(QFrame): if game is None: return + self.transfers.setEnabled(True) self.settings.setEnabled(True) self.statistics.setEnabled(True) @@ -121,13 +132,17 @@ class QTopPanel(QFrame): else: self.proceedButton.setEnabled(True) + def open_transfers(self): + self.dialog = PendingTransfersDialog(self.game_model) + self.dialog.show() + def openSettings(self): - self.subwindow = QSettingsWindow(self.game) - self.subwindow.show() + self.dialog = QSettingsWindow(self.game) + self.dialog.show() def openStatisticsWindow(self): - self.subwindow = QStatsWindow(self.game) - self.subwindow.show() + self.dialog = QStatsWindow(self.game) + self.dialog.show() def passTurn(self): start = timeit.default_timer() diff --git a/qt_ui/widgets/ato.py b/qt_ui/widgets/ato.py index fa5e7072..93b170bb 100644 --- a/qt_ui/widgets/ato.py +++ b/qt_ui/widgets/ato.py @@ -1,7 +1,6 @@ """Widgets for displaying air tasking orders.""" import logging -from contextlib import contextmanager -from typing import ContextManager, Optional +from typing import Optional from PySide2.QtCore import ( QItemSelectionModel, @@ -32,11 +31,11 @@ from PySide2.QtWidgets import ( QVBoxLayout, ) -from game import db from gen.ato import Package from gen.flights.flight import Flight from gen.flights.traveltime import TotEstimator from qt_ui.windows.GameUpdateSignal import GameUpdateSignal +from ..delegate_helpers import painter_context from ..models import AtoModel, GameModel, NullListModel, PackageModel @@ -312,15 +311,6 @@ class QFlightPanel(QGroupBox): self.flight_list.delete_flight(index) -@contextmanager -def painter_context(painter: QPainter) -> ContextManager[None]: - try: - painter.save() - yield - finally: - painter.restore() - - class PackageDelegate(QStyledItemDelegate): FONT_SIZE = 12 HMARGIN = 4 diff --git a/qt_ui/windows/PendingTransfersDialog.py b/qt_ui/windows/PendingTransfersDialog.py new file mode 100644 index 00000000..6892debf --- /dev/null +++ b/qt_ui/windows/PendingTransfersDialog.py @@ -0,0 +1,189 @@ +from typing import Optional + +from PySide2.QtCore import ( + QItemSelection, + QItemSelectionModel, + QModelIndex, + QSize, + Qt, +) +from PySide2.QtGui import QContextMenuEvent, QFont, QFontMetrics, QIcon, QPainter +from PySide2.QtWidgets import ( + QAbstractItemView, + QAction, + QDialog, + QHBoxLayout, + QListView, + QMenu, + QPushButton, + QStyle, + QStyleOptionViewItem, + QStyledItemDelegate, + QVBoxLayout, +) + +from game.transfers import RoadTransferOrder +from qt_ui.delegate_helpers import painter_context +from qt_ui.models import GameModel, TransferModel + + +class TransferDelegate(QStyledItemDelegate): + FONT_SIZE = 10 + HMARGIN = 4 + VMARGIN = 4 + + def __init__(self, transfer_model: TransferModel) -> None: + super().__init__() + self.transfer_model = transfer_model + + def get_font(self, option: QStyleOptionViewItem) -> QFont: + font = QFont(option.font) + font.setPointSize(self.FONT_SIZE) + return font + + @staticmethod + def transfer(index: QModelIndex) -> RoadTransferOrder: + return index.data(TransferModel.TransferRole) + + def first_row_text(self, index: QModelIndex) -> str: + return self.transfer_model.data(index, Qt.DisplayRole) + + def second_row_text(self, index: QModelIndex) -> str: + transfer = self.transfer(index) + return f"Currently at {transfer.origin}. Arrives at destination in 1 turn." + + def paint( + self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex + ) -> None: + # Draw the list item with all the default selection styling, but with an + # invalid index so text formatting is left to us. + super().paint(painter, option, QModelIndex()) + + rect = option.rect.adjusted( + self.HMARGIN, self.VMARGIN, -self.HMARGIN, -self.VMARGIN + ) + + with painter_context(painter): + painter.setFont(self.get_font(option)) + + icon: Optional[QIcon] = index.data(Qt.DecorationRole) + if icon is not None: + icon.paint( + painter, + rect, + Qt.AlignLeft | Qt.AlignVCenter, + self.icon_mode(option), + self.icon_state(option), + ) + + rect = rect.adjusted(self.icon_size(option).width() + self.HMARGIN, 0, 0, 0) + painter.drawText(rect, Qt.AlignLeft, self.first_row_text(index)) + line2 = rect.adjusted(0, rect.height() / 2, 0, rect.height() / 2) + painter.drawText(line2, Qt.AlignLeft, self.second_row_text(index)) + + @staticmethod + def icon_mode(option: QStyleOptionViewItem) -> QIcon.Mode: + if not (option.state & QStyle.State_Enabled): + return QIcon.Disabled + elif option.state & QStyle.State_Selected: + return QIcon.Selected + elif option.state & QStyle.State_Active: + return QIcon.Active + return QIcon.Normal + + @staticmethod + def icon_state(option: QStyleOptionViewItem) -> QIcon.State: + return QIcon.On if option.state & QStyle.State_Open else QIcon.Off + + @staticmethod + def icon_size(option: QStyleOptionViewItem) -> QSize: + icon_size: Optional[QSize] = option.decorationSize + if icon_size is None: + return QSize(0, 0) + else: + return icon_size + + def sizeHint(self, option: QStyleOptionViewItem, index: QModelIndex) -> QSize: + left = self.icon_size(option).width() + self.HMARGIN + metrics = QFontMetrics(self.get_font(option)) + first = metrics.size(0, self.first_row_text(index)) + second = metrics.size(0, self.second_row_text(index)) + text_width = max(first.width(), second.width()) + return QSize( + left + text_width + 2 * self.HMARGIN, + first.height() + second.height() + 2 * self.VMARGIN, + ) + + +class PendingTransfersList(QListView): + """List view for displaying the pending unit transfers.""" + + def __init__(self, transfer_model: TransferModel) -> None: + super().__init__() + self.transfer_model = transfer_model + + self.setItemDelegate(TransferDelegate(self.transfer_model)) + self.setModel(self.transfer_model) + self.selectionModel().setCurrentIndex( + self.transfer_model.index(0, 0, QModelIndex()), QItemSelectionModel.Select + ) + + # self.setIconSize(QSize(91, 24)) + self.setSelectionBehavior(QAbstractItemView.SelectItems) + + def contextMenuEvent(self, event: QContextMenuEvent) -> None: + index = self.indexAt(event.pos()) + + menu = QMenu("Menu") + + delete_action = QAction("Cancel") + delete_action.triggered.connect(lambda: self.cancel_transfer(index)) + menu.addAction(delete_action) + + menu.exec_(event.globalPos()) + + def cancel_transfer(self, index: QModelIndex) -> None: + """Cancels the given transfer order.""" + self.transfer_model.cancel_transfer_at_index(index) + + +class PendingTransfersDialog(QDialog): + """Dialog window showing all scheduled transfers for the player.""" + + def __init__(self, game_model: GameModel, parent=None) -> None: + super().__init__(parent) + self.transfer_model = game_model.transfer_model + + self.setMinimumSize(1000, 440) + self.setWindowTitle(f"Pending Transfers") + # TODO: self.setWindowIcon() + + layout = QVBoxLayout() + self.setLayout(layout) + + self.transfer_list = PendingTransfersList(self.transfer_model) + self.transfer_list.selectionModel().selectionChanged.connect( + self.on_selection_changed + ) + layout.addWidget(self.transfer_list) + + button_layout = QHBoxLayout() + layout.addLayout(button_layout) + + button_layout.addStretch() + + self.cancel_button = QPushButton("Cancel Transfer") + self.cancel_button.setProperty("style", "btn-danger") + self.cancel_button.clicked.connect(self.on_cancel_transfer) + self.cancel_button.setEnabled(self.transfer_model.rowCount() > 0) + button_layout.addWidget(self.cancel_button) + + def on_cancel_transfer(self) -> None: + """Cancels the selected transfer order.""" + self.transfer_model.cancel_transfer_at_index(self.transfer_list.currentIndex()) + + def on_selection_changed( + self, selected: QItemSelection, _deselected: QItemSelection + ) -> None: + """Updates the state of the delete button.""" + self.cancel_button.setEnabled(not selected.empty()) diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py new file mode 100644 index 00000000..4df54d31 --- /dev/null +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -0,0 +1,310 @@ +from __future__ import annotations + +import logging +from collections import defaultdict +from typing import Callable, Dict, Type + +from PySide2.QtCore import Qt +from PySide2.QtWidgets import ( + QComboBox, + QDialog, + QFrame, + QGridLayout, + QGroupBox, + QHBoxLayout, + QLabel, + QPushButton, + QScrollArea, + QSizePolicy, + QSpacerItem, + QVBoxLayout, + QWidget, +) +from dcs.task import PinpointStrike +from dcs.unittype import UnitType + +from game import db +from game.theater import ControlPoint +from game.transfers import RoadTransferOrder +from qt_ui.models import GameModel +from qt_ui.widgets.QLabeledWidget import QLabeledWidget + + +class TransferDestinationComboBox(QComboBox): + def __init__(self, game_model: GameModel, origin: ControlPoint) -> None: + super().__init__() + + for cp in game_model.game.theater.controlpoints: + if cp != origin and cp.captured and not cp.is_global: + self.addItem(cp.name, cp) + self.model().sort(0) + self.setCurrentIndex(0) + + +class UnitTransferList(QFrame): + def __init__(self, cp: ControlPoint, game_model: GameModel): + super().__init__(self) + self.cp = cp + self.game_model = game_model + + self.bought_amount_labels = {} + self.existing_units_labels = {} + + main_layout = QVBoxLayout() + self.setLayout(main_layout) + + scroll_content = QWidget() + task_box_layout = QGridLayout() + scroll_content.setLayout(task_box_layout) + + units_column = sorted( + cp.base.armor, + key=lambda u: db.unit_get_expanded_info( + self.game_model.game.player_country, u, "name" + ), + ) + + count = 0 + for count, unit_type in enumerate(units_column): + self.add_purchase_row(unit_type, task_box_layout, count) + stretch = QVBoxLayout() + stretch.addStretch() + task_box_layout.addLayout(stretch, count, 0) + + scroll_content.setLayout(task_box_layout) + scroll = QScrollArea() + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + scroll.setWidgetResizable(True) + scroll.setWidget(scroll_content) + main_layout.addWidget(scroll) + + +class TransferDestinationPanel(QVBoxLayout): + def __init__(self, label: str, origin: ControlPoint, game_model: GameModel) -> None: + super().__init__() + + self.source_combo_box = TransferDestinationComboBox(game_model, origin) + self.addLayout(QLabeledWidget(label, self.source_combo_box)) + + @property + def changed(self): + return self.source_combo_box.currentIndexChanged + + @property + def current(self) -> ControlPoint: + return self.source_combo_box.currentData() + + +class TransferControls(QGroupBox): + def __init__( + self, + increase_text: str, + on_increase: Callable[[TransferControls], None], + decrease_text: str, + on_decrease: Callable[[TransferControls], None], + initial_amount: int = 0, + disabled: bool = False, + ) -> None: + super().__init__() + + self.quantity = initial_amount + + self.setProperty("style", "buy-box") + self.setMaximumHeight(36) + self.setMinimumHeight(36) + layout = QHBoxLayout() + self.setLayout(layout) + + decrease = QPushButton(decrease_text) + decrease.setProperty("style", "btn-sell") + decrease.setDisabled(disabled) + decrease.setMinimumSize(16, 16) + decrease.setMaximumSize(16, 16) + decrease.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) + decrease.clicked.connect(lambda: on_decrease(self)) + layout.addWidget(decrease) + + self.count_label = QLabel() + self.count_label.setSizePolicy( + QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + ) + self.set_quantity(initial_amount) + layout.addWidget(self.count_label) + + increase = QPushButton(increase_text) + increase.setProperty("style", "btn-buy") + increase.setDisabled(disabled) + increase.setMinimumSize(16, 16) + increase.setMaximumSize(16, 16) + increase.clicked.connect(lambda: on_increase(self)) + increase.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) + layout.addWidget(increase) + + def set_quantity(self, quantity: int) -> None: + self.quantity = quantity + self.count_label.setText(f"{self.quantity}") + + +class ScrollingUnitTransferGrid(QFrame): + def __init__(self, cp: ControlPoint, game_model: GameModel) -> None: + super().__init__() + self.cp = cp + self.game_model = game_model + self.transfers: Dict[Type[UnitType, int]] = defaultdict(int) + + main_layout = QVBoxLayout() + + scroll_content = QWidget() + task_box_layout = QGridLayout() + + unit_types = set( + db.find_unittype(PinpointStrike, self.game_model.game.player_name) + ) + sorted_units = sorted( + {u for u in unit_types if self.cp.base.total_units_of_type(u)}, + key=lambda u: db.unit_get_expanded_info( + self.game_model.game.player_country, u, "name" + ), + ) + for row, unit_type in enumerate(sorted_units): + self.add_unit_row(unit_type, task_box_layout, row) + stretch = QVBoxLayout() + stretch.addStretch() + task_box_layout.addLayout(stretch, task_box_layout.count(), 0) + + scroll_content.setLayout(task_box_layout) + scroll = QScrollArea() + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + scroll.setWidgetResizable(True) + scroll.setWidget(scroll_content) + main_layout.addWidget(scroll) + self.setLayout(main_layout) + + def add_unit_row( + self, + unit_type: Type[UnitType], + layout: QGridLayout, + row: int, + ) -> None: + exist = QGroupBox() + exist.setProperty("style", "buy-box") + exist.setMaximumHeight(36) + exist.setMinimumHeight(36) + origin_inventory_layout = QHBoxLayout() + exist.setLayout(origin_inventory_layout) + + origin_inventory = self.cp.base.total_units_of_type(unit_type) + + unit_name = QLabel( + "" + + db.unit_get_expanded_info( + self.game_model.game.player_country, unit_type, "name" + ) + + "" + ) + unit_name.setSizePolicy( + QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + ) + + origin_inventory_label = QLabel(str(origin_inventory)) + origin_inventory_label.setSizePolicy( + QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + ) + + def increase(controls: TransferControls): + nonlocal origin_inventory + nonlocal origin_inventory_label + if not origin_inventory: + return + + self.transfers[unit_type] += 1 + origin_inventory -= 1 + controls.set_quantity(self.transfers[unit_type]) + origin_inventory_label.setText(str(origin_inventory)) + + def decrease(controls: TransferControls): + nonlocal origin_inventory + nonlocal origin_inventory_label + if not controls.quantity: + return + + self.transfers[unit_type] -= 1 + origin_inventory += 1 + controls.set_quantity(self.transfers[unit_type]) + origin_inventory_label.setText(str(origin_inventory)) + + transfer_controls = TransferControls("->", increase, "<-", decrease) + + origin_inventory_layout.addWidget(unit_name) + origin_inventory_layout.addItem( + QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum) + ) + origin_inventory_layout.addWidget(origin_inventory_label) + origin_inventory_layout.addItem( + QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Minimum) + ) + + layout.addWidget(exist, row, 1) + layout.addWidget(transfer_controls, row, 2) + + +class NewUnitTransferDialog(QDialog): + def __init__( + self, + game_model: GameModel, + origin: ControlPoint, + parent=None, + ) -> None: + super().__init__(parent) + self.origin = origin + self.setWindowTitle(f"New unit transfer from {origin.name}") + + self.game_model = game_model + + layout = QVBoxLayout() + self.setLayout(layout) + + self.dest_panel = TransferDestinationPanel("Destination:", origin, game_model) + self.dest_panel.changed.connect(self.on_destination_changed) + layout.addLayout(self.dest_panel) + + self.transfer_panel = ScrollingUnitTransferGrid(origin, game_model) + layout.addWidget(self.transfer_panel) + + self.submit_button = QPushButton("Create Transfer Order", parent=self) + self.submit_button.clicked.connect(self.on_submit) + self.submit_button.setProperty("style", "start-button") + layout.addWidget(self.submit_button) + + def on_destination_changed(self, index: int) -> None: + # Rebuild the transfer panel to reset everything. It's easier to recreate the + # panel itself than to clear the grid layout in the panel. + self.layout().removeWidget(self.transfer_panel) + self.layout().removeWidget(self.submit_button) + self.transfer_panel = ScrollingUnitTransferGrid(self.origin, self.game_model) + self.layout().addWidget(self.transfer_panel) + self.layout().addWidget(self.submit_button) + + def on_submit(self) -> None: + transfers = {} + for unit_type, count in self.transfer_panel.transfers.items(): + if not count: + continue + + logging.info( + f"Transferring {count} {unit_type.id} from " + f"{self.transfer_panel.cp.name} to {self.dest_panel.current.name}" + ) + transfers[unit_type] = count + + self.game_model.transfer_model.new_transfer( + RoadTransferOrder( + player=True, + origin=self.transfer_panel.cp, + destination=self.dest_panel.current, + units=transfers, + ) + ) + self.close() diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index a4793b97..ec6f03dc 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -19,6 +19,7 @@ from qt_ui.uiconstants import EVENT_ICONS from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.basemenu.QBaseMenuTabs import QBaseMenuTabs from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour +from qt_ui.windows.basemenu.NewUnitTransferDialog import NewUnitTransferDialog class QBaseMenu2(QDialog): @@ -88,6 +89,11 @@ class QBaseMenu2(QDialog): runway_attack_button.setProperty("style", "btn-danger") runway_attack_button.clicked.connect(self.new_package) + if self.cp.captured and not self.cp.is_global: + transfer_button = QPushButton("Transfer Units") + bottom_row.addWidget(transfer_button) + transfer_button.clicked.connect(self.open_transfer_dialog) + self.budget_display = QLabel( QRecruitBehaviour.BUDGET_FORMAT.format(self.game_model.game.budget) ) @@ -180,5 +186,8 @@ class QBaseMenu2(QDialog): def new_package(self) -> None: Dialog.open_new_package_dialog(self.cp, parent=self.window()) + def open_transfer_dialog(self) -> None: + NewUnitTransferDialog(self.game_model, self.cp, parent=self.window()).show() + def update_budget(self, game: Game) -> None: self.budget_display.setText(QRecruitBehaviour.BUDGET_FORMAT.format(game.budget)) diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 97ef9b2a..1e429895 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -1,17 +1,22 @@ import logging -from typing import Type +from typing import Callable, Set, Type from PySide2.QtCore import Qt from PySide2.QtWidgets import ( + QFrame, + QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLayout, QPushButton, + QScrollArea, QSizePolicy, QSpacerItem, + QVBoxLayout, + QWidget, ) -from dcs.unittype import UnitType +from dcs.unittype import FlyingType, UnitType from game import db from game.event import UnitsDeliveryEvent @@ -27,13 +32,11 @@ class QRecruitBehaviour: existing_units_labels = None bought_amount_labels = None maximum_units = -1 - recruitable_types = [] BUDGET_FORMAT = "Available Budget: ${:.2f}M" def __init__(self) -> None: self.bought_amount_labels = {} self.existing_units_labels = {} - self.recruitable_types = [] self.update_available_budget() @property @@ -195,9 +198,3 @@ class QRecruitBehaviour: Set the maximum number of units that can be bought """ self.maximum_units = maximum_units - - def set_recruitable_types(self, recruitables_types): - """ - Set the maximum number of units that can be bought - """ - self.recruitables_types = recruitables_types diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 53f6cf69..970e1c6c 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -34,7 +34,6 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): # Determine maximum number of aircrafts that can be bought self.set_maximum_units(self.cp.total_aircraft_parking) - self.set_recruitable_types([CAP, CAS]) self.bought_amount_labels = {} self.existing_units_labels = {} From 65f6a4eddd9ffe68f6b5359127bb4396001e5c80 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 17 Apr 2021 17:27:21 -0700 Subject: [PATCH 003/438] Restrict transfers to connected bases. https://github.com/Khopa/dcs_liberation/issues/824 --- game/theater/controlpoint.py | 19 ++++++++++++++- game/theater/supplyroutes.py | 23 +++++++++++++++++++ .../windows/basemenu/NewUnitTransferDialog.py | 14 +++++------ qt_ui/windows/basemenu/QBaseMenu2.py | 8 +++++-- 4 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 game/theater/supplyroutes.py diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index f2a95200..69b9db2f 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -8,7 +8,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass, field from enum import Enum from functools import total_ordering -from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type +from typing import Any, Dict, Iterator, List, Optional, Set, TYPE_CHECKING, Type from dcs.mapping import Point from dcs.ships import ( @@ -292,6 +292,23 @@ class ControlPoint(MissionTarget, ABC): def is_global(self): return not self.connected_points + def transitive_connected_friendly_points( + self, seen: Optional[Set[ControlPoint]] = None + ) -> List[ControlPoint]: + if seen is None: + seen = {self} + + connected = [] + for cp in self.connected_points: + if cp.captured != self.captured: + continue + if cp in seen: + continue + seen.add(cp) + connected.append(cp) + connected.extend(cp.transitive_connected_friendly_points(seen)) + return connected + @property def is_carrier(self): """ diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py new file mode 100644 index 00000000..0bbaaec1 --- /dev/null +++ b/game/theater/supplyroutes.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import Iterator, List, Optional + +from game.theater.controlpoint import ControlPoint + + +class SupplyRoute: + def __init__(self, control_points: List[ControlPoint]) -> None: + self.control_points = control_points + + def __contains__(self, item: ControlPoint) -> bool: + return item in self.control_points + + def __iter__(self) -> Iterator[ControlPoint]: + yield from self.control_points + + @classmethod + def for_control_point(cls, control_point: ControlPoint) -> Optional[SupplyRoute]: + connected_friendly_points = control_point.transitive_connected_friendly_points() + if not connected_friendly_points: + return None + return SupplyRoute([control_point] + connected_friendly_points) diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index 4df54d31..a4c3f554 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -24,18 +24,18 @@ from dcs.task import PinpointStrike from dcs.unittype import UnitType from game import db -from game.theater import ControlPoint +from game.theater import ControlPoint, SupplyRoute from game.transfers import RoadTransferOrder from qt_ui.models import GameModel from qt_ui.widgets.QLabeledWidget import QLabeledWidget class TransferDestinationComboBox(QComboBox): - def __init__(self, game_model: GameModel, origin: ControlPoint) -> None: + def __init__(self, origin: ControlPoint) -> None: super().__init__() - for cp in game_model.game.theater.controlpoints: - if cp != origin and cp.captured and not cp.is_global: + for cp in SupplyRoute.for_control_point(origin): + if cp != origin and cp.captured: self.addItem(cp.name, cp) self.model().sort(0) self.setCurrentIndex(0) @@ -81,10 +81,10 @@ class UnitTransferList(QFrame): class TransferDestinationPanel(QVBoxLayout): - def __init__(self, label: str, origin: ControlPoint, game_model: GameModel) -> None: + def __init__(self, label: str, origin: ControlPoint) -> None: super().__init__() - self.source_combo_box = TransferDestinationComboBox(game_model, origin) + self.source_combo_box = TransferDestinationComboBox(origin) self.addLayout(QLabeledWidget(label, self.source_combo_box)) @property @@ -266,7 +266,7 @@ class NewUnitTransferDialog(QDialog): layout = QVBoxLayout() self.setLayout(layout) - self.dest_panel = TransferDestinationPanel("Destination:", origin, game_model) + self.dest_panel = TransferDestinationPanel("Destination:", origin) self.dest_panel.changed.connect(self.on_destination_changed) layout.addLayout(self.dest_panel) diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index ec6f03dc..8f287b6d 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -11,7 +11,7 @@ from PySide2.QtWidgets import ( ) from game import Game, db -from game.theater import ControlPoint, ControlPointType +from game.theater import ControlPoint, ControlPointType, SupplyRoute from gen.flights.flight import FlightType from qt_ui.dialogs import Dialog from qt_ui.models import GameModel @@ -89,7 +89,7 @@ class QBaseMenu2(QDialog): runway_attack_button.setProperty("style", "btn-danger") runway_attack_button.clicked.connect(self.new_package) - if self.cp.captured and not self.cp.is_global: + if self.cp.captured and self.has_transfer_destinations: transfer_button = QPushButton("Transfer Units") bottom_row.addWidget(transfer_button) transfer_button.clicked.connect(self.open_transfer_dialog) @@ -103,6 +103,10 @@ class QBaseMenu2(QDialog): GameUpdateSignal.get_instance().budgetupdated.connect(self.update_budget) self.setLayout(main_layout) + @property + def has_transfer_destinations(self) -> bool: + return SupplyRoute.for_control_point(self.cp) is not None + @property def can_repair_runway(self) -> bool: return self.cp.captured and self.cp.runway_can_be_repaired From bd9cbf5e3bbb243a928394504e439b59108cb586 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 17 Apr 2021 19:03:33 -0700 Subject: [PATCH 004/438] Move transfers one CP per turn. https://github.com/Khopa/dcs_liberation/issues/824 --- game/game.py | 2 +- game/theater/__init__.py | 1 + game/theater/supplyroutes.py | 75 ++++++++++++++++++++- game/transfers.py | 89 ++++++++++++++++++++----- qt_ui/windows/PendingTransfersDialog.py | 8 ++- 5 files changed, 154 insertions(+), 21 deletions(-) diff --git a/game/game.py b/game/game.py index 9dcf5006..5e388d38 100644 --- a/game/game.py +++ b/game/game.py @@ -275,7 +275,7 @@ class Game: for control_point in self.theater.controlpoints: control_point.process_turn(self) - self.transfers.complete_transfers() + self.transfers.perform_transfers() self.process_enemy_income() diff --git a/game/theater/__init__.py b/game/theater/__init__.py index c5b83a16..f4491283 100644 --- a/game/theater/__init__.py +++ b/game/theater/__init__.py @@ -2,4 +2,5 @@ from .base import * from .conflicttheater import * from .controlpoint import * from .missiontarget import MissionTarget +from .supplyroutes import SupplyRoute from .theatergroundobject import SamGroundObject diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py index 0bbaaec1..72cf9602 100644 --- a/game/theater/supplyroutes.py +++ b/game/theater/supplyroutes.py @@ -1,10 +1,37 @@ from __future__ import annotations -from typing import Iterator, List, Optional +import heapq +import math +from collections import defaultdict +from dataclasses import dataclass, field +from typing import Dict, Iterator, List, Optional from game.theater.controlpoint import ControlPoint +@dataclass(frozen=True, order=True) +class FrontierNode: + cost: float + point: ControlPoint = field(compare=False) + + +class Frontier: + def __init__(self) -> None: + self.nodes: List[FrontierNode] = [] + + def push(self, poly: ControlPoint, cost: float) -> None: + heapq.heappush(self.nodes, FrontierNode(cost, poly)) + + def pop(self) -> Optional[FrontierNode]: + try: + return heapq.heappop(self.nodes) + except IndexError: + return None + + def __bool__(self) -> bool: + return bool(self.nodes) + + class SupplyRoute: def __init__(self, control_points: List[ControlPoint]) -> None: self.control_points = control_points @@ -21,3 +48,49 @@ class SupplyRoute: if not connected_friendly_points: return None return SupplyRoute([control_point] + connected_friendly_points) + + def shortest_path_between( + self, origin: ControlPoint, destination: ControlPoint + ) -> List[ControlPoint]: + if origin not in self: + raise ValueError(f"{origin.name} is not in this supply route") + if destination not in self: + raise ValueError(f"{destination.name} is not in this supply route") + + frontier = Frontier() + frontier.push(origin, 0) + + came_from: Dict[ControlPoint, Optional[ControlPoint]] = {origin: None} + + best_known: Dict[ControlPoint, float] = defaultdict(lambda: math.inf) + best_known[origin] = 0.0 + + while (node := frontier.pop()) is not None: + cost = node.cost + current = node.point + if cost > best_known[current]: + continue + + for neighbor in current.connected_points: + if current.captured != neighbor.captured: + continue + + new_cost = cost + 1 + if new_cost < best_known[neighbor]: + best_known[neighbor] = new_cost + frontier.push(neighbor, new_cost) + came_from[neighbor] = current + + # Reconstruct and reverse the path. + current = destination + path: List[ControlPoint] = [] + while current != origin: + path.append(current) + previous = came_from[current] + if previous is None: + raise RuntimeError( + f"Could not reconstruct path to {destination} from {origin}" + ) + current = previous + path.reverse() + return path diff --git a/game/transfers.py b/game/transfers.py index cc2e7665..3d6fa6fe 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -1,9 +1,10 @@ import logging -from dataclasses import dataclass +from dataclasses import dataclass, field from typing import Dict, List, Type from dcs.unittype import VehicleType from game.theater import ControlPoint +from game.theater.supplyroutes import SupplyRoute @dataclass @@ -30,6 +31,19 @@ class RoadTransferOrder(TransferOrder): #: The units being transferred. units: Dict[Type[VehicleType], int] + #: The current position of the group being transferred. Groups move one control + #: point a turn through the supply line. + position: ControlPoint = field(init=False) + + def __post_init__(self) -> None: + self.position = self.origin + + def path(self) -> List[ControlPoint]: + supply_route = SupplyRoute.for_control_point(self.position) + if supply_route is None: + raise RuntimeError(f"Supply route from {self.position.name} interrupted") + return supply_route.shortest_path_between(self.position, self.destination) + class PendingTransfers: def __init__(self) -> None: @@ -50,27 +64,66 @@ class PendingTransfers: self.pending_transfers.remove(transfer) transfer.origin.base.commision_units(transfer.units) - def complete_transfers(self) -> None: + def perform_transfers(self) -> None: + incomplete = [] for transfer in self.pending_transfers: - self.complete_transfer(transfer) - self.pending_transfers.clear() + if not self.perform_transfer(transfer): + incomplete.append(transfer) + self.pending_transfers = incomplete - @staticmethod - def complete_transfer(transfer: RoadTransferOrder) -> None: - if transfer.player == transfer.destination.captured: + def perform_transfer(self, transfer: RoadTransferOrder) -> bool: + if transfer.player != transfer.destination.captured: + logging.info( + f"Transfer destination {transfer.destination.name} was captured." + ) + self.handle_route_interrupted(transfer) + return True + + supply_route = SupplyRoute.for_control_point(transfer.destination) + if supply_route is None or transfer.position not in supply_route: + logging.info( + f"Route from {transfer.position.name} to {transfer.destination.name} " + "was cut off." + ) + self.handle_route_interrupted(transfer) + return True + + path = transfer.path() + next_hop = path[0] + if next_hop == transfer.destination: logging.info( f"Units transferred from {transfer.origin.name} to " f"{transfer.destination.name}" ) transfer.destination.base.commision_units(transfer.units) - elif transfer.player == transfer.origin.captured: - logging.info( - f"{transfer.destination.name} was captured. Transferring units are " - f"returning to {transfer.origin.name}" - ) - transfer.origin.base.commision_units(transfer.units) - else: - logging.info( - f"Both {transfer.origin.name} and {transfer.destination.name} were " - "captured. Units were surrounded and captured during transfer." - ) + return True + + logging.info( + f"Units transferring from {transfer.origin.name} to " + f"{transfer.destination.name} arrived at {next_hop.name}. {len(path) - 1} " + "turns remaining." + ) + transfer.position = next_hop + return False + + @staticmethod + def handle_route_interrupted(transfer: RoadTransferOrder): + # Halt the transfer in place if safe. + if transfer.player == transfer.position.captured: + logging.info(f"Transferring units are halting at {transfer.position.name}.") + transfer.position.base.commision_units(transfer.units) + return + + # If the current position was captured attempt to divert to a neighboring + # friendly CP. + for connected in transfer.position.connected_points: + if connected.captured == transfer.player: + logging.info(f"Transferring units are re-routing to {connected.name}.") + connected.base.commision_units(transfer.units) + return + + # If the units are cutoff they are destroyed. + logging.info( + f"Both {transfer.position.name} and {transfer.destination.name} were " + "captured. Units were surrounded and destroyed during transfer." + ) diff --git a/qt_ui/windows/PendingTransfersDialog.py b/qt_ui/windows/PendingTransfersDialog.py index 6892debf..447f6437 100644 --- a/qt_ui/windows/PendingTransfersDialog.py +++ b/qt_ui/windows/PendingTransfersDialog.py @@ -22,6 +22,7 @@ from PySide2.QtWidgets import ( QVBoxLayout, ) +from game.theater.supplyroutes import SupplyRoute from game.transfers import RoadTransferOrder from qt_ui.delegate_helpers import painter_context from qt_ui.models import GameModel, TransferModel @@ -50,7 +51,12 @@ class TransferDelegate(QStyledItemDelegate): def second_row_text(self, index: QModelIndex) -> str: transfer = self.transfer(index) - return f"Currently at {transfer.origin}. Arrives at destination in 1 turn." + path = transfer.path() + if len(path) == 1: + turns = "1 turn" + else: + turns = f"{len(path)} turns" + return f"Currently at {transfer.position}. Arrives at destination in {turns}." def paint( self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex From 5dd7ea306053b3e0b08a27e40640def5e020fe82 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 17 Apr 2021 20:04:48 -0700 Subject: [PATCH 005/438] Spawn convoys for transfers. Destroying these units currently has no effect. https://github.com/Khopa/dcs_liberation/issues/824 --- game/operation/operation.py | 7 +++ game/transfers.py | 5 +- game/unitmap.py | 27 +++++++++++ gen/convoys.py | 96 +++++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 gen/convoys.py diff --git a/game/operation/operation.py b/game/operation/operation.py index 271a99aa..5bf7c4e7 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -22,6 +22,7 @@ from gen.airsupportgen import AirSupport, AirSupportConflictGenerator from gen.armor import GroundConflictGenerator, JtacInfo from gen.beacons import load_beacons_for_terrain from gen.briefinggen import BriefingGenerator, MissionInfoGenerator +from gen.convoys import ConvoyGenerator from gen.environmentgen import EnvironmentGenerator from gen.forcedoptionsgen import ForcedOptionsGenerator from gen.groundobjectsgen import GroundObjectsGenerator @@ -314,6 +315,7 @@ class Operation: cls.airgen.flights, cls.airsupportgen.air_support ) cls._generate_ground_conflicts() + cls._generate_convoys() # Triggers triggersgen = TriggersGenerator(cls.current_mission, cls.game) @@ -428,6 +430,11 @@ class Operation: ground_conflict_gen.generate() cls.jtacs.extend(ground_conflict_gen.jtacs) + @classmethod + def _generate_convoys(cls) -> None: + """Generates convoys for unit transfers by road.""" + ConvoyGenerator(cls.current_mission, cls.game, cls.unit_map).generate() + @classmethod def reset_naming_ids(cls): namegen.reset_numbers() diff --git a/game/transfers.py b/game/transfers.py index 3d6fa6fe..9372df65 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -1,6 +1,6 @@ import logging from dataclasses import dataclass, field -from typing import Dict, List, Type +from typing import Dict, Iterator, List, Type from dcs.unittype import VehicleType from game.theater import ControlPoint @@ -49,6 +49,9 @@ class PendingTransfers: def __init__(self) -> None: self.pending_transfers: List[RoadTransferOrder] = [] + def __iter__(self) -> Iterator[RoadTransferOrder]: + yield from self.pending_transfers + @property def pending_transfer_count(self) -> int: return len(self.pending_transfers) diff --git a/game/unitmap.py b/game/unitmap.py index 149cba40..119eae50 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -9,6 +9,7 @@ from dcs.unittype import VehicleType from game import db from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import BuildingGroundObject +from game.transfers import RoadTransferOrder from gen.flights.flight import Flight @@ -25,6 +26,12 @@ class GroundObjectUnit: unit: Unit +@dataclass(frozen=True) +class ConvoyUnit: + unit_type: Type[VehicleType] + transfer: RoadTransferOrder + + @dataclass(frozen=True) class Building: ground_object: BuildingGroundObject @@ -37,6 +44,7 @@ class UnitMap: self.front_line_units: Dict[str, FrontLineUnit] = {} self.ground_object_units: Dict[str, GroundObjectUnit] = {} self.buildings: Dict[str, Building] = {} + self.convoys: Dict[str, ConvoyUnit] = {} def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: for unit in group.units: @@ -113,6 +121,25 @@ class UnitMap: def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]: return self.ground_object_units.get(name, None) + def add_convoy_units(self, group: Group, transfer: RoadTransferOrder) -> None: + for unit in group.units: + # The actual name is a String (the pydcs translatable string), which + # doesn't define __eq__. + name = str(unit.name) + if name in self.convoys: + raise RuntimeError(f"Duplicate convoy unit: {name}") + unit_type = db.unit_type_from_name(unit.type) + if unit_type is None: + raise RuntimeError(f"Unknown unit type: {unit.type}") + if not issubclass(unit_type, VehicleType): + raise RuntimeError( + f"{name} is a {unit_type.__name__}, expected a VehicleType" + ) + self.convoys[name] = ConvoyUnit(unit_type, transfer) + + def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: + return self.convoys.get(name, None) + def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None: # The actual name is a String (the pydcs translatable string), which # doesn't define __eq__. diff --git a/gen/convoys.py b/gen/convoys.py new file mode 100644 index 00000000..ff9f9ccc --- /dev/null +++ b/gen/convoys.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import itertools +from typing import Dict, TYPE_CHECKING, Type + +from dcs import Mission +from dcs.mapping import Point +from dcs.point import PointAction +from dcs.unit import Vehicle +from dcs.unitgroup import VehicleGroup +from dcs.unittype import VehicleType + +from game.transfers import RoadTransferOrder +from game.unitmap import UnitMap + +if TYPE_CHECKING: + from game import Game + + +class ConvoyGenerator: + def __init__(self, mission: Mission, game: Game, unit_map: UnitMap) -> None: + self.mission = mission + self.game = game + self.unit_map = unit_map + self.count = itertools.count() + + def generate(self) -> None: + # Reset the count to make generation deterministic. + self.count = itertools.count() + for transfer in self.game.transfers: + self.generate_convoy_for(transfer) + + def generate_convoy_for(self, transfer: RoadTransferOrder) -> None: + # TODO: Add convoy spawn points to campaign so these can start on/near a road. + # Groups that start with an on-road waypoint that are not on a road will move to + # the road one at a time. Spawning them arbitrarily at the control point spawns + # them on the runway (or in a FOB structure) and they'll take forever to get to + # a road. + origin = transfer.position.position + next_hop = transfer.path()[0] + destination = next_hop.position + + group = self._create_mixed_unit_group( + f"Convoy {next(self.count)}", + origin, + transfer.units, + transfer.player, + ) + group.add_waypoint(destination, move_formation=PointAction.OnRoad) + self.make_drivable(group) + self.unit_map.add_convoy_units(group, transfer) + + def _create_mixed_unit_group( + self, + name: str, + position: Point, + units: Dict[Type[VehicleType], int], + for_player: bool, + ) -> VehicleGroup: + country = self.mission.country( + self.game.player_country if for_player else self.game.enemy_country + ) + + unit_types = list(units.items()) + main_unit_type, main_unit_count = unit_types[0] + + group = self.mission.vehicle_group( + country, + name, + main_unit_type, + position=position, + group_size=main_unit_count, + move_formation=PointAction.OnRoad, + ) + + unit_name_counter = itertools.count(main_unit_count + 1) + # pydcs spreads units out by 20 in the Y axis by default. Pick up where it left + # off. + y = itertools.count(position.y + main_unit_count * 20, 20) + for unit_type, count in unit_types[1:]: + for i in range(count): + v = self.mission.vehicle( + f"{name} Unit #{next(unit_name_counter)}", unit_type + ) + v.position.x = position.x + v.position.y = next(y) + v.heading = 0 + group.add_unit(v) + + return group + + @staticmethod + def make_drivable(group: VehicleGroup) -> None: + for v in group.units: + if isinstance(v, Vehicle): + v.player_can_drive = True From 65ed110ab731453d080b13a44b208e522d83fda8 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 17 Apr 2021 21:52:06 -0700 Subject: [PATCH 006/438] Track convoy kills. https://github.com/Khopa/dcs_liberation/issues/824 --- game/debriefing.py | 28 ++++++++++++++++++- game/event/event.py | 18 ++++++++++++ qt_ui/windows/QDebriefingWindow.py | 22 +++++++++++++++ .../windows/QWaitingForMissionResultWindow.py | 15 ++++++---- 4 files changed, 76 insertions(+), 7 deletions(-) diff --git a/game/debriefing.py b/game/debriefing.py index c0221838..ea5b8758 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -22,7 +22,7 @@ from dcs.unittype import FlyingType, UnitType from game import db from game.theater import Airfield, ControlPoint -from game.unitmap import Building, FrontLineUnit, GroundObjectUnit, UnitMap +from game.unitmap import Building, ConvoyUnit, FrontLineUnit, GroundObjectUnit, UnitMap from gen.flights.flight import Flight if TYPE_CHECKING: @@ -60,6 +60,9 @@ class GroundLosses: player_front_line: List[FrontLineUnit] = field(default_factory=list) enemy_front_line: List[FrontLineUnit] = field(default_factory=list) + player_convoy: List[ConvoyUnit] = field(default_factory=list) + enemy_convoy: List[ConvoyUnit] = field(default_factory=list) + player_ground_objects: List[GroundObjectUnit] = field(default_factory=list) enemy_ground_objects: List[GroundObjectUnit] = field(default_factory=list) @@ -120,6 +123,11 @@ class Debriefing: yield from self.ground_losses.player_front_line yield from self.ground_losses.enemy_front_line + @property + def convoy_losses(self) -> Iterator[ConvoyUnit]: + yield from self.ground_losses.player_convoy + yield from self.ground_losses.enemy_convoy + @property def ground_object_losses(self) -> Iterator[GroundObjectUnit]: yield from self.ground_losses.player_ground_objects @@ -148,6 +156,16 @@ class Debriefing: losses_by_type[loss.unit_type] += 1 return losses_by_type + def convoy_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]: + losses_by_type: Dict[Type[UnitType], int] = defaultdict(int) + if player: + losses = self.ground_losses.player_convoy + else: + losses = self.ground_losses.enemy_convoy + for loss in losses: + losses_by_type[loss.unit_type] += 1 + return losses_by_type + def building_losses_by_type(self, player: bool) -> Dict[str, int]: losses_by_type: Dict[str, int] = defaultdict(int) if player: @@ -186,6 +204,14 @@ class Debriefing: losses.enemy_front_line.append(front_line_unit) continue + convoy_unit = self.unit_map.convoy_unit(unit_name) + if convoy_unit is not None: + if convoy_unit.transfer.player: + losses.player_convoy.append(convoy_unit) + else: + losses.enemy_convoy.append(convoy_unit) + continue + ground_object_unit = self.unit_map.ground_object_unit(unit_name) if ground_object_unit is not None: if ground_object_unit.ground_object.control_point.captured: diff --git a/game/event/event.py b/game/event/event.py index ea7f0e17..157b7461 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -154,6 +154,23 @@ class Event: logging.info(f"{unit_type} destroyed from {control_point}") control_point.base.armor[unit_type] -= 1 + @staticmethod + def commit_convoy_losses(debriefing: Debriefing) -> None: + for loss in debriefing.convoy_losses: + unit_type = loss.unit_type + transfer = loss.transfer + available = loss.transfer.units.get(unit_type, 0) + convoy_name = f"convoy from {transfer.position} to {transfer.destination}" + if available <= 0: + logging.error( + f"Found killed {unit_type} in {convoy_name} but that convoy has " + "none available." + ) + continue + + logging.info(f"{unit_type} destroyed in {convoy_name}") + transfer.units[unit_type] -= 1 + @staticmethod def commit_ground_object_losses(debriefing: Debriefing) -> None: for loss in debriefing.ground_object_losses: @@ -186,6 +203,7 @@ class Event: self.commit_air_losses(debriefing) self.commit_front_line_losses(debriefing) + self.commit_convoy_losses(debriefing) self.commit_ground_object_losses(debriefing) self.commit_building_losses(debriefing) self.commit_damaged_runways(debriefing) diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py index d4fa312b..4b62c397 100644 --- a/qt_ui/windows/QDebriefingWindow.py +++ b/qt_ui/windows/QDebriefingWindow.py @@ -72,6 +72,17 @@ class QDebriefingWindow(QDialog): except AttributeError: logging.exception(f"Issue adding {unit_type} to debriefing information") + convoy_losses = self.debriefing.convoy_losses_by_type(player=True) + for unit_type, count in convoy_losses.items(): + try: + lostUnitsLayout.addWidget( + QLabel(f"{db.unit_type_name(unit_type)} from convoy"), row, 0 + ) + lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) + row += 1 + except AttributeError: + logging.exception(f"Issue adding {unit_type} to debriefing information") + building_losses = self.debriefing.building_losses_by_type(player=True) for building, count in building_losses.items(): try: @@ -113,6 +124,17 @@ class QDebriefingWindow(QDialog): enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) row += 1 + convoy_losses = self.debriefing.convoy_losses_by_type(player=False) + for unit_type, count in convoy_losses.items(): + try: + lostUnitsLayout.addWidget( + QLabel(f"{db.unit_type_name(unit_type)} from convoy"), row, 0 + ) + lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) + row += 1 + except AttributeError: + logging.exception(f"Issue adding {unit_type} to debriefing information") + building_losses = self.debriefing.building_losses_by_type(player=False) for building, count in building_losses.items(): try: diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index 2efa730b..3b06e877 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -148,16 +148,19 @@ class QWaitingForMissionResultWindow(QDialog): QLabel(str(len(list(debriefing.front_line_losses)))), 1, 1 ) - updateLayout.addWidget(QLabel("Other ground units destroyed"), 2, 0) + updateLayout.addWidget(QLabel("Convoy units destroyed"), 2, 0) + updateLayout.addWidget(QLabel(str(len(list(debriefing.convoy_losses)))), 2, 1) + + updateLayout.addWidget(QLabel("Other ground units destroyed"), 3, 0) updateLayout.addWidget( - QLabel(str(len(list(debriefing.ground_object_losses)))), 2, 1 + QLabel(str(len(list(debriefing.ground_object_losses)))), 3, 1 ) - updateLayout.addWidget(QLabel("Buildings destroyed"), 3, 0) - updateLayout.addWidget(QLabel(str(len(list(debriefing.building_losses)))), 3, 1) + updateLayout.addWidget(QLabel("Buildings destroyed"), 4, 0) + updateLayout.addWidget(QLabel(str(len(list(debriefing.building_losses)))), 4, 1) - updateLayout.addWidget(QLabel("Base Capture Events"), 4, 0) - updateLayout.addWidget(QLabel(str(len(debriefing.base_capture_events))), 4, 1) + updateLayout.addWidget(QLabel("Base Capture Events"), 5, 0) + updateLayout.addWidget(QLabel(str(len(debriefing.base_capture_events))), 5, 1) # Clear previous content of the window for i in reversed(range(self.gridLayout.count())): From 3b72c13f9d2a6f57a7657b52f79f02ea53e6d150 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 16:18:47 -0700 Subject: [PATCH 007/438] Add ground unit transfers to the changelog. Also documented the behavior on the wiki (link in the changelog). This is currently fully functional for players, but since units can be bought and sold at any base there's no real reason to use these yet. Will follow up with making ground units only purchasable at bases with factories (the UI will still allow the purchase directly at the base, but it will automatically create the transfer order) so convoys end up being used, and to make factories a more interesting strategic target. https://github.com/Khopa/dcs_liberation/issues/824 --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 11770bcb..fb153681 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,8 @@ Saves from 2.5 are not compatible with 2.6. ## Features/Improvements +* **[Campaign]** Ground units can now be transferred by road. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. + ## Fixes # 2.5.0 From 5e054cfc774c9b325254c5c8c7ba8da48a75b1e7 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 16:32:02 -0700 Subject: [PATCH 008/438] Disallow selling ground units. Ground units should be transferred to a new location, not sold and repurchased. https://github.com/Khopa/dcs_liberation/issues/823 --- changelog.md | 1 + game/event/event.py | 7 +++++-- .../basemenu/ground_forces/QArmorRecruitmentMenu.py | 10 ++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index fb153681..33a77730 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ Saves from 2.5 are not compatible with 2.6. ## Features/Improvements * **[Campaign]** Ground units can now be transferred by road. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. +* **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. ## Fixes diff --git a/game/event/event.py b/game/event/event.py index 157b7461..116bc5d0 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -469,12 +469,15 @@ class UnitsDeliveryEvent: logging.info(f"Refunding {count} {unit_type.id} at {self.to_cp.name}") game.adjust_budget(price * count, player=self.to_cp.captured) - def available_next_turn(self, unit_type: Type[UnitType]) -> int: + def pending_orders(self, unit_type: Type[UnitType]) -> int: pending_units = self.units.get(unit_type) if pending_units is None: pending_units = 0 + return pending_units + + def available_next_turn(self, unit_type: Type[UnitType]) -> int: current_units = self.to_cp.base.total_units_of_type(unit_type) - return pending_units + current_units + return self.pending_orders(unit_type) + current_units def process(self, game: Game) -> None: bought_units: Dict[Type[UnitType], int] = {} diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index 46536204..fa7d0246 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -1,3 +1,5 @@ +from typing import Type + from PySide2.QtCore import Qt from PySide2.QtWidgets import ( QFrame, @@ -65,13 +67,13 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): main_layout.addWidget(scroll) self.setLayout(main_layout) - def sell(self, unit_type: UnitType): - if self.pending_deliveries.available_next_turn(unit_type) <= 0: + def sell(self, unit_type: Type[UnitType]) -> None: + if self.pending_deliveries.pending_orders(unit_type) <= 0: QMessageBox.critical( self, "Could not sell ground unit", - f"Attempted to sell one {unit_type.id} at {self.cp.name} " - "but none are available.", + f"Attempted to cancel order of one {unit_type.id} at {self.cp.name} " + "but no orders are pending.", QMessageBox.Ok, ) return From 39135f8c8017bf41de298c8d82365c2b6675e0be Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 17:30:49 -0700 Subject: [PATCH 009/438] Add version field to campaign descriptor file. This is used to provide a UI hint to guide players towards campaigns that have been updated to work with the current version of the game. All the campaigns we currently have were made for an unknown version of the game, so they're all flagged as incompatible. The version field is not the DCS Liberation version number because the campaign format may change multiple times during development. Instead the version number is a monotonically increasing integer that we increment whenever a game change requires campaign updates. --- changelog.md | 1 + game/theater/conflicttheater.py | 9 +++++ qt_ui/windows/newgame/QCampaignList.py | 38 +++++++++++++++++-- qt_ui/windows/newgame/QNewGameWizard.py | 2 +- resources/ui/templates/campaigntemplate_EN.j2 | 28 +++++++++++--- 5 files changed, 68 insertions(+), 10 deletions(-) diff --git a/changelog.md b/changelog.md index 33a77730..59ee06e0 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ Saves from 2.5 are not compatible with 2.6. * **[Campaign]** Ground units can now be transferred by road. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. +* **[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. ## Fixes diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 80194189..07c17aa9 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -84,6 +84,15 @@ def pairwise(iterable): class MizCampaignLoader: + #: The latest version of the campaign format. Increment this version whenever all + #: existing campaigns should be flagged as incompatible in the UI. We will still + #: attempt to load old campaigns, but this provides a warning to the user that the + #: campaign may not work correctly. + #: + #: There is no verification that the campaign author updated their campaign + #: correctly, this is just a UI hint. + VERSION = 1 + BLUE_COUNTRY = CombinedJointTaskForcesBlue() RED_COUNTRY = CombinedJointTaskForcesRed() diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index b97f2ef0..44ed7175 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -4,7 +4,7 @@ import json import logging from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, List, Union +from typing import Any, Dict, List, Optional, Union from PySide2 import QtGui from PySide2.QtCore import QItemSelectionModel @@ -12,7 +12,7 @@ from PySide2.QtGui import QStandardItem, QStandardItemModel from PySide2.QtWidgets import QAbstractItemView, QListView import qt_ui.uiconstants as CONST -from game.theater import ConflictTheater +from game.theater import ConflictTheater, MizCampaignLoader PERF_FRIENDLY = 0 PERF_MEDIUM = 1 @@ -26,6 +26,12 @@ class Campaign: icon_name: str authors: str description: str + + #: The revision of the campaign format the campaign was built for. We do not attempt + #: to migrate old campaigns, but this is used to show a warning in the UI when + #: selecting a campaign that is not up to date. + version: int + recommended_player_faction: str recommended_enemy_faction: str performance: Union[PERF_FRIENDLY, PERF_MEDIUM, PERF_HARD, PERF_NASA] @@ -43,6 +49,7 @@ class Campaign: f"Terrain_{sanitized_theater}", data.get("authors", "???"), data.get("description", ""), + data.get("version", 0), data.get("recommended_player_faction", "USA 2005"), data.get("recommended_enemy_faction", "Russia 1990"), data.get("performance", 0), @@ -53,6 +60,27 @@ class Campaign: def load_theater(self) -> ConflictTheater: return ConflictTheater.from_json(self.path.parent, self.data) + @property + def is_out_of_date(self) -> bool: + """Returns True if this campaign is not up to date with the latest format.""" + return self.version < MizCampaignLoader.VERSION + + @property + def is_from_future(self) -> bool: + """Returns True if this campaign is newer than the supported format.""" + return self.version > MizCampaignLoader.VERSION + + @property + def is_compatible(self) -> bool: + """Returns True is this campaign was built for this version of the game.""" + if not self.version: + return False + if self.is_out_of_date: + return False + if self.is_from_future: + return False + return True + def load_campaigns() -> List[Campaign]: campaign_dir = Path("resources\\campaigns") @@ -73,7 +101,11 @@ class QCampaignItem(QStandardItem): super(QCampaignItem, self).__init__() self.setIcon(QtGui.QIcon(CONST.ICONS[campaign.icon_name])) self.setEditable(False) - self.setText(campaign.name) + if campaign.is_compatible: + name = campaign.name + else: + name = f"[INCOMPATIBLE] {campaign.name}" + self.setText(name) class QCampaignList(QListView): diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index 0ff28d59..c3cac473 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -321,7 +321,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage): # Faction description self.campaignMapDescription = QTextEdit("") self.campaignMapDescription.setReadOnly(True) - self.campaignMapDescription.setMaximumHeight(100) + self.campaignMapDescription.setMaximumHeight(200) self.performanceText = QTextEdit("") self.performanceText.setReadOnly(True) diff --git a/resources/ui/templates/campaigntemplate_EN.j2 b/resources/ui/templates/campaigntemplate_EN.j2 index 2ec97f6e..6cd913c7 100644 --- a/resources/ui/templates/campaigntemplate_EN.j2 +++ b/resources/ui/templates/campaigntemplate_EN.j2 @@ -1,8 +1,24 @@ -Author(s): {{ campaign.authors }} -

+

Author(s): {{ campaign.authors }}

-Default factions: - {{campaign.recommended_player_faction}} VS  {{campaign.recommended_enemy_faction}} -
+{% if not campaign.version %} +

This campaign was created for an unknown version +of the game.

+

You can still attempt to play this campaign but there may be game breaking +{% elif campaign.is_out_of_date %} +

This campaign was created for an older version +of the game.

+

You can still attempt to play this campaign but there may be game breaking +bugs.

+{% elif campaign.is_from_future %} +

This campaign was created for a newer version +of the game.

+

You can still attempt to play this campaign but there may be game breaking +bugs.

+{% else %} +

This campaign is up to date.

+{% endif %} -{{ campaign.description|safe }} +

Default factions:

+

{{campaign.recommended_player_faction}} VS  {{campaign.recommended_enemy_faction}}

+ +{{ campaign.description|safe }} \ No newline at end of file From c92e4e06cc818c95b9cbbb8f67bf41ea63e66bab Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 17:37:55 -0700 Subject: [PATCH 010/438] Move campaign format version to a stable location. --- game/theater/conflicttheater.py | 9 --------- game/version.py | 9 +++++++++ qt_ui/windows/newgame/QCampaignList.py | 5 +++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 07c17aa9..80194189 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -84,15 +84,6 @@ def pairwise(iterable): class MizCampaignLoader: - #: The latest version of the campaign format. Increment this version whenever all - #: existing campaigns should be flagged as incompatible in the UI. We will still - #: attempt to load old campaigns, but this provides a warning to the user that the - #: campaign may not work correctly. - #: - #: There is no verification that the campaign author updated their campaign - #: correctly, this is just a UI hint. - VERSION = 1 - BLUE_COUNTRY = CombinedJointTaskForcesBlue() RED_COUNTRY = CombinedJointTaskForcesRed() diff --git a/game/version.py b/game/version.py index 37a7dcfb..9bb8c434 100644 --- a/game/version.py +++ b/game/version.py @@ -16,3 +16,12 @@ def _build_version_string() -> str: #: Current version of Liberation. VERSION = _build_version_string() + +#: The latest version of the campaign format. Increment this version whenever all +#: existing campaigns should be flagged as incompatible in the UI. We will still attempt +#: to load old campaigns, but this provides a warning to the user that the campaign may +#: not work correctly. +#: +#: There is no verification that the campaign author updated their campaign correctly +#: this is just a UI hint. +CAMPAIGN_FORMAT_VERSION = 1 diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 44ed7175..45ac295b 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -13,6 +13,7 @@ from PySide2.QtWidgets import QAbstractItemView, QListView import qt_ui.uiconstants as CONST from game.theater import ConflictTheater, MizCampaignLoader +from game.version import CAMPAIGN_FORMAT_VERSION PERF_FRIENDLY = 0 PERF_MEDIUM = 1 @@ -63,12 +64,12 @@ class Campaign: @property def is_out_of_date(self) -> bool: """Returns True if this campaign is not up to date with the latest format.""" - return self.version < MizCampaignLoader.VERSION + return self.version < CAMPAIGN_FORMAT_VERSION @property def is_from_future(self) -> bool: """Returns True if this campaign is newer than the supported format.""" - return self.version > MizCampaignLoader.VERSION + return self.version > CAMPAIGN_FORMAT_VERSION @property def is_compatible(self) -> bool: From 9a4ec5a899f859511121f0d2cd301537f57fa014 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 17:41:27 -0700 Subject: [PATCH 011/438] Fix campaign description template. --- resources/ui/templates/campaigntemplate_EN.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/ui/templates/campaigntemplate_EN.j2 b/resources/ui/templates/campaigntemplate_EN.j2 index 6cd913c7..7101e095 100644 --- a/resources/ui/templates/campaigntemplate_EN.j2 +++ b/resources/ui/templates/campaigntemplate_EN.j2 @@ -4,6 +4,7 @@

This campaign was created for an unknown version of the game.

You can still attempt to play this campaign but there may be game breaking +bugs.

{% elif campaign.is_out_of_date %}

This campaign was created for an older version of the game.

From 777cd310efa4c13b2b756ce1e96f1020a3abcadd Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 17:45:27 -0700 Subject: [PATCH 012/438] Clarify which game we're talking about. --- resources/ui/templates/campaigntemplate_EN.j2 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/ui/templates/campaigntemplate_EN.j2 b/resources/ui/templates/campaigntemplate_EN.j2 index 7101e095..c9ee31e2 100644 --- a/resources/ui/templates/campaigntemplate_EN.j2 +++ b/resources/ui/templates/campaigntemplate_EN.j2 @@ -2,17 +2,17 @@ {% if not campaign.version %}

This campaign was created for an unknown version -of the game.

+of DCS Liberation.

You can still attempt to play this campaign but there may be game breaking bugs.

{% elif campaign.is_out_of_date %}

This campaign was created for an older version -of the game.

+of DCS Liberation.

You can still attempt to play this campaign but there may be game breaking bugs.

{% elif campaign.is_from_future %}

This campaign was created for a newer version -of the game.

+of DCS Liberation.

You can still attempt to play this campaign but there may be game breaking bugs.

{% else %} From cb2ba2f53a47118d015eea5aab5cb9fbdf6e7f1e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 19:14:48 -0700 Subject: [PATCH 013/438] Update pydcs, move back to upstream. --- pydcs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydcs b/pydcs index 8c657333..22c14702 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit 8c657333af437c715c20772c9dee452cf88dcb78 +Subproject commit 22c147026553f378f70bc06234e35a9061aca96c From 707323ca120d7e1389ed44651aa0f2c629795e98 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 19:08:14 -0700 Subject: [PATCH 014/438] Update Inherent Resolve to latest camapign format. * Moves front line endpoints to roads for convoys (not used yet). * Adds EWR sites. --- resources/campaigns/inherent_resolve.json | 1 + resources/campaigns/inherent_resolve.miz | Bin 47602 -> 50819 bytes 2 files changed, 1 insertion(+) diff --git a/resources/campaigns/inherent_resolve.json b/resources/campaigns/inherent_resolve.json index 10d8d2fe..67acba0a 100644 --- a/resources/campaigns/inherent_resolve.json +++ b/resources/campaigns/inherent_resolve.json @@ -5,6 +5,7 @@ "recommended_player_faction": "USA 2005", "recommended_enemy_faction": "Insurgents (Hard)", "description": "

In this scenario, you start from Jordan, and have to fight your way through eastern Syria.

", + "version": 1, "miz": "inherent_resolve.miz", "performance": 1 } \ No newline at end of file diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index 64a7448561dd6a9e98b4ac05e6ed6c2c61635d8a..8652f8dfe1b55cc8c20bd312fb2222601e2d1379 100644 GIT binary patch literal 50819 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 literal 47602 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 E0CNt_2mk;8 From eff5b94db7ee8ed515990194d7c768ccdf85ae19 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 20:24:36 -0700 Subject: [PATCH 015/438] Add factory placement to the campaign files. This also removes the "factory" type from the normal strike target (money generating) generators to avoid confusion. Later only control points with factories will be able to spawn ground units, at which point these will no longer generate income. https://github.com/Khopa/dcs_liberation/issues/986 --- changelog.md | 1 + game/data/building_data.py | 5 +--- game/db.py | 1 + game/theater/conflicttheater.py | 14 +++++++++++ game/theater/controlpoint.py | 3 +++ game/theater/start_generator.py | 21 ++++++++++++++++ game/theater/theatergroundobject.py | 22 ++++++++++++++++ game/version.py | 15 +++++++++++ gen/groundobjectsgen.py | 29 +++++++++++++++++++--- resources/campaigns/inherent_resolve.json | 2 +- resources/campaigns/inherent_resolve.miz | Bin 50819 -> 51309 bytes 11 files changed, 104 insertions(+), 9 deletions(-) diff --git a/changelog.md b/changelog.md index 59ee06e0..71823de9 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ Saves from 2.5 are not compatible with 2.6. * **[Campaign]** Ground units can now be transferred by road. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[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. +* **[Modding]** Campaigns now choose locations for factories to spawn. ## Fixes diff --git a/game/data/building_data.py b/game/data/building_data.py index 5331ebbd..e6cd4ba9 100644 --- a/game/data/building_data.py +++ b/game/data/building_data.py @@ -10,14 +10,12 @@ DEFAULT_AVAILABLE_BUILDINGS = [ "farp", "fob", "power", - "factory", "derrick", ] -WW2_FREE = ["fuel", "factory", "ware", "fob"] +WW2_FREE = ["fuel", "ware", "fob"] WW2_GERMANY_BUILDINGS = [ "fuel", - "factory", "ww2bunker", "ww2bunker", "ww2bunker", @@ -27,7 +25,6 @@ WW2_GERMANY_BUILDINGS = [ ] WW2_ALLIES_BUILDINGS = [ "fuel", - "factory", "allycamp", "allycamp", "allycamp", diff --git a/game/db.py b/game/db.py index c091a399..d1cd9029 100644 --- a/game/db.py +++ b/game/db.py @@ -1350,6 +1350,7 @@ REWARDS = { "ammo": 2, "farp": 1, "fob": 1, + # TODO: Should generate no cash once they generate units. "factory": 10, "comms": 10, "oil": 10, diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 80194189..33a48b8c 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -120,6 +120,8 @@ class MizCampaignLoader: REQUIRED_EWR_UNIT_TYPE = AirDefence.EWR_1L13.id + FACTORY_UNIT_TYPE = Fortification.Workshop_A.id + BASE_DEFENSE_RADIUS = nautical_miles(2) def __init__(self, miz: Path, theater: ConflictTheater) -> None: @@ -261,6 +263,12 @@ class MizCampaignLoader: if group.units[0].type == self.FARP_HELIPAD: yield group + @property + def factories(self) -> Iterator[StaticGroup]: + for group in self.blue.static_group: + if group.units[0].type in self.FACTORY_UNIT_TYPE: + yield group + @cached_property def control_points(self) -> Dict[int, ControlPoint]: control_points = {} @@ -421,6 +429,12 @@ class MizCampaignLoader: PointWithHeading.from_point(group.position, group.units[0].heading) ) + for group in self.factories: + closest, distance = self.objective_info(group) + closest.preset_locations.factories.append( + PointWithHeading.from_point(group.position, group.units[0].heading) + ) + def populate_theater(self) -> None: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 69b9db2f..45477bc0 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -110,6 +110,9 @@ class PresetLocations: #: Locations of EWRs which should always be spawned. required_ewrs: List[PointWithHeading] = field(default_factory=list) + #: Locations of factories for producing ground units. These will always be spawned. + factories: List[PointWithHeading] = field(default_factory=list) + @staticmethod def _random_from(points: List[PointWithHeading]) -> Optional[PointWithHeading]: """Finds, removes, and returns a random position from the given list.""" diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 5316b9c8..9dd6e05c 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -18,6 +18,7 @@ from game.theater.theatergroundobject import ( BuildingGroundObject, CarrierGroundObject, EwrGroundObject, + FactoryGroundObject, LhaGroundObject, MissileSiteGroundObject, SamGroundObject, @@ -612,6 +613,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): """Generate ground objects and AA sites for the control point.""" skip_sams = self.generate_required_aa() skip_ewrs = self.generate_required_ewr() + self.generate_factories() if self.control_point.is_global: return @@ -717,6 +719,25 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): self.control_point.connected_objectives.append(g) + def generate_factories(self) -> None: + """Generates the factories that are required by the campaign.""" + for position in self.control_point.preset_locations.factories: + self.generate_factory_at(position) + + def generate_factory_at(self, point: PointWithHeading) -> None: + obj_name = namegen.random_objective_name() + group_id = self.game.next_group_id() + + g = FactoryGroundObject( + obj_name, + group_id, + point, + point.heading, + self.control_point, + ) + + self.control_point.connected_objectives.append(g) + def generate_aa_site(self) -> None: position = self.location_finder.location_for(LocationType.Sam) if position is None: diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index c476a90b..949e93a7 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -266,6 +266,28 @@ class BuildingGroundObject(TheaterGroundObject): self._dead = True +class FactoryGroundObject(BuildingGroundObject): + def __init__( + self, + name: str, + group_id: int, + position: Point, + heading: int, + control_point: ControlPoint, + ) -> None: + super().__init__( + name=name, + category="factory", + group_id=group_id, + object_id=0, + position=position, + heading=heading, + control_point=control_point, + dcs_identifier="Workshop A", + airbase_group=False, + ) + + class NavalGroundObject(TheaterGroundObject): def mission_types(self, for_player: bool) -> Iterator[FlightType]: from gen.flights.flight import FlightType diff --git a/game/version.py b/game/version.py index 9bb8c434..dc69650f 100644 --- a/game/version.py +++ b/game/version.py @@ -24,4 +24,19 @@ VERSION = _build_version_string() #: #: There is no verification that the campaign author updated their campaign correctly #: this is just a UI hint. +#: +#: Version history: +#: +#: Version 0 +#: * Unknown compatibility. +#: +#: Version 1 +#: * Compatible with Liberation 2.5. +#: +#: Version 2 +#: * Front line endpoints now define convoy origin/destination waypoints. They should be +#: placed on or near roads. +#: * Factories (Warehouse_A) define factory objectives. Only control points with +#: factories will be able to recruit ground units, so they should exist in sufficient +#: number and be protected by IADS. CAMPAIGN_FORMAT_VERSION = 1 diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index 589bd960..f2c5fa6f 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -14,7 +14,7 @@ from typing import Dict, Iterator, Optional, TYPE_CHECKING, Type, List from dcs import Mission, Point, unitgroup from dcs.country import Country from dcs.point import StaticPoint -from dcs.statics import fortification_map, warehouse_map, Warehouse +from dcs.statics import Fortification, fortification_map, warehouse_map, Warehouse from dcs.task import ( ActivateBeaconCommand, ActivateICLSCommand, @@ -34,6 +34,7 @@ from game.theater import ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import ( BuildingGroundObject, CarrierGroundObject, + FactoryGroundObject, GenericCarrierGroundObject, LhaGroundObject, ShipGroundObject, @@ -213,7 +214,7 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator): f"{self.ground_object.dcs_identifier} not found in static maps" ) - def generate_vehicle_group(self, unit_type: UnitType) -> None: + def generate_vehicle_group(self, unit_type: Type[UnitType]) -> None: if not self.ground_object.is_dead: group = self.m.vehicle_group( country=self.country, @@ -224,7 +225,7 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator): ) self._register_fortification(group) - def generate_static(self, static_type: StaticType) -> None: + def generate_static(self, static_type: Type[StaticType]) -> None: group = self.m.static_group( country=self.country, name=self.ground_object.group_name, @@ -244,6 +245,22 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator): self.unit_map.add_building(self.ground_object, building) +class FactoryGenerator(BuildingSiteGenerator): + """Generator for factory sites. + + Factory sites are the buildings that allow the recruitment of ground units. + Destroying these prevents the owner from recruiting ground units at the connected + control point. + """ + + def generate(self) -> None: + if self.game.position_culled(self.ground_object.position): + return + + # TODO: Faction specific? + self.generate_static(Fortification.Workshop_A) + + class GenericCarrierGenerator(GenericGroundObjectGenerator): """Base type for carrier group generation. @@ -557,7 +574,11 @@ class GroundObjectsGenerator: ).generate() for ground_object in cp.ground_objects: - if isinstance(ground_object, BuildingGroundObject): + if isinstance(ground_object, FactoryGroundObject): + generator = FactoryGenerator( + ground_object, country, self.game, self.m, self.unit_map + ) + elif isinstance(ground_object, BuildingGroundObject): generator = BuildingSiteGenerator( ground_object, country, self.game, self.m, self.unit_map ) diff --git a/resources/campaigns/inherent_resolve.json b/resources/campaigns/inherent_resolve.json index 67acba0a..a5f3f079 100644 --- a/resources/campaigns/inherent_resolve.json +++ b/resources/campaigns/inherent_resolve.json @@ -5,7 +5,7 @@ "recommended_player_faction": "USA 2005", "recommended_enemy_faction": "Insurgents (Hard)", "description": "

In this scenario, you start from Jordan, and have to fight your way through eastern Syria.

", - "version": 1, + "version": 2, "miz": "inherent_resolve.miz", "performance": 1 } \ No newline at end of file diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index 8652f8dfe1b55cc8c20bd312fb2222601e2d1379..9fe585f8fa31642ea7958c3a45cf1caf9927f5cd 100644 GIT binary patch delta 33154 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⁢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 delta 32704 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 Date: Sun, 18 Apr 2021 20:57:08 -0700 Subject: [PATCH 016/438] Add feature flag for new ground unit recruitment. This is going to render most campaigns unusable because they won't have any places to spawn ground units, so flagging off for now. https://github.com/Khopa/dcs_liberation/issues/986 --- game/settings.py | 4 ++ qt_ui/windows/newgame/QNewGameWizard.py | 20 +++++++++ qt_ui/windows/settings/QSettingsWindow.py | 54 ++++++++++++++++++----- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/game/settings.py b/game/settings.py index 0e6f968a..cbc6108c 100644 --- a/game/settings.py +++ b/game/settings.py @@ -33,6 +33,10 @@ class Settings: disable_legacy_aewc: bool = False generate_dark_kneeboard: bool = False + #: Feature flag for new ground unit behavior. Old campaigns are sufficiently broken + #: that we need to implement this conditionally for the time being. + enable_new_ground_unit_recruitment: bool = False + # Performance oriented perf_red_alert_state: bool = True perf_smoke_gen: bool = True diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index c3cac473..bfd34a21 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -18,6 +18,9 @@ from qt_ui.windows.newgame.QCampaignList import ( QCampaignList, load_campaigns, ) +from qt_ui.windows.settings.QSettingsWindow import ( + NEW_GROUND_UNIT_RECRUITMENT_BEHAVIOR_LABEL, +) jinja_env = Environment( loader=FileSystemLoader("resources/ui/templates"), @@ -84,6 +87,9 @@ class NewGameWizard(QtWidgets.QWizard): ), automate_aircraft_reinforcements=self.field("automate_aircraft_purchases"), supercarrier=self.field("supercarrier"), + enable_new_ground_unit_recruitment=self.field( + "new_ground_unit_recruitment" + ), ) generator_settings = GeneratorSettings( start_date=start_date, @@ -519,6 +525,20 @@ class DifficultyAndAutomationOptions(QtWidgets.QWizardPage): self.registerField("automate_aircraft_purchases", aircraft) assist_layout.addWidget(aircraft, 2, 1, Qt.AlignRight) + flags_group = QtWidgets.QGroupBox("Feature flags") + layout.addWidget(flags_group) + flags_layout = QtWidgets.QGridLayout() + flags_group.setLayout(flags_layout) + + new_ground_unit_recruitment_label = QtWidgets.QLabel( + NEW_GROUND_UNIT_RECRUITMENT_BEHAVIOR_LABEL + ) + new_ground_unit_recruitment_label.setOpenExternalLinks(True) + flags_layout.addWidget(new_ground_unit_recruitment_label, 0, 0) + new_ground_unit_recruitment = QtWidgets.QCheckBox() + self.registerField("new_ground_unit_recruitment", new_ground_unit_recruitment) + flags_layout.addWidget(new_ground_unit_recruitment, 0, 1, Qt.AlignRight) + self.setLayout(layout) diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index 7432c341..d328b74c 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -1,22 +1,22 @@ import logging from typing import Callable -from PySide2.QtCore import QSize, Qt, QItemSelectionModel, QPoint -from PySide2.QtGui import QStandardItemModel, QStandardItem +from PySide2.QtCore import QItemSelectionModel, QPoint, QSize, Qt +from PySide2.QtGui import QStandardItem, QStandardItemModel from PySide2.QtWidgets import ( - QLabel, + QAbstractItemView, + QCheckBox, + QComboBox, QDialog, QGridLayout, - QListView, - QStackedLayout, - QComboBox, - QWidget, - QAbstractItemView, - QPushButton, QGroupBox, - QCheckBox, - QVBoxLayout, + QLabel, + QListView, + QPushButton, QSpinBox, + QStackedLayout, + QVBoxLayout, + QWidget, ) from dcs.forcedoptions import ForcedOptions @@ -77,6 +77,17 @@ class CheatSettingsBox(QGroupBox): START_TYPE_TOOLTIP = "Selects the start type used for AI aircraft." +NEW_GROUND_UNIT_RECRUITMENT_TOOLTIP = ( + "If checked, the new ground unit recruitment behavior will be used. Ground " + "units may only be purchased at factories or off-map spawns and must " + "travel to the front line." +) +NEW_GROUND_UNIT_RECRUITMENT_BEHAVIOR_LABEL = ( + "Enable new ground unit recruitment behavior (WIP)
" + '
' + 'More information.' +) + class StartTypeComboBox(QComboBox): def __init__(self, settings: Settings) -> None: @@ -367,6 +378,27 @@ class QSettingsWindow(QDialog): general_layout.addWidget(old_awac_label, 1, 0) general_layout.addWidget(old_awac, 1, 1, Qt.AlignRight) + def set_new_ground_unit_recruitment(value: bool) -> None: + self.game.settings.enable_new_ground_unit_recruitment = value + + new_ground_unit_recruitment_behavior_label = QLabel( + NEW_GROUND_UNIT_RECRUITMENT_BEHAVIOR_LABEL + ) + new_ground_unit_recruitment_behavior_label.setToolTip( + NEW_GROUND_UNIT_RECRUITMENT_TOOLTIP + ) + new_ground_unit_recruitment_behavior_label.setOpenExternalLinks(True) + + new_ground_unit_recruitment = QCheckBox() + new_ground_unit_recruitment.setToolTip(NEW_GROUND_UNIT_RECRUITMENT_TOOLTIP) + new_ground_unit_recruitment.setChecked( + self.game.settings.enable_new_ground_unit_recruitment + ) + new_ground_unit_recruitment.toggled.connect(set_new_ground_unit_recruitment) + + general_layout.addWidget(new_ground_unit_recruitment_behavior_label, 2, 0) + general_layout.addWidget(new_ground_unit_recruitment, 2, 1, Qt.AlignRight) + automation = QGroupBox("HQ Automation") campaign_layout.addWidget(automation) From 627f18c42b75d10a304fe6bf3bb14170f37328a9 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 21:18:44 -0700 Subject: [PATCH 017/438] Require factories for purchasing ground units. https://github.com/Khopa/dcs_liberation/issues/986 --- game/theater/controlpoint.py | 11 +++++ qt_ui/windows/basemenu/QRecruitBehaviour.py | 41 +++++++++++++------ .../airfield/QAircraftRecruitmentMenu.py | 21 +++++++--- .../ground_forces/QArmorRecruitmentMenu.py | 16 +++----- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 45477bc0..245746c2 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -31,6 +31,7 @@ from game.point_with_heading import PointWithHeading from .theatergroundobject import ( BaseDefenseGroundObject, EwrGroundObject, + FactoryGroundObject, GenericCarrierGroundObject, SamGroundObject, TheaterGroundObject, @@ -312,6 +313,16 @@ class ControlPoint(MissionTarget, ABC): connected.extend(cp.transitive_connected_friendly_points(seen)) return connected + def can_recruit_ground_units(self, game: Game) -> bool: + """Returns True if this control point is capable of recruiting ground units.""" + if not game.settings.enable_new_ground_unit_recruitment: + return True + + for tgo in self.connected_objectives: + if isinstance(tgo, FactoryGroundObject) and not tgo.is_dead: + return True + return False + @property def is_carrier(self): """ diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 1e429895..67a26ca6 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -56,7 +56,6 @@ class QRecruitBehaviour: unit_type: Type[UnitType], layout: QLayout, row: int, - disabled: bool = False, ) -> int: exist = QGroupBox() exist.setProperty("style", "buy-box") @@ -100,19 +99,31 @@ class QRecruitBehaviour: buy = QPushButton("+") buy.setProperty("style", "btn-buy") - buy.setDisabled(disabled) + buy.setDisabled(not self.enable_purchase(unit_type)) buy.setMinimumSize(16, 16) buy.setMaximumSize(16, 16) - buy.clicked.connect(lambda: self.buy(unit_type)) + + def on_buy(): + self.buy(unit_type) + buy.setDisabled(not self.enable_purchase(unit_type)) + sell.setDisabled(not self.enable_sale(unit_type)) + + buy.clicked.connect(on_buy) buy.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) sell = QPushButton("-") sell.setProperty("style", "btn-sell") - sell.setDisabled(disabled) + sell.setDisabled(not self.enable_sale(unit_type)) sell.setMinimumSize(16, 16) sell.setMaximumSize(16, 16) sell.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) - sell.clicked.connect(lambda: self.sell(unit_type)) + + def on_sell(): + self.sell(unit_type) + sell.setDisabled(not self.enable_sale(unit_type)) + buy.setDisabled(not self.enable_purchase(unit_type)) + + sell.clicked.connect(on_sell) info = QGroupBox() info.setProperty("style", "buy-box") @@ -123,7 +134,6 @@ class QRecruitBehaviour: unitInfo = QPushButton("i") unitInfo.setProperty("style", "btn-info") - unitInfo.setDisabled(disabled) unitInfo.setMinimumSize(16, 16) unitInfo.setMaximumSize(16, 16) unitInfo.clicked.connect(lambda: self.info(unit_type)) @@ -169,13 +179,13 @@ class QRecruitBehaviour: GameUpdateSignal.get_instance().updateBudget(self.game_model.game) def buy(self, unit_type: Type[UnitType]): + if not self.enable_purchase(unit_type): + logging.error(f"Purchase of {unit_type.id} not allowed at {self.cp.name}") + return + price = db.PRICES[unit_type] - if self.budget >= price: - self.pending_deliveries.order({unit_type: 1}) - self.budget -= price - else: - # TODO : display modal warning - logging.info("Not enough money !") + self.pending_deliveries.order({unit_type: 1}) + self.budget -= price self._update_count_label(unit_type) self.update_available_budget() @@ -189,6 +199,13 @@ class QRecruitBehaviour: self._update_count_label(unit_type) self.update_available_budget() + def enable_purchase(self, unit_type: Type[UnitType]) -> bool: + price = db.PRICES[unit_type] + return self.budget >= price + + def enable_sale(self, unit_type: Type[UnitType]) -> bool: + return True + def info(self, unit_type): self.info_window = QUnitInfoWindow(self.game_model.game, unit_type) self.info_window.show() diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 970e1c6c..97058d83 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -77,12 +77,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): ), ) for unit_type in sorted_units: - row = self.add_purchase_row( - unit_type, - task_box_layout, - row, - disabled=not self.cp.can_operate(unit_type), - ) + row = self.add_purchase_row(unit_type, task_box_layout, row) stretch = QVBoxLayout() stretch.addStretch() task_box_layout.addLayout(stretch, row, 0) @@ -97,6 +92,20 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): main_layout.addWidget(scroll) self.setLayout(main_layout) + def enable_purchase(self, unit_type: Type[UnitType]) -> bool: + if not issubclass(unit_type, FlyingType): + return False + if not self.cp.can_operate(unit_type): + return False + return True + + def enable_sale(self, unit_type: Type[UnitType]) -> bool: + if not issubclass(unit_type, FlyingType): + return False + if not self.cp.can_operate(unit_type): + return False + return True + def buy(self, unit_type): if self.maximum_units > 0: if self.cp.unclaimed_parking(self.game_model.game) <= 0: diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index fa7d0246..08834e5c 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -67,14 +67,8 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): main_layout.addWidget(scroll) self.setLayout(main_layout) - def sell(self, unit_type: Type[UnitType]) -> None: - if self.pending_deliveries.pending_orders(unit_type) <= 0: - QMessageBox.critical( - self, - "Could not sell ground unit", - f"Attempted to cancel order of one {unit_type.id} at {self.cp.name} " - "but no orders are pending.", - QMessageBox.Ok, - ) - return - super().sell(unit_type) + def enable_purchase(self, unit_type: Type[UnitType]) -> bool: + return self.cp.can_recruit_ground_units(self.game_model.game) + + def enable_sale(self, unit_type: Type[UnitType]) -> bool: + return self.pending_deliveries.pending_orders(unit_type) > 0 From 56fc2986e952a1e774a5b71b9ac79cfdb5e183a8 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 23:11:26 -0700 Subject: [PATCH 018/438] Automate transfers from factories. The purchase system will seek out a source for its units when the purchase is completed. If no source is available the order will be refunded. Orders with no source available are prevented, so this only happens when the source was cut off from the destination during the turn. There's still some funkiness going on with the first turn (but possibly only when the first turn includes a cheat to capture) where the AI buys a ton of units somewhere other than the front line. First turn behavior should probably be different anyway, with the first turn allowing purchases anywhere to avoid empty front lines while troops reinforce if the front line isn't a factory. https://github.com/Khopa/dcs_liberation/issues/986 --- game/event/event.py | 131 +++++++++++++++++++++++++++-------- game/game.py | 8 ++- game/procurement.py | 24 ++++++- game/theater/controlpoint.py | 30 +++++++- 4 files changed, 155 insertions(+), 38 deletions(-) diff --git a/game/event/event.py b/game/event/event.py index 116bc5d0..54f7bcac 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -1,8 +1,8 @@ from __future__ import annotations import logging -import math -from typing import Dict, Iterator, List, TYPE_CHECKING, Tuple, Type +from collections import defaultdict +from typing import Dict, List, Optional, TYPE_CHECKING, Type from dcs.mapping import Point from dcs.task import Task @@ -12,10 +12,11 @@ from game import persistency from game.debriefing import AirLosses, Debriefing from game.infos.information import Information from game.operation.operation import Operation -from game.theater import ControlPoint +from game.theater import ControlPoint, SupplyRoute from gen import AirTaskingOrder from gen.ground_forces.combat_stance import CombatStance from ..db import PRICES +from ..transfers import RoadTransferOrder from ..unitmap import UnitMap if TYPE_CHECKING: @@ -439,35 +440,37 @@ class Event: class UnitsDeliveryEvent: - def __init__(self, control_point: ControlPoint) -> None: - self.to_cp = control_point - self.units: Dict[Type[UnitType], int] = {} + def __init__(self, destination: ControlPoint) -> None: + self.destination = destination + + # Maps unit type to order quantity. + self.units: Dict[Type[UnitType], int] = defaultdict(int) def __str__(self) -> str: - return "Pending delivery to {}".format(self.to_cp) + return f"Pending delivery to {self.destination}" def order(self, units: Dict[Type[UnitType], int]) -> None: for k, v in units.items(): - self.units[k] = self.units.get(k, 0) + v + self.units[k] += v def sell(self, units: Dict[Type[UnitType], int]) -> None: for k, v in units.items(): - self.units[k] = self.units.get(k, 0) - v - - def consume_each_order(self) -> Iterator[Tuple[Type[UnitType], int]]: - while self.units: - yield self.units.popitem() + self.units[k] -= v def refund_all(self, game: Game) -> None: - for unit_type, count in self.consume_each_order(): + self.refund(game, self.units) + self.units = defaultdict(int) + + def refund(self, game: Game, units: Dict[Type[UnitType], int]) -> None: + for unit_type, count in units.items(): try: price = PRICES[unit_type] except KeyError: logging.error(f"Could not refund {unit_type.id}, price unknown") continue - logging.info(f"Refunding {count} {unit_type.id} at {self.to_cp.name}") - game.adjust_budget(price * count, player=self.to_cp.captured) + logging.info(f"Refunding {count} {unit_type.id} at {self.destination.name}") + game.adjust_budget(price * count, player=self.destination.captured) def pending_orders(self, unit_type: Type[UnitType]) -> int: pending_units = self.units.get(unit_type) @@ -476,26 +479,94 @@ class UnitsDeliveryEvent: return pending_units def available_next_turn(self, unit_type: Type[UnitType]) -> int: - current_units = self.to_cp.base.total_units_of_type(unit_type) + current_units = self.destination.base.total_units_of_type(unit_type) return self.pending_orders(unit_type) + current_units def process(self, game: Game) -> None: + ground_unit_source = self.find_ground_unit_source(game) bought_units: Dict[Type[UnitType], int] = {} + units_needing_transfer: Dict[Type[VehicleType], int] = {} sold_units: Dict[Type[UnitType], int] = {} for unit_type, count in self.units.items(): - coalition = "Ally" if self.to_cp.captured else "Enemy" - aircraft = unit_type.id - name = self.to_cp.name + coalition = "Ally" if self.destination.captured else "Enemy" + name = unit_type.id + + if ( + issubclass(unit_type, VehicleType) + and self.destination != ground_unit_source + ): + source = ground_unit_source + d = units_needing_transfer + ground = True + else: + source = self.destination + d = bought_units + ground = False + if count >= 0: - bought_units[unit_type] = count - game.message( - f"{coalition} reinforcements: {aircraft} x {count} at {name}" - ) + # The destination dict will be set appropriately even if we have no + # source, and we'll refund later, buto nly emit the message when we're + # actually completing the purchase. + d[unit_type] = count + if ground or ground_unit_source is not None: + game.message( + f"{coalition} reinforcements: {name} x {count} at {source}" + ) else: sold_units[unit_type] = -count - game.message(f"{coalition} sold: {aircraft} x {-count} at {name}") - self.to_cp.base.commision_units(bought_units) - self.to_cp.base.commit_losses(sold_units) - self.units = {} - bought_units = {} - sold_units = {} + game.message(f"{coalition} sold: {name} x {-count} at {source}") + + self.units = defaultdict(int) + self.destination.base.commision_units(bought_units) + self.destination.base.commit_losses(sold_units) + + if ground_unit_source is None: + game.message( + f"{self.destination.name} lost its source for ground unit " + "reinforcements. Refunding purchase price." + ) + self.refund(game, units_needing_transfer) + return + + if units_needing_transfer: + ground_unit_source.base.commision_units(units_needing_transfer) + game.transfers.new_transfer( + RoadTransferOrder( + ground_unit_source, + self.destination, + self.destination.captured, + units_needing_transfer, + ) + ) + + def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]: + # Fast path if the destination is a valid source. + if self.destination.can_recruit_ground_units(game): + return self.destination + + supply_route = SupplyRoute.for_control_point(self.destination) + if supply_route is None: + return None + + sources = [] + for control_point in supply_route: + if control_point.can_recruit_ground_units(game): + sources.append(control_point) + + if not sources: + return None + + # Fast path to skip the distance calculation if we have only one option. + if len(sources) == 1: + return sources[0] + + closest = sources[0] + distance = len(supply_route.shortest_path_between(self.destination, closest)) + for source in sources: + new_distance = len( + supply_route.shortest_path_between(self.destination, source) + ) + if new_distance < distance: + closest = source + distance = new_distance + return closest diff --git a/game/game.py b/game/game.py index 5e388d38..bdd0a896 100644 --- a/game/game.py +++ b/game/game.py @@ -34,7 +34,7 @@ from .settings import Settings from .theater import ConflictTheater, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import MissileSiteGroundObject from .threatzones import ThreatZones -from .transfers import PendingTransfers +from .transfers import PendingTransfers, RoadTransferOrder from .unitmap import UnitMap from .weather import Conditions, TimeOfDay @@ -272,11 +272,13 @@ class Game: ) self.turn += 1 + # Must happen *before* unit deliveries are handled, or else new units will spawn + # one hop ahead. ControlPoint.process_turn handles unit deliveries. + self.transfers.perform_transfers() + for control_point in self.theater.controlpoints: control_point.process_turn(self) - self.transfers.perform_transfers() - self.process_enemy_income() self.process_player_income() diff --git a/game/procurement.py b/game/procurement.py index 0ff83155..859a4b9c 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -145,6 +145,8 @@ class ProcurementAi: if not self.faction.frontline_units and not self.faction.artillery_units: return budget + # TODO: Attempt to transfer from reserves. + while budget > 0: candidates = self.front_line_candidates() if not candidates: @@ -239,7 +241,16 @@ class ProcurementAi: # Prefer to buy front line units at active front lines that are not # already overloaded. for cp in self.owned_points: - if cp.expected_ground_units_next_turn.total >= 30: + if not cp.has_ground_unit_source(self.game): + continue + + # Buy to a higher limit when using the new recruitment mechanic since it + # will take longer to reinforce losses. + if self.game.settings.enable_new_ground_unit_recruitment: + limit = 50 + else: + limit = 30 + if self.total_ground_units_allocated_to(cp) >= limit: # Control point is already sufficiently defended. continue for connected in cp.connected_points: @@ -258,12 +269,19 @@ class ProcurementAi: # Also, do not bother buying units at bases that will never connect # to a front line. for cp in self.owned_points: - if not cp.can_deploy_ground_units: + if not cp.can_recruit_ground_units(self.game): continue - if cp.expected_ground_units_next_turn.total >= 10: + if self.total_ground_units_allocated_to(cp) >= 10: continue if cp.is_global: continue candidates.append(cp) return candidates + + def total_ground_units_allocated_to(self, control_point: ControlPoint) -> int: + total = control_point.expected_ground_units_next_turn.total + for transfer in self.game.transfers: + if transfer.destination == control_point: + total += sum(transfer.units.values()) + return total diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 245746c2..0803b393 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -313,13 +313,39 @@ class ControlPoint(MissionTarget, ABC): connected.extend(cp.transitive_connected_friendly_points(seen)) return connected + @property + def has_factory(self) -> bool: + for tgo in self.connected_objectives: + if isinstance(tgo, FactoryGroundObject) and not tgo.is_dead: + return True + return False + def can_recruit_ground_units(self, game: Game) -> bool: """Returns True if this control point is capable of recruiting ground units.""" + if not self.can_deploy_ground_units: + return False + if not game.settings.enable_new_ground_unit_recruitment: return True - for tgo in self.connected_objectives: - if isinstance(tgo, FactoryGroundObject) and not tgo.is_dead: + return self.has_factory + + def has_ground_unit_source(self, game: Game) -> bool: + """Returns True if this control point has access to ground reinforcements.""" + if not self.can_deploy_ground_units: + return False + + if not game.settings.enable_new_ground_unit_recruitment: + return True + + from game.theater.supplyroutes import SupplyRoute + + supply_route = SupplyRoute.for_control_point(self) + if supply_route is None: + return False + + for cp in supply_route: + if cp.can_recruit_ground_units(game): return True return False From df98e1f8aca5f54f5f5435ea0d2f76b4b66ec42a Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 23:25:44 -0700 Subject: [PATCH 019/438] Fix campaign format version. Updatd the comment but forgot to update the number... --- game/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/version.py b/game/version.py index dc69650f..398c0c27 100644 --- a/game/version.py +++ b/game/version.py @@ -39,4 +39,4 @@ VERSION = _build_version_string() #: * Factories (Warehouse_A) define factory objectives. Only control points with #: factories will be able to recruit ground units, so they should exist in sufficient #: number and be protected by IADS. -CAMPAIGN_FORMAT_VERSION = 1 +CAMPAIGN_FORMAT_VERSION = 2 From 3c4d6eb8e44c558cbacec7c85826283f071745a5 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 23:31:45 -0700 Subject: [PATCH 020/438] Automate transfers for purchases in the UI. Buying a unit places the order, but the unit will appear at the nearest connected source and a transfer will be automatically created next turn. https://github.com/Khopa/dcs_liberation/issues/986 --- qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index 08834e5c..e9b63882 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -68,7 +68,7 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): self.setLayout(main_layout) def enable_purchase(self, unit_type: Type[UnitType]) -> bool: - return self.cp.can_recruit_ground_units(self.game_model.game) + return self.cp.has_ground_unit_source(self.game_model.game) def enable_sale(self, unit_type: Type[UnitType]) -> bool: return self.pending_deliveries.pending_orders(unit_type) > 0 From d4679e0029ad8c9298399831b613e76741711a1d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 23:35:18 -0700 Subject: [PATCH 021/438] Note the semi-completion of the factory feature. https://github.com/Khopa/dcs_liberation/issues/986 --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 71823de9..d9e6bbe9 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ Saves from 2.5 are not compatible with 2.6. * **[Campaign]** Ground units can now be transferred by road. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. +* **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory and a transfer will be created on the next turn. This feature is off by default. * **[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. * **[Modding]** Campaigns now choose locations for factories to spawn. From d11c9a4615cda8e414a41888de5741252eb22ea9 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Apr 2021 23:57:13 -0700 Subject: [PATCH 022/438] Use convoy spawn points defined by the campaign. The start/end points of the waypoints that define the course of the front line also define the spawn points for convoys. Use them. https://github.com/Khopa/dcs_liberation/issues/824 --- game/theater/conflicttheater.py | 11 +++++++++-- game/theater/controlpoint.py | 4 +++- gen/convoys.py | 9 ++------- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 33a48b8c..1da19a05 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -335,13 +335,20 @@ class MizCampaignLoader: f"No control point near the final waypoint of {group.name}" ) + convoy_origin = waypoints[0] + convoy_destination = waypoints[-1] + # Snap the begin and end points to the control points. waypoints[0] = origin.position waypoints[-1] = destination.position front_line_id = f"{origin.id}|{destination.id}" front_lines[front_line_id] = ComplexFrontLine(origin, waypoints) - self.control_points[origin.id].connect(self.control_points[destination.id]) - self.control_points[destination.id].connect(self.control_points[origin.id]) + self.control_points[origin.id].connect( + self.control_points[destination.id], convoy_origin + ) + self.control_points[destination.id].connect( + self.control_points[origin.id], convoy_destination + ) return front_lines def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 0803b393..2be2feb2 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -267,6 +267,7 @@ class ControlPoint(MissionTarget, ABC): # TODO: Should be Airbase specific. self.has_frontline = has_frontline self.connected_points: List[ControlPoint] = [] + self.convoy_spawns: Dict[ControlPoint, Point] = {} self.base: Base = Base() self.cptype = cptype # TODO: Should be Airbase specific. @@ -392,8 +393,9 @@ class ControlPoint(MissionTarget, ABC): ... # TODO: Should be Airbase specific. - def connect(self, to: ControlPoint) -> None: + def connect(self, to: ControlPoint, convoy_location: Point) -> None: self.connected_points.append(to) + self.convoy_spawns[to] = convoy_location self.stances[to.id] = CombatStance.DEFENSIVE @abstractmethod diff --git a/gen/convoys.py b/gen/convoys.py index ff9f9ccc..520902ea 100644 --- a/gen/convoys.py +++ b/gen/convoys.py @@ -31,14 +31,9 @@ class ConvoyGenerator: self.generate_convoy_for(transfer) def generate_convoy_for(self, transfer: RoadTransferOrder) -> None: - # TODO: Add convoy spawn points to campaign so these can start on/near a road. - # Groups that start with an on-road waypoint that are not on a road will move to - # the road one at a time. Spawning them arbitrarily at the control point spawns - # them on the runway (or in a FOB structure) and they'll take forever to get to - # a road. - origin = transfer.position.position next_hop = transfer.path()[0] - destination = next_hop.position + origin = transfer.position.convoy_spawns[next_hop] + destination = next_hop.convoy_spawns[transfer.position] group = self._create_mixed_unit_group( f"Convoy {next(self.count)}", From 30bf4542f0f3a8d37398d45e6d7ac6e7ce704b77 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 00:03:47 -0700 Subject: [PATCH 023/438] Special case turn 0 for recruitment. We want there to be units on the front line on turn 1 regardless of factory locations, so bypass the recruitment restrictions on turn 0. https://github.com/Khopa/dcs_liberation/issues/986 --- game/event/event.py | 6 ++++++ game/theater/controlpoint.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/game/event/event.py b/game/event/event.py index 54f7bcac..0d7daa8e 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -540,6 +540,12 @@ class UnitsDeliveryEvent: ) def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]: + # This is running *after* the turn counter has been incremented, so this is the + # reaction to turn 0. On turn zero we allow units to be recruited anywhere for + # delivery on turn 1 so that turn 1 always starts with units on the front line. + if game.turn == 1: + return self.destination + # Fast path if the destination is a valid source. if self.destination.can_recruit_ground_units(game): return self.destination diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 2be2feb2..9ce59a19 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -329,6 +329,11 @@ class ControlPoint(MissionTarget, ABC): if not game.settings.enable_new_ground_unit_recruitment: return True + if game.turn == 0: + # Allow units to be recruited anywhere on turn 0 to avoid long delays to get + # everyone to the front line. + return True + return self.has_factory def has_ground_unit_source(self, game: Game) -> bool: From a3ff58c42d7b3269def8cd4db3acd1e62230b7cb Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 00:04:46 -0700 Subject: [PATCH 024/438] Turn feature flags on for development mode. --- qt_ui/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qt_ui/main.py b/qt_ui/main.py index 95b07f3d..08967914 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -179,6 +179,7 @@ def create_game( automate_runway_repair=auto_procurement, automate_front_line_reinforcements=auto_procurement, automate_aircraft_reinforcements=auto_procurement, + enable_new_ground_unit_recruitment=True, ), GeneratorSettings( start_date=datetime.today(), From bb3e83548c05582c4fd22ba49c1899f605f48610 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 01:54:16 -0700 Subject: [PATCH 025/438] Remove support for old-style campaigns. These won't work any more since they won't be able to define factories or supply routes. They were made obsolete ages ago, so just remove them. --- game/theater/conflicttheater.py | 34 ++++++--------------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 1da19a05..70887541 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -494,14 +494,7 @@ class ConflictTheater: logging.warning("Replacing existing frontline data") self._frontline_data = data - def add_controlpoint( - self, point: ControlPoint, connected_to: Optional[List[ControlPoint]] = None - ): - if connected_to is None: - connected_to = [] - for connected_point in connected_to: - point.connect(to=connected_point) - + def add_controlpoint(self, point: ControlPoint): self.controlpoints.append(point) def find_ground_objects_by_obj_name(self, obj_name): @@ -707,26 +700,11 @@ class ConflictTheater: t = theater() miz = data.get("miz", None) - if miz is not None: - MizCampaignLoader(directory / miz, t).populate_theater() - return t - - cps = {} - for p in data["player_points"]: - cp = t.add_json_cp(theater, p) - cp.captured = True - cps[p["id"]] = cp - t.add_controlpoint(cp) - - for p in data["enemy_points"]: - cp = t.add_json_cp(theater, p) - cps[p["id"]] = cp - t.add_controlpoint(cp) - - for l in data["links"]: - cps[l[0]].connect(cps[l[1]]) - cps[l[1]].connect(cps[l[0]]) - + if miz is None: + raise RuntimeError( + "Old format (non-miz) campaigns are no longer supported." + ) + MizCampaignLoader(directory / miz, t).populate_theater() return t From 81d5cddac9003abc18e89c69e196172d975597f6 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 17:12:33 -0700 Subject: [PATCH 026/438] Remove weird single-CP supply route edge case. A CP with a factory would be able to supply itself, but was not in a supply route if it was the only connected friendly CP. When the player starts with only one base against an enemy base this meant that it was in no supply route, causing it to not be a recruitment location or a place to buy more than a reserve of vehicles automatically. --- game/event/event.py | 2 -- game/theater/controlpoint.py | 6 +----- game/theater/supplyroutes.py | 7 +++++-- game/transfers.py | 4 +--- qt_ui/windows/basemenu/QBaseMenu2.py | 2 +- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/game/event/event.py b/game/event/event.py index 0d7daa8e..5f5917d3 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -551,8 +551,6 @@ class UnitsDeliveryEvent: return self.destination supply_route = SupplyRoute.for_control_point(self.destination) - if supply_route is None: - return None sources = [] for control_point in supply_route: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 9ce59a19..9b9558f4 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -346,11 +346,7 @@ class ControlPoint(MissionTarget, ABC): from game.theater.supplyroutes import SupplyRoute - supply_route = SupplyRoute.for_control_point(self) - if supply_route is None: - return False - - for cp in supply_route: + for cp in SupplyRoute.for_control_point(self): if cp.can_recruit_ground_units(game): return True return False diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py index 72cf9602..eb02663d 100644 --- a/game/theater/supplyroutes.py +++ b/game/theater/supplyroutes.py @@ -42,11 +42,14 @@ class SupplyRoute: def __iter__(self) -> Iterator[ControlPoint]: yield from self.control_points + def __len__(self) -> int: + return len(self.control_points) + @classmethod - def for_control_point(cls, control_point: ControlPoint) -> Optional[SupplyRoute]: + def for_control_point(cls, control_point: ControlPoint) -> SupplyRoute: connected_friendly_points = control_point.transitive_connected_friendly_points() if not connected_friendly_points: - return None + return SupplyRoute([control_point]) return SupplyRoute([control_point] + connected_friendly_points) def shortest_path_between( diff --git a/game/transfers.py b/game/transfers.py index 9372df65..165b8030 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -40,8 +40,6 @@ class RoadTransferOrder(TransferOrder): def path(self) -> List[ControlPoint]: supply_route = SupplyRoute.for_control_point(self.position) - if supply_route is None: - raise RuntimeError(f"Supply route from {self.position.name} interrupted") return supply_route.shortest_path_between(self.position, self.destination) @@ -83,7 +81,7 @@ class PendingTransfers: return True supply_route = SupplyRoute.for_control_point(transfer.destination) - if supply_route is None or transfer.position not in supply_route: + if transfer.position not in supply_route: logging.info( f"Route from {transfer.position.name} to {transfer.destination.name} " "was cut off." diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index 8f287b6d..1dac5f43 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -105,7 +105,7 @@ class QBaseMenu2(QDialog): @property def has_transfer_destinations(self) -> bool: - return SupplyRoute.for_control_point(self.cp) is not None + return len(SupplyRoute.for_control_point(self.cp)) > 1 @property def can_repair_runway(self) -> bool: From 480039ca50c6787bad5480f46d232879a2026373 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 17:22:31 -0700 Subject: [PATCH 027/438] Add `--cheats` to the CLI mission generator. Doesn't enable the red ATO display because that's a lot of clutter, but enables the commonly needed for debugging things. --- qt_ui/main.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qt_ui/main.py b/qt_ui/main.py index 08967914..5acfba73 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -158,6 +158,8 @@ def parse_args() -> argparse.Namespace: "--inverted", action="store_true", help="Invert the campaign." ) + new_game.add_argument("--cheats", action="store_true", help="Enable cheats.") + return parser.parse_args() @@ -168,6 +170,7 @@ def create_game( supercarrier: bool, auto_procurement: bool, inverted: bool, + cheats: bool, ) -> Game: campaign = Campaign.from_json(campaign_path) generator = GameGenerator( @@ -180,6 +183,8 @@ def create_game( automate_front_line_reinforcements=auto_procurement, automate_aircraft_reinforcements=auto_procurement, enable_new_ground_unit_recruitment=True, + enable_frontline_cheats=cheats, + enable_base_capture_cheat=cheats, ), GeneratorSettings( start_date=datetime.today(), @@ -227,6 +232,7 @@ def main(): args.supercarrier, args.auto_procurement, args.inverted, + args.cheats, ) run_ui(game) From cabbd234afd65fc6a68cbdac05291036c9d29b81 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 20:24:20 -0700 Subject: [PATCH 028/438] Fix incorrect docs. --- game/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/version.py b/game/version.py index 398c0c27..71c43a10 100644 --- a/game/version.py +++ b/game/version.py @@ -36,7 +36,7 @@ VERSION = _build_version_string() #: Version 2 #: * Front line endpoints now define convoy origin/destination waypoints. They should be #: placed on or near roads. -#: * Factories (Warehouse_A) define factory objectives. Only control points with +#: * Factories (Workshop_A) define factory objectives. Only control points with #: factories will be able to recruit ground units, so they should exist in sufficient #: number and be protected by IADS. CAMPAIGN_FORMAT_VERSION = 2 From 2a5b37b9ad8a9e9cafccc0171f47b1969c4a5563 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 20:37:15 -0700 Subject: [PATCH 029/438] Show convoys on the map. --- game/theater/controlpoint.py | 6 ++ game/transfers.py | 3 + qt_ui/widgets/map/QLiberationMap.py | 124 ++++++++++++++++++++-------- 3 files changed, 100 insertions(+), 33 deletions(-) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 9b9558f4..84e209b7 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -711,6 +711,12 @@ class ControlPoint(MissionTarget, ABC): def has_active_frontline(self) -> bool: return any(not c.is_friendly(self.captured) for c in self.connected_points) + def front_is_active(self, other: ControlPoint) -> bool: + if other not in self.connected_points: + raise ValueError + + return self.captured != other.captured + class Airfield(ControlPoint): def __init__( diff --git a/game/transfers.py b/game/transfers.py index 165b8030..e9a5fdc3 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -42,6 +42,9 @@ class RoadTransferOrder(TransferOrder): supply_route = SupplyRoute.for_control_point(self.position) return supply_route.shortest_path_between(self.position, self.destination) + def next_stop(self) -> ControlPoint: + return self.path()[0] + class PendingTransfers: def __init__(self) -> None: diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index d8b5f206..cd41827f 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -4,7 +4,7 @@ import datetime import logging import math from functools import singledispatchmethod -from typing import Iterable, Iterator, List, Optional, Tuple +from typing import Iterable, Iterator, List, Optional, Set, Tuple from PySide2 import QtCore, QtWidgets from PySide2.QtCore import QLineF, QPointF, QRectF, Qt @@ -39,7 +39,7 @@ from shapely.geometry import ( import qt_ui.uiconstants as CONST from game import Game from game.navmesh import NavMesh -from game.theater import ControlPoint, Enum +from game.theater import ControlPoint, Enum, SupplyRoute from game.theater.conflicttheater import FrontLine, ReferencePoint from game.theater.theatergroundobject import ( TheaterGroundObject, @@ -644,14 +644,7 @@ class QLiberationMap(QGraphicsView): 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) - - for cp in self.game.theater.player_points(): - if DisplayOptions.lines: - self.scene_create_lines_for_cp(cp, playerColor, enemyColor) - + self.draw_supply_routes() self.draw_flight_plans(scene) for cp in self.game.theater.controlpoints: @@ -829,7 +822,11 @@ class QLiberationMap(QGraphicsView): ) def draw_bezier_frontline( - self, scene: QGraphicsScene, pen: QPen, frontline: FrontLine + self, + scene: QGraphicsScene, + pen: QPen, + frontline: FrontLine, + convoy_size: int = 0, ) -> None: """ Thanks to Alquimista for sharing a python implementation of the bezier algorithm this is adapted from. @@ -844,32 +841,93 @@ class QLiberationMap(QGraphicsView): for point in bezier_curve_range( int(len(bezier_fixed_points) * 2), bezier_fixed_points ): - scene.addLine(old_point[0], old_point[1], point[0], point[1], pen=pen) + line = scene.addLine( + old_point[0], old_point[1], point[0], point[1], pen=pen + ) + if convoy_size: + units = "units" if convoy_size > 1 else "unit" + tooltip = ( + f"{convoy_size} {units} transferring between " + f"{frontline.control_point_a} and {frontline.control_point_b}." + ) + else: + tooltip = "No convoys present on this supply route." + line.setToolTip(tooltip) old_point = point - def scene_create_lines_for_cp(self, cp: ControlPoint, playerColor, enemyColor): + def draw_supply_routes(self) -> None: + seen = set() + for cp in self.game.theater.controlpoints: + seen.add(cp) + for connected in cp.connected_points: + if connected in seen: + continue + if DisplayOptions.lines: + self.draw_supply_route_between(cp, connected) + + def _count_units_tranferring_between(self, a: ControlPoint, b: ControlPoint) -> int: + # We attempt to short circuit the expensive shortest path computation for the + # cases where there is never a transfer, but caching might be needed. + + if a.captured != b.captured: + # Cannot transfer to enemy CPs. + return 0 + + # This is only called for drawing lines between nodes and have rules out routes + # to enemy bases, so a and b are guaranteed to be in the same supply route. + supply_route = SupplyRoute.for_control_point(a) + + count = 0 + points = {a, b} + for transfer in self.game.transfers: + # No possible route from our network to this transfer. + if transfer.position not in supply_route: + continue + + # Anything left is a transfer within our supply route. + transfer_points = {transfer.position, transfer.next_stop()} + if points == transfer_points: + count += sum(transfer.units.values()) + return count + + def supply_route_color(self, a: ControlPoint, b: ControlPoint) -> QColor: + if a.front_is_active(b): + return CONST.COLORS["red"] + elif a.captured: + return CONST.COLORS["dark_" + self.game.get_player_color()] + else: + return CONST.COLORS["dark_" + self.game.get_enemy_color()] + + def supply_route_style( + self, a: ControlPoint, b: ControlPoint, has_transfer: bool + ) -> Qt.PenStyle: + if a.front_is_active(b) or has_transfer: + return Qt.PenStyle.SolidLine + return Qt.PenStyle.DotLine + + def supply_route_pen( + self, a: ControlPoint, b: ControlPoint, has_transfer: bool + ) -> QPen: + color = self.supply_route_color(a, b) + pen = QPen(brush=color) + pen.setColor(color) + pen.setStyle(self.supply_route_style(a, b, has_transfer)) + pen.setWidth(6) + return pen + + def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None: scene = self.scene() - for connected_cp in cp.connected_points: - pos2 = self._transform_point(connected_cp.position) - if not cp.captured: - color = CONST.COLORS["dark_" + enemyColor] + + convoy_size = self._count_units_tranferring_between(a, b) + pen = self.supply_route_pen(a, b, convoy_size > 0) + frontline = FrontLine(a, b, self.game.theater) + if a.front_is_active(b): + if DisplayOptions.actual_frontline_pos: + self.draw_actual_frontline(frontline, scene, pen) else: - color = CONST.COLORS["dark_" + playerColor] - pen = QPen(brush=color) - pen.setColor(color) - pen.setWidth(6) - frontline = FrontLine(cp, connected_cp, self.game.theater) - if ( - cp.captured - and not connected_cp.captured - and Conflict.has_frontline_between(cp, connected_cp) - ): - if DisplayOptions.actual_frontline_pos: - self.draw_actual_frontline(frontline, scene, pen) - else: - self.draw_frontline_approximation(frontline, scene, pen) - else: - self.draw_bezier_frontline(scene, pen, frontline) + self.draw_frontline_approximation(frontline, scene, pen) + else: + self.draw_bezier_frontline(scene, pen, frontline, convoy_size) def draw_frontline_approximation( self, frontline: FrontLine, scene: QGraphicsScene, pen: QPen From 2b06d8a096f93a163beda332a432dbbe984a1771 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 21:41:56 -0700 Subject: [PATCH 030/438] Convert front line segments to proper class. Needed so we can add context menus to the lines. --- qt_ui/widgets/map/QLiberationMap.py | 90 ++++++++++--------------- qt_ui/widgets/map/SupplyRouteSegment.py | 73 ++++++++++++++++++++ 2 files changed, 109 insertions(+), 54 deletions(-) create mode 100644 qt_ui/widgets/map/SupplyRouteSegment.py diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index cd41827f..1b902e03 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -4,7 +4,7 @@ import datetime import logging import math from functools import singledispatchmethod -from typing import Iterable, Iterator, List, Optional, Set, Tuple +from typing import Iterable, Iterator, List, Optional, Tuple from PySide2 import QtCore, QtWidgets from PySide2.QtCore import QLineF, QPointF, QRectF, Qt @@ -26,8 +26,8 @@ from PySide2.QtWidgets import ( QGraphicsView, ) from dcs import Point -from dcs.planes import F_16C_50 from dcs.mapping import point_from_heading +from dcs.planes import F_16C_50 from dcs.unitgroup import Group from shapely.geometry import ( LineString, @@ -44,6 +44,7 @@ from game.theater.conflicttheater import FrontLine, ReferencePoint from game.theater.theatergroundobject import ( TheaterGroundObject, ) +from game.transfers import RoadTransferOrder from game.utils import Distance, meters, nautical_miles from game.weather import TimeOfDay from gen import Conflict, Package @@ -66,6 +67,7 @@ from qt_ui.widgets.map.QFrontLine import QFrontLine from qt_ui.widgets.map.QLiberationScene import QLiberationScene from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject +from qt_ui.widgets.map.SupplyRouteSegment import SupplyRouteSegment from qt_ui.windows.GameUpdateSignal import GameUpdateSignal MAX_SHIP_DISTANCE = nautical_miles(80) @@ -824,9 +826,8 @@ class QLiberationMap(QGraphicsView): def draw_bezier_frontline( self, scene: QGraphicsScene, - pen: QPen, frontline: FrontLine, - convoy_size: int = 0, + convoys: List[RoadTransferOrder], ) -> None: """ Thanks to Alquimista for sharing a python implementation of the bezier algorithm this is adapted from. @@ -841,18 +842,17 @@ class QLiberationMap(QGraphicsView): for point in bezier_curve_range( int(len(bezier_fixed_points) * 2), bezier_fixed_points ): - line = scene.addLine( - old_point[0], old_point[1], point[0], point[1], pen=pen - ) - if convoy_size: - units = "units" if convoy_size > 1 else "unit" - tooltip = ( - f"{convoy_size} {units} transferring between " - f"{frontline.control_point_a} and {frontline.control_point_b}." + scene.addItem( + SupplyRouteSegment( + old_point[0], + old_point[1], + point[0], + point[1], + frontline.control_point_a, + frontline.control_point_b, + convoys, ) - else: - tooltip = "No convoys present on this supply route." - line.setToolTip(tooltip) + ) old_point = point def draw_supply_routes(self) -> None: @@ -865,19 +865,21 @@ class QLiberationMap(QGraphicsView): if DisplayOptions.lines: self.draw_supply_route_between(cp, connected) - def _count_units_tranferring_between(self, a: ControlPoint, b: ControlPoint) -> int: + def _transfers_between( + self, a: ControlPoint, b: ControlPoint + ) -> List[RoadTransferOrder]: # We attempt to short circuit the expensive shortest path computation for the # cases where there is never a transfer, but caching might be needed. if a.captured != b.captured: # Cannot transfer to enemy CPs. - return 0 + return [] # This is only called for drawing lines between nodes and have rules out routes # to enemy bases, so a and b are guaranteed to be in the same supply route. supply_route = SupplyRoute.for_control_point(a) - count = 0 + transfers = [] points = {a, b} for transfer in self.game.transfers: # No possible route from our network to this transfer. @@ -887,55 +889,32 @@ class QLiberationMap(QGraphicsView): # Anything left is a transfer within our supply route. transfer_points = {transfer.position, transfer.next_stop()} if points == transfer_points: - count += sum(transfer.units.values()) - return count - - def supply_route_color(self, a: ControlPoint, b: ControlPoint) -> QColor: - if a.front_is_active(b): - return CONST.COLORS["red"] - elif a.captured: - return CONST.COLORS["dark_" + self.game.get_player_color()] - else: - return CONST.COLORS["dark_" + self.game.get_enemy_color()] - - def supply_route_style( - self, a: ControlPoint, b: ControlPoint, has_transfer: bool - ) -> Qt.PenStyle: - if a.front_is_active(b) or has_transfer: - return Qt.PenStyle.SolidLine - return Qt.PenStyle.DotLine - - def supply_route_pen( - self, a: ControlPoint, b: ControlPoint, has_transfer: bool - ) -> QPen: - color = self.supply_route_color(a, b) - pen = QPen(brush=color) - pen.setColor(color) - pen.setStyle(self.supply_route_style(a, b, has_transfer)) - pen.setWidth(6) - return pen + transfers.append(transfer) + return transfers def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None: scene = self.scene() - convoy_size = self._count_units_tranferring_between(a, b) - pen = self.supply_route_pen(a, b, convoy_size > 0) + convoys = self._transfers_between(a, b) frontline = FrontLine(a, b, self.game.theater) if a.front_is_active(b): if DisplayOptions.actual_frontline_pos: - self.draw_actual_frontline(frontline, scene, pen) + self.draw_actual_frontline(scene, frontline, convoys) else: - self.draw_frontline_approximation(frontline, scene, pen) + self.draw_frontline_approximation(scene, frontline, convoys) else: - self.draw_bezier_frontline(scene, pen, frontline, convoy_size) + self.draw_bezier_frontline(scene, frontline, convoys) def draw_frontline_approximation( - self, frontline: FrontLine, scene: QGraphicsScene, pen: QPen + self, + scene: QGraphicsScene, + frontline: FrontLine, + convoys: List[RoadTransferOrder], ) -> None: posx = frontline.position h = frontline.attack_heading pos2 = self._transform_point(posx) - self.draw_bezier_frontline(scene, pen, frontline) + self.draw_bezier_frontline(scene, frontline, convoys) p1 = point_from_heading(pos2[0], pos2[1], h + 180, 25) p2 = point_from_heading(pos2[0], pos2[1], h, 25) scene.addItem( @@ -943,9 +922,12 @@ class QLiberationMap(QGraphicsView): ) def draw_actual_frontline( - self, frontline: FrontLine, scene: QGraphicsScene, pen: QPen + self, + scene: QGraphicsScene, + frontline: FrontLine, + convoys: List[RoadTransferOrder], ) -> None: - self.draw_bezier_frontline(scene, pen, frontline) + self.draw_bezier_frontline(scene, frontline, convoys) vector = Conflict.frontline_vector( frontline.control_point_a, frontline.control_point_b, self.game.theater ) diff --git a/qt_ui/widgets/map/SupplyRouteSegment.py b/qt_ui/widgets/map/SupplyRouteSegment.py new file mode 100644 index 00000000..02f8ce33 --- /dev/null +++ b/qt_ui/widgets/map/SupplyRouteSegment.py @@ -0,0 +1,73 @@ +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 RoadTransferOrder +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[RoadTransferOrder], + 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()) + + @cached_property + def convoy_size(self) -> int: + return sum(sum(c.units.values()) for c in self.convoys) + + def make_tooltip(self) -> str: + if not self.convoys: + return "No convoys present on this supply route." + units = "units" if self.convoy_size > 1 else "unit" + + return ( + f"{self.convoy_size} {units} transferring between {self.control_point_a} " + f"and {self.control_point_b}." + ) + + @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 + + @property + def has_convoys(self) -> bool: + return bool(self.convoys) From 3f16c0378af6e08ba262f6fa9870def2233aec6c Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Apr 2021 23:09:39 -0700 Subject: [PATCH 031/438] Add BAI planning against supply routes. This currently is only supported for player flights. I have no idea how to create an AI flight plan that won't just get them killed. AI-only BAI missions against supply routes will warn the player on mission creation. --- game/theater/supplyroutes.py | 24 ++++++++ gen/flights/flightplan.py | 79 ++++++++++++++++++++++++- gen/flights/waypointbuilder.py | 57 ++++++++++++++++++ qt_ui/widgets/QTopPanel.py | 38 ++++++++++++ qt_ui/widgets/map/SupplyRouteSegment.py | 42 ++++++++++++- 5 files changed, 238 insertions(+), 2 deletions(-) diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py index eb02663d..790f95a8 100644 --- a/game/theater/supplyroutes.py +++ b/game/theater/supplyroutes.py @@ -6,6 +6,8 @@ from collections import defaultdict from dataclasses import dataclass, field from typing import Dict, Iterator, List, Optional +from dcs import Point +from game.theater import FlightType, MissionTarget from game.theater.controlpoint import ControlPoint @@ -97,3 +99,25 @@ class SupplyRoute: current = previous path.reverse() return path + + +class SupplyRouteLink(MissionTarget): + def __init__(self, a: ControlPoint, b: ControlPoint) -> None: + self.control_point_a = a + self.control_point_b = b + super().__init__( + f"Supply route between {a} and {b}", + Point((a.position.x + b.position.x) / 2, (a.position.y + b.position.y) / 2), + ) + + def mission_types(self, for_player: bool) -> Iterator[FlightType]: + yield from [ + FlightType.BAI, + # TODO: Escort + # TODO: SEAD + # TODO: Recon + # TODO: TARCAP + ] + + def is_friendly(self, to_player: bool) -> bool: + return self.control_point_a.captured diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 2b929387..85c2895a 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -30,6 +30,7 @@ from game.theater import ( SamGroundObject, TheaterGroundObject, ) +from game.theater.supplyroutes import SupplyRouteLink from game.theater.theatergroundobject import EwrGroundObject from game.utils import Distance, Speed, feet, meters, nautical_miles from .closestairfields import ObjectiveDistanceCache @@ -466,6 +467,25 @@ class CasFlightPlan(PatrollingFlightPlan): return self.patrol_end +@dataclass(frozen=True) +class ConvoyInterdictionFlightPlan(PatrollingFlightPlan): + takeoff: FlightWaypoint + land: FlightWaypoint + divert: Optional[FlightWaypoint] + + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield self.takeoff + yield from self.nav_to + yield from [ + self.patrol_start, + self.patrol_end, + ] + yield from self.nav_from + yield self.land + if self.divert is not None: + yield self.divert + + @dataclass(frozen=True) class TarCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint @@ -1014,7 +1034,7 @@ class FlightPlanBuilder: hold_duration=timedelta(hours=4), ) - def generate_bai(self, flight: Flight) -> StrikeFlightPlan: + def generate_bai(self, flight: Flight) -> FlightPlan: """Generates a BAI flight plan. Args: @@ -1022,6 +1042,9 @@ class FlightPlanBuilder: """ location = self.package.target + if isinstance(location, SupplyRouteLink): + return self.generate_supply_route_bai(flight, location) + if not isinstance(location, TheaterGroundObject): raise InvalidObjectiveLocation(flight.flight_type, location) @@ -1034,6 +1057,60 @@ class FlightPlanBuilder: flight, location, FlightWaypointType.INGRESS_BAI, targets ) + def generate_supply_route_bai( + self, flight: Flight, location: SupplyRouteLink + ) -> ConvoyInterdictionFlightPlan: + """Generates a BAI flight plan for attacking a supply route. + + These flight plans are extremely rough because we do not know where the roads + are. For now they're mostly only usable by players. The flight plan includes a + start and end patrol point matching the end points of the convoy's route and a + 30 minute time on station. It is up to the player to find the target. + + Args: + flight: The flight to generate the flight plan for. + location: The supply route link to attack. + """ + + origin = self.package_airfield() + a_dist = origin.distance_to(location.control_point_a) + b_dist = origin.distance_to(location.control_point_b) + if a_dist < b_dist: + near = location.control_point_a + far = location.control_point_b + else: + near = location.control_point_b + far = location.control_point_a + + patrol_alt = meters( + random.randint( + int(self.doctrine.min_patrol_altitude.meters), + int(self.doctrine.max_patrol_altitude.meters), + ) + ) + + builder = WaypointBuilder(flight, self.game, self.is_player) + start, end = builder.convoy_search(near, far, patrol_alt) + + return ConvoyInterdictionFlightPlan( + self.package, + flight, + takeoff=builder.takeoff(flight.departure), + nav_to=builder.nav_path( + flight.departure.position, near.position, patrol_alt + ), + nav_from=builder.nav_path( + far.position, flight.arrival.position, patrol_alt + ), + patrol_start=start, + patrol_end=end, + land=builder.land(flight.arrival), + divert=builder.divert(flight.divert), + # Not relevant because player only. + engagement_distance=meters(0), + patrol_duration=timedelta(minutes=30), + ) + def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan: """Generates an anti-ship flight plan. diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index 53fad47c..ad1db18a 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -349,6 +349,63 @@ class WaypointBuilder: self.race_track_end(end, altitude), ) + @staticmethod + def convoy_search_start( + control_point: ControlPoint, altitude: Distance + ) -> FlightWaypoint: + """Creates a convoy search start waypoint. + + Args: + control_point: Control point for the beginning of the search. + altitude: Altitude of the racetrack. + """ + waypoint = FlightWaypoint( + FlightWaypointType.INGRESS_BAI, + control_point.position.x, + control_point.position.y, + altitude, + ) + waypoint.name = control_point.name + waypoint.description = "Beginning of convoy search area" + waypoint.pretty_name = "Search start" + return waypoint + + @staticmethod + def convoy_search_end( + control_point: ControlPoint, altitude: Distance + ) -> FlightWaypoint: + """Creates a convoy search start waypoint. + + Args: + control_point: Control point for the beginning of the search. + altitude: Altitude of the racetrack. + """ + waypoint = FlightWaypoint( + FlightWaypointType.EGRESS, + control_point.position.x, + control_point.position.y, + altitude, + ) + waypoint.name = control_point.name + waypoint.description = "End of convoy search area" + waypoint.pretty_name = "Search end" + return waypoint + + def convoy_search( + self, start: ControlPoint, end: ControlPoint, altitude: Distance + ) -> Tuple[FlightWaypoint, FlightWaypoint]: + """Creates two waypoint for a convoy search path. + + Args: + start: The beginning convoy search waypoint. + end: The ending convoy search waypoint. + altitude: The convoy search altitude. + """ + return ( + self.convoy_search_start(start, altitude), + self.convoy_search_end(end, altitude), + ) + @staticmethod def orbit(start: Point, altitude: Distance) -> FlightWaypoint: """Creates an circular orbit point. diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index f43657f7..f08e84e0 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -16,6 +16,8 @@ import qt_ui.uiconstants as CONST from game import Game from game.event.airwar import AirWarEvent from gen.ato import Package +from gen.flights.flight import FlightType +from gen.flights.flightplan import ConvoyInterdictionFlightPlan from gen.flights.traveltime import TotEstimator from qt_ui.models import GameModel from qt_ui.widgets.QBudgetBox import QBudgetBox @@ -199,6 +201,36 @@ class QTopPanel(QFrame): ) return result == QMessageBox.Yes + def ato_has_ai_convoy_interdiction(self) -> bool: + for package in self.game.blue_ato.packages: + for flight in package.flights: + if ( + isinstance(flight.flight_plan, ConvoyInterdictionFlightPlan) + and not flight.client_count + ): + return True + return False + + def confirm_ai_convoy_interdiction_launch(self) -> bool: + result = QMessageBox.question( + self, + "Continue with AI convoy interdiction missions?", + ( + "AI only convoy interdiction missions were planned. AI behavior for " + "these missions has not been developed so they will probably get " + "themselves killed. Continuing is not recommended.
" + "
" + "To remove AI convoy interdiction missions, delete any BAI flights " + "that are planned against supply route objectives.
" + "
" + "Click 'Yes' to continue with AI only convoy interdiction missions." + "

Click 'No' to cancel and revise your flight planning." + ), + QMessageBox.No, + QMessageBox.Yes, + ) + return result == QMessageBox.Yes + def confirm_negative_start_time(self, negative_starts: List[Package]) -> bool: formatted = "
".join( [f"{p.primary_task} {p.target.name}" for p in negative_starts] @@ -241,6 +273,12 @@ class QTopPanel(QFrame): if not self.ato_has_clients() and not self.confirm_no_client_launch(): return + if ( + self.ato_has_ai_convoy_interdiction() + and not self.confirm_ai_convoy_interdiction_launch() + ): + return + negative_starts = self.negative_start_packages() if negative_starts: if not self.confirm_negative_start_time(negative_starts): diff --git a/qt_ui/widgets/map/SupplyRouteSegment.py b/qt_ui/widgets/map/SupplyRouteSegment.py index 02f8ce33..58fda738 100644 --- a/qt_ui/widgets/map/SupplyRouteSegment.py +++ b/qt_ui/widgets/map/SupplyRouteSegment.py @@ -3,10 +3,19 @@ from typing import List, Optional from PySide2.QtCore import Qt from PySide2.QtGui import QColor, QPen -from PySide2.QtWidgets import QGraphicsItem, QGraphicsLineItem +from PySide2.QtWidgets import ( + QAction, + QGraphicsItem, + QGraphicsLineItem, + QGraphicsSceneContextMenuEvent, + QGraphicsSceneHoverEvent, + QMenu, +) from game.theater import ControlPoint +from game.theater.supplyroutes import SupplyRouteLink from game.transfers import RoadTransferOrder +from qt_ui.dialogs import Dialog from qt_ui.uiconstants import COLORS @@ -28,6 +37,7 @@ class SupplyRouteSegment(QGraphicsLineItem): self.convoys = convoys self.setPen(self.make_pen()) self.setToolTip(self.make_tooltip()) + self.setAcceptHoverEvents(True) @cached_property def convoy_size(self) -> int: @@ -71,3 +81,33 @@ class SupplyRouteSegment(QGraphicsLineItem): @property def has_convoys(self) -> bool: return bool(self.convoys) + + @property + def targetable(self) -> bool: + return self.convoys and not self.control_point_a.captured + + def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: + # Can only plan missions against enemy supply routes that have convoys. + if not self.targetable: + super().contextMenuEvent(event) + return + + menu = QMenu("Menu") + + new_package_action = QAction(f"New package") + new_package_action.triggered.connect(self.open_new_package_dialog) + menu.addAction(new_package_action) + + menu.exec_(event.screenPos()) + + def open_new_package_dialog(self) -> None: + """Opens the dialog for planning a new mission package.""" + Dialog.open_new_package_dialog( + SupplyRouteLink(self.control_point_a, self.control_point_b) + ) + + def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): + if self.targetable: + self.setCursor(Qt.PointingHandCursor) + else: + super().hoverEnterEvent(event) From 50d8e08a343884296f8f6be26f443238a715e60f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 20 Apr 2021 21:06:58 -0700 Subject: [PATCH 032/438] Redo convoy attack flight plans. The previous flight plan only makes sense if the convoy will make it a significant distance from its starting point. At road speeds over the typical mission duration this is not true, so we can actually plan this as if it was a strike mission near the origin point and that's close enough. There's some cleanup work to do here that I've added todos for. Fixes https://github.com/Khopa/dcs_liberation/issues/996 --- game/operation/operation.py | 27 ++--- game/theater/supplyroutes.py | 24 ----- game/transfers.py | 27 ++++- gen/aircraft.py | 32 +++--- gen/convoys.py | 11 +- gen/flights/flightplan.py | 94 ++-------------- gen/flights/waypointbuilder.py | 63 +---------- gen/naming.py | 8 ++ qt_ui/widgets/QTopPanel.py | 38 ------- qt_ui/widgets/map/SupplyRouteSegment.py | 46 +------- .../windows/basemenu/DepartingConvoysMenu.py | 101 ++++++++++++++++++ qt_ui/windows/basemenu/QBaseMenuTabs.py | 46 ++++---- 12 files changed, 216 insertions(+), 301 deletions(-) create mode 100644 qt_ui/windows/basemenu/DepartingConvoysMenu.py diff --git a/game/operation/operation.py b/game/operation/operation.py index 5bf7c4e7..1376be88 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -1,10 +1,9 @@ from __future__ import annotations -from game.theater.theatergroundobject import TheaterGroundObject import logging import os from pathlib import Path -from typing import TYPE_CHECKING, Iterable, List, Optional, Set +from typing import Iterable, List, Optional, Set, TYPE_CHECKING from dcs import Mission from dcs.action import DoScript, DoScriptFile @@ -14,7 +13,9 @@ from dcs.lua.parse import loads from dcs.mapping import Point from dcs.translation import String from dcs.triggers import TriggerStart + from game.plugins import LuaPluginManager +from game.theater.theatergroundobject import TheaterGroundObject from gen import Conflict, FlightType, VisualGenerator from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData from gen.airfields import AIRFIELD_DATA @@ -31,7 +32,6 @@ from gen.naming import namegen from gen.radios import RadioFrequency, RadioRegistry from gen.tacan import TacanRegistry from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator - from .. import db from ..theater import Airfield from ..unitmap import UnitMap @@ -43,18 +43,13 @@ if TYPE_CHECKING: class Operation: """Static class for managing the final Mission generation""" - current_mission = None # type: Mission - airgen = None # type: AircraftConflictGenerator - triggersgen = None # type: TriggersGenerator - airsupportgen = None # type: AirSupportConflictGenerator - visualgen = None # type: VisualGenerator - groundobjectgen = None # type: GroundObjectsGenerator - briefinggen = None # type: BriefingGenerator - forcedoptionsgen = None # type: ForcedOptionsGenerator - radio_registry: Optional[RadioRegistry] = None - tacan_registry: Optional[TacanRegistry] = None - game = None # type: Game - environment_settings = None + current_mission: Mission + airgen: AircraftConflictGenerator + airsupportgen: AirSupportConflictGenerator + groundobjectgen: GroundObjectsGenerator + radio_registry: RadioRegistry + tacan_registry: TacanRegistry + game: Game trigger_radius = TRIGGER_RADIUS_MEDIUM is_quick = None player_awacs_enabled = True @@ -309,13 +304,13 @@ class Operation: # Set mission time and weather conditions. EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate() cls._generate_ground_units() + cls._generate_convoys() cls._generate_destroyed_units() cls._generate_air_units() cls.assign_channels_to_flights( cls.airgen.flights, cls.airsupportgen.air_support ) cls._generate_ground_conflicts() - cls._generate_convoys() # Triggers triggersgen = TriggersGenerator(cls.current_mission, cls.game) diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py index 790f95a8..eb02663d 100644 --- a/game/theater/supplyroutes.py +++ b/game/theater/supplyroutes.py @@ -6,8 +6,6 @@ from collections import defaultdict from dataclasses import dataclass, field from typing import Dict, Iterator, List, Optional -from dcs import Point -from game.theater import FlightType, MissionTarget from game.theater.controlpoint import ControlPoint @@ -99,25 +97,3 @@ class SupplyRoute: current = previous path.reverse() return path - - -class SupplyRouteLink(MissionTarget): - def __init__(self, a: ControlPoint, b: ControlPoint) -> None: - self.control_point_a = a - self.control_point_b = b - super().__init__( - f"Supply route between {a} and {b}", - Point((a.position.x + b.position.x) / 2, (a.position.y + b.position.y) / 2), - ) - - def mission_types(self, for_player: bool) -> Iterator[FlightType]: - yield from [ - FlightType.BAI, - # TODO: Escort - # TODO: SEAD - # TODO: Recon - # TODO: TARCAP - ] - - def is_friendly(self, to_player: bool) -> bool: - return self.control_point_a.captured diff --git a/game/transfers.py b/game/transfers.py index e9a5fdc3..dce97546 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -3,8 +3,10 @@ from dataclasses import dataclass, field from typing import Dict, Iterator, List, Type from dcs.unittype import VehicleType -from game.theater import ControlPoint +from game.theater import ControlPoint, MissionTarget from game.theater.supplyroutes import SupplyRoute +from gen.naming import namegen +from gen.flights.flight import FlightType @dataclass @@ -35,6 +37,8 @@ class RoadTransferOrder(TransferOrder): #: point a turn through the supply line. position: ControlPoint = field(init=False) + name: str = field(init=False, default_factory=namegen.next_convoy_name) + def __post_init__(self) -> None: self.position = self.origin @@ -46,6 +50,27 @@ class RoadTransferOrder(TransferOrder): return self.path()[0] +class Convoy(MissionTarget): + def __init__(self, transfer: RoadTransferOrder) -> None: + self.transfer = transfer + count = sum(c for c in transfer.units.values()) + super().__init__( + f"{transfer.name} of {count} units moving from {transfer.position} to " + f"{transfer.destination}", + transfer.position.position, + ) + + def mission_types(self, for_player: bool) -> Iterator[FlightType]: + if self.is_friendly(for_player): + return + + yield FlightType.BAI + yield from super().mission_types(for_player) + + def is_friendly(self, to_player: bool) -> bool: + return self.transfer.position.captured + + class PendingTransfers: def __init__(self) -> None: self.pending_transfers: List[RoadTransferOrder] = [] diff --git a/gen/aircraft.py b/gen/aircraft.py index 024a49c4..b4893704 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -72,7 +72,7 @@ from dcs.task import ( ) from dcs.terrain.terrain import Airport, NoParkingSlotError from dcs.triggers import Event, TriggerOnce, TriggerRule -from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup +from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup, VehicleGroup from dcs.unittype import FlyingType, UnitType from game import db @@ -88,6 +88,7 @@ from game.theater.controlpoint import ( OffMapSpawn, ) from game.theater.theatergroundobject import TheaterGroundObject +from game.transfers import Convoy, RoadTransferOrder from game.unitmap import UnitMap from game.utils import Distance, meters, nautical_miles from gen.ato import AirTaskingOrder, Package @@ -1691,25 +1692,30 @@ class BaiIngressBuilder(PydcsWaypointBuilder): def build(self) -> MovingPoint: waypoint = super().build() + # TODO: Add common "UnitGroupTarget" base type. target_group = self.package.target if isinstance(target_group, TheaterGroundObject): - tgroup = self.mission.find_group(target_group.group_name) - if tgroup is not None: - task = AttackGroup(tgroup.id, weapon_type=WeaponType.Auto) - task.params["attackQtyLimit"] = False - task.params["directionEnabled"] = False - task.params["altitudeEnabled"] = False - task.params["groupAttack"] = True - waypoint.tasks.append(task) - else: - logging.error( - "Could not find group for BAI mission %s", target_group.group_name - ) + group_name = target_group.group_name + elif isinstance(target_group, Convoy): + group_name = target_group.transfer.name else: logging.error( "Unexpected target type for BAI mission: %s", target_group.__class__.__name__, ) + return waypoint + + group = self.mission.find_group(group_name) + if group is None: + logging.error("Could not find group for BAI mission %s", group_name) + return waypoint + + task = AttackGroup(group.id, weapon_type=WeaponType.Auto) + task.params["attackQtyLimit"] = False + task.params["directionEnabled"] = False + task.params["altitudeEnabled"] = False + task.params["groupAttack"] = True + waypoint.tasks.append(task) return waypoint diff --git a/gen/convoys.py b/gen/convoys.py index 520902ea..9f021135 100644 --- a/gen/convoys.py +++ b/gen/convoys.py @@ -12,6 +12,7 @@ from dcs.unittype import VehicleType from game.transfers import RoadTransferOrder from game.unitmap import UnitMap +from game.utils import kph if TYPE_CHECKING: from game import Game @@ -26,24 +27,26 @@ class ConvoyGenerator: def generate(self) -> None: # Reset the count to make generation deterministic. - self.count = itertools.count() for transfer in self.game.transfers: self.generate_convoy_for(transfer) - def generate_convoy_for(self, transfer: RoadTransferOrder) -> None: + def generate_convoy_for(self, transfer: RoadTransferOrder) -> VehicleGroup: next_hop = transfer.path()[0] origin = transfer.position.convoy_spawns[next_hop] destination = next_hop.convoy_spawns[transfer.position] group = self._create_mixed_unit_group( - f"Convoy {next(self.count)}", + transfer.name, origin, transfer.units, transfer.player, ) - group.add_waypoint(destination, move_formation=PointAction.OnRoad) + group.add_waypoint( + destination, speed=kph(40).kph, move_formation=PointAction.OnRoad + ) self.make_drivable(group) self.unit_map.add_convoy_units(group, transfer) + return group def _create_mixed_unit_group( self, diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 85c2895a..d447e3ed 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -30,8 +30,8 @@ from game.theater import ( SamGroundObject, TheaterGroundObject, ) -from game.theater.supplyroutes import SupplyRouteLink from game.theater.theatergroundobject import EwrGroundObject +from game.transfers import Convoy from game.utils import Distance, Speed, feet, meters, nautical_miles from .closestairfields import ObjectiveDistanceCache from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType @@ -467,25 +467,6 @@ class CasFlightPlan(PatrollingFlightPlan): return self.patrol_end -@dataclass(frozen=True) -class ConvoyInterdictionFlightPlan(PatrollingFlightPlan): - takeoff: FlightWaypoint - land: FlightWaypoint - divert: Optional[FlightWaypoint] - - def iter_waypoints(self) -> Iterator[FlightWaypoint]: - yield self.takeoff - yield from self.nav_to - yield from [ - self.patrol_start, - self.patrol_end, - ] - yield from self.nav_from - yield self.land - if self.divert is not None: - yield self.divert - - @dataclass(frozen=True) class TarCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint @@ -1042,75 +1023,22 @@ class FlightPlanBuilder: """ location = self.package.target - if isinstance(location, SupplyRouteLink): - return self.generate_supply_route_bai(flight, location) - - if not isinstance(location, TheaterGroundObject): - raise InvalidObjectiveLocation(flight.flight_type, location) - targets: List[StrikeTarget] = [] - for group in location.groups: - if group.units: - targets.append(StrikeTarget(f"{group.name} at {location.name}", group)) + if isinstance(location, TheaterGroundObject): + for group in location.groups: + if group.units: + targets.append( + StrikeTarget(f"{group.name} at {location.name}", group) + ) + elif isinstance(location, Convoy): + targets.append(StrikeTarget(location.name, location)) + else: + raise InvalidObjectiveLocation(flight.flight_type, location) return self.strike_flightplan( flight, location, FlightWaypointType.INGRESS_BAI, targets ) - def generate_supply_route_bai( - self, flight: Flight, location: SupplyRouteLink - ) -> ConvoyInterdictionFlightPlan: - """Generates a BAI flight plan for attacking a supply route. - - These flight plans are extremely rough because we do not know where the roads - are. For now they're mostly only usable by players. The flight plan includes a - start and end patrol point matching the end points of the convoy's route and a - 30 minute time on station. It is up to the player to find the target. - - Args: - flight: The flight to generate the flight plan for. - location: The supply route link to attack. - """ - - origin = self.package_airfield() - a_dist = origin.distance_to(location.control_point_a) - b_dist = origin.distance_to(location.control_point_b) - if a_dist < b_dist: - near = location.control_point_a - far = location.control_point_b - else: - near = location.control_point_b - far = location.control_point_a - - patrol_alt = meters( - random.randint( - int(self.doctrine.min_patrol_altitude.meters), - int(self.doctrine.max_patrol_altitude.meters), - ) - ) - - builder = WaypointBuilder(flight, self.game, self.is_player) - start, end = builder.convoy_search(near, far, patrol_alt) - - return ConvoyInterdictionFlightPlan( - self.package, - flight, - takeoff=builder.takeoff(flight.departure), - nav_to=builder.nav_path( - flight.departure.position, near.position, patrol_alt - ), - nav_from=builder.nav_path( - far.position, flight.arrival.position, patrol_alt - ), - patrol_start=start, - patrol_end=end, - land=builder.land(flight.arrival), - divert=builder.divert(flight.divert), - # Not relevant because player only. - engagement_distance=meters(0), - patrol_duration=timedelta(minutes=30), - ) - def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan: """Generates an anti-ship flight plan. diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index ad1db18a..4559ca20 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -16,6 +16,8 @@ from dcs.mapping import Point from dcs.unit import Unit from dcs.unitgroup import Group, VehicleGroup +from game.transfers import Convoy + if TYPE_CHECKING: from game import Game @@ -32,7 +34,7 @@ from .flight import Flight, FlightWaypoint, FlightWaypointType @dataclass(frozen=True) class StrikeTarget: name: str - target: Union[VehicleGroup, TheaterGroundObject, Unit, Group] + target: Union[VehicleGroup, TheaterGroundObject, Unit, Group, Convoy] class WaypointBuilder: @@ -349,63 +351,6 @@ class WaypointBuilder: self.race_track_end(end, altitude), ) - @staticmethod - def convoy_search_start( - control_point: ControlPoint, altitude: Distance - ) -> FlightWaypoint: - """Creates a convoy search start waypoint. - - Args: - control_point: Control point for the beginning of the search. - altitude: Altitude of the racetrack. - """ - waypoint = FlightWaypoint( - FlightWaypointType.INGRESS_BAI, - control_point.position.x, - control_point.position.y, - altitude, - ) - waypoint.name = control_point.name - waypoint.description = "Beginning of convoy search area" - waypoint.pretty_name = "Search start" - return waypoint - - @staticmethod - def convoy_search_end( - control_point: ControlPoint, altitude: Distance - ) -> FlightWaypoint: - """Creates a convoy search start waypoint. - - Args: - control_point: Control point for the beginning of the search. - altitude: Altitude of the racetrack. - """ - waypoint = FlightWaypoint( - FlightWaypointType.EGRESS, - control_point.position.x, - control_point.position.y, - altitude, - ) - waypoint.name = control_point.name - waypoint.description = "End of convoy search area" - waypoint.pretty_name = "Search end" - return waypoint - - def convoy_search( - self, start: ControlPoint, end: ControlPoint, altitude: Distance - ) -> Tuple[FlightWaypoint, FlightWaypoint]: - """Creates two waypoint for a convoy search path. - - Args: - start: The beginning convoy search waypoint. - end: The ending convoy search waypoint. - altitude: The convoy search altitude. - """ - return ( - self.convoy_search_start(start, altitude), - self.convoy_search_end(end, altitude), - ) - @staticmethod def orbit(start: Point, altitude: Distance) -> FlightWaypoint: """Creates an circular orbit point. @@ -463,7 +408,7 @@ class WaypointBuilder: end: The end of the sweep. altitude: The sweep altitude. """ - return (self.sweep_start(start, altitude), self.sweep_end(end, altitude)) + return self.sweep_start(start, altitude), self.sweep_end(end, altitude) def escort( self, ingress: Point, target: MissionTarget, egress: Point diff --git a/gen/naming.py b/gen/naming.py index f1b14114..dcd09c2c 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -250,6 +250,7 @@ class NameGenerator: number = 0 infantry_number = 0 aircraft_number = 0 + convoy_number = 0 ANIMALS = ANIMALS existing_alphas: List[str] = [] @@ -258,6 +259,7 @@ class NameGenerator: def reset(cls): cls.number = 0 cls.infantry_number = 0 + cls.convoy_number = 0 cls.ANIMALS = ANIMALS cls.existing_alphas = [] @@ -266,6 +268,7 @@ class NameGenerator: cls.number = 0 cls.infantry_number = 0 cls.aircraft_number = 0 + cls.convoy_number = 0 @classmethod def next_aircraft_name(cls, country: Country, parent_base_id: int, flight: Flight): @@ -327,6 +330,11 @@ class NameGenerator: cls.number += 1 return "carrier|{}|{}|0|".format(country.id, cls.number) + @classmethod + def next_convoy_name(cls) -> str: + cls.convoy_number += 1 + return f"Convoy {cls.convoy_number:04}" + @classmethod def random_objective_name(cls): if len(cls.ANIMALS) == 0: diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index f08e84e0..f43657f7 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -16,8 +16,6 @@ import qt_ui.uiconstants as CONST from game import Game from game.event.airwar import AirWarEvent from gen.ato import Package -from gen.flights.flight import FlightType -from gen.flights.flightplan import ConvoyInterdictionFlightPlan from gen.flights.traveltime import TotEstimator from qt_ui.models import GameModel from qt_ui.widgets.QBudgetBox import QBudgetBox @@ -201,36 +199,6 @@ class QTopPanel(QFrame): ) return result == QMessageBox.Yes - def ato_has_ai_convoy_interdiction(self) -> bool: - for package in self.game.blue_ato.packages: - for flight in package.flights: - if ( - isinstance(flight.flight_plan, ConvoyInterdictionFlightPlan) - and not flight.client_count - ): - return True - return False - - def confirm_ai_convoy_interdiction_launch(self) -> bool: - result = QMessageBox.question( - self, - "Continue with AI convoy interdiction missions?", - ( - "AI only convoy interdiction missions were planned. AI behavior for " - "these missions has not been developed so they will probably get " - "themselves killed. Continuing is not recommended.
" - "
" - "To remove AI convoy interdiction missions, delete any BAI flights " - "that are planned against supply route objectives.
" - "
" - "Click 'Yes' to continue with AI only convoy interdiction missions." - "

Click 'No' to cancel and revise your flight planning." - ), - QMessageBox.No, - QMessageBox.Yes, - ) - return result == QMessageBox.Yes - def confirm_negative_start_time(self, negative_starts: List[Package]) -> bool: formatted = "
".join( [f"{p.primary_task} {p.target.name}" for p in negative_starts] @@ -273,12 +241,6 @@ class QTopPanel(QFrame): if not self.ato_has_clients() and not self.confirm_no_client_launch(): return - if ( - self.ato_has_ai_convoy_interdiction() - and not self.confirm_ai_convoy_interdiction_launch() - ): - return - negative_starts = self.negative_start_packages() if negative_starts: if not self.confirm_negative_start_time(negative_starts): diff --git a/qt_ui/widgets/map/SupplyRouteSegment.py b/qt_ui/widgets/map/SupplyRouteSegment.py index 58fda738..68a23cfc 100644 --- a/qt_ui/widgets/map/SupplyRouteSegment.py +++ b/qt_ui/widgets/map/SupplyRouteSegment.py @@ -4,18 +4,12 @@ from typing import List, Optional from PySide2.QtCore import Qt from PySide2.QtGui import QColor, QPen from PySide2.QtWidgets import ( - QAction, QGraphicsItem, QGraphicsLineItem, - QGraphicsSceneContextMenuEvent, - QGraphicsSceneHoverEvent, - QMenu, ) from game.theater import ControlPoint -from game.theater.supplyroutes import SupplyRouteLink from game.transfers import RoadTransferOrder -from qt_ui.dialogs import Dialog from qt_ui.uiconstants import COLORS @@ -39,12 +33,16 @@ class SupplyRouteSegment(QGraphicsLineItem): self.setToolTip(self.make_tooltip()) self.setAcceptHoverEvents(True) + @property + def has_convoys(self) -> bool: + return bool(self.convoys) + @cached_property def convoy_size(self) -> int: return sum(sum(c.units.values()) for c in self.convoys) def make_tooltip(self) -> str: - if not self.convoys: + if not self.has_convoys: return "No convoys present on this supply route." units = "units" if self.convoy_size > 1 else "unit" @@ -77,37 +75,3 @@ class SupplyRouteSegment(QGraphicsLineItem): pen.setStyle(self.line_style) pen.setWidth(6) return pen - - @property - def has_convoys(self) -> bool: - return bool(self.convoys) - - @property - def targetable(self) -> bool: - return self.convoys and not self.control_point_a.captured - - def contextMenuEvent(self, event: QGraphicsSceneContextMenuEvent) -> None: - # Can only plan missions against enemy supply routes that have convoys. - if not self.targetable: - super().contextMenuEvent(event) - return - - menu = QMenu("Menu") - - new_package_action = QAction(f"New package") - new_package_action.triggered.connect(self.open_new_package_dialog) - menu.addAction(new_package_action) - - menu.exec_(event.screenPos()) - - def open_new_package_dialog(self) -> None: - """Opens the dialog for planning a new mission package.""" - Dialog.open_new_package_dialog( - SupplyRouteLink(self.control_point_a, self.control_point_b) - ) - - def hoverEnterEvent(self, event: QGraphicsSceneHoverEvent): - if self.targetable: - self.setCursor(Qt.PointingHandCursor) - else: - super().hoverEnterEvent(event) diff --git a/qt_ui/windows/basemenu/DepartingConvoysMenu.py b/qt_ui/windows/basemenu/DepartingConvoysMenu.py new file mode 100644 index 00000000..193aafdd --- /dev/null +++ b/qt_ui/windows/basemenu/DepartingConvoysMenu.py @@ -0,0 +1,101 @@ +from PySide2.QtCore import Qt +from PySide2.QtWidgets import ( + QFrame, + QGridLayout, + QGroupBox, + QLabel, + QPushButton, + QScrollArea, + QVBoxLayout, + QWidget, +) + +from game import db +from game.theater import ControlPoint +from game.transfers import Convoy, RoadTransferOrder +from qt_ui.dialogs import Dialog +from qt_ui.models import GameModel +from qt_ui.uiconstants import VEHICLES_ICONS + + +class DepartingConvoyInfo(QGroupBox): + def __init__(self, convoy: RoadTransferOrder, game_model: GameModel) -> None: + super().__init__(f"To {convoy.destination}") + self.convoy = convoy + + main_layout = QVBoxLayout() + self.setLayout(main_layout) + + unit_layout = QGridLayout() + main_layout.addLayout(unit_layout) + + for idx, (unit_type, count) in enumerate(convoy.units.items()): + icon = QLabel() + if unit_type.id in VEHICLES_ICONS.keys(): + icon.setPixmap(VEHICLES_ICONS[unit_type.id]) + else: + icon.setText("" + unit_type.id[:8] + "") + icon.setProperty("style", "icon-armor") + unit_layout.addWidget(icon, idx, 0) + unit_display_name = db.unit_get_expanded_info( + game_model.game.enemy_country, unit_type, "name" + ) + unit_layout.addWidget( + QLabel(f"{count} x {unit_display_name}"), + idx, + 1, + ) + + if not convoy.units: + unit_layout.addWidget(QLabel("/"), 0, 0) + + attack_button = QPushButton("Attack") + attack_button.setProperty("style", "btn-danger") + attack_button.setMaximumWidth(180) + attack_button.clicked.connect(self.on_attack) + main_layout.addWidget(attack_button, 0, Qt.AlignLeft) + + def on_attack(self): + # TODO: Maintain Convoy list in Game. + # The fact that we create these here makes some of the other bookkeeping + # complicated. We could instead generate this at the start of the turn (and + # update whenever transfers are created or canceled) and also use that time to + # precalculate things like the next stop and group names. + Dialog.open_new_package_dialog(Convoy(self.convoy), parent=self.window()) + + +class DepartingConvoysList(QFrame): + def __init__(self, cp: ControlPoint, game_model: GameModel): + super().__init__() + self.cp = cp + self.game_model = game_model + self.setMinimumWidth(500) + + layout = QVBoxLayout() + self.setLayout(layout) + + scroll_content = QWidget() + task_box_layout = QGridLayout() + scroll_content.setLayout(task_box_layout) + + for convoy in game_model.game.transfers: + if convoy.position != cp: + continue + group_info = DepartingConvoyInfo(convoy, game_model) + task_box_layout.addWidget(group_info) + + scroll_content.setLayout(task_box_layout) + scroll = QScrollArea() + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) + scroll.setWidgetResizable(True) + scroll.setWidget(scroll_content) + layout.addWidget(scroll) + + +class DepartingConvoysMenu(QFrame): + def __init__(self, cp: ControlPoint, game_model: GameModel): + super().__init__() + layout = QVBoxLayout() + layout.addWidget(DepartingConvoysList(cp, game_model)) + self.setLayout(layout) diff --git a/qt_ui/windows/basemenu/QBaseMenuTabs.py b/qt_ui/windows/basemenu/QBaseMenuTabs.py index c6c35603..fe80dbe3 100644 --- a/qt_ui/windows/basemenu/QBaseMenuTabs.py +++ b/qt_ui/windows/basemenu/QBaseMenuTabs.py @@ -2,6 +2,7 @@ from PySide2.QtWidgets import QTabWidget from game.theater import ControlPoint, OffMapSpawn, Fob from qt_ui.models import GameModel +from qt_ui.windows.basemenu.DepartingConvoysMenu import DepartingConvoysMenu from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand from qt_ui.windows.basemenu.base_defenses.QBaseDefensesHQ import QBaseDefensesHQ from qt_ui.windows.basemenu.ground_forces.QGroundForcesHQ import QGroundForcesHQ @@ -18,27 +19,28 @@ class QBaseMenuTabs(QTabWidget): self.addTab(self.base_defenses_hq, "Base Defenses") self.intel = QIntelInfo(cp, game_model.game) self.addTab(self.intel, "Intel") + + self.departing_convoys = DepartingConvoysMenu(cp, game_model) + self.addTab(self.departing_convoys, "Departing Convoys") + return + + if isinstance(cp, Fob): + self.ground_forces_hq = QGroundForcesHQ(cp, game_model) + self.addTab(self.ground_forces_hq, "Ground Forces HQ") + if cp.helipads: + self.airfield_command = QAirfieldCommand(cp, game_model) + self.addTab(self.airfield_command, "Heliport") + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Base Defenses") else: + self.airfield_command = QAirfieldCommand(cp, game_model) + self.addTab(self.airfield_command, "Airfield Command") - if cp: - if isinstance(cp, Fob): - self.ground_forces_hq = QGroundForcesHQ(cp, game_model) - self.addTab(self.ground_forces_hq, "Ground Forces HQ") - if cp.helipads: - self.airfield_command = QAirfieldCommand(cp, game_model) - self.addTab(self.airfield_command, "Heliport") - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Base Defenses") - else: - - self.airfield_command = QAirfieldCommand(cp, game_model) - self.addTab(self.airfield_command, "Airfield Command") - - if cp.is_carrier: - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Fleet") - elif not isinstance(cp, OffMapSpawn): - self.ground_forces_hq = QGroundForcesHQ(cp, game_model) - self.addTab(self.ground_forces_hq, "Ground Forces HQ") - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Base Defenses") + if cp.is_carrier: + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Fleet") + elif not isinstance(cp, OffMapSpawn): + self.ground_forces_hq = QGroundForcesHQ(cp, game_model) + self.addTab(self.ground_forces_hq, "Ground Forces HQ") + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Base Defenses") From 6cffc47f3cbfd641eb21304392a039355d2603d0 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 20 Apr 2021 22:21:42 -0700 Subject: [PATCH 033/438] Clean up convoy code. --- game/debriefing.py | 2 +- game/event/event.py | 8 +- game/game.py | 12 +- game/transfers.py | 117 ++++++++++++++++-- game/unitmap.py | 8 +- gen/aircraft.py | 2 +- gen/convoys.py | 26 ++-- gen/naming.py | 2 +- qt_ui/widgets/map/QLiberationMap.py | 18 ++- qt_ui/widgets/map/SupplyRouteSegment.py | 21 ++-- .../windows/basemenu/DepartingConvoysMenu.py | 13 +- 11 files changed, 159 insertions(+), 70 deletions(-) diff --git a/game/debriefing.py b/game/debriefing.py index ea5b8758..3e6b4689 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -206,7 +206,7 @@ class Debriefing: convoy_unit = self.unit_map.convoy_unit(unit_name) if convoy_unit is not None: - if convoy_unit.transfer.player: + if convoy_unit.convoy.player_owned: losses.player_convoy.append(convoy_unit) else: losses.enemy_convoy.append(convoy_unit) diff --git a/game/event/event.py b/game/event/event.py index 5f5917d3..c2b77aa2 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -159,9 +159,9 @@ class Event: def commit_convoy_losses(debriefing: Debriefing) -> None: for loss in debriefing.convoy_losses: unit_type = loss.unit_type - transfer = loss.transfer - available = loss.transfer.units.get(unit_type, 0) - convoy_name = f"convoy from {transfer.position} to {transfer.destination}" + convoy = loss.convoy + available = loss.convoy.units.get(unit_type, 0) + convoy_name = f"convoy from {convoy.origin} to {convoy.destination}" if available <= 0: logging.error( f"Found killed {unit_type} in {convoy_name} but that convoy has " @@ -170,7 +170,7 @@ class Event: continue logging.info(f"{unit_type} destroyed in {convoy_name}") - transfer.units[unit_type] -= 1 + convoy.kill_unit(unit_type) @staticmethod def commit_ground_object_losses(debriefing: Debriefing) -> None: diff --git a/game/game.py b/game/game.py index bdd0a896..f59e140f 100644 --- a/game/game.py +++ b/game/game.py @@ -34,7 +34,7 @@ from .settings import Settings from .theater import ConflictTheater, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import MissileSiteGroundObject from .threatzones import ThreatZones -from .transfers import PendingTransfers, RoadTransferOrder +from .transfers import Convoy, ConvoyMap, PendingTransfers, RoadTransferOrder from .unitmap import UnitMap from .weather import Conditions, TimeOfDay @@ -122,7 +122,7 @@ class Game: self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints) - self._transfers = PendingTransfers() + self.transfers = PendingTransfers() self.sanitize_sides() @@ -154,14 +154,6 @@ class Game: # Regenerate any state that was not persisted. self.on_load() - @property - def transfers(self) -> PendingTransfers: - try: - return self._transfers - except AttributeError: - self._transfers = PendingTransfers() - return self._transfers - def generate_conditions(self) -> Conditions: return Conditions.generate( self.theater, self.current_day, self.current_turn_time_of_day, self.settings diff --git a/game/transfers.py b/game/transfers.py index dce97546..df96b0ea 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -1,8 +1,14 @@ +from __future__ import annotations + import logging +from collections import defaultdict from dataclasses import dataclass, field -from typing import Dict, Iterator, List, Type +from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type from dcs.unittype import VehicleType + +if TYPE_CHECKING: + pass from game.theater import ControlPoint, MissionTarget from game.theater.supplyroutes import SupplyRoute from gen.naming import namegen @@ -37,8 +43,6 @@ class RoadTransferOrder(TransferOrder): #: point a turn through the supply line. position: ControlPoint = field(init=False) - name: str = field(init=False, default_factory=namegen.next_convoy_name) - def __post_init__(self) -> None: self.position = self.origin @@ -51,14 +55,11 @@ class RoadTransferOrder(TransferOrder): class Convoy(MissionTarget): - def __init__(self, transfer: RoadTransferOrder) -> None: - self.transfer = transfer - count = sum(c for c in transfer.units.values()) - super().__init__( - f"{transfer.name} of {count} units moving from {transfer.position} to " - f"{transfer.destination}", - transfer.position.position, - ) + def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: + super().__init__(namegen.next_convoy_name(), origin.position) + self.origin = origin + self.destination = destination + self.transfers: List[RoadTransferOrder] = [] def mission_types(self, for_player: bool) -> Iterator[FlightType]: if self.is_friendly(for_player): @@ -68,11 +69,93 @@ class Convoy(MissionTarget): yield from super().mission_types(for_player) def is_friendly(self, to_player: bool) -> bool: - return self.transfer.position.captured + return self.origin.captured + + def add_units(self, transfer: RoadTransferOrder) -> None: + self.transfers.append(transfer) + + def remove_units(self, transfer: RoadTransferOrder) -> None: + self.transfers.remove(transfer) + + def kill_unit(self, unit_type: Type[VehicleType]) -> None: + for transfer in self.transfers: + if unit_type in transfer.units: + transfer.units[unit_type] -= 1 + return + raise KeyError + + @property + def size(self) -> int: + return sum(sum(t.units.values()) for t in self.transfers) + + @property + def units(self) -> Dict[Type[VehicleType], int]: + units: Dict[Type[VehicleType], int] = defaultdict(int) + for transfer in self.transfers: + for unit_type, count in transfer.units.items(): + units[unit_type] += count + return units + + @property + def player_owned(self) -> bool: + return self.origin.captured + + +class ConvoyMap: + def __init__(self) -> None: + # Dict of origin -> destination -> convoy. + self.convoys: Dict[ControlPoint, Dict[ControlPoint, Convoy]] = defaultdict(dict) + + def convoy_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool: + return destination in self.convoys[origin] + + def find_convoy( + self, origin: ControlPoint, destination: ControlPoint + ) -> Optional[Convoy]: + return self.convoys[origin].get(destination) + + def find_or_create_convoy( + self, origin: ControlPoint, destination: ControlPoint + ) -> Convoy: + convoy = self.find_convoy(origin, destination) + if convoy is None: + convoy = Convoy(origin, destination) + self.convoys[origin][destination] = convoy + return convoy + + def departing_from(self, origin: ControlPoint) -> Iterator[Convoy]: + yield from self.convoys[origin].values() + + def disband_convoy(self, convoy: Convoy) -> None: + del self.convoys[convoy.origin][convoy.destination] + + def add(self, transfer: RoadTransferOrder) -> None: + next_stop = transfer.next_stop() + self.find_or_create_convoy(transfer.position, next_stop).add_units(transfer) + + def remove(self, transfer: RoadTransferOrder) -> None: + next_stop = transfer.next_stop() + convoy = self.find_convoy(transfer.position, next_stop) + if convoy is None: + logging.error( + f"Attempting to remove {transfer} from convoy but it is in no convoy." + ) + return + convoy.remove_units(transfer) + if not convoy.transfers: + self.disband_convoy(convoy) + + def disband_all(self) -> None: + self.convoys = defaultdict(dict) + + def __iter__(self) -> Iterator[Convoy]: + for destination_dict in self.convoys.values(): + yield from destination_dict.values() class PendingTransfers: def __init__(self) -> None: + self.convoys = ConvoyMap() self.pending_transfers: List[RoadTransferOrder] = [] def __iter__(self) -> Iterator[RoadTransferOrder]: @@ -88,8 +171,10 @@ class PendingTransfers: def new_transfer(self, transfer: RoadTransferOrder) -> None: transfer.origin.base.commit_losses(transfer.units) self.pending_transfers.append(transfer) + self.convoys.add(transfer) def cancel_transfer(self, transfer: RoadTransferOrder) -> None: + self.convoys.remove(transfer) self.pending_transfers.remove(transfer) transfer.origin.base.commision_units(transfer.units) @@ -99,8 +184,16 @@ class PendingTransfers: if not self.perform_transfer(transfer): incomplete.append(transfer) self.pending_transfers = incomplete + self.rebuild_convoys() + + def rebuild_convoys(self) -> None: + self.convoys.disband_all() + for transfer in self.pending_transfers: + self.convoys.add(transfer) def perform_transfer(self, transfer: RoadTransferOrder) -> bool: + # TODO: Can be improved to use the convoy map. + # The convoy map already has a lot of the data that we're recomputing here. if transfer.player != transfer.destination.captured: logging.info( f"Transfer destination {transfer.destination.name} was captured." diff --git a/game/unitmap.py b/game/unitmap.py index 119eae50..520e80f9 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -9,7 +9,7 @@ from dcs.unittype import VehicleType from game import db from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import BuildingGroundObject -from game.transfers import RoadTransferOrder +from game.transfers import Convoy, RoadTransferOrder from gen.flights.flight import Flight @@ -29,7 +29,7 @@ class GroundObjectUnit: @dataclass(frozen=True) class ConvoyUnit: unit_type: Type[VehicleType] - transfer: RoadTransferOrder + convoy: Convoy @dataclass(frozen=True) @@ -121,7 +121,7 @@ class UnitMap: def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]: return self.ground_object_units.get(name, None) - def add_convoy_units(self, group: Group, transfer: RoadTransferOrder) -> None: + def add_convoy_units(self, group: Group, convoy: Convoy) -> None: for unit in group.units: # The actual name is a String (the pydcs translatable string), which # doesn't define __eq__. @@ -135,7 +135,7 @@ class UnitMap: raise RuntimeError( f"{name} is a {unit_type.__name__}, expected a VehicleType" ) - self.convoys[name] = ConvoyUnit(unit_type, transfer) + self.convoys[name] = ConvoyUnit(unit_type, convoy) def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: return self.convoys.get(name, None) diff --git a/gen/aircraft.py b/gen/aircraft.py index b4893704..174b2c8f 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1697,7 +1697,7 @@ class BaiIngressBuilder(PydcsWaypointBuilder): if isinstance(target_group, TheaterGroundObject): group_name = target_group.group_name elif isinstance(target_group, Convoy): - group_name = target_group.transfer.name + group_name = target_group.name else: logging.error( "Unexpected target type for BAI mission: %s", diff --git a/gen/convoys.py b/gen/convoys.py index 9f021135..21d48c8f 100644 --- a/gen/convoys.py +++ b/gen/convoys.py @@ -10,7 +10,7 @@ from dcs.unit import Vehicle from dcs.unitgroup import VehicleGroup from dcs.unittype import VehicleType -from game.transfers import RoadTransferOrder +from game.transfers import Convoy, RoadTransferOrder from game.unitmap import UnitMap from game.utils import kph @@ -27,25 +27,23 @@ class ConvoyGenerator: def generate(self) -> None: # Reset the count to make generation deterministic. - for transfer in self.game.transfers: - self.generate_convoy_for(transfer) - - def generate_convoy_for(self, transfer: RoadTransferOrder) -> VehicleGroup: - next_hop = transfer.path()[0] - origin = transfer.position.convoy_spawns[next_hop] - destination = next_hop.convoy_spawns[transfer.position] + for convoy in self.game.transfers.convoys: + self.generate_convoy(convoy) + def generate_convoy(self, convoy: Convoy) -> VehicleGroup: group = self._create_mixed_unit_group( - transfer.name, - origin, - transfer.units, - transfer.player, + convoy.name, + convoy.origin.position, + convoy.units, + convoy.player_owned, ) group.add_waypoint( - destination, speed=kph(40).kph, move_formation=PointAction.OnRoad + convoy.destination.position, + speed=kph(40).kph, + move_formation=PointAction.OnRoad, ) self.make_drivable(group) - self.unit_map.add_convoy_units(group, transfer) + self.unit_map.add_convoy_units(group, convoy) return group def _create_mixed_unit_group( diff --git a/gen/naming.py b/gen/naming.py index dcd09c2c..d8c7f768 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -333,7 +333,7 @@ class NameGenerator: @classmethod def next_convoy_name(cls) -> str: cls.convoy_number += 1 - return f"Convoy {cls.convoy_number:04}" + return f"Convoy {cls.convoy_number:03}" @classmethod def random_objective_name(cls): diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 1b902e03..540f9e0c 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -44,7 +44,7 @@ from game.theater.conflicttheater import FrontLine, ReferencePoint from game.theater.theatergroundobject import ( TheaterGroundObject, ) -from game.transfers import RoadTransferOrder +from game.transfers import Convoy, RoadTransferOrder from game.utils import Distance, meters, nautical_miles from game.weather import TimeOfDay from gen import Conflict, Package @@ -827,7 +827,7 @@ class QLiberationMap(QGraphicsView): self, scene: QGraphicsScene, frontline: FrontLine, - convoys: List[RoadTransferOrder], + convoys: List[Convoy], ) -> None: """ Thanks to Alquimista for sharing a python implementation of the bezier algorithm this is adapted from. @@ -895,7 +895,15 @@ class QLiberationMap(QGraphicsView): def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None: scene = self.scene() - convoys = self._transfers_between(a, b) + convoy_map = self.game.transfers.convoys + convoys = [] + convoy = convoy_map.find_convoy(a, b) + if convoy is not None: + convoys.append(convoy) + convoy = convoy_map.find_convoy(b, a) + if convoy is not None: + convoys.append(convoy) + frontline = FrontLine(a, b, self.game.theater) if a.front_is_active(b): if DisplayOptions.actual_frontline_pos: @@ -909,7 +917,7 @@ class QLiberationMap(QGraphicsView): self, scene: QGraphicsScene, frontline: FrontLine, - convoys: List[RoadTransferOrder], + convoys: List[Convoy], ) -> None: posx = frontline.position h = frontline.attack_heading @@ -925,7 +933,7 @@ class QLiberationMap(QGraphicsView): self, scene: QGraphicsScene, frontline: FrontLine, - convoys: List[RoadTransferOrder], + convoys: List[Convoy], ) -> None: self.draw_bezier_frontline(scene, frontline, convoys) vector = Conflict.frontline_vector( diff --git a/qt_ui/widgets/map/SupplyRouteSegment.py b/qt_ui/widgets/map/SupplyRouteSegment.py index 68a23cfc..78401bc2 100644 --- a/qt_ui/widgets/map/SupplyRouteSegment.py +++ b/qt_ui/widgets/map/SupplyRouteSegment.py @@ -9,7 +9,7 @@ from PySide2.QtWidgets import ( ) from game.theater import ControlPoint -from game.transfers import RoadTransferOrder +from game.transfers import Convoy from qt_ui.uiconstants import COLORS @@ -22,7 +22,7 @@ class SupplyRouteSegment(QGraphicsLineItem): y1: float, control_point_a: ControlPoint, control_point_b: ControlPoint, - convoys: List[RoadTransferOrder], + convoys: List[Convoy], parent: Optional[QGraphicsItem] = None, ) -> None: super().__init__(x0, y0, x1, y1, parent) @@ -37,19 +37,18 @@ class SupplyRouteSegment(QGraphicsLineItem): def has_convoys(self) -> bool: return bool(self.convoys) - @cached_property - def convoy_size(self) -> int: - return sum(sum(c.units.values()) for c in self.convoys) - def make_tooltip(self) -> str: if not self.has_convoys: return "No convoys present on this supply route." - units = "units" if self.convoy_size > 1 else "unit" - return ( - f"{self.convoy_size} {units} transferring between {self.control_point_a} " - f"and {self.control_point_b}." - ) + 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: diff --git a/qt_ui/windows/basemenu/DepartingConvoysMenu.py b/qt_ui/windows/basemenu/DepartingConvoysMenu.py index 193aafdd..3e49900a 100644 --- a/qt_ui/windows/basemenu/DepartingConvoysMenu.py +++ b/qt_ui/windows/basemenu/DepartingConvoysMenu.py @@ -12,15 +12,15 @@ from PySide2.QtWidgets import ( from game import db from game.theater import ControlPoint -from game.transfers import Convoy, RoadTransferOrder +from game.transfers import Convoy from qt_ui.dialogs import Dialog from qt_ui.models import GameModel from qt_ui.uiconstants import VEHICLES_ICONS class DepartingConvoyInfo(QGroupBox): - def __init__(self, convoy: RoadTransferOrder, game_model: GameModel) -> None: - super().__init__(f"To {convoy.destination}") + def __init__(self, convoy: Convoy, game_model: GameModel) -> None: + super().__init__(f"{convoy.name} to {convoy.destination}") self.convoy = convoy main_layout = QVBoxLayout() @@ -61,7 +61,7 @@ class DepartingConvoyInfo(QGroupBox): # complicated. We could instead generate this at the start of the turn (and # update whenever transfers are created or canceled) and also use that time to # precalculate things like the next stop and group names. - Dialog.open_new_package_dialog(Convoy(self.convoy), parent=self.window()) + Dialog.open_new_package_dialog(self.convoy, parent=self.window()) class DepartingConvoysList(QFrame): @@ -78,9 +78,8 @@ class DepartingConvoysList(QFrame): task_box_layout = QGridLayout() scroll_content.setLayout(task_box_layout) - for convoy in game_model.game.transfers: - if convoy.position != cp: - continue + convoy_map = game_model.game.transfers.convoys + for convoy in convoy_map.departing_from(cp): group_info = DepartingConvoyInfo(convoy, game_model) task_box_layout.addWidget(group_info) From 489b4d6acf09e2dcf6fa5492fa285321cc8811d1 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 20 Apr 2021 22:56:53 -0700 Subject: [PATCH 034/438] Add AI convoy attack planning. Like the comment says this rarely has any effect due to the ordering of flight planning and convoy creation. Could be separated, but opfor will still not be able to target any convoys that the player creates in the UI on that turn because they planning is done before the player can use the UI. Multi-turn transfers will be targetable, however. --- game/transfers.py | 5 +++++ gen/flights/ai_flight_planner.py | 38 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/game/transfers.py b/game/transfers.py index df96b0ea..fe84d312 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -126,6 +126,11 @@ class ConvoyMap: def departing_from(self, origin: ControlPoint) -> Iterator[Convoy]: yield from self.convoys[origin].values() + def travelling_to(self, destination: ControlPoint) -> Iterator[Convoy]: + for destination_dict in self.convoys.values(): + if destination in destination_dict: + yield destination_dict[destination] + def disband_convoy(self, convoy: Convoy) -> None: del self.convoys[convoy.origin][convoy.destination] diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 944f2479..909eb873 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -39,6 +39,7 @@ from game.theater.theatergroundobject import ( NavalGroundObject, VehicleGroupGroundObject, ) +from game.transfers import Convoy from game.utils import Distance, nautical_miles from gen import Conflict from gen.ato import Package @@ -444,6 +445,15 @@ class ObjectiveFinder: airfields.append(control_point) return self._targets_by_range(airfields) + def convoys(self) -> Iterator[Convoy]: + for front_line in self.front_lines(): + if front_line.control_point_a.is_friendly(self.is_player): + enemy_cp = front_line.control_point_a + else: + enemy_cp = front_line.control_point_b + + yield from self.game.transfers.convoys.travelling_to(enemy_cp) + def friendly_control_points(self) -> Iterator[ControlPoint]: """Iterates over all friendly control points.""" return ( @@ -605,6 +615,34 @@ class CoalitionMissionPlanner: ], ) + # These will only rarely get planned. When a convoy is travelling multiple legs, + # they're targetable after the first leg. The reason for this is that + # procurement happens *after* mission planning so that the missions that could + # not be filled will guide the procurement process. Procurement is the stage + # that convoys are created (because they're created to move ground units that + # were just purchased), so we haven't created any yet. Any incomplete transfers + # from the previous turn (multi-leg journeys) will still be present though so + # they can be targeted. + # + # Even after this is fixed, the player's convoys that were created through the + # UI will never be targeted on the first turn of their journey because the AI + # stops planning after the start of the turn. We could potentially fix this by + # moving opfor mission planning until the takeoff button is pushed. + for convoy in self.objective_finder.convoys(): + yield ProposedMission( + convoy, + [ + ProposedFlight(FlightType.BAI, 2, self.MAX_BAI_RANGE), + # TODO: Max escort range. + ProposedFlight( + FlightType.ESCORT, 2, self.MAX_BAI_RANGE, EscortType.AirToAir + ), + ProposedFlight( + FlightType.SEAD, 2, self.MAX_BAI_RANGE, EscortType.Sead + ), + ], + ) + for group in self.objective_finder.threatening_ships(): yield ProposedMission( group, From c2ebf61fd36f28f65da399bb39607635708cc728 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 20 Apr 2021 23:20:08 -0700 Subject: [PATCH 035/438] Disable cancel actions on enemy transfers. Decided to leave these in the menu as intel for the player, but the player can no longer cancel them. Fixes https://github.com/Khopa/dcs_liberation/issues/995 --- qt_ui/windows/PendingTransfersDialog.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/qt_ui/windows/PendingTransfersDialog.py b/qt_ui/windows/PendingTransfersDialog.py index 447f6437..ac671c86 100644 --- a/qt_ui/windows/PendingTransfersDialog.py +++ b/qt_ui/windows/PendingTransfersDialog.py @@ -139,6 +139,10 @@ class PendingTransfersList(QListView): def contextMenuEvent(self, event: QContextMenuEvent) -> None: index = self.indexAt(event.pos()) + if not index.isValid(): + return + if not self.transfer_model.transfer_at_index(index).player: + return menu = QMenu("Menu") @@ -181,15 +185,23 @@ class PendingTransfersDialog(QDialog): self.cancel_button = QPushButton("Cancel Transfer") self.cancel_button.setProperty("style", "btn-danger") self.cancel_button.clicked.connect(self.on_cancel_transfer) - self.cancel_button.setEnabled(self.transfer_model.rowCount() > 0) + self.cancel_button.setEnabled( + self.can_cancel(self.transfer_list.currentIndex()) + ) button_layout.addWidget(self.cancel_button) def on_cancel_transfer(self) -> None: """Cancels the selected transfer order.""" self.transfer_model.cancel_transfer_at_index(self.transfer_list.currentIndex()) + def can_cancel(self, index: QModelIndex) -> bool: + return self.transfer_model.transfer_at_index(index).player + def on_selection_changed( self, selected: QItemSelection, _deselected: QItemSelection ) -> None: """Updates the state of the delete button.""" - self.cancel_button.setEnabled(not selected.empty()) + if selected.empty(): + self.cancel_button.setEnabled(False) + return + self.cancel_button.setEnabled(self.can_cancel(selected.indexes()[0])) From 29cd55e795d5c5646deb0a1f1e84ef53131739d3 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 21 Apr 2021 01:02:02 -0700 Subject: [PATCH 036/438] Add WIP airlift UI. Doesn't do anything yet. https://github.com/Khopa/dcs_liberation/issues/825 --- .../windows/basemenu/NewUnitTransferDialog.py | 84 ++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index a4c3f554..66873aab 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -2,10 +2,12 @@ from __future__ import annotations import logging from collections import defaultdict +from dataclasses import dataclass from typing import Callable, Dict, Type from PySide2.QtCore import Qt from PySide2.QtWidgets import ( + QCheckBox, QComboBox, QDialog, QFrame, @@ -23,7 +25,7 @@ from PySide2.QtWidgets import ( from dcs.task import PinpointStrike from dcs.unittype import UnitType -from game import db +from game import Game, db from game.theater import ControlPoint, SupplyRoute from game.transfers import RoadTransferOrder from qt_ui.models import GameModel @@ -80,12 +82,43 @@ class UnitTransferList(QFrame): main_layout.addWidget(scroll) -class TransferDestinationPanel(QVBoxLayout): - def __init__(self, label: str, origin: ControlPoint) -> None: +@dataclass(frozen=True) +class AirliftCapacity: + helicopter: int + cargo_plane: int + + @property + def total(self) -> int: + return self.helicopter + self.cargo_plane + + @classmethod + def to_control_point(cls, game: Game) -> AirliftCapacity: + helo_capacity = 0 + plane_capacity = 0 + for cp in game.theater.player_points(): + inventory = game.aircraft_inventory.for_control_point(cp) + for unit_type, count in inventory.all_aircraft: + if unit_type.helicopter: + helo_capacity += count + return AirliftCapacity(helicopter=helo_capacity, cargo_plane=plane_capacity) + + +class TransferOptionsPanel(QVBoxLayout): + def __init__(self, origin: ControlPoint, airlift_capacity: AirliftCapacity) -> None: super().__init__() self.source_combo_box = TransferDestinationComboBox(origin) - self.addLayout(QLabeledWidget(label, self.source_combo_box)) + self.addLayout(QLabeledWidget("Destination:", self.source_combo_box)) + self.airlift = QCheckBox() + self.airlift.toggled.connect(self.set_airlift) + self.addLayout(QLabeledWidget("Airlift (non-functional):", self.airlift)) + self.addWidget( + QLabel( + f"{airlift_capacity.total} airlift capacity " + f"({airlift_capacity.cargo_plane} from cargo planes, " + f"{airlift_capacity.helicopter} from helicopters)" + ) + ) @property def changed(self): @@ -95,6 +128,9 @@ class TransferDestinationPanel(QVBoxLayout): def current(self) -> ControlPoint: return self.source_combo_box.currentData() + def set_airlift(self, value: bool) -> None: + pass + class TransferControls(QGroupBox): def __init__( @@ -147,9 +183,17 @@ class TransferControls(QGroupBox): class ScrollingUnitTransferGrid(QFrame): - def __init__(self, cp: ControlPoint, game_model: GameModel) -> None: + def __init__( + self, + cp: ControlPoint, + airlift: bool, + airlift_capacity: AirliftCapacity, + game_model: GameModel, + ) -> None: super().__init__() self.cp = cp + self.airlift = airlift + self.remaining_capacity = airlift_capacity.total self.game_model = game_model self.transfers: Dict[Type[UnitType, int]] = defaultdict(int) @@ -219,6 +263,11 @@ class ScrollingUnitTransferGrid(QFrame): if not origin_inventory: return + if self.airlift: + if not self.remaining_capacity: + return + self.remaining_capacity -= 1 + self.transfers[unit_type] += 1 origin_inventory -= 1 controls.set_quantity(self.transfers[unit_type]) @@ -230,6 +279,9 @@ class ScrollingUnitTransferGrid(QFrame): if not controls.quantity: return + if self.airlift: + self.remaining_capacity += 1 + self.transfers[unit_type] -= 1 origin_inventory += 1 controls.set_quantity(self.transfers[unit_type]) @@ -266,11 +318,18 @@ class NewUnitTransferDialog(QDialog): layout = QVBoxLayout() self.setLayout(layout) - self.dest_panel = TransferDestinationPanel("Destination:", origin) - self.dest_panel.changed.connect(self.on_destination_changed) + self.airlift_capacity = AirliftCapacity.to_control_point(game_model.game) + self.dest_panel = TransferOptionsPanel(origin, self.airlift_capacity) + self.dest_panel.changed.connect(self.rebuild_transfers) layout.addLayout(self.dest_panel) - self.transfer_panel = ScrollingUnitTransferGrid(origin, game_model) + self.transfer_panel = ScrollingUnitTransferGrid( + origin, + airlift=False, + airlift_capacity=self.airlift_capacity, + game_model=game_model, + ) + self.dest_panel.airlift.toggled.connect(self.rebuild_transfers) layout.addWidget(self.transfer_panel) self.submit_button = QPushButton("Create Transfer Order", parent=self) @@ -278,12 +337,17 @@ class NewUnitTransferDialog(QDialog): self.submit_button.setProperty("style", "start-button") layout.addWidget(self.submit_button) - def on_destination_changed(self, index: int) -> None: + def rebuild_transfers(self) -> None: # Rebuild the transfer panel to reset everything. It's easier to recreate the # panel itself than to clear the grid layout in the panel. self.layout().removeWidget(self.transfer_panel) self.layout().removeWidget(self.submit_button) - self.transfer_panel = ScrollingUnitTransferGrid(self.origin, self.game_model) + self.transfer_panel = ScrollingUnitTransferGrid( + self.origin, + airlift=self.dest_panel.airlift.isChecked(), + airlift_capacity=self.airlift_capacity, + game_model=self.game_model, + ) self.layout().addWidget(self.transfer_panel) self.layout().addWidget(self.submit_button) From 696a429e9e35f7f5bcf25a6744da0f33780b9d16 Mon Sep 17 00:00:00 2001 From: Khopa Date: Wed, 21 Apr 2021 13:20:06 +0200 Subject: [PATCH 037/438] Pydcs update to latest version --- pydcs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydcs b/pydcs index 22c14702..cd14f0a0 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit 22c147026553f378f70bc06234e35a9061aca96c +Subproject commit cd14f0a049d2bfdf1fa2e4e71f6258cd49040819 From a0a55797a99ffbb35f8b00be06f91b0439c0c6b4 Mon Sep 17 00:00:00 2001 From: Khopa Date: Wed, 21 Apr 2021 22:14:18 +0200 Subject: [PATCH 038/438] Fixed airfields frequency on Syria --- gen/airfields.py | 66 +++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/gen/airfields.py b/gen/airfields.py index 35b59e4d..99835730 100644 --- a/gen/airfields.py +++ b/gen/airfields.py @@ -655,7 +655,7 @@ AIRFIELD_DATA = { elevation=55, runway_length=8115, vor=("ADA", MHz(112, 700)), - atc=AtcData(MHz(4, 225), MHz(39, 350), MHz(121, 100), MHz(250, 900)), + atc=AtcData(MHz(4, 275), MHz(39, 450), MHz(121, 100), MHz(251, 0)), ils={ "05": ("IADA", MHz(108, 700)), }, @@ -668,7 +668,7 @@ AIRFIELD_DATA = { tacan=TacanChannel(21, TacanBand.X), tacan_callsign="DAN", vor=("DAN", MHz(108, 400)), - atc=AtcData(MHz(3, 850), MHz(38, 600), MHz(129, 400), MHz(360, 100)), + atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(122, 100), MHz(360, 100)), ils={ "50": ("IDAN", MHz(109, 300)), "23": ("DANM", MHz(111, 700)), @@ -679,7 +679,7 @@ AIRFIELD_DATA = { icao="OS71", elevation=1614, runway_length=4648, - atc=AtcData(MHz(4, 125), MHz(39, 150), MHz(120, 600), MHz(250, 700)), + atc=AtcData(MHz(4, 175), MHz(39, 250), MHz(120, 600), MHz(250, 800)), ), "Hatay": AirfieldData( theater="Syria", @@ -687,7 +687,7 @@ AIRFIELD_DATA = { elevation=253, runway_length=9052, vor=("HTY", MHz(112, 500)), - atc=AtcData(MHz(3, 825), MHz(38, 550), MHz(128, 500), MHz(250, 150)), + atc=AtcData(MHz(3, 875), MHz(38, 650), MHz(128, 500), MHz(250, 250)), ils={ "22": ("IHTY", MHz(108, 150)), "04": ("IHAT", MHz(108, 900)), @@ -698,25 +698,21 @@ AIRFIELD_DATA = { icao="OS66", elevation=1200, runway_length=6662, - atc=AtcData(MHz(4, 275), MHz(39, 450), MHz(120, 500), MHz(251)), + atc=AtcData(MHz(4, 325), MHz(39, 550), MHz(120, 500), MHz(251, 100)), ), "Aleppo": AirfieldData( theater="Syria", icao="OSAP", elevation=1253, runway_length=8332, - atc=AtcData(MHz(4, 150), MHz(39, 200), MHz(119, 100), MHz(250, 750)), - ils={ - "50": ("IDAN", MHz(109, 300)), - "23": ("DANM", MHz(111, 700)), - }, + atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(119, 100), MHz(250, 850)) ), "Jirah": AirfieldData( theater="Syria", icao="OS62", elevation=1170, runway_length=9090, - atc=AtcData(MHz(3, 875), MHz(38, 650), MHz(118, 100), MHz(250, 200)), + atc=AtcData(MHz(3, 925), MHz(38, 750), MHz(118, 100), MHz(250, 300)), ), "Taftanaz": AirfieldData( theater="Syria", @@ -729,14 +725,14 @@ AIRFIELD_DATA = { icao="OS59", elevation=1083, runway_length=9036, - atc=AtcData(MHz(4, 350), MHz(39, 600), MHz(118, 500), MHz(251, 150)), + atc=AtcData(MHz(4, 500), MHz(39, 900), MHz(122, 800), MHz(251, 450)), ), "Abu al-Dahur": AirfieldData( theater="Syria", icao="OS57", elevation=820, runway_length=8728, - atc=AtcData(MHz(3, 950), MHz(38, 800), MHz(122, 200), MHz(250, 350)), + atc=AtcData(MHz(4, 0), MHz(38, 900), MHz(122, 200), MHz(250, 450)), ), "Bassel Al-Assad": AirfieldData( theater="Syria", @@ -744,7 +740,7 @@ AIRFIELD_DATA = { elevation=93, runway_length=7305, vor=("LTK", MHz(114, 800)), - atc=AtcData(MHz(4), MHz(38, 900), MHz(118, 100), MHz(250, 450)), + atc=AtcData(MHz(4, 50), MHz(39, 0), MHz(118, 100), MHz(250, 550)), ils={ "17": ("IBA", MHz(109, 100)), }, @@ -754,28 +750,28 @@ AIRFIELD_DATA = { icao="OS58", elevation=983, runway_length=7957, - atc=AtcData(MHz(3, 800), MHz(38, 500), MHz(118, 50), MHz(250, 100)), + atc=AtcData(MHz(3, 850), MHz(38, 600), MHz(118, 50), MHz(250, 200)), ), "Rene Mouawad": AirfieldData( theater="Syria", icao="OLKA", elevation=14, runway_length=8614, - atc=AtcData(MHz(4, 325), MHz(39, 550), MHz(129, 500), MHz(251, 100)), + atc=AtcData(MHz(4, 375), MHz(39, 650), MHz(121, 0), MHz(251, 200)), ), "Al Quasayr": AirfieldData( theater="Syria", icao="OS70", elevation=1729, runway_length=8585, - atc=AtcData(MHz(4, 400), MHz(39, 700), MHz(119, 200), MHz(251, 250)), + atc=AtcData(MHz(4, 550), MHz(40, 0), MHz(119, 200), MHz(251, 550)), ), "Palmyra": AirfieldData( theater="Syria", icao="OSPR", elevation=1267, runway_length=8704, - atc=AtcData(MHz(4, 175), MHz(39, 250), MHz(121, 900), MHz(250, 800)), + atc=AtcData(MHz(4, 225), MHz(39, 350), MHz(121, 900), MHz(250, 900)), ), "Wujah Al Hajar": AirfieldData( theater="Syria", @@ -783,14 +779,14 @@ AIRFIELD_DATA = { elevation=619, runway_length=4717, vor=("CAK", MHz(116, 200)), - atc=AtcData(MHz(4, 425), MHz(39, 750), MHz(121, 500), MHz(251, 300)), + atc=AtcData(MHz(4, 575), MHz(40, 50), MHz(121, 500), MHz(251, 600)), ), "An Nasiriyah": AirfieldData( theater="Syria", icao="OS64", elevation=2746, runway_length=8172, - atc=AtcData(MHz(4, 450), MHz(39, 800), MHz(122, 300), MHz(251, 350)), + atc=AtcData(MHz(4, 600), MHz(40, 100), MHz(122, 300), MHz(251, 650)), ), "Rayak": AirfieldData( theater="Syria", @@ -798,7 +794,7 @@ AIRFIELD_DATA = { elevation=2934, runway_length=8699, vor=("HTY", MHz(124, 400)), - atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(124, 400), MHz(251, 50)), + atc=AtcData(MHz(4, 350), MHz(39, 600), MHz(124, 400), MHz(251, 150)), ), "Beirut-Rafic Hariri": AirfieldData( theater="Syria", @@ -806,7 +802,7 @@ AIRFIELD_DATA = { elevation=39, runway_length=9463, vor=("KAD", MHz(112, 600)), - atc=AtcData(MHz(4, 475), MHz(39, 850), MHz(118, 900), MHz(251, 400)), + atc=AtcData(MHz(4, 675), MHz(40, 250), MHz(118, 900), MHz(251, 800)), ils={ "17": ("BIL", MHz(109, 500)), }, @@ -816,32 +812,32 @@ AIRFIELD_DATA = { icao="OS61", elevation=2066, runway_length=8902, - atc=AtcData(MHz(4, 550), MHz(40), MHz(120, 300), MHz(251, 550)), + atc=AtcData(MHz(4, 750), MHz(40, 400), MHz(120, 300), MHz(251, 950)), ), "Marj as Sultan North": AirfieldData( theater="Syria", elevation=2007, runway_length=268, - atc=AtcData(MHz(4, 25), MHz(38, 950), MHz(122, 700), MHz(250, 500)), + atc=AtcData(MHz(4, 75), MHz(38, 50), MHz(122, 700), MHz(250, 600)), ), "Marj as Sultan South": AirfieldData( theater="Syria", elevation=2007, runway_length=166, - atc=AtcData(MHz(4, 525), MHz(39, 950), MHz(122, 900), MHz(251, 500)), + atc=AtcData(MHz(4, 725), MHz(40, 350), MHz(122, 900), MHz(251, 900)), ), "Mezzeh": AirfieldData( theater="Syria", icao="OS67", elevation=2355, runway_length=7522, - atc=AtcData(MHz(4, 100), MHz(39, 100), MHz(120, 700), MHz(250, 650)), + atc=AtcData(MHz(4, 150), MHz(39, 200), MHz(120, 700), MHz(250, 750)), ), "Qabr as Sitt": AirfieldData( theater="Syria", elevation=2134, runway_length=489, - atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(122, 600), MHz(250, 850)), + atc=AtcData(MHz(4, 250), MHz(39, 400), MHz(122, 600), MHz(250, 950)), ), "Damascus": AirfieldData( theater="Syria", @@ -849,7 +845,7 @@ AIRFIELD_DATA = { elevation=2007, runway_length=11423, vor=("DAM", MHz(116)), - atc=AtcData(MHz(4, 500), MHz(39, 900), MHz(118, 500), MHz(251, 450)), + atc=AtcData(MHz(4, 700), MHz(40, 300), MHz(118, 500), MHz(251, 850)), ils={ "24": ("IDA", MHz(109, 900)), }, @@ -859,42 +855,42 @@ AIRFIELD_DATA = { icao="OS63", elevation=2160, runway_length=7576, - atc=AtcData(MHz(4, 50), MHz(39), MHz(120, 800), MHz(250, 550)), + atc=AtcData(MHz(4, 100), MHz(39, 100), MHz(120, 800), MHz(250, 6550)), ), "Kiryat Shmona": AirfieldData( theater="Syria", icao="LLKS", elevation=328, runway_length=3258, - atc=AtcData(MHz(3, 975), MHz(38, 850), MHz(118, 400), MHz(250, 400)), + atc=AtcData(MHz(4, 25), MHz(38, 950), MHz(118, 400), MHz(250, 500)), ), "Khalkhalah": AirfieldData( theater="Syria", icao="OS69", elevation=2337, runway_length=8248, - atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(122, 500), MHz(250, 250)), + atc=AtcData(MHz(3, 950), MHz(38, 800), MHz(122, 500), MHz(250, 350)), ), "Haifa": AirfieldData( theater="Syria", icao="LLHA", elevation=19, runway_length=3253, - atc=AtcData(MHz(3, 775), MHz(38, 450), MHz(127, 800), MHz(250, 50)), + atc=AtcData(MHz(3, 825), MHz(38, 550), MHz(127, 800), MHz(250, 150)), ), "Ramat David": AirfieldData( theater="Syria", icao="LLRD", elevation=105, runway_length=7037, - atc=AtcData(MHz(4, 250), MHz(39, 400), MHz(118, 600), MHz(250, 950)), + atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(118, 600), MHz(251, 20)), ), "Megiddo": AirfieldData( theater="Syria", icao="LLMG", elevation=180, runway_length=6098, - atc=AtcData(MHz(4, 75), MHz(39, 50), MHz(119, 900), MHz(250, 600)), + atc=AtcData(MHz(4, 125), MHz(39, 150), MHz(119, 900), MHz(250, 700)), ), "Eyn Shemer": AirfieldData( theater="Syria", @@ -908,7 +904,7 @@ AIRFIELD_DATA = { icao="OJMF", elevation=2204, runway_length=8595, - atc=AtcData(MHz(3, 925), MHz(38, 750), MHz(118, 300), MHz(250, 300)), + atc=AtcData(MHz(3, 975), MHz(38, 850), MHz(118, 300), MHz(250, 400)), ), "Tha'lah": AirfieldData( theater="Syria", From 35f49d9bc096bae072dcc3c60acac3b98bcdae31 Mon Sep 17 00:00:00 2001 From: Khopa Date: Wed, 21 Apr 2021 22:30:08 +0200 Subject: [PATCH 039/438] Fixed airfields frequency on Persian Gulf --- gen/airfields.py | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/gen/airfields.py b/gen/airfields.py index 99835730..360e043b 100644 --- a/gen/airfields.py +++ b/gen/airfields.py @@ -402,7 +402,7 @@ AIRFIELD_DATA = { tacan=TacanChannel(96, TacanBand.X), tacan_callsign="MA", vor=("MA", MHz(114, 900)), - atc=AtcData(MHz(4, 250), MHz(39, 400), MHz(126, 500), MHz(251, 000)), + atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(126, 500), MHz(251, 100)), ils={ "13": ("MMA", MHz(111, 100)), "31": ("IMA", MHz(109, 100)), @@ -414,7 +414,7 @@ AIRFIELD_DATA = { elevation=11, runway_length=6808, vor=("ALB", MHz(114, 0)), - atc=AtcData(MHz(4, 25), MHz(38, 950), MHz(119, 900), MHz(250, 550)), + atc=AtcData(MHz(4, 75), MHz(39, 50), MHz(119, 900), MHz(250, 600)), ), "Sas Al Nakheel": AirfieldData( theater="Persian Gulf", @@ -422,7 +422,7 @@ AIRFIELD_DATA = { elevation=9, runway_length=5387, vor=("SAS", MHz(128, 930)), - atc=AtcData(MHz(3, 975), MHz(38, 850), MHz(128, 900), MHz(250, 450)), + atc=AtcData(MHz(4, 0), MHz(38, 900), MHz(128, 900), MHz(250, 450)), ), "Abu Dhabi Intl": AirfieldData( theater="Persian Gulf", @@ -430,7 +430,7 @@ AIRFIELD_DATA = { elevation=91, runway_length=12817, vor=("ADV", MHz(114, 250)), - atc=AtcData(MHz(4, 000), MHz(38, 900), MHz(119, 200), MHz(250, 500)), + atc=AtcData(MHz(4, 50), MHz(39, 0), MHz(119, 200), MHz(250, 550)), ), "Al Ain Intl": AirfieldData( theater="Persian Gulf", @@ -438,14 +438,14 @@ AIRFIELD_DATA = { elevation=813, runway_length=11267, vor=("ALN", MHz(112, 600)), - atc=AtcData(MHz(4, 75), MHz(39, 50), MHz(119, 850), MHz(250, 650)), + atc=AtcData(MHz(4, 125), MHz(39, 150), MHz(119, 850), MHz(250, 700)), ), "Al Maktoum Intl": AirfieldData( theater="Persian Gulf", icao="OMDW", elevation=123, runway_length=11500, - atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(118, 650), MHz(251, 100)), + atc=AtcData(MHz(4, 350), MHz(39, 600), MHz(118, 600), MHz(251, 200)), ils={ "30": ("IJWA", MHz(109, 750)), "12": ("IMA", MHz(111, 750)), @@ -458,7 +458,7 @@ AIRFIELD_DATA = { runway_length=11865, tacan=TacanChannel(99, TacanBand.X), tacan_callsign="MIN", - atc=AtcData(MHz(3, 800), MHz(38, 500), MHz(121, 800), MHz(250, 100)), + atc=AtcData(MHz(3, 800), MHz(38, 500), MHz(118, 550), MHz(250, 100)), ils={ "27": ("IMNR", MHz(110, 750)), "9": ("IMNW", MHz(110, 700)), @@ -469,7 +469,7 @@ AIRFIELD_DATA = { icao="OMDB", elevation=16, runway_length=11018, - atc=AtcData(MHz(4, 275), MHz(39, 450), MHz(118, 750), MHz(251, 50)), + atc=AtcData(MHz(4, 325), MHz(39, 550), MHz(118, 750), MHz(251, 150)), ils={ "30": ("IDBL", MHz(110, 900)), "12": ("IDBR", MHz(110, 100)), @@ -480,7 +480,7 @@ AIRFIELD_DATA = { icao="OMSJ", elevation=98, runway_length=10535, - atc=AtcData(MHz(3, 850), MHz(38, 600), MHz(118, 600), MHz(252, 200)), + atc=AtcData(MHz(3, 850), MHz(38, 600), MHz(118, 600), MHz(250, 200)), ils={ "30": ("ISHW", MHz(111, 950)), "12": ("ISRE", MHz(108, 550)), @@ -492,18 +492,18 @@ AIRFIELD_DATA = { elevation=60, runway_length=9437, vor=("FJV", MHz(113, 800)), - atc=AtcData(MHz(4, 325), MHz(39, 550), MHz(124, 600), MHz(251, 150)), + atc=AtcData(MHz(4, 375), MHz(39, 650), MHz(124, 600), MHz(251, 250)), ils={ "29": ("IFJR", MHz(111, 500)), }, ), - "Ras AL Khaimah Intl": AirfieldData( + "Ras Al Khaimah Intl": AirfieldData( theater="Persian Gulf", icao="OMRK", elevation=70, runway_length=8406, vor=("OMRK", MHz(113, 600)), - atc=AtcData(MHz(4, 150), MHz(39, 200), MHz(121, 600), MHz(250, 800)), + atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(121, 600), MHz(250, 900)), ), "Khasab": AirfieldData( theater="Persian Gulf", @@ -516,7 +516,8 @@ AIRFIELD_DATA = { }, ), "Sir Abu Nuayr": AirfieldData( - theater="Persian Gulf", icao="OMSN", elevation=25, runway_length=2229 + theater="Persian Gulf", icao="OMSN", elevation=25, runway_length=2229, + atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(118, 0), MHz(250, 800)), ), "Sirri Island": AirfieldData( theater="Persian Gulf", @@ -539,7 +540,7 @@ AIRFIELD_DATA = { elevation=15, runway_length=1481, tacan=TacanChannel(89, TacanBand.X), - tacan_callsign="KCK", + tacan_callsign="KCK" ), "Tunb Island AFB": AirfieldData( theater="Persian Gulf", @@ -561,7 +562,7 @@ AIRFIELD_DATA = { elevation=26, runway_length=6842, vor=("KHM", MHz(116, 300)), - atc=AtcData(MHz(3, 825), MHz(38, 550), MHz(118, 50), MHz(250, 150)), + atc=AtcData(MHz(4, 25), MHz(38, 950), MHz(118, 150), MHz(250, 500)), ), "Bandar Lengeh": AirfieldData( theater="Persian Gulf", @@ -569,7 +570,7 @@ AIRFIELD_DATA = { elevation=80, runway_length=7625, vor=("LEN", MHz(114, 800)), - atc=AtcData(MHz(4, 225), MHz(39, 350), MHz(121, 700), MHz(250, 950)), + atc=AtcData(MHz(4, 275), MHz(39, 450), MHz(121, 700), MHz(251, 50)), ), "Kish Intl": AirfieldData( theater="Persian Gulf", @@ -578,7 +579,7 @@ AIRFIELD_DATA = { runway_length=10617, tacan=TacanChannel(112, TacanBand.X), tacan_callsign="KIH", - atc=AtcData(MHz(4, 50), MHz(39, 000), MHz(121, 650), MHz(250, 600)), + atc=AtcData(MHz(4, 100), MHz(39, 100), MHz(121, 650), MHz(250, 650)), ), "Lavan Island": AirfieldData( theater="Persian Gulf", @@ -586,7 +587,7 @@ AIRFIELD_DATA = { elevation=75, runway_length=8234, vor=("LVA", MHz(116, 850)), - atc=AtcData(MHz(4, 100), MHz(39, 100), MHz(128, 550), MHz(250, 700)), + atc=AtcData(MHz(4, 150), MHz(39, 200), MHz(128, 550), MHz(250, 750)), ), "Lar": AirfieldData( theater="Persian Gulf", @@ -603,7 +604,7 @@ AIRFIELD_DATA = { runway_length=7300, tacan=TacanChannel(47, TacanBand.X), tacan_callsign="HDR", - atc=AtcData(MHz(4, 350), MHz(39, 600), MHz(123, 150), MHz(251, 200)), + atc=AtcData(MHz(4, 400), MHz(39, 700), MHz(123, 150), MHz(251, 300)), ils={ "8": ("IBHD", MHz(108, 900)), }, @@ -616,7 +617,7 @@ AIRFIELD_DATA = { tacan=TacanChannel(78, TacanBand.X), tacan_callsign="BND", vor=("BND", MHz(117, 200)), - atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(118, 100), MHz(250, 900)), + atc=AtcData(MHz(4, 250), MHz(39, 401), MHz(118, 100), MHz(251, 0)), ils={ "21": ("IBND", MHz(333, 800)), }, @@ -636,7 +637,7 @@ AIRFIELD_DATA = { tacan=TacanChannel(97, TacanBand.X), tacan_callsign="KER", vor=("KER", MHz(112, 0)), - atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(118, 250), MHz(250, 300)), + atc=AtcData(MHz(3, 925), MHz(38, 750), MHz(118, 250), MHz(250, 300)), ), "Shiraz Intl": AirfieldData( theater="Persian Gulf", @@ -646,7 +647,7 @@ AIRFIELD_DATA = { tacan=TacanChannel(94, TacanBand.X), tacan_callsign="SYZ1", vor=("SYZ", MHz(112, 0)), - atc=AtcData(MHz(3, 925), MHz(38, 750), MHz(121, 900), MHz(250, 350)), + atc=AtcData(MHz(3, 950), MHz(38, 800), MHz(121, 900), MHz(250, 350)), ), # Syria Map "Adana Sakirpasa": AirfieldData( From 76efcca64b521b7158c601c7df0f668241428543 Mon Sep 17 00:00:00 2001 From: Khopa Date: Wed, 21 Apr 2021 22:38:08 +0200 Subject: [PATCH 040/438] Fixed error with Ramat David frequency (typo) --- gen/airfields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/airfields.py b/gen/airfields.py index 360e043b..58d98d4e 100644 --- a/gen/airfields.py +++ b/gen/airfields.py @@ -884,7 +884,7 @@ AIRFIELD_DATA = { icao="LLRD", elevation=105, runway_length=7037, - atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(118, 600), MHz(251, 20)), + atc=AtcData(MHz(4, 300), MHz(39, 500), MHz(118, 600), MHz(251, 50)), ), "Megiddo": AirfieldData( theater="Syria", From e39fd53727ab051f7d1fd8f612ddd63b806bc244 Mon Sep 17 00:00:00 2001 From: Khopa Date: Wed, 21 Apr 2021 22:54:48 +0200 Subject: [PATCH 041/438] Fixed Lint issue --- gen/airfields.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gen/airfields.py b/gen/airfields.py index 58d98d4e..2903e72d 100644 --- a/gen/airfields.py +++ b/gen/airfields.py @@ -516,7 +516,10 @@ AIRFIELD_DATA = { }, ), "Sir Abu Nuayr": AirfieldData( - theater="Persian Gulf", icao="OMSN", elevation=25, runway_length=2229, + theater="Persian Gulf", + icao="OMSN", + elevation=25, + runway_length=2229, atc=AtcData(MHz(3, 900), MHz(38, 700), MHz(118, 0), MHz(250, 800)), ), "Sirri Island": AirfieldData( @@ -540,7 +543,7 @@ AIRFIELD_DATA = { elevation=15, runway_length=1481, tacan=TacanChannel(89, TacanBand.X), - tacan_callsign="KCK" + tacan_callsign="KCK", ), "Tunb Island AFB": AirfieldData( theater="Persian Gulf", @@ -706,7 +709,7 @@ AIRFIELD_DATA = { icao="OSAP", elevation=1253, runway_length=8332, - atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(119, 100), MHz(250, 850)) + atc=AtcData(MHz(4, 200), MHz(39, 300), MHz(119, 100), MHz(250, 850)), ), "Jirah": AirfieldData( theater="Syria", From 8e361a87769c72c6be36c11ee08b4ed007af8cb7 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 21 Apr 2021 17:14:45 -0700 Subject: [PATCH 042/438] Fix missing index valid check in transfer menu. Fixes https://github.com/Khopa/dcs_liberation/issues/1006 --- qt_ui/windows/PendingTransfersDialog.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qt_ui/windows/PendingTransfersDialog.py b/qt_ui/windows/PendingTransfersDialog.py index ac671c86..e49c5dca 100644 --- a/qt_ui/windows/PendingTransfersDialog.py +++ b/qt_ui/windows/PendingTransfersDialog.py @@ -195,6 +195,8 @@ class PendingTransfersDialog(QDialog): self.transfer_model.cancel_transfer_at_index(self.transfer_list.currentIndex()) def can_cancel(self, index: QModelIndex) -> bool: + if not index.isValid(): + return False return self.transfer_model.transfer_at_index(index).player def on_selection_changed( From bf71351e6d3f0ade9bc731879a6258d8b912f6d7 Mon Sep 17 00:00:00 2001 From: SpaceEnthusiast <29228440+SpaceEnthusiast@users.noreply.github.com> Date: Wed, 21 Apr 2021 22:45:44 -0400 Subject: [PATCH 043/438] Canadian, Australian, and Spanish Hornets now use proper liveries --- resources/factions/australia_2005.json | 8 +++++++- resources/factions/canada_2005.json | 8 +++++++- resources/factions/spain_1990.json | 19 ++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/resources/factions/australia_2005.json b/resources/factions/australia_2005.json index 00cec1de..efca64d4 100644 --- a/resources/factions/australia_2005.json +++ b/resources/factions/australia_2005.json @@ -61,5 +61,11 @@ "ArleighBurkeGroupGenerator" ], "has_jtac": true, - "jtac_unit": "MQ_9_Reaper" + "jtac_unit": "MQ_9_Reaper", + "liveries_overrides": { + "FA_18C_hornet": [ + "Australian 75th Squadron", + "Australian 77th Squadron" + ] + } } diff --git a/resources/factions/canada_2005.json b/resources/factions/canada_2005.json index 0775dff5..7d394842 100644 --- a/resources/factions/canada_2005.json +++ b/resources/factions/canada_2005.json @@ -59,5 +59,11 @@ "ArleighBurkeGroupGenerator" ], "has_jtac": true, - "jtac_unit": "MQ_9_Reaper" + "jtac_unit": "MQ_9_Reaper", + "liveries_overrides": { + "FA_18C_hornet": [ + "Canada 409th Squadron", + "Canada 425th Squadron" + ] + } } diff --git a/resources/factions/spain_1990.json b/resources/factions/spain_1990.json index 344ea866..6dd974be 100644 --- a/resources/factions/spain_1990.json +++ b/resources/factions/spain_1990.json @@ -64,5 +64,22 @@ "OliverHazardPerryGroupGenerator" ], "has_jtac": true, - "jtac_unit": "MQ_9_Reaper" + "jtac_unit": "MQ_9_Reaper", + "liveries_overrides": { + "FA_18C_hornet": [ + "Spain 111th Escuadron C.15-73", + "Spain 111th Escuadron C.15-88", + "Spain 121th Escuadron C.15-45", + "Spain 121th Escuadron C.15-50", + "Spain 121th Escuadron C.15-60", + "Spain 151th Escuadron C.15-14", + "Spain 151th Escuadron C.15-18", + "Spain 151th Escuadron C.15-23", + "Spain 151th Escuadron C.15-24", + "Spain 211th Escuadron C.15-76", + "Spain 211th Escuadron C.15-77", + "Spain 462th Escuadron C.15-79", + "Spain 462th Escuadron C.15-90" + ] + } } From 60b9ae0a709dbd62d869bca529548d9175a3811d Mon Sep 17 00:00:00 2001 From: SpaceEnthusiast <29228440+SpaceEnthusiast@users.noreply.github.com> Date: Wed, 21 Apr 2021 23:43:31 -0400 Subject: [PATCH 044/438] Added Canada with C130 Faction --- resources/factions/canada_2005_c130.json | 75 ++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 resources/factions/canada_2005_c130.json diff --git a/resources/factions/canada_2005_c130.json b/resources/factions/canada_2005_c130.json new file mode 100644 index 00000000..9620dc24 --- /dev/null +++ b/resources/factions/canada_2005_c130.json @@ -0,0 +1,75 @@ +{ + "country": "Canada", + "name": "Canada 2005 (With C-130)", + "authors": "Khopa, SpaceEnthusiast", + "description": "

Canada in the 2000s, an F/A-18C Hornet focused faction.

", + "aircrafts": [ + "FA_18C_hornet", + "UH_1H", + "AH_1W", + "Hercules" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Leopard_1A3", + "MBT_Leopard_2", + "IFV_LAV_25", + "APC_M113", + "IFV_Warrior", + "SAM_Avenger__Stinger" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Truck_M818_6x6" + ], + "infantry_units": [ + "Infantry_M4", + "Infantry_M249", + "MANPADS_Stinger" + ], + "air_defenses": [ + "AvengerGenerator", + "HawkGenerator" + ], + "ewrs": [ + "HawkEwrGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + ], + "destroyers": [ + "DDG_Arleigh_Burke_IIa" + ], + "cruisers": [ + "CG_Ticonderoga" + ], + "requirements": { + "C-130J-30 Super Hercules Mod by Anubis": "https://forums.eagle.ru/topic/252075-dcs-super-hercules-mod-by-anubis/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper", + "liveries_overrides": { + "FA_18C_hornet": [ + "Canada 409th Squadron", + "Canada 425th Squadron" + ], + "Hercules": [ + "Royal Canadian AF CC-130J" + ] + } +} From d6c1550a1d15319bb2a577a22a5b028d0ddd9c3e Mon Sep 17 00:00:00 2001 From: SpaceEnthusiast <29228440+SpaceEnthusiast@users.noreply.github.com> Date: Wed, 21 Apr 2021 23:58:18 -0400 Subject: [PATCH 045/438] Added Australia with C130 Faction --- resources/factions/australia_2005_c130.json | 74 +++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 resources/factions/australia_2005_c130.json diff --git a/resources/factions/australia_2005_c130.json b/resources/factions/australia_2005_c130.json new file mode 100644 index 00000000..ae5ecb18 --- /dev/null +++ b/resources/factions/australia_2005_c130.json @@ -0,0 +1,74 @@ +{ + "country": "Australia", + "name": "Australia 2005 (With C-130)", + "authors": "Khopa, SpaceEnthusiast", + "description": "

The Australian army in 2005.

Some units might not be accurate, but were picked to represent at best this army.

", + "aircrafts": [ + "FA_18C_hornet", + "UH_1H", + "SH_60B", + "AH_1W", + "Hercules" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_M1A2_Abrams", + "MBT_Leopard_1A3", + "APC_M113", + "IFV_LAV_25", + "IFV_Warrior" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Truck_M818_6x6" + ], + "infantry_units": [ + "Infantry_M4", + "Infantry_M249", + "MANPADS_Stinger" + ], + "air_defenses": [ + "HawkGenerator", + "RapierGenerator" + ], + "ewrs": [ + "HawkEwrGenerator" + ], + "aircraft_carrier": [ + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "DDG_Arleigh_Burke_IIa" + ], + "cruisers": [ + ], + "requirements": { + "C-130J-30 Super Hercules Mod by Anubis": "https://forums.eagle.ru/topic/252075-dcs-super-hercules-mod-by-anubis/" + }, + "carrier_names": [ + ], + "helicopter_carrier_names": [ + "HMAS Canberra", + "HMAS Adelaide" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper", + "liveries_overrides": { + "FA_18C_hornet": [ + "Australian 75th Squadron", + "Australian 77th Squadron" + ] + } +} From 481f195725ff46449827cd91ae08070158063260 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 21 Apr 2021 17:13:35 -0700 Subject: [PATCH 046/438] Airlift support. UI isn't finished. Bulk transfers where the player doesn't care what aircraft get used work (though they're chosen with no thought at all), but being able to plan your own airlift flight isn't here yet. Cargo planes are not implemented yet. No way to view the cargo of a flight (will come with the cargo flight planning UI). The airlift flight/package creation should probably be moved out of the UI and into the game code. AI doesn't use these yet. https://github.com/Khopa/dcs_liberation/issues/825 --- game/db.py | 1 + game/game.py | 2 +- game/transfers.py | 100 ++++++++++++--- gen/ato.py | 3 + gen/flights/flight.py | 9 +- gen/flights/flightplan.py | 97 +++++++++++++- gen/flights/waypointbuilder.py | 54 +++++++- qt_ui/models.py | 74 ++++++----- qt_ui/widgets/ato.py | 1 - qt_ui/windows/PendingTransfersDialog.py | 13 +- .../windows/basemenu/NewUnitTransferDialog.py | 119 ++++++++++++++++-- qt_ui/windows/mission/QPackageDialog.py | 6 +- 12 files changed, 406 insertions(+), 73 deletions(-) diff --git a/game/db.py b/game/db.py index d1cd9029..9cc81055 100644 --- a/game/db.py +++ b/game/db.py @@ -1174,6 +1174,7 @@ EXPANDED_TASK_PAYLOAD_OVERRIDE = { "SWEEP": ("CAP HEAVY", "CAP"), "OCA_RUNWAY": ("RUNWAY_ATTACK", "RUNWAY_STRIKE", "STRIKE"), "OCA_AIRCRAFT": ("OCA", "CAS MAVERICK F", "CAS"), + "TRANSPORT": (), } PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = { diff --git a/game/game.py b/game/game.py index f59e140f..dc1d6c2e 100644 --- a/game/game.py +++ b/game/game.py @@ -122,7 +122,7 @@ class Game: self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints) - self.transfers = PendingTransfers() + self.transfers = PendingTransfers(self) self.sanitize_sides() diff --git a/game/transfers.py b/game/transfers.py index fe84d312..64e2f830 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -3,16 +3,17 @@ from __future__ import annotations import logging from collections import defaultdict from dataclasses import dataclass, field +from functools import singledispatchmethod from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type from dcs.unittype import VehicleType if TYPE_CHECKING: - pass + from game import Game from game.theater import ControlPoint, MissionTarget from game.theater.supplyroutes import SupplyRoute from gen.naming import namegen -from gen.flights.flight import FlightType +from gen.flights.flight import Flight, FlightType @dataclass @@ -31,14 +32,18 @@ class TransferOrder: #: True if the transfer order belongs to the player. player: bool + #: The units being transferred. + units: Dict[Type[VehicleType], int] + + @property + def description(self) -> str: + raise NotImplementedError + @dataclass class RoadTransferOrder(TransferOrder): """A transfer order that moves units by road.""" - #: The units being transferred. - units: Dict[Type[VehicleType], int] - #: The current position of the group being transferred. Groups move one control #: point a turn through the supply line. position: ControlPoint = field(init=False) @@ -53,6 +58,26 @@ class RoadTransferOrder(TransferOrder): def next_stop(self) -> ControlPoint: return self.path()[0] + @property + def description(self) -> str: + path = self.path() + if len(path) == 1: + turns = "1 turn" + else: + turns = f"{len(path)} turns" + return f"Currently at {self.position}. Arrives at destination in {turns}." + + +@dataclass +class AirliftOrder(TransferOrder): + """A transfer order that moves units by cargo planes and helicopters.""" + + flight: Flight + + @property + def description(self) -> str: + return "Airlift" + class Convoy(MissionTarget): def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: @@ -159,27 +184,54 @@ class ConvoyMap: class PendingTransfers: - def __init__(self) -> None: + def __init__(self, game: Game) -> None: + self.game = game self.convoys = ConvoyMap() - self.pending_transfers: List[RoadTransferOrder] = [] + self.pending_transfers: List[TransferOrder] = [] - def __iter__(self) -> Iterator[RoadTransferOrder]: + def __iter__(self) -> Iterator[TransferOrder]: yield from self.pending_transfers @property def pending_transfer_count(self) -> int: return len(self.pending_transfers) - def transfer_at_index(self, index: int) -> RoadTransferOrder: + def transfer_at_index(self, index: int) -> TransferOrder: return self.pending_transfers[index] - def new_transfer(self, transfer: RoadTransferOrder) -> None: - transfer.origin.base.commit_losses(transfer.units) - self.pending_transfers.append(transfer) + def index_of_transfer(self, transfer: TransferOrder) -> int: + return self.pending_transfers.index(transfer) + + # TODO: Move airlift arrangements here? + @singledispatchmethod + def arrange_transport(self, transfer) -> None: + pass + + @arrange_transport.register + def _arrange_transport_road(self, transfer: RoadTransferOrder) -> None: self.convoys.add(transfer) - def cancel_transfer(self, transfer: RoadTransferOrder) -> None: + def new_transfer(self, transfer: TransferOrder) -> None: + transfer.origin.base.commit_losses(transfer.units) + self.pending_transfers.append(transfer) + self.arrange_transport(transfer) + + @singledispatchmethod + def cancel_transport(self, transfer) -> None: + pass + + @cancel_transport.register + def _cancel_transport_air(self, transfer: AirliftOrder) -> None: + flight = transfer.flight + flight.package.remove_flight(flight) + self.game.aircraft_inventory.return_from_flight(flight) + + @cancel_transport.register + def _cancel_transport_road(self, transfer: RoadTransferOrder) -> None: self.convoys.remove(transfer) + + def cancel_transfer(self, transfer: TransferOrder) -> None: + self.cancel_transport(transfer) self.pending_transfers.remove(transfer) transfer.origin.base.commision_units(transfer.units) @@ -194,9 +246,27 @@ class PendingTransfers: def rebuild_convoys(self) -> None: self.convoys.disband_all() for transfer in self.pending_transfers: - self.convoys.add(transfer) + self.arrange_transport(transfer) - def perform_transfer(self, transfer: RoadTransferOrder) -> bool: + @singledispatchmethod + def perform_transfer(self, transfer) -> bool: + raise NotImplementedError + + @perform_transfer.register + def _perform_transfer_air(self, transfer: AirliftOrder) -> bool: + if transfer.player != transfer.destination.captured: + logging.info( + f"Transfer destination {transfer.destination} was captured. Cancelling " + "transport." + ) + transfer.origin.base.commision_units(transfer.units) + return True + + transfer.destination.base.commision_units(transfer.units) + return True + + @perform_transfer.register + def _perform_transfer_road(self, transfer: RoadTransferOrder) -> bool: # TODO: Can be improved to use the convoy map. # The convoy map already has a lot of the data that we're recomputing here. if transfer.player != transfer.destination.captured: diff --git a/gen/ato.py b/gen/ato.py index da58430f..614641fb 100644 --- a/gen/ato.py +++ b/gen/ato.py @@ -8,6 +8,8 @@ example, the package to strike an enemy airfield may contain an escort flight, a SEAD flight, and the strike aircraft themselves. CAP packages may contain only the single CAP flight. """ +from __future__ import annotations + import logging from collections import defaultdict from dataclasses import dataclass, field @@ -172,6 +174,7 @@ class Package: FlightType.OCA_RUNWAY, FlightType.BAI, FlightType.DEAD, + FlightType.TRANSPORT, FlightType.SEAD, FlightType.TARCAP, FlightType.BARCAP, diff --git a/gen/flights/flight.py b/gen/flights/flight.py index bfcf9f28..00b7523b 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -1,6 +1,5 @@ from __future__ import annotations -from collections import defaultdict from datetime import timedelta from enum import Enum from typing import Dict, List, Optional, TYPE_CHECKING, Type @@ -15,6 +14,7 @@ from game.theater.controlpoint import ControlPoint, MissionTarget from game.utils import Distance, meters if TYPE_CHECKING: + from game.transfers import AirliftOrder from gen.ato import Package from gen.flights.flightplan import FlightPlan @@ -43,6 +43,7 @@ class FlightType(Enum): OCA_RUNWAY = "OCA/Runway" OCA_AIRCRAFT = "OCA/Aircraft" AEWC = "AEW&C" + TRANSPORT = "Transport" def __str__(self) -> str: return self.value @@ -75,6 +76,8 @@ class FlightWaypointType(Enum): DIVERT = 23 INGRESS_OCA_RUNWAY = 24 INGRESS_OCA_AIRCRAFT = 25 + PICKUP = 26 + DROP_OFF = 27 class FlightWaypoint: @@ -164,6 +167,7 @@ class Flight: arrival: ControlPoint, divert: Optional[ControlPoint], custom_name: Optional[str] = None, + cargo: Optional[AirliftOrder] = None, ) -> None: self.package = package self.country = country @@ -181,6 +185,9 @@ class Flight: self.client_count = 0 self.custom_name = custom_name + # Only used by transport missions. + self.cargo = cargo + # Will be replaced with a more appropriate FlightPlan by # FlightPlanBuilder, but an empty flight plan the flight begins with an # empty flight plan. diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index d447e3ed..8e405552 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -31,7 +31,6 @@ from game.theater import ( TheaterGroundObject, ) from game.theater.theatergroundobject import EwrGroundObject -from game.transfers import Convoy from game.utils import Distance, Speed, feet, meters, nautical_miles from .closestairfields import ObjectiveDistanceCache from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType @@ -42,6 +41,7 @@ from ..conflictgen import Conflict, FRONTLINE_LENGTH if TYPE_CHECKING: from game import Game from gen.ato import Package + from game.transfers import Convoy INGRESS_TYPES = { FlightWaypointType.INGRESS_CAS, @@ -736,6 +736,46 @@ class AwacsFlightPlan(LoiterFlightPlan): return self.push_time +@dataclass(frozen=True) +class AirliftFlightPlan(FlightPlan): + takeoff: FlightWaypoint + nav_to_pickup: List[FlightWaypoint] + pickup: Optional[FlightWaypoint] + nav_to_drop_off: List[FlightWaypoint] + drop_off: FlightWaypoint + nav_to_home: List[FlightWaypoint] + land: FlightWaypoint + divert: Optional[FlightWaypoint] + + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield self.takeoff + yield from self.nav_to_pickup + if self.pickup: + yield self.pickup + yield from self.nav_to_drop_off + yield self.drop_off + yield from self.nav_to_home + yield self.land + if self.divert is not None: + yield self.divert + + @property + def tot_waypoint(self) -> Optional[FlightWaypoint]: + return self.drop_off + + def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]: + # TOT planning isn't really useful for transports. They're behind the front + # lines so no need to wait for escorts or for other missions to complete. + return None + + def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]: + return None + + @property + def mission_departure_time(self) -> timedelta: + return self.package.time_over_target + + @dataclass(frozen=True) class CustomFlightPlan(FlightPlan): custom_waypoints: List[FlightWaypoint] @@ -844,6 +884,8 @@ class FlightPlanBuilder: return self.generate_tarcap(flight) elif task == FlightType.AEWC: return self.generate_aewc(flight) + elif task == FlightType.TRANSPORT: + return self.generate_transport(flight) raise PlanningError(f"{task} flight plan generation not implemented") def regenerate_package_waypoints(self) -> None: @@ -1023,6 +1065,8 @@ class FlightPlanBuilder: """ location = self.package.target + from game.transfers import Convoy + targets: List[StrikeTarget] = [] if isinstance(location, TheaterGroundObject): for group in location.groups: @@ -1141,6 +1185,57 @@ class FlightPlanBuilder: divert=builder.divert(flight.divert), ) + def generate_transport(self, flight: Flight) -> AirliftFlightPlan: + """Generate an airlift flight at a given location. + + Args: + flight: The flight to generate the flight plan for. + """ + cargo = flight.cargo + if cargo is None: + raise PlanningError( + "Cannot plan transport mission for flight with no cargo." + ) + + altitude = feet(1500) + altitude_is_agl = True + + builder = WaypointBuilder(flight, self.game, self.is_player) + + pickup = None + nav_to_pickup = [] + if cargo.origin != flight.departure: + pickup = builder.pickup(cargo.origin) + nav_to_pickup = builder.nav_path( + flight.departure.position, + cargo.origin.position, + altitude, + altitude_is_agl, + ) + + return AirliftFlightPlan( + package=self.package, + flight=flight, + takeoff=builder.takeoff(flight.departure), + nav_to_pickup=nav_to_pickup, + pickup=pickup, + nav_to_drop_off=builder.nav_path( + cargo.origin.position, + cargo.destination.position, + altitude, + altitude_is_agl, + ), + drop_off=builder.drop_off(cargo.destination), + nav_to_home=builder.nav_path( + cargo.origin.position, + flight.arrival.position, + altitude, + altitude_is_agl, + ), + land=builder.land(flight.arrival), + divert=builder.divert(flight.divert), + ) + def racetrack_for_objective( self, location: MissionTarget, barcap: bool ) -> Tuple[Point, Point]: diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index 4559ca20..5487e2c0 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -16,10 +16,9 @@ from dcs.mapping import Point from dcs.unit import Unit from dcs.unitgroup import Group, VehicleGroup -from game.transfers import Convoy - if TYPE_CHECKING: from game import Game + from game.transfers import Convoy from game.theater import ( ControlPoint, @@ -444,24 +443,69 @@ class WaypointBuilder: return ingress, waypoint, egress @staticmethod - def nav(position: Point, altitude: Distance) -> FlightWaypoint: + def pickup(control_point: ControlPoint) -> FlightWaypoint: + """Creates a cargo pickup waypoint. + + Args: + control_point: Pick up location. + """ + waypoint = FlightWaypoint( + FlightWaypointType.PICKUP, + control_point.position.x, + control_point.position.y, + meters(0), + ) + waypoint.alt_type = "RADIO" + waypoint.name = "PICKUP" + waypoint.description = f"Pick up cargo from {control_point}" + waypoint.pretty_name = "Pick up location" + return waypoint + + @staticmethod + def drop_off(control_point: ControlPoint) -> FlightWaypoint: + """Creates a cargo drop-off waypoint. + + Args: + control_point: Drop-off location. + """ + waypoint = FlightWaypoint( + FlightWaypointType.PICKUP, + control_point.position.x, + control_point.position.y, + meters(0), + ) + waypoint.alt_type = "RADIO" + waypoint.name = "DROP OFF" + waypoint.description = f"Drop off cargo at {control_point}" + waypoint.pretty_name = "Drop off location" + return waypoint + + @staticmethod + def nav( + position: Point, altitude: Distance, altitude_is_agl: bool = False + ) -> FlightWaypoint: """Creates a navigation point. Args: position: Position of the waypoint. altitude: Altitude of the waypoint. + altitude_is_agl: True for altitude is AGL. False if altitude is MSL. """ waypoint = FlightWaypoint( FlightWaypointType.NAV, position.x, position.y, altitude ) + if altitude_is_agl: + waypoint.alt_type = "RADIO" waypoint.name = "NAV" waypoint.description = "NAV" waypoint.pretty_name = "Nav" return waypoint - def nav_path(self, a: Point, b: Point, altitude: Distance) -> List[FlightWaypoint]: + def nav_path( + self, a: Point, b: Point, altitude: Distance, altitude_is_agl: bool = False + ) -> List[FlightWaypoint]: path = self.clean_nav_points(self.navmesh.shortest_path(a, b)) - return [self.nav(self.perturb(p), altitude) for p in path] + return [self.nav(self.perturb(p), altitude, altitude_is_agl) for p in path] def clean_nav_points(self, points: Iterable[Point]) -> Iterator[Point]: # Examine a sliding window of three waypoints. `current` is the waypoint diff --git a/qt_ui/models.py b/qt_ui/models.py index bd7d2bc8..7317f8f9 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -15,7 +15,7 @@ from PySide2.QtGui import QIcon from game import db from game.game import Game from game.theater.missiontarget import MissionTarget -from game.transfers import RoadTransferOrder +from game.transfers import TransferOrder from gen.ato import AirTaskingOrder, Package from gen.flights.flight import Flight from gen.flights.traveltime import TotEstimator @@ -52,8 +52,13 @@ class DeletableChildModelManager: ModelDict = Dict[DataType, ModelType] - def __init__(self, create_model: Callable[[DataType], ModelType]) -> None: + def __init__( + self, + create_model: Callable[[DataType, GameModel], ModelType], + game_model: GameModel, + ) -> None: self.create_model = create_model + self.game_model = game_model self.models: DeletableChildModelManager.ModelDict = {} def acquire(self, data: DataType) -> ModelType: @@ -64,7 +69,7 @@ class DeletableChildModelManager: """ if data in self.models: return self.models[data] - model = self.create_model(data) + model = self.create_model(data, self.game_model) self.models[data] = model return model @@ -105,9 +110,10 @@ class PackageModel(QAbstractListModel): tot_changed = Signal() - def __init__(self, package: Package) -> None: + def __init__(self, package: Package, game_model: GameModel) -> None: super().__init__() self.package = package + self.game_model = game_model def rowCount(self, parent: QModelIndex = QModelIndex()) -> int: return len(self.package.flights) @@ -154,14 +160,15 @@ class PackageModel(QAbstractListModel): self.delete_flight(self.flight_at_index(index)) def delete_flight(self, flight: Flight) -> None: - """Removes the given flight from the package. - - If the flight is using claimed inventory, the caller is responsible for - returning that inventory. - """ + """Removes the given flight from the package.""" index = self.package.flights.index(flight) self.beginRemoveRows(QModelIndex(), index, index) - self.package.remove_flight(flight) + if flight.cargo is None: + self.game_model.game.aircraft_inventory.return_from_flight(flight) + self.package.remove_flight(flight) + else: + # Deleted transfers will clean up after themselves. + self.game_model.transfer_model.cancel_transfer(flight.cargo) self.endRemoveRows() self.update_tot() @@ -210,11 +217,15 @@ class AtoModel(QAbstractListModel): client_slots_changed = Signal() - def __init__(self, game: Optional[Game], ato: AirTaskingOrder) -> None: + def __init__(self, game_model: GameModel, ato: AirTaskingOrder) -> None: super().__init__() - self.game = game + self.game_model = game_model self.ato = ato - self.package_models = DeletableChildModelManager(PackageModel) + self.package_models = DeletableChildModelManager(PackageModel, game_model) + + @property + def game(self) -> Optional[Game]: + return self.game_model.game def rowCount(self, parent: QModelIndex = QModelIndex()) -> int: return len(self.ato.packages) @@ -249,6 +260,8 @@ class AtoModel(QAbstractListModel): self.ato.remove_package(package) for flight in package.flights: self.game.aircraft_inventory.return_from_flight(flight) + if flight.cargo is not None: + self.game_model.transfer_model.cancel_transfer(flight.cargo) self.endRemoveRows() # noinspection PyUnresolvedReferences self.client_slots_changed.emit() @@ -257,20 +270,19 @@ class AtoModel(QAbstractListModel): """Returns the package at the given index.""" return self.ato.packages[index.row()] - def replace_from_game(self, game: Optional[Game], player: bool) -> None: + def replace_from_game(self, player: bool) -> None: """Updates the ATO object to match the updated game object. If the game is None (as is the case when no game has been loaded), an empty ATO will be used. """ self.beginResetModel() - self.game = game self.package_models.clear() if self.game is not None: if player: - self.ato = game.blue_ato + self.ato = self.game.blue_ato else: - self.ato = game.red_ato + self.ato = self.game.red_ato else: self.ato = AirTaskingOrder() self.endResetModel() @@ -313,7 +325,7 @@ class TransferModel(QAbstractListModel): return None @staticmethod - def text_for_transfer(transfer: RoadTransferOrder) -> str: + def text_for_transfer(transfer: TransferOrder) -> str: """Returns the text that should be displayed for the transfer.""" count = sum(transfer.units.values()) origin = transfer.origin.name @@ -321,11 +333,11 @@ class TransferModel(QAbstractListModel): return f"Transfer of {count} units from {origin} to {destination}" @staticmethod - def icon_for_transfer(_transfer: RoadTransferOrder) -> Optional[QIcon]: + def icon_for_transfer(_transfer: TransferOrder) -> Optional[QIcon]: """Returns the icon that should be displayed for the transfer.""" return None - def new_transfer(self, transfer: RoadTransferOrder) -> None: + def new_transfer(self, transfer: TransferOrder) -> None: """Updates the game with the new unit transfer.""" self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount()) # TODO: Needs to regenerate base inventory tab. @@ -334,13 +346,17 @@ class TransferModel(QAbstractListModel): def cancel_transfer_at_index(self, index: QModelIndex) -> None: """Cancels the planned unit transfer at the given index.""" - transfer = self.transfer_at_index(index) - self.beginRemoveRows(QModelIndex(), index.row(), index.row()) + self.cancel_transfer(self.transfer_at_index(index)) + + def cancel_transfer(self, transfer: TransferOrder) -> None: + """Cancels the planned unit transfer at the given index.""" + index = self.game_model.game.transfers.index_of_transfer(transfer) + self.beginRemoveRows(QModelIndex(), index, index) # TODO: Needs to regenerate base inventory tab. self.game_model.game.transfers.cancel_transfer(transfer) self.endRemoveRows() - def transfer_at_index(self, index: QModelIndex) -> RoadTransferOrder: + def transfer_at_index(self, index: QModelIndex) -> TransferOrder: """Returns the transfer located at the given index.""" return self.game_model.game.transfers.transfer_at_index(index.row()) @@ -356,11 +372,11 @@ class GameModel: self.game: Optional[Game] = game self.transfer_model = TransferModel(self) if self.game is None: - self.ato_model = AtoModel(self.game, AirTaskingOrder()) - self.red_ato_model = AtoModel(self.game, AirTaskingOrder()) + self.ato_model = AtoModel(self, AirTaskingOrder()) + self.red_ato_model = AtoModel(self, AirTaskingOrder()) else: - self.ato_model = AtoModel(self.game, self.game.blue_ato) - self.red_ato_model = AtoModel(self.game, self.game.red_ato) + self.ato_model = AtoModel(self, self.game.blue_ato) + self.red_ato_model = AtoModel(self, self.game.red_ato) def set(self, game: Optional[Game]) -> None: """Updates the managed Game object. @@ -371,5 +387,5 @@ class GameModel: loaded. """ self.game = game - self.ato_model.replace_from_game(self.game, player=True) - self.red_ato_model.replace_from_game(self.game, player=False) + self.ato_model.replace_from_game(player=True) + self.red_ato_model.replace_from_game(player=False) diff --git a/qt_ui/widgets/ato.py b/qt_ui/widgets/ato.py index 93b170bb..2305a25e 100644 --- a/qt_ui/widgets/ato.py +++ b/qt_ui/widgets/ato.py @@ -204,7 +204,6 @@ class QFlightList(QListView): ) def delete_flight(self, index: QModelIndex) -> None: - self.game_model.game.aircraft_inventory.return_from_flight(self.selected_item) self.package_model.delete_flight_at_index(index) GameUpdateSignal.get_instance().redraw_flight_paths() diff --git a/qt_ui/windows/PendingTransfersDialog.py b/qt_ui/windows/PendingTransfersDialog.py index e49c5dca..6b882bd8 100644 --- a/qt_ui/windows/PendingTransfersDialog.py +++ b/qt_ui/windows/PendingTransfersDialog.py @@ -22,8 +22,7 @@ from PySide2.QtWidgets import ( QVBoxLayout, ) -from game.theater.supplyroutes import SupplyRoute -from game.transfers import RoadTransferOrder +from game.transfers import TransferOrder from qt_ui.delegate_helpers import painter_context from qt_ui.models import GameModel, TransferModel @@ -43,20 +42,14 @@ class TransferDelegate(QStyledItemDelegate): return font @staticmethod - def transfer(index: QModelIndex) -> RoadTransferOrder: + def transfer(index: QModelIndex) -> TransferOrder: return index.data(TransferModel.TransferRole) def first_row_text(self, index: QModelIndex) -> str: return self.transfer_model.data(index, Qt.DisplayRole) def second_row_text(self, index: QModelIndex) -> str: - transfer = self.transfer(index) - path = transfer.path() - if len(path) == 1: - turns = "1 turn" - else: - turns = f"{len(path)} turns" - return f"Currently at {transfer.position}. Arrives at destination in {turns}." + return self.transfer(index).description def paint( self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index 66873aab..ca6b1bc6 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -15,6 +15,7 @@ from PySide2.QtWidgets import ( QGroupBox, QHBoxLayout, QLabel, + QMessageBox, QPushButton, QScrollArea, QSizePolicy, @@ -23,12 +24,16 @@ from PySide2.QtWidgets import ( QWidget, ) from dcs.task import PinpointStrike -from dcs.unittype import UnitType +from dcs.unittype import FlyingType, UnitType, VehicleType from game import Game, db +from game.inventory import ControlPointAircraftInventory from game.theater import ControlPoint, SupplyRoute -from game.transfers import RoadTransferOrder -from qt_ui.models import GameModel +from game.transfers import AirliftOrder, RoadTransferOrder +from gen.ato import Package +from gen.flights.flight import Flight, FlightType +from gen.flights.flightplan import FlightPlanBuilder, PlanningError +from qt_ui.models import GameModel, PackageModel from qt_ui.widgets.QLabeledWidget import QLabeledWidget @@ -111,7 +116,7 @@ class TransferOptionsPanel(QVBoxLayout): self.addLayout(QLabeledWidget("Destination:", self.source_combo_box)) self.airlift = QCheckBox() self.airlift.toggled.connect(self.set_airlift) - self.addLayout(QLabeledWidget("Airlift (non-functional):", self.airlift)) + self.addLayout(QLabeledWidget("Airlift (WIP):", self.airlift)) self.addWidget( QLabel( f"{airlift_capacity.total} airlift capacity " @@ -363,12 +368,112 @@ class NewUnitTransferDialog(QDialog): ) transfers[unit_type] = count - self.game_model.transfer_model.new_transfer( - RoadTransferOrder( + if self.dest_panel.airlift.isChecked(): + self.create_package_for_airlift( + self.transfer_panel.cp, + self.dest_panel.current, + transfers, + ) + else: + transfer = RoadTransferOrder( player=True, origin=self.transfer_panel.cp, destination=self.dest_panel.current, units=transfers, ) - ) + self.game_model.transfer_model.new_transfer(transfer) self.close() + + @staticmethod + def take_units( + units: Dict[Type[VehicleType], int], count: int + ) -> Dict[Type[VehicleType], int]: + taken = {} + for unit_type, remaining in units.items(): + take = min(remaining, count) + count -= take + units[unit_type] -= take + taken[unit_type] = take + if not count: + break + return taken + + def create_airlift_flight( + self, + game: Game, + package_model: PackageModel, + unit_type: Type[FlyingType], + inventory: ControlPointAircraftInventory, + needed_capacity: int, + pickup: ControlPoint, + drop_off: ControlPoint, + units: Dict[Type[VehicleType], int], + ) -> int: + available = inventory.available(unit_type) + # 4 is the max flight size in DCS. + flight_size = min(needed_capacity, available, 4) + flight = Flight( + package_model.package, + game.player_country, + unit_type, + flight_size, + FlightType.TRANSPORT, + game.settings.default_start_type, + departure=inventory.control_point, + arrival=inventory.control_point, + divert=None, + ) + + transfer = AirliftOrder( + player=True, + origin=pickup, + destination=drop_off, + units=self.take_units(units, flight_size), + flight=flight, + ) + flight.cargo = transfer + + package_model.add_flight(flight) + planner = FlightPlanBuilder(game, package_model.package, is_player=True) + try: + planner.populate_flight_plan(flight) + except PlanningError as ex: + package_model.delete_flight(flight) + logging.exception("Could not create flight") + QMessageBox.critical( + self, "Could not create flight", str(ex), QMessageBox.Ok + ) + game.aircraft_inventory.claim_for_flight(flight) + self.game_model.transfer_model.new_transfer(transfer) + return flight_size + + def create_package_for_airlift( + self, + pickup: ControlPoint, + drop_off: ControlPoint, + units: Dict[Type[VehicleType], int], + ) -> None: + package = Package(target=drop_off, auto_asap=True) + package_model = PackageModel(package, self.game_model) + + needed_capacity = sum(c for c in units.values()) + game = self.game_model.game + for cp in game.theater.player_points(): + inventory = game.aircraft_inventory.for_control_point(cp) + for unit_type, available in inventory.all_aircraft: + if unit_type.helicopter: + while available and needed_capacity: + flight_size = self.create_airlift_flight( + self.game_model.game, + package_model, + unit_type, + inventory, + needed_capacity, + pickup, + drop_off, + units, + ) + available -= flight_size + needed_capacity -= flight_size + package_model.update_tot() + self.game_model.ato_model.add_package(package) diff --git a/qt_ui/windows/mission/QPackageDialog.py b/qt_ui/windows/mission/QPackageDialog.py index 3497140f..666f8539 100644 --- a/qt_ui/windows/mission/QPackageDialog.py +++ b/qt_ui/windows/mission/QPackageDialog.py @@ -185,7 +185,6 @@ class QPackageDialog(QDialog): try: planner.populate_flight_plan(flight) except PlanningError as ex: - self.game.aircraft_inventory.return_from_flight(flight) self.package_model.delete_flight(flight) logging.exception("Could not create flight") QMessageBox.critical( @@ -201,7 +200,6 @@ class QPackageDialog(QDialog): if flight is None: logging.error(f"Cannot delete flight when no flight is selected.") return - self.game.aircraft_inventory.return_from_flight(flight) self.package_model.delete_flight(flight) # noinspection PyUnresolvedReferences self.package_changed.emit() @@ -216,7 +214,9 @@ class QNewPackageDialog(QPackageDialog): def __init__( self, game_model: GameModel, model: AtoModel, target: MissionTarget, parent=None ) -> None: - super().__init__(game_model, PackageModel(Package(target)), parent=parent) + super().__init__( + game_model, PackageModel(Package(target), game_model), parent=parent + ) self.ato_model = model self.save_button = QPushButton("Save") From 1fd7c95f1b128818456d77511f946ec50c7b45e8 Mon Sep 17 00:00:00 2001 From: Simon Clark Date: Thu, 22 Apr 2021 23:08:56 +0100 Subject: [PATCH 047/438] Fix Russian carrier name; Kuznetov -> Kuznetsov. --- resources/factions/redfor_russia_2010.json | 2 +- resources/factions/russia_1990.json | 2 +- resources/factions/russia_2010.json | 2 +- resources/factions/russia_2010_hds.json | 2 +- resources/factions/russia_2020.json | 2 +- resources/factions/ukraine_2010.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/factions/redfor_russia_2010.json b/resources/factions/redfor_russia_2010.json index e195e8d5..be4023fa 100644 --- a/resources/factions/redfor_russia_2010.json +++ b/resources/factions/redfor_russia_2010.json @@ -104,7 +104,7 @@ ], "requirements": {}, "carrier_names": [ - "Admiral Kuznetov" + "Admiral Kuznetsov" ], "navy_generators": [ "RussianNavyGroupGenerator", diff --git a/resources/factions/russia_1990.json b/resources/factions/russia_1990.json index 4237b95c..ce40dc11 100644 --- a/resources/factions/russia_1990.json +++ b/resources/factions/russia_1990.json @@ -85,7 +85,7 @@ ], "requirements": {}, "carrier_names": [ - "Admiral Kuznetov", + "Admiral Kuznetsov", "Admiral Gorshkov" ], "navy_generators": [ diff --git a/resources/factions/russia_2010.json b/resources/factions/russia_2010.json index 1d9ccf97..d2df9602 100644 --- a/resources/factions/russia_2010.json +++ b/resources/factions/russia_2010.json @@ -88,7 +88,7 @@ ], "requirements": {}, "carrier_names": [ - "Admiral Kuznetov" + "Admiral Kuznetsov" ], "navy_generators": [ "RussianNavyGroupGenerator" diff --git a/resources/factions/russia_2010_hds.json b/resources/factions/russia_2010_hds.json index 751bf335..23799043 100644 --- a/resources/factions/russia_2010_hds.json +++ b/resources/factions/russia_2010_hds.json @@ -85,7 +85,7 @@ ], "requirements": { "High Digit SAMs": "https://github.com/Auranis/HighDigitSAMs/releases"}, "carrier_names": [ - "Admiral Kuznetov" + "Admiral Kuznetsov" ], "navy_generators": [ "RussianNavyGroupGenerator" diff --git a/resources/factions/russia_2020.json b/resources/factions/russia_2020.json index 9f134ebe..0ada877b 100644 --- a/resources/factions/russia_2020.json +++ b/resources/factions/russia_2020.json @@ -86,7 +86,7 @@ "SU-57 Felon By CubanAce Simulations": "https://www.digitalcombatsimulator.com/fr/files/2539621/" }, "carrier_names": [ - "Admiral Kuznetov" + "Admiral Kuznetsov" ], "navy_generators": [ "RussianNavyGroupGenerator" diff --git a/resources/factions/ukraine_2010.json b/resources/factions/ukraine_2010.json index 9ecceb81..d8fd710b 100644 --- a/resources/factions/ukraine_2010.json +++ b/resources/factions/ukraine_2010.json @@ -61,7 +61,7 @@ ], "requirements": {}, "carrier_names": [ - "Admiral Kuznetov", + "Admiral Kuznetsov", "Admiral Gorshkov" ], "navy_generators": [ From 208d1b82b57d258804a9affc2ae160bb1b3f44f0 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Apr 2021 17:45:19 -0700 Subject: [PATCH 048/438] Show BARCAP commit ranges by default. BARCAP placement confuses a lot of people but this should make it more clear. --- changelog.md | 8 ++++++++ qt_ui/displayoptions.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index d9e6bbe9..5eef3f90 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,14 @@ Saves from 2.5 are not compatible with 2.6. ## Fixes +# 2.5.1 + +## Features/Improvements + +* **[UI]** BARCAP commit ranges are now displayed by default. + +## Fixes + # 2.5.0 Saves from 2.4 are not compatible with 2.5. diff --git a/qt_ui/displayoptions.py b/qt_ui/displayoptions.py index 423fe77f..d3224a3c 100644 --- a/qt_ui/displayoptions.py +++ b/qt_ui/displayoptions.py @@ -103,7 +103,7 @@ class DisplayOptions: waypoint_info = DisplayRule("Waypoint Information", True) culling = DisplayRule("Display Culling Zones", False) actual_frontline_pos = DisplayRule("Display Actual Frontline Location", False) - barcap_commit_range = DisplayRule("Display selected BARCAP commit range", False) + barcap_commit_range = DisplayRule("Display selected BARCAP commit range", True) flight_paths = FlightPathOptions() blue_threat_zones = ThreatZoneOptions("Blue") red_threat_zones = ThreatZoneOptions("Red") From 132ba905c7da3c815d9d8457282fa87a7a851fa5 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Apr 2021 17:55:14 -0700 Subject: [PATCH 049/438] Generalize commit range display for all patrols. Fixes https://github.com/Khopa/dcs_liberation/issues/890 --- changelog.md | 3 ++- qt_ui/displayoptions.py | 4 +++- qt_ui/widgets/map/QLiberationMap.py | 12 ++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/changelog.md b/changelog.md index 5eef3f90..96edd054 100644 --- a/changelog.md +++ b/changelog.md @@ -16,7 +16,8 @@ Saves from 2.5 are not compatible with 2.6. ## Features/Improvements -* **[UI]** BARCAP commit ranges are now displayed by default. +* **[UI]** Engagement ranges are now displayed by default. +* **[UI]** Engagement range display generalized to work for all patrolling flight plans (BARCAP, TARCAP, and CAS). ## Fixes diff --git a/qt_ui/displayoptions.py b/qt_ui/displayoptions.py index d3224a3c..2abf369c 100644 --- a/qt_ui/displayoptions.py +++ b/qt_ui/displayoptions.py @@ -103,7 +103,9 @@ class DisplayOptions: waypoint_info = DisplayRule("Waypoint Information", True) culling = DisplayRule("Display Culling Zones", False) actual_frontline_pos = DisplayRule("Display Actual Frontline Location", False) - barcap_commit_range = DisplayRule("Display selected BARCAP commit range", True) + patrol_engagement_range = DisplayRule( + "Display selected patrol engagement range", True + ) flight_paths = FlightPathOptions() blue_threat_zones = ThreatZoneOptions("Blue") red_threat_zones = ThreatZoneOptions("Red") diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 540f9e0c..3d54b651 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -59,6 +59,8 @@ from gen.flights.flightplan import ( FlightPlan, FlightPlanBuilder, InvalidObjectiveLocation, + PatrollingFlightPlan, + TarCapFlightPlan, ) from gen.flights.traveltime import TotEstimator from qt_ui.displayoptions import DisplayOptions, ThreatZoneOptions @@ -716,13 +718,11 @@ class QLiberationMap(QGraphicsView): ) prev_pos = tuple(new_pos) - if selected and DisplayOptions.barcap_commit_range: - self.draw_barcap_commit_range(scene, flight) + if selected and DisplayOptions.patrol_engagement_range: + self.draw_patrol_commit_range(scene, flight) - def draw_barcap_commit_range(self, scene: QGraphicsScene, flight: Flight) -> None: - if flight.flight_type is not FlightType.BARCAP: - return - if not isinstance(flight.flight_plan, BarCapFlightPlan): + def draw_patrol_commit_range(self, scene: QGraphicsScene, flight: Flight) -> None: + if not isinstance(flight.flight_plan, PatrollingFlightPlan): return start = flight.flight_plan.patrol_start end = flight.flight_plan.patrol_end From e474748f4d816a577627e8b9e9f41dfae38012fd Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Apr 2021 18:20:26 -0700 Subject: [PATCH 050/438] Stop projecting threat zones from front lines. This is an interim improvement since we should probably be pushing the BARCAPs into TARCAP roles when the front line is so close. This does regress flight pathing for anything that should route around the front (to avoid getting shot at by SHORADS and TARCAPs), but for now it's one or the other and this is the one everyone's complaining about. --- changelog.md | 1 + game/threatzones.py | 17 ----------------- gen/flights/ai_flight_planner.py | 20 +++++++++++++++++--- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/changelog.md b/changelog.md index 96edd054..f09addf7 100644 --- a/changelog.md +++ b/changelog.md @@ -18,6 +18,7 @@ Saves from 2.5 are not compatible with 2.6. * **[UI]** Engagement ranges are now displayed by default. * **[UI]** Engagement range display generalized to work for all patrolling flight plans (BARCAP, TARCAP, and CAS). +* **[Flight Planner]** Front lines no longer project threat zones to avoid pushing BARCAPs back so much. TARCAPs will be forcibly planned but strike packages will not route around front lines even if it is reasonable to do so. ## Fixes diff --git a/game/threatzones.py b/game/threatzones.py index 85169f17..e4ad0c39 100644 --- a/game/threatzones.py +++ b/game/threatzones.py @@ -152,23 +152,6 @@ class ThreatZones: threat_zone = point.buffer(threat_range.meters) air_defenses.append(threat_zone) - for front_line in game.theater.conflicts(player): - vector = Conflict.frontline_vector( - front_line.control_point_a, front_line.control_point_b, game.theater - ) - - start = vector[0] - end = vector[0].point_from_heading(vector[1], vector[2]) - - line = LineString( - [ - ShapelyPoint(start.x, start.y), - ShapelyPoint(end.x, end.y), - ] - ) - doctrine = game.faction_for(player).doctrine - air_threats.append(line.buffer(doctrine.cap_engagement_range.meters)) - return cls( airbases=unary_union(air_threats), air_defenses=unary_union(air_defenses) ) diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 909eb873..72309cd9 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -589,9 +589,23 @@ class CoalitionMissionPlanner: front_line, [ ProposedFlight(FlightType.CAS, 2, self.MAX_CAS_RANGE), - ProposedFlight( - FlightType.TARCAP, 2, self.MAX_CAP_RANGE, EscortType.AirToAir - ), + # This is *not* an escort because front lines don't create a threat + # zone. Generating threat zones from front lines causes the front + # line to push back BARCAPs as it gets closer to the base. While + # front lines do have the same problem of potentially pulling + # BARCAPs off bases to engage a front line TARCAP, that's probably + # the one time where we do want that. + # + # TODO: Use intercepts and extra TARCAPs to cover bases near fronts. + # We don't have intercept missions yet so this isn't something we + # can do today, but we should probably return to having the front + # line project a threat zone (so that strike missions will route + # around it) and instead *not plan* a BARCAP at bases near the + # front, since there isn't a place to put a barrier. Instead, the + # aircraft that would have been a BARCAP could be used as additional + # interceptors and TARCAPs which will defend the base but won't be + # trying to avoid front line contacts. + ProposedFlight(FlightType.TARCAP, 2, self.MAX_CAP_RANGE), ], ) From 29b70b324702cb4ee420d0940e5ebd2b2439dcff Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Apr 2021 19:22:41 -0700 Subject: [PATCH 051/438] Track airlift cargo kills. https://github.com/Khopa/dcs_liberation/issues/825 --- game/debriefing.py | 36 ++++++++++++++++++- game/event/event.py | 18 ++++++++++ game/transfers.py | 11 ++++++ game/unitmap.py | 26 +++++++++++++- qt_ui/windows/QDebriefingWindow.py | 22 ++++++++++++ .../windows/QWaitingForMissionResultWindow.py | 15 ++++---- 6 files changed, 120 insertions(+), 8 deletions(-) diff --git a/game/debriefing.py b/game/debriefing.py index 3e6b4689..1ccafaca 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -22,7 +22,14 @@ from dcs.unittype import FlyingType, UnitType from game import db from game.theater import Airfield, ControlPoint -from game.unitmap import Building, ConvoyUnit, FrontLineUnit, GroundObjectUnit, UnitMap +from game.unitmap import ( + AirliftUnit, + Building, + ConvoyUnit, + FrontLineUnit, + GroundObjectUnit, + UnitMap, +) from gen.flights.flight import Flight if TYPE_CHECKING: @@ -63,6 +70,9 @@ class GroundLosses: player_convoy: List[ConvoyUnit] = field(default_factory=list) enemy_convoy: List[ConvoyUnit] = field(default_factory=list) + player_airlifts: List[AirliftUnit] = field(default_factory=list) + enemy_airlifts: List[AirliftUnit] = field(default_factory=list) + player_ground_objects: List[GroundObjectUnit] = field(default_factory=list) enemy_ground_objects: List[GroundObjectUnit] = field(default_factory=list) @@ -128,6 +138,11 @@ class Debriefing: yield from self.ground_losses.player_convoy yield from self.ground_losses.enemy_convoy + @property + def airlift_losses(self) -> Iterator[AirliftUnit]: + yield from self.ground_losses.player_airlifts + yield from self.ground_losses.enemy_airlifts + @property def ground_object_losses(self) -> Iterator[GroundObjectUnit]: yield from self.ground_losses.player_ground_objects @@ -166,6 +181,16 @@ class Debriefing: losses_by_type[loss.unit_type] += 1 return losses_by_type + def airlift_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]: + losses_by_type: Dict[Type[UnitType], int] = defaultdict(int) + if player: + losses = self.ground_losses.player_airlifts + else: + losses = self.ground_losses.enemy_airlifts + for loss in losses: + losses_by_type[loss.unit_type] += 1 + return losses_by_type + def building_losses_by_type(self, player: bool) -> Dict[str, int]: losses_by_type: Dict[str, int] = defaultdict(int) if player: @@ -250,6 +275,15 @@ class Debriefing: "have no effect. This may be normal behavior." ) + for unit_name in self.state_data.killed_aircraft: + airlift_unit = self.unit_map.airlift_unit(unit_name) + if airlift_unit is not None: + if airlift_unit.transfer.player: + losses.player_airlifts.append(airlift_unit) + else: + losses.enemy_airlifts.append(airlift_unit) + continue + return losses @property diff --git a/game/event/event.py b/game/event/event.py index c2b77aa2..beebd54a 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -172,6 +172,23 @@ class Event: logging.info(f"{unit_type} destroyed in {convoy_name}") convoy.kill_unit(unit_type) + @staticmethod + def commit_airlift_losses(debriefing: Debriefing) -> None: + for loss in debriefing.airlift_losses: + unit_type = loss.unit_type + transfer = loss.transfer + available = loss.transfer.units.get(unit_type, 0) + airlift_name = f"airlift from {transfer.origin} to {transfer.destination}" + if available <= 0: + logging.error( + f"Found killed {unit_type} in {airlift_name} but that airlift has " + "none available." + ) + continue + + logging.info(f"{unit_type} destroyed in {airlift_name}") + transfer.kill_unit(unit_type) + @staticmethod def commit_ground_object_losses(debriefing: Debriefing) -> None: for loss in debriefing.ground_object_losses: @@ -205,6 +222,7 @@ class Event: self.commit_air_losses(debriefing) self.commit_front_line_losses(debriefing) self.commit_convoy_losses(debriefing) + self.commit_airlift_losses(debriefing) self.commit_ground_object_losses(debriefing) self.commit_building_losses(debriefing) self.commit_damaged_runways(debriefing) diff --git a/game/transfers.py b/game/transfers.py index 64e2f830..0819baf1 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -78,6 +78,17 @@ class AirliftOrder(TransferOrder): def description(self) -> str: return "Airlift" + def iter_units(self) -> Iterator[Type[VehicleType]]: + for unit_type, count in self.units.items(): + for _ in range(count): + yield unit_type + + def kill_unit(self, unit_type: Type[VehicleType]) -> None: + if unit_type in self.units: + self.units[unit_type] -= 1 + return + raise KeyError + class Convoy(MissionTarget): def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: diff --git a/game/unitmap.py b/game/unitmap.py index 520e80f9..322320b7 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -9,7 +9,7 @@ from dcs.unittype import VehicleType from game import db from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import BuildingGroundObject -from game.transfers import Convoy, RoadTransferOrder +from game.transfers import AirliftOrder, Convoy from gen.flights.flight import Flight @@ -32,6 +32,12 @@ class ConvoyUnit: convoy: Convoy +@dataclass(frozen=True) +class AirliftUnit: + unit_type: Type[VehicleType] + transfer: AirliftOrder + + @dataclass(frozen=True) class Building: ground_object: BuildingGroundObject @@ -45,6 +51,7 @@ class UnitMap: self.ground_object_units: Dict[str, GroundObjectUnit] = {} self.buildings: Dict[str, Building] = {} self.convoys: Dict[str, ConvoyUnit] = {} + self.airlifts: Dict[str, AirliftUnit] = {} def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: for unit in group.units: @@ -54,6 +61,8 @@ class UnitMap: if name in self.aircraft: raise RuntimeError(f"Duplicate unit name: {name}") self.aircraft[name] = flight + if flight.cargo is not None: + self.add_airlift_units(group, flight.cargo) def flight(self, unit_name: str) -> Optional[Flight]: return self.aircraft.get(unit_name, None) @@ -140,6 +149,21 @@ class UnitMap: def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: return self.convoys.get(name, None) + def add_airlift_units(self, group: FlyingGroup, airlift: AirliftOrder) -> None: + for transport, cargo_type in zip(group.units, airlift.iter_units()): + # The actual name is a String (the pydcs translatable string), which + # doesn't define __eq__. + name = str(transport.name) + if name in self.airlifts: + raise RuntimeError(f"Duplicate airlift unit: {name}") + unit_type = db.unit_type_from_name(transport.type) + if unit_type is None: + raise RuntimeError(f"Unknown unit type: {transport.type}") + self.airlifts[name] = AirliftUnit(cargo_type, airlift) + + def airlift_unit(self, name: str) -> Optional[AirliftUnit]: + return self.airlifts.get(name, None) + def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None: # The actual name is a String (the pydcs translatable string), which # doesn't define __eq__. diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py index 4b62c397..7308b0e6 100644 --- a/qt_ui/windows/QDebriefingWindow.py +++ b/qt_ui/windows/QDebriefingWindow.py @@ -83,6 +83,17 @@ class QDebriefingWindow(QDialog): except AttributeError: logging.exception(f"Issue adding {unit_type} to debriefing information") + airlift_losses = self.debriefing.airlift_losses_by_type(player=True) + for unit_type, count in airlift_losses.items(): + try: + lostUnitsLayout.addWidget( + QLabel(f"{db.unit_type_name(unit_type)} from airlift"), row, 0 + ) + lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) + row += 1 + except AttributeError: + logging.exception(f"Issue adding {unit_type} to debriefing information") + building_losses = self.debriefing.building_losses_by_type(player=True) for building, count in building_losses.items(): try: @@ -135,6 +146,17 @@ class QDebriefingWindow(QDialog): except AttributeError: logging.exception(f"Issue adding {unit_type} to debriefing information") + airlift_losses = self.debriefing.airlift_losses_by_type(player=False) + for unit_type, count in airlift_losses.items(): + try: + lostUnitsLayout.addWidget( + QLabel(f"{db.unit_type_name(unit_type)} from airlift"), row, 0 + ) + lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) + row += 1 + except AttributeError: + logging.exception(f"Issue adding {unit_type} to debriefing information") + building_losses = self.debriefing.building_losses_by_type(player=False) for building, count in building_losses.items(): try: diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index 3b06e877..bd7f6163 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -151,16 +151,19 @@ class QWaitingForMissionResultWindow(QDialog): updateLayout.addWidget(QLabel("Convoy units destroyed"), 2, 0) updateLayout.addWidget(QLabel(str(len(list(debriefing.convoy_losses)))), 2, 1) - updateLayout.addWidget(QLabel("Other ground units destroyed"), 3, 0) + updateLayout.addWidget(QLabel("Airlift cargo destroyed"), 3, 0) + updateLayout.addWidget(QLabel(str(len(list(debriefing.airlift_losses)))), 3, 1) + + updateLayout.addWidget(QLabel("Other ground units destroyed"), 4, 0) updateLayout.addWidget( - QLabel(str(len(list(debriefing.ground_object_losses)))), 3, 1 + QLabel(str(len(list(debriefing.ground_object_losses)))), 4, 1 ) - updateLayout.addWidget(QLabel("Buildings destroyed"), 4, 0) - updateLayout.addWidget(QLabel(str(len(list(debriefing.building_losses)))), 4, 1) + updateLayout.addWidget(QLabel("Buildings destroyed"), 5, 0) + updateLayout.addWidget(QLabel(str(len(list(debriefing.building_losses)))), 5, 1) - updateLayout.addWidget(QLabel("Base Capture Events"), 5, 0) - updateLayout.addWidget(QLabel(str(len(debriefing.base_capture_events))), 5, 1) + updateLayout.addWidget(QLabel("Base Capture Events"), 6, 0) + updateLayout.addWidget(QLabel(str(len(debriefing.base_capture_events))), 6, 1) # Clear previous content of the window for i in reversed(range(self.gridLayout.count())): From 182422249f1ac65d883c7886053c44a42ce4371e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Apr 2021 22:12:52 -0700 Subject: [PATCH 052/438] Remove support for mizdata/random locations. The introduction of factories broke every existing campaign so we don't need to keep support for the ancient formats any more. --- game/theater/start_generator.py | 161 +----------------- gen/locations/preset_location_finder.py | 87 ---------- .../mizdata/caucasus/Anapa-Vityazevo.miz | Bin 9279 -> 0 bytes resources/mizdata/caucasus/Batumi.miz | Bin 8244 -> 0 bytes resources/mizdata/caucasus/Beslan.miz | Bin 9028 -> 0 bytes resources/mizdata/caucasus/Gelendzhik.miz | Bin 9359 -> 0 bytes resources/mizdata/caucasus/Gudauta.miz | Bin 8988 -> 0 bytes resources/mizdata/caucasus/Kobuleti.miz | Bin 9661 -> 0 bytes .../mizdata/caucasus/Krasnodar-Center.miz | Bin 9788 -> 0 bytes .../mizdata/caucasus/Krasnodar-Pashkovsky.miz | Bin 10776 -> 0 bytes resources/mizdata/caucasus/Krymsk.miz | Bin 9446 -> 0 bytes resources/mizdata/caucasus/Kutaisi.miz | Bin 8953 -> 0 bytes .../mizdata/caucasus/Maykop-Khanskaya.miz | Bin 10178 -> 0 bytes .../mizdata/caucasus/Mineralnye Vody.miz | Bin 9535 -> 0 bytes resources/mizdata/caucasus/Mozdok.miz | Bin 9777 -> 0 bytes resources/mizdata/caucasus/Nalchik.miz | Bin 9025 -> 0 bytes resources/mizdata/caucasus/Novorossiysk.miz | Bin 8581 -> 0 bytes resources/mizdata/caucasus/Senaki-Kolkhi.miz | Bin 9501 -> 0 bytes resources/mizdata/caucasus/Sochi-Adler.miz | Bin 9004 -> 0 bytes .../mizdata/caucasus/Sukhumi-Babushara.miz | Bin 10104 -> 0 bytes resources/mizdata/caucasus/Vaziani.miz | Bin 8987 -> 0 bytes .../mizdata/caucasus/caucusus_frontline.miz | Bin 31640 -> 0 bytes .../Al Ain International Airport.miz | Bin 8857 -> 0 bytes resources/mizdata/persiangulf/Fujairah.miz | Bin 9658 -> 0 bytes resources/mizdata/syria/Eyn Shemer.miz | Bin 8544 -> 0 bytes .../mizdata/syria/Khalkhalah Defenses.miz | Bin 15599 -> 0 bytes .../syria/King Hussein Air College.miz | Bin 8756 -> 0 bytes .../mizdata/thechannel/Abbeville Drucat.miz | Bin 12748 -> 0 bytes resources/mizdata/thechannel/Detling.miz | Bin 10307 -> 0 bytes .../mizdata/thechannel/Dunkirk Mardyck.miz | Bin 12734 -> 0 bytes resources/mizdata/thechannel/Hawkinge.miz | Bin 9584 -> 0 bytes resources/mizdata/thechannel/High Halden.miz | Bin 8696 -> 0 bytes resources/mizdata/thechannel/Lympne.miz | Bin 9339 -> 0 bytes resources/mizdata/thechannel/Manston.miz | Bin 11013 -> 0 bytes .../mizdata/thechannel/Merville Calonne.miz | Bin 9704 -> 0 bytes .../thechannel/Saint Omer Longuenesse.miz | Bin 9194 -> 0 bytes 36 files changed, 4 insertions(+), 244 deletions(-) delete mode 100644 gen/locations/preset_location_finder.py delete mode 100644 resources/mizdata/caucasus/Anapa-Vityazevo.miz delete mode 100644 resources/mizdata/caucasus/Batumi.miz delete mode 100644 resources/mizdata/caucasus/Beslan.miz delete mode 100644 resources/mizdata/caucasus/Gelendzhik.miz delete mode 100644 resources/mizdata/caucasus/Gudauta.miz delete mode 100644 resources/mizdata/caucasus/Kobuleti.miz delete mode 100644 resources/mizdata/caucasus/Krasnodar-Center.miz delete mode 100644 resources/mizdata/caucasus/Krasnodar-Pashkovsky.miz delete mode 100644 resources/mizdata/caucasus/Krymsk.miz delete mode 100644 resources/mizdata/caucasus/Kutaisi.miz delete mode 100644 resources/mizdata/caucasus/Maykop-Khanskaya.miz delete mode 100644 resources/mizdata/caucasus/Mineralnye Vody.miz delete mode 100644 resources/mizdata/caucasus/Mozdok.miz delete mode 100644 resources/mizdata/caucasus/Nalchik.miz delete mode 100644 resources/mizdata/caucasus/Novorossiysk.miz delete mode 100644 resources/mizdata/caucasus/Senaki-Kolkhi.miz delete mode 100644 resources/mizdata/caucasus/Sochi-Adler.miz delete mode 100644 resources/mizdata/caucasus/Sukhumi-Babushara.miz delete mode 100644 resources/mizdata/caucasus/Vaziani.miz delete mode 100644 resources/mizdata/caucasus/caucusus_frontline.miz delete mode 100644 resources/mizdata/persiangulf/Al Ain International Airport.miz delete mode 100644 resources/mizdata/persiangulf/Fujairah.miz delete mode 100644 resources/mizdata/syria/Eyn Shemer.miz delete mode 100644 resources/mizdata/syria/Khalkhalah Defenses.miz delete mode 100644 resources/mizdata/syria/King Hussein Air College.miz delete mode 100644 resources/mizdata/thechannel/Abbeville Drucat.miz delete mode 100644 resources/mizdata/thechannel/Detling.miz delete mode 100644 resources/mizdata/thechannel/Dunkirk Mardyck.miz delete mode 100644 resources/mizdata/thechannel/Hawkinge.miz delete mode 100644 resources/mizdata/thechannel/High Halden.miz delete mode 100644 resources/mizdata/thechannel/Lympne.miz delete mode 100644 resources/mizdata/thechannel/Manston.miz delete mode 100644 resources/mizdata/thechannel/Merville Calonne.miz delete mode 100644 resources/mizdata/thechannel/Saint Omer Longuenesse.miz diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 9dd6e05c..28d610ff 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -35,7 +35,6 @@ from gen.fleet.ship_group_generator import ( generate_lha_group, generate_ship_group, ) -from gen.locations.preset_location_finder import MizDataLocationFinder from gen.missiles.missiles_group_generator import generate_missile_group from gen.sam.airdefensegroupgenerator import AirDefenseRange from gen.sam.sam_group_generator import generate_anti_air_group @@ -142,12 +141,8 @@ class GameGenerator: class LocationFinder: - def __init__(self, game: Game, control_point: ControlPoint) -> None: - self.game = game + def __init__(self, control_point: ControlPoint) -> None: self.control_point = control_point - self.miz_data = MizDataLocationFinder.compute_possible_locations( - game.theater.terrain.name, control_point.full_name - ) def location_for(self, location_type: LocationType) -> Optional[PointWithHeading]: position = self.control_point.preset_locations.random_for(location_type) @@ -155,158 +150,10 @@ class LocationFinder: return position logging.warning( - f"No campaign location for %s Mat %s", + f"No campaign location for %s at %s", location_type.value, self.control_point, ) - position = self.random_from_miz_data( - location_type == LocationType.OffshoreStrikeTarget - ) - if position is not None: - return position - - logging.debug( - f"No mizdata location for %s at %s", location_type.value, self.control_point - ) - position = self.random_position(location_type) - if position is not None: - return position - - logging.error( - f"Could not find position for %s at %s", - location_type.value, - self.control_point, - ) - return None - - def random_from_miz_data(self, offshore: bool) -> Optional[PointWithHeading]: - if offshore: - locations = self.miz_data.offshore_locations - else: - locations = self.miz_data.ashore_locations - if self.miz_data.offshore_locations: - preset = random.choice(locations) - locations.remove(preset) - return PointWithHeading.from_point(preset.position, preset.heading) - return None - - def random_position( - self, location_type: LocationType - ) -> Optional[PointWithHeading]: - # TODO: Flesh out preset locations so we never hit this case. - - if location_type == LocationType.Coastal: - # No coastal locations generated randomly - return None - - logging.warning( - "Falling back to random location for %s at %s", - location_type.value, - self.control_point, - ) - - is_base_defense = location_type in { - LocationType.BaseAirDefense, - LocationType.Garrison, - LocationType.Shorad, - } - - on_land = location_type not in { - LocationType.OffshoreStrikeTarget, - LocationType.Ship, - } - - avoid_others = location_type not in { - LocationType.Garrison, - LocationType.MissileSite, - LocationType.Sam, - LocationType.Ship, - LocationType.Shorad, - } - - if is_base_defense: - min_range = 400 - max_range = 3200 - elif location_type == LocationType.Ship: - min_range = 5000 - max_range = 40000 - elif location_type == LocationType.MissileSite: - min_range = 2500 - max_range = 40000 - else: - min_range = 10000 - max_range = 40000 - - position = self._find_random_position( - min_range, max_range, on_land, is_base_defense, avoid_others - ) - - # Retry once, searching a bit further (On some big airbases, 3200 is too - # short (Ex : Incirlik)), but searching farther on every base would be - # problematic, as some base defense units would end up very far away - # from small airfields. - if position is None and is_base_defense: - position = self._find_random_position( - 3200, 4800, on_land, is_base_defense, avoid_others - ) - return position - - def _find_random_position( - self, - min_range: int, - max_range: int, - on_ground: bool, - is_base_defense: bool, - avoid_others: bool, - ) -> Optional[PointWithHeading]: - """ - Find a valid ground object location - :param on_ground: Whether it should be on ground or on sea (True = on - ground) - :param min_range: Minimal range from point - :param max_range: Max range from point - :param is_base_defense: True if the location is for base defense. - :return: - """ - near = self.control_point.position - others = self.control_point.ground_objects - - def is_valid(point: Optional[PointWithHeading]) -> bool: - if point is None: - return False - - if on_ground and not self.game.theater.is_on_land(point): - return False - elif not on_ground and not self.game.theater.is_in_sea(point): - return False - - if avoid_others: - for other in others: - if other.position.distance_to_point(point) < 10000: - return False - - if is_base_defense: - # If it's a base defense we don't care how close it is to other - # points. - return True - - # Else verify that it's not too close to another control point. - for control_point in self.game.theater.controlpoints: - if control_point != self.control_point: - if control_point.position.distance_to_point(point) < 30000: - return False - for ground_obj in control_point.ground_objects: - if ground_obj.position.distance_to_point(point) < 10000: - return False - return True - - for _ in range(300): - # Check if on land or sea - p = PointWithHeading.from_point( - near.random_point_within(max_range, min_range), random.randint(0, 360) - ) - if is_valid(p): - return p return None @@ -320,7 +167,7 @@ class ControlPointGroundObjectGenerator: self.game = game self.generator_settings = generator_settings self.control_point = control_point - self.location_finder = LocationFinder(game, control_point) + self.location_finder = LocationFinder(control_point) @property def faction_name(self) -> str: @@ -437,7 +284,7 @@ class BaseDefenseGenerator: def __init__(self, game: Game, control_point: ControlPoint) -> None: self.game = game self.control_point = control_point - self.location_finder = LocationFinder(game, control_point) + self.location_finder = LocationFinder(control_point) @property def faction_name(self) -> str: diff --git a/gen/locations/preset_location_finder.py b/gen/locations/preset_location_finder.py deleted file mode 100644 index eb591ff9..00000000 --- a/gen/locations/preset_location_finder.py +++ /dev/null @@ -1,87 +0,0 @@ -from pathlib import Path -from typing import List - -from dcs import Mission, ships -from dcs.vehicles import MissilesSS - -from gen.locations.preset_control_point_locations import PresetControlPointLocations -from gen.locations.preset_locations import PresetLocation - - -class MizDataLocationFinder: - @staticmethod - def compute_possible_locations( - terrain_name: str, cp_name: str - ) -> PresetControlPointLocations: - """ - Extract the list of preset locations from miz data - :param terrain_name: Terrain/Map name - :param cp_name: Control Point / Airbase name - :return: - """ - - miz_file = Path("./resources/mizdata/", terrain_name.lower(), cp_name + ".miz") - - offshore_locations: List[PresetLocation] = [] - ashore_locations: List[PresetLocation] = [] - powerplants_locations: List[PresetLocation] = [] - antiship_locations: List[PresetLocation] = [] - - if miz_file.exists(): - m = Mission() - m.load_file(miz_file.absolute()) - - for vehicle_group in m.country("USA").vehicle_group: - if len(vehicle_group.units) > 0: - ashore_locations.append( - PresetLocation( - vehicle_group.position, - vehicle_group.units[0].heading, - vehicle_group.name, - ) - ) - - for ship_group in m.country("USA").ship_group: - if ( - len(ship_group.units) > 0 - and ship_group.units[0].type == ships.FFG_Oliver_Hazzard_Perry.id - ): - offshore_locations.append( - PresetLocation( - ship_group.position, - ship_group.units[0].heading, - ship_group.name, - ) - ) - - for static_group in m.country("USA").static_group: - if len(static_group.units) > 0: - powerplants_locations.append( - PresetLocation( - static_group.position, - static_group.units[0].heading, - static_group.name, - ) - ) - - if m.country("Iran") is not None: - for vehicle_group in m.country("Iran").vehicle_group: - if ( - len(vehicle_group.units) > 0 - and vehicle_group.units[0].type - == MissilesSS.AShM_SS_N_2_Silkworm.id - ): - antiship_locations.append( - PresetLocation( - vehicle_group.position, - vehicle_group.units[0].heading, - vehicle_group.name, - ) - ) - - return PresetControlPointLocations( - ashore_locations, - offshore_locations, - antiship_locations, - powerplants_locations, - ) diff --git a/resources/mizdata/caucasus/Anapa-Vityazevo.miz b/resources/mizdata/caucasus/Anapa-Vityazevo.miz deleted file mode 100644 index 6382b0bdbfd734998ada1028d3fd417f0f88d811..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9279 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 S{Hp``5u?Xd>3i&ggZm$jV&2^V diff --git a/resources/mizdata/caucasus/Batumi.miz b/resources/mizdata/caucasus/Batumi.miz deleted file mode 100644 index ac262ee80efa1e7cea53aca3da2bf35ec6ca4c8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8244 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 O4PJNU`|A_{@P7a?wmn$@ diff --git a/resources/mizdata/caucasus/Beslan.miz b/resources/mizdata/caucasus/Beslan.miz deleted file mode 100644 index 238fa1c1cef5e676b4ccc278db7db74c8b53c35b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9028 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 Bk4*po diff --git a/resources/mizdata/caucasus/Gelendzhik.miz b/resources/mizdata/caucasus/Gelendzhik.miz deleted file mode 100644 index 2c9144653fe47256b3be5a8cc7edaeb84eb09faf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9359 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 diff --git a/resources/mizdata/caucasus/Gudauta.miz b/resources/mizdata/caucasus/Gudauta.miz deleted file mode 100644 index 1411c3f33592cd2c9939bae9c401a51658782eb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8988 zcmZ{q1yI|~*6@RCf#NM*C>Gq^t+*6-x8m*&#oeK}yKC^`?oRQdL5fRX=yUIP-`j8A z{Uw>q?y))l&1TM-m6w8o#somZ!vg>SVnF+M^jmE-0H9DF0s!|iV`c8(U~Xf5lBpTJ z(unT6@dU*7c7|T3N8Km+B!?OnP$-(#SVV~^Y-#BdbT~0oDfkW2sSM)#|u2YXqzYN@|s?HX+ z4(G~%Hfe8Y%casigk8;YbZ|tbhpD<>2KBri*zq%-5SGN)4E5q7UZZf6=YH?+!v_rWJ z=V>*#+q=y>U8IO%arOa$|9*V~+D>7g0z9ru0C^bM7TSK{FG209&{l)7uxrDnbK9&1 zb8~a*d=jLcHil;n^1>%)XGzu{vo8m^rv&$lI>64pnLz8_5VLF6{oXAa+0H|4*wyW{ z6NH*v@Uw_9)shzew{7p$1v;EIf#xbDDt?YNpf6d)r9lcBjN4K_KeTH=Xj~tda^nW z3NIr|YFR1!xxL-)S>+U0L%*=jcCkHh2q1bK?7U_Vv5b_|cu+VdOj6-uOi%oLT008Y zZ52WgXZFY#TRRU{7F+iGeJnQDkad{jeNHjFR+YyWKw8Yy4*Le@c{$R4Y|28|2gkik zyFHStk1L{fJe6>N!><(_=WoC@vC;aFhvBK{mJ<2atR~x)`k&Zvp zsr~duRfo&VXNb<84B;)xb_S*Y;?`0#qKfHf$^6WEa=0zUFQgr!7VWG{6Ees)(JNJ^;4hQ`cL(szW?YB9{k^4d8t z;#B%G5r)QO&B)0TO&IBz7nSU+hb1Xe6IN$|WBEIY9n4fMoC0Z|hQ?aN9F$b<$MgFN z4a~_uvce5Ay{xpUlYeAp9kiz!v#XPqG+&Q1R;(T7+HIkKiH1tum@GA68uQ!BoTMF} zz6_Ncxp4stMw}{-7&#epMc1XWIpWZZ(J*F`ni7F;a*L81sWGdwm=hG`>S;s8qugPh z3TZQfFVRB1ZHXzA6d})#iLCiGlzg)UG14!8w4z*Lv+q<%aLKhmXP#+knm^u9$2Adk zgx_Y=5S2+IwgMg|^)H`8sGLDaa+1s`m!+y_ThGzR9ONo^c`-En1jQ?lzd?ysYB$5W zZ>v8GxS^V$**|!m_W=zQ*x7~dze(2yKUg_~s@k5~448au^P(erDOELUw{0a^*U1`f zPPXLrKOLoY-pp52gda&w=ZCRBeLgQtYko7hvbXRcBLow@ZqD$qh+LZ`+qGDf{w{B^ z6?H`Whw3*Ar_;$KJe7MM+bso*@ZrKMZ@iFbkV|>E%C@^YPzj#qoypj>t0;?@Nzh2O zWFeYVIM3Y=NxOzzw{0RHS6*F~*$amzxwK008oUSA=(o$@=X%X1OY7zJcG0aF?!kdr zhKuEmy>#^1?lcQoj_)bvH5@r^A4HGGxW(<4%@Cs(8HR$dJaUJRx#-v+!{O@8rs_8B z_+9wGE5FP?+*qeomGv!4poL(BCg|ncP*mWhoyZ_%1vVH3awm}8;E-Rq+}L9T-dMlC zNjgB;+dFd?_i@^hL-b(xK0Q9Ose6vxYgj)IbpF1@t)FVLUg)}KW<5IFv}p6BgtHln zgeO;%yIq|pj;ch%_Uhua+ZNw=sC zI-L_Ny(AQ)cH8W&AAr9yXP<;Y*6lThbCyV+8K7xnX?Z!yq`U%zQs*?-<_&91Oya55 zbv8*UxnKxfv~SMzs_7h52w9T_qvoX*sK~HBr>tnjj6RaazbkB*T-}sJ3OtlByElTR zIGR$xEHfg8j7uGgfA?l+beDqpemCx&B@Tn8V`lB=EJV z;S(nC?%fsdG+*ty^r>+Xryt;rjMuWys9s*bClZ_yu6?&0J64TQ`%WZP%pVw4tb)j9 z+VRyf7_mIR7eQv8pK~IfoGMlrZ9nkW27$k0p$k_4NoLEVBW$N0m*Ej)aMflMF%UMN z85FRjev#Ty+N631UrHD^Q61$O9Q|#=mSX6ZY(dLHC?@#J9@3ym3OS|%9K~;@FCF8L zOzF&Nj0X}Q8qgpKS!J@u&<0KD_|r+~?}~?}U$ag?qM~Wa3=&@Av`<|RYf`<14zpnn=HtSeAU1=UdIhP>1x5;?Y}63v-@1w*;T;-WY*W;>fvw} z%d|U16E{k3dCZ>KnmbWyC!VpfLC33;py>PPMeA7*)S7Ek1M*jGWVK=&Hd{(w4@wiz z)ASu)hlQIYwe5b@!g{0b^o5Jy9*6Wrkhak9K>L^4l+0Cgf>od5)iaSywz13orz^k5 z=x1_%8QbUkZlpEMiMTV}&gV}WonjW}UEOS2sHk&J`u77ODKxSxK8w?qSs#X62tj8R zZBNv{h}ysD9DdJ||J9HPisUMOCesL;X7)&~@d0NVBs+MbKq3D#c(|Z2=z<)Zg(MY% zE8eLo_!H1F7)wWobm2n%YshtVLF3}$-ew?6mq)o&BP|37I{_gLp^Y)Yv)y z7QxV~1|Y*C^HIPerN@`iZeN|Lt%Y?50$~5dxXhUHV~A1BQLh^MMd+0ddE_KDvg#tn zneyL9qvBO#4BaS666iDop6_V@0T6$n=+Z;LMaK1%MNVSF{qn9VWZ=;GHSXR>W1D;w zWRrNq>f(JaceI(MVAWG)OGuy45Im_52tI44*~Nrv$qH0YknT5)l@Wj{*jW zhuDa;&@1f34Gr}D(cM_vqyPssGW1T(iJ=;r~jJuO70g2oy}#O|rigN@!!`VsD-k5RRFH+#T9*39p18aOoGE z=n)E9KfDC)3$C8%!A0G9G1d86oL{L%>z@$RG4*%lMPRxO2{FfhWSar>U)cO4fGZmY zi^fL}zk-it^{zSu?#LM>I6gOIWq4hT0GH=Ua0+41*_HtoNz6^{6#5oCMB(4Tu=7 z!gNCu`?dM{5KA0>>?8t8%7;J}deobeOdftw=*5)SGQy5w+%vO)Ha7Zk;f{2#(=>M@2e z4{VaKQC{r?yh5?5$3gX;_F2trxWcA;9sLIBxr!)!##~!l{&(;A+lqnrMe0rwAxi=u zlZwSk;K~d~TQ<=~?FhkK%#pvVzihQkSn(#|ji-@+E{zO&sefiLcB)WyuHNpX1WgB{ zib=-jQt7Y`6mIWWA~|K5LJ2b-?{|5l9Pj^HpV+Lq+3FPmz!Mf=hWvtOj&3g!ZG3Uy=X*`e>nsRL zzd+#@MYSVFB0Qj;;UBiC7IC5Qpzw4d@eI?ewrHVl&a&4~2G9xdqpi6;(w!!2QZk~< z;Ru+s4PV$kKfu)~CEPBs$ftCw&P7)mD@P4CRI?Lz2%}6k>eEH(6&UyjrAbBzE?BM( zXfATLDrHUB(@nv)r(;JWnSs$wXhxBIVj=_kFWU$DXOR zn{VzzYcgzvg5kLzD-(i(ygzhZK?N187MGsMVDG1@=&U_2s64- zpcbIc0q3&@s=gk4hC8-dCqQE;&X8ST3e~9cg}v5CKl32BEcv`WgNy&ICzYg~cix2l z%u4<7dP2u@`M#9YHFJB5e5q(?(S;4|Ad#+dM`w8lpTzEnlNV=Iy|r{YWmF=-K-@1+ zPaa+cvy2gm?2GQeI!*^zZ0sm2M?bMi9AW;s#yo4-4d=?&+Corb&S1G*xRcjIL%%9OyjIpf)$rOI%5GNIi8G>b7C?U~5~o zWg4PHmpPH<@D^Z-enf}+Y$VCnL#rKvWk1KzuRmqHk|(X5-{w>~OLwWs@#|8N3PdfiXDw%tj_#JJ=Kc zxiq0saqcA{LM9o~L4Nin9tzJM2x#`W6y~HKO|NQ=P)tpldCKIDb9=N*oy;Y{qv?;S zu48rw;*EoKxztZpO{O*zPNXM4d2q+0#9J+^a+##QSUf7sc^j)f(KXDiER$JXd9tNP zSy&gYcl()6FOIO~&5vg|r7c!eNI!O1-!IXesOVrdq;eH#Drt+GCzUr|n=0c})r>w- zWw3gV%Dh!(1GO^nOV?^F=Yo&u*cuCuDZG|GuCqGt0UZptD!aGFa(P!jCSEtWxrRw~ zE+vf_v^CCEHIl8CQ6BNgl~WD-oD8W~HIE-OkLTul?O2Kb?!8N%$V$TL;67(ps$jb+ zTOE0rm*d~N1mBTHySaLWa1yyYq(|Fpi=-d6+{kL}JFccuc0Q;)cW&6u&8%SEfTy15 z#5LdBxbs*BM4?fN5JL+QLr-H;CO{H=K!YSfgEU8Uq=eymnfx>VQv$JG;9uX(NMN|c z{yndT=;$v1A1DByPc<2e`4iI*4c-q;LMdB`M4mzTkB}l}v7k6Si}=5_U;d*pB41TX zA%~4}L?s|Gghu;J+}}yc~?VjDSDwSsUBe5L%dm=o4h49H>b!F(4Bsw&y_Oj#sB=obDR~#hd7nJcM5XL>P0}8tjfgl+p*mQ%>Zf|A-LUs>nTr22I?|~gtJ{h6jbi76K z*JX0L?0)d=_#5uoC*UPl2qDUBl$Kpm+bScgY8WfUVnI%}Gr}=|o32h5vwD#p0!vfi zBVtGjssZXaarwbzu z95(C-sfl%^(|q#jF#b9gD4u|sqO)$)PtXFm$q;0#c|AKEJfH5ACJgaC)#(h&TZVY( zFAU#qiX>8pe*9*RtG}R8>L#P*&Vz8rMFAo40d*2XWEIc9o7${$pUBpefw5vZXqPoCzMRY*8Od0gN zElbJ>LH*Td#C7abSu%Rmm?JgLGLXMh2?1TTPsAA`De3cD|BKJLo}?TAldyWhzFFgk z(X3L%W>>k|z?sCoScCq-xWT%Qa)jnR2?NLvemoK)G20&qsUd;c{9nU~V?$U*ACltl(rGY9qW1SX<7>zCen%kSe1mqm zR`>W5G?GFg_f>&1C1fxCEp@h1^gNP!?Q@IA0{!>**O~1c&4H>t;pOl;pX%GJZ4#}71JqSb4HFvYvhDs zxnQWu#acLg5f1r1XxN6)B-_a##6NuP!}QQuj>UB;sDONrJvdIXro?_jA5-54Nuf@f z`0KRe>_&bIy4&l3F~UByU;a#LGl7n#DL^pOGyS(l_fcG|(}`f2BV`PJ4hcH0beI8HByPI*rPehibIsLu=p0plkz_) zEIrqYNv9zz_}^ch2I*|N?+m)aLfb2G7rW+R)YiNrx)l*X- z9#)vNV$tk2pJLnOgj{t|d>Le+X1>mERv!RYNwzd%2yKC5ykLZVdxWs;H5D>qhqGr7 zH(G5UeI4IkG~)_o37srJk}`x9jy>U&&KB!xd@SBTmU9h5K#0A$V=zbUW5TW{QpD zq|u>M(vkx1vz3aB=w%_@PE_&qyYURFsMI&)@tdBk+NO!Z!<`-K@Aalt;$KUpoxH`$m z<_9f?1l*a&LD;{+P0gQlPso`MyXJPEBx!({EsS`)4V=)CY~rd_^jVD#4p;ReE8jg? z8Zq4<&qVXi^?t3mC*D0|niSW1#j{^IINann;?uGnHM$$@cvqF-OKw)x6(tP z_ioK8-&WtG>+pECnw%mU-7|=~A^q@`e&?H!Ac`Az|$K zqd4q8!nbwVEiEZXOU;rj66TIEvD?7+O5Hzv(6FC~n53Ph0c^U$8FJ@Rd z)$@k<7( z-`E3wm-^-L;hTXVTRO|ol`(YnSF^8dqlukCK9fxb=c-4stXaniTLS~$1lAliR2)v% z0B!r9)G3^{-@yv;vbsc1HvQ6cn&*XKP~C;r&u%hQ_O1r8NJXCdvpAh3M;CF1vle7~ z>u6D<8-#od3BdFFhOV-_1uYUc99He0yp5|#$>CvUNcQrdR5)*is5sdJflb2KQwk>@ z5LnmdUL5)0$*P@9U70>+?+4;dHjD;e*+|?NDo9!^CRI0x7O@P(RdB2t2(~%L7PPrKlt*#>{oO)(4~~gWmEE;8yWIM!ZNW9m*)_$ROP3OlyKrsR z!tY8}bjDPk_UIbqNcs+8OF4=#^ACY?B$k+QL#nF>mSb)^z*9?ZSzgq3=TCj1oTURn zKuhCM1{q%j&1rcW**+NVl!O(!Po1EZooUm9R>c+R3jbWWx+p%&lH@vIK^XHb`SWc> zxs)8Umx&2cHvNRt6TdF=R+R;Jzt;Et?#D?JW@Uz9G%CINJDb_=y$h8YJgXnld07qH zl7Ld~)--o<(FvIf|DH$z=Gdw7gOEy=TZxRc(Nv?E!Dym2jdHu#l4(4=`SX+ ziuh0K)$WnF2T&2{YZphfxaiXN9u^mM%?=KZpYUWyK5}4y(xRR(ECXq>J`2ov^kR?p zudFQ!G5cqJJSR0SNRcp{U}W?nI$wv@_O!ctB{&lR!F2F z@+gpErS~o&bo8Lb;&R()OgTz>>!aLv%e;1J#4^?+q~5`H@MDnO6J=|U`|+qUd482d zOMVvh%n)C(XDpa=WVDV>#=Lig8o?@i)a^na$PU=aqM?`_$>I%@Nq1t*bJiFYi?+ipqO$hvXQa0!yN&Q*)+8gp;td zFW=)sZ$5l|5MeaAA6^}dcXj28Ajn9O6Y6`k46f`oqc64%^}LF-TTsa8-Bxe>H9pS} z*763vX7{qNNZ^eyn*d(qA9 z4@W{U>QmI(K(k9#Y={1_&-!p`S--2*IJ&O_ADY_xkko#2fsB6e%qVMDd~NckwgN3m zsv{2?D6?2fo`>~#YC}1G6g;0~cbOZrdp$m+ZB=e7%D4dg;Q^%vj&i-q=BwMn;x~ zju!Ss9O9)2{l9N%yyyU4S|A>VeEc76mcNbsU1|TvNF(}R zM*dgb{kN6B%j*AFLB;srMfTr%f0sZ1(aXXD0RB}B{jKzOcK@T~MDqV~EiVNF`?>)8 Og278x@?Vw!fd2u2X_N8* diff --git a/resources/mizdata/caucasus/Kobuleti.miz b/resources/mizdata/caucasus/Kobuleti.miz deleted file mode 100644 index 0c917e55d255bae0aff0ea31926c51f798590905..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9661 zcmZ{K1yGzz)9x-1Gzspm!QDLscNPl-Um&=k)ZE3UYE zdwtY#I8O$xr*`FJXeg_e)O@T(S-4Cc1N&5N>uZ4-@OD+GyV<>&l7A&si=szH&$vZB zPH(zDBfkbd)2BcG;G_oYo}I?Cx^TYAut866EoTud(A+hZB0hODpzRo%lu9D$>xDl% zJI&=2)wR}ElC4={bP~Ju;mom)F{?pE!0n#k4Xh6zBbL|>=lS+x@dtlr3gVAN<*m)# zGravpBnZ1CefqLP-ji`e&4S!x^c>tAcT{&@!;-)4>VAu;G+k-Yz39wQtl3M&cgNZ? zpA_K<-!UXKfeIH-cQ`s)1%+;7Q{_*G=kDOHCrvBYS+j^3UpU>a7?fZnJ zQGmXd?}=k3mWqp`wUJh;&NgW_&Pdw~jfs9`K2=Oa(FMQ~`9(X?n9)l5TdfRiHBKxy W8CEv=5z(_ifS3xeXqzwxcUvjJm`Xc zs5_VNLM5JUtyw)&yqBm*sEP~U)b_P&$C0P1veI@h57Y*GtHsNH%I~y=cBrNG&{s-+ z_>tSx1mU~-XTfdX#iQ4n6FxT&AZt2hF*c;wWO=q8TfL~V5a-Ajxc(hjGSqxc=3M7h zr!`f@<}zRXSY@btzdP3E5l#OfcSYrKIr>SoUG~+>DsR;xXpo(q&fCx0!{WF(v9O%6 zP=k)8zPkdXqBm3y(lQ^QS2sbqsBgCFw870E#B;HyBd#v&PIBvFuH3N1PJvm|SS$<@ zU+gXX6foCYqpk3yP+;}yYa+%XC3$gm#5qVCS;qQ24kSH6&m3HEaV1ShRUb^1gnww5 zAS?aBh2qdo4Le0_a;8y^ylEMcDY#&gnYl2rfwzv`q%003YqOt}3_5-Ck+?)c##Fd* zp_89P_rbah8S`)rBx`{hn*?(3bg2c&np(!HgBI3J3lk@g;*&r%GyF`2#tmUwbkvqn z1&N@At|y%t`bwH|CTf;KtE6PH)8szdVB-dCw|aVOa(9zK4SPDO2JHSaq(zGstoX(B zwI4zCa&q`d%lXAPg+j#Q)CEFlDoEyK;)!C&?wqM@SEp>A2Ag>N-Yt8-55Eu9gMV%= zP#-!RMmjaQ%xuovr+`z+r>YYOZ3Xny#bZD7ck1$Y*2vK%I@1NaKm?{sFy{-gp4t~1 z$zu~P$H4AaNwoSVCocL|%r_N-S-7{vmF*Hra{GPMdPMVw9s)Ms_N_H9S7sBgbyIx> z`6IU^8PnC@L3(4Y68`)ennpx+XC24sqjHkSs*JjrWu+ zFU3_`xO9#kW_TheM*73Y7KbBTTNuL#k9NTtIh?(B-rspdNarg)wpde^Y*1Emxx_y6K)kB^X+7WKP=dMl z*xHkkQckU7`RQdWKr9nMo|)4-&}K#gl`KPH@vs@&@Mtf z)+Wv>o&sauJ9|=!kBLAs_UPh!Gx}&zj`Pq*E7RWIT`ezuYmY2#$tFI>Dln7p4U6;e zNIVK3e`K9hBTtW-?DEE{R8S@&)LmgAxln2g8JlocTVA|huT`n>C?y5O6ScPG@BHZ2 zN~pg;)~v-}SnkjbSzZ}k(MT?gP)+Z@MD=8jxFYB8WzDbCI^>1nc!nMthmiu}D_RqI zM_1FHbW40zIz-4tMV8EPu^+zw9|*Bi!kfaG3bJq*A(%QvO;6=JiEzjoNVX62&Wsl7!_xlvLLBST?^*~Ov+=RhKI6{=I2@kTp8TSK zB@5WV{%hjoe4E1g6r5z~3|-&Z^WmPR_>ABEf2dEs5BC$LPH_E0N9%`w&%XZtiO$Ur z|N3CK|7nbXf7XwHLsmv2=MxT=f?IYRJtHh5deP)IJ}&|Aj~Ir-!&vh5FygSAp6Lej z@ISNlF`e#+%u2H_$X$LV*b!;rVv5!GWAa4n%ON_`LUtV;sz>Xdf=hq3su5`!OL6Dh zYF|-lAH_7>AFgSY?Me~1t+jmhr%Dqv+SvK0Ia=d_DohvSN+Mn zEJ}2v6xA2Mo`1kWRt=znrxJdHfr3lr5;;2sc4ooEgpog3b~)nS4>byaX6pMH`K^ti zhhr;OBhZ^BvSZl!b5R5ch^Q%ItpUm7k*ioJRAJO!{(&GYwq=e{&YB~r+9k(m)=fxA z;}XQyE86?hdfTSfhyDwqB0D@iJpBV>Nh%DsXO0mK+Uu{}(JA&H>1l%Oh?L8#an=tf z-}Mp|Xy+Vo0uxj)NYkZA(u(+_e*+~)AL#or!=$;;R zN83h7I)CO%xM~D^1A3PGM(Z#FBZRzw?;XNm*B#>|0~)i3nNpE#AS-f(g2Lh>tOjg< za6IJ#^K`@Ve4eaB_K5snf52^TTFzig^e88tw~ zeqd@ilP>&-q*&Tj$g--Uw-FocA02qcEZ7LTC-M`m>eOa~YH&{$NyTk-Dh!V2x6dsj zH`S?e^VRQP8&t#mtKKHhh?9CI$`*p?=o3O<9&Rs+mSFZ{tvcujNqT5)cob{OB>){u{ z2Ez*fgbP#%))CGvZ@4ycI1Ej(Xlir&he$XOP;qR8Ar56C% zegRHF#clZ$Zrn&dug1Cd!zJq(REXCDDlXwKVlwbc<7XUXM*<_g1tqNm;*e9-ZbO|? z&xpa5a7c*QL?~2(0o8y%tWUhKk<@-{ekWR;@0$gs4)Lmsr1pJSw*Zho`x3}%dKSn= z0t1FIc=-ojX)z$j=EqEuJR%AhCBUd>Y>R@CpEe;uHg;6nP}p=Oxu-|tkpwsrDsAxyapch!Ap{FPrcH-< zBdn$ZiD3UgpN0r!knMG2PLO5DXInzKU}e#-ISV~J*>*1xRR2*KgXyzl3F)AAun{R7uDBgZ5(cbaqZV-=2mtzsR`H%NCqe|K@{blG>JG+F0dDD_Q!nqn z$G0Uk2xi4zAA5D23x+{ZgC%~(m{^~kDA)Lj`|jMP-@nA;Y?;dc5a+F5#}sq_@^sE4 zk^AN)9y3!IGvWQ6zjMVcK92MJ>@)j_yGd zaNm7K;ZW2YEHKX7zF(SXk10mR9Qna%8+YT5_3Jb;0#@21S^21BiGrWD*NpAez!+VX z4t~T+p%zyft}pksmKR~wAbwnoEXL`r9U+tcj4FCXaxOUU!ZF=@_;~8@oNzm3RYx(@ zUUzV6cC8~qeV9*|kT`xc-XconodbS0yY=KzfG0@ zZqmKMSCamxE|dgIi24n_M`4!7n2a)6TDr}(agH7zKVdW>DRTOI3&h*ZLsod6)5+dQAE2JSNQ z4e<81Ff4`U6&X``kx`=mlkRywNdYWwg#O!&*xZxpk5Kapxb0Zt_vfkmBtU%1-l^;%=QB47q9am#8#- zjn0+fLZjPyRaQeH%rPcX?U(g)K{k!Fg5M3N?nFL zV{Aq)ORd~_wM`%SwUbE$E`i7w$Au8rc^gcwx^qv>qNx47^fKLKiGjAN1!HMag8HAm zl(NK;x?2=wX7}9<$inxZwP#7R5!XkU35iaIOf(-Y?|z|edvfXAlPdp@x{p4uoc%Hk z0Pq6?0Kk2!`&hBD+OUXANWN8`geW(n+G7iLY4zXVI<#2=%0_LJ$-(uAh4 zrGxGs`Olio*D+ENopaXr2lBMdi;qzNk{|@PJ(b*Jx>s+Cj0M&1MnD<^;T^D`MksKDNC9?9n_-5($=eT zPfd*fz2DZh$6USuTT{T%d;WVbgrXWGS_POiUBCPRmj(@P&5!oJvCPmih z42g+=-HAcMR_4;s)K)r(^|GLY<5ly`aJ4cH6A3|bx z&|jtX+5r8`)tty*-d#vD^xGeUT@W5eLb}V-leO-J7+h^@MjwFx^F;7TeW;{-Itg@7 zU%bChgteg^$i&gs*#T@aq`-(M%QVWuB&(?YReiN|t5kjbhr0BLoJzlvYQKcE0}${( ziE~g9W$T}gnurGgfc!-4YUp5MX6x)|;&`|ur?e=F6$1743Ep%r#>jws?-chUiGD(( zbOF1RB0=7OzR$(m+yniw6~Ya5!=RP6TjU;#%i{KbI4U)ckGLdz!`hh@c3>s_ojMc!HJi(Z=(7G#?%sV zS6X-I@P>+c!Vt&Va?e@XNK5mPamozVa?Y4Q?_$@en}q3b4HK(3m$mRwBJ%Cf0qNRO zRf@?}q3+!2$0_QiT@^JgoDT`Oz8?}e^zE}$?c*fv9fvZ)A8x0{vV})SyF+MHeNw-7t7@O*nvVLmle7d~GIJIf3w#5DQVp42zHn05Zkq4&JS zwYBzV{7uABjCMXH95OnhKN_Mx=L_UQ(NM%7(WjOULoDl0^Pj##fv75+7}--(@c#nJ zq5MxjxJB;jx26eJ4%j|Buo#y;_EQa~R&ERJBD_v~6) zoh&Wvb-y?z8Vq@CsVF-pacr36vp02rV;X)k%&6xPmBI|jJBiW|JbO@k5udi>C(j8( zjs{bLOvNz)Of!4-0K%3w&3pH+13G0|jtTPrkAPk|CG4l}hfA^BqgR(1hn~lmZR=S@ zZw_v351T(R!0HI{ccR{Ppxy~?JE{($dJe22_^e9BkT%6J8dKsw3wBN@UE_ieC;~~Z z{7Hp;Xv9OGRkpLYLYA67wfDo%=IvG*{zCYlo4X0oilZ6@0LWtl{CR(1Yxn%m;rK&a z%W=IC-6vZgdU`fUPG@daM30K$Ji0?KdvRbz9~>YpEtW=whgK)m!u2RLBDV#MJ+&dh zxK%LYY;G3@a-+bR__&?LWGMN1HT$>w{)A^1#%${%X}ZU~^SM3mf$_SPYuK9@r>Tta z?#g2&m&TbKp+j=fy$BKO(npfY9YH$j#>AvM2ey!37m9jQ%$>|J#8n;fHt28RP>8@m zg*@1M;RS&*nwX1&#r%UaSA$-7Vd;B|~vL*;76`{b4-HZIL@t~Yaqxj8OK2LJ)) zT79gqb1XpY52Dn_p{b}wsH0@%yJsQ#{mg5({nTw0b{|j?CIb;)kKwqmrq~LI_o%ZL z2!9re+KAwLa9qjd#)*_0`>A?VKrZ*ZgmQ(iEuUnBq$~Uh5Lo*aC?I zDHQ!?a>M!?UX&uEf|&~eyo;SSTy7e6C+0Nx0QgvtjwIj9jux`640Sa7>DAq*k9EI%co0j>yFV@Z*KsCok&$Q$@VCTHkq|KqEI2-E=0Pq z-MvV}e6P^XmTT{JT8C2KD1B0)O%2`3K&Q`9jhR8ws(EPgoMq`HxcK~?w=qcbzQm|{ zPB5)7nh4>I1;Y(WOc42%Ixlea1L4Q6-A&dn51(o~sKYq{In6HxVk69FAcAJuTose4 zpK^!xhpUy|!SlbME0=8I^+Vbd@nqy4M3ena1(c%r)Pv=zw-ATtT37)?fYU!pzO2e~ z$r4-F1M^0UGU@kG+pkOIHMmaiUA9PvFo^OmIcOp?m>2RV@A0Fw&-xriXlq4ek$MB&isbr*JACED6LFv)g!V7@ z=#K$9!Is&@+X-hgU>0LF#6z1Vy_O?xbGt%CZf&JK)_LX#A}#%0qiVUNC7j`0btMH7X*vW?3 zxoW0jEUmOu;O!#2P8^y&^ggadNz6_EwI8b@?9`_j=+|9_CGs`xSQ0yi5uszGZ3m?A zoMjCvGRI#Jp6*P#zJ_{!-DoBi>gvUUebtDQl$>)?7~AK8j72HZZ0F-)jX@!%Ur%r7wT@&`&XW&A z_-Z$XhQj#7R+1^}2-6%mHnzvg+O&jMEasMNlSv9Jjec@`v>vQT&lz$~8Tzu_vKdO) za-3n$)z9%}+Sh7^7gwHzl3pW#*4trR=>6TJg<9tpX9~afaMPJOCtZo;P;|O()I0U)8ZQM1fFw8$IF5SA|~$r1`neRpQz zFa2eu;{0W3I8VVaF3)Pjx;~Gk)`?ixG>+6! zNORvI`>zx-{FL0q@>%ATBMRhcD8IVaJ-3U~nz>n!!c=@9Mqd#w+s)a1YwF-HzCTb9 zRtY11KfoX`muc->ClngsW+v4Nk6)ocsxZa89B|8c`9bh1KpDM0I@erV&RC>k%j|2a zlID6Gdouh!aA9<2Ws9L#?zbmJmeE`849+AC&w&AU&<8{6O=sN7sr}EkL9;+lM>XY8vLgbl0oO}s~?fhc%;I2UD;ARwuQsiYg zjoVJXe;N;-wxHVji55M)LLxky$Z&E~-%*x7t3&RN%cZ*`)UcGC5)p2O;-D;}!FMG_ z$Hx=I@a^5jgvy~O5c|U1o44S2ylUgKzCsU&&n+308?)X|F$yo14u%PvUDF-dB$y+eN*;&K5DPXAC5>ma8 zQ(X+5Ka;-OL};;jhhMU&H==RB#aypM-m?c^%3Fk0u*aZ8Ziy8?pt-bbIpV&-aAYZ< zD1`dmMW!c=ue2|i!O~=yRlyJG!=y5!V$TbK)Wk(*nfBJjjmeL@&7ej3ihw+&+Gt_R zl9XD8!f=i&nun{3aycaqZ&Op!9F{TXdl7w(wJHk%h)(Zz=iRs|hdS#Z8l6Giwe580 z)~Uu6q4lbKes=x3ETB}NIo(4_VoaeTpest0BW|L6H?)%TN;)%rIL&yfKZbN!yWBpm zWRj5ZCN_j=tXhpV7vA%Lv}7gL4I;9pG^58}oA<@Tb=}eQ&4n^!((dUgO zr{oK(a9pxl&-41%*7?yB?LQBVPH*=Iz=5xYds-J;vjkNh?QbVuk)rL$NzEdVZi=+} zbi--$o(-ZY4r_Bd7Kt3yy}v;b=tD(fshJzn;b+d+x}BTTH{0DkxF=K`qUObHO^<#! zwG3j+Rui4_?8X^}EH2MIRYE>fpHP|UlXCDSA( zDBch4?iF%l3o1#Bq@IOxTr55%Bu<`8*!=D*4XOL-=)USbR~*Y{#+)NvVp?t7yQ}>? zUa!}-1n%}LQ)ZUPb(E)3e}RcWT_YiUL&LSq3g+EI52jL%WubWL9nQa7?snPh=}e{TGG{IH16wN0PF)fkE6)x4z8YDJ(qR#e`7HK4?H zA5;=EnU*^#{w^6O=Zt^|4to3PR-DcBW^k!L!Oe|7k~lL}Nv!A2GNiKGjHTEv%dmt+MWV0XaZ6JP+$5=Fwre5k`AY5Sj)t7tMqm zPYrW*B-Erpbyb*Nr#T6twSF#^Qx@bpm{?Iy7(Sj!wm-{@T{s8k!1zp8brs=4_ZLI& zDd!i7jg~~5JP_{2-RoqY&g*3>f*1*@LDS0>w(Qjb>XINu9GvXa`P?N2%eSDqcJ^|i zLB91@^NE@k@pl2)Q7sznv>M{=(o|CnQyWubT~+hH#)}^RN?8NnY*Ia^s?T?Xo^J&? znVA?mIhZ&qGAbxCGBd$HsRN%f+W-9;(UT1T`26_u`S()M-$sAepq_RAF}ixXk@UY* zr2Z!PJ8l0j2{QIyB>ztm|2NCuIr@KDz)vjy&Dj68`#b&lubt>q4dLHp=x?LHSNFe0 bI8^_4t(E0oz&{J%pJ4Q~DiTj30O0=s`~%J7 diff --git a/resources/mizdata/caucasus/Krasnodar-Center.miz b/resources/mizdata/caucasus/Krasnodar-Center.miz deleted file mode 100644 index f25e54a67cfaac3c589d1babf2b0bcbe6c7d28ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9788 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 diff --git a/resources/mizdata/caucasus/Krasnodar-Pashkovsky.miz b/resources/mizdata/caucasus/Krasnodar-Pashkovsky.miz deleted file mode 100644 index f2bd0f4f2d563a4bc72fa9182e5d0b73c2640b64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10776 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 diff --git a/resources/mizdata/caucasus/Krymsk.miz b/resources/mizdata/caucasus/Krymsk.miz deleted file mode 100644 index 3359c2b89f1807f069c0672883803560e514c4ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9446 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 Mz`Ww{)J_2WA7^=HjsO4v diff --git a/resources/mizdata/caucasus/Kutaisi.miz b/resources/mizdata/caucasus/Kutaisi.miz deleted file mode 100644 index ff860fece1523d648c6f5075359c348e176da667..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8953 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 BZfXDk diff --git a/resources/mizdata/caucasus/Maykop-Khanskaya.miz b/resources/mizdata/caucasus/Maykop-Khanskaya.miz deleted file mode 100644 index 92f139ee63104f6cf5fad4ecbb580465987141d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10178 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 diff --git a/resources/mizdata/caucasus/Mineralnye Vody.miz b/resources/mizdata/caucasus/Mineralnye Vody.miz deleted file mode 100644 index cd84551b939a6c9491ac45e3207b77c72b4a4a84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9535 zcmZ{K1z1~Kur5%vIK>JD0)*mLf@^Ts0)-;Qt+;!EQrz7gin|vm4#nNwiv&w?c(kYI zp5DIi=FiSf)?V|^?3r2lGLxk!^8_9j1`Zh+1_lNI)7vADrGx_m^Su=o2I+Cc(#+o8 z%-ZTOP1|}yJpL2pjvh^$7ks8Xk4G>jh zzICl07T8k%rf&ixCSg>rk<16WAYy#QJM!(l&dMt2+QT`||5E15@UZ2oi+$d`e%k;? zgvqqvvR0vYn=bQkiT0vqX>%~?aB0elCdq|yi|jRFxn>r}Lq-+N(2oTjW0$gxH#xd* z%gsThx%wx3675!P-;!s>x9`2;N;XvP>QYL*Al%y)8eM7V(*$RQ2vc*h)fK75BTj|0 zbPI-KbP%c%acJ)p=@0vIeDT6J{YS+t%8Y9aDV-ODl}0dv;%#0T3{?+I44RgRnO5bl_XyT8D1n9*7kpMzsL8M#_#>;Mpo)% z%MLg|KPg+9bJ~|yE9UY{X6A^97;}4GpdHUhSGM69Kaa7*@*cUjr?}T>SIa?NXyQ1P z7Pzk+e?&01T_mnU634bW)T^E9)yO2$7TOqSQYE8A5=Q4S-x;LNwi;H3 zBrSKROJ`2c^2I~k+XFcXyMrnY{Of6sta8hBE#}2Hc$fRT8+7R_)}+bxyZTVi*#fdd zDU%k{H_w~7<}IfWEht0C>TQWgqNQDz8+q9~4D408jnp^4Gd&l0n< zC!$2>{IUF-hQ;z6Z9Q(a^2GNOE|o*m&EaMw5_2_40_ANRg*P#bGzn)1&8BUEPB%*j zWp*#U11Gf4tZvFlQO|VaROf7*=y_b@y%)AI2u}2_?pp$%F(T){=JXIUoi7#J#+f)W zCMxN@6A4BZnbR&Ud>FF65mzab!?NqBMXoh7#JkG03Lod!Y#eXl8CEyDniM7v2odF# z&%avKnqebo+RWo)Hj*#wa}BYPHu7S2mR1!&tk+JfUtP91#b0!iN?bX>@ZGG=IXaMJ z<=?|6I4j26c?PVlJA1CYfxn{?JISaf25RhXdLwFOD2FjaSu*hc5U`s%pOBOzLSpcDzC~Z9P{;o?SubQ}t=0 zeiK@)<|>c)>}e+fnKg{at!msOO#^>=3(K8qV40qVfXp^VNv{>ohB;w@)%J z-v>1l)h?G;pBHjgFbGsk-YF6tq{2l;T0RBv~yN1n;Z)wQF1420}-yGYJFufy^8{SWF75wK z@j0?sE})EI-zqmQs7{^n0!WJx;}hFMgWuHq{YdSIbI-t`&;9I4hZac&WY;=;UBSPF zzHRxSPtfXGJ(>L|++>p84-(2U=KDU?A%pwyMM@ANU|ClWabvfsxVjP`VvG!s(03%} z31048d0U4R#FNkx%0fftije#4_13qxq`{;6AEZ`0oqAN79CrxsDriH_n9h;U`-J#~ z12=stB%b4$pZ;(Y#V%D9s@)7GlFpRKpQglh+x!kihK^CD`|$DMES%+DODVEN`^C!J}dLB3_{tA0E(uSO??E|Tv@4yE$%bIt%c82EM z9RP_ko$!i#yDr{vBqV+Jc%Z|F7kf$JOym-73rh>jl(VjZFZ4+4K8l13g@*zp8e)N~ zpV$l*O>&ln!%cf$6H0<}F}RUPle8jicB6`CC>JFnTo6x>B@ZNH*CUuD-H4V~Rw)VH z0x8x=f<;5iBwfBrnnFqOVw*?4h(z9k5m45;;DvD*r>N4&#-fRc6y*?1qNR|d3lV}o z0276hpk4~OUc;}bvK);=t99=JP7X$QxjT?>j$R74&+f$3L& zJk|m#`4mHm%61XpgCc#$zaF!o`FEX_#O!0E@Y_M%MBVrV*NJ=mo>cP7DZdg5KbMtA z7^Q4EJhFsfAMlBW_hE2nzl@{=#m2IExvZ+c>WeA|hugHHqu`2YvC;TNqdsGZWmCI+ z6vZiej_oUXqe~NA4vs<=6Mp`}u=V`1{m@3NBNo8JBCB{KI4Be)TPg{{?VqB@In0v$ zmy~@KJ31X@k0Lm?eC-Zhz&9&^2FG)r2AvtaoL-T212=bCOG~|6beiWvc?G$trMn8z z9FR)YgW3rOIb}#J_%5kO;ag_JA>(%!m#9b0papkpezCs#5g_+t4{KpZ?VeVrJER1c zP@^e=87SqvV0y>igYXAmZO-W=UURELm^r9Y97#y0KfP*mD;Kj6C^8{)8YapXWvdT6}) z-08J!-1yXBvX}d8%J`&FJ)74NgTUwWEoHz9KkqMXZJuqF=9L#k{4ekOFUC*vJgOla z5oDZK100?l;3ol{7KzI~5lqMhwMb-+Nv<`~9O_dcj7!GLqe#F(i?X>>x3C5b~f{(?{r!_E$20X!m{{;{N3dK)b2&ljU zD+T96vz^tByido4zy{63D@G&?l5)eo%iC-i@4$aTL<37jbN2%a1yx)}41$!jD&efR z$j~*X9T9@x{$VjP;L&D^0KwM?vd<|*wlIBj<6#NJPa}`9SDtktNMilMC@wJ22i^x>I~JZ5 z6K6A}GQect3Af9~x5p{1ZGh(uR!S;D%TR($yYJUOfLINnjYIhi{KP88uMs((x+nBh z1cB|&+jzRe7xV_}7e;Xz0{swt5VT_vY{~F9lYTMd=YIW{IWqMpv&qI^%vFHG+?N!J zS!&U*bn$cJFyy|{%nT;lbm<17z~RIH0tf*W=cfk=jKJA~sHej}{Y;OFL;hxTw(QJV z0}1t2Of_Jt{eLX^ZI3D+0j?%q&l9s6MQNH9>waSM+$AI2>IHm~7zEo2z%OLdzKMiK zg+pzkL5(IILAWwLSet7~Ltw4Qtkm9tR5{@TcQ+X%ohtxOe%tboMGhow*b}0~Us>|< zj?Vh7_cN8&kHb*K@>tX+!}@%2|MQ5X&p#fa%~%O^{Y@_Qn;Zil_|gQN?}reD#e)0? zc6r`KuK6MfE6UdbTXkdW7tl~>^0HX;WeI^IUje_6N&7Am5e*Kli3V+4fTfSk0CQye zkfjd>G3@Yh-8g;)@N=V4=n%j|eE_ z_fPtnVr8TwjxiB+{WRe}l=&grTF&t1iO(&GyN?6NX{*5oxa`7#tQqL9{=GkyQ2^Yd z5(gzBQ4iBdlOEFa(gm|fM0BDB$9tyzOt85dQCE{G77{w#A7~1X7_gvF&7@I^rQ4A5 z%N?!5Hg1@9n7lxwefqD>2)Htbp1gve3jsRL zzn1)Se)KgOrz}!3AtUg_Go)r!tMCJ|5h#YT3IO|GTI>H#>p?$+tL^=)#-=oQ+sMpH zlAZku42-V)9ii-yCsHL1;@yq*L5j?my1`wfTxlbMw;Ih2$b}DT-ZM$ z2lnN9TF=2T6Zu`9R>l$dTHdfJ{VsD> z1tQUdmsFT50!ADjgMpBUY54yVgTfLME+gB>ZD=nrP6Y zNkM&_zmggh=<`30mMbU#H-#WR|2_Y=RB(&4BiK$s7$mk2DIA z_7CQtVSi*+{*PJG_xExc1+em?zbZIfPh?$*QAiwXgv#8~YJkMuikZ{Y@Kf%1WHRVV zxi~$v4vRf!mx^VBIENN8N-K5eJP!)>o|Rh1_HKQ}RnqfQiqN4JMKAhOq58J@Z{&5)`q+eR{c z#U@^9yX=U($L>n*jjC4Vv^8hg-u6{%)wX^{{>k;IdS#6N=4}2mzSsLX{kKXyGJRm9ludY*CLj@8O91M&R1`G_rW7p|D zD~lDgsF*ldRrWQrp_zfhV+%^p&NWS4&SF6btK~+WK|peGqUHS59P&9@M=Y?bF-UCf&>(`N$(Y`aqVeUVUlx;si?CQ#hQHeSF>!r5q`27G~X zlt}!HhU)U`bJS6Wz4jNF_?sQ&AKx+0mCVXx*Ep0oMt?c~nEOM^oOuG8PTooXLP33C zdl7uC5ML!?#)l??^aNs1-gg zla&!NgW7Z$a{B->2ImA=pbw6kp89%kFl1$?NfPThG$w`AwcVFwoh^!O9a3}rglF_o zv(~o!NMRGQGi=9VL|^)$`50AX0yyZP)TQ9mR#mng5q296fw$PhO}jj%mPqo5HYkg5 zl~1BQQJ;^f^wBf8mv-A%njO>ob(I3_k2N+{T`k-vUB>0pKiM8(uG3p4_#TxmN|y}g zsYPF93IMuJJGW$uA9PcxI~kqu+R?I8vc5NbCU(>__#i^3Gi6g`pofp&MlP@jY2G1O zt&!Wo6PhexDYSTbY?;mi9k3FN7`i)`)lGxEX6S+WMj&bLC>L5HcC4ndzlq-_rk)0BKW^kj9cPu8RcVY(LNr8f2WwGo{h4Ry|tsAfzf~*1F|&ZFf*gHylRE& zYSDI)>gbfJO1{xhh>Xeau}kT^N|BkHpS;c1EVwj`l|Ohl?`S zDMGlP)>^y~^bcQi0OhOtIznC-#nvfLm1TbuHcplG_pvK4(k6)PNMcW9Y7nn^iVj)>F&zwO{_;8DGzt~2#3QZnk>q*U&*{T zzg3#@GE%=|s-0Sx2UJX<`wSt}J>gU-<$xA14%|#D2yUN2t|%j1T|5K0$=vKyBJ8w9Q@%G|$ZPF5 zEGE&k-KgESt=LRWEZ|*0#_pIT-V#~6@mctVW6_8L;DrJ3TF+EP)J?_*a?BD?P))ysbRm-fbEr`yLNi^jhxo(eE}UNj^pqyo%D}`_jLZgl9IwnKtik-NvU7gkKYI8l$JOrloGr!P(1-@bKkELFlMU6`mP`?E-Vq^j5|i>jNbKc3fDz zF&fNlV_06Dby~aC2KUtCB1U zB!$fCA+{-*PsoQ!zY2Htx+iccE2lIvefAgj@lRd-h~Spf`UJlVg(TtGCzBNc+wF-J zSov+#VJ-jTcQ>54iU}w+Cc}-==Vu8i@;jd{htYW_@958X0!T3?!?kSVn-^Kx)q~lo zXY;Z$olp*7c$unoaVut-Ve#GyQ9TVv#MH+e29)fbebVh?TEFR|YAv;Si-|Puhm0|T z@5GW|%`4ia%91OP#visB!hLVQlEr}^Dl@XIa+LdWJXo+A^n}lEqTsk4Qwm;)FaZ|0 znA5Sz#V2*8GGg$;Lxahnq_Llm`P2Y=O*D=!h$`KTP;W-F$W>0ujSuCDhZbx)4Lc0~ai<-@e*~{lJh>^-#;@oIoBMgYc zt3xkJWL_#EsM#nl^s=nQA8Azm72vnj;4A@S=rpr``m`2s8x^Hx!VeTgS zk@khF;iLLpp?>8oUs7K93nXfD`fJpPkK~uCT(HA$iA_58wpjA-1FOGM1%qHRo6vY8 zL(Hao_)Ig{OUDxfvj+ADD-}c#c@Stz#2dLjpnMl}XW;mTCH(~mD?t(1h3l>}A4TY# zR|-dj-#1LYq{4Z@99h!^N3B5_cYf4*e4)4w-|o4`8fq8Rqj)T{7R$s??*~rvNO{m~ z-;a)RJOmdz&_uq-BF7<=-iFr#p;8?l=|tZZobl|!SiGOqRzjkw7L-Qm_H!wa=@azxI}Y|Kd9x1V+o>BIEN;%-OU7v*ByxL#sxe<|7U>n>=|AO@;3)b4(#GG<5g$ zD`nyq@dvY1mHTPr5nq_2jVkU^TX-ygrW}VW6(rJ}dyiT?Ts^z%M6aeZ(;rhsZ%@99 zv?a*|(_*_8IbJt2TgmttS0gDA_GaOgRuq=)+Few$g0PD&#s?PpCli6wYZZI+i{$H? zk)$^CL;Mhw9XpiZ%q2D8;}3&-cUMMjZ#|t4omfVts;Zy)dMlB~DM7OmI6F{3hJpl1 zR_JI@1MHe*tPKPU%bpaD*_*u7W6fzz4{Vf>|5Uzm8v)QN%X96?hEHt4KjjwFZKDIN zIK8IRIFd?$#vgv-uHGCN2;vrAiKna~PO@X$+!-lu)eu-QpIx$!C(X6cUuHA0{8pNr zIpCNukiXNs6+qk!NwH<`W20UrR@19=DN| zn7@`AK8!;`y_`9t&fXqcBf8M-0jk4wg*z6@IeB-!;1+jvlV<$6$}@%&YHxp$Tla#I zW53Q#f77eH_=WUZd3AwBYPV^bgqv4WR>_9O8dIzLz2)c_+2FQ*_yr|JAoFIrp#j;E zC_ZPb@HRvQna>^Ty>_x3=0v}>8O4H0}%rd@mHhk?~Ay$18Wi?VpP z*7S`eR?%8|)UFfIAsFkHOlyYyAWt_{yJ z)08n3EZsJ(NK{bYh-#?8um^24&FJpxdu8<1M#L}N!*M2R|nb9J5C1BUy<*!?ePY4M% zMYU6uQscf9rsd}RNMA4VV@&DL9TxA0nI~5+WVC!UO;@gq&FcoR#__E7gM1iaBrO~x z-Ya!i*hcZxr;Bcy#RBLO36ZxtLRA=Uw>3yiQ7AT92j+~~IyC#yJw5Hh?`|C89Lw6P z7`Axz)SEw5&SzGBUYk3Uyxl@-wi0<>xS%tncDKz`t3clM9kGb305|tLy#l!fZgju; z;-1Bj>n8n?1+P3m<`*Zat|0EB-p9Z3j0RccKA^lES7eayLf}n|U0{-GYgyPFH`!}a zUXU&I%~q%m7qBQysHV>gX1jcOe_2{0qrm2AY)qEPJmPpKsLQrqZqD1I)xFbxJ8I0P z%JL11_Fc`D^M(u_)Oc8HWvYUk1h+L^}N0<;>)L6-0KpE&#GBtTH z$#9}Cf^12%#5SsMoS67J@)P4oH~mX`NTq+4*kH8y0REQ5~V9 z$I8vLsKO9=FKTs=IKum20zkDpv=+lMCw#1J`B4A#X20LS4@01^Fg(loP@dtYrmS%<7eAB2-D2?)xWIvgj3?+Q$8s=iO89zuq zxqJtl%%i7VY3)v%54Wu2g{DEwix0LbK`i;^j zVOaaCMG1wIq(-Qo_ie7FG9&X!aSUYcc{1$GUWKF%?u>Xmt}Atk`^nhes$G|COJ|0l zp$=hZK28se^=;nU{j!AVMRG00Nz7w|7s?$&pSTAGtC{4?ItR$1W@q-^RZT&i z#jvHmUIY6oSK0orCRkF^!s@k+5s0Z8UQHOKE1y3W^N{<6=j=OlDeMiQ_#RD*axL$P z)JF~NX``4JNgf(p?MvNo}KCG6s&oYX9><42jsxgC*>3G|-?8td2Y$lx7 zWO+vc!P@>pz#Zk>0*U^jpo1II?Wk*w)Z==+Xh9MtC41M1w!)gR+D}#ZQ63*Z<8&@- zk=_EVT+{Zdg#R1&2KrpAx_R`iZ$?sJChyVb91aJ;VKu(SY0N5Nt0T3QbY%H9dEbMHL zGIgRi8WMv~4%D%|b;wS^8-|RYrg1%nv8maG@$)2zsI_IqI147cRZFai!Vg}?Qsv4@ zRmMun^VN{MxEIvt&*OHsjHEpy+?w61+aMK->8rN3)vK#!Qs2gWGQ@@iZIZyMHR+HI z`!oBZ{?n7@MZ31f$l0OA5g(P~sw9(?m$nn@gR8kr0~JgnGdf$H*J~=V@+C!n3#C&E z9-gW57D5hr=j9fdf^4ho+WDG-w5=YQ9Yx=`-3o$tWoqQd%D$#FD*INh7NqyF4iU4e z53Zifg|o`wYL=L&uXQz=T$IkZ+RY9%M4uJISwg;3KZ$p4KQam4VToeDo4G6&o06n~ zAG@IYa-?o?p|P;ETCwipyx(?YTC&66=S?5jP~CZ_FTkG7Q{o7pvQ!eJM58u1tBO~IW1loKbNn9FJjgi4>i+zYCsEvpZ|glnxcq?WOpAPXvtVC|oYwZO)i-M0=P>&|4GZEkNUG@rEexPX^vJ@PF&VyW_(S@CEU|?++K2^u#GAc zC2XtcT^5|{k(Vk_9|$k(v8;|7 zojn>>G=#Xy{GR*3WTQ#lYHts`!&y8A2_#IT^fO(8IL;M1 zL`c}yT z*LeulJbOjm_kCuj@2bxdpLo@;Q;eP4EqZr}JL9$a(MsSvRYRC%k$*esR1l}xdC^#jmCiy9{_`R#5Sub1FA>EfD&AP)SOocO^L z6hlla#?Q#p>AJ+?O{i>GA~EIa45}tL25n$Ry!w__CUU(7s^;xFX)wO}rEmFvkPUVUDXv0RZlPK(~kd{w*I62D|aJj6S&EML_bX%|Anr! zTj^P6Vr^4+(II})PV1&EjQEPoUX*R@wQ>Uvwm9RmYxtveYeBykwdDodNrAl%UD!?e5+Cm+M4_RtViohNNVX?XV1#I`a^dtaG3_-PJ#0V)hHry_-;Im(V+ zah4rvvy~mSIM`4|3M+8ZA2nH+;BJ?54i;MmZhWVnu#N3r6kQM5ofFX-MSFALmsi;6 zB$VF?FMK?CIg@lGhol%d%Jk zlrNq@kWYeIDFZ=4cmtFKNvD{y)5qSGIOOe0G+o^1^d zxb?D)sHdOnF}RH_XdAXijb*+ylegySiFi-X5-n&wNWu)>ppe=CyJO%q?UqZ3H!G}h zq_qzhvS|%>>9V)TZe(!|>?{|KTwUo@Afs`dtmRL9I6rOF&+c4Npe4(F@%Tg^E;suzO$?Tf)xwm>=!wenv`wpVP=@^n z7zRA{x^@tra6F=s>}T@$3sbw04P;Ej!H7_AQ?0$5qC4Pxu#OduZX{0 zCh3WgKSUMh>v~;(jp$1BLQ*V*Bo+%1vpA6}BaJgm^NH{|J0fN$eJs>Y8cWyKA}yv7 z9ILMcQj>G15Aeht30Wjs#SPYFx^U>=}19Lf2#vEIL)}tzUm*i*-mOa(MW{dk(gtzuxm-6{#0RtMk~LdTQc6qnN+E1G5BCLhvG=x<2yl%E?g}( zNKu+a?oNQ_GL6W}XxE_7G$E;!^20f%@tB;xoi!5!v{cPZTE7AO6a_Gi>E)gDuC4-8 zM49)H#bxawDmD9qR~I_iIFmlJ-cLXy=lW_Jop|NeW~!>Ly+z&i>ZtizaFn4=g1{i> znOUqwoPnU-6g2AXjcMBgoxnA+Q{mMbOH5)Ge$|tQvxkQ(2*p%bE5Z@;p+Z|+(n(LM zPrb)|!+_qW&FTo-E|B-qrCp_7iC1{@ne72Jbn}h4MO(VR+@7W=_dcX>^dx#Y!-e5g zrA7PYfv*VUI>2iZgoU~&7bkEcPJyy8pP7{2hh9tY-eO8+sfh7$o>Z=NOCD_Pdw*}qBIfO39vlBa_I_uz@Q?ipmeU& z@F4K6U5tOOw0=j6pMU41(zV=W#c8p*t1oWb;vO$E&zbRzQ=mnUKs|e}aS3?z}<{JM}e&MFc zi;!#V2>ixTaSDS7{Ri+ppNBf7y+iGI7xVT`0AHpc&VMsCAx}CQDF7q{{E2Z`l28~} zC|Dg#*jsOcK~7!t=$RuDNdRo*5szQ8%Nr5_ZbZ-YYlkpoOh6(O767SZ%NUYWj5&C|JjjV{B7|Nm`ZPhfbMOfb zmJK3mV0Te~O!DWIonz+zcAbMHNzv!3Uc$;L7?b8Bq8P;%Dh8Pd$xA4Iz)w)T5yAu_ z0(awp{X5{Dwlh!nQrjM(2ms7)(!4)Nj|Kim_{Y-|AKsX%2;(k(Fts5>7|8A2H?2WC z`f%5;`D47l+R6a}upi?xPA(jC?!g;l)LsHJ?C%mGmbMD_b~ zaRGl~9QJvC0E|D3P8^ITG1?~X5ACa=c0Xep?|*-Bt-%Yo?(B$c%u59%|ERZQ6LZB5OMd5sosiu?6feVGi0Jpn{?BtL=3`K@WdcZ~y(MGJx`~L&7L{UPk$*<{ zKwsF<5l2i47d7{d@Sm6i1X7tL!4FDX1hG9ZNt)D3TGCHJVwF))tA00A`AmY02aD&d zgeS(YA7l5^u|W(J>MzG{ggrg+#G3ViFsRUfpa~hz*n(pz5hFr_ro{jl8v)B!0xGfK z!vW!G0Doc}n=aHJ&L2)E0gjm%XY<)71cU=MY!H>=_?Pz=R~o!Xn8u!%-A$4}1SIG` z&;*a!4U5G`5<)pqd(CP%i#!?-sJk|a$NvI3PDuC{RI*9Xuwk*Cm9V?{75`zZ83RE1 z&Dh%$ue(`42Ji=8Xw;@CBjJ#_XJN9D%)O=82;HG;{<&em+!{g*_-CIQ}boCKsNnfO9ld}@>Cbe1Y96UANU8xRX>v$yhHlPoc3Ct-yp^yc*=Sd zPxLL}#nD6@G(6N#$U*%y8W%T+`Q|VD9>dGM#x_Ve4gmk_?fXo^%a0nT!zB5u_3yh* zkCEZD7JiEL3ed8F5j9t={|!}F0)~+Q1qIwc1jey{8R=ldQ)l&c^O60c%=HgtUeq{i zPrQ5{ePEZ{7E~bMUzBfV3abqXS&@a}tgI#$CO8oqTgq#V?z%Hct~Hov z;2(W6uV0Rmw1@KZY1Ma<1f2{g=3UMa2s{-o*ZW;4x|h1QPylvQTN^G z%=CZCTH$f3|Ml+q=)LB^_)+QqBkZug?i&6K!R;ly0Uu&4RxKw2d+{>%;Exyvz~3#? z0PvaDt9*}R9)HF3akb;U|IY~(d}a(4*z20vKExvrpSq_2pIRO-jLL-g2X>M5V@9Wg z*5_L=Iwe;bhg3QvzZ@38 z28WD4@AN=qMJb#n>qi&rLWojDBK#-R&pJtZQlGHA6M0s3q|Ox%4pjXD!LXFwz$`Y93uRAsh>x*o%gnpK^5=PN^869NsmC zVETmNMzXffB+Ju>kN9R%C?slSpc1Qt_xV|jUV^QQG`X$r4%nG!>-0>*=T*9-we;wt zjdD9aN>uPBFL4O09`?H2C*Kw=FWcF$tD&r9;LDCBH8@rA5!BA{^Xi-#-iGnUDd$A& zBb66X&>v^NY65NZXghxDCGa5Qyq7Z?uz!l1o|*1=)}QNmSSlpGaDUTO)#WYk+DAC) zAm|smea^ zHQ-fB8leu(ukK)+w|&rQJjFXfQ){?ISNxxyG~E2bU0G-VKmr{Az>CN*}fLx+2t*Lizp{oCp&_3PO|Q{XC9_sH%ijW1d$O z_sUS@{v@9k`*4Qi)5hkPtW~cor$t}h>#p)=#No+3`-{aI4+7bVF2=rIJF#9buDjx$6gC^^X(v8(n)M}sRWKyNyUi-= z!74$ag_7i&vY^x{WUpDB7%!*Tw-ir8`tOQV$7zt*3VniWg0(s)II5+C3nK$?2TnCf zHq-%&=ZxknM_1=-aP5RaUv5zTfUd zNh~zT0Gnu(X=`pGvB!FTfb%f{#F=~JEVxd&9$xnmx=s0Ha_X~evt(k5*9xges9@u@ zlnGIm%WcsbyALS5(H}0lz$p~E^q20^Sd^SvCLhz3?;R4Y(j{W8hS}Pi=fDl?VyOr_ zUU4~+V_mk($V^f$vW5&x&NWq%>jmbwu<0cD+k88>rWVfo`U5VCJvbp=;R?_K$ z+xmqq1w$?3E#B6G$EM!-e>la#GgLC5g~LL~mIzw%uB%5`EFA2zTpYxk-+#JAMS%}O zD;@NYbeKeoH;0ZGj;ED_w`MfHHLo7du9Eg9II2xG@$>ZWk(bhg7ZT7BkJ5sS-0ZT-7|qPIfv-g2Ft=0N%*8_bWH|jk)qTWaPol2BI+q4g zQEz59=0bL~%ZE1_CDCM6k;@`uZF#RW<7=W0VW^#1TmaczdUK>lS=xSB>F_t3{4&Im zH#?f)lJ=#nO!mIb_HLf;NKN-?Z7NT}YZV;{i=@)JD>GHxippUKbq2flupEXeM{5(4 zplp@aLhi`{14rG5LrR}{@)dU1ZCWRz_41DOkzD>I^2Dop5BD&c_W7g{!{)k~iaO9z z3Dp6gLMip2-_d|(MZ@S`!)R{4&!&yU_paOIv8*KAHr_K%l`@XYlBFT=tb*Y7#mOyM zw1>M-2sg2(Q+l+cj#&DB2^~nT;LE<&OohP4FU=)x_ zj09GM1a=aeDgm1C1rVAP2yKDvOa;gDSopJiR}#5K=$~hEQaGNMzsog{odbjr-wGk- zQ;)yH-o^F@BKiX*RkBq`6`4eTHd4m^C@g`V) zQg`3Tvi<0}2pPmW0hMb0qi5x(lBT&ZEE7L_potk4Lom+8_|bzZN$NYaelfB~t}(?z zELemw?|yPp^*5&A0sZPA4LaztBJTgMD2BdF-G#JX3f7!_w{ItS@H%BkX28#H7IyYO)>fw(ElsD51`OXUy|wd;K~ic9>p~iIOqbDJ z8tJP;Ynq?{NlB4Z5FW5bqM7|W|A@>sRLr?85hhs9oUN%{0Ez(|*lc*7bd+!Ur?1uIR%hc^pj8l|{`*h|qkxJrBj!+}H>Rbhjbm}2VlR~mzder!`Lam6y#*PA{_#F8LIWLKYd|@diq+3962Nf-4K10q;&5h zSg)U99o$deR%ZVi9bqyM5p4{|l_}YdSFBr=DPJIyKXNmi`@v~7hXW@@g5KBQY7N+`(}=;K&oOE}&e;+0Q%69$rB^qb2J>uq|I36An*ECg^ZcG$AJ ztJ$AfP&@;`$0%#RmC4)tSK~E79AKaR{}e0&z3SoSabObc?IWK z9>jTFXEJ$jO+m;;QUptHyIbeG^|Frd)fp%oK>vc;I2^hb}Df@ z*08rfuD_aGfykmWp$`@8&nGDsv+;t62AVcoFf^PbHiT_R!LtXS8eQBt1dq|-rYcA_ z?d6Wn7**xYCLqX}SV^5^pX~V-gBEKfdT*EObDfy39wb8UC%}v4>igZ+p%hAmP$jCA zknMB~nrxNmSrpBxheoeC#vc5u%oeVOx9X50!^$swsRdEb5hyKb?@*%OlHRCtL5;p9 zFzejgV*2n9TK$#L7SFAq3>qJ&f0T4th4Y#*rluR3Qj;w4+i~0Jwckp~7d*QF zE9+?;B?PK!L20C(K=(qKeu2+^GGYlhYp?kauJ>t90NO!TSs!=eFJ_Gxjb9-i**5An zA9GmP7szvHDeN=NGlW0a)Y~(xlu2B|8O~8v9-xwke{P97uDDBS<-PKeY!bRm5MOKF z8l_~k`pd2>t(xxKKztRg6X`C(mLvn9)nP4WvTp86CH+Ti&D0bq@FI(LEYM*M65FgG z;;x7GhDjb~Ds*=3(;n>->AF@7kv;7Q{|VBLBT`uQvKolQ>GZ+NgI>qaK=(}-(4=V=XW%k$5P3cNk_rl)tf^@@3=))lgMfaQXN@0cg9NE zGzC^IzbxA&5#?JMuCSWf43?#354j`{eb{N<3L$7dNq1oHXQf;{Od1|ICMzo7JzXrz zh+YuUYe$zzza7n>j!Jz(5x?fmu49%cI@s#{*1ITHMlwZ(RQ?Lhr1JdF*v6EDS}6zE(~>*4#IB%XEnf|v$ z&%|4Brs>BjpLotoC#UQDy65y92X&r?o4yq#&!yKYstc_$dd$luJbjyTN;fpu7}~rZ zY{n;uhqn!*uE}16GH$*%HX=S2!{P2$h)C`&eCc|gahBS@)y_jN`wfBCZ+oaC&ZFGT z9v`+i+p#12&Cu6dg9gjJ>+&SewuM|M=kd1WK=Xdd536ODs3BpT`NO!JU&A-_IIXNG z$%@UBtd!Vp>Z2Y$MiRZIBMR=Rdp%bpXj9cNQu9KR;~v?6wSacz0GMmivK+d|- zn_Hbr`&iA{y9Eg>`3FLbRe_Sd?7hcLCV#Pmf&6#n&|;7QTHY@iHZC>%Ap!2@60Pvh z%H)XUrWlq3z;xHI`ECLfF>0f7EVN{d1;S0y!<%N0P!ahE2A+rH;S zDPCTW7-H8e%kcW_Ll{iQ2ipe^Ici6D!&sC;Z-Z&vcG83MIHPGx(Dn*2YIv1MU@n37 z?5_56N#2|`sRu5*&Mtr5Qc`kwm^q50qLdoVC&v-1qqT&}5iN?hVrG zNkuyOZaChQghd9a_SVJCNwd8s z#xWO&pdRabg(YvVcF#`7{kSQsD$^j4TEFJjZn|UpTy2WL=BsR8R_%r~pqRHQ%~L{r zOs*`TGg62(cA|7Iq@3+WG9zs`)p)8uns`~O)FHNLl7QeYCYXM#@)c7Kyw@Rd(Q1r) zui(1EtS(D+?gvk|4J*&A*v^RYQ{`rA6p<5oUrKehmvGi$xFpqHm$hg%c~KMXD@TUs z;DZ69Ks15w*2UKMd`jOPz!Oi1fx9via|py+f~~$?uv%OfgFyLVEjFh@!Q&c@I~3kN zbR@>AFGJcq4C&k8FJJV`_x27U1oA`VTv)AXQ4i-_z{S$78iF=LQA;xziH(R1a_ zkznqj;c5msi>@KkON$F9zpAEpT_sRu0lq^ADz~{oEK^MBnUVE6rjI9O8d#=G-Yb90 zZ1{BYu=tF9hbEo9Ap*y{X-Tft8ee^^u(a!DK!F+ZwkUcsHD^*xGzlmB0{=Pe8aNaz z#%y{wxYQr-?#>fIn319&(tU3gT;65Q_|g8I_hqERoKi;DhGyNj(OIUj#wUoCTNfV+ zg`S9VNP>9p5|z@kjYk_BbiiQZ1)cLO2&4tPQSI-wuv0!ezcJ=nczyHLndk%^61Cjd z;8qdaW^m}YGMHM@>uxiO`9?_qsDU4n+G`<{(R(sA%>FsPDtS#ui5@N0nGe{S`B6rZ zkNt3BRW*M2WH!m+A~$B?5-JQ6DN5(Ie9t;diclYP>)II z|9y1n(FOqZ((^S6_~GwuJJ3_bnJ$sc+5-(LPs ztN-%?!ud1F{@d>F^yfdjn#Z*NpJeE7qrap3pHUd--&iZkz`_5hfPaMHV^kU+s{nxi E2b^}M-T(jq diff --git a/resources/mizdata/caucasus/Nalchik.miz b/resources/mizdata/caucasus/Nalchik.miz deleted file mode 100644 index f29c44b0199e55c5aa41e4b7abf5dc9d7b5f2086..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9025 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 JFUWpf{Xfy%)hz%3 diff --git a/resources/mizdata/caucasus/Senaki-Kolkhi.miz b/resources/mizdata/caucasus/Senaki-Kolkhi.miz deleted file mode 100644 index 63d6ff923914364d2846e3c7fc3864e99dc8db01..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9501 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 AWdHyG diff --git a/resources/mizdata/caucasus/Sochi-Adler.miz b/resources/mizdata/caucasus/Sochi-Adler.miz deleted file mode 100644 index c93339fc55271cf97a397268aa338ba8847c1fac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9004 zcmZ{K1yEewvNa6uPH=a3w-AEEAi-UOy95vJ?oJ5qt^qRm;1=B7HMr!FeE0tM-Td{= z+H-14_u6Oo={Z&1)k<l0v4x_0pJu z6{8K1>#EBRtIDc>P@VN~p=|9R8fetV)|Wq>tq39bu7$|dJc#Jq&b<}*wRXCDs;<0# zc2xd`wJN~E6=#hrnMFtIUdvE}`PBPFG=mV@K%?pf_fEzU-$=RR!iUJreOJ+Afk13h zdBHj*sCl&GVVEhko?`S0p#C*IM{)V)gujlw);nID^pz6rT@hEYuCtr9#2%*44)~JgAqY18*}1F6>QO zfdUCWskT=YY$n{G*r(egLr1JNp7wp?n~oKlGv}%B6ckQMlqas3wmnJQW&42MHJIcC z_o^ky7W=9U#T`y3!m?$RHw40`C05{}A+ zQtl_!9yT2HPpp$a9!IL77aHAYk{Xt7>U}XMK1*J!>J$p|9fX_PuUZ`??dI|(ts)et zzW=(HWTIws;)hd|-o^W3H`(Z}Y+Gj)?fOlx@Ld5_3C+jI5IMh-;=VhJ4&xS?fE}91 zDU#=7sfBHMv@3hyiu--AlfZb_WwV^_z$FzZ5s zTZWN5^T(MLgQZe0*R1hwrzHtcd#0wABuziud{OaDQDp1Th?Uj-nnQ-NmkWpOGUNVX(NH%+sPF9!_Lv{iX59{Kx|I*f$HEy~v+k_;l}Vh>GlT+^|{;zmh}O;ACj zBO!U~3Ou*CilqUMimVQic3RzRSbV~gXQ8C5zV(tc_ykgglDc`1fLUJabr^2j_d0BZ z8;nX?&RH3e2HT+2iOYH|k4V#>u%yQ){Fp6`PgtNamy}f}43Ae??B=ATF4tqBsbHq& zRhg|KAomzGiR=Edm>Q{Oi3CPcon;{;udNi*m!H>Ol9aZyj(CGL+i7L`!7VmlGwH?! zJvi=0OoF_i64NLMyN)|eJw)!EJSWk=@?uB!Q>ZO)^7Q(E9SY1zamP zyFU-UHYL=5FA*0l08$VD)ydE&anlF$^b5|EU@a72zqX<(ID0cS=fLMP`nPTJ-J_?Q zOY7qlUO8txEiYI?EESnZLiP(Mo}4z#M+&O9)x+SYS)gbG>Q`+G5Nc0p(x>F5&iZg4 ze;C`c7aC31S(Y_b zdltvFJ9HX;L^Sf;=6#yzb+w)IBs2qWtur6h%{J)Ig0TT|AzJOi`smT|dg~k&#@L${ z@wBA6?YgWM?oU@h*1b9A!l`x#PJ5f?j+0`A^q8`hAA;Nbo&G~FAOVZT9~RX~J0uL* zw>N&vcs3L)iz3F@OBPzkB~{NL%DXP4m_(z(<4A~)-n?UiMv|uV zJ^N-EY{_N~(Nh;T(#0`edxJI!@Q!%r;jv>r_XvHbdP!X<{qa>uRtg` zieWNLr8g}F3C4Y)JSv_>71*kaSn~7%=>&^IiD!$ncwKgNdK@)*y?uOrqKmGee{uQ+EUQ@zBq)M=L2E^r-^*`uNzf@VPWz^s#jNCYvXEE!r@X zHFzD--AvhBl3S|KdmcI8qz-5l>)sJK)hT7)Hbk7Q%?b#q6x&`sd3Zrd07Zk2HqpUP zJMZG|nn4f-8VY2-PrIX;Yp_37Mq{=j zuARknJcP~*l$?&99^0TZBPkeD@O!>^Z5F(YW|SAIre%*zoWk019G?z6DHv&Si57*s zxKBC92j7C&L+}emjk_?ye&Zq_EZ#7pkd_Wd!eUo8DvR^2@F?}%%uBKWX`o6O+pg-%P|0U}|39ELl|&waANizj-uoZQr2kFX_ey!gMhW!~ zC6jppK!!n>JZYlaH|O{cg~h*hog`7)QufYeWRQ)G@(;FxMx=jlFg_e*%Eu`nb$FrN zmWYoJFwaWDha+5E`7S(C0vwB~2*v0BM?RQ+>oi(vWGjO$Km2GqD#&Rlc%V??0|o~< zCx@ZrAM_)n#{)P4oH?oZrBIx0lD|1K@#)#N8br=lc{gpHdK*@yJLZe0sXS+nl$)LN{AVpB1zQ9XKV1Lx>HQ_-T^QKAn`gTd% ze@yh`;Ou-&bE8pX36Tj~W9fkLdjAOF1#5DPtl#s}0qRv;~F zPcZiFWiqKxb`Tm!jP)9f_6Dsnxjnk=i@|rnC946*Z1Car{}gaO>&k`T!7$nNBV z3|Zg}&p#on2Ou3gQgd|=-lrN4;lIft6PG5Z@t(nx`)>FL>sW?tP{^>zBDAoaaL8`a zb6X~^ys${$*Dv~=cvW~|O;A}g0I0C2FPQvzzQ{SB1MsovJ_<+I_Nuh0uuVs)01avQ zFtKt%Y2fMb{@@N8d&ED$KVV!2wCo?Cw{?A|xuO6fp!@W#b~$?H9SBiB8wMNs50;Nh zaK?xm=R3RuFn69jbSUty;MaV{boLSG(*Q6~e~|k*07i*T>71Wlvqh=Kk|MJl9&oj9wN=-o~ySkUCOC z#XYYqK!|!J&9SG>eld|L3k~FUVEcn}GpCxVATNOOTXv%{AS{yjhgHOc*2kQ4`G%la zYDhS!SL6l-ZbX84?{3S5ab^KA-m5P3?VkI0&0fc1NA6x?krYNy0OLn zdEY4A30Xuz&J)?hrIK60Ix`kHKJcgAW=D%Wlw;jy?PpfEs-Z?MtsSxT@pkukP1RK} zo8(mN$^llTv4ZG+0PW}F1@zYXjb&nkthI?=3 z7rTdr)@~u$`bLK>Ndwh=r_i4{_kP+qNDIgnaeuftMa&hOJlieT1q}cu$n-iX6Zfc= zv1H|BO1~2s4a&UW^CN&Y0tadhGpu}KvZ&kzrYJG3<@vU$ipOQBwy9Ec6I(iz#%$?h z4I+iVOg-ZVfXEMo_`za*?Tp@l_px9ZX`rGwFg{TU%w`s0<(736khOg^E`x`e6L+6W z@FHA{;Dp)Vs!lS8jnGR@&F>+bPe$GPlqA7lNM*<9@l)lPpEa!Cl&S|XmCli1oAYBjSqV)~%-Ca`*z^a@PHeWuR7ER=S~px%ay;fq!Od(S%lnS!R86 zHdkT7uV7J}Js`tO)=5Yk1arNN7d`w!JXK^EoN9w8RARglt-kj@{5*rgw-9vCbUnLZ zbA1@d69Hf7=9&D#R_TG}rVy@&wxa^S^NVc0L;_Z<5q)F7i?~3M*uXz1&ND%EL3Z&* za2X-qpXYXnMO(~v83Dr!tadz!V}a;3w0%G>LNjisJVze#(79PLVE$E%OR6jP)o`tYv?B5iiXX{17xDE(BiP z?Pf}?d@#Whc+-5$tn;K*vvOs7?|XYwzg8vMUvr>w8frk;>b*>!!s~iO`M6%j-g|O6 ziu`LL){AQo&IQv@0&w1F9YZq&waFb+HTI!@x}R%9!Eq=v`8(m8H_O>zbfP@hsamS( zh6&cg*=@LUy^HNDe z)*k2mh0)-rQ1k)wTO|Bk{|_&r70*h2MSjVT9lwODWRxuwBCv-f$tW5lIErHW)lZA9 zIekF02v&C=5Sm{~51xYEvK*k=!%a96@3~@JB(*VZY3an`??g&xDyz>{&g*c4=(XY= z;yxSrlaXst=)r*@)hup@oFdc?EY+172sqRV!<$4I@!X;%)QYRJM2ore2BllcT5`lT zm{g^;uuHg75ZjWdvS;9z$ zg{aYA^mC}*P|e~3Wwr;PY2%@v9zT(tpQVSDRYv$CSq??3( z&K~mzviPE9hDD`49a5KKcVMXda!SsjdJ!BzQexX^GqJk#X!DT z1Ps60xlA>~ztS7+#wrIEKW&o!w?FYq=~Pj^`Vsx>5ASb(vNdv0F>|(ebuuv1-(}-HgPN>Psg;JZ83w0fQMXv@Ob6Ys4(KrF z?}{1HqskvT^f`Lc@oPx6_OCu+?d~#EBC7ILXHTBRt9Fg8Ozdi9qy|6k?Cy`&HWgZ4 zKb=ha%6XldX%^HfL@gaPF4dnr^VIHS4)rS7+jHRGKIq0~2SN={#V1yI7Q&gR%h=2{ zaZR?DRehW;$o5FrFc;cyMgy8<_t>-?Qsr%9M1o9L`lpH7u4=Hq)|E z(0{U32)vKDg2hR-#N%!LR=VFQ`uudT^)y0^`t&0yQtg^)?y~A=KKA-BHeQPdw$$$JhyASUVfEFiv zZ3_MX`U!FXIXZwmu1G9YC6Dso^DzZts{ig6spOgbyB||1MhhP#_S!N4+0y>z|L%_l ziM>Gkqr>~7ODpFQ5R=D+{+59+ppT=4p}>m%JtzZ!S`92(ze{>n*MkF3h^D%yts*$CK-+(13e?MuN7zfr#Vz}h*6R7!{ z(g=|Kvq1&`h69T@?*CsX1GuK|uQa+6F1iaY+c!O~o~>==*aPRURZiN%7@>53EddXO zd0IkA@BDM$)U*o&t2f|$*QH`f@AorA@d96`N#FZ!vmPLzeL;K3i_H+J?})9~!8)95 zv}m)&dxrdPr*4I};jDfA^5(Nc{7Ki^JN*9MI{(nta^7me@Xa;YxV#!7r?<2zWFY<474IS$Z zUoM)Yqiiz;df(*DLaOot7Ix^?rP)%6OIbA{9W(Qw$j3;&i}wzArShq$WVW)D1dICz zXRQapcz*4K!tF&MPQ?oOxF+JbJG};}xQjHV8+>l~$cwF%iqPu&gkuOnmU;v1nt4`8933%g#LzS}W3(}{ zii4{VgF%+f$3g1ON(UV@xT!#R)Nx!lwp4pTiC#6f0+DRt=rf#HK1^10xm6p=TYq(uRo}u0J5I z;+`r}v7*HtY4VqM1h|$Gv()%SowHL?zF-7ge#rNx z1C!@XGcg$QRAXn6wCY}3z2;c^2yU{!@wEhLJeL~R&I_d%#t^~LS}{H##RiezsqsOM z=@5SGKGsX6d%ws+<)ZO~Cdyz9x^au>QdqA%o_c(S|Dx?D)qC#U)e&&5{) z`w-SPGkVH!boHXL2z`N{isc4HzWd2ZB;ju82p`=ZGMqu^23zNr>?K~!ny{LFfIqQo z)o(xLv2-X@(+*jnjnkdI3oy7B0a8_)6W zK=DrosGr#sp{B!TH);+Tm&rG^<47DBM}^N2_M8wR@>bNT$ehn#ygZoo{EYNJ_n@0q zs;QL-4b;L~qk+uDN_*&^nb#y-qm^;jiBw9uDUhUrV8EAWV1S+};~6 z@6-}mvzlMAPa!F=HeTiYXggGykvHs`I$X5Zz7tB=ewOLTJ;+JBcAPQ-KBX)z6g*$5 z%!*wUH|Ro>%Df-TqK`>Op-J5E=GOa|{BEerD_*tAycs9hBM~xr`0I}0*AO~k0e94Q zJ4EV%_ifCxbF9ULWz+S~+y^;x&d%R7GXvj$y=x7m^YL1ZdizaA3M*oOpMsA?1aLN{Ls-nQX18)xlON0qXvnkK5wojY!ATs`>z*< zKbJg{?;o?xOX_?Qd9R(FZws1;n0bzxJdL+~tILUGH>&H4t+VG7{ zdcD|AOp=c58pqsHQiQQ?=a`z1o=V{I_X49*`->&rF0(Gu2Y0#znB{-MG5YNegAzQd z+#Lv@%krE+d7p>Z?~Pil4sNSbJUbWjAzdaqQv)pqrHj@ouaQF|cne0}@UBO08Sq+L z(^8gMq*yC+-8IL<h#0Fp-4xHR;SXh}zb*j5bh6^E@H?uN5+`N*-YQu5if_1@bmk z+}-P4JH%_vJ}gRMDZUV6tcjE#ad$7a`d8T;gZdQs#U zzvao~0$-a!*MwPw@r)#Q1^Z1j8((M~#dGH#Cv6T4_!8Uk)zb61-azO%?K7nDJM^6? zCn_3{KHK-pv*=tDML>ay>|Q(+=$$?p$0HSc8_m4wB0suJFqyHU+FeDD8CfF{nM-24 zcxe1y{&h~5+~WuUQXXfvJD=r#?Ok;cyaUqW*iO;56D)@EW~9`?Q#(msnob9eX%-J&!+}BmUsk* zFUMBQ`TBH63H|*bal=QKWY;QC9n+4Wkw$w+?P6YS$;QH!^wSPpyWKnd(k1;-_2*rd zMj(0bA#536F?PWrBaqx0I{~b*d|*B5vCVjDEvP7r_RUSEH=Mt0AehnGY=lkWGlI^P z5|d&tj9^;Q5{pb%$I|xH$AdPNCHcyLd|-Wyh;?adJ!4@6=N-+HiS-Z*l=$@1KPb*pC+_jm;ajExFriwCfJtYDfNC_ zp)#O5T8uM(vf?1LitA1~D`O|BBZTjefLVmRdSPVus}N(&bSpH@>Kx%tY!{D96p}TT0^M z#f&51vDTD!l!4)=)_cdfa%IXj+AXft$#bwi$m@-|xhwc|RFyirOs=amgLZC0q|!Ya z!aqDx&!S-2GfaMMdFAX^*B0JW4p|xCJA9;ipC8OI&6b%R-K=L0LrC4iF>RKk@-w@o z=Imt&pL>rXle;Af*Sl?5p~HqiW4ySc=MD^He-0{*ol5^QCGjo=H}8so2zui&>`{W< z{9$N$F!9qTfhgjvG@y9zlXXZ{j|FRqL%8>KwBwv|R?n7J)6cP4wun{~_}ZPTqGB{k<0Vs%96q#yyK~@77&N&d{D?Rt8$! ztK&P3j{R1L(#!il*^Xg;Ru)0mBnVCKw-n3jKbs!m{+?Kux}m4cjGFEugx--|BBvz8 zeLT6QmN;@Yo8owtAGdf7nFsKlsqQX*vvIT(`b@d7L~Oh)>f#CaG~v-8^ZI|i?SK;} zAvJ78USrQ)2UC{@DdOVhUM_rDX0(2*($K|GAw0ytg|d*OVU_R{kQ?2u-bJS_(Irha z%{aY1J>Ffta6VD|>#tBXkamaacZmACB;$7xhKq%nk&Ba=vm%p%A`=TU?5jBBYb5)B zS8crNKtTS!{ki^C!|}J$-kq{41&s*-1rHAa0YMD$mfuI^>agge6XJX9|?8%4}Ey59|L*kT6eHYQ}{My)kjtz|OT&YC{KWI(k0MEe(E;AB_k68Z1(WA8S6^;S89b zpR9DR6tH$b+E#Yl>;N@JtJE50o3HAJH~Ac*Qso~qdNj_0(MxSUfryVqsdwIJG1MVjt?R3w2Bhj@n}yEryS;#aeExTGY3}e17uBlM9^%tG!XON;>GA zA~hCs4fQ%8!vy2FsfGOq0o~E0a_KsgPLrAuwx!%w6ueoZ&8coaXTlcc+!C)*w8?GC zv4&c27e)T6--Dpj{Y}aNLPqKFF&{^qC0a1W=+2b%p>vy^gxd8!PrNfBkJa&447IRT zBE4QP9~h&9+`(5ZWh_d8r9;nV_O}D;2+^F>ipPB-Bk!SOmX&b7#m}|!BvZ)aS0Hv? zlcOWN^2?w$S3z|r!3TBoV^uFl^YeK=x*7GD)3mcbG1fL6A4|@O?L+X_5dqCLBlc?X1we*Sz(@|Rso7UBulvHr1 z@(Xk>^c5F$gYv#|X y+FE+pOjTStL{u{kJDkQjqeuc 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

SJyj}ox4Ywf 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 diff --git a/resources/mizdata/syria/Khalkhalah Defenses.miz b/resources/mizdata/syria/Khalkhalah Defenses.miz deleted file mode 100644 index 7584826ba20e249b819afb386e509db3a585edab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15599 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 XTU%ZV8s=pJ2J|olSw$H%1qSv%(c(6& diff --git a/resources/mizdata/thechannel/Detling.miz b/resources/mizdata/thechannel/Detling.miz deleted file mode 100644 index d4cecfbb70e7fa0c1cf5f75925b81528d42cd901..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10307 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 VBM%MpasuZ0Yxu0n&C5^#@PFeMmFEBe diff --git a/resources/mizdata/thechannel/High Halden.miz b/resources/mizdata/thechannel/High Halden.miz deleted file mode 100644 index 9cf4d23e6fa36ea4b1dd360033c51eb431361e06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8696 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 diff --git a/resources/mizdata/thechannel/Lympne.miz b/resources/mizdata/thechannel/Lympne.miz deleted file mode 100644 index 58e556106be1140d89a63b242821660b63985dc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9339 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 diff --git a/resources/mizdata/thechannel/Manston.miz b/resources/mizdata/thechannel/Manston.miz deleted file mode 100644 index ea7b9fda05dc5b8b6895cbeb038d58e560e143f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11013 zcmZ{q1yo$y(yeh#@BqOH5Zv8ef#R&!d})BeA6P!v%ZbiJ3BMS7(j4N8;$Nd=JdZQivsyr9f2` zeZ(x&K9%;XE-)^BK~{DhQYbX#YJ5svOr&P6Jsy2%r+eD{teO+qIzpUi)PV6wz#X4T zMtwWFe8jxElvCM%^w@H6U`84_&UR%%IqJ&oOeRU=>|l90YgVuhyI(22(baiNt$?T}tN>fuCxvl#ZFZBMdZ4FoLkL7O2n=t0WtH-mU2M09V zCHL0&kkO{brpwcn{k_fl8`M~w8W*laJ+oj3$4;3Sca5-^!{LnRkJmSjGlq&YLF@%W zvaF8Rm0H#DC562ckI!xi!xLYxW)hEX7i@+U>HRTbIdFz@A>?#rR@%--j*qOh3a?x_ z6Hu!Goyt$z4IWRAmDw_IMO!oS==&sV;sv=U<~`=br<;4VCzWUS)Y;&q%npmlunxAB z;x9=Olej`*`|$69k8jV-SaD-7hE30tVWkG|s+`mN6u9*#@0?wRS10{Un}YIgGWW?i zlMs(RZRJnQYVGwWjT&4rLU+T$GUo}^nc{1jtYXY(#1zxH;mLP&x968xE+y~z(+Zq4 z&?@GQo0o!TFA9zCv6RzF`dtY}m=Z))xvK3N*3qUCSgTeYavP3~JhRf&Xo&9+u3cQ$aSVDj}j?PyX? z7=xN4@0&V}EG;THPKCZuQd}2M&dWPZpXW2T%#G zHV&VAMY2^|b@wS{XHL5+a5K&9Z+=n_Jx{Cnz~PXqQb&GFlWI+1?a1P)IrTECOd4I~ zY~}dKkTUXZe;)er`P_@nfa>ct3jmB zfE-VZ?>8Chs`VqJ-vU2JiSX7rdr%H!Jh$3{ZHf~w(MV){ZirAoF>9D7I?2pWZNg=` z5LDx0lrFU_)gcds-0l(ON>r)4iBd^mH&g zt8UH$jMBOHab>*1Wq@1l`ed(+1pJ44-5!4B^Lj>5;GLfxYw}~KAn}z9Xpn|%D^W0( zR?%)|6_)PnaOAMy%%GOSHk7u1gqs>@gF(x1kVd_78>rZK3LUXOwpOO>Y?kO}U9_$9T$fuak+@fhB7I zLCK3)s_#laK6X-@y2avpNv-_!OBwd&iBO>waiW#uxb)9N8_VumbCD_3@- zN#m216x@ohIkz>*q%EA1OKe+apt<@PbN z2(mbv&hmzmWtUVpj{Q|F3=X%yniw`WlOJz9$k%0*@ugv=%x-3O_M+W?csbf-_G&~{ zEXJwmPODg&)aTBOOHe(uDBT~u1IOa=Gg2YClv=H_ADl2AQ+jbSJC(OozW!J|H`GHU zoE)lh)28g6+;h-wZ^cD>*%5*ybf};#pyx!u;FBPuvZ2L#*cL-=@{o8y^1PN&ATN%N;%T9gGA8{VpUC~ z{u!nd>`Puogsi^VB40{uiMs6BpiVL|5WlFBzm4zY-R&gPqB=FR(c&j#ZAUM^-Uo*4 zO+3{T*O?VB9_4Gh)8)sr&ZfT8V-Qj0;F!!_`3F?L>V~TZoYIB1tJd_mn%@UDI$$4V zniRU6T=*GG&fgk6z(&PvZI1$E6j?0F%oTkSSFo^uu95Zbj3QCgq!4-83@B}fIl|4( z>pF+(swh`xg^dNjp_uhCdz%a~3NxeX(*2p!p*o^#rw|J*qW>wMz|-i8j5XJ`Wqyfz1SjfD6%g}%_?T~$iZj8Oner{cboAv11Oq}a z^HB4S%JU+i|7afS6A%GHtn_tFq##}DzA!DU=w})NLXavDUpZUQFlCtV??Nh|_(pd+ zETmtJYF-_oW|FDf%!hX*L6{oV=DJ0hNP!A*uM~LJ)0@mpJ%?0VKX2mShb|spA?hv56!npziK-9o4$KL zo!n{bWhw^<3D?(E{XNlv*NGYfLNI<$bU=?FH;}Lg!ytgctj58mT*Zh_VB=X^MH%@k z6CKFwn%_W|VS;({bZ|fj5f9Ug-)@&?84r_EPgiYzZeg+=$2=k=$6-G2)n*Sq zlpg7ZEKy3U(f53uW|O0#BQoS;K#a07(jgOq{a4fYA5HA@@}99@h3YQ^I*u;SP;>KE zsJ5BM>db8Fb9h*!-UP2~Rv&qHB9q0HdCHO(ze5gHqwPcztoy7M5eJ`Z4Y3aO3v?M4 z3@Bl!-a!~)~J!wUguo zfY(*Lfxo!oZ&^rkxftDNz%ErK;%hd%nC<|tTTNxiEzMB0*R0M~nCRMBt;+U3Kp3w0 z{xJ#0966&pMqx0fH5p>N^#QePmMKm=iouh-U``%A_-Ru4y9TA5S|1-6&t-4B=zrY2*jGa7cGKG>z-cr>CREJ3|mTh*#|2=ewcGXO{>AC$?=8X^rP!6wd-f^n{2O5QIQ+tQT4k^Ogm^)X58w z^4kf#%lhH*8|OBj+bsr~F+p1CG00rdPP<)cbtw-*5^2*>aF-%rkgHZPO|Ju|QD6_C zKPUkPf$mV!`H(#$f+Om|f=>}H4x{L6fJh1e2M77Zfd|Ms?0FgwS~d^lHE}}F zwegIrqy4OafSYqDbt0oi( zIIvfyAR!~())>3RL+1_Z({ngd-VpOPjJj&qPF1+eQ(6{KVz|bGga!2ndl*cRI&uNt zr1`~L0^rto`6LoqOi3c-pLFn`-+}#N3KT-Nji-$#wu^^M&mwKia+y(X_A4GR49c=i zI*!Q?o~M%lN=!Te2-u%A1cq5)79wXCAtfjyLwR`PGmKA2a299C;(@||{bC9fd?Jk} zjVCsXhiwmb$>XfSrl_Qs2Lqj?;m}ikrp-g{1WSa`67T)5<-INO4cqrveIYi{(qF2q zUZB5|2Ai}a>9A~eZIhj`0Mh<*jyWyyK;L4&!0GX^NED98 z=*K@S#0fl0cTsn(?iWh9ivH4#=Piv)L9{!V-b{Co&C(1pp2S0@*+aS%Gii z^V{VVP%F!{y%Jb^Wll(>A_q7T-RDqs`ef+%Xh>bQ?CBnR@ zB)^^h;VFJX;vexDRiNFo?GH*kz^$_N`HIqxsWY`HM64wP#N>mmKEl-^DkDI`g8bsZ z3!JtLuPiO70uQyrlaIiD*HC@XlMoa(@VG%McDfSp%fmO`s(@cXc&~xKd9_+F*fIkt zHd$%A;vH(AXH}w2z)l3hfKiuXSJrQ35ZG63tADg{&I6SUyNhpzwlfdc+tKLqc;@n!^ofBCN$Z;qsyXz}`*B#3CEFm~mQHD}hZ2cU{8 z5$I&#L8E~EVhR#QmWP*z7fq0t3=G@{;%7;FLkbu)AZ;%N_DTs9YU9ZZZywAOg$Lme z#^gCr2(u-!3<5>?<@b8V#rM0&OzJ;VY#8tk`~dU##TBn7g9w7inBW%xoHGdtjgCX; z%oT|UD7c`*>t`s265t>eJpVIl)?cNey=fS$3fO2wO`t`*^^8LajPNEE6Ko;Z;XutI z{nw1Xe_W0Sgk%O#Lus- z50qN~Z4MlS)#laZ=GE(2PZz-Bga_%a7?0`nG7rK7WW6JsxVwYaH1N+qD=L3-VWJV} zOp^&KaR#IApE_|BPo7z##N|BEX}YO!4x!arct$c}Milu-ZjacMbAgVnr@ETqMp8wZ zR9Cx}brUGvKWS}HxR&4<>kr-(`4tI#%&0=_qA9KxR5+K{BNG1_N%FZ`R^fbhR1~V3 z4nF}o(*5g4_#>~PTfJe%N9#b{R+KoD0W zwlZF&S`nP$aM~642v4MdGz{>0ogh?o;5IC z*C_zku;a7!Zu}g==T3)b6YNl`h_EUKm$G_-xOy?Bi7NfngZ0{vIh2v&A3O4i9Cfw| zMTCRLKU=+6<`)63L7)Q?!=RCuLO3MC0hg%&C_=*7RDic@I)@{fLrT#5a#@F4vm7(= z1`)}Lh4XgQFs*$m<$!}tpY&x|UgqM*y$^ILuN|!`b{(r#yWjqfWx4I(E|cZ5Tq*l; z=bCC2c9vZs)^Iia0CJix4hycLOON`JX?wUVpdrL#B9K8C z%V(VNT&9XELkTX%uuGM^p5TWW{8dM9;~B$5LbctuI7}l9vo~=#A~jDhMV!uNlP~ChGTqnh)I!;d<6WU4!ddUjo=aQ z-T8t(56~4-%<`SuC$h<#dQ9nPHFJ%c9yV&FPG5+N-LHb~%dZSUB7ywD!95rynkS)v zW%oyVipyjz6S?QX!cT!F7>LlMc=*3a=K=uUQW_u#v-nct;NLGLd)ad$MPLIEh^h7! zVjA7EpFs)IXIfydl|hk^nhDZp=TV_Wp#C85qNP?mUI0@EJoo6PdD(zWjwala|3hrT znZHOM2sH37t~|UM00@9FMm&7%szE!gwPUQR!*Ia<5AP&V+8(y{$%DbJR_;$zGp}E9 zOz`*`96ohXpCwoAa=mmH!+-c5eDADx#Ol+#3}Vgwim<_922Ln)O>;8Et|z-i_TI#8y5w!UXhG{v2OsAY{T_r0_FeBDmgL7e z5>2Sm(y2y!#SW50^Oz7yx0atf+>pdLmGb7ki7z11|GP1+ZhXWC3Ix>l0SE}~bz|I= zmd2b~Kv0NRUR;h^&q&wyb(dV*+9_H|!eWvKq4`Epi50oO^6-hxlJo->fDBBK6AvYR zPP(u?6bmvB;UziCYGSBk#Kt8!r?S#wrNwEpKp9SAHs*={ny&Hod-%~a*Kmn_YfCXM zs>Qu|V+&R5ENtPoR0qV$Cu%cBVs;nAZa34!b=oNsoPwPwxa%DR*RM4$jX0Y}_*@@(7QFmDk$`hEk|0QvvNZL%~Lg|WdC}$!EuDc-k zWr8B-#eY0`=7PiJAnk1l<#9!sT~#Wt=+~;dx7Hud@Hr2jeEJ}J%6D#ypC#;HGKjlv zELs1f&L;1>E#VS=B68uSZgi|fYm>=>#e;CP_j(uY9I+ROwO5IZB_ebL&78lE5KK;! zmSNECUXnf&U$e(79;dDkLnnVqr1fqsRKFa??gW-GZ^`~G2z)Lo(ntbxw?7McsErB% z)P*K{u*S$SK9rgV1Q^%QWL5cfJAtw|}ZEUVhM|%^jhOq@rcuT-jHb)X@iH~${ z3iY5l#ZCd0BDC!&1Bd5qcDsa@>U5(1YSE3sUc(C9_xgpUsKf_Bj{sruBv|dK^&T>= zLm2$3H7;_f-A_j~5Mhsf<5{KgP22f6VY|uSM4=oFN4=s2A~`U~keaxVUaETgX`Po? z0g_<>Kf1q}cI&T3PaBXVx_NC0pAGhX8a1iT_wCXyzRi?c$ELBb-PyjBt#Y zrF{(3H6#)!)?nS?GFi>R3EtKT~nsbqU|&*4=%^AH>m&jvSSriEg^Wl+(=&!^uI4VGi^&*eH#lq zYhC?82?{7t$`NWxQAznS`L%+b0{O9Nd68i;nLeqneS#v^z(D^ijE|tgVg~{Qgs2Gw z1oJBFplz*hXkllgZ*#OXYB5QJ(st(Z5=gQOSA8~93<<~i4%+C4_nKTf8N;zb)MtDv z67H&(WUAo_v|3C&TG(`pu=0zP;HpI2-@>kpsk=&uEfCv$Eb88EKzm*Bi2BYD1k0hBtY zCnwp(BvhBPImugW#&aybUy7;5VN;)7Sm{zup_?Ajn#7dXCXziiB|Tmhc?NUf-$yv@Hmb{23!Y`=)01z+&I5Qqj(NlG2K>1^(o%4 z0;<5Ny^8zkq@=UJizNR+4#`B-6@hn}nl0`(_Vynd0=SJDm8PYvDR0toTZdy)gCNaq zZmy23aUOQe-6IF-H0HUR?Ke1xH-vh-pDea-HtoJ~a;aNA;Ut|DF=sz#Jt+4+m|j4+ zpF+By_GWU2p9-(TKd-?*J6;MyyGx$Ez6yux}ylxM9y~Q0_e_Yrs%Hfcg$W(rQ55S!(UmbsDX$CX2suG}z0? zH1(FaPnU!l6?+`I7cLD>G-udDaG!#5Ki`H&(|2c@+&4{$dbeu=_LgzFc{DmJa^7je{%Zu6`#c*vP>}!ENLcC$JNq01{6EU7 zJ0DyW*5C4RxvhH(GEU+C8SrrsT*rP>emtY+Y)}<9hr?V7%=zBAM*P3USr27OUk(cd zltByh>%XuTmT!MvY^GI|Z8qx=-BLBz&o2h>NsUZ%$>5RfM)t@=uYa17`FM(m@Wm6N zBUA}DGQMyQi|qgd&dqU<9wZDI8d|x4nP9>6-5f8%5~bW-8$4RwS0JdkkXt%%>Yvaa z-5##GKwKZhY7WN7K9>Z2@5rFR7eSpECWW?FzxL(p(1aFFABH~bL`EjP0&c{=%jWt1 zY3^)}JiPRnrABiH^%FPPI+y)8Igte^aeF4xetMXzCo42~M@^N{2FB3^`d6a<; zp)j5`3j%wjGBy1;f?H>e8dN0hu{18Mrw~JMx;U?zmQ|(J^I45ZuvsEo7h2j3@K-+d}_rYtHd-={1KcszOG)ESQc5?#CobcA3hJCq&07F=gbyR)Glc3SY+RC zt6Ww)ldHgzJFp{aKBrm_%qY^a&{f|?>P0UvViP5IeQ!tJvrj#dU$FV%z)gj!S;aIi z(K0FqG7`^ar>EIN{{&*Es?Y1y7Kin>+?h)cO?TThu388X*>KE zFdp<+V8W%$jx83BFLz%@b+KHPsdS6#`#Gr3brIJEqR9e?QjE~GXH^QEB-ETapzqj- zfki&Sk0A>zp}KAgmVaQ6>W}@@XDBwLx#dd0J;IT+;K{PsZqDeaV0mWr`6CbnKvqqN z^<7(-E@o(D@J*4pqbCH6GS5VDm{*WQA=r#1L);*C)s-B~3hJ>WAvHYUP>Hp$+0!l` zo2tw`_>`7_;2F{LTrR_vfCb1Pu!^^9O8;RfwLrGcQL4gwGI}Rmr>8HXuaZa#$EYK! z_uYdBhll`R^Aipk2zeT}e-K`{AH$%Ob2kPlyr7;RI%+SjldSmlc6 zdY4)1Zj9^XCYCyH#ix9o@;Q$9?APy7#Kz?JuwmZ#xAH8&BdVC+I`+3|a-RJw+lT@g zfYKV?u>*pPW_mabQyGgV;{4MG4~NR7_#xQ9NsEN)S$;qtaJx`24Iqd%5dsT;^6x@% z(O3vacgQXV!9eXB!C(H$d_xVW>H;BFCWyW~ZaKY?-T-U=zE2x$9nd3vDz+X)MN#X; zo9vqSqSAgC5pH+HTWCuHz)HtQLKocuQ)7T7Iy%;fc*?zC+XFH&ol=*9B&p;Uh3@up z%oXe7YI7G8h(cXg81AbVfQwvA1T7L>}l;U&Lg!@4w2|DRNvtG7O zzJ$M_0>H5(ALcxP-nE7fOj}kU#Irelc5$Lqch}bV(TSj6EH9tO(OV8RLBKF4jI?{@ zMUfjTLVGn9PzStj7G-&YfMA0ILhX)U?7D8huFb0-n*$?Rz5|ZeBF=XIogNj_gmc{ZN>FKgx2;9vkN-)am&6=u9O{nqYiVT&@?s`1>iMGQ`siOvfB zH?x7_gtS4s*uk9L#%({$#*;)V#y)!D)t@m#{l^6P+3cr_#Ytfce44HB!ijeyNu;6i z?>|SbyE3YOi{>9_bqW7kVo>|R$0-sxdN6ZaE7O;Rlg$B+e;Z4|>#l)vW|le^vtY9F ziE%$=*2bnuDbY(X^S0iL0UilFjN$dg zRxLFztqOFdO@|W~X`eWd48rE0DZ4Rjt%QU#ea8Jf&h#VAs4pkAbzd}(KGPqxO>aMm zQjnw6(_(T~vx0>%2`iRTr`FimTvZ7ycDpjvpg4h?3TB>ZT`oFD-#sK7fQad|cy8^;~m(FwgF_~cK$ zm7=GMdn|y;+9een+mkC~f1Y#}-?!b$Mk#&?N$$Qg*dF0j;$VpZR*+`hp7vvK?M}PS zc>ks(#<^u71K4(~CDzNZPb6ox`0AZsAam9b8uMDvrY5tA2{A!|VT_3k!)5vE5NkJW7Q6`_3;w`B$~EHCDoqT4(5E6Z@@nfnD{WXWf2 z#8s}s{j_~v18Wa~!~U#*5)gr>escD?Bs04zPCrjaL*ZtKkHr$W5|dQRo(~i^svNhT z(umce=|(DIdfdf3hGlV5iksndF%XBq3nMeDJLKJBmoA@DbaV(7Qx@u3Mwc;P1N9ax&6Yi`agbuCZaLq=MpWRot6=u$=;X9!*s_${u zEXBkI1scLyOMg*dz2zfiW%eeo<-Z=6IdTF1aBcLRCF^9YbSqg?qKn?`0dJiNx%!7> z2s(figz^KOq7!hvP!i0Nvq~Y?d*N8XqXtg}obzK9Bvm-HW%^HJ$_x#X!-$@qc0R2K z+i1Iz_6mw^c5TH*-|~gD^1Su=3z5fd$VPMikNJxl!wOG3RMk@WT?Y^aEV(FI2jo)t zCMXg8ic9+@!%kb|$0qEOobXNdU%CQV3wnLXP4tIoBz{1vPDoQoc7e0UMJ-Z&X>DHI zn)tTgAiF4D?3p1|8OmjnA6rSD9Y}xs`T4fENKA_UyMY028uh5%6SpS)MyWA-k6QO` z`{S4ay*$kT0;yKjoyAo9&bh)QrrDZ!W@`1OC{O`=LxQug;HX5gXGaJRefW6MzF!H$ ztw>VBP`ut`Ul{JPN|9A~{sboGJ;0Z8v|NrR9m3@&ZvHC3v4?v@YDR;uG9$;?VbjDp zHM}Eu>{Paq6qfHq(v4V=K?vM55Dl-=<*FLaEHiYxb>&Fs{Nb=)*9(rTt9h|Gg+u1W z>S6pnF2bIe@GKe{&Th2`r?SZjR$IE07edWy*sqdLj0=D65HQf>7~{8wKqlTkhvi5WD#(jZyK2YG zZ{TO5SA`%Pt`)@QPvPjpet&LtDi#}Ekcp3b(Pt{&-jtJF|qZCOrjzs*5GtG3?7aI8;d`VbnQ@e`R!G z<6hAa&{+sv?CCam`1LNshi;N4F*&4G-2fbusE%$@KSlO3xvuQwdGRCTE?FXDT`;O^ z!;(a^DTd-`Zc*oLzZC70cYfGJeENg{e++8c1qK$_`h)+20Ik9Oz*1kNqa#}|c2b-a zU)Q6FZ%L;ib)IE_>s5%=tV~kprgF{Y$P7(j{d=hL?TegTp7;DrB82Ss(K3l?dL#99 z>JJaN3+m^oPftw`o0T3e3%eyV^P8j2xi`0Kwm2v7Poc}bbq=NBE!scbR|et>dmPP1 zkbcN;At+(^#rGKTB=ww34l%YxR>ZEW%TU6_+j1Z@C+CStb1?oKUzLv>I+=;Fy2t=5 zTmh$nxJ{LINq`EYQx>ff!>rmpo9<$Ft_ zqNbkDr!SG4@XA)T(iL$Iux`Gek5V*_c=SvSX;f$>Q4nYqA)F+i+?pKiD4jnY%YFH) z<@78(T6*`|YQD9bZ}(Q(8tQA?TI<91i`b2b3=N76zxAlVuRZDieLLoB77*~;k6({} zZqEGwlK$GNdW-#?^zn7)>i^id`dj4h#`|9p0LEV;f3)F$tNh(%|Ese9s`78o{deBq u4bZ=NzSuxO|FlDYC;jd3ze#GZ+Xw%+EiDEP@iqYQiq5MmA#XbiK>rV)v3j8Z diff --git a/resources/mizdata/thechannel/Merville Calonne.miz b/resources/mizdata/thechannel/Merville Calonne.miz deleted file mode 100644 index e0be3b8af5d79eaaf3bc0939e3605b6037682677..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9704 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 From 4069074f4120660f4c3661184cc06a88714af90b Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Apr 2021 21:40:58 -0700 Subject: [PATCH 053/438] Move unit delivery out of an unrelated file. Historically this inherited from Event but there was no reason for that. That's gone now. Finish the separation and move the unit order tracking class out of the combat results reaction class's file. --- game/event/event.py | 146 +------------------ game/game.py | 8 +- game/theater/controlpoint.py | 4 +- game/unitdelivery.py | 151 ++++++++++++++++++++ qt_ui/windows/basemenu/QRecruitBehaviour.py | 14 +- 5 files changed, 164 insertions(+), 159 deletions(-) create mode 100644 game/unitdelivery.py diff --git a/game/event/event.py b/game/event/event.py index beebd54a..d37664ee 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -1,22 +1,19 @@ from __future__ import annotations import logging -from collections import defaultdict -from typing import Dict, List, Optional, TYPE_CHECKING, Type +from typing import List, TYPE_CHECKING, Type from dcs.mapping import Point from dcs.task import Task -from dcs.unittype import UnitType, VehicleType +from dcs.unittype import VehicleType from game import persistency from game.debriefing import AirLosses, Debriefing from game.infos.information import Information from game.operation.operation import Operation -from game.theater import ControlPoint, SupplyRoute +from game.theater import ControlPoint from gen import AirTaskingOrder from gen.ground_forces.combat_stance import CombatStance -from ..db import PRICES -from ..transfers import RoadTransferOrder from ..unitmap import UnitMap if TYPE_CHECKING: @@ -455,140 +452,3 @@ class Event: info = Information("Units redeployed", text, self.game.turn) self.game.informations.append(info) logging.info(text) - - -class UnitsDeliveryEvent: - def __init__(self, destination: ControlPoint) -> None: - self.destination = destination - - # Maps unit type to order quantity. - self.units: Dict[Type[UnitType], int] = defaultdict(int) - - def __str__(self) -> str: - return f"Pending delivery to {self.destination}" - - def order(self, units: Dict[Type[UnitType], int]) -> None: - for k, v in units.items(): - self.units[k] += v - - def sell(self, units: Dict[Type[UnitType], int]) -> None: - for k, v in units.items(): - self.units[k] -= v - - def refund_all(self, game: Game) -> None: - self.refund(game, self.units) - self.units = defaultdict(int) - - def refund(self, game: Game, units: Dict[Type[UnitType], int]) -> None: - for unit_type, count in units.items(): - try: - price = PRICES[unit_type] - except KeyError: - logging.error(f"Could not refund {unit_type.id}, price unknown") - continue - - logging.info(f"Refunding {count} {unit_type.id} at {self.destination.name}") - game.adjust_budget(price * count, player=self.destination.captured) - - def pending_orders(self, unit_type: Type[UnitType]) -> int: - pending_units = self.units.get(unit_type) - if pending_units is None: - pending_units = 0 - return pending_units - - def available_next_turn(self, unit_type: Type[UnitType]) -> int: - current_units = self.destination.base.total_units_of_type(unit_type) - return self.pending_orders(unit_type) + current_units - - def process(self, game: Game) -> None: - ground_unit_source = self.find_ground_unit_source(game) - bought_units: Dict[Type[UnitType], int] = {} - units_needing_transfer: Dict[Type[VehicleType], int] = {} - sold_units: Dict[Type[UnitType], int] = {} - for unit_type, count in self.units.items(): - coalition = "Ally" if self.destination.captured else "Enemy" - name = unit_type.id - - if ( - issubclass(unit_type, VehicleType) - and self.destination != ground_unit_source - ): - source = ground_unit_source - d = units_needing_transfer - ground = True - else: - source = self.destination - d = bought_units - ground = False - - if count >= 0: - # The destination dict will be set appropriately even if we have no - # source, and we'll refund later, buto nly emit the message when we're - # actually completing the purchase. - d[unit_type] = count - if ground or ground_unit_source is not None: - game.message( - f"{coalition} reinforcements: {name} x {count} at {source}" - ) - else: - sold_units[unit_type] = -count - game.message(f"{coalition} sold: {name} x {-count} at {source}") - - self.units = defaultdict(int) - self.destination.base.commision_units(bought_units) - self.destination.base.commit_losses(sold_units) - - if ground_unit_source is None: - game.message( - f"{self.destination.name} lost its source for ground unit " - "reinforcements. Refunding purchase price." - ) - self.refund(game, units_needing_transfer) - return - - if units_needing_transfer: - ground_unit_source.base.commision_units(units_needing_transfer) - game.transfers.new_transfer( - RoadTransferOrder( - ground_unit_source, - self.destination, - self.destination.captured, - units_needing_transfer, - ) - ) - - def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]: - # This is running *after* the turn counter has been incremented, so this is the - # reaction to turn 0. On turn zero we allow units to be recruited anywhere for - # delivery on turn 1 so that turn 1 always starts with units on the front line. - if game.turn == 1: - return self.destination - - # Fast path if the destination is a valid source. - if self.destination.can_recruit_ground_units(game): - return self.destination - - supply_route = SupplyRoute.for_control_point(self.destination) - - sources = [] - for control_point in supply_route: - if control_point.can_recruit_ground_units(game): - sources.append(control_point) - - if not sources: - return None - - # Fast path to skip the distance calculation if we have only one option. - if len(sources) == 1: - return sources[0] - - closest = sources[0] - distance = len(supply_route.shortest_path_between(self.destination, closest)) - for source in sources: - new_distance = len( - supply_route.shortest_path_between(self.destination, source) - ) - if new_distance < distance: - closest = source - distance = new_distance - return closest diff --git a/game/game.py b/game/game.py index dc1d6c2e..dae06d7c 100644 --- a/game/game.py +++ b/game/game.py @@ -15,6 +15,7 @@ from game import db from game.inventory import GlobalAircraftInventory from game.models.game_stats import GameStats from game.plugins import LuaPluginManager +from game.theater.theatergroundobject import MissileSiteGroundObject from gen.ato import AirTaskingOrder from gen.conflictgen import Conflict from gen.flights.ai_flight_planner import CoalitionMissionPlanner @@ -23,7 +24,7 @@ from gen.flights.flight import FlightType from gen.ground_forces.ai_ground_planner import GroundPlanner from . import persistency from .debriefing import Debriefing -from .event.event import Event, UnitsDeliveryEvent +from .event.event import Event from .event.frontlineattack import FrontlineAttackEvent from .factions.faction import Faction from .income import Income @@ -31,10 +32,9 @@ from .infos.information import Information from .navmesh import NavMesh from .procurement import ProcurementAi from .settings import Settings -from .theater import ConflictTheater, ControlPoint, TheaterGroundObject -from game.theater.theatergroundobject import MissileSiteGroundObject +from .theater import ConflictTheater from .threatzones import ThreatZones -from .transfers import Convoy, ConvoyMap, PendingTransfers, RoadTransferOrder +from .transfers import PendingTransfers from .unitmap import UnitMap from .weather import Conditions, TimeOfDay diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 84e209b7..d63b3b48 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -272,9 +272,9 @@ class ControlPoint(MissionTarget, ABC): self.cptype = cptype # TODO: Should be Airbase specific. self.stances: Dict[int, CombatStance] = {} - from ..event import UnitsDeliveryEvent + from ..unitdelivery import PendingUnitDeliveries - self.pending_unit_deliveries = UnitsDeliveryEvent(self) + self.pending_unit_deliveries = PendingUnitDeliveries(self) self.target_position: Optional[Point] = None diff --git a/game/unitdelivery.py b/game/unitdelivery.py new file mode 100644 index 00000000..36f30fcc --- /dev/null +++ b/game/unitdelivery.py @@ -0,0 +1,151 @@ +from __future__ import annotations + +import logging +from collections import defaultdict +from typing import Dict, Optional, TYPE_CHECKING, Type + +from dcs.unittype import UnitType, VehicleType + +from game.theater import ControlPoint, SupplyRoute +from .db import PRICES +from .transfers import RoadTransferOrder + +if TYPE_CHECKING: + from .game import Game + + +class PendingUnitDeliveries: + def __init__(self, destination: ControlPoint) -> None: + self.destination = destination + + # Maps unit type to order quantity. + self.units: Dict[Type[UnitType], int] = defaultdict(int) + + def __str__(self) -> str: + return f"Pending delivery to {self.destination}" + + def order(self, units: Dict[Type[UnitType], int]) -> None: + for k, v in units.items(): + self.units[k] += v + + def sell(self, units: Dict[Type[UnitType], int]) -> None: + for k, v in units.items(): + self.units[k] -= v + + def refund_all(self, game: Game) -> None: + self.refund(game, self.units) + self.units = defaultdict(int) + + def refund(self, game: Game, units: Dict[Type[UnitType], int]) -> None: + for unit_type, count in units.items(): + try: + price = PRICES[unit_type] + except KeyError: + logging.error(f"Could not refund {unit_type.id}, price unknown") + continue + + logging.info(f"Refunding {count} {unit_type.id} at {self.destination.name}") + game.adjust_budget(price * count, player=self.destination.captured) + + def pending_orders(self, unit_type: Type[UnitType]) -> int: + pending_units = self.units.get(unit_type) + if pending_units is None: + pending_units = 0 + return pending_units + + def available_next_turn(self, unit_type: Type[UnitType]) -> int: + current_units = self.destination.base.total_units_of_type(unit_type) + return self.pending_orders(unit_type) + current_units + + def process(self, game: Game) -> None: + ground_unit_source = self.find_ground_unit_source(game) + bought_units: Dict[Type[UnitType], int] = {} + units_needing_transfer: Dict[Type[VehicleType], int] = {} + sold_units: Dict[Type[UnitType], int] = {} + for unit_type, count in self.units.items(): + coalition = "Ally" if self.destination.captured else "Enemy" + name = unit_type.id + + if ( + issubclass(unit_type, VehicleType) + and self.destination != ground_unit_source + ): + source = ground_unit_source + d = units_needing_transfer + ground = True + else: + source = self.destination + d = bought_units + ground = False + + if count >= 0: + # The destination dict will be set appropriately even if we have no + # source, and we'll refund later, buto nly emit the message when we're + # actually completing the purchase. + d[unit_type] = count + if ground or ground_unit_source is not None: + game.message( + f"{coalition} reinforcements: {name} x {count} at {source}" + ) + else: + sold_units[unit_type] = -count + game.message(f"{coalition} sold: {name} x {-count} at {source}") + + self.units = defaultdict(int) + self.destination.base.commision_units(bought_units) + self.destination.base.commit_losses(sold_units) + + if ground_unit_source is None: + game.message( + f"{self.destination.name} lost its source for ground unit " + "reinforcements. Refunding purchase price." + ) + self.refund(game, units_needing_transfer) + return + + if units_needing_transfer: + ground_unit_source.base.commision_units(units_needing_transfer) + game.transfers.new_transfer( + RoadTransferOrder( + ground_unit_source, + self.destination, + self.destination.captured, + units_needing_transfer, + ) + ) + + def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]: + # This is running *after* the turn counter has been incremented, so this is the + # reaction to turn 0. On turn zero we allow units to be recruited anywhere for + # delivery on turn 1 so that turn 1 always starts with units on the front line. + if game.turn == 1: + return self.destination + + # Fast path if the destination is a valid source. + if self.destination.can_recruit_ground_units(game): + return self.destination + + supply_route = SupplyRoute.for_control_point(self.destination) + + sources = [] + for control_point in supply_route: + if control_point.can_recruit_ground_units(game): + sources.append(control_point) + + if not sources: + return None + + # Fast path to skip the distance calculation if we have only one option. + if len(sources) == 1: + return sources[0] + + closest = sources[0] + distance = len(supply_route.shortest_path_between(self.destination, closest)) + for source in sources: + new_distance = len( + supply_route.shortest_path_between(self.destination, source) + ) + if new_distance < distance: + closest = source + distance = new_distance + return closest diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 67a26ca6..a9209521 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -1,26 +1,20 @@ import logging -from typing import Callable, Set, Type +from typing import Type -from PySide2.QtCore import Qt from PySide2.QtWidgets import ( - QFrame, - QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLayout, QPushButton, - QScrollArea, QSizePolicy, QSpacerItem, - QVBoxLayout, - QWidget, ) -from dcs.unittype import FlyingType, UnitType +from dcs.unittype import UnitType from game import db -from game.event import UnitsDeliveryEvent from game.theater import ControlPoint +from game.unitdelivery import PendingUnitDeliveries from qt_ui.models import GameModel from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.QUnitInfoWindow import QUnitInfoWindow @@ -40,7 +34,7 @@ class QRecruitBehaviour: self.update_available_budget() @property - def pending_deliveries(self) -> UnitsDeliveryEvent: + def pending_deliveries(self) -> PendingUnitDeliveries: return self.cp.pending_unit_deliveries @property From 26cd2d3feff69c3ec56d6a257b1aeaacbadfb2dc Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Apr 2021 22:24:57 -0700 Subject: [PATCH 054/438] Add todo for transfer refactor. --- game/transfers.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/game/transfers.py b/game/transfers.py index 0819baf1..ffbc3d9e 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -16,6 +16,11 @@ from gen.naming import namegen from gen.flights.flight import Flight, FlightType +# TODO: Remove base classes. +# Eventually we'll want multi-mode transfers (convoy from factory to port, onto a ship, +# then airlifted to the final destination, etc). To do this we'll need to make the +# transfer *order* represent the full journey and let classes like Convoy handle the +# individual hops. @dataclass class TransferOrder: """The base type of all transfer orders. From c258409a8d6d4e692862c39f2f8dae05aaeba2c4 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 01:00:33 -0700 Subject: [PATCH 055/438] Add AI planning for airlifts. Downside to the current implementation is that whether or not transports that were purchased last turn will be available for airlift this turn is arbitrary. This is because transfers are created at the same time as units are delivered, and units are delivered in an arbitrary order per CP. If the helicopters are delivered before the ground units they'll have access to the transports, otherwise they'll be refunded. This will be fixed later when I rework the transfer requests to not require immediate fulfillment. https://github.com/Khopa/dcs_liberation/issues/825 --- changelog.md | 2 +- game/game.py | 15 +- game/theater/controlpoint.py | 6 +- game/transfers.py | 86 ++++++++- game/unitdelivery.py | 119 +++++++++---- qt_ui/widgets/map/QFrontLine.py | 4 + qt_ui/widgets/map/QMapControlPoint.py | 4 +- .../windows/basemenu/NewUnitTransferDialog.py | 167 +++++------------- qt_ui/windows/basemenu/QBaseMenu2.py | 5 +- 9 files changed, 244 insertions(+), 164 deletions(-) diff --git a/changelog.md b/changelog.md index f09addf7..0cd53adb 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ Saves from 2.5 are not compatible with 2.6. ## Features/Improvements -* **[Campaign]** Ground units can now be transferred by road. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. +* **[Campaign]** Ground units can now be transferred by road and airlift. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory and a transfer will be created on the next turn. This feature is off by default. * **[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. diff --git a/game/game.py b/game/game.py index dae06d7c..e264842f 100644 --- a/game/game.py +++ b/game/game.py @@ -154,6 +154,11 @@ class Game: # Regenerate any state that was not persisted. self.on_load() + def ato_for(self, player: bool) -> AirTaskingOrder: + if player: + return self.blue_ato + return self.red_ato + def generate_conditions(self) -> Conditions: return Conditions.generate( self.theater, self.current_day, self.current_turn_time_of_day, self.settings @@ -257,6 +262,10 @@ class Game: self.compute_conflicts_position() self.compute_threat_zones() + def reset_ato(self) -> None: + self.blue_ato.clear() + self.red_ato.clear() + def pass_turn(self, no_action: bool = False) -> None: logging.info("Pass turn") self.informations.append( @@ -268,11 +277,13 @@ class Game: # one hop ahead. ControlPoint.process_turn handles unit deliveries. self.transfers.perform_transfers() + # Needs to happen *before* planning transfers so we don't cancel the + self.reset_ato() + for control_point in self.theater.controlpoints: control_point.process_turn(self) self.process_enemy_income() - self.process_player_income() if not no_action and self.turn > 1: @@ -325,8 +336,6 @@ class Game: self.compute_conflicts_position() self.compute_threat_zones() self.ground_planners = {} - self.blue_ato.clear() - self.red_ato.clear() blue_planner = CoalitionMissionPlanner(self, is_player=True) blue_planner.plan_missions() diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index d63b3b48..a6777663 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -344,10 +344,8 @@ class ControlPoint(MissionTarget, ABC): if not game.settings.enable_new_ground_unit_recruitment: return True - from game.theater.supplyroutes import SupplyRoute - - for cp in SupplyRoute.for_control_point(self): - if cp.can_recruit_ground_units(game): + for cp in game.theater.controlpoints: + if cp.is_friendly(self.captured) and cp.can_recruit_ground_units(game): return True return False diff --git a/game/transfers.py b/game/transfers.py index ffbc3d9e..d45f7832 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -6,15 +6,19 @@ from dataclasses import dataclass, field from functools import singledispatchmethod from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type -from dcs.unittype import VehicleType +from dcs.unittype import FlyingType, VehicleType -if TYPE_CHECKING: - from game import Game +from gen.ato import Package +from gen.flights.flightplan import FlightPlanBuilder from game.theater import ControlPoint, MissionTarget from game.theater.supplyroutes import SupplyRoute from gen.naming import namegen from gen.flights.flight import Flight, FlightType +if TYPE_CHECKING: + from game import Game + from game.inventory import ControlPointAircraftInventory + # TODO: Remove base classes. # Eventually we'll want multi-mode transfers (convoy from factory to port, onto a ship, @@ -95,6 +99,82 @@ class AirliftOrder(TransferOrder): raise KeyError +class AirliftPlanner: + def __init__( + self, + game: Game, + pickup: ControlPoint, + drop_off: ControlPoint, + units: Dict[Type[VehicleType], int], + ) -> None: + self.game = game + self.pickup = pickup + self.drop_off = drop_off + self.units = units + self.for_player = drop_off.captured + self.package = Package(target=drop_off, auto_asap=True) + + def create_package_for_airlift(self) -> Dict[Type[VehicleType], int]: + for cp in self.game.theater.player_points(): + inventory = self.game.aircraft_inventory.for_control_point(cp) + for unit_type, available in inventory.all_aircraft: + if unit_type.helicopter: + while available and self.needed_capacity: + flight_size = self.create_airlift_flight(unit_type, inventory) + available -= flight_size + self.game.ato_for(self.for_player).add_package(self.package) + return self.units + + def take_units(self, count: int) -> Dict[Type[VehicleType], int]: + taken = {} + for unit_type, remaining in self.units.items(): + take = min(remaining, count) + count -= take + self.units[unit_type] -= take + taken[unit_type] = take + if not count: + break + return taken + + @property + def needed_capacity(self) -> int: + return sum(c for c in self.units.values()) + + def create_airlift_flight( + self, unit_type: Type[FlyingType], inventory: ControlPointAircraftInventory + ) -> int: + available = inventory.available(unit_type) + # 4 is the max flight size in DCS. + flight_size = min(self.needed_capacity, available, 4) + flight = Flight( + self.package, + self.game.player_country, + unit_type, + flight_size, + FlightType.TRANSPORT, + self.game.settings.default_start_type, + departure=inventory.control_point, + arrival=inventory.control_point, + divert=None, + ) + + transfer = AirliftOrder( + player=True, + origin=self.pickup, + destination=self.drop_off, + units=self.take_units(flight_size), + flight=flight, + ) + flight.cargo = transfer + + self.package.add_flight(flight) + planner = FlightPlanBuilder(self.game, self.package, self.for_player) + planner.populate_flight_plan(flight) + self.game.aircraft_inventory.claim_for_flight(flight) + self.game.transfers.new_transfer(transfer) + return flight_size + + class Convoy(MissionTarget): def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: super().__init__(namegen.next_convoy_name(), origin.position) diff --git a/game/unitdelivery.py b/game/unitdelivery.py index 36f30fcc..845eec69 100644 --- a/game/unitdelivery.py +++ b/game/unitdelivery.py @@ -2,18 +2,28 @@ from __future__ import annotations import logging from collections import defaultdict +from dataclasses import dataclass from typing import Dict, Optional, TYPE_CHECKING, Type from dcs.unittype import UnitType, VehicleType from game.theater import ControlPoint, SupplyRoute +from gen.ato import Package +from gen.flights.closestairfields import ObjectiveDistanceCache +from gen.flights.flight import Flight from .db import PRICES -from .transfers import RoadTransferOrder +from .transfers import AirliftOrder, AirliftPlanner, RoadTransferOrder if TYPE_CHECKING: from .game import Game +@dataclass(frozen=True) +class GroundUnitSource: + control_point: ControlPoint + requires_airlift: bool + + class PendingUnitDeliveries: def __init__(self, destination: ControlPoint) -> None: self.destination = destination @@ -59,6 +69,14 @@ class PendingUnitDeliveries: def process(self, game: Game) -> None: ground_unit_source = self.find_ground_unit_source(game) + if ground_unit_source is None: + game.message( + f"{self.destination.name} lost its source for ground unit " + "reinforcements. Refunding purchase price." + ) + self.refund_all(game) + return + bought_units: Dict[Type[UnitType], int] = {} units_needing_transfer: Dict[Type[VehicleType], int] = {} sold_units: Dict[Type[UnitType], int] = {} @@ -68,25 +86,19 @@ class PendingUnitDeliveries: if ( issubclass(unit_type, VehicleType) - and self.destination != ground_unit_source + and self.destination != ground_unit_source.control_point ): - source = ground_unit_source + source = ground_unit_source.control_point d = units_needing_transfer - ground = True else: source = self.destination d = bought_units - ground = False if count >= 0: - # The destination dict will be set appropriately even if we have no - # source, and we'll refund later, buto nly emit the message when we're - # actually completing the purchase. d[unit_type] = count - if ground or ground_unit_source is not None: - game.message( - f"{coalition} reinforcements: {name} x {count} at {source}" - ) + game.message( + f"{coalition} reinforcements: {name} x {count} at {source}" + ) else: sold_units[unit_type] = -count game.message(f"{coalition} sold: {name} x {-count} at {source}") @@ -95,36 +107,70 @@ class PendingUnitDeliveries: self.destination.base.commision_units(bought_units) self.destination.base.commit_losses(sold_units) - if ground_unit_source is None: - game.message( - f"{self.destination.name} lost its source for ground unit " - "reinforcements. Refunding purchase price." - ) - self.refund(game, units_needing_transfer) - return - if units_needing_transfer: - ground_unit_source.base.commision_units(units_needing_transfer) - game.transfers.new_transfer( - RoadTransferOrder( - ground_unit_source, - self.destination, - self.destination.captured, - units_needing_transfer, - ) + ground_unit_source.control_point.base.commision_units( + units_needing_transfer ) + if ground_unit_source.requires_airlift: + self.create_air_transfer( + game, ground_unit_source.control_point, units_needing_transfer + ) + else: + self.create_road_transfer( + game, ground_unit_source.control_point, units_needing_transfer + ) - def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]: + def create_air_transfer( + self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int] + ) -> None: + planner = AirliftPlanner(game, source, self.destination, units) + leftovers = planner.create_package_for_airlift() + if leftovers: + game.message( + f"No airlift capacity remaining for {self.destination}. " + "Remaining unit orders were refunded." + ) + self.refund(game, leftovers) + source.base.commit_losses(leftovers) + + def find_transport_for( + self, + origin: ControlPoint, + destination: ControlPoint, + units: Dict[Type[VehicleType], int], + ) -> Optional[Flight]: + pass + + def create_road_transfer( + self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int] + ) -> None: + game.transfers.new_transfer( + RoadTransferOrder( + source, self.destination, self.destination.captured, units + ) + ) + + def find_ground_unit_source(self, game: Game) -> Optional[GroundUnitSource]: # This is running *after* the turn counter has been incremented, so this is the # reaction to turn 0. On turn zero we allow units to be recruited anywhere for # delivery on turn 1 so that turn 1 always starts with units on the front line. if game.turn == 1: - return self.destination + return GroundUnitSource(self.destination, requires_airlift=False) # Fast path if the destination is a valid source. if self.destination.can_recruit_ground_units(game): - return self.destination + return GroundUnitSource(self.destination, requires_airlift=False) + by_road = self.find_ground_unit_source_by_road(game) + if by_road is not None: + return GroundUnitSource(by_road, requires_airlift=False) + + by_air = self.find_ground_unit_source_by_air(game) + if by_air is not None: + return GroundUnitSource(by_air, requires_airlift=True) + return None + + def find_ground_unit_source_by_road(self, game: Game) -> Optional[ControlPoint]: supply_route = SupplyRoute.for_control_point(self.destination) sources = [] @@ -149,3 +195,14 @@ class PendingUnitDeliveries: closest = source distance = new_distance return closest + + def find_ground_unit_source_by_air(self, game: Game) -> Optional[ControlPoint]: + closest_airfields = ObjectiveDistanceCache.get_closest_airfields( + self.destination + ) + for airfield in closest_airfields.operational_airfields: + if airfield.is_friendly( + self.destination.captured + ) and airfield.can_recruit_ground_units(game): + return airfield + return None diff --git a/qt_ui/widgets/map/QFrontLine.py b/qt_ui/widgets/map/QFrontLine.py index 41edcaea..2203bf57 100644 --- a/qt_ui/widgets/map/QFrontLine.py +++ b/qt_ui/widgets/map/QFrontLine.py @@ -103,11 +103,15 @@ class QFrontLine(QGraphicsLineItem): def cheat_forward(self) -> None: self.mission_target.control_point_a.base.affect_strength(0.1) self.mission_target.control_point_b.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.control_point_a.base.affect_strength(-0.1) self.mission_target.control_point_b.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) diff --git a/qt_ui/widgets/map/QMapControlPoint.py b/qt_ui/widgets/map/QMapControlPoint.py index 12bfce6a..b7016536 100644 --- a/qt_ui/widgets/map/QMapControlPoint.py +++ b/qt_ui/widgets/map/QMapControlPoint.py @@ -103,7 +103,9 @@ class QMapControlPoint(QMapObject): def cheat_capture(self) -> None: self.control_point.capture(self.game_model.game, for_player=True) - # Reinitialized ground planners and the like. + # 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) diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index ca6b1bc6..9e2b5af4 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -24,25 +24,27 @@ from PySide2.QtWidgets import ( QWidget, ) from dcs.task import PinpointStrike -from dcs.unittype import FlyingType, UnitType, VehicleType +from dcs.unittype import UnitType from game import Game, db -from game.inventory import ControlPointAircraftInventory from game.theater import ControlPoint, SupplyRoute -from game.transfers import AirliftOrder, RoadTransferOrder -from gen.ato import Package -from gen.flights.flight import Flight, FlightType -from gen.flights.flightplan import FlightPlanBuilder, PlanningError -from qt_ui.models import GameModel, PackageModel +from game.transfers import AirliftPlanner, RoadTransferOrder +from qt_ui.models import GameModel from qt_ui.widgets.QLabeledWidget import QLabeledWidget class TransferDestinationComboBox(QComboBox): - def __init__(self, origin: ControlPoint) -> None: + def __init__(self, game: Game, origin: ControlPoint) -> None: super().__init__() + self.game = game + self.origin = origin - for cp in SupplyRoute.for_control_point(origin): - if cp != origin and cp.captured: + for cp in self.game.theater.controlpoints: + if ( + cp != self.origin + and cp.is_friendly(to_player=True) + and cp.can_deploy_ground_units + ): self.addItem(cp.name, cp) self.model().sort(0) self.setCurrentIndex(0) @@ -109,14 +111,21 @@ class AirliftCapacity: class TransferOptionsPanel(QVBoxLayout): - def __init__(self, origin: ControlPoint, airlift_capacity: AirliftCapacity) -> None: + def __init__( + self, + game: Game, + origin: ControlPoint, + airlift_capacity: AirliftCapacity, + airlift_required: bool, + ) -> None: super().__init__() - self.source_combo_box = TransferDestinationComboBox(origin) + self.source_combo_box = TransferDestinationComboBox(game, origin) self.addLayout(QLabeledWidget("Destination:", self.source_combo_box)) self.airlift = QCheckBox() - self.airlift.toggled.connect(self.set_airlift) - self.addLayout(QLabeledWidget("Airlift (WIP):", self.airlift)) + self.airlift.setChecked(airlift_required) + self.airlift.setDisabled(airlift_required) + self.addLayout(QLabeledWidget("Airlift:", self.airlift)) self.addWidget( QLabel( f"{airlift_capacity.total} airlift capacity " @@ -133,9 +142,6 @@ class TransferOptionsPanel(QVBoxLayout): def current(self) -> ControlPoint: return self.source_combo_box.currentData() - def set_airlift(self, value: bool) -> None: - pass - class TransferControls(QGroupBox): def __init__( @@ -324,13 +330,16 @@ class NewUnitTransferDialog(QDialog): self.setLayout(layout) self.airlift_capacity = AirliftCapacity.to_control_point(game_model.game) - self.dest_panel = TransferOptionsPanel(origin, self.airlift_capacity) + airlift_required = len(SupplyRoute.for_control_point(origin)) == 1 + self.dest_panel = TransferOptionsPanel( + game_model.game, origin, self.airlift_capacity, airlift_required + ) self.dest_panel.changed.connect(self.rebuild_transfers) layout.addLayout(self.dest_panel) self.transfer_panel = ScrollingUnitTransferGrid( origin, - airlift=False, + airlift=airlift_required, airlift_capacity=self.airlift_capacity, game_model=game_model, ) @@ -357,123 +366,41 @@ class NewUnitTransferDialog(QDialog): self.layout().addWidget(self.submit_button) def on_submit(self) -> None: + destination = self.dest_panel.current + supply_route = SupplyRoute.for_control_point(self.origin) + if not self.dest_panel.airlift.isChecked() and destination not in supply_route: + QMessageBox.critical( + self, + "Could not create transfer", + f"Transfers from {self.origin} to {destination} require airlift.", + QMessageBox.Ok, + ) + return transfers = {} for unit_type, count in self.transfer_panel.transfers.items(): if not count: continue logging.info( - f"Transferring {count} {unit_type.id} from " - f"{self.transfer_panel.cp.name} to {self.dest_panel.current.name}" + f"Transferring {count} {unit_type.id} from {self.origin} to " + f"{destination}" ) transfers[unit_type] = count if self.dest_panel.airlift.isChecked(): - self.create_package_for_airlift( - self.transfer_panel.cp, - self.dest_panel.current, + planner = AirliftPlanner( + self.game_model.game, + self.origin, + destination, transfers, ) + planner.create_package_for_airlift() else: transfer = RoadTransferOrder( player=True, - origin=self.transfer_panel.cp, - destination=self.dest_panel.current, + origin=self.origin, + destination=destination, units=transfers, ) self.game_model.transfer_model.new_transfer(transfer) self.close() - - @staticmethod - def take_units( - units: Dict[Type[VehicleType], int], count: int - ) -> Dict[Type[VehicleType], int]: - taken = {} - for unit_type, remaining in units.items(): - take = min(remaining, count) - count -= take - units[unit_type] -= take - taken[unit_type] = take - if not count: - break - return taken - - def create_airlift_flight( - self, - game: Game, - package_model: PackageModel, - unit_type: Type[FlyingType], - inventory: ControlPointAircraftInventory, - needed_capacity: int, - pickup: ControlPoint, - drop_off: ControlPoint, - units: Dict[Type[VehicleType], int], - ) -> int: - available = inventory.available(unit_type) - # 4 is the max flight size in DCS. - flight_size = min(needed_capacity, available, 4) - flight = Flight( - package_model.package, - game.player_country, - unit_type, - flight_size, - FlightType.TRANSPORT, - game.settings.default_start_type, - departure=inventory.control_point, - arrival=inventory.control_point, - divert=None, - ) - - transfer = AirliftOrder( - player=True, - origin=pickup, - destination=drop_off, - units=self.take_units(units, flight_size), - flight=flight, - ) - flight.cargo = transfer - - package_model.add_flight(flight) - planner = FlightPlanBuilder(game, package_model.package, is_player=True) - try: - planner.populate_flight_plan(flight) - except PlanningError as ex: - package_model.delete_flight(flight) - logging.exception("Could not create flight") - QMessageBox.critical( - self, "Could not create flight", str(ex), QMessageBox.Ok - ) - game.aircraft_inventory.claim_for_flight(flight) - self.game_model.transfer_model.new_transfer(transfer) - return flight_size - - def create_package_for_airlift( - self, - pickup: ControlPoint, - drop_off: ControlPoint, - units: Dict[Type[VehicleType], int], - ) -> None: - package = Package(target=drop_off, auto_asap=True) - package_model = PackageModel(package, self.game_model) - - needed_capacity = sum(c for c in units.values()) - game = self.game_model.game - for cp in game.theater.player_points(): - inventory = game.aircraft_inventory.for_control_point(cp) - for unit_type, available in inventory.all_aircraft: - if unit_type.helicopter: - while available and needed_capacity: - flight_size = self.create_airlift_flight( - self.game_model.game, - package_model, - unit_type, - inventory, - needed_capacity, - pickup, - drop_off, - units, - ) - available -= flight_size - needed_capacity -= flight_size - package_model.update_tot() - self.game_model.ato_model.add_package(package) diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index 1dac5f43..b5b9c885 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -105,7 +105,10 @@ class QBaseMenu2(QDialog): @property def has_transfer_destinations(self) -> bool: - return len(SupplyRoute.for_control_point(self.cp)) > 1 + return ( + self.cp.runway_is_operational() + or len(SupplyRoute.for_control_point(self.cp)) > 1 + ) @property def can_repair_runway(self) -> bool: From 45913b0add4c19be59b105dda71728d8abbb72e8 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 01:11:46 -0700 Subject: [PATCH 056/438] Bump to 3.0. DCS version numbers and Liberation version numbers are getting confusing. Push us ahead before we're both on 2.7. --- changelog.md | 4 ++-- game/version.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 0cd53adb..a28e5bd2 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,6 @@ -# 2.6.0 +# 3.0.0 -Saves from 2.5 are not compatible with 2.6. +Saves from 2.5 are not compatible with 3.0. ## Features/Improvements diff --git a/game/version.py b/game/version.py index 71c43a10..5827b455 100644 --- a/game/version.py +++ b/game/version.py @@ -2,7 +2,7 @@ from pathlib import Path def _build_version_string() -> str: - components = ["2.6"] + components = ["3.0"] build_number_path = Path("resources/buildnumber") if build_number_path.exists(): with build_number_path.open("r") as build_number_file: From 6aba07c33b5de30283d38560dfb8176a704ded5c Mon Sep 17 00:00:00 2001 From: Khopa Date: Fri, 23 Apr 2021 23:45:14 +0200 Subject: [PATCH 057/438] Fixed ai_flight_planner for maps lacking frontlines (such as battle of britain on The Channel map) --- gen/flights/ai_flight_planner.py | 33 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 72309cd9..a07bb6dd 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -460,21 +460,31 @@ class ObjectiveFinder: c for c in self.game.theater.controlpoints if c.is_friendly(self.is_player) ) - def farthest_friendly_control_point(self) -> ControlPoint: + def farthest_friendly_control_point(self) -> Optional[ControlPoint]: """ Iterates over all friendly control points and find the one farthest away from the frontline BUT! prefer Cvs. Everybody likes CVs! """ from_frontline = 0 + cp = None + first_friendly_cp = None for c in self.game.theater.controlpoints: - if c.is_carrier and c.is_friendly(self.is_player): - return c - if c.is_friendly(self.is_player) and c.has_frontline: - if c.distance_to(self.front_lines().__next__()) > from_frontline: - from_frontline = c.distance_to(self.front_lines().__next__()) - cp = c - return cp + if c.is_friendly(self.is_player): + if first_friendly_cp is None: + first_friendly_cp = c + if c.is_carrier: + return c + if c.has_active_frontline: + if c.distance_to(self.front_lines().__next__()) > from_frontline: + from_frontline = c.distance_to(self.front_lines().__next__()) + cp = c + + # If no frontlines on the map, return the first friendly cp + if cp is None: + return first_friendly_cp + else: + return cp def enemy_control_points(self) -> Iterator[ControlPoint]: """Iterates over all enemy control points.""" @@ -556,9 +566,10 @@ class CoalitionMissionPlanner: # Find farthest, friendly CP for AEWC cp = self.objective_finder.farthest_friendly_control_point() - yield ProposedMission( - cp, [ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)] - ) + if cp is not None: + yield ProposedMission( + cp, [ProposedFlight(FlightType.AEWC, 1, self.MAX_AWEC_RANGE)] + ) # Find friendly CPs within 100 nmi from an enemy airfield, plan CAP. for cp in self.objective_finder.vulnerable_control_points(): From d80f7ebf3ba23cd4594e7d6132d77ecd89e9a416 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 18:40:31 -0700 Subject: [PATCH 058/438] Refactor transfers to support unfulfilled orders. This gives a clean break between the transfer request and the type of transport allocated to make way for transports that need to switch types (to support driving to a port, then getting on a ship, to a train, then back on the road, etc). https://github.com/Khopa/dcs_liberation/issues/823 --- game/transfers.py | 361 ++++++++---------- game/unitdelivery.py | 44 +-- game/unitmap.py | 13 +- gen/aircraft.py | 37 +- gen/convoys.py | 2 +- gen/flights/flight.py | 4 +- qt_ui/models.py | 12 +- qt_ui/widgets/map/QLiberationMap.py | 33 +- .../windows/basemenu/NewUnitTransferDialog.py | 120 +----- 9 files changed, 227 insertions(+), 399 deletions(-) diff --git a/game/transfers.py b/game/transfers.py index d45f7832..4236a7aa 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -20,11 +20,14 @@ if TYPE_CHECKING: from game.inventory import ControlPointAircraftInventory -# TODO: Remove base classes. -# Eventually we'll want multi-mode transfers (convoy from factory to port, onto a ship, -# then airlifted to the final destination, etc). To do this we'll need to make the -# transfer *order* represent the full journey and let classes like Convoy handle the -# individual hops. +class Transport: + def find_escape_route(self) -> Optional[ControlPoint]: + raise NotImplementedError + + def description(self) -> str: + raise NotImplementedError + + @dataclass class TransferOrder: """The base type of all transfer orders. @@ -38,59 +41,30 @@ class TransferOrder: #: The location the units are transferring to. destination: ControlPoint + #: The current position of the group being transferred. Groups may make multiple + #: stops and can switch transport modes before reaching their destination. + position: ControlPoint = field(init=False) + #: True if the transfer order belongs to the player. - player: bool + player: bool = field(init=False) #: The units being transferred. units: Dict[Type[VehicleType], int] - @property - def description(self) -> str: - raise NotImplementedError - - -@dataclass -class RoadTransferOrder(TransferOrder): - """A transfer order that moves units by road.""" - - #: The current position of the group being transferred. Groups move one control - #: point a turn through the supply line. - position: ControlPoint = field(init=False) + transport: Optional[Transport] = field(default=None) def __post_init__(self) -> None: self.position = self.origin - - def path(self) -> List[ControlPoint]: - supply_route = SupplyRoute.for_control_point(self.position) - return supply_route.shortest_path_between(self.position, self.destination) - - def next_stop(self) -> ControlPoint: - return self.path()[0] + self.player = self.origin.is_friendly(to_player=True) @property def description(self) -> str: - path = self.path() - if len(path) == 1: - turns = "1 turn" - else: - turns = f"{len(path)} turns" - return f"Currently at {self.position}. Arrives at destination in {turns}." + if self.transport is None: + return "No transports available" + return self.transport.description() - -@dataclass -class AirliftOrder(TransferOrder): - """A transfer order that moves units by cargo planes and helicopters.""" - - flight: Flight - - @property - def description(self) -> str: - return "Airlift" - - def iter_units(self) -> Iterator[Type[VehicleType]]: - for unit_type, count in self.units.items(): - for _ in range(count): - yield unit_type + def kill_all(self) -> None: + self.units.clear() def kill_unit(self, unit_type: Type[VehicleType]) -> None: if unit_type in self.units: @@ -98,54 +72,102 @@ class AirliftOrder(TransferOrder): return raise KeyError + @property + def size(self) -> int: + return sum(c for c in self.units.values()) + + def iter_units(self) -> Iterator[Type[VehicleType]]: + for unit_type, count in self.units.items(): + for _ in range(count): + yield unit_type + + @property + def completed(self) -> bool: + return self.destination == self.position or not self.units + + def disband_at(self, location: ControlPoint) -> None: + logging.info(f"Units halting at {location}.") + location.base.commision_units(self.units) + self.units.clear() + + def proceed(self) -> None: + if self.transport is None: + return + + if not self.destination.is_friendly(self.player): + logging.info(f"Transfer destination {self.destination} was captured.") + if self.position.is_friendly(self.player): + self.disband_at(self.position) + elif (escape_route := self.transport.find_escape_route()) is not None: + self.disband_at(escape_route) + else: + logging.info( + f"No escape route available. Units were surrounded and destroyed " + "during transfer." + ) + self.kill_all() + return + + self.position = self.destination + self.transport = None + + if self.completed: + self.disband_at(self.position) + + +@dataclass +class Airlift(Transport): + """A transfer order that moves units by cargo planes and helicopters.""" + + transfer: TransferOrder + + flight: Flight + + @property + def units(self) -> Dict[Type[VehicleType], int]: + return self.transfer.units + + @property + def player_owned(self) -> bool: + return self.transfer.player + + def find_escape_route(self) -> Optional[ControlPoint]: + # TODO: Move units to closest base. + return None + + def description(self) -> str: + return f"Being airlifted by {self.flight}" + class AirliftPlanner: - def __init__( - self, - game: Game, - pickup: ControlPoint, - drop_off: ControlPoint, - units: Dict[Type[VehicleType], int], - ) -> None: + def __init__(self, game: Game, transfer: TransferOrder) -> None: self.game = game - self.pickup = pickup - self.drop_off = drop_off - self.units = units - self.for_player = drop_off.captured - self.package = Package(target=drop_off, auto_asap=True) + self.transfer = transfer + self.for_player = transfer.destination.captured + self.package = Package(target=transfer.destination, auto_asap=True) - def create_package_for_airlift(self) -> Dict[Type[VehicleType], int]: + def create_package_for_airlift(self) -> None: for cp in self.game.theater.player_points(): inventory = self.game.aircraft_inventory.for_control_point(cp) for unit_type, available in inventory.all_aircraft: if unit_type.helicopter: - while available and self.needed_capacity: + while available and self.transfer.transport is None: flight_size = self.create_airlift_flight(unit_type, inventory) available -= flight_size self.game.ato_for(self.for_player).add_package(self.package) - return self.units - - def take_units(self, count: int) -> Dict[Type[VehicleType], int]: - taken = {} - for unit_type, remaining in self.units.items(): - take = min(remaining, count) - count -= take - self.units[unit_type] -= take - taken[unit_type] = take - if not count: - break - return taken - - @property - def needed_capacity(self) -> int: - return sum(c for c in self.units.values()) def create_airlift_flight( self, unit_type: Type[FlyingType], inventory: ControlPointAircraftInventory ) -> int: available = inventory.available(unit_type) # 4 is the max flight size in DCS. - flight_size = min(self.needed_capacity, available, 4) + flight_size = min(self.transfer.size, available, 4) + + if flight_size < self.transfer.size: + transfer = self.game.transfers.split_transfer(self.transfer, flight_size) + else: + transfer = self.transfer + flight = Flight( self.package, self.game.player_country, @@ -156,31 +178,25 @@ class AirliftPlanner: departure=inventory.control_point, arrival=inventory.control_point, divert=None, + cargo=transfer, ) - transfer = AirliftOrder( - player=True, - origin=self.pickup, - destination=self.drop_off, - units=self.take_units(flight_size), - flight=flight, - ) - flight.cargo = transfer + transport = Airlift(transfer, flight) + transfer.transport = transport self.package.add_flight(flight) planner = FlightPlanBuilder(self.game, self.package, self.for_player) planner.populate_flight_plan(flight) self.game.aircraft_inventory.claim_for_flight(flight) - self.game.transfers.new_transfer(transfer) return flight_size -class Convoy(MissionTarget): +class Convoy(MissionTarget, Transport): def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: super().__init__(namegen.next_convoy_name(), origin.position) self.origin = origin self.destination = destination - self.transfers: List[RoadTransferOrder] = [] + self.transfers: List[TransferOrder] = [] def mission_types(self, for_player: bool) -> Iterator[FlightType]: if self.is_friendly(for_player): @@ -192,17 +208,20 @@ class Convoy(MissionTarget): def is_friendly(self, to_player: bool) -> bool: return self.origin.captured - def add_units(self, transfer: RoadTransferOrder) -> None: + def add_units(self, transfer: TransferOrder) -> None: self.transfers.append(transfer) + transfer.transport = self - def remove_units(self, transfer: RoadTransferOrder) -> None: + def remove_units(self, transfer: TransferOrder) -> None: self.transfers.remove(transfer) def kill_unit(self, unit_type: Type[VehicleType]) -> None: for transfer in self.transfers: - if unit_type in transfer.units: - transfer.units[unit_type] -= 1 + try: + transfer.kill_unit(unit_type) return + except KeyError: + pass raise KeyError @property @@ -221,6 +240,12 @@ class Convoy(MissionTarget): def player_owned(self) -> bool: return self.origin.captured + def find_escape_route(self) -> Optional[ControlPoint]: + return None + + def description(self) -> str: + return f"In a convoy to {self.destination}" + class ConvoyMap: def __init__(self) -> None: @@ -255,18 +280,21 @@ class ConvoyMap: def disband_convoy(self, convoy: Convoy) -> None: del self.convoys[convoy.origin][convoy.destination] - def add(self, transfer: RoadTransferOrder) -> None: - next_stop = transfer.next_stop() + @staticmethod + def path_for(transfer: TransferOrder) -> List[ControlPoint]: + supply_route = SupplyRoute.for_control_point(transfer.position) + return supply_route.shortest_path_between( + transfer.position, transfer.destination + ) + + def next_stop_for(self, transfer: TransferOrder) -> ControlPoint: + return self.path_for(transfer)[0] + + def add(self, transfer: TransferOrder) -> None: + next_stop = self.next_stop_for(transfer) self.find_or_create_convoy(transfer.position, next_stop).add_units(transfer) - def remove(self, transfer: RoadTransferOrder) -> None: - next_stop = transfer.next_stop() - convoy = self.find_convoy(transfer.position, next_stop) - if convoy is None: - logging.error( - f"Attempting to remove {transfer} from convoy but it is in no convoy." - ) - return + def remove(self, convoy: Convoy, transfer: TransferOrder) -> None: convoy.remove_units(transfer) if not convoy.transfers: self.disband_convoy(convoy) @@ -298,43 +326,63 @@ class PendingTransfers: def index_of_transfer(self, transfer: TransferOrder) -> int: return self.pending_transfers.index(transfer) - # TODO: Move airlift arrangements here? - @singledispatchmethod - def arrange_transport(self, transfer) -> None: - pass - - @arrange_transport.register - def _arrange_transport_road(self, transfer: RoadTransferOrder) -> None: - self.convoys.add(transfer) + def arrange_transport(self, transfer: TransferOrder) -> None: + supply_route = SupplyRoute.for_control_point(transfer.position) + if transfer.destination in supply_route: + self.convoys.add(transfer) + else: + AirliftPlanner(self.game, transfer).create_package_for_airlift() def new_transfer(self, transfer: TransferOrder) -> None: transfer.origin.base.commit_losses(transfer.units) self.pending_transfers.append(transfer) self.arrange_transport(transfer) + def split_transfer(self, transfer: TransferOrder, size: int) -> TransferOrder: + """Creates a smaller transfer that is a subset of the original.""" + if transfer.size <= size: + raise ValueError + + units = {} + for unit_type, remaining in transfer.units.items(): + take = min(remaining, size) + size -= take + transfer.units[unit_type] -= take + units[unit_type] = take + if not size: + break + new_transfer = TransferOrder(transfer.origin, transfer.destination, units) + self.pending_transfers.append(new_transfer) + return new_transfer + @singledispatchmethod - def cancel_transport(self, transfer) -> None: + def cancel_transport(self, transfer: TransferOrder, transport) -> None: pass @cancel_transport.register - def _cancel_transport_air(self, transfer: AirliftOrder) -> None: - flight = transfer.flight + def _cancel_transport_air( + self, _transfer: TransferOrder, transport: Airlift + ) -> None: + flight = transport.flight flight.package.remove_flight(flight) self.game.aircraft_inventory.return_from_flight(flight) - @cancel_transport.register - def _cancel_transport_road(self, transfer: RoadTransferOrder) -> None: - self.convoys.remove(transfer) + def _cancel_transport_convoy( + self, transfer: TransferOrder, transport: Convoy + ) -> None: + self.convoys.remove(transport, transfer) def cancel_transfer(self, transfer: TransferOrder) -> None: - self.cancel_transport(transfer) + if transfer.transport is not None: + self.cancel_transport(transfer, transfer.transport) self.pending_transfers.remove(transfer) transfer.origin.base.commision_units(transfer.units) def perform_transfers(self) -> None: incomplete = [] for transfer in self.pending_transfers: - if not self.perform_transfer(transfer): + transfer.proceed() + if not transfer.completed: incomplete.append(transfer) self.pending_transfers = incomplete self.rebuild_convoys() @@ -343,80 +391,3 @@ class PendingTransfers: self.convoys.disband_all() for transfer in self.pending_transfers: self.arrange_transport(transfer) - - @singledispatchmethod - def perform_transfer(self, transfer) -> bool: - raise NotImplementedError - - @perform_transfer.register - def _perform_transfer_air(self, transfer: AirliftOrder) -> bool: - if transfer.player != transfer.destination.captured: - logging.info( - f"Transfer destination {transfer.destination} was captured. Cancelling " - "transport." - ) - transfer.origin.base.commision_units(transfer.units) - return True - - transfer.destination.base.commision_units(transfer.units) - return True - - @perform_transfer.register - def _perform_transfer_road(self, transfer: RoadTransferOrder) -> bool: - # TODO: Can be improved to use the convoy map. - # The convoy map already has a lot of the data that we're recomputing here. - if transfer.player != transfer.destination.captured: - logging.info( - f"Transfer destination {transfer.destination.name} was captured." - ) - self.handle_route_interrupted(transfer) - return True - - supply_route = SupplyRoute.for_control_point(transfer.destination) - if transfer.position not in supply_route: - logging.info( - f"Route from {transfer.position.name} to {transfer.destination.name} " - "was cut off." - ) - self.handle_route_interrupted(transfer) - return True - - path = transfer.path() - next_hop = path[0] - if next_hop == transfer.destination: - logging.info( - f"Units transferred from {transfer.origin.name} to " - f"{transfer.destination.name}" - ) - transfer.destination.base.commision_units(transfer.units) - return True - - logging.info( - f"Units transferring from {transfer.origin.name} to " - f"{transfer.destination.name} arrived at {next_hop.name}. {len(path) - 1} " - "turns remaining." - ) - transfer.position = next_hop - return False - - @staticmethod - def handle_route_interrupted(transfer: RoadTransferOrder): - # Halt the transfer in place if safe. - if transfer.player == transfer.position.captured: - logging.info(f"Transferring units are halting at {transfer.position.name}.") - transfer.position.base.commision_units(transfer.units) - return - - # If the current position was captured attempt to divert to a neighboring - # friendly CP. - for connected in transfer.position.connected_points: - if connected.captured == transfer.player: - logging.info(f"Transferring units are re-routing to {connected.name}.") - connected.base.commision_units(transfer.units) - return - - # If the units are cutoff they are destroyed. - logging.info( - f"Both {transfer.position.name} and {transfer.destination.name} were " - "captured. Units were surrounded and destroyed during transfer." - ) diff --git a/game/unitdelivery.py b/game/unitdelivery.py index 845eec69..402a25cd 100644 --- a/game/unitdelivery.py +++ b/game/unitdelivery.py @@ -8,11 +8,9 @@ from typing import Dict, Optional, TYPE_CHECKING, Type from dcs.unittype import UnitType, VehicleType from game.theater import ControlPoint, SupplyRoute -from gen.ato import Package from gen.flights.closestairfields import ObjectiveDistanceCache -from gen.flights.flight import Flight from .db import PRICES -from .transfers import AirliftOrder, AirliftPlanner, RoadTransferOrder +from .transfers import TransferOrder if TYPE_CHECKING: from .game import Game @@ -111,44 +109,14 @@ class PendingUnitDeliveries: ground_unit_source.control_point.base.commision_units( units_needing_transfer ) - if ground_unit_source.requires_airlift: - self.create_air_transfer( - game, ground_unit_source.control_point, units_needing_transfer - ) - else: - self.create_road_transfer( - game, ground_unit_source.control_point, units_needing_transfer - ) + self.create_transfer( + game, ground_unit_source.control_point, units_needing_transfer + ) - def create_air_transfer( + def create_transfer( self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int] ) -> None: - planner = AirliftPlanner(game, source, self.destination, units) - leftovers = planner.create_package_for_airlift() - if leftovers: - game.message( - f"No airlift capacity remaining for {self.destination}. " - "Remaining unit orders were refunded." - ) - self.refund(game, leftovers) - source.base.commit_losses(leftovers) - - def find_transport_for( - self, - origin: ControlPoint, - destination: ControlPoint, - units: Dict[Type[VehicleType], int], - ) -> Optional[Flight]: - pass - - def create_road_transfer( - self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int] - ) -> None: - game.transfers.new_transfer( - RoadTransferOrder( - source, self.destination, self.destination.captured, units - ) - ) + game.transfers.new_transfer(TransferOrder(source, self.destination, units)) def find_ground_unit_source(self, game: Game) -> Optional[GroundUnitSource]: # This is running *after* the turn counter has been incremented, so this is the diff --git a/game/unitmap.py b/game/unitmap.py index 322320b7..c6d8d0de 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -9,7 +9,7 @@ from dcs.unittype import VehicleType from game import db from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import BuildingGroundObject -from game.transfers import AirliftOrder, Convoy +from game.transfers import Convoy, TransferOrder from gen.flights.flight import Flight @@ -35,7 +35,7 @@ class ConvoyUnit: @dataclass(frozen=True) class AirliftUnit: unit_type: Type[VehicleType] - transfer: AirliftOrder + transfer: TransferOrder @dataclass(frozen=True) @@ -149,17 +149,14 @@ class UnitMap: def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: return self.convoys.get(name, None) - def add_airlift_units(self, group: FlyingGroup, airlift: AirliftOrder) -> None: - for transport, cargo_type in zip(group.units, airlift.iter_units()): + def add_airlift_units(self, group: FlyingGroup, transfer: TransferOrder) -> None: + for transport, cargo_type in zip(group.units, transfer.iter_units()): # The actual name is a String (the pydcs translatable string), which # doesn't define __eq__. name = str(transport.name) if name in self.airlifts: raise RuntimeError(f"Duplicate airlift unit: {name}") - unit_type = db.unit_type_from_name(transport.type) - if unit_type is None: - raise RuntimeError(f"Unknown unit type: {transport.type}") - self.airlifts[name] = AirliftUnit(cargo_type, airlift) + self.airlifts[name] = AirliftUnit(cargo_type, transfer) def airlift_unit(self, name: str) -> Optional[AirliftUnit]: return self.airlifts.get(name, None) diff --git a/gen/aircraft.py b/gen/aircraft.py index 174b2c8f..104cb6d9 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging import random -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import timedelta from functools import cached_property from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union @@ -42,6 +42,7 @@ from dcs.planes import ( from dcs.point import MovingPoint, PointAction from dcs.task import ( AWACS, + AWACSTaskAction, AntishipStrike, AttackGroup, Bombing, @@ -66,13 +67,12 @@ from dcs.task import ( StartCommand, Targets, Task, + Transport, WeaponType, - AWACSTaskAction, - SetFrequencyCommand, ) from dcs.terrain.terrain import Airport, NoParkingSlotError from dcs.triggers import Event, TriggerOnce, TriggerRule -from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup, VehicleGroup +from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup from dcs.unittype import FlyingType, UnitType from game import db @@ -88,7 +88,7 @@ from game.theater.controlpoint import ( OffMapSpawn, ) from game.theater.theatergroundobject import TheaterGroundObject -from game.transfers import Convoy, RoadTransferOrder +from game.transfers import Convoy from game.unitmap import UnitMap from game.utils import Distance, meters, nautical_miles from gen.ato import AirTaskingOrder, Package @@ -101,17 +101,17 @@ from gen.flights.flight import ( ) from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio from gen.runways import RunwayData +from .airsupportgen import AirSupport, AwacsInfo +from .callsigns import callsign_for_support_unit from .flights.flightplan import ( + AwacsFlightPlan, CasFlightPlan, LoiterFlightPlan, PatrollingFlightPlan, SweepFlightPlan, - AwacsFlightPlan, ) from .flights.traveltime import GroundSpeed, TotEstimator from .naming import namegen -from .airsupportgen import AirSupport, AwacsInfo -from .callsigns import callsign_for_support_unit if TYPE_CHECKING: from game import Game @@ -1421,6 +1421,25 @@ class AircraftConflictGenerator: group, roe=OptROE.Values.OpenFire, restrict_jettison=True ) + def configure_transport( + self, + group: FlyingGroup, + package: Package, + flight: Flight, + dynamic_runways: Dict[str, RunwayData], + ) -> None: + # Escort groups are actually given the CAP task so they can perform the + # Search Then Engage task, which we have to use instead of the Escort + # task for the reasons explained in JoinPointBuilder. + group.task = Transport.name + self._setup_group(group, Transport, package, flight, dynamic_runways) + self.configure_behavior( + group, + react_on_threat=OptReactOnThreat.Values.EvadeFire, + roe=OptROE.Values.WeaponHold, + restrict_jettison=True, + ) + def configure_unknown_task(self, group: FlyingGroup, flight: Flight) -> None: logging.error(f"Unhandled flight type: {flight.flight_type}") self.configure_behavior(group) @@ -1459,6 +1478,8 @@ class AircraftConflictGenerator: self.configure_runway_attack(group, package, flight, dynamic_runways) elif flight_type == FlightType.OCA_AIRCRAFT: self.configure_oca_strike(group, package, flight, dynamic_runways) + elif flight_type == FlightType.TRANSPORT: + self.configure_transport(group, package, flight, dynamic_runways) else: self.configure_unknown_task(group, flight) diff --git a/gen/convoys.py b/gen/convoys.py index 21d48c8f..aa0e3e3d 100644 --- a/gen/convoys.py +++ b/gen/convoys.py @@ -10,7 +10,7 @@ from dcs.unit import Vehicle from dcs.unitgroup import VehicleGroup from dcs.unittype import VehicleType -from game.transfers import Convoy, RoadTransferOrder +from game.transfers import Convoy from game.unitmap import UnitMap from game.utils import kph diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 00b7523b..bab73cb4 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -14,7 +14,7 @@ from game.theater.controlpoint import ControlPoint, MissionTarget from game.utils import Distance, meters if TYPE_CHECKING: - from game.transfers import AirliftOrder + from game.transfers import Airlift, TransferOrder from gen.ato import Package from gen.flights.flightplan import FlightPlan @@ -167,7 +167,7 @@ class Flight: arrival: ControlPoint, divert: Optional[ControlPoint], custom_name: Optional[str] = None, - cargo: Optional[AirliftOrder] = None, + cargo: Optional[TransferOrder] = None, ) -> None: self.package = package self.country = country diff --git a/qt_ui/models.py b/qt_ui/models.py index 7317f8f9..b07e7020 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -163,12 +163,10 @@ class PackageModel(QAbstractListModel): """Removes the given flight from the package.""" index = self.package.flights.index(flight) self.beginRemoveRows(QModelIndex(), index, index) - if flight.cargo is None: - self.game_model.game.aircraft_inventory.return_from_flight(flight) - self.package.remove_flight(flight) - else: - # Deleted transfers will clean up after themselves. - self.game_model.transfer_model.cancel_transfer(flight.cargo) + if flight.cargo is not None: + flight.cargo.transport = None + self.game_model.game.aircraft_inventory.return_from_flight(flight) + self.package.remove_flight(flight) self.endRemoveRows() self.update_tot() @@ -261,7 +259,7 @@ class AtoModel(QAbstractListModel): for flight in package.flights: self.game.aircraft_inventory.return_from_flight(flight) if flight.cargo is not None: - self.game_model.transfer_model.cancel_transfer(flight.cargo) + flight.cargo.transport = None self.endRemoveRows() # noinspection PyUnresolvedReferences self.client_slots_changed.emit() diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 3d54b651..b01504e1 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -39,12 +39,12 @@ from shapely.geometry import ( import qt_ui.uiconstants as CONST from game import Game from game.navmesh import NavMesh -from game.theater import ControlPoint, Enum, SupplyRoute +from game.theater import ControlPoint, Enum from game.theater.conflicttheater import FrontLine, ReferencePoint from game.theater.theatergroundobject import ( TheaterGroundObject, ) -from game.transfers import Convoy, RoadTransferOrder +from game.transfers import Convoy from game.utils import Distance, meters, nautical_miles from game.weather import TimeOfDay from gen import Conflict, Package @@ -55,12 +55,10 @@ from gen.flights.flight import ( FlightWaypointType, ) from gen.flights.flightplan import ( - BarCapFlightPlan, FlightPlan, FlightPlanBuilder, InvalidObjectiveLocation, PatrollingFlightPlan, - TarCapFlightPlan, ) from gen.flights.traveltime import TotEstimator from qt_ui.displayoptions import DisplayOptions, ThreatZoneOptions @@ -865,33 +863,6 @@ class QLiberationMap(QGraphicsView): if DisplayOptions.lines: self.draw_supply_route_between(cp, connected) - def _transfers_between( - self, a: ControlPoint, b: ControlPoint - ) -> List[RoadTransferOrder]: - # We attempt to short circuit the expensive shortest path computation for the - # cases where there is never a transfer, but caching might be needed. - - if a.captured != b.captured: - # Cannot transfer to enemy CPs. - return [] - - # This is only called for drawing lines between nodes and have rules out routes - # to enemy bases, so a and b are guaranteed to be in the same supply route. - supply_route = SupplyRoute.for_control_point(a) - - transfers = [] - points = {a, b} - for transfer in self.game.transfers: - # No possible route from our network to this transfer. - if transfer.position not in supply_route: - continue - - # Anything left is a transfer within our supply route. - transfer_points = {transfer.position, transfer.next_stop()} - if points == transfer_points: - transfers.append(transfer) - return transfers - def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None: scene = self.scene() diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index 9e2b5af4..591d8a07 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -28,7 +28,7 @@ from dcs.unittype import UnitType from game import Game, db from game.theater import ControlPoint, SupplyRoute -from game.transfers import AirliftPlanner, RoadTransferOrder +from game.transfers import TransferOrder from qt_ui.models import GameModel from qt_ui.widgets.QLabeledWidget import QLabeledWidget @@ -89,50 +89,12 @@ class UnitTransferList(QFrame): main_layout.addWidget(scroll) -@dataclass(frozen=True) -class AirliftCapacity: - helicopter: int - cargo_plane: int - - @property - def total(self) -> int: - return self.helicopter + self.cargo_plane - - @classmethod - def to_control_point(cls, game: Game) -> AirliftCapacity: - helo_capacity = 0 - plane_capacity = 0 - for cp in game.theater.player_points(): - inventory = game.aircraft_inventory.for_control_point(cp) - for unit_type, count in inventory.all_aircraft: - if unit_type.helicopter: - helo_capacity += count - return AirliftCapacity(helicopter=helo_capacity, cargo_plane=plane_capacity) - - class TransferOptionsPanel(QVBoxLayout): - def __init__( - self, - game: Game, - origin: ControlPoint, - airlift_capacity: AirliftCapacity, - airlift_required: bool, - ) -> None: + def __init__(self, game: Game, origin: ControlPoint) -> None: super().__init__() self.source_combo_box = TransferDestinationComboBox(game, origin) self.addLayout(QLabeledWidget("Destination:", self.source_combo_box)) - self.airlift = QCheckBox() - self.airlift.setChecked(airlift_required) - self.airlift.setDisabled(airlift_required) - self.addLayout(QLabeledWidget("Airlift:", self.airlift)) - self.addWidget( - QLabel( - f"{airlift_capacity.total} airlift capacity " - f"({airlift_capacity.cargo_plane} from cargo planes, " - f"{airlift_capacity.helicopter} from helicopters)" - ) - ) @property def changed(self): @@ -194,17 +156,9 @@ class TransferControls(QGroupBox): class ScrollingUnitTransferGrid(QFrame): - def __init__( - self, - cp: ControlPoint, - airlift: bool, - airlift_capacity: AirliftCapacity, - game_model: GameModel, - ) -> None: + def __init__(self, cp: ControlPoint, game_model: GameModel) -> None: super().__init__() self.cp = cp - self.airlift = airlift - self.remaining_capacity = airlift_capacity.total self.game_model = game_model self.transfers: Dict[Type[UnitType, int]] = defaultdict(int) @@ -274,11 +228,6 @@ class ScrollingUnitTransferGrid(QFrame): if not origin_inventory: return - if self.airlift: - if not self.remaining_capacity: - return - self.remaining_capacity -= 1 - self.transfers[unit_type] += 1 origin_inventory -= 1 controls.set_quantity(self.transfers[unit_type]) @@ -290,9 +239,6 @@ class ScrollingUnitTransferGrid(QFrame): if not controls.quantity: return - if self.airlift: - self.remaining_capacity += 1 - self.transfers[unit_type] -= 1 origin_inventory += 1 controls.set_quantity(self.transfers[unit_type]) @@ -329,21 +275,10 @@ class NewUnitTransferDialog(QDialog): layout = QVBoxLayout() self.setLayout(layout) - self.airlift_capacity = AirliftCapacity.to_control_point(game_model.game) - airlift_required = len(SupplyRoute.for_control_point(origin)) == 1 - self.dest_panel = TransferOptionsPanel( - game_model.game, origin, self.airlift_capacity, airlift_required - ) - self.dest_panel.changed.connect(self.rebuild_transfers) + self.dest_panel = TransferOptionsPanel(game_model.game, origin) layout.addLayout(self.dest_panel) - self.transfer_panel = ScrollingUnitTransferGrid( - origin, - airlift=airlift_required, - airlift_capacity=self.airlift_capacity, - game_model=game_model, - ) - self.dest_panel.airlift.toggled.connect(self.rebuild_transfers) + self.transfer_panel = ScrollingUnitTransferGrid(origin, game_model) layout.addWidget(self.transfer_panel) self.submit_button = QPushButton("Create Transfer Order", parent=self) @@ -351,31 +286,8 @@ class NewUnitTransferDialog(QDialog): self.submit_button.setProperty("style", "start-button") layout.addWidget(self.submit_button) - def rebuild_transfers(self) -> None: - # Rebuild the transfer panel to reset everything. It's easier to recreate the - # panel itself than to clear the grid layout in the panel. - self.layout().removeWidget(self.transfer_panel) - self.layout().removeWidget(self.submit_button) - self.transfer_panel = ScrollingUnitTransferGrid( - self.origin, - airlift=self.dest_panel.airlift.isChecked(), - airlift_capacity=self.airlift_capacity, - game_model=self.game_model, - ) - self.layout().addWidget(self.transfer_panel) - self.layout().addWidget(self.submit_button) - def on_submit(self) -> None: destination = self.dest_panel.current - supply_route = SupplyRoute.for_control_point(self.origin) - if not self.dest_panel.airlift.isChecked() and destination not in supply_route: - QMessageBox.critical( - self, - "Could not create transfer", - f"Transfers from {self.origin} to {destination} require airlift.", - QMessageBox.Ok, - ) - return transfers = {} for unit_type, count in self.transfer_panel.transfers.items(): if not count: @@ -387,20 +299,10 @@ class NewUnitTransferDialog(QDialog): ) transfers[unit_type] = count - if self.dest_panel.airlift.isChecked(): - planner = AirliftPlanner( - self.game_model.game, - self.origin, - destination, - transfers, - ) - planner.create_package_for_airlift() - else: - transfer = RoadTransferOrder( - player=True, - origin=self.origin, - destination=destination, - units=transfers, - ) - self.game_model.transfer_model.new_transfer(transfer) + transfer = TransferOrder( + origin=self.origin, + destination=destination, + units=transfers, + ) + self.game_model.transfer_model.new_transfer(transfer) self.close() From d3fdbdbca54ac1972e0a7f66149eb6f65904fee5 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 20:23:23 -0700 Subject: [PATCH 059/438] Fix convoys not spawning where they should. --- game/transfers.py | 9 +++++++++ gen/convoys.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/game/transfers.py b/game/transfers.py index 4236a7aa..b8832e4b 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -6,6 +6,7 @@ from dataclasses import dataclass, field from functools import singledispatchmethod from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type +from dcs.mapping import Point from dcs.unittype import FlyingType, VehicleType from gen.ato import Package @@ -246,6 +247,14 @@ class Convoy(MissionTarget, Transport): def description(self) -> str: return f"In a convoy to {self.destination}" + @property + def route_start(self) -> Point: + return self.origin.convoy_spawns[self.destination] + + @property + def route_end(self) -> Point: + return self.destination.convoy_spawns[self.origin] + class ConvoyMap: def __init__(self) -> None: diff --git a/gen/convoys.py b/gen/convoys.py index aa0e3e3d..9c904009 100644 --- a/gen/convoys.py +++ b/gen/convoys.py @@ -33,12 +33,12 @@ class ConvoyGenerator: def generate_convoy(self, convoy: Convoy) -> VehicleGroup: group = self._create_mixed_unit_group( convoy.name, - convoy.origin.position, + convoy.route_start, convoy.units, convoy.player_owned, ) group.add_waypoint( - convoy.destination.position, + convoy.route_end, speed=kph(40).kph, move_formation=PointAction.OnRoad, ) From 20d8cc2b47e55ed987ffd711e845e981f3befb6b Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 20:31:52 -0700 Subject: [PATCH 060/438] Plan transports at the beginning of the turn. https://github.com/Khopa/dcs_liberation/issues/823 --- game/game.py | 2 ++ game/transfers.py | 6 +++--- gen/flights/ai_flight_planner.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/game/game.py b/game/game.py index e264842f..ec687561 100644 --- a/game/game.py +++ b/game/game.py @@ -337,6 +337,8 @@ class Game: self.compute_threat_zones() self.ground_planners = {} + self.transfers.plan_transports() + blue_planner = CoalitionMissionPlanner(self, is_player=True) blue_planner.plan_missions() diff --git a/game/transfers.py b/game/transfers.py index b8832e4b..545f004f 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -394,9 +394,9 @@ class PendingTransfers: if not transfer.completed: incomplete.append(transfer) self.pending_transfers = incomplete - self.rebuild_convoys() - def rebuild_convoys(self) -> None: + def plan_transports(self) -> None: self.convoys.disband_all() for transfer in self.pending_transfers: - self.arrange_transport(transfer) + if transfer.transport is None: + self.arrange_transport(transfer) diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index a07bb6dd..87d00a60 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -39,7 +39,7 @@ from game.theater.theatergroundobject import ( NavalGroundObject, VehicleGroupGroundObject, ) -from game.transfers import Convoy +from game.transfers import Convoy, TransferOrder from game.utils import Distance, nautical_miles from gen import Conflict from gen.ato import Package From 5320d20f714e92a1da958b936f667cec7fd3b7fc Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 21:21:05 -0700 Subject: [PATCH 061/438] Add C-130 to the CAS capable list, clean up. --- gen/flights/ai_flight_planner_db.py | 1 + qt_ui/widgets/combos/QAircraftTypeSelector.py | 10 ++-------- qt_ui/windows/QUnitInfoWindow.py | 10 ++-------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/gen/flights/ai_flight_planner_db.py b/gen/flights/ai_flight_planner_db.py index d717bc86..a0dd3c73 100644 --- a/gen/flights/ai_flight_planner_db.py +++ b/gen/flights/ai_flight_planner_db.py @@ -157,6 +157,7 @@ CAS_CAPABLE = [ A_10C_2, B_1B, A_10C, + Hercules, F_14B, F_14A_135_GR, Su_25TM, diff --git a/qt_ui/widgets/combos/QAircraftTypeSelector.py b/qt_ui/widgets/combos/QAircraftTypeSelector.py index 5afa3761..e5919b3c 100644 --- a/qt_ui/widgets/combos/QAircraftTypeSelector.py +++ b/qt_ui/widgets/combos/QAircraftTypeSelector.py @@ -49,10 +49,7 @@ class QAircraftTypeSelector(QComboBox): FlightType.BAI, FlightType.OCA_AIRCRAFT, ]: - if ( - aircraft in gen.flights.ai_flight_planner_db.CAS_CAPABLE - or aircraft in gen.flights.ai_flight_planner_db.TRANSPORT_CAPABLE - ): + if aircraft in gen.flights.ai_flight_planner_db.CAS_CAPABLE: self.addItem( f"{db.unit_get_expanded_info(self.country, aircraft, 'name')}", userData=aircraft, @@ -70,10 +67,7 @@ class QAircraftTypeSelector(QComboBox): userData=aircraft, ) elif mission_type in [FlightType.STRIKE]: - if ( - aircraft in gen.flights.ai_flight_planner_db.STRIKE_CAPABLE - or aircraft in gen.flights.ai_flight_planner_db.TRANSPORT_CAPABLE - ): + if aircraft in gen.flights.ai_flight_planner_db.STRIKE_CAPABLE: self.addItem( f"{db.unit_get_expanded_info(self.country, aircraft, 'name')}", userData=aircraft, diff --git a/qt_ui/windows/QUnitInfoWindow.py b/qt_ui/windows/QUnitInfoWindow.py index cd87a07f..dca1aaf1 100644 --- a/qt_ui/windows/QUnitInfoWindow.py +++ b/qt_ui/windows/QUnitInfoWindow.py @@ -128,10 +128,7 @@ class QUnitInfoWindow(QDialog): aircraft_tasks + f"{FlightType.BARCAP}, {FlightType.ESCORT}, {FlightType.INTERCEPTION}, {FlightType.SWEEP}, {FlightType.TARCAP}, " ) - if ( - self.unit_type in gen.flights.ai_flight_planner_db.CAS_CAPABLE - or self.unit_type in gen.flights.ai_flight_planner_db.TRANSPORT_CAPABLE - ): + if self.unit_type in gen.flights.ai_flight_planner_db.CAS_CAPABLE: aircraft_tasks = ( aircraft_tasks + f"{FlightType.CAS}, {FlightType.BAI}, {FlightType.OCA_AIRCRAFT}, " @@ -144,9 +141,6 @@ class QUnitInfoWindow(QDialog): aircraft_tasks = aircraft_tasks + f"{FlightType.ANTISHIP}, " if self.unit_type in gen.flights.ai_flight_planner_db.RUNWAY_ATTACK_CAPABLE: aircraft_tasks = aircraft_tasks + f"{FlightType.OCA_RUNWAY}, " - if ( - self.unit_type in gen.flights.ai_flight_planner_db.STRIKE_CAPABLE - or self.unit_type in gen.flights.ai_flight_planner_db.TRANSPORT_CAPABLE - ): + if self.unit_type in gen.flights.ai_flight_planner_db.STRIKE_CAPABLE: aircraft_tasks = aircraft_tasks + f"{FlightType.STRIKE}, " return aircraft_tasks[:-2] From dac2271084d278ebde0585e52ddd6063928a33c2 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 21:37:01 -0700 Subject: [PATCH 062/438] Add transport aircraft to US factions. --- game/db.py | 89 ++++++++++++------- game/theater/base.py | 3 +- gen/flights/ai_flight_planner_db.py | 24 ++++- .../airfield/QAircraftRecruitmentMenu.py | 4 +- resources/factions/usa_1955.json | 5 +- resources/factions/usa_1960.json | 3 +- resources/factions/usa_1965.json | 6 +- resources/factions/usa_1975.json | 11 ++- resources/factions/usa_1990.json | 27 +++--- resources/factions/usa_2005.json | 30 ++++--- resources/factions/usa_2005_c130.json | 30 ++++--- 11 files changed, 152 insertions(+), 80 deletions(-) diff --git a/game/db.py b/game/db.py index 9cc81055..ffeecd8c 100644 --- a/game/db.py +++ b/game/db.py @@ -9,9 +9,12 @@ from dcs.helicopters import ( AH_1W, AH_64A, AH_64D, + CH_47D, + CH_53E, HelicopterType, Ka_50, Mi_24V, + Mi_26, Mi_28N, Mi_8MT, OH_58D, @@ -43,6 +46,7 @@ from dcs.planes import ( Bf_109K_4, C_101CC, C_130, + C_17A, E_3A, E_2C, FA_18C_hornet, @@ -441,6 +445,10 @@ PRICES = { AH_64D: 30, OH_58D: 6, SH_60B: 6, + CH_47D: 4, + CH_53E: 4, + UH_60A: 4, + Mi_26: 4, # Bombers B_52H: 35, B_1B: 50, @@ -464,6 +472,7 @@ PRICES = { E_2C: 50, C_130: 25, Hercules: 25, + C_17A: 20, # WW2 P_51D_30_NA: 18, P_51D: 16, @@ -729,42 +738,42 @@ Following tasks are present: """ UNIT_BY_TASK = { CAP: [ + A_4E_C, + Bf_109K_4, + C_101CC, + FA_18C_hornet, + FW_190A8, + FW_190D9, + F_14A_135_GR, + F_14B, + F_15C, + F_16A, + F_16C_50, + F_22A, + F_4E, F_5E_3, - Su_27, - Su_33, - Su_57, + I_16, + JF_17, + J_11A, + M_2000C, MiG_19P, MiG_21Bis, MiG_23MLD, MiG_25PD, MiG_29A, + MiG_29G, MiG_29S, MiG_31, - FA_18C_hornet, - F_15C, - F_22A, - F_14A_135_GR, - F_14B, - F_16A, - F_16C_50, - M_2000C, Mirage_2000_5, - P_51D_30_NA, P_51D, - MiG_29G, - Su_30, - J_11A, - JF_17, - F_4E, - C_101CC, - I_16, - Bf_109K_4, - FW_190D9, - FW_190A8, - SpitfireLFMkIXCW, - SpitfireLFMkIX, - A_4E_C, + P_51D_30_NA, SA342Mistral, + SpitfireLFMkIX, + SpitfireLFMkIXCW, + Su_27, + Su_30, + Su_33, + Su_57, ], CAS: [ AH_1W, @@ -782,6 +791,7 @@ UNIT_BY_TASK = { F_117A, F_15E, F_86F_Sabre, + Hercules, Ju_88A4, Ka_50, L_39ZA, @@ -797,10 +807,11 @@ UNIT_BY_TASK = { P_47D_30bl1, P_47D_40, RQ_1A_Predator, - S_3B, SA342L, SA342M, SA342Minigun, + SH_60B, + S_3B, Su_17M4, Su_24M, Su_24MR, @@ -813,19 +824,33 @@ UNIT_BY_TASK = { Tu_22M3, Tu_95MS, UH_1H, - SH_60B, WingLoong_I, - Hercules, ], - Transport: [IL_76MD, An_26B, An_30M, Yak_40, C_130], + Transport: [ + An_26B, + An_30M, + CH_47D, + CH_53E, + C_130, + C_17A, + IL_76MD, + Mi_26, + UH_60A, + Yak_40, + ], Refueling: [ IL_78M, - KC_135, KC130, - S_3B_Tanker, KC135MPRS, + KC_135, + S_3B_Tanker, + ], + AWACS: [ + A_50, + E_2C, + E_3A, + KJ_2000, ], - AWACS: [E_3A, E_2C, A_50, KJ_2000], PinpointStrike: [ Armor.APC_MTLB, Armor.APC_MTLB, diff --git a/game/theater/base.py b/game/theater/base.py index 5db7bba7..fa329531 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -4,7 +4,7 @@ import math import typing from typing import Dict, Type -from dcs.task import AWACS, CAP, CAS, Embarking, PinpointStrike, Task +from dcs.task import AWACS, CAP, CAS, Embarking, PinpointStrike, Task, Transport from dcs.unittype import FlyingType, UnitType, VehicleType from dcs.vehicles import AirDefence, Armor @@ -152,6 +152,7 @@ class Base: or for_task == CAS or for_task == CAP or for_task == Embarking + or for_task == Transport ): target_dict = self.aircraft elif for_task == PinpointStrike: diff --git a/gen/flights/ai_flight_planner_db.py b/gen/flights/ai_flight_planner_db.py index a0dd3c73..c5d7c977 100644 --- a/gen/flights/ai_flight_planner_db.py +++ b/gen/flights/ai_flight_planner_db.py @@ -5,8 +5,11 @@ from dcs.helicopters import ( AH_1W, AH_64A, AH_64D, + CH_47D, + CH_53E, Ka_50, Mi_24V, + Mi_26, Mi_28N, Mi_8MT, OH_58D, @@ -14,6 +17,7 @@ from dcs.helicopters import ( SA342M, SH_60B, UH_1H, + UH_60A, ) from dcs.planes import ( AJS37, @@ -23,11 +27,14 @@ from dcs.planes import ( A_10C_2, A_20G, A_50, + An_26B, B_17G, B_1B, B_52H, Bf_109K_4, C_101CC, + C_130, + C_17A, E_2C, E_3A, FA_18C_hornet, @@ -43,6 +50,7 @@ from dcs.planes import ( F_4E, F_5E_3, F_86F_Sabre, + IL_76MD, I_16, JF_17, J_11A, @@ -88,6 +96,7 @@ from dcs.planes import ( Tu_95MS, WingLoong_I, I_16, + Yak_40, ) from dcs.unittype import FlyingType @@ -355,9 +364,20 @@ RUNWAY_ATTACK_CAPABLE = [ # For any aircraft that isn't necessarily directly involved in strike # missions in a direct combat sense, but can transport objects and infantry. TRANSPORT_CAPABLE = [ + C_17A, Hercules, - Mi_8MT, + C_130, + IL_76MD, + An_26B, + Yak_40, + CH_53E, + CH_47D, + SH_60B, + UH_60A, UH_1H, + Mi_8MT, + Mi_8MT, + Mi_26, ] DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I] @@ -394,6 +414,8 @@ def aircraft_for_task(task: FlightType) -> List[Type[FlyingType]]: return CAP_CAPABLE elif task == FlightType.AEWC: return AEWC_CAPABLE + elif task == FlightType.TRANSPORT: + return TRANSPORT_CAPABLE else: logging.error(f"Unplannable flight type: {task}") return [] diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 97058d83..d6c3fa6b 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -13,7 +13,7 @@ from PySide2.QtWidgets import ( QWidget, ) from dcs.helicopters import helicopter_map -from dcs.task import CAP, CAS, AWACS +from dcs.task import CAP, CAS, AWACS, Transport from dcs.unittype import FlyingType, UnitType from game import db @@ -45,7 +45,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): def init_ui(self): main_layout = QVBoxLayout() - tasks = [CAP, CAS, AWACS] + tasks = [CAP, CAS, AWACS, Transport] scroll_content = QWidget() task_box_layout = QGridLayout() diff --git a/resources/factions/usa_1955.json b/resources/factions/usa_1955.json index df202d6a..0dd8262b 100644 --- a/resources/factions/usa_1955.json +++ b/resources/factions/usa_1955.json @@ -4,10 +4,11 @@ "authors": "Khopa", "description": "

US army in the 50s, circa Korean War.

", "aircrafts": [ + "B_52H", + "C_130", "F_86F_Sabre", "P_51D", - "P_51D_30_NA", - "B_52H" + "P_51D_30_NA" ], "frontline_units": [ "MT_M4_Sherman", diff --git a/resources/factions/usa_1960.json b/resources/factions/usa_1960.json index 0548300f..9ae7cc8f 100644 --- a/resources/factions/usa_1960.json +++ b/resources/factions/usa_1960.json @@ -4,10 +4,11 @@ "authors": "Khopa", "description": "

US army in the 60s, pre-Vietnam war.

", "aircrafts": [ + "B_52H", + "C_130", "F_86F_Sabre", "P_51D", "P_51D_30_NA", - "B_52H", "UH_1H" ], "frontline_units": [ diff --git a/resources/factions/usa_1965.json b/resources/factions/usa_1965.json index e252c371..6af26fe7 100644 --- a/resources/factions/usa_1965.json +++ b/resources/factions/usa_1965.json @@ -4,9 +4,11 @@ "authors": "Khopa", "description": "

US army in the late 60s, during Vietnam war.

", "aircrafts": [ - "F_5E_3", - "F_4E", "B_52H", + "CH_47D", + "C_130", + "F_4E", + "F_5E_3", "UH_1H" ], "awacs": [ diff --git a/resources/factions/usa_1975.json b/resources/factions/usa_1975.json index c1e894bb..10830133 100644 --- a/resources/factions/usa_1975.json +++ b/resources/factions/usa_1975.json @@ -4,11 +4,14 @@ "authors": "Khopa", "description": "

US army in the 70s at the end of the war in Vietnam.

", "aircrafts": [ - "F_5E_3", - "F_4E", - "F_14A_135_GR", - "S_3B", "B_52H", + "CH_47D", + "CH_53E", + "C_130", + "F_14A_135_GR", + "F_4E", + "F_5E_3", + "S_3B", "UH_1H" ], "awacs": [ diff --git a/resources/factions/usa_1990.json b/resources/factions/usa_1990.json index b9c203ea..18d18849 100644 --- a/resources/factions/usa_1990.json +++ b/resources/factions/usa_1990.json @@ -4,21 +4,26 @@ "authors": "Khopa", "description": "

US army in the 90s, Gulf War/Desert Storm.

", "aircrafts": [ - "F_15C", - "F_15E", + "AH_64A", + "AV8BNA", + "A_10A", + "B_1B", + "B_52H", + "CH_47D", + "CH_53E", + "C_130", + "FA_18C_hornet", + "F_117A", "F_14A_135_GR", "F_14B", - "FA_18C_hornet", + "F_15C", + "F_15E", "F_16C_50", - "A_10A", - "AV8BNA", - "UH_1H", - "S_3B", "SH_60B", - "AH_64A", - "B_52H", - "B_1B", - "F_117A" + "SH_60B", + "S_3B", + "UH_1H", + "UH_60A" ], "awacs": [ "E_3A", diff --git a/resources/factions/usa_2005.json b/resources/factions/usa_2005.json index 50255813..e8af8e95 100644 --- a/resources/factions/usa_2005.json +++ b/resources/factions/usa_2005.json @@ -4,21 +4,27 @@ "authors": "Khopa", "description": "

USA in the 2000s.

", "aircrafts": [ - "F_15C", - "F_15E", - "F_14B", - "F_117A", - "FA_18C_hornet", - "F_16C_50", + "AH_64D", + "AV8BNA", "A_10C", "A_10C_2", - "AV8BNA", - "UH_1H", - "S_3B", - "SH_60B", - "AH_64D", + "B_1B", "B_52H", - "B_1B" + "CH_47D", + "CH_53E", + "C_130", + "C_17A", + "FA_18C_hornet", + "F_117A", + "F_14B", + "F_15C", + "F_15E", + "F_16C_50", + "SH_60B", + "SH_60B", + "S_3B", + "UH_1H", + "UH_60A" ], "awacs": [ "E_3A", diff --git a/resources/factions/usa_2005_c130.json b/resources/factions/usa_2005_c130.json index 9546bd55..d2ceeaf0 100644 --- a/resources/factions/usa_2005_c130.json +++ b/resources/factions/usa_2005_c130.json @@ -4,22 +4,28 @@ "authors": "Khopa", "description": "

USA in the 2000s.

", "aircrafts": [ - "F_15C", - "F_15E", - "F_14B", - "FA_18C_hornet", - "F_16C_50", + "AH_64D", + "AV8BNA", "A_10C", "A_10C_2", - "AV8BNA", - "UH_1H", - "S_3B", - "SH_60B", - "AH_64D", - "B_52H", "B_1B", + "B_52H", + "CH_47D", + "CH_53E", + "C_130", + "C_17A", + "FA_18C_hornet", "F_117A", - "Hercules" + "F_14B", + "F_15C", + "F_15E", + "F_16C_50", + "Hercules", + "SH_60B", + "SH_60B", + "S_3B", + "UH_1H", + "UH_60A" ], "awacs": [ "E_3A", From c8b4fd1690284c541892f17803b90fb8f69e782e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 22:05:49 -0700 Subject: [PATCH 063/438] Use any (and only) transport aircraft for airlift. https://github.com/Khopa/dcs_liberation/issues/825 --- game/transfers.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/game/transfers.py b/game/transfers.py index 545f004f..a0387342 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -10,6 +10,7 @@ from dcs.mapping import Point from dcs.unittype import FlyingType, VehicleType from gen.ato import Package +from gen.flights.ai_flight_planner_db import TRANSPORT_CAPABLE from gen.flights.flightplan import FlightPlanBuilder from game.theater import ControlPoint, MissionTarget from game.theater.supplyroutes import SupplyRoute @@ -147,15 +148,23 @@ class AirliftPlanner: self.for_player = transfer.destination.captured self.package = Package(target=transfer.destination, auto_asap=True) + def is_airlift_capable(self, unit_type: Type[FlyingType]) -> bool: + return ( + unit_type in TRANSPORT_CAPABLE + and self.transfer.origin.can_operate(unit_type) + and self.transfer.destination.can_operate(unit_type) + ) + def create_package_for_airlift(self) -> None: for cp in self.game.theater.player_points(): inventory = self.game.aircraft_inventory.for_control_point(cp) for unit_type, available in inventory.all_aircraft: - if unit_type.helicopter: + if self.is_airlift_capable(unit_type): while available and self.transfer.transport is None: flight_size = self.create_airlift_flight(unit_type, inventory) available -= flight_size - self.game.ato_for(self.for_player).add_package(self.package) + if self.package.flights: + self.game.ato_for(self.for_player).add_package(self.package) def create_airlift_flight( self, unit_type: Type[FlyingType], inventory: ControlPointAircraftInventory From 909aad22a697c1e827dcd6e6d73de29ae86a0b5a Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 21 Apr 2021 21:34:22 -0700 Subject: [PATCH 064/438] Make landing stops for cargo missions. Until pydcs supports the timeReFuAr property this will have a wait time of zero minutes, but it does seem to work. Updating to https://github.com/pydcs/dcs/pull/132 will make the wait time work automatically. https://github.com/Khopa/dcs_liberation/issues/825 --- gen/aircraft.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gen/aircraft.py b/gen/aircraft.py index 104cb6d9..f25c82fa 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1647,6 +1647,7 @@ class PydcsWaypointBuilder: mission: Mission, ) -> PydcsWaypointBuilder: builders = { + FlightWaypointType.DROP_OFF: CargoStopBuilder, FlightWaypointType.INGRESS_BAI: BaiIngressBuilder, FlightWaypointType.INGRESS_CAS: CasIngressBuilder, FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder, @@ -1660,6 +1661,7 @@ class PydcsWaypointBuilder: FlightWaypointType.LOITER: HoldPointBuilder, FlightWaypointType.PATROL: RaceTrackEndBuilder, FlightWaypointType.PATROL_TRACK: RaceTrackBuilder, + FlightWaypointType.PICKUP: CargoStopBuilder, } builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder) return builder(waypoint, group, package, flight, mission) @@ -2002,6 +2004,15 @@ class LandingPointBuilder(PydcsWaypointBuilder): return waypoint +class CargoStopBuilder(PydcsWaypointBuilder): + def build(self) -> MovingPoint: + waypoint = super().build() + waypoint.type = "LandingReFuAr" + waypoint.action = PointAction.LandingReFuAr + waypoint.landing_refuel_rearm_time = 2 # Minutes. + return waypoint + + class RaceTrackBuilder(PydcsWaypointBuilder): def build(self) -> MovingPoint: waypoint = super().build() From 3161ccced362c0b7dd0b300a549a3443bedf7867 Mon Sep 17 00:00:00 2001 From: Mustang-25 <72566076+Mustang-25@users.noreply.github.com> Date: Fri, 23 Apr 2021 22:25:11 -0700 Subject: [PATCH 065/438] Re-adds GAR-8 (now AIM-9B in DCS) Fallback dates (#1025) --- game/data/weapons.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/game/data/weapons.py b/game/data/weapons.py index 5fe60a3c..a0fec4ca 100644 --- a/game/data/weapons.py +++ b/game/data/weapons.py @@ -830,6 +830,8 @@ WEAPON_INTRODUCTION_YEARS = { Weapon.from_pydcs(Weapons.LAU_115C_with_AIM_7F_Sparrow_Semi_Active_Radar): 1976, Weapon.from_pydcs(Weapons.LAU_115C_with_AIM_7MH_Sparrow_Semi_Active_Radar): 1987, # AIM-9 Sidewinder + Weapon.from_pydcs(Weapons.LAU_7_with_AIM_9B_Sidewinder_IR_AAM): 1956, + Weapon.from_pydcs(Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM): 1956, Weapon.from_pydcs(Weapons.AIM_9L_Sidewinder_IR_AAM): 1977, Weapon.from_pydcs(Weapons.AIM_9M_Sidewinder_IR_AAM): 1982, Weapon.from_pydcs(Weapons.AIM_9P5_Sidewinder_IR_AAM): 1980, From f69450e2ae24df81d42b1ebc63d8375731154612 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 23:06:31 -0700 Subject: [PATCH 066/438] Add auto-procurment for airlift assets. https://github.com/Khopa/dcs_liberation/issues/825 --- game/game.py | 29 ++++++++++++++++--------- game/procurement.py | 8 +++---- game/transfers.py | 37 ++++++++++++++++++++++++++++++++ gen/flights/ai_flight_planner.py | 2 +- 4 files changed, 60 insertions(+), 16 deletions(-) diff --git a/game/game.py b/game/game.py index ec687561..673ad30d 100644 --- a/game/game.py +++ b/game/game.py @@ -30,7 +30,7 @@ from .factions.faction import Faction from .income import Income from .infos.information import Information from .navmesh import NavMesh -from .procurement import ProcurementAi +from .procurement import AircraftProcurementRequest, ProcurementAi from .settings import Settings from .theater import ConflictTheater from .threatzones import ThreatZones @@ -117,6 +117,9 @@ class Game: self.conditions = self.generate_conditions() + self.blue_procurement_requests: List[AircraftProcurementRequest] = [] + self.red_procurement_requests: List[AircraftProcurementRequest] = [] + self.blue_ato = AirTaskingOrder() self.red_ato = AirTaskingOrder() @@ -131,13 +134,15 @@ class Game: # Turn 0 procurement. We don't actually have any missions to plan, but # the planner will tell us what it would like to plan so we can use that # to drive purchase decisions. + self.transfers.order_airlift_assets() + blue_planner = CoalitionMissionPlanner(self, is_player=True) blue_planner.plan_missions() red_planner = CoalitionMissionPlanner(self, is_player=False) red_planner.plan_missions() - self.plan_procurement(blue_planner, red_planner) + self.plan_procurement() def __getstate__(self) -> Dict[str, Any]: state = self.__dict__.copy() @@ -159,6 +164,13 @@ class Game: return self.blue_ato return self.red_ato + def procurement_requests_for( + self, player: bool + ) -> List[AircraftProcurementRequest]: + if player: + return self.blue_procurement_requests + return self.red_procurement_requests + def generate_conditions(self) -> Conditions: return Conditions.generate( self.theater, self.current_day, self.current_turn_time_of_day, self.settings @@ -337,6 +349,7 @@ class Game: self.compute_threat_zones() self.ground_planners = {} + self.transfers.order_airlift_assets() self.transfers.plan_transports() blue_planner = CoalitionMissionPlanner(self, is_player=True) @@ -351,13 +364,9 @@ class Game: gplanner.plan_groundwar() self.ground_planners[cp.id] = gplanner - self.plan_procurement(blue_planner, red_planner) + self.plan_procurement() - def plan_procurement( - self, - blue_planner: CoalitionMissionPlanner, - red_planner: CoalitionMissionPlanner, - ) -> None: + def plan_procurement(self) -> None: # The first turn needs to buy a *lot* of aircraft to fill CAPs, so it # gets much more of the budget that turn. Otherwise budget (after # repairs) is split evenly between air and ground. For the default @@ -372,7 +381,7 @@ class Game: manage_front_line=self.settings.automate_front_line_reinforcements, manage_aircraft=self.settings.automate_aircraft_reinforcements, front_line_budget_share=ground_portion, - ).spend_budget(self.budget, blue_planner.procurement_requests) + ).spend_budget(self.budget, self.blue_procurement_requests) self.enemy_budget = ProcurementAi( self, @@ -382,7 +391,7 @@ class Game: manage_front_line=True, manage_aircraft=True, front_line_budget_share=ground_portion, - ).spend_budget(self.enemy_budget, red_planner.procurement_requests) + ).spend_budget(self.enemy_budget, self.red_procurement_requests) def message(self, text: str) -> None: self.informations.append(Information(text, turn=self.turn)) diff --git a/game/procurement.py b/game/procurement.py index 859a4b9c..97453d3e 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -72,7 +72,7 @@ class ProcurementAi: if not self.is_player: budget += self.sell_incomplete_squadrons() if self.manage_aircraft: - budget = self.purchase_aircraft(budget, aircraft_requests) + budget = self.purchase_aircraft(budget) return budget def sell_incomplete_squadrons(self) -> float: @@ -192,10 +192,8 @@ class ProcurementAi: aircraft_for_task(request.task_capability), airbase, request.number, budget ) - def purchase_aircraft( - self, budget: float, aircraft_requests: List[AircraftProcurementRequest] - ) -> float: - for request in aircraft_requests: + def purchase_aircraft(self, budget: float) -> float: + for request in self.game.procurement_requests_for(self.is_player): for airbase in self.best_airbases_for(request): unit = self.affordable_aircraft_for(request, airbase, budget) if unit is None: diff --git a/game/transfers.py b/game/transfers.py index a0387342..e2224dd7 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -9,6 +9,8 @@ from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type from dcs.mapping import Point from dcs.unittype import FlyingType, VehicleType +from game.procurement import AircraftProcurementRequest +from game.utils import nautical_miles from gen.ato import Package from gen.flights.ai_flight_planner_db import TRANSPORT_CAPABLE from gen.flights.flightplan import FlightPlanBuilder @@ -409,3 +411,38 @@ class PendingTransfers: for transfer in self.pending_transfers: if transfer.transport is None: self.arrange_transport(transfer) + + def order_airlift_assets(self) -> None: + for control_point in self.game.theater.controlpoints: + self.order_airlift_assets_at(control_point) + + @staticmethod + def desired_airlift_capacity(control_point: ControlPoint) -> int: + return 4 if control_point.has_factory else 0 + + def current_airlift_capacity(self, control_point: ControlPoint) -> int: + inventory = self.game.aircraft_inventory.for_control_point(control_point) + return sum( + count + for unit_type, count in inventory.all_aircraft + if unit_type in TRANSPORT_CAPABLE + ) + + def order_airlift_assets_at(self, control_point: ControlPoint) -> None: + gap = self.desired_airlift_capacity( + control_point + ) - self.current_airlift_capacity(control_point) + + if gap <= 0: + return + + if gap % 2: + # Always buy in pairs since we're not trying to fill odd squadrons. Purely + # aesthetic. + gap += 1 + + self.game.procurement_requests_for(player=control_point.captured).append( + AircraftProcurementRequest( + control_point, nautical_miles(200), FlightType.TRANSPORT, gap + ) + ) diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 87d00a60..a82877b0 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -551,7 +551,7 @@ class CoalitionMissionPlanner: self.objective_finder = ObjectiveFinder(self.game, self.is_player) self.ato = self.game.blue_ato if is_player else self.game.red_ato self.threat_zones = self.game.threat_zone_for(not self.is_player) - self.procurement_requests: List[AircraftProcurementRequest] = [] + self.procurement_requests = self.game.procurement_requests_for(self.is_player) def critical_missions(self) -> Iterator[ProposedMission]: """Identifies the most important missions to plan this turn. From 8a44fc19eead47afad7714c415b8a8780be821ee Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 23:23:42 -0700 Subject: [PATCH 067/438] Limit range for transport helicopters. https://github.com/Khopa/dcs_liberation/issues/825 --- game/transfers.py | 48 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/game/transfers.py b/game/transfers.py index e2224dd7..9a0cec5f 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -10,7 +10,7 @@ from dcs.mapping import Point from dcs.unittype import FlyingType, VehicleType from game.procurement import AircraftProcurementRequest -from game.utils import nautical_miles +from game.utils import meters, nautical_miles from gen.ato import Package from gen.flights.ai_flight_planner_db import TRANSPORT_CAPABLE from gen.flights.flightplan import FlightPlanBuilder @@ -144,24 +144,56 @@ class Airlift(Transport): class AirliftPlanner: + #: Maximum range from for any link in the route of takeoff, pickup, dropoff, and RTB + #: for a helicopter to be considered for airlift. Total route length is not + #: considered because the helicopter can refuel at each stop. Cargo planes have no + #: maximum range. + HELO_MAX_RANGE = nautical_miles(100) + def __init__(self, game: Game, transfer: TransferOrder) -> None: self.game = game self.transfer = transfer self.for_player = transfer.destination.captured self.package = Package(target=transfer.destination, auto_asap=True) - def is_airlift_capable(self, unit_type: Type[FlyingType]) -> bool: - return ( - unit_type in TRANSPORT_CAPABLE - and self.transfer.origin.can_operate(unit_type) - and self.transfer.destination.can_operate(unit_type) - ) + def compatible_with_mission( + self, unit_type: Type[FlyingType], airfield: ControlPoint + ) -> bool: + if not unit_type in TRANSPORT_CAPABLE: + return False + if not self.transfer.origin.can_operate(unit_type): + return False + if not self.transfer.destination.can_operate(unit_type): + return False + + # Cargo planes have no maximum range. + if not unit_type.helicopter: + return True + + # A helicopter that is transport capable and able to operate at both bases. Need + # to check that no leg of the journey exceeds the maximum range. This doesn't + # account for any routing around threats that might take place, but it's close + # enough. + + home = airfield.position + pickup = self.transfer.position.position + drop_off = self.transfer.position.position + if meters(home.distance_to_point(pickup)) > self.HELO_MAX_RANGE: + return False + + if meters(pickup.distance_to_point(drop_off)) > self.HELO_MAX_RANGE: + return False + + if meters(drop_off.distance_to_point(home)) > self.HELO_MAX_RANGE: + return False + + return True def create_package_for_airlift(self) -> None: for cp in self.game.theater.player_points(): inventory = self.game.aircraft_inventory.for_control_point(cp) for unit_type, available in inventory.all_aircraft: - if self.is_airlift_capable(unit_type): + if self.compatible_with_mission(unit_type, cp): while available and self.transfer.transport is None: flight_size = self.create_airlift_flight(unit_type, inventory) available -= flight_size From 6016ebd3b495c17033a4cdbc0a7f937619c4efd0 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 23 Apr 2021 23:24:03 -0700 Subject: [PATCH 068/438] Get transports from the closest airfield. https://github.com/Khopa/dcs_liberation/issues/825 --- game/transfers.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/game/transfers.py b/game/transfers.py index 9a0cec5f..24008293 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -13,6 +13,7 @@ from game.procurement import AircraftProcurementRequest from game.utils import meters, nautical_miles from gen.ato import Package from gen.flights.ai_flight_planner_db import TRANSPORT_CAPABLE +from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.flightplan import FlightPlanBuilder from game.theater import ControlPoint, MissionTarget from game.theater.supplyroutes import SupplyRoute @@ -190,7 +191,13 @@ class AirliftPlanner: return True def create_package_for_airlift(self) -> None: - for cp in self.game.theater.player_points(): + distance_cache = ObjectiveDistanceCache.get_closest_airfields( + self.transfer.position + ) + for cp in distance_cache.closest_airfields: + if cp.captured != self.for_player: + continue + inventory = self.game.aircraft_inventory.for_control_point(cp) for unit_type, available in inventory.all_aircraft: if self.compatible_with_mission(unit_type, cp): From 17751e52fd6253910660d0935fb0c8a5989ede86 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 24 Apr 2021 15:13:29 -0700 Subject: [PATCH 069/438] Clear transports when disbanding convoys. Also changes when we clear the convoys. Because we plan when transfers are added (to plan UI orders immediately) we were planning convoys when delivering units, then clearing the convoys, then planning them again. Aside from the wasted effort, when we cleared the convoys we forgot to tell the transfer that they no longer had transport, so when replanning they did not get a new convoy. --- game/game.py | 1 - game/transfers.py | 12 ++++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/game/game.py b/game/game.py index 673ad30d..34ddb5bc 100644 --- a/game/game.py +++ b/game/game.py @@ -291,7 +291,6 @@ class Game: # Needs to happen *before* planning transfers so we don't cancel the self.reset_ato() - for control_point in self.theater.controlpoints: control_point.process_turn(self) diff --git a/game/transfers.py b/game/transfers.py index 24008293..1a5bfba1 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -264,6 +264,7 @@ class Convoy(MissionTarget, Transport): transfer.transport = self def remove_units(self, transfer: TransferOrder) -> None: + transfer.transport = None self.transfers.remove(transfer) def kill_unit(self, unit_type: Type[VehicleType]) -> None: @@ -275,6 +276,11 @@ class Convoy(MissionTarget, Transport): pass raise KeyError + def disband(self) -> None: + for transfer in list(self.transfers): + self.remove_units(transfer) + self.transfers.clear() + @property def size(self) -> int: return sum(sum(t.units.values()) for t in self.transfers) @@ -337,6 +343,7 @@ class ConvoyMap: yield destination_dict[destination] def disband_convoy(self, convoy: Convoy) -> None: + self.convoys[convoy.origin][convoy.destination].disband() del self.convoys[convoy.origin][convoy.destination] @staticmethod @@ -359,7 +366,8 @@ class ConvoyMap: self.disband_convoy(convoy) def disband_all(self) -> None: - self.convoys = defaultdict(dict) + for convoy in list(self): + self.disband_convoy(convoy) def __iter__(self) -> Iterator[Convoy]: for destination_dict in self.convoys.values(): @@ -444,9 +452,9 @@ class PendingTransfers: if not transfer.completed: incomplete.append(transfer) self.pending_transfers = incomplete + self.convoys.disband_all() def plan_transports(self) -> None: - self.convoys.disband_all() for transfer in self.pending_transfers: if transfer.transport is None: self.arrange_transport(transfer) From bc54e57fd4c15ff61e1a48ae01131af6a2afc921 Mon Sep 17 00:00:00 2001 From: Starfire13 <72491792+Starfire13@users.noreply.github.com> Date: Sun, 25 Apr 2021 08:30:31 +1000 Subject: [PATCH 070/438] Update campaigns and the splash damage plugin. --- resources/campaigns/exercise_vegas_nerve.json | 15 +- resources/campaigns/exercise_vegas_nerve.miz | Bin 31132 -> 29859 bytes .../campaigns/operation_peace_spring.json | 11 + .../campaigns/operation_peace_spring.miz | Bin 0 -> 51256 bytes .../plugins/splashdamage/SplashDamage.lua | 228 ------------------ .../splashdamage/Weapons_Damage_Updated.lua | 209 ++++++++++++++++ resources/plugins/splashdamage/plugin.json | 4 +- 7 files changed, 230 insertions(+), 237 deletions(-) create mode 100644 resources/campaigns/operation_peace_spring.json create mode 100644 resources/campaigns/operation_peace_spring.miz delete mode 100644 resources/plugins/splashdamage/SplashDamage.lua create mode 100644 resources/plugins/splashdamage/Weapons_Damage_Updated.lua diff --git a/resources/campaigns/exercise_vegas_nerve.json b/resources/campaigns/exercise_vegas_nerve.json index 03f2b7af..bfd2d757 100644 --- a/resources/campaigns/exercise_vegas_nerve.json +++ b/resources/campaigns/exercise_vegas_nerve.json @@ -1,8 +1,9 @@ -{ - "name": "Nevada - Exercise Vegas Nerve", - "theater": "Nevada", - "authors": "Starfire", - "description": "

A Red Flag Exercise scenario for the NTTR comprising 4 control points.

", - "miz": "exercise_vegas_nerve.miz", - "performance": 0 +{ + "name": "Nevada - Exercise Vegas Nerve", + "theater": "Nevada", + "authors": "Starfire", + "description": "

A Red Flag Exercise scenario for the NTTR comprising 4 control points.

", + "version": 2, + "miz": "exercise_vegas_nerve.miz", + "performance": 0 } \ No newline at end of file diff --git a/resources/campaigns/exercise_vegas_nerve.miz b/resources/campaigns/exercise_vegas_nerve.miz index 7c0e3e25a25cb4f5c8b1839bc57a8773225cee18..32029cd13a777b2730d883a5ae62e364fdf2ec00 100644 GIT binary patch literal 29859 zcmaHSWmwbi`!>?jqJT7rsN_III+YfX?ora+4bm#9bc=+N1BoGQq;!KYa&&i$j%R@Q z`@Z-e&l`?w?B=?!IIr`(KljF4IW+VKD5%)jC@3g&C|t&A_HSiTP~4$+QLvD|v9WM+ zvaqw=faRPi>bPnbp)WK(LN(R=H4yjnOQ7o$MlhMxw=1~la&~T^dB5|FheyQrfx(W1n@L@@a7Ihr;cl-;Z!ddG z!PvS(d&1=b(}2h>YNfFKv4k$-dAV@@j`9m%FBp48Win7fYH81I#)a*Iw@&hu+poqv zR+~l54voUXB9S+OPzY?d-i>?a;ppMxP^|^si|O1u^73}A@N@O$gC3{ox9ImM*oNkb zNG)m1d4?8U7Jdy5GFIy-T3cHmDhAM)O52-`t8EBhGkaesuc_fNrH zV_EINrytL+-n7TKGQtxr;USe8-38j-I+knF*bh zyBH9N+foaNVr!y1<;Z1!YO6;fcRb9<+(#vPYRbF7*HehRPDFrqE3!7yP3H-Ar=Ra> zzCQeA?<(QcS@%Y&k+;ipzSEbKKV9vT56ztsurqQy)8KQDvvc=mTIw|(Fv-!O`-^jB z*iM7B#|dJ0RR^)Qv$KB+-ua1jak~D^#GO`NXz9#ia(;E3mwjJ8qW6g+IfHGjeI6C< zVu!GmjBTA`9_#L}@z=uEF)dqU&xh%kjP_fPkB)jfdmUJ`AdFJ57?FIB&1RWwvojx8 zmxI>P)Q6hTGCy zEW*mdR@`@+);gA?$UvZrlZ)I7J@BU@+mDtZ+Zu@dWlhNA@m14gKc$T@MV8kEAt_-6 zZBl-Idw%Sse`ePMX{VgaM$P8V3oSpc4HmF-fAP*!V3Bn(7R)M;Xv$w8O!r9%SFDu> z6l;&oR>qc!ofeNzggmKL6{%VYwsURui&BtsGu{ao6p4DOvD0wm$G@L>ndkBq8_r%k z-!op@T(|vBaLuBw+wVB5&z~i?2)j`$$JhOBNs4<2=PiVUIS z`9TX()lJ)L^(?_x5cEy+q{D;S#QeftNoMpxFaX%L7_}5;iPjg&d*QAIH3jJ?&~!l@$**y*+x*%y%kWLZBQAVVzzpi)4&H zo}7y>{2+afE2DebpK{GTfpXyee1PRI)lCmJc@=6dP>$`(!X(%LcY!e}b)^B(i(aX^)>>$Pg$2-f!Jw zJ~7RloQ<=-a0WJ+LMI^sm7}G|pSfCg;J2kr;rh-)i?7ZgA(h8*zCek(cbd3Wb= zC8`ZPyti*@$Sl1*akr%c_xX#Z29xC%c8|VE9-{pjh)TFDIc`q5blJZ^j4#-MGS^m8 z7R|>#u*gddmbLwCnG3Fpy71OH?{5{kkZ#VncrU@n94jnTa|BtXj{5x>0Qx@1Q#5s6 zzEwv~rLkXJz9d}w*_^QUt~i_(UnFyeVkdrFdOL+jPsDR7P{42B^{50c@I<#nIrIrr zxyVAl`RW`klsx{$Ask&DUtXE}yV3^X@t)(Z%Zsx5Ijz^bXRbP;hl`qpYOWXCO-c%9 zYifst(2oRRiboUOorgg%XW`n3!V2u1j9pWG{Zt_YoA+!A+lhP5RI2J1zgkFXD9;w) z>0p^iuYzRQuSG&?8fS#bzKyr{rMP_S)7C5n#VWrjKWOTi3u0k+d@VwOFtNgeEY5On z+oqK))$Le!l`i^>6c$?2^eeEo)(%439Q92F&2=9zfIH!S*A5<|abCgwD?(@Npj#!~ zUc18Fnvwn&6PdqJmnMht3csdICPcYQ+pcKwq`-WL|GV zsri!aoPOo~zOQ|5x=*fm9%oZ;yV;>Bcq&ur@N*>9UHH+ELB9r>z#wo7s8H7hU;OTS zWbZ93oCJR(O|$9bIn1{{1n!qmRM8w)mvV zMQ)s0rEyo-3!gPnJ@WBR7HU@95RJ{uI7JHpz!|$snm(BDNlx>8-48C4JrDsDSfmI_ z7;ej7sfdx?dq5J`_uQQng4@93rc|U<=ax*_nGd85x9^7XQ@7qV%U_APt1C0N-?{HK zlsQ#Xv2xJTqb?Vy^pm1KNMYq*SS`x1@oq5&nbr74(ZCpIYsF!lz*siv{RVZrKD?K) zhk$_%y@Y-ssMlP7c0}#KTpwN-Ej+x2jG|cnI~nj-Jp}gTnVCdK8_9#ENRQTtCGldL z?+KE!P3SyhKVrqcQA`#jG42iQ5Y2ZwpAgWV`S@UZ=7e($m8i+f6v`?0J)_s;{-bf3 zIWczN&Q=)R)nWM~`niqk+%Z*P>=*5hgrq-isJJK}j=NocC@v^niOrXRIEh*c{yFEuVxxl5u1?V@_>yo^{YlUAD-tXNNiYQ%FD z<+IyCWhtIcgohP4h+`T9P2=(hACc&1m6-T5Xp9;|ql=RhmZwvP1x-F>sUk2POd*97 z8FhURLfw`R*UDb%7(6UqR?s)-3!7M|VeP~z6=v}HIHx|!09BPO!O_jfEPIqUE=TRo z{VO$%?f`&MMm=_Ni1;`Y%PYfH4I}&I7dWw2^V&X0Hw$WpL7*2*c!04voJDw`H*DHW zu^~mI26j{S(uz2cG1e%7^Qq&%|MtbnN9y#En|)u6(8^nQH_e4#N1oc;{hlHkJw^5& zcHugBMuqrNOoEOSW%>PVwb8BnB6S`$4kd?+&5==y53P2?xn#Zvu&_n;0u(P+okJF9 z7&fEODNQXeM`G{)^76YlKACoO9{VNkX701Y0y+vgJ84}I3?(<2ID45KUJG;q za2EYJw0V6ddjca~y5muMv?%oJ;-po+!#bdqn|}?l|K+Hoa8tHUL2{}#LXn3QAik=3Zdbv?shb7WtuRru)9cdeqvLS31 zZm=ZCU+3{UpuG%?4!i?;uYottqRObyt0PM~=vB5%Gu>8OTZC@rSo^Th zXZAhJMJ**}e56(sMm{8fd?n$XAa@n)Qr%C^o1sN&t4-K>Z4jT`4^>roC z8lfxK4aHZI;k&yx&k&?n&u&15CX(11CbO@x7e=OEO{9R`6B|DeQbJnu?aOn@#db^V zy7&6^0L)FYZ;878Zoc$Kof%a`aI1FHJajeg>f$D?&Fu9L9?uyXISd|hg!@y5ur3fmJg9SePZyo=c3V9K7u>x!zJ|DMWF&=#V{$!#^H!xnGZh1Gw6_AMfnV?g zHG~_=e|8x0u&txI?)3h|Rq5j1K)CSH}y_vXjVoyE-|;ih1O z_R(`N1V148epP^yFuzzm%>f%3@F6X9_2^go0>XOQYY5mG$%ArsvDi7fvU2pT(_r^B zWi71iJA8J(ycjkU5<{RN4oy;!B-5B;e>ylCL1X8MSYJ(10Mya2`53m|sUGd86$ZT; z0$uP6i0I?FQTjza&FY;a2-NeT*x8YySN-1G<0*A0@N8f!M7EABf3u4=pKN7zUuq|7 zS@jq&*KfhSru`tOH_D6xxRY0+EiknpGxToh)cq%4wL1^rY0a7oeHuv1+!>O>bEf8( z-#_#zZ-0@AwkA39$xzV#`eGBCREhxi%zfMMN4@uDj}AwNv|2xB4bD%C=$$sG$omZ& zE3gA{kxzbm_wq1Zg!FHe@Y}5ty6z$bRJxaqz+n1=X%kzYP4YM~^KNv|QK#!9@nLl1 zAJ$O3!2n8@eTpeA%POJ(nXKSds%h);#OK4MTJ^Ct+s_ZftNrFTs{L7dlkjtiRl*+Q zLkofQxfM7A$A}GtSg+k_zpRCxe7drB7x{P8`*3xq2gS0rdI^195{DiC1hUI~tIC#+1h4+@`4&OrwN18XKsU=G18yiiK5h`yI?^+JltGY^?ek6Fv(8l9oM zHqua_{R)1}n6%EQ@mQ*Tde|_QlHqdazNeln4PYlKBPV`xt!T$PzDSa9Nn9BO0#X`l zPHXo%;@3%o08eQ9_YO|9Uh*^-I*V(k^Mc+}3-b`_OkN%E^x+=i zU@*H+1psElLX|fO?p?aqv3rLY7yP2a6_Xo3hDS?=?R0Q5XH;l^B1?v)DBI3HxO9KX zl4^?knBEbjk)wM~f3brUB53Vz5&}oi_YLP?T6eu>t4B9#6KP0$eskFjl>^edB0&uY zUD+mI1mzy{fz4dUjVj&IUCoT#O#SZr3+h9DAnzxCozUwyR{hU35q+L-C(EADleBeeva6hhdYSqK4=GpjE*4~;! zF3Njlcj2<$v)34YL}!5GJVS5o}}&gq)m1iO+z&FL=88i&!0=OL~!37I$b z&vr%|qV_UA^wv_28QR7mTG!8vRftorg)a;fYVqUju`20IEQL5dMH2a;*(8aA1%MLV z5sY=C5!X!F(RfcgG6e{)o@Vij0-`3znFt+Ru(YO6PuVUW_&t&jLpDgOW7&LN!0tP* zhYU%>9DB4)yt%3Mv1+78|0J5dVrcOWKYqA}`*2U5N$1?sSlI7)+`0;p>hLse4_Jip zDBIe$tE}|i2b7xkdU)gP>vcHo^Nx084RsJbb1PL7vY`?4AkEP6xENR0rO_dLP?h-A=OQ%huDXzC1TnQ5=ojuAnGKH;rn_$> z?0w@CIaG;Snon{aV^ie}lPcnMmR>7|k?U7L@AE%vcWM6^iQTR1403DfEq*>P&P!`8 z`;%FTJl9l#f-FFL4n4QW)2zUuQ8q1pP~pQWh7qvF($?tp4*3}DS&Xqias1?x5KMt{ zCeB`vEr*)tr=@3B_D`|6j5ei0rSA!2IE!f@Ov|{~HYH{ii)XPz6D&0?3_kwwBUX*R z!hRDf#z@`ZC`6=OcdR22Bk9i?i{N(IhNHFyRG-w3yp@8{9H7+n)63`M{w3?(G1!u& zOuL8u7ry2K84r643LFbf1Z`Q7j%OMJ??Q#2ph* zAJs#>*7@ik{9vtzm$Qcy=q6gzLqr7<9FEizk4$1@)A534g>3=Z*WK*_l=e z^R#ZMDmqk4T>(%9X433pOv~?WBLZPmT=xRr3gR8`KE+xJaEb{j^Vav?Gkqu6J<>VT zU8GO=CCYkHI7hpsR6f<5Qgc}nkIs3Hs9J3K3AeLx7uIR@c>Ot@=NxuF<*p!?PbB$W zyyc)}e8uo#=Vjqun&Op0V!gCPW@xCDw(BQhL$_y*+}N-O6qFu|h1Hh6lgY~B*eqXu zuJW_9&B414=8`MA`IgKMYn~UZTbs*Ua)9=&<&s}Cv=@^R3)?AA`gw*AFf8cCziPf~ zET8Ux$tnB$*}bgS;lwwFx1i+8)yv%+0-0xJ5GEEimc%a}ML|Vaca@;>7`<)ys&&U-q0LpdA`A3rPdYm|51AVcqw) z#|^4fM-0_1qX%%Q0_3BVnCZQ_OWzJW>lNaDod@9@nvm>KpL~ECi~aVI=tmUB8zYHJ zl!^CI?1#6t&OyrCoAk1~FSuMjk$(*O6?Bws(BYW%#=e1>VVFmIREs13EvLUHp<3m; z{If|0)sJr(UE>!B$Et{k%G>Rk-`dZSgB9*J*Vx^yq0UPiY~XB;z)ua!Y_W+3Q7Tc< zZB;)4q3oi1sC+)$ZtZw-NT{!K9y5v5Nct?$SE{0K$u8AOQqvvAZVEJU%d5XtSExOF zaCb530MTS^DYPE)hlOmS@U|msAMQ2^rij88^XI!EbWk(#XKdA>k3=DKKq9~gu>Ncu z(y-(F&BF9s4zcn%J$;DkE{xBV&NV<<=e&6C_ogzd$M{R=IvyI2Tr=0Yr*#K zri+AGHHl>1Szde~`;9w`u@f%=sfs24twXT$h21>&I<{q5dk#CAq~+b$CM#*4EcrGK zz&q<02dG}B^*~+sFBo1qPH(Wc0;W?Vp$ak$g?o(_Iq#wMKm$Mtn00*!Y1na&6v&Lo zy;yV3irk4qIavJskzG+W{-RW)+z^D0bmR~9F#xE0m08=xH$G>I;MR4JXaO2Se|%NK z$fHt6zJ2AyaKsAf7H(7mi$37F-_1Yy!`PeAAL{Y$Tj%|_9KZEAn~{RseO1?8T6lBg zzG!;_+DEKBxOK-3Ab1^D8(n$Q8nHqeSDuth5-f*TB&p|EFxI;n?(3!hU_CbmSvCRj zuM^xe2E`DMq(nkxlME7xlz&-%Wz?@;fIcrwCgNp#aljksDF*!tZ~OuCM;gs8g`t0YfN%b)iMR1)rb1zZ*S{Q>m^Tlpp7go*~mEBfl;Uv z!FpV(S0_Ym2*oB_QF@M*du{-Gjmni5?J8GzV+jum(r7lYqcZ@E77j-6w`h=#q)0uf z7F5zYX$6jlKnX^0uXDw12HF+og_vg*st$dS`wd(PM1(L2bk;;UtA7Q)1^fWBuEtUQ z!Chqw_GEne7BE^6bCoASUy2mt&zn49EfAXPJOez!OyCBLgFvUABF=YPBIukz_hmhwKsW{8do@6(f%bM569x6qX}D39hSEEcGF_K~oO^`D| zQxQj>;pNMSQ-R}{X3vT{6ka=Dqq9@J!8W^_;1G9Tql6cgm6M9 zf^`~Bwd56u_eK@1xI8j_kQtliL75mIb-+z*)9{~Fu&d3H?ikay82?7+|G(%8kkJKR zM<-mR;b3nUi2!b;8H+L7Rp7nb9Gbw%jU`n#O5sA{5O$t)uaFU< zLGkAn8KJ;QOekg_?lQ*y0Sa`0Pt=ZJc}Q25)V)vKQ!xmrhQANz><*ljHH_i5-REeAdTb6-ZMeH=CZ%0| zXCKuQi4*wGuRJJ7qgmAMt0G95ren|MD+Xw?=f7 zY;X^7ZbKsTs&ERonhBcOFN2DC_EN#%EH`@K4-Ga1VIuiFtN|$B1WeZYPU#X3LNB?U zp(yA#e6OpJuNa3k3>v>ypZ%ccuHpR>n0@Xm`=R$6*Zh2~8{9X#VI}=fH_-p189vUn-%E~Vyl%U!t1lA3XqE6J1R-yyh?GuHo##CB68ZQ_F)FnJ^8>7Qz-Aywmc zSKakE1&$>)FmSLG_K-fQOdC5AH~d;s>?}ss{)$h3NrLH(WZ?ftGHjcGXxB7~BwHg; zr8-ALFyW)V-h+ID{26T=dg@ydL;CqEJuxK-<~M%O{^Q4@33x-t$XXhK<|kx(V!0); zEFYQO3n2KSruao26Jr$v5rC8%6D2zoq!E@07ySy%*Z6+w9A3=&l_yNr+2vdQelMgl z!h`ynZ!z}btN&1na|FoP#1bhFWF}ZO1tbKp08sw2=(FV^D&%moVR&+XXGPpFi}!-) z|4QUya~<@xppWDLSD;4XESW)n?vAWs;I=R~NTB@f#Lp6q8?gb=75$xC?}h&eG{RZZV*VUe&x0q~qJS{x%w7^%< zc??oGTzRN>SJaLad1QO7wvyo?LAfV;D{wraWsZ)n*v-;&dZ_#SO^}T9P*GtPq_~sy z={(%0xKjCt%iQIUd;ZS8%zxzFgz|v-Hh7*0u7r5LO-8rphM{FeAyz9A)3S!4|2X#+ z*V+_je@E@Xzs#bb`S@Pkx1&}w#*%H{_3V4<&hr@qP;3{IjgXu49NvxYHvzG(#xW2Y zSw41qM#KEitbq+dcm8%bfQQeiHI}f!oMd@qO~Rnj<0%w&EwD)PC%xuA@WXY)Z~30b zf6p!dA+>+!79`QQ#FQQC_7HCr7+AXv)Q}e!5S~saV(W%Y-@=1q9sg$jDW9dJ=G_3` z=YIe&_Hy0jpRyOARq=GPn-!6i4Sbl6paUH8lkswCTd~+%0jkI*a5ve)z3{P+Dys5J zgJujS1tZHqejR{9DG6pjMqrk#^gYEHuXqCY`PQIRe!|G~g#699O$rDLfzTlN9s_xH z@00+NhnswH0+maT%&fou@&0q~^M^)xSHgg?OBw<}y;2BN4~z}?qrVOXqi;ts3Q~*j zfp#q-7`wIX?r|FYErECGpPev+92z;6f^o%+;{G`zzH!9p5SQ@o&&m_(zyg%{?r+G) z!XM1%40h&Xz$Sl)C=mHN7`q4l5Y2_|K#1&keZV2` znVd-HS~&QPrLkUu*TcULD~Eg^VDH_?=eFBA?n2Sx0<}9H`-Ez1Hpx3 zYz4UKC)5;Z;j{;-$_mW1L)PN*L)J#^X?c;4F;|*RiU}b_8f>WUY&IHPf5XE0{lBn= zLLd~^VWE2HC!`dQ@-`F}UDRzlHkw;N7Sq)N1;NBv9@iDMvuIm;4-thwiiY(Ma1Vy_ zPi7n2=lDakRKHyxOXr7d+N_WZu4)g~ohO;Vc;aF|hVEHyx)t#5lK_3})gvC#6TC4R zhK4jG=s@48=X1zis%2WIJ5W5zt{!spWiQ*T#-d@@D}i)&zuH)WNxJ0d3qHS`Q3e_c z-fv0%8iDrL(a&felxs(JUyF7JZUQp-Pe2tm*Ad@#s{M_i|d>@n*ue@?a$ z9Je?pt9Vy7FG9VGuZWfX8R|{|vS&H>KqyEfj1DM956oA5>g9}AB>0{uO!}R}w{QEs zHMJ3LXzpa!&5Q4>8QUF|uy4RrsQave$%OA@`Ld7Y`Vkb_GCX(2t7lR4ZCT=N;)v^N zlZgJW+W2g3R(yYFCO!-zNzocqo=;)9(zccg?$RlMINhKPIho=>jH?;+hkOcxyv|bK zS;5xTBDkFyD1)6uIa?dcZQ~5)@!?#6CRT}B1_qD=Ge>K1u`}mw;-tHlSIxzqA9``y zINAR+j%6%bX+(1k-CqV8OV}?wkQ+ z`3i`C%~1Z3g-0OA4z9c+9NoS_ZWa<#7|6=v@ynN2U=)Q9M8odfpfYR0D)$DJvS|N9 zWx;qf>QR65$4>(yTh%Y&h-tlT zb|fTqn72IV*vuNGZB4kfRs3NBl&C6j7F*TFhA)U4YM?WZzT9{@&h388E}@&VBmywuD-3ELT;36s@5gonZNY z5_#)QdW3CZYH-0e$~W6@9u@^VBzwR5e5lU9SYSrJ^tqV)dPAhX+>$wlN8Z)&-m7w? zx+PyvDflOIooJ2gt>HA$8sv0#r1J~PTtyMe5DH{$R#5J60lVNAx3z!x59a?`_ktuBY!O zH#MeFTp1Y3%R;y^gB|0oPhKnRe(RhL835w#FL#mLjyPOFMN)V6Uu^Q`muSIEBCM)|Bw zj(gQPCbA(8<5rt5KEo>MK{p~GLvsMGv+BNOk^{@HaNB|4@Y0D~%m<8@P zuC7kKo>gnmTuR(kcsM0A#r5|-@jwnjd^M^8?u^0i*Wl%>#G(7AJUbzzvk0o|o5urD zNJyYN7ZFH9ffG>SNHW(CbH_N0l(DN=#IgE&&B-yVc6H5p?{~qo-#3*jiCg;TO<+e| zbX**Qdh1R-z}=~Ehq)n1O*_xv{;<1GNjzUp)%K*)z3UYL?x&1E{F`Kx%Uk4LTy>%} z4rS->De2;)M)^Gu`nB%)5CMkWorvs+#5sGQ#M+~xPvFyU3_7f4e!c!_dR-5lhyP~b z!EDYoHIB#zxZmpjZ_Pgj)%*c~%SB1zWKZM(UM z_|GVm|CTFXQ4Ndqk3-!w?f;N3uNre9y%)$dom<;+3?(`k%kWXCO^)m@{P*o-1(o%{yWpy`Ob3>BdfJ;KHS39Ua>G> zF0T)F0X{D%RPV96W!o@!A!Ji^(04osB2B+totwjp@IKrga}^=E5$Ce`Eb79Rfi+|HeN@<vFRa zYj(p|{lRZa@^3}cj+F_Lyk=-ZicWpCp5bXGK%%xmyldQcK7!X@6_@w}kNrULZFp4w z39mTfI)2>QZbe9=fVEZ6dq|JP_r$lUYnF+*?7OmSX_arx*$S_Y`&ZoE7_DJH(So}u zZwhasWdTiyc-tTpUDpspK^;)V)yU&@{>glX4 z_()Km%9a2|$kxZvP91~Kd&oz4H0cEg8=n=tq63_Gn&fA~(F;6sQ?l@9cIzLf*btv7QHltkLu_wlR)Sz4rn^rKl4rJy19fOVW&j zJ4tMI`d$%D7OILbX+An~!JS%^so8omo?&leyODbWvx3UksiVFEIzK=sNpbt#9pEEIcQ@2O)s3(j4 zABz=r6E6BSz?q;sC#XLDwW3qX{XF&?Ni*64GcOR?AiN3b2FQMh9NKk1#cEAHgX-04 z>82dpG~RCRG1j>JRFS$PhvtpEPK$raj=cG`8oRlCora%HumtR)OgST~&b*Q68Yy@3 z1@vHmf;4t`Nab-o*)%S)5Tuy3s7Qki%Nyg4cb;w9<{zcT=O|OGS9t3j5IGuqx$T0Y z{~k~e#@Zet1yd}v*)^Ln*l@qadK&)i3&>u(TI=ic<>}AkG)b3Jq@Gp_JLsL5>BCxTY1VNMFisEZYt;jNxJp2(5Q@~KcyS5_beAEREG_E& zNA{FKn5O|g4YdYSjUyU?NMoS{^rYkl9vzxA>;H2%)lz%s)}6L=i5=!an#41&B##+_uei~zx-1alW=N0ZLPS2gIZ$Q}* zZp*c`qaQ_t4@{UFYH`oYl29Sk_32GGhBQGw*a|a_4E2C%vTYrTGi{^+raG#4aFjFV zQmxiXCud)O>yp~n%z9k14s{>*1mpZbyzb6^iob90ZVv}b8evCtj3CQe@H?QxO3~q0 z{XW1l*}l%hbAF@&raEzXaB}TVib?Lh@80rj*wf-pu0D`?j{aSws{miGulnsI%RH@P zAUsLV&a?_i-V+k39=<0JKrNnB*DJ*8n)^Xm$fcc``ik18j90-Sy#@Ton{|#p#;rFq z?q!}PAt?n68%+-Hc%uxQx+lC#Nr(9q_qmaI;vQj~#begpK^kmWzGw^S4BvFV3%JCV zJ{aK%Lw<-c`%@6@Bwu}hNc3tD!C3nhROFzax;s{dYOL-i5#aQWTK;E6iFo3gStQ6| zzCu8JbM2R_<~Emr=CcKSKf0#b4k>jEyq^-}??-%G+r`^2&{=`K_Z6%AIz_V#)1f-) z7ONX@UrKr~tWAb@+y|N$1YeP*i# z(aw|APb#kK48}T9BM$X-qB=n`TSIodIB$w*-7rgT(001OO?AcUkj_wVoJsKQ%T-)4 zopLH=KTSsuyR?)gK!wnv+bL~uB19&K1z@zVwS!JEhSni?0YCuQeRRa2b zcD?%rSD832Yp5(ISi)A+sdHEfPr9@2>t$52_lnjP$N35&5rBKL;qMD|`#MF%1S;H5 z#aIh0OZFZGC78Z^Ct&=TY~&d4I$OCc{j0R&U)6=g?tL=9c2|hK6DH6y}@*K3% z@A$l;Ji#H@$3PO4m#F?Yb?N)s&aCp-M#^?UH)d(Z_Vu+u^*5TfX)37yn=`DNIg9+c zk@Pp&J4b($Js-4duC&=_5xuSp;eJ6H%DL%R{hR=zE^}&Y>~zS+s6e}xy_kJg$?nzM z1o$GeCA`TtUA*pQZcSwR{tlT3<_SsBKFdv&eNQSd$w7|wpl#*nnQ@Es&idt0^3pgB zF|{Zq;O^HKf1ynJKPY4P=3OJNP5FPw+h)<3(YWJ_WP+))?E)yq*^?&F7X|Ib!a)U{ zmA3V@B1BgW-Q$^BgKK5NJ=jR_A;CgYR+yoFb4?jwv}iDm|C1)g@@)nEz13KbEw42+ zCBxh1a4wH-YC+61UajxhX6olgOXg#K_KHH9`6AK=e{ZoXX|yw(XF+P@70na1-3XZk zE84A)^@`S3$9d!52)DmCqoZC<49GrGU=c$@`)Ct-8dxV^Yam zW;Su#Mao9CiqyT%0PnJ^4-DBmxuQW9jQTfc1}{2&Dnz%#AC1vz5+_-fqPQ&f6U&~o zJyvvv!l98(ylWiDz0$`(8u5X(pX~o6SYP2Um=){b7S1Sjl_b8@*TwE8tZTGPMLL9n zVghhY3m@=16F!^BeS%j>QIKRm=6S#I?MgzRTSJXZlU6OD)`P3K?t(L&RT4r=oWWh4 z4kXt9GNfm*1`FiL>!r}|TEzT5+7Uk>w)ZiCq3bi1ohY{1q&RQY4_x}xd7?;8+VwTt zS8fc>uYP?E6NJ6VFq0HVKatYyi<3brP-!2&Uu0i|75=1qknPnsXD~BUj9&4`sx!Rc z`lOLnPPg=INgKUA|6a|BT@RgeU?09X5BB6=!{ZfCl?g#+3aU6y?D#^PQ}@2d;-|id z*UL{rkpyFuNx4HMrQmI~{N&#s#!^&s4t%H=bdzNQw0*=3#F^kU#s@3Q-R@0!V?422 z#&mo9$XP{nImD6Ykw;>B^k8y-7l&x zHSP7(iWIlC;6k=#$jZFe22+H{(cD?}1@#cG%bYK07EKRHqI>x@K2dlHov&ukNs@G* z*(EyqcP^X!sOVN#kkvvw>Jd-*Tw^gd zf(*-`HS7;9HI*%wB20ln2I7b75Nx?etXc+|#GI*yt@i1zPBh}Y*fWAJHAZOVpPD5M z!j{PX&?$g%U#dj#O~x{m2GBX!Gyna3oVLMY)RRH1icXivmN2LjiZYXXznB2tQ*5~S zQuT88PXrH~Wg*Eymw$>ej2fFP2)7Yq=HOLT)xiBA9n=}(&E0HDr)nmtw;f(YwzuCk zu{Xh;VJ2e72Gcff&^~Q0dsAZj748ifQiol=$TLjKki5KLVazp_(LnUQ;hT&$E)9$( z8u;ARW+~ncbx?OL=$J0vSji>VEBGM^ZFN(X^Oj<$4Ll;475K14hcpyIjv|Riz%dKS z%vM3~{8=o1+5hno_#~H^xHIf-TVjWGhX&o9Xxd{3g|K*E?EO+RnqWDu&xhjF&9>4# zLY#Z)!mqQ!NJ#PyiK1-p?mP@~Cz=)efJIMpWwaB`I_NU|j-mf;9Gu)O_u#O>MUl(L z4=wX^z%h~cp^qr`HeG!n3L&cCFEqibBdj~=WQ5z%#pjuxj`_5$kFcm_(u5yp$&$qV zIGm2OxVx!|?nX2rW{=fCePzfHZn_r`jmt!B@EUVFCGQcvMx49_Z#@LPFw@3ljB7cd ze(gpyDdxP?oF!02iQS(@7Pa}-7V}Y!V>Lr-02b;_+HL6AQ8pj9d0)E|&4>kI-J`iO zWTLjGgVb|31oAy?sP;Ov7Xq?F+^Y&rxx29M^TMPX0*MI41+jM^&L5=gBOekP2?D6* zJd^=lOPwp}RAIE{=pH9T5$x)!+3-NYV#c<)d-Gz^SYkBj9z;mv>8ofdoj^K8&uFQR zX>>DYbjtAWxig=?SkWYsY0DJ72oS{HrE3eM>kY&5u=gVk5CqW9XHKL`94t61Mm7`T z1qk|lKz=!pS$Rv0W{P z!2FT!@4CHNF`DZa0Gj#FL3D(p34=m>q#`BQyZiJMdWAovp;#V`t1af`lipOoe%A<* zbFZ_7^>+)MdZ6V+KWeC-4@abYDa}yzPViGTC;6vSR{A65DM@0rpL`vieJc}pq16`M zmgf95Oz@hrn3dkSt=6y%>Zz6qQ|?sV$)5WXRp%?tjt3r&KElPvZGJ$B(E6IXn&bI) z#JlIUi9WE60I(}q(WZ?BS~n@f8yXt3vjPWe;g8i;?0~_F57W~0730IaxyYwRPfYCG zyxiQ@rVUP;6r~99mohU|jejR-Vif{U+81bdgre)}$n^ymS?cPJPh2pa_Xq)(`Nu91 z6o7aej9rYX-CQljk00P7(add0)bv!T|XALC>~b=>!XFE^Ol32?}0^gO+}MU(7?H@ zRX-X+dpu9&U?ZqIKc9$iorgQ*SR!^ZW(_0fa1m>$sG{}A(Ll%(IPDqb3-&p)aPu;s zJShHy$hW4VvVyI*3FvHvs{fK&Rq`Vt%!ZE$jg3uCK2$%ac>qz*+?(s=G5p-1x{%ve zAxmYmMMY3R5Jn8kFVPx3(AyFiJE^L3DREFeKQvUca$&Pn6B=>XfLc=uNR88iBT2VL z9EP~(5IJ5|HUenw= z^_b{~_qX(*Jk9FYE~H2!hEVEo zPdcs`52o(zw0jGm8hUf4hvz1k9L`8PtlZC#9!e?wFxOrfNXYTXL1(*qKBLk#@};Yv zIvQo{+CpTgRwcVIEtMTa$oJB>>`1dS@MkW%E{%HiFh`xO&HB~`4Jno5FPnIG83CS- zPdKo&Ww09-mQJV+R^7VaNtFnPie;?OuH;&pVQs(Xl7pglR~6u?errtNPW%3PZ`qFM zI+OOU^Y>3Y(pgzy5#8N0E;|x=31^3pd<4;ko6@G|dv~wB!RV~%(p|uY$M~spxeEs* zX;*oM!MKoWk%)y>)%5iI)KJYBIITuns)iq!(MtAoYAQ09#LE{jMG-HXYI?U5zoeXq zvtIfzE|a7(d4z2t0&oF=De|ZbW+Ciy-RW5Ax7jD3?-r7qkMs)So!V?o zV0oI3x49m?-kzW;;iPJ*~Wndo^*le1=)6dE3v z2l}a(A65TG3ifZZ2iDT%@eGD=*uPo9WRWwSeaZL(oG99lD3+cTafQoDD@16q*HCq z<+6%kA^#iJf^Qr7e(6!Aj|*St4?ljL&z@dK=a&2z{NXuOaTto)i8_;1ERi?__6tvy z&-gty*KIBYyMi+$`x*A`adxeI_+YJ)QZ%uFZ};ez>XB4{UK8H}IA$$&N?|{Cs^y?z zJTM{y>wq7gP|e-iH4e?nf<~=Hzgw()Qo-DN$mio-$Je*8FTJyN?2J{VmFNK6NaT!whaI9 zEDAmV7hH(E*AP+f>9HX$vQD$96y5|BS{uAoVL4>@$PiNI>ltlzzE4Mil}8;&y}sD& zxk5|RpM1F6kk((#I$?m#AJa_^HX&kt1~DU3!!2Zhv$*#WL|{ zOY4!5(<;9b7hc|}!i3Y$5zVwb1imyUr3K^sFWh>22myH8&b#uz)-?Q6y{Sjk=*m!~ zqSWxbOa}_ZoYC4)2D{lvYA@I~{HPxB{`my`lze%)*5ABueRAHkytjc7C1LE81+kcz zC_KxT|1=&5&#rmvu`#VYy!;rGaeBL9Zicwi(jq9i3lV* z^{TVQ-zNGkUrIZ^NFP$s{p#JsBK@~L;d<(4bmnCauEOcMz*yR>YBqE2 z@YUjK!pcVko4bqc0}I_=mup}`9W=Q(7oA0IZqpUh(2}(0NPHlo)GIxdnm7Oq_v8+v z&tNUBQLoCn#-D>~UA;M-Wax7f*Y)+ZTL(qTLb-mir3$98dML&!SkT*MS$km#G@8eBl_W zrvpy_(|?$Oeoi7#FR%-TjtT8ZiVln08vUSFezFU%QT313Rg+Xp4uP{Op-zpL%gw2t z+>dy0@auC(tDnxSme81fl7gnr?lkq+o3hbT7>@KK_IJle zj~D7ArG`FHho#%n&|E&}zx2{|{kmTqyPvr1BatALu?y3Ko5+;z6|*(>EVhou8W<>5 zraW&g>&HJB{^PmAKqiU5|JAf;IfAWc3-&H-@AQDJCoE@s##BEn?y@2MJ3ZX}#m7na z@=`EoTT#zpGtej6=f~SWKk8QnJB+YBVkI1DOm@uYEu_O_v5raO2-I1byXl>cot)k; z3j+RMU0(qdN4IT_5AGJ+o!}aRCP>iW4ueZ@*TE&YTd?3hxVyW%y9ams`QE$r-+NQ_ zrmCyD`<%V2dUaLL+I?29eUqb=ALQz2qe88E2EyoWOj0vyN>cmsXVBb@{izk#R;IUC(gmG%w3^VQnjk;R>2mIHlaSy#K^_H{0^Dj_h(gZk zUKFQex1z+3u?mP_EC?2$kF3BYvO2LL{$Yh`wfz2nV2ShK1_GhaGxZNGHbVYRD3fUL zCds;3$9W9pnYKp#W3n{b^1f3psHN7rznfW-hQod;v)YncK>@ni->N7tUVmL*Vrk3J z(Mi|QZFvMK{FmueSybvly8hI=(wl0qxx) zlx5`uSkYUr7zxH=Szoej&$HMr^s4|m)c`|yUxG9p-}u-DwpSMoRXbok@@Hbc*1hO_u7L3m6O-NzE&8_`Q9ZL^yPtkA7&;Xor^QH^X^Ag> z=?ga+N|H8F-oWkK4(TQ$rBd$!BeNg<=5@oiyfIhm@Ph8}e6mFHacNzxEe3@lqkW?j zPtP7M{sal+gdKlFV4T&AJ5BY*Z|i{L)7Tt=`cOsc8)j-x^Z|bVk|B=%-tRt+_vc{L za!$JxI=TvOt~+J#`L>79e}m5_p;b=#4m$n&hW|JCRzO=tV+R{2dqd-4Stj^z%wsIf z-{e%lDjUUn#VQlCDw3np-v{KC2EIz#Lqhy_VrqHeZ&mLZ6PchP5Z@EK0PT&ujlHqF4R2LA zK4g}F2IO1s3hb~W{rwYibV*W3z1UHwb2QMIBlA;ly?2w}TF=m@5c=xV@2z*IEbo3r zT><3dG|s(GCkn{C&im~gZ?In_O{IvV9xib$$IqQGsk{jy&j6hj zB96O(Qg*91!L2C%=62Jof(CAzwNJ9NBDQII9gq6VuVq40oX?aCo?&NzMa#eoD_5-i zb{141OJwpGO&lF^>|vdYrpMZlo26Py=!@CKAfB4Q{Zuj&#~r+6Vc5CH>7{6%b{idN{EJfthlHP-q$nyq(9l9<$F1i`XAVw5GIBFNS{HCG7CKG5Dt&IDTXN1y zFBx+^iLe1|zK9)B%iCeD=o%@kxZ|0+T{6nqUr7wZj3+`D)$v}cAC=AHs6fI)nshZb z+)|#=XGBbohHq=Pd|?0#nDz(g6#E&j#zR#ZOF}*fT=Y*jy7;PF!zUtv?Xx^%$aSu= zfEp4^^_audntb|yh_T{svVAqgSnDxI{y9Ms)bk+&x;l|wbNCe6K2%vS+G(4RmFyUP zNA-OnT{&ZUpG}O^B&j_!LZnl}1!K(W`dRgy0qO<<{})$)gY>4r2GrswnrqC&1Rz_F z9i#tnJZbdP36>JH2~!&U_HAba1cMN^=|XUas9km2b-i zzRGQO*~=3F>@?+Cmii-|3?U3!6ImnZLuDvTTH17=D{<3wYivPTpTJEfPPV1sT?S3q zs>KH=Cfwj#duiA!qM~O}dFf*)X!be%8a6&fvIOXzivAu)pM*6nb@z2qvLs2IIsJ~R zIAT2C712;_vIIR>`6yomqw71(kZq{Kt<(nd)O!r@0qoLXS;7#t?z$D9d)};*7~=`m zWq&7Qv;H`|rN4xfaWVx?9XC~&gsBh&+cu|!oS0;#4;7$7B0#fs%;`EC$5=`coW~AM z2-`<7{Z2q*hwb=hk2enYEJuHB@k~#`mnaeKWhScYB8SKouoyEF+jHW~kCXfo=K1@& z0{!RFRJehC_z;?|=dyo!X8s~in!Kn5CWcx=-=<;a!DsbHv8josYj;!oojB^a7escx zDkJ+h+@zZJ9%_bB>Q=WUdVoMM3Ut@Xn``m8$k0_Z^29!cH3c;e`3F@MaZY8F_^dts zHtE8b?>Ga5_G5M081ou?b9tR!uG5W?ndNyAmE|j)bxhK~+Pg9DSfpHKQ)#ZbI49F& z;`{(B6ai5IO$Aw=xT`T&nvuG@C0V*=GPRTdMV+jsZkC>8~(0?}$t%M6gWMk|!el_EzC zgc0;ZD3%M+OeR#)^&epyCA^)0y33Usms9;KTFp9mu9Vw%% zh*oRfnEk~|q^mQQMAv_DpYaQVgH%Jd(--NCkX!M@NAZbj5E03BP~x{`>KwGHO9+?x zK(Z{JUX?Ft9P1PK==%!6jM1^a%q??6Wqo}PVp z{Y$9AvOiaDkN2ttTn+kitS19_B)wPvs2{v#Un*tCH^XC-XLC6{0@wG9D0ivD5f~pA z?>2B!Us2M%@YR2XHb(p^WRkXH%d>1}pXbYEV$CT{;ouGCoa)YGt>?F zP#`I0zK#U8`W0PduPV_4b#I_1YP&?ZJx;Ge5R4y z8o_fiAi_Z*)M;9HHmkvcPIy!{@t6RtB7obiB7h&@6+^3c?!yzhH~m`&ZjOmS3$$Dt z2r)QWn$1Oh@W6FRaC)PEQ?x)pAY(x&BdvagX&)vUSrW|q0YDG7KtW+F;O3oF=Tot4 zvQ4o0J-M}4SQ7p;W`uuu!d(n21~~N+a7C52_6jy6b{cy=rh-u20p?KW)lt_%S&59y z!ohk5)yi5(Xjy$+csmusR0_uVYQKB_k;4foBHmqCt4lE%m$1u%*J*;+_g%1gxlgfz zQQVjr3R$WOUuNIys|U%=k~l>j-dM1Pr~i3Iou+{Inz3U|(d)$yM*5FZne6QRWhfN# zK2NQ-M<*)@QdEc(kQt#?Mp@8;F%xs9MWBN^yL^wYV+G~pP3%|h;`fa}oxoaT1xx6*8J10=3LLP1jjjwE^>N7D{I$TxemH`9O#I2sPt};{~R1(Q3#>tDf;#o}kr^G~>l7Jwmwp2n~M^P***}QAU5HFc>OMfh; zLWXs}L4_D!$L{dwaVG`#*qH*)b`=aK-gzj9)N#*&Efm4>M}+SfJU^vJ!8YQmYvUUm zkf5|7F+2Oy6rxLR4~=~0^Ouz|x^vp26T=uHYAv;kpCe@SGGyE`BWfBQz;BtXXbaCT zI#KHqgqCOAbAlT2905tbwDDb+)6CiwrQjUhjTHY|!?$_~{afshXsSdPeEDCQRlvpX zS2Zh)_WRvHE%q4XE4$DsRrixI$jQ+`QsHAJV2&#AsR=vyjicNx?iYee{*G7@LHWj9 z;|6w)q}AZeZo#13)WR|H5iGUaw2arQr}^$4&aq00%Gt93_k64P=L6M@~{VhF!NoS;I`z_E^ik`T_d@RZ1`}TvXQdRZpZ2 z{sn9s79s0C_o|(Qpw<9Xt9q$PFl@pzjLOjVr0n_CT_>GT-}Hn3lbr z<)@B-#9Sw0`iXXNDYBC^*eB}phrHBsJ^>}zyCCd8=5fU(F^p5tw*)GXtwyNz!eb}h zhy=jqwyrsi1eWpiz zO84~S2i9Nn;1`(e*FA-bEDi8sKLJVC(u5D?_vIKrEx$OSF}3<8F!-3lS<817Gp$8L zjH3%#T?4}EF5=kCpoU9NVIz}pn$xG3b5zsAo2_Xv`5?bD%Gx^>qyGX~osWIKltYT9 zNLJw!=MNK`!2lO6RHh8~n}%A$s-)(38teE1J|TtzVMZ0ONL>{ixDQv)mESsH8sVe# z-}!IMlQr=VF~x1=tyd895lU<-CBxOPUF8@9)*dD~T?L(Q>K?C7?cH0KpTX;Be_qUq z0=M;-EpZ@yPAFAKoK7|7bzdH)T6hS?_DEZwzfT+CmbO1DMGM|<^ESC7f^w^KBl|tw zkS+>R<1DX|n*O5k(Kh9Ia{$W=o0pjUP14RiEh9(xSk^Wao386Sf7zJGlaoaw%s_{j zd;STvF|Fn;*%OH{dGF}3`q$ov0YakE_4|FY)~2-COD#b?&F7S-c9}X1d$)1ffg802 zhPUNfafq_E>mwbFudn!YAca>>4Bp*a?Vt`5?-rcRcwvDYFA^{8~PEDBDnhIf7*6wS?bK>~iaQc0QlAxm0ZE~ddQ(0IZ7mV})XGwcZ@BeAFeVJNXS70+ z2Kx)dEjFRZ(iv_w)xz}=mHHJ{!J-Tyzgrk~RP_d4^4K`Of2_JW4Wo`fCW@8grC$D9 zg)A+drNh9^jHO<+`TkBP*F9ia22$C9EQ!@TPQ zrT68mbBfe)KJLl*KOen}d{1hVu~QII!D?^-hSSu$4I$(tRbY zDIEhv1)92$FZm490tCZ7G~Mw%19KyoYp8m|SKpY`NgjwI>heQdzo1-KT{N}cj@p6L zXH!Sw-A`|1KY6dBDeD$}t_rDp@udpsi-&`=+Kvw{J{SLKkdnT^nn<;{RdqfD7YpD5 zElQyu8rI!{lL6A#+Qu*zhWf)7{;to^UvZVhV6z+iY{$P?`&c7OfYIe$v{ZZ_vk>|~ zsD?56rrn+7OrPMXo3~;=2iNGQ(U*2n4%q@Jqt(&2lO9~xfBnHzC#m?jNTzU1vObTq zBvN!Yh-M<2uOx-<8>R18jP)CLuP=U{%t7WV=<`h>e_|Ycf$+`{>DbE!CET~=ZG1dQ zTrN6o$YZ$)E379hZgr`JOa%{6S3(xwre`8?HZqnIJ;!iSH78E(_{V8SM(dHeaj?8e zLJ!9I&;)SS^LT)9XZE8j7cFYATGAEukexsqEp5k$gsPJ5M%PnN1oo$_89}M4E-2m0 zn1j^(8=qNX0~B0#lj+{19~=EtBZ8&w*!4jIpT)^p5i8jcftPoC?g199jKs>t67}J! zBYZ|=?B~0^NkrRLAc~n=eH8jiz4}y0=AJsLOhhIJQ?}$sw$cV7OGRzen^CR9v}ew0 zU$%C?IL~<*@?9!p685Ll<0Dq*NwPbs+(`k0YXmTFrlsL8w8pB*$-zOg6X~F@xN*Qu z3<96@`UAH=p}}4$^Ba2x`l0nci9_MfL%z&r(S~3eGA^>|02
rGw;-WuB)1$dc1a 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 literal 31132 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 z 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 zThis is a semi-fictional what-if scenario for Operation Peace Spring, during which Turkish forces that crossed into Syria on an offensive against Kurdish militias were emboldened by early successes to continue pushing further southward. Attempts to broker a ceasefire have failed. Members of Operation Inherent Resolve have gathered at Ramat David Airbase in Israel to launch a counter-offensive. Campaign inversion is available if you wish to play as Turkey.

", + "version": 2, + "miz": "operation_peace_spring.miz", + "performance": 1 +} \ No newline at end of file diff --git a/resources/campaigns/operation_peace_spring.miz b/resources/campaigns/operation_peace_spring.miz new file mode 100644 index 0000000000000000000000000000000000000000..67a01d7e729aea652e140ac5d2a5a1c698d87d73 GIT binary patch literal 51256 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 Date: Sat, 24 Apr 2021 15:33:51 -0700 Subject: [PATCH 071/438] Update pydcs. --- pydcs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydcs b/pydcs index cd14f0a0..84247a9f 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit cd14f0a049d2bfdf1fa2e4e71f6258cd49040819 +Subproject commit 84247a9fa108174b4f9696136a413d53cee23533 From 423925700001340ddfede5a6c33536435374f9c1 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 24 Apr 2021 17:57:27 -0700 Subject: [PATCH 072/438] Add UAE 2015 faction. Same as 2005 but with the Patriot and some additional utilit aircraft. --- resources/factions/uae_2015.json | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 resources/factions/uae_2015.json diff --git a/resources/factions/uae_2015.json b/resources/factions/uae_2015.json new file mode 100644 index 00000000..10bc290c --- /dev/null +++ b/resources/factions/uae_2015.json @@ -0,0 +1,54 @@ +{ + "country": "United Arab Emirates", + "name": "United Arab Emirates 2015", + "authors": "Khopa", + "description": "

UAE army in the 2010s.

", + "aircrafts": [ + "AH_64D", + "C_130", + "C_17A", + "CH_47D", + "F_16C_50", + "M_2000C", + "Mirage_2000_5", + "UH_60A" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "MBT_Leclerc", + "APC_TPz_Fuchs", + "IFV_BMP_3" + ], + "artillery_units": [ + ], + "logistics_units": [ + "Truck_M818_6x6" + ], + "infantry_units": [ + "Infantry_M4", + "Infantry_M249", + "MANPADS_SA_18_Igla_S_Grouse" + ], + "air_defenses": [ + "HawkGenerator", + "PatriotGenerator", + "RapierGenerator" + ], + "ewrs": [ + "PatriotEwrGenerator" + ], + "requirements": {}, + "carrier_names": [ + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "WingLoong_I" +} From 97b73e1a011d2fd6aece1fa3cddae3d898d0d8f0 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 24 Apr 2021 19:37:38 -0700 Subject: [PATCH 073/438] Add a north-to-south PG campaign. Going to use this as the test bed for shipping transfers. --- resources/campaigns/battle_of_abu_dhabi.json | 11 +++++++++++ resources/campaigns/battle_of_abu_dhabi.miz | Bin 0 -> 44865 bytes 2 files changed, 11 insertions(+) create mode 100644 resources/campaigns/battle_of_abu_dhabi.json create mode 100644 resources/campaigns/battle_of_abu_dhabi.miz diff --git a/resources/campaigns/battle_of_abu_dhabi.json b/resources/campaigns/battle_of_abu_dhabi.json new file mode 100644 index 00000000..ac2e4ff7 --- /dev/null +++ b/resources/campaigns/battle_of_abu_dhabi.json @@ -0,0 +1,11 @@ +{ + "name": "Persian Gulf - Battle of Abu Dhabi", + "theater": "Persian Gulf", + "authors": "Colonel Panic", + "recommended_player_faction": "Iran 2015", + "recommended_enemy_faction": "United Arab Emirates 2015", + "description": "

You have managed to establish a foothold at Khasab. Continue pushing south.

", + "miz": "battle_of_abu_dhabi.miz", + "performance": 2, + "version": 2 +} \ No newline at end of file diff --git a/resources/campaigns/battle_of_abu_dhabi.miz b/resources/campaigns/battle_of_abu_dhabi.miz new file mode 100644 index 0000000000000000000000000000000000000000..785ae0fd2d48ffdc09ef4b6d69a33de7ccc34712 GIT binary patch literal 44865 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:>-Or~s;)hGcaXFOQF!0U0L6;84JeL^YXM(ndp8z4SD)oI4%l{lrTiqV_7 zkf085R9|-xx++`i99YNEpy*WLf(>(tY5nX#4i2MDW9Y=ZXH2$S_uGOjPcMx{Chd_! za-So)%>LAud9EMU`-bb!zn|%XSVG2|PU=>ps=afpWvf?l>Yr=-C#eOH3BKhB*&b!& z;bT&<@^y*%+AT_)x!I<-l+mi9jxUH8zNNwxvRrYq*qQakANJz-RvY8H(@wGmVuKa* zKb&Ym`2}r?4`30LG6}8d7M)Dt%&LUd7nJQD7M7*B&))^fiYHS$kPpvhDBT{id;{zDeeF%?~>GF08&^(>^m*_hcVOkaR|v?LhnV}~Vg2gKIL zb();8cNV##lo->=jXADmPbW!Rgy^wPympr{UlG^l#El}F-?gZVMYy{Th9)2x z6A%MeKDtb`D`^(o;LQ5mHhmFWX&Xg|TSefRGY2iw@=T@y%M&481T3!?h%$ua)735Y z*7()lAFPRz|3#FFz9_sy`;NSeMWgFR`Xb})umEVE;x?B=^WMAW4?LS@Jjvfztw0Nx z!U_6#>q&3jB0}GMAW01!qrUih50IIr_Vu7ToSGxW%;oc1WhQH6sc&{YH!T%Ott& z-e$ussp?s12qoq|Aeq)Z-Aru}lE*%Y+Fd4m#ZNaU=!D_SzgoiLp1FekL*p(Dn1*fUe^_dQIXr#CI*-aw*&GjBl6yWk-T@ zO!K$-^WOkFT_(xn3ubivQLsEzkbD*ctq#5DqEsJk$nujA`_JEn6DZrqL3a6CDo8hQ zARlik$RDG$KbIR@r&w*q7hEL`_v!EAX!luWpjYEL311O-UvYIwbcNe7|%`gp&R@t|&dgzqg(W%j^V$tMH_Mo0%>c19Q!E2 zNl31VCds{M*1OiH!LMXaj@@j(OeX?|{7Br# zsQ&8bnv#$Kd2$B~baFDvPQAmfip16BXP*YWed2^+yX`3-yT%0H}ZAjrVo zxFo}Hbe3=H)-o@azSy^Jtg2N-3PZz>h9MGl2tpJI_y#<-TV92hOAC;ph-Dm{%A)?im`|8iq{N%IQD0!nJ?XiQW&!MgQ#+ z5SlE)9GAgNakJ|20YFD7f)474mHZ{&c6@uou2OscM@1sTE3_cdazmr8-zzxF?!<%A zEm`t9$>ALz+pOs_Ll8y5L|dMl-${aeO)#Qq5s9UTsZ2 zM*VW1fRkpT$j*3A5od`5@7L7)bM8P~pZy_2F@--HgIpYEv4>-s#jh-h+WAR*ekFDH z?+(4(ej~ze|1(}y4W{b(mA!pwGgj*N@$-Wt_W7^5-R|APDty(2_1>jjKedISOiS&;xUSiBmy5d>#I9ZL zu0qG2j+&kkAj-aWV_Ous;P{}u(!kD8ainXF)0?ezW8&SCm^o7h?r97=dfB`mD4c}E zc9HPkg#=;Xr>dOAKQEx8rh3vzV6zRHkJwXGX9trJ*{fwnW7I!5heq|ps-^?B%=S;6 zPfzAJ$C%5a&+%b)$GdBbYjb(~`Vi~=i3kl3ngWBim69`)qpw!*cwV2@)+*4&ZzWZE z$eXpfR#!bSA7Oc|+3=kY zWXAR)LA<~=P@d|2Y)N&WNyULESre-v2ebk3tu~Er^FM;vV zUrqe;9K-XNnJjra?pTujTP(8~kGM+=l};vlJC zX)d@zCAUG~56j~aorEo_~nYdN#j2|+rIC!pxGQPc5E|#bJ ztm)ljw?j!UaXZTq49QQw=W0EI^C>qJajVC+O9zgAOY{hy{4lGxxMB3xO-``-yIK8F zqq}nAg-I||%lC5Q7Z8^gCPp%i`|6D!l!@CIoY`9fA8o$oL4d8m-O_hF?|77Rdk!rx z{$%^b(2m@4naHg;F4gn@109HQ;hy!n{l1Jr#Xb(BxAxhPY=Khxc;7AM&)kpi1Y`aK z7B3_<7>Jq!>Bz7<1#wowH(_ECA^lhs5Tcv+kdUf{eoOrO)9-jbFD*n`2VdLUel5{@ za1UR4$o>0XGwY+r!!;knc!Qamzc>8@cs`we6}n1Y(i00ALs|>TBnG82S!6Ufh;Jc5 zSuq^*a7%mu&pTMg5jWG_%JwTn>*hVb4_0C?PhKS4P2J#JPy~>+{)4jVJDw4L0!)Z6 zJ@gywvsZU#Z)p`>M47ZVk@1U5nBaa~?M@hM@meyw##OzcoX<#$4Nuwk<6oq9Sv|bL z!R%K=T$0i|6V61O63VY4pidSz#(GaxP$=#p#^$)>z*8iYXT5lR%1R7OlW{!Ezohg* z>P_h(@8P`xR*lC~MlglSz5xQtTmL}s7R+BhU7(c~Q-P(Rm`25>AqF7T?ffU2>ivUq8MZ_38Zca<=B;i`qtqwCcVA z0fBq}KUK$=ww*9E4Euc~y5YD6fGAtA*s}Xsz!51Hnm;Svm-mRmP?J^yFLTZ&=3JI*Y3N z=lCJPN_vn%?w(xgM@HpAxqFwy#y&=uAM%Fp{bG%K{KZ3lZj~YO>EOTc_UWJ;H(PSL zh;Y&{nJ}ZZX$)NM&Flk-t4ZZ_Ry+*17<#|QG8HgHIv~wT{$6m7m6*-R>je4K9}PJ% zLh?la5_#VWAd^APx~MhhB@!mQPA$u!rJ~FG-US zsj+72qO<7H*Plz;W#+1x*Va+*k6V-NRU}JtS_kurjlmkix{j6 zRi;fL*2pr3HP#T7Zd#@!<)du2ry6Iyc>pV7OUC}BQKu_0{6Asf3PuaO8EMn z<3C`WV3eqPT8CF;n6=1DZ(a$1q5&!4px(S&LHNMSx2rs26;!8|;cW7P3<*j0Ae1=? z3p<-WZgskK46Z&RW_r<2=~P|+@ZMAx@0x@T@WoT@BA==lS-uS3eGXNxpOMbiqRWj| zOL4=VN+Te!|b--d#RKty)I zhQ{15&ToB;KWJ|OSgv8`N4)?p0&BUOy_VymbQ)4sy%8a9R`lv8Hxp8lh8x_roff4p zks5>Atw2%+#Bnbl^#XkD;DqOxnV~ca!HKF^h0<#V?)20(TK#& zAkiXFBhiPsnIw}XCpGutTK7coAq-V3H3?;RP$p;Z4r|!kvZ=HrHA@iP)%0$GUd1=f z5wwfHAyS2I*qx5tz4{jRw5|0`|G*P|q#Dd$@6JB{%e`XYHTQt=*S87n#{R=Sp=@RZ zww`+Y2R=^jHYui01j+s(|ES5&&FvNF2|z;k%p&X`GSRQde8l;q!A;(2k@1?2w}bU1 z3+y(KlCm~&YY@-UyfQ1(%goq-|A`YFuoZwzy8m=28So zFVx^f-$z3NkIKG;pavg{yUKj|Vo*lKfK+U9S;v$N7w3P#xX9Q72p8`aD675xK^bf@ z1zh1u8=6Flw0oDBjRxEiH?m+CKP24EbGc(JnDWA~TuPn|lnx(H`^cx8L>N{7^dE>1 zO+w#B+vcfumA|*0rHrupk;NdBYbqJv6oaZ&8U#L*AtC8mKAEL0H$O!<*zvWv*CL~O zPuXtRcsRq=_gAx)Rv0B4o;KhWfl4JBJ`cebzkn;;?bSGeYhIcxo_)ePZO$T<2G>iJ z2P2BX?pD8s6?ek$NXp7{Ay4)QKOa3K0*60y_3|3ZcfCXJcyaSP*LN=CQDqY?RbwqK zYYhvL_s=?iZQ}|mm7dS;D0COSf82o?ziK;IQHd9g^9G}$l7PYKwL-w4NZE;y1ql~h zGD**bXjzd_IO_KkPj3~$95VxAW{%iWA**lW9&w!-#+&5BI4?0O{Pl!CXkh{$vXX_6 z4_f>FldjOhCM{oUVS9B%jX&+ITBI&pggTGofKgMaS_HSXMKxe~7dc(s#9c@1G_z9Z z*qI~M?ACvG2InS~izx2=ult(L0iFIA$27QMH739TYSDT? z0ns~aQwL=;o({SBKrgkgQ>p%k({hzzZ71kRE>Vl9e63M{$rJ&rR`FGNVjv^$>OYFN z3QfFTXckvQj*av1?YI_JB;5ZJpm8sL!;g0irJxG9`e#EV@xel^RsTn{9+h8?To#Uv zow@bm7}Q9wwhimP+VqHR0}d!MRat9Yee!imVUs3`weT}_nLKvZLBV2D0ln4&bYPQ( z*s^5LYpV81YbXs!4KOcYvI&+(DfQ4YnAnt{=>X3#jB@z$q5^|#l+kk0lKioW_Ej4a zXcmeFg^7Ad9y|L-53^h$GxPgwNFh>LEf^ngGnh(}ra?;4Y}(br(0zf zz`%*PCzV$-pd!L{E~pYm@0stH1c5Je8l*{ZkqQT@3L!PN&m@3lQ7t#mfc-xi`zsE# z?-%F0)u;79$(h0ic29oeBF#>`aDSK;9T-OW=>#f=3wW;B4P~7 zd6&RxHUDi*i6FKKL_UG>4=mI;V<fPNAL$}w?CYTb-P z_9dyvx-I_^C!z{N0FU}E_=ezIgwLE*7aAvVa((A`Ss=Lbe^dh&ApamBm*1VJM=#*v z?7HG#Dme*xv!CJ?_J0(EK>^C+X4bZqStOjJ4(y9ZZQQSe0SW#GEO$7saw2Vz>i`o{ zSJRBmb>##|{VT43>Z*m-XD@a2!LZ$J23A`3G1-CzYgJlbhrbNCzIZp=$OzQZ|3T8y zbP%G%$2{bK2ySKdl2TZPceg19yILW;*iw>!vgO}8P{s-rNCn+ar_`_OJNMspbaEb# z3F{1&nid3#ivJ*K2~naVOM>*4`p;#s_LNzkT?x{a+(_du0nZ83je%UNo(Do3#zjK6 zz)#*%n@1@O;DGLl$s9wu&K%;wwdvZMS5?SmEwW>POwLKoBN1cuHaSRQ6-RKz8_Msn zJH6`~#&s$N+{!&njq?)bo^y(J5WhA5WhTNhg3Fv4$ymwFx*_ZD?)MjtnLm>u{&m-& zAylT}1(Hzp{T84mKv$&7;n{Bi%v2{2M0ian`G{iafUao<!5 zWbIvhhcLT>!*-PsQD7g8c^#VXugmeHG`zep4=Szk>$c>KT z=`Bc+R`}eLNeCN;G}HLMmfxG+=<$ zKAKabAz9bM2Lb~*e`Z1@eRx|JeGEGrSE!NR=3ItKahN%xK-|~8ak}F z=p|!ms%AC3l&omyUN#|$49T+;4=PTI$Y_qX^z44+c5A*Z zSaL{w0IqU-#6`i_+hdwfR&?yjxFNU>DrQ-M;H*wTwuj31nXIKC5n-64k6nvzfHA=q zrfo`6$Ecwx?cl}JGM|fWpzgA^$jU$Qbw(TwBz{hD^}zwHTeO893cB{ix;6~m(hvrh zy!x@X%^Ie0$b0|B2;%g>FQ>WF{~#toB)NjSEH*A(sqgDiKc#L6qz$GjOH`;VSk#yj z9e{!C>Zy2d)PM!__@!mC3q~*Ym67L0wLHlQP8CTvAvSV&EqdKL1PR1AcYD5NAz z7XGyFDa(pssf}kK0$F3^qiOo(7kFv+B)fN%OsB~HN424&E2?bU#DWwuT9eJJJY%7l z5sDSjQf{`{cbIhjQW&WHL-B{ghhbFKDf8Z`xPsvrYm^#)1M#B!W6)`(Cj}J7>U(rr zUcB5L##1)=_-2A`Yo%yfs0F?w&cHjsfPv&@=?SDZTBimb7mS{MpwMB<^4X6I}$Bl7D^>yrBXvF%A*ET{jF9v2i9_}5vtuED8?r+S`ymr|%279Gi9~fAI ze#&m|^&XG+F8@*OSU+68!}Z>4@Lom%O;T!~*rr7%Iq}WCrRC9r^^K01ZsuFum_~Ji z{FwX~e;yYW?-~XyWpnq@=Voui!_2Z79=I&QN^FB~*`$Ec7c`&_79l-FHY?8H1=A37U88+yj=rp)P) zX6It-AS6AbKN=I;>1KI)v^TTRoJPI4SWLs)HvD94AlAh7{P=8V@AvE>eidhNvDg%~ z_n#xn!yD=cA0_dWt(KNBmlsA4@HcP&TwCjRC=bZC5+UfnCpHEx>#rK~0U3aPsuU(_L{ouJ^2vSO3TpO;+TJWens<$s`9qAp;th4zys!Goey8S%j}MlrU0-pZ zUEix>kE7d3o{RUt$jgtBnKv!>uHlM`Gj74Rj^8y-jth@ZTr?}&sK2*!sab;8oSgrD zv%0XnIyQO!EsB{&;N7TwlpWRe{!($59h1 z8s|F?Jcc|=&!-2OJuXffFHU#P%KI%(S7mP>zv*}K6fHa%E1;j4c%M9TIk>y*k{DD6XuVnYW+dlRbnM8$?0QV*mKUyeV~c5}DBm~pktj>_(Jd9jTGJdFQJ zFlEN#!n9S9r)%@#&wja_)Nk|Vb4#B$nJOHf(*!m1;H0I-rO7Ualh0p8vomWSen_=c+(QlUX zZli}WPWhD5I#xTDmx)wRC5OoLTb6qmP4%t)9-VGJ7@gj@NSRn1jD7V)`Daz*`#`td zS3VA%XW-p}gL9WZo9mt9BQ!2{$$y-^9fybdF3O^*Y&I{BQF=K0ihYuGVg;>VJN3cz z&le4%Gx?4C_H(;;<94FsJO@mR#;vKkJj%+P%B@0nHct2Ew-)}44it=e=!X#XkY)1z zS+URM#+vC{z-@Vft1sQV<0j(KUma@C&$v-=V1%3!0 z?U3PDMX5N;*47|IQT3-w_-y{x$HOsgALVtDkEeWM%k9S_n`GKc8mr$-3kp=`8mA`y zc(+(?Z>BYtoV%7kBEk#VoTgT>BXan}7qDmhpDw z^!dS1KP6rdX)Va@u}QJ^`riDF5aGlUZPv8+l=+j#uHL(+$0MhWM(?Ml-~T+D8j5ML zq!EUHX<53VcFeRqx*h-X{*(~N)%@4I+0t(#4G+y<9|v|Bd6|+mrk?bF*)Z~^x)5(F zb!Pp!S2#VIK|7pDR<$XnM0~M02}-E0pB5FJ`ZMFM0HPMNEkR#YbkXKudeq!P#&hef~B;`->uAE}>9`Y+ry7hS68#}5k_aA|~eMyue* zD_M9AcDiDx-%-0CKYyho#^P|`oaT|gz;~L{{IX`ssSG@3b2zjQ$v-VFE6hpi@bRe? zxq#16>n-HrS7d96P-^nt@ew-ul9w7Uf)>IeJd28QB~R-bsE zmzoOfzQY<)l*{Dx4x?E;&aH2_Qy*WY4g6x&(T#!b>_Qr*0|9SaC) zFS|`u6%HbqkNCDEhL)nU*6Ky-H+S0lvU(~`kF6HByNd>cU-t8i)x%2agFAvV`cgKZ zj}(nwoOn7rdDx#Grj`Aq;e7PlTi^PZB-Z4Z-kRsg{azKulMd4`k;nl+g*Kt2wW9nI zA$71ve16NhkPPc+c`HlBpK-~1s~>QwbarmL_fs^;(!+m?uqHg7w6kOuXO>~dw*_r8 zoJ?~u4;;!k5_%PvTKtN(O!CP%)F|8flO3F|d-Q!Ezs1O9!NQ~XwBiW(HLBp#K-(dAVjK>sp^>8hBd?MJBW_PF&2>L*8C zF;dmnKN>u^oB5$M6Cys3zMV!B)!j84ompvf8RWd#fB0c9y+_JvEUvv@OfkeR?|0o| zYQq^Ef8TvABv|x7l987!DmqW6WY)D)lfEua^nPvK>!)v)wqArs&lr>`cI7A)s|^e# zCfR6AJ;WdSQQ-c#_GJ=P~WlP~$~ba3i;+jajH*u$v|vwtaX zGwL9Z&rYP$?fsoR2h3efHcKv}k@wB)BF0ZV>>Y`2EBrj1>(t+kt1CEL95-CR^~#uy zY|a(YscAaKY@ibv%dFYBI67Y)Dy+(L*<4Z)?CU;(=71cG`2Bs3OK1z|Ij6DzoCQst z{@6KLoICCvI6j?>_{q`d;bmX5w;SRWQsXJNShaodb8TZ&$vA@(=rEo1$(<@}S=GLn z_~|aZd^*;+H*-wF6z5@T_H{}zpXkt&9v$C@9zE&DO>Q>*XKzRQ{`?u;Te0=DI*L8d zm@bZ;q=0EUcyt%VrdiyTb=PX%{tXmK!m?%S7h%0>j_XDBs;4>0u&AFKeXI$}#|rDr zQ^~7qKCc?Qge%^QR349OzYaHQ+&PtEcGoBo%kS&k=!^epi=pg(W8R9N)p_kxbxegz z{k^W~h7Y5s_L14oD8~fy1&X#8jbyPjN{Y?b4Lsx&goXLZu`W)H?3|gLJ^D=3mTI9@ z6&EjEE_#=zZ6trY4p)_HK6YAv_IYKeY>aE;l|b4`U58_&(YJ4Isdoj%y1$V*G%gp) zji`4TzCM``@pk*B zJssE;SA(4}3H<49q?29C({&vas&)1@# zDGg%t`v?06QpLJ#))#Se_ciMOS7TQh6i3so7k3SWpo;}baEAa{f;)=_TY|eTwm1P6 zOK>MhfM5xb1osd;xJz)?Ko(duaQC(EyK@xIK2RvB z2&+(L*t?xSfEB%2v=Cc#HB~-&AFHI}Dqm#Z$2_hZ*k`W)-ujCmaVqfXELr3pn&HNA z#o-~I#f9nn^ju`tT4r6jhVId>&WI@S#4*bBz2r~DFIFLj@sT#IC6q{?r2~yi84ml~ z0ENbr^+kO|7orcfT;2cH%p933iL2|q)=!17N)qO86S>}vt*d^W+23mGzq~X4Ofz}O z`uPrMP9A)3aU;FNtOZC+!tT!PY4~J@A#8kj__`9PQDOjIS1mN)nrM9bYOD(rI9ls4jTe8@0=e!JE{t>*Q^Rwj6^#_SGB;d-^J@9Pk4o6EB1Cs)P zj*ANb02l$p!w@7Ch0ah$g)%38J{Mj&c?B6gHCe^mNs5o!EDhsESf-{ClPwexH$o8<4VsOB z|Bh2!(YTTY6(1+c9{e7sv$>nLHPqG1!_s;{oeNimdzhD7MMLk;NXzv`r}UJDRKb0k zI(_m=9%#P_D<-V@o?!q0l8XQUK1$ff+{4<=)eCA3JzP|Eod;3CSEbs~Ke15?nxMz> zl&ZW_v41Q-^e85Q7S5My+1w`NTzGR%uGB{@JFf2#$9(#^%ciGuGjU|l7D~D4lBgf? zIIAlA^}Yi1Qp{OidStwX%gDBVWJ$Vy>GQmUh$ftNnzuD7MhF@0h~@8m)VOpVxHjOE z8f)O6tx-aM0G)7Qpc66`a4bH_-!;n9BfnNPYnqF6n9J2nJ_}8UZO+%4@|reAB?;w1 zu7K95PkYT>e85lOnwbNI-rSPc(N6A?m) z^y9CXC~wohFLv8IigV=+-!$?DS9%FS?wZ5AL z2e?n{!35RwbK z)d|Xr(=Rvy5Dw|yWKpLiSEaiw z&=L5G;FVTTM?Aw*flH?-=}wYOD)cgZ){_x9KnR_Q^bdS*6ps537953>{)2T!;Sc`7 z&`~(!KR9_bjrA|QgaL-XQo>Tn0}eiY5er8$zM_QAX?KCqfbss$I;(=e!&p zxO{)ATi}V{tMH|*qTz-Bls;h+q^~v>3IWha0i5WQcmNAZ!awO~_y!#fg;IaLw?Yre z2*+kbbs`_~RoRY50qZa9lz?GD{#SQUkOz6wj4d%r6gT7)uonphnY_aPyK_;F4lxW= z?C6Z$WelfR{-9f{pqTPtY}<889Jbi! z#G_8qM6o7P6YQk;d*nOU8$@zZZOr)0*~}CQ4CZ*X1ix?tzk&U6CD35~+4lRK7vCCRJTdAxChe5n(_4_^ z6TK?%I9SK4rlRW}6`dxJUM+(F*((kDaC3@t5zJmG8O_DiUnjiGtdK7wi#v8 zt_8dNkDz|moi4IOJF%`^T%0(|So5(L>ssN;G0t#0oobQ!SjivHQ!L|IZ^MjUbH@@5 zjFJs7)uerUpjl&mh}+5DNLj@sH64HD7Ju{dZ7RCDbWss_tGhIw2dy|o>xGU-QodVs z1Np=&76tT=3+kYIbGZRHXe2Pnj2@w+d)pm0w6|vCHTIoD>=a!uLkRJu5ktqF-0B# z?^iV@VO~N$L#SL=RF{yK={tslf!SXxC#5TOe4d}JHd$zdOPI}x%dF`*Bw@2@lTXlM zm>FgYskfcx`Sd;+sf;bGvPJzA0uG5$l)agf_p)^2 zZ>3{#x*Vx{p=ak`xzI>NeTQPvh8N|yG`{xdWTh2OS)&dUNDZeX!C{ifMiVbk9$qFu z{^)6Qh3p*}L9?7}g|#kOH3U!d853SVlN;e?JGe?P#G|m$a982PSC`w!mppf)8H`<{ z5rc}|r?`RBr%mF9t!ihR`LeK=8T+S%HXSePPH zw0)EJb*os~E1rF zFH)OPMwf_ZY$1f-ox>$1u$6Qbgu@?#u;j$M+6MjhWCiV>SUxd;S5NsGiuA5s6w@7g zbe7+o^gGn&02~Wu99h=exW3yZHiPfZZeW%?+XU|q1RCS7uPi2N_yU=dO_mKE*NUV} zURuT9d9!Et*kve8QK=uteO~>B&+h1K`>QEi1a-Vp$3#`AZLJA!1-8?*wv$<%YGBj+ z-h8<|0$ePi$ClmYa=I9Ej9a~C&Wt*Uk7uK-6#dBQ9reUd4?#`z^}<29EG|3W2(8pI z#R4gph8F@;Z0*jXoP>F)ZEBI~JOX{iTGnE9Vzb4!jP)mF(f4z zm|N1Avu8B0RYXH&kF`FEY|<)u~XUo<84nnQae#(BGB` z!X~mZi%TJ{K;{LdAeSeON}R~$A0@r0xXz$;wPI#aY!LR$vFWAc9L{Frm?)*IOr`Fgzz9GL)Szd&%}KR+$DEgVgVk z2(hIa&zXVOctSwnR}hUpX8(W*g@F84QV+-S$YT1#PTF?QD$}@LKCZ-D*ED_>i(W!gXpDm8*(ICm| zw^u-&ydB}5D(k@vy=Ti#Y_b{Z{g&tSbo8JpSm0_O5RD-dOKRp}l&tQT_$KnRumyz1 zaudcb&l))v%Re9@XN*vnO$>S9CO4XpY~noz)2)eTt2GWEl>26zW9V9>ar@MQc{@ zN4J1`!T5!2mJB$amtgs7*P4<*{qE5=vc(YHx%Q1~ak5RpkhcYwt^Nsldz=r&+=aG= zZeT^xoA=Bf-!Ad}3)6@X9@B9Sy*EbrJ0d{Q#g8~8+*Mab@<#SbMfiMRPbU`vOX29s#lGR<~mbcENY zH-zQ*2aMDa?LO{vz{8f|W3fG-&b85g^U<~e32|AkNqK@Y5^o|f{mz2unX-Fs6HjsB zd0x{j!~)x+TzgitGazW-%GUGgkh;3C&f&$IUt?SJ{aOM1S{r65qRo#3lc#Al{QbW3 zV27~}i_U$i+$SFY_~pIiNq9n$%iyd@eYhvdJDg{)zxB%v*3%SVYxRmbG?s->n|th( ztfEB{sUuID8$4-DQ2VQuxjZ2WF1ZsX?ex~^<)t|VmdZlkbh4RMAsLQrhwLu-Jk>BW zw{92|oshn~@j1Tk&5MY0zcQleQBe}6Wvi$#dImMRHvvKlxeaa%6Kwp%?xoAQAR(t; z?2f$o{8>mN{0rUG_fP0{AvP@=3qR{j`2W zn{1BN=A|#Pn@;lP6v=CD6y9RJe?Bl-Q;hwbapwd0f$D2t5n7Ca=arjnDIP}d=M?86 zUCl)q{pakp{27kZHK}=*@XSL)D_f(ql+4;UfQqN-g4ZIb|G`hPLoZK?Blci8gNrU> zk1}}tr&APEesalxnk=3C_v{{an>y&MPsvnY@uX7{u3h`7nLWI{dwG6}sObn%%R=++ z@9uX!4q#r`+4S2#kWrqe(ZP(@w$XWDE_Vi}Qs>O*W(mFOB&S>ZzW90`uJ-CjxVUhB zs_}A@|9P9kTQ!4>HtlDx+Q)VxLlOZh0m71mM&64~{+Lj)&qgu? z8GD_l?VPr>q-WBYPQesd;yMa}(*X zTu9~@uqV}^>EJ5X;^U{u6R<;c9oS9X7}gQQcXd*k+jzjA?OVnE1!pd-{%iHCMh=V& z(N?M64;<^-Tko1Un0T~p5GzD2uvtZrBrEYT$z1kC_Cr=%=gzh2e2zd@J}uUZ)sKXE zMj=&Ik{yw46FNWga>W8FzlR}{zthge3Frb3aK!w$zWVe*vXvmQMLZ`={#&VrPTiph z_(S#G{%R>0mZEB)>aPm;DZSSvzMV4SJ;-Z7g$l7cvQV{qFOB35>%X(rFFTnuM5rv4 z0A+tZmvq0AOvR`jdmA0nq+< z{CPRsL!qdI_jl6oMZ-VZ{zV!wGu&FLSlE9!uu-=KYE=4BE&$+v0MQZRssI20 literal 0 HcmV?d00001 From 5e67ce0ab253bf32c95e99a29d415a8316802b63 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 24 Apr 2021 21:32:37 -0700 Subject: [PATCH 074/438] Add shipping lane support to campaign files. These don't actually do anything yet, this is just the campaign support and UI. https://github.com/Khopa/dcs_liberation/issues/826 --- game/theater/conflicttheater.py | 60 ++++++++++++++------- game/theater/controlpoint.py | 4 ++ game/utils.py | 11 ++++ game/version.py | 7 ++- qt_ui/widgets/map/QLiberationMap.py | 63 ++++++++++++++-------- qt_ui/widgets/map/ShippingLaneSegment.py | 68 ++++++++++++++++++++++++ 6 files changed, 173 insertions(+), 40 deletions(-) create mode 100644 qt_ui/widgets/map/ShippingLaneSegment.py diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 70887541..8d59f982 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -5,12 +5,8 @@ import json import logging from dataclasses import dataclass from functools import cached_property -from itertools import tee from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, cast - -from shapely import geometry -from shapely import ops +from typing import Any, Dict, Iterator, List, Optional, Tuple, Union, cast from dcs import Mission from dcs.countries import ( @@ -21,9 +17,10 @@ from dcs.country import Country from dcs.mapping import Point from dcs.planes import F_15C from dcs.ships import ( + Bulker_Handy_Wind, CVN_74_John_C__Stennis, - LHA_1_Tarawa, DDG_Arleigh_Burke_IIa, + LHA_1_Tarawa, ) from dcs.statics import Fortification from dcs.terrain import ( @@ -43,20 +40,21 @@ from dcs.unitgroup import ( VehicleGroup, ) from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed +from shapely import geometry, ops from gen.flights.flight import FlightType from .controlpoint import ( Airfield, Carrier, ControlPoint, + Fob, Lha, MissionTarget, OffMapSpawn, - Fob, ) from .landmap import Landmap, load_landmap, poly_contains from ..point_with_heading import PointWithHeading -from ..utils import Distance, meters, nautical_miles +from ..utils import Distance, meters, nautical_miles, pairwise Numeric = Union[int, float] @@ -73,16 +71,6 @@ IMPORTANCE_HIGH = 1.4 FRONTLINE_MIN_CP_DISTANCE = 5000 -def pairwise(iterable): - """ - itertools recipe - s -> (s0,s1), (s1,s2), (s2, s3), ... - """ - a, b = tee(iterable) - next(b, None) - return zip(a, b) - - class MizCampaignLoader: BLUE_COUNTRY = CombinedJointTaskForcesBlue() RED_COUNTRY = CombinedJointTaskForcesRed() @@ -92,6 +80,7 @@ class MizCampaignLoader: CV_UNIT_TYPE = CVN_74_John_C__Stennis.id LHA_UNIT_TYPE = LHA_1_Tarawa.id FRONT_LINE_UNIT_TYPE = Armor.APC_M113.id + SHIPPING_LANE_UNIT_TYPE = Bulker_Handy_Wind.id FOB_UNIT_TYPE = Unarmed.Truck_SKP_11_Mobile_ATC.id FARP_HELIPAD = "SINGLE_HELIPAD" @@ -315,6 +304,12 @@ class MizCampaignLoader: if group.units[0].type == self.FRONT_LINE_UNIT_TYPE: yield group + @property + def shipping_lane_groups(self) -> Iterator[ShipGroup]: + for group in self.country(blue=True).ship_group: + if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE: + yield group + @cached_property def front_lines(self) -> Dict[str, ComplexFrontLine]: # Dict of front line ID to a front line. @@ -351,6 +346,28 @@ class MizCampaignLoader: ) return front_lines + def add_shipping_lanes(self) -> None: + for group in self.shipping_lane_groups: + # The unit will have its first waypoint at the source CP and the final + # waypoint at the destination CP. Each waypoint defines the path of the + # cargo ship. + waypoints = [p.position for p in group.points] + origin = self.theater.closest_control_point(waypoints[0]) + if origin is None: + raise RuntimeError( + f"No control point near the first waypoint of {group.name}" + ) + destination = self.theater.closest_control_point(waypoints[-1]) + if destination is None: + raise RuntimeError( + f"No control point near the final waypoint of {group.name}" + ) + + self.control_points[origin.id].create_shipping_lane(destination, waypoints) + self.control_points[destination.id].create_shipping_lane( + origin, list(reversed(waypoints)) + ) + def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]: closest = self.theater.closest_control_point(group.position) distance = meters(closest.position.distance_to_point(group.position)) @@ -446,6 +463,7 @@ class MizCampaignLoader: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) self.add_preset_locations() + self.add_shipping_lanes() self.theater.set_frontline_data(self.front_lines) @@ -877,6 +895,12 @@ class FrontLine(MissionTarget): """ return self.point_from_a(self._position_distance) + @property + def points(self) -> Iterator[Point]: + yield self.segments[0].point_a + for segment in self.segments: + yield segment.point_b + @property def control_points(self) -> Tuple[ControlPoint, ControlPoint]: """Returns a tuple of the two control points.""" diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index a6777663..b3a67d3b 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -267,6 +267,7 @@ class ControlPoint(MissionTarget, ABC): # TODO: Should be Airbase specific. self.has_frontline = has_frontline self.connected_points: List[ControlPoint] = [] + self.shipping_lanes: Dict[ControlPoint, List[Point]] = {} self.convoy_spawns: Dict[ControlPoint, Point] = {} self.base: Base = Base() self.cptype = cptype @@ -397,6 +398,9 @@ class ControlPoint(MissionTarget, ABC): self.convoy_spawns[to] = convoy_location self.stances[to.id] = CombatStance.DEFENSIVE + def create_shipping_lane(self, to: ControlPoint, waypoints: List[Point]) -> None: + self.shipping_lanes[to] = waypoints + @abstractmethod def runway_is_operational(self) -> bool: """ diff --git a/game/utils.py b/game/utils.py index 85fa85da..a35a41cd 100644 --- a/game/utils.py +++ b/game/utils.py @@ -1,5 +1,6 @@ from __future__ import annotations +import itertools import math from dataclasses import dataclass from typing import Union @@ -178,3 +179,13 @@ def mach(value: float, altitude: Distance) -> Speed: SPEED_OF_SOUND_AT_SEA_LEVEL = knots(661.5) + + +def pairwise(iterable): + """ + itertools recipe + s -> (s0,s1), (s1,s2), (s2, s3), ... + """ + a, b = itertools.tee(iterable) + next(b, None) + return zip(a, b) diff --git a/game/version.py b/game/version.py index 5827b455..b48f3056 100644 --- a/game/version.py +++ b/game/version.py @@ -39,4 +39,9 @@ VERSION = _build_version_string() #: * Factories (Workshop_A) define factory objectives. Only control points with #: factories will be able to recruit ground units, so they should exist in sufficient #: number and be protected by IADS. -CAMPAIGN_FORMAT_VERSION = 2 +#: +#: Version 3 +#: * Bulker Handy Winds define shipping lanes. They should be placed in port areas that +#: are navigable by ships and have a route to another port area. DCS ships *will not* +#: avoid driving into islands, so ensure that their waypoints plot a navigable route. +CAMPAIGN_FORMAT_VERSION = 3 diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index b01504e1..d5e2ec2a 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -45,7 +45,7 @@ from game.theater.theatergroundobject import ( TheaterGroundObject, ) from game.transfers import Convoy -from game.utils import Distance, meters, nautical_miles +from game.utils import Distance, meters, nautical_miles, pairwise from game.weather import TimeOfDay from gen import Conflict, Package from gen.flights.flight import ( @@ -67,12 +67,16 @@ from qt_ui.widgets.map.QFrontLine import QFrontLine from qt_ui.widgets.map.QLiberationScene import QLiberationScene from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject +from qt_ui.widgets.map.ShippingLaneSegment import ShippingLaneSegment from qt_ui.widgets.map.SupplyRouteSegment import SupplyRouteSegment from qt_ui.windows.GameUpdateSignal import GameUpdateSignal MAX_SHIP_DISTANCE = nautical_miles(80) +MapPoint = Tuple[float, float] + + def binomial(i: int, n: int) -> float: """Binomial coefficient""" return math.factorial(n) / float(math.factorial(i) * math.factorial(n - i)) @@ -821,47 +825,64 @@ class QLiberationMap(QGraphicsView): ) ) + def bezier_points( + self, points: Iterable[Point] + ) -> Iterator[Tuple[MapPoint, MapPoint]]: + # Thanks to Alquimista for sharing a python implementation of the bezier + # algorithm this is adapted from. + # https://gist.github.com/Alquimista/1274149#file-bezdraw-py + bezier_fixed_points = [] + for a, b in pairwise(points): + bezier_fixed_points.append(self._transform_point(a)) + bezier_fixed_points.append(self._transform_point(b)) + + old_point = bezier_fixed_points[0] + for point in bezier_curve_range( + int(len(bezier_fixed_points) * 2), bezier_fixed_points + ): + yield old_point, point + old_point = point + def draw_bezier_frontline( self, scene: QGraphicsScene, frontline: FrontLine, convoys: List[Convoy], ) -> None: - """ - Thanks to Alquimista for sharing a python implementation of the bezier algorithm this is adapted from. - https://gist.github.com/Alquimista/1274149#file-bezdraw-py - """ - bezier_fixed_points = [] - for segment in frontline.segments: - bezier_fixed_points.append(self._transform_point(segment.point_a)) - bezier_fixed_points.append(self._transform_point(segment.point_b)) - - old_point = bezier_fixed_points[0] - for point in bezier_curve_range( - int(len(bezier_fixed_points) * 2), bezier_fixed_points - ): + for a, b in self.bezier_points(frontline.points): scene.addItem( SupplyRouteSegment( - old_point[0], - old_point[1], - point[0], - point[1], + a[0], + a[1], + b[0], + b[1], frontline.control_point_a, frontline.control_point_b, convoys, ) ) - old_point = point def draw_supply_routes(self) -> None: + if not DisplayOptions.lines: + return + seen = set() for cp in self.game.theater.controlpoints: seen.add(cp) for connected in cp.connected_points: if connected in seen: continue - if DisplayOptions.lines: - self.draw_supply_route_between(cp, connected) + self.draw_supply_route_between(cp, connected) + for destination, shipping_lane in cp.shipping_lanes.items(): + if destination in seen: + continue + if cp.is_friendly(destination.captured): + self.draw_shipping_lane_between(cp, destination) + + def draw_shipping_lane_between(self, a: ControlPoint, b: ControlPoint) -> None: + scene = self.scene() + for pa, pb in self.bezier_points(a.shipping_lanes[b]): + scene.addItem(ShippingLaneSegment(pa[0], pa[1], pb[0], pb[1], a, b)) def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None: scene = self.scene() diff --git a/qt_ui/widgets/map/ShippingLaneSegment.py b/qt_ui/widgets/map/ShippingLaneSegment.py new file mode 100644 index 00000000..9eb111f8 --- /dev/null +++ b/qt_ui/widgets/map/ShippingLaneSegment.py @@ -0,0 +1,68 @@ +from typing import Optional + +from PySide2.QtCore import Qt +from PySide2.QtGui import QColor, QPen +from PySide2.QtWidgets import ( + QGraphicsItem, + QGraphicsLineItem, +) + +from game.theater import ControlPoint +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, + 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 = [] + 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 From 42694d2004f6c4ad3e5ba1ff20280c1146bcb11a Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 24 Apr 2021 21:55:40 -0700 Subject: [PATCH 075/438] Update 3.0 compatible campaigns to latest version. Adds shipping lanes to Battle of Abu Dhabi. The others are acking the new requirement but don't have an viable shipping lanes so no changes are needed. --- resources/campaigns/battle_of_abu_dhabi.json | 2 +- resources/campaigns/battle_of_abu_dhabi.miz | Bin 44865 -> 47608 bytes resources/campaigns/exercise_vegas_nerve.json | 2 +- resources/campaigns/inherent_resolve.json | 2 +- .../campaigns/operation_peace_spring.json | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/campaigns/battle_of_abu_dhabi.json b/resources/campaigns/battle_of_abu_dhabi.json index ac2e4ff7..cd30c875 100644 --- a/resources/campaigns/battle_of_abu_dhabi.json +++ b/resources/campaigns/battle_of_abu_dhabi.json @@ -7,5 +7,5 @@ "description": "

You have managed to establish a foothold at Khasab. Continue pushing south.

", "miz": "battle_of_abu_dhabi.miz", "performance": 2, - "version": 2 + "version": 3 } \ No newline at end of file diff --git a/resources/campaigns/battle_of_abu_dhabi.miz b/resources/campaigns/battle_of_abu_dhabi.miz index 785ae0fd2d48ffdc09ef4b6d69a33de7ccc34712..39f64f72cb4b8715fb4a44a12567e6df24166b8d 100644 GIT binary patch literal 47608 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

WY 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 G;Qs*}Ud2%W literal 44865 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:>-Or~s;)hGcaXFOQF!0U0L6;84JeL^YXM(ndp8z4SD)oI4%l{lrTiqV_7 zkf085R9|-xx++`i99YNEpy*WLf(>(tY5nX#4i2MDW9Y=ZXH2$S_uGOjPcMx{Chd_! za-So)%>LAud9EMU`-bb!zn|%XSVG2|PU=>ps=afpWvf?l>Yr=-C#eOH3BKhB*&b!& z;bT&<@^y*%+AT_)x!I<-l+mi9jxUH8zNNwxvRrYq*qQakANJz-RvY8H(@wGmVuKa* zKb&Ym`2}r?4`30LG6}8d7M)Dt%&LUd7nJQD7M7*B&))^fiYHS$kPpvhDBT{id;{zDeeF%?~>GF08&^(>^m*_hcVOkaR|v?LhnV}~Vg2gKIL zb();8cNV##lo->=jXADmPbW!Rgy^wPympr{UlG^l#El}F-?gZVMYy{Th9)2x z6A%MeKDtb`D`^(o;LQ5mHhmFWX&Xg|TSefRGY2iw@=T@y%M&481T3!?h%$ua)735Y z*7()lAFPRz|3#FFz9_sy`;NSeMWgFR`Xb})umEVE;x?B=^WMAW4?LS@Jjvfztw0Nx z!U_6#>q&3jB0}GMAW01!qrUih50IIr_Vu7ToSGxW%;oc1WhQH6sc&{YH!T%Ott& z-e$ussp?s12qoq|Aeq)Z-Aru}lE*%Y+Fd4m#ZNaU=!D_SzgoiLp1FekL*p(Dn1*fUe^_dQIXr#CI*-aw*&GjBl6yWk-T@ zO!K$-^WOkFT_(xn3ubivQLsEzkbD*ctq#5DqEsJk$nujA`_JEn6DZrqL3a6CDo8hQ zARlik$RDG$KbIR@r&w*q7hEL`_v!EAX!luWpjYEL311O-UvYIwbcNe7|%`gp&R@t|&dgzqg(W%j^V$tMH_Mo0%>c19Q!E2 zNl31VCds{M*1OiH!LMXaj@@j(OeX?|{7Br# zsQ&8bnv#$Kd2$B~baFDvPQAmfip16BXP*YWed2^+yX`3-yT%0H}ZAjrVo zxFo}Hbe3=H)-o@azSy^Jtg2N-3PZz>h9MGl2tpJI_y#<-TV92hOAC;ph-Dm{%A)?im`|8iq{N%IQD0!nJ?XiQW&!MgQ#+ z5SlE)9GAgNakJ|20YFD7f)474mHZ{&c6@uou2OscM@1sTE3_cdazmr8-zzxF?!<%A zEm`t9$>ALz+pOs_Ll8y5L|dMl-${aeO)#Qq5s9UTsZ2 zM*VW1fRkpT$j*3A5od`5@7L7)bM8P~pZy_2F@--HgIpYEv4>-s#jh-h+WAR*ekFDH z?+(4(ej~ze|1(}y4W{b(mA!pwGgj*N@$-Wt_W7^5-R|APDty(2_1>jjKedISOiS&;xUSiBmy5d>#I9ZL zu0qG2j+&kkAj-aWV_Ous;P{}u(!kD8ainXF)0?ezW8&SCm^o7h?r97=dfB`mD4c}E zc9HPkg#=;Xr>dOAKQEx8rh3vzV6zRHkJwXGX9trJ*{fwnW7I!5heq|ps-^?B%=S;6 zPfzAJ$C%5a&+%b)$GdBbYjb(~`Vi~=i3kl3ngWBim69`)qpw!*cwV2@)+*4&ZzWZE z$eXpfR#!bSA7Oc|+3=kY zWXAR)LA<~=P@d|2Y)N&WNyULESre-v2ebk3tu~Er^FM;vV zUrqe;9K-XNnJjra?pTujTP(8~kGM+=l};vlJC zX)d@zCAUG~56j~aorEo_~nYdN#j2|+rIC!pxGQPc5E|#bJ ztm)ljw?j!UaXZTq49QQw=W0EI^C>qJajVC+O9zgAOY{hy{4lGxxMB3xO-``-yIK8F zqq}nAg-I||%lC5Q7Z8^gCPp%i`|6D!l!@CIoY`9fA8o$oL4d8m-O_hF?|77Rdk!rx z{$%^b(2m@4naHg;F4gn@109HQ;hy!n{l1Jr#Xb(BxAxhPY=Khxc;7AM&)kpi1Y`aK z7B3_<7>Jq!>Bz7<1#wowH(_ECA^lhs5Tcv+kdUf{eoOrO)9-jbFD*n`2VdLUel5{@ za1UR4$o>0XGwY+r!!;knc!Qamzc>8@cs`we6}n1Y(i00ALs|>TBnG82S!6Ufh;Jc5 zSuq^*a7%mu&pTMg5jWG_%JwTn>*hVb4_0C?PhKS4P2J#JPy~>+{)4jVJDw4L0!)Z6 zJ@gywvsZU#Z)p`>M47ZVk@1U5nBaa~?M@hM@meyw##OzcoX<#$4Nuwk<6oq9Sv|bL z!R%K=T$0i|6V61O63VY4pidSz#(GaxP$=#p#^$)>z*8iYXT5lR%1R7OlW{!Ezohg* z>P_h(@8P`xR*lC~MlglSz5xQtTmL}s7R+BhU7(c~Q-P(Rm`25>AqF7T?ffU2>ivUq8MZ_38Zca<=B;i`qtqwCcVA z0fBq}KUK$=ww*9E4Euc~y5YD6fGAtA*s}Xsz!51Hnm;Svm-mRmP?J^yFLTZ&=3JI*Y3N z=lCJPN_vn%?w(xgM@HpAxqFwy#y&=uAM%Fp{bG%K{KZ3lZj~YO>EOTc_UWJ;H(PSL zh;Y&{nJ}ZZX$)NM&Flk-t4ZZ_Ry+*17<#|QG8HgHIv~wT{$6m7m6*-R>je4K9}PJ% zLh?la5_#VWAd^APx~MhhB@!mQPA$u!rJ~FG-US zsj+72qO<7H*Plz;W#+1x*Va+*k6V-NRU}JtS_kurjlmkix{j6 zRi;fL*2pr3HP#T7Zd#@!<)du2ry6Iyc>pV7OUC}BQKu_0{6Asf3PuaO8EMn z<3C`WV3eqPT8CF;n6=1DZ(a$1q5&!4px(S&LHNMSx2rs26;!8|;cW7P3<*j0Ae1=? z3p<-WZgskK46Z&RW_r<2=~P|+@ZMAx@0x@T@WoT@BA==lS-uS3eGXNxpOMbiqRWj| zOL4=VN+Te!|b--d#RKty)I zhQ{15&ToB;KWJ|OSgv8`N4)?p0&BUOy_VymbQ)4sy%8a9R`lv8Hxp8lh8x_roff4p zks5>Atw2%+#Bnbl^#XkD;DqOxnV~ca!HKF^h0<#V?)20(TK#& zAkiXFBhiPsnIw}XCpGutTK7coAq-V3H3?;RP$p;Z4r|!kvZ=HrHA@iP)%0$GUd1=f z5wwfHAyS2I*qx5tz4{jRw5|0`|G*P|q#Dd$@6JB{%e`XYHTQt=*S87n#{R=Sp=@RZ zww`+Y2R=^jHYui01j+s(|ES5&&FvNF2|z;k%p&X`GSRQde8l;q!A;(2k@1?2w}bU1 z3+y(KlCm~&YY@-UyfQ1(%goq-|A`YFuoZwzy8m=28So zFVx^f-$z3NkIKG;pavg{yUKj|Vo*lKfK+U9S;v$N7w3P#xX9Q72p8`aD675xK^bf@ z1zh1u8=6Flw0oDBjRxEiH?m+CKP24EbGc(JnDWA~TuPn|lnx(H`^cx8L>N{7^dE>1 zO+w#B+vcfumA|*0rHrupk;NdBYbqJv6oaZ&8U#L*AtC8mKAEL0H$O!<*zvWv*CL~O zPuXtRcsRq=_gAx)Rv0B4o;KhWfl4JBJ`cebzkn;;?bSGeYhIcxo_)ePZO$T<2G>iJ z2P2BX?pD8s6?ek$NXp7{Ay4)QKOa3K0*60y_3|3ZcfCXJcyaSP*LN=CQDqY?RbwqK zYYhvL_s=?iZQ}|mm7dS;D0COSf82o?ziK;IQHd9g^9G}$l7PYKwL-w4NZE;y1ql~h zGD**bXjzd_IO_KkPj3~$95VxAW{%iWA**lW9&w!-#+&5BI4?0O{Pl!CXkh{$vXX_6 z4_f>FldjOhCM{oUVS9B%jX&+ITBI&pggTGofKgMaS_HSXMKxe~7dc(s#9c@1G_z9Z z*qI~M?ACvG2InS~izx2=ult(L0iFIA$27QMH739TYSDT? z0ns~aQwL=;o({SBKrgkgQ>p%k({hzzZ71kRE>Vl9e63M{$rJ&rR`FGNVjv^$>OYFN z3QfFTXckvQj*av1?YI_JB;5ZJpm8sL!;g0irJxG9`e#EV@xel^RsTn{9+h8?To#Uv zow@bm7}Q9wwhimP+VqHR0}d!MRat9Yee!imVUs3`weT}_nLKvZLBV2D0ln4&bYPQ( z*s^5LYpV81YbXs!4KOcYvI&+(DfQ4YnAnt{=>X3#jB@z$q5^|#l+kk0lKioW_Ej4a zXcmeFg^7Ad9y|L-53^h$GxPgwNFh>LEf^ngGnh(}ra?;4Y}(br(0zf zz`%*PCzV$-pd!L{E~pYm@0stH1c5Je8l*{ZkqQT@3L!PN&m@3lQ7t#mfc-xi`zsE# z?-%F0)u;79$(h0ic29oeBF#>`aDSK;9T-OW=>#f=3wW;B4P~7 zd6&RxHUDi*i6FKKL_UG>4=mI;V<fPNAL$}w?CYTb-P z_9dyvx-I_^C!z{N0FU}E_=ezIgwLE*7aAvVa((A`Ss=Lbe^dh&ApamBm*1VJM=#*v z?7HG#Dme*xv!CJ?_J0(EK>^C+X4bZqStOjJ4(y9ZZQQSe0SW#GEO$7saw2Vz>i`o{ zSJRBmb>##|{VT43>Z*m-XD@a2!LZ$J23A`3G1-CzYgJlbhrbNCzIZp=$OzQZ|3T8y zbP%G%$2{bK2ySKdl2TZPceg19yILW;*iw>!vgO}8P{s-rNCn+ar_`_OJNMspbaEb# z3F{1&nid3#ivJ*K2~naVOM>*4`p;#s_LNzkT?x{a+(_du0nZ83je%UNo(Do3#zjK6 zz)#*%n@1@O;DGLl$s9wu&K%;wwdvZMS5?SmEwW>POwLKoBN1cuHaSRQ6-RKz8_Msn zJH6`~#&s$N+{!&njq?)bo^y(J5WhA5WhTNhg3Fv4$ymwFx*_ZD?)MjtnLm>u{&m-& zAylT}1(Hzp{T84mKv$&7;n{Bi%v2{2M0ian`G{iafUao<!5 zWbIvhhcLT>!*-PsQD7g8c^#VXugmeHG`zep4=Szk>$c>KT z=`Bc+R`}eLNeCN;G}HLMmfxG+=<$ zKAKabAz9bM2Lb~*e`Z1@eRx|JeGEGrSE!NR=3ItKahN%xK-|~8ak}F z=p|!ms%AC3l&omyUN#|$49T+;4=PTI$Y_qX^z44+c5A*Z zSaL{w0IqU-#6`i_+hdwfR&?yjxFNU>DrQ-M;H*wTwuj31nXIKC5n-64k6nvzfHA=q zrfo`6$Ecwx?cl}JGM|fWpzgA^$jU$Qbw(TwBz{hD^}zwHTeO893cB{ix;6~m(hvrh zy!x@X%^Ie0$b0|B2;%g>FQ>WF{~#toB)NjSEH*A(sqgDiKc#L6qz$GjOH`;VSk#yj z9e{!C>Zy2d)PM!__@!mC3q~*Ym67L0wLHlQP8CTvAvSV&EqdKL1PR1AcYD5NAz z7XGyFDa(pssf}kK0$F3^qiOo(7kFv+B)fN%OsB~HN424&E2?bU#DWwuT9eJJJY%7l z5sDSjQf{`{cbIhjQW&WHL-B{ghhbFKDf8Z`xPsvrYm^#)1M#B!W6)`(Cj}J7>U(rr zUcB5L##1)=_-2A`Yo%yfs0F?w&cHjsfPv&@=?SDZTBimb7mS{MpwMB<^4X6I}$Bl7D^>yrBXvF%A*ET{jF9v2i9_}5vtuED8?r+S`ymr|%279Gi9~fAI ze#&m|^&XG+F8@*OSU+68!}Z>4@Lom%O;T!~*rr7%Iq}WCrRC9r^^K01ZsuFum_~Ji z{FwX~e;yYW?-~XyWpnq@=Voui!_2Z79=I&QN^FB~*`$Ec7c`&_79l-FHY?8H1=A37U88+yj=rp)P) zX6It-AS6AbKN=I;>1KI)v^TTRoJPI4SWLs)HvD94AlAh7{P=8V@AvE>eidhNvDg%~ z_n#xn!yD=cA0_dWt(KNBmlsA4@HcP&TwCjRC=bZC5+UfnCpHEx>#rK~0U3aPsuU(_L{ouJ^2vSO3TpO;+TJWens<$s`9qAp;th4zys!Goey8S%j}MlrU0-pZ zUEix>kE7d3o{RUt$jgtBnKv!>uHlM`Gj74Rj^8y-jth@ZTr?}&sK2*!sab;8oSgrD zv%0XnIyQO!EsB{&;N7TwlpWRe{!($59h1 z8s|F?Jcc|=&!-2OJuXffFHU#P%KI%(S7mP>zv*}K6fHa%E1;j4c%M9TIk>y*k{DD6XuVnYW+dlRbnM8$?0QV*mKUyeV~c5}DBm~pktj>_(Jd9jTGJdFQJ zFlEN#!n9S9r)%@#&wja_)Nk|Vb4#B$nJOHf(*!m1;H0I-rO7Ualh0p8vomWSen_=c+(QlUX zZli}WPWhD5I#xTDmx)wRC5OoLTb6qmP4%t)9-VGJ7@gj@NSRn1jD7V)`Daz*`#`td zS3VA%XW-p}gL9WZo9mt9BQ!2{$$y-^9fybdF3O^*Y&I{BQF=K0ihYuGVg;>VJN3cz z&le4%Gx?4C_H(;;<94FsJO@mR#;vKkJj%+P%B@0nHct2Ew-)}44it=e=!X#XkY)1z zS+URM#+vC{z-@Vft1sQV<0j(KUma@C&$v-=V1%3!0 z?U3PDMX5N;*47|IQT3-w_-y{x$HOsgALVtDkEeWM%k9S_n`GKc8mr$-3kp=`8mA`y zc(+(?Z>BYtoV%7kBEk#VoTgT>BXan}7qDmhpDw z^!dS1KP6rdX)Va@u}QJ^`riDF5aGlUZPv8+l=+j#uHL(+$0MhWM(?Ml-~T+D8j5ML zq!EUHX<53VcFeRqx*h-X{*(~N)%@4I+0t(#4G+y<9|v|Bd6|+mrk?bF*)Z~^x)5(F zb!Pp!S2#VIK|7pDR<$XnM0~M02}-E0pB5FJ`ZMFM0HPMNEkR#YbkXKudeq!P#&hef~B;`->uAE}>9`Y+ry7hS68#}5k_aA|~eMyue* zD_M9AcDiDx-%-0CKYyho#^P|`oaT|gz;~L{{IX`ssSG@3b2zjQ$v-VFE6hpi@bRe? zxq#16>n-HrS7d96P-^nt@ew-ul9w7Uf)>IeJd28QB~R-bsE zmzoOfzQY<)l*{Dx4x?E;&aH2_Qy*WY4g6x&(T#!b>_Qr*0|9SaC) zFS|`u6%HbqkNCDEhL)nU*6Ky-H+S0lvU(~`kF6HByNd>cU-t8i)x%2agFAvV`cgKZ zj}(nwoOn7rdDx#Grj`Aq;e7PlTi^PZB-Z4Z-kRsg{azKulMd4`k;nl+g*Kt2wW9nI zA$71ve16NhkPPc+c`HlBpK-~1s~>QwbarmL_fs^;(!+m?uqHg7w6kOuXO>~dw*_r8 zoJ?~u4;;!k5_%PvTKtN(O!CP%)F|8flO3F|d-Q!Ezs1O9!NQ~XwBiW(HLBp#K-(dAVjK>sp^>8hBd?MJBW_PF&2>L*8C zF;dmnKN>u^oB5$M6Cys3zMV!B)!j84ompvf8RWd#fB0c9y+_JvEUvv@OfkeR?|0o| zYQq^Ef8TvABv|x7l987!DmqW6WY)D)lfEua^nPvK>!)v)wqArs&lr>`cI7A)s|^e# zCfR6AJ;WdSQQ-c#_GJ=P~WlP~$~ba3i;+jajH*u$v|vwtaX zGwL9Z&rYP$?fsoR2h3efHcKv}k@wB)BF0ZV>>Y`2EBrj1>(t+kt1CEL95-CR^~#uy zY|a(YscAaKY@ibv%dFYBI67Y)Dy+(L*<4Z)?CU;(=71cG`2Bs3OK1z|Ij6DzoCQst z{@6KLoICCvI6j?>_{q`d;bmX5w;SRWQsXJNShaodb8TZ&$vA@(=rEo1$(<@}S=GLn z_~|aZd^*;+H*-wF6z5@T_H{}zpXkt&9v$C@9zE&DO>Q>*XKzRQ{`?u;Te0=DI*L8d zm@bZ;q=0EUcyt%VrdiyTb=PX%{tXmK!m?%S7h%0>j_XDBs;4>0u&AFKeXI$}#|rDr zQ^~7qKCc?Qge%^QR349OzYaHQ+&PtEcGoBo%kS&k=!^epi=pg(W8R9N)p_kxbxegz z{k^W~h7Y5s_L14oD8~fy1&X#8jbyPjN{Y?b4Lsx&goXLZu`W)H?3|gLJ^D=3mTI9@ z6&EjEE_#=zZ6trY4p)_HK6YAv_IYKeY>aE;l|b4`U58_&(YJ4Isdoj%y1$V*G%gp) zji`4TzCM``@pk*B zJssE;SA(4}3H<49q?29C({&vas&)1@# zDGg%t`v?06QpLJ#))#Se_ciMOS7TQh6i3so7k3SWpo;}baEAa{f;)=_TY|eTwm1P6 zOK>MhfM5xb1osd;xJz)?Ko(duaQC(EyK@xIK2RvB z2&+(L*t?xSfEB%2v=Cc#HB~-&AFHI}Dqm#Z$2_hZ*k`W)-ujCmaVqfXELr3pn&HNA z#o-~I#f9nn^ju`tT4r6jhVId>&WI@S#4*bBz2r~DFIFLj@sT#IC6q{?r2~yi84ml~ z0ENbr^+kO|7orcfT;2cH%p933iL2|q)=!17N)qO86S>}vt*d^W+23mGzq~X4Ofz}O z`uPrMP9A)3aU;FNtOZC+!tT!PY4~J@A#8kj__`9PQDOjIS1mN)nrM9bYOD(rI9ls4jTe8@0=e!JE{t>*Q^Rwj6^#_SGB;d-^J@9Pk4o6EB1Cs)P zj*ANb02l$p!w@7Ch0ah$g)%38J{Mj&c?B6gHCe^mNs5o!EDhsESf-{ClPwexH$o8<4VsOB z|Bh2!(YTTY6(1+c9{e7sv$>nLHPqG1!_s;{oeNimdzhD7MMLk;NXzv`r}UJDRKb0k zI(_m=9%#P_D<-V@o?!q0l8XQUK1$ff+{4<=)eCA3JzP|Eod;3CSEbs~Ke15?nxMz> zl&ZW_v41Q-^e85Q7S5My+1w`NTzGR%uGB{@JFf2#$9(#^%ciGuGjU|l7D~D4lBgf? zIIAlA^}Yi1Qp{OidStwX%gDBVWJ$Vy>GQmUh$ftNnzuD7MhF@0h~@8m)VOpVxHjOE z8f)O6tx-aM0G)7Qpc66`a4bH_-!;n9BfnNPYnqF6n9J2nJ_}8UZO+%4@|reAB?;w1 zu7K95PkYT>e85lOnwbNI-rSPc(N6A?m) z^y9CXC~wohFLv8IigV=+-!$?DS9%FS?wZ5AL z2e?n{!35RwbK z)d|Xr(=Rvy5Dw|yWKpLiSEaiw z&=L5G;FVTTM?Aw*flH?-=}wYOD)cgZ){_x9KnR_Q^bdS*6ps537953>{)2T!;Sc`7 z&`~(!KR9_bjrA|QgaL-XQo>Tn0}eiY5er8$zM_QAX?KCqfbss$I;(=e!&p zxO{)ATi}V{tMH|*qTz-Bls;h+q^~v>3IWha0i5WQcmNAZ!awO~_y!#fg;IaLw?Yre z2*+kbbs`_~RoRY50qZa9lz?GD{#SQUkOz6wj4d%r6gT7)uonphnY_aPyK_;F4lxW= z?C6Z$WelfR{-9f{pqTPtY}<889Jbi! z#G_8qM6o7P6YQk;d*nOU8$@zZZOr)0*~}CQ4CZ*X1ix?tzk&U6CD35~+4lRK7vCCRJTdAxChe5n(_4_^ z6TK?%I9SK4rlRW}6`dxJUM+(F*((kDaC3@t5zJmG8O_DiUnjiGtdK7wi#v8 zt_8dNkDz|moi4IOJF%`^T%0(|So5(L>ssN;G0t#0oobQ!SjivHQ!L|IZ^MjUbH@@5 zjFJs7)uerUpjl&mh}+5DNLj@sH64HD7Ju{dZ7RCDbWss_tGhIw2dy|o>xGU-QodVs z1Np=&76tT=3+kYIbGZRHXe2Pnj2@w+d)pm0w6|vCHTIoD>=a!uLkRJu5ktqF-0B# z?^iV@VO~N$L#SL=RF{yK={tslf!SXxC#5TOe4d}JHd$zdOPI}x%dF`*Bw@2@lTXlM zm>FgYskfcx`Sd;+sf;bGvPJzA0uG5$l)agf_p)^2 zZ>3{#x*Vx{p=ak`xzI>NeTQPvh8N|yG`{xdWTh2OS)&dUNDZeX!C{ifMiVbk9$qFu z{^)6Qh3p*}L9?7}g|#kOH3U!d853SVlN;e?JGe?P#G|m$a982PSC`w!mppf)8H`<{ z5rc}|r?`RBr%mF9t!ihR`LeK=8T+S%HXSePPH zw0)EJb*os~E1rF zFH)OPMwf_ZY$1f-ox>$1u$6Qbgu@?#u;j$M+6MjhWCiV>SUxd;S5NsGiuA5s6w@7g zbe7+o^gGn&02~Wu99h=exW3yZHiPfZZeW%?+XU|q1RCS7uPi2N_yU=dO_mKE*NUV} zURuT9d9!Et*kve8QK=uteO~>B&+h1K`>QEi1a-Vp$3#`AZLJA!1-8?*wv$<%YGBj+ z-h8<|0$ePi$ClmYa=I9Ej9a~C&Wt*Uk7uK-6#dBQ9reUd4?#`z^}<29EG|3W2(8pI z#R4gph8F@;Z0*jXoP>F)ZEBI~JOX{iTGnE9Vzb4!jP)mF(f4z zm|N1Avu8B0RYXH&kF`FEY|<)u~XUo<84nnQae#(BGB` z!X~mZi%TJ{K;{LdAeSeON}R~$A0@r0xXz$;wPI#aY!LR$vFWAc9L{Frm?)*IOr`Fgzz9GL)Szd&%}KR+$DEgVgVk z2(hIa&zXVOctSwnR}hUpX8(W*g@F84QV+-S$YT1#PTF?QD$}@LKCZ-D*ED_>i(W!gXpDm8*(ICm| zw^u-&ydB}5D(k@vy=Ti#Y_b{Z{g&tSbo8JpSm0_O5RD-dOKRp}l&tQT_$KnRumyz1 zaudcb&l))v%Re9@XN*vnO$>S9CO4XpY~noz)2)eTt2GWEl>26zW9V9>ar@MQc{@ zN4J1`!T5!2mJB$amtgs7*P4<*{qE5=vc(YHx%Q1~ak5RpkhcYwt^Nsldz=r&+=aG= zZeT^xoA=Bf-!Ad}3)6@X9@B9Sy*EbrJ0d{Q#g8~8+*Mab@<#SbMfiMRPbU`vOX29s#lGR<~mbcENY zH-zQ*2aMDa?LO{vz{8f|W3fG-&b85g^U<~e32|AkNqK@Y5^o|f{mz2unX-Fs6HjsB zd0x{j!~)x+TzgitGazW-%GUGgkh;3C&f&$IUt?SJ{aOM1S{r65qRo#3lc#Al{QbW3 zV27~}i_U$i+$SFY_~pIiNq9n$%iyd@eYhvdJDg{)zxB%v*3%SVYxRmbG?s->n|th( ztfEB{sUuID8$4-DQ2VQuxjZ2WF1ZsX?ex~^<)t|VmdZlkbh4RMAsLQrhwLu-Jk>BW zw{92|oshn~@j1Tk&5MY0zcQleQBe}6Wvi$#dImMRHvvKlxeaa%6Kwp%?xoAQAR(t; z?2f$o{8>mN{0rUG_fP0{AvP@=3qR{j`2W zn{1BN=A|#Pn@;lP6v=CD6y9RJe?Bl-Q;hwbapwd0f$D2t5n7Ca=arjnDIP}d=M?86 zUCl)q{pakp{27kZHK}=*@XSL)D_f(ql+4;UfQqN-g4ZIb|G`hPLoZK?Blci8gNrU> zk1}}tr&APEesalxnk=3C_v{{an>y&MPsvnY@uX7{u3h`7nLWI{dwG6}sObn%%R=++ z@9uX!4q#r`+4S2#kWrqe(ZP(@w$XWDE_Vi}Qs>O*W(mFOB&S>ZzW90`uJ-CjxVUhB zs_}A@|9P9kTQ!4>HtlDx+Q)VxLlOZh0m71mM&64~{+Lj)&qgu? z8GD_l?VPr>q-WBYPQesd;yMa}(*X zTu9~@uqV}^>EJ5X;^U{u6R<;c9oS9X7}gQQcXd*k+jzjA?OVnE1!pd-{%iHCMh=V& z(N?M64;<^-Tko1Un0T~p5GzD2uvtZrBrEYT$z1kC_Cr=%=gzh2e2zd@J}uUZ)sKXE zMj=&Ik{yw46FNWga>W8FzlR}{zthge3Frb3aK!w$zWVe*vXvmQMLZ`={#&VrPTiph z_(S#G{%R>0mZEB)>aPm;DZSSvzMV4SJ;-Z7g$l7cvQV{qFOB35>%X(rFFTnuM5rv4 z0A+tZmvq0AOvR`jdmA0nq+< z{CPRsL!qdI_jl6oMZ-VZ{zV!wGu&FLSlE9!uu-=KYE=4BE&$+v0MQZRssI20 diff --git a/resources/campaigns/exercise_vegas_nerve.json b/resources/campaigns/exercise_vegas_nerve.json index bfd2d757..010a0bb2 100644 --- a/resources/campaigns/exercise_vegas_nerve.json +++ b/resources/campaigns/exercise_vegas_nerve.json @@ -3,7 +3,7 @@ "theater": "Nevada", "authors": "Starfire", "description": "

A Red Flag Exercise scenario for the NTTR comprising 4 control points.

", - "version": 2, + "version": 3, "miz": "exercise_vegas_nerve.miz", "performance": 0 } \ No newline at end of file diff --git a/resources/campaigns/inherent_resolve.json b/resources/campaigns/inherent_resolve.json index a5f3f079..7d9143a6 100644 --- a/resources/campaigns/inherent_resolve.json +++ b/resources/campaigns/inherent_resolve.json @@ -5,7 +5,7 @@ "recommended_player_faction": "USA 2005", "recommended_enemy_faction": "Insurgents (Hard)", "description": "

In this scenario, you start from Jordan, and have to fight your way through eastern Syria.

", - "version": 2, + "version": 3, "miz": "inherent_resolve.miz", "performance": 1 } \ No newline at end of file diff --git a/resources/campaigns/operation_peace_spring.json b/resources/campaigns/operation_peace_spring.json index ea2190dd..8b01906e 100644 --- a/resources/campaigns/operation_peace_spring.json +++ b/resources/campaigns/operation_peace_spring.json @@ -5,7 +5,7 @@ "recommended_player_faction": "Bluefor Modern", "recommended_enemy_faction": "Turkey 2005", "description": "

This is a semi-fictional what-if scenario for Operation Peace Spring, during which Turkish forces that crossed into Syria on an offensive against Kurdish militias were emboldened by early successes to continue pushing further southward. Attempts to broker a ceasefire have failed. Members of Operation Inherent Resolve have gathered at Ramat David Airbase in Israel to launch a counter-offensive. Campaign inversion is available if you wish to play as Turkey.

", - "version": 2, + "version": 3, "miz": "operation_peace_spring.miz", "performance": 1 } \ No newline at end of file From ba8fafcc957e7f228fd19824c04c40c4c5f408c8 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 11:17:41 -0700 Subject: [PATCH 076/438] First pass at cargo ships. The simple form of this works, but without the multi-mode routing it'll only get used when the final destination is a port with a link to a port with a factory. These also aren't targetable or simulated yet. https://github.com/Khopa/dcs_liberation/issues/826 --- game/theater/controlpoint.py | 17 ++ game/theater/supplyroutes.py | 61 +++++- game/transfers.py | 185 +++++++++++++----- game/unitdelivery.py | 40 ++-- game/unitmap.py | 6 +- gen/convoys.py | 4 +- gen/flights/ai_flight_planner.py | 4 +- gen/naming.py | 8 + qt_ui/widgets/map/QLiberationMap.py | 25 ++- qt_ui/widgets/map/ShippingLaneSegment.py | 6 +- .../windows/basemenu/NewUnitTransferDialog.py | 5 +- 11 files changed, 262 insertions(+), 99 deletions(-) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index b3a67d3b..886c3846 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -315,6 +315,23 @@ class ControlPoint(MissionTarget, ABC): connected.extend(cp.transitive_connected_friendly_points(seen)) return connected + def transitive_friendly_shipping_destinations( + self, seen: Optional[Set[ControlPoint]] = None + ) -> List[ControlPoint]: + if seen is None: + seen = {self} + + connected = [] + for cp in self.shipping_lanes: + if cp.captured != self.captured: + continue + if cp in seen: + continue + seen.add(cp) + connected.append(cp) + connected.extend(cp.transitive_friendly_shipping_destinations(seen)) + return connected + @property def has_factory(self) -> bool: for tgo in self.connected_objectives: diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py index eb02663d..73254df4 100644 --- a/game/theater/supplyroutes.py +++ b/game/theater/supplyroutes.py @@ -4,7 +4,7 @@ import heapq import math from collections import defaultdict from dataclasses import dataclass, field -from typing import Dict, Iterator, List, Optional +from typing import Dict, Iterable, Iterator, List, Optional from game.theater.controlpoint import ControlPoint @@ -32,6 +32,25 @@ class Frontier: return bool(self.nodes) +# TODO: Build a single SupplyRoute for each coalition at the start of the turn. +# Supply routes need to cover the whole network to support multi-mode links. +# +# Traverse each friendly control point and build out a network from each. Nodes create +# connections to: +# +# 1. Bases connected by road +# 2. Bases connected by rail +# 3. Bases connected by shipping lane +# 4. Airports large enough to operate cargo planes connect to each other +# 5. Airports capable of operating helicopters connect to other airports within cargo +# helicopter range, and FOBs within half of the range (since they can't be refueled +# at the drop off). +# +# The costs of each link would be set such that the above order roughly corresponds to +# the prevalence of each type of transport. Most units should move by road, rail should +# be used a little less often than road, ships a bit less often than that, cargo planes +# infrequently, and helicopters rarely. Convoys, trains, and ships make the most +# interesting targets for players (and the easiest to generate AI flight plans for). class SupplyRoute: def __init__(self, control_points: List[ControlPoint]) -> None: self.control_points = control_points @@ -45,20 +64,16 @@ class SupplyRoute: def __len__(self) -> int: return len(self.control_points) - @classmethod - def for_control_point(cls, control_point: ControlPoint) -> SupplyRoute: - connected_friendly_points = control_point.transitive_connected_friendly_points() - if not connected_friendly_points: - return SupplyRoute([control_point]) - return SupplyRoute([control_point] + connected_friendly_points) + def connections_from(self, control_point: ControlPoint) -> Iterable: + raise NotImplementedError def shortest_path_between( self, origin: ControlPoint, destination: ControlPoint ) -> List[ControlPoint]: if origin not in self: - raise ValueError(f"{origin.name} is not in this supply route") + raise ValueError(f"{origin} is not in supply route to {destination}") if destination not in self: - raise ValueError(f"{destination.name} is not in this supply route") + raise ValueError(f"{destination} is not in supply route from {origin}") frontier = Frontier() frontier.push(origin, 0) @@ -74,7 +89,7 @@ class SupplyRoute: if cost > best_known[current]: continue - for neighbor in current.connected_points: + for neighbor in self.connections_from(current): if current.captured != neighbor.captured: continue @@ -97,3 +112,29 @@ class SupplyRoute: current = previous path.reverse() return path + + +class RoadNetwork(SupplyRoute): + @classmethod + def for_control_point(cls, control_point: ControlPoint) -> RoadNetwork: + connected_friendly_points = control_point.transitive_connected_friendly_points() + if not connected_friendly_points: + return RoadNetwork([control_point]) + return RoadNetwork([control_point] + connected_friendly_points) + + def connections_from(self, control_point: ControlPoint) -> Iterable: + yield from control_point.connected_points + + +class ShippingNetwork(SupplyRoute): + @classmethod + def for_control_point(cls, control_point: ControlPoint) -> ShippingNetwork: + connected_friendly_points = ( + control_point.transitive_friendly_shipping_destinations() + ) + if not connected_friendly_points: + return ShippingNetwork([control_point]) + return ShippingNetwork([control_point] + connected_friendly_points) + + def connections_from(self, control_point: ControlPoint) -> Iterable: + yield from control_point.shipping_lanes diff --git a/game/transfers.py b/game/transfers.py index 1a5bfba1..3685ed79 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -16,7 +16,7 @@ from gen.flights.ai_flight_planner_db import TRANSPORT_CAPABLE from gen.flights.closestairfields import ObjectiveDistanceCache from gen.flights.flightplan import FlightPlanBuilder from game.theater import ControlPoint, MissionTarget -from game.theater.supplyroutes import SupplyRoute +from game.theater.supplyroutes import RoadNetwork, ShippingNetwork, SupplyRoute from gen.naming import namegen from gen.flights.flight import Flight, FlightType @@ -242,20 +242,15 @@ class AirliftPlanner: return flight_size -class Convoy(MissionTarget, Transport): - def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: - super().__init__(namegen.next_convoy_name(), origin.position) +class MultiGroupTransport(MissionTarget, Transport): + def __init__( + self, name: str, origin: ControlPoint, destination: ControlPoint + ) -> None: + super().__init__(name, origin.position) self.origin = origin self.destination = destination self.transfers: List[TransferOrder] = [] - def mission_types(self, for_player: bool) -> Iterator[FlightType]: - if self.is_friendly(for_player): - return - - yield FlightType.BAI - yield from super().mission_types(for_player) - def is_friendly(self, to_player: bool) -> bool: return self.origin.captured @@ -298,10 +293,30 @@ class Convoy(MissionTarget, Transport): return self.origin.captured def find_escape_route(self) -> Optional[ControlPoint]: - return None + raise NotImplementedError def description(self) -> str: - return f"In a convoy to {self.destination}" + raise NotImplementedError + + @property + def route_start(self) -> Point: + raise NotImplementedError + + @property + def route_end(self) -> Point: + raise NotImplementedError + + +class Convoy(MultiGroupTransport): + def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: + super().__init__(namegen.next_convoy_name(), origin, destination) + + def mission_types(self, for_player: bool) -> Iterator[FlightType]: + if self.is_friendly(for_player): + return + + yield FlightType.BAI + yield from super().mission_types(for_player) @property def route_start(self) -> Point: @@ -311,44 +326,85 @@ class Convoy(MissionTarget, Transport): def route_end(self) -> Point: return self.destination.convoy_spawns[self.origin] + def description(self) -> str: + return f"In a convoy to {self.destination}" -class ConvoyMap: + def find_escape_route(self) -> Optional[ControlPoint]: + return None + + +class CargoShip(MultiGroupTransport): + def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: + super().__init__(namegen.next_cargo_ship_name(), origin, destination) + + def mission_types(self, for_player: bool) -> Iterator[FlightType]: + if self.is_friendly(for_player): + return + + yield FlightType.ANTISHIP + yield from super().mission_types(for_player) + + @property + def route_start(self) -> Point: + return self.origin.shipping_lanes[self.destination][0] + + @property + def route_end(self) -> Point: + return self.destination.shipping_lanes[self.origin][-1] + + def description(self) -> str: + return f"On a ship to {self.destination}" + + def find_escape_route(self) -> Optional[ControlPoint]: + return None + + +class TransportMap: def __init__(self) -> None: - # Dict of origin -> destination -> convoy. - self.convoys: Dict[ControlPoint, Dict[ControlPoint, Convoy]] = defaultdict(dict) + # Dict of origin -> destination -> transport. + self.transports: Dict[ + ControlPoint, Dict[ControlPoint, MultiGroupTransport] + ] = defaultdict(dict) - def convoy_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool: - return destination in self.convoys[origin] - - def find_convoy( + def create_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> Optional[Convoy]: - return self.convoys[origin].get(destination) + ) -> MultiGroupTransport: + raise NotImplementedError - def find_or_create_convoy( + def transport_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool: + return destination in self.transports[origin] + + def find_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> Convoy: - convoy = self.find_convoy(origin, destination) - if convoy is None: - convoy = Convoy(origin, destination) - self.convoys[origin][destination] = convoy - return convoy + ) -> Optional[MultiGroupTransport]: + return self.transports[origin].get(destination) - def departing_from(self, origin: ControlPoint) -> Iterator[Convoy]: - yield from self.convoys[origin].values() + def find_or_create_transport( + self, origin: ControlPoint, destination: ControlPoint + ) -> MultiGroupTransport: + transport = self.find_transport(origin, destination) + if transport is None: + transport = self.create_transport(origin, destination) + self.transports[origin][destination] = transport + return transport - def travelling_to(self, destination: ControlPoint) -> Iterator[Convoy]: - for destination_dict in self.convoys.values(): + def departing_from(self, origin: ControlPoint) -> Iterator[MultiGroupTransport]: + yield from self.transports[origin].values() + + def travelling_to(self, destination: ControlPoint) -> Iterator[MultiGroupTransport]: + for destination_dict in self.transports.values(): if destination in destination_dict: yield destination_dict[destination] - def disband_convoy(self, convoy: Convoy) -> None: - self.convoys[convoy.origin][convoy.destination].disband() - del self.convoys[convoy.origin][convoy.destination] + def disband_transport(self, transport: MultiGroupTransport) -> None: + transport.disband() + del self.transports[transport.origin][transport.destination] - @staticmethod - def path_for(transfer: TransferOrder) -> List[ControlPoint]: - supply_route = SupplyRoute.for_control_point(transfer.position) + def network_for(self, control_point: ControlPoint) -> SupplyRoute: + raise NotImplementedError + + def path_for(self, transfer: TransferOrder) -> List[ControlPoint]: + supply_route = self.network_for(transfer.position) return supply_route.shortest_path_between( transfer.position, transfer.destination ) @@ -358,26 +414,47 @@ class ConvoyMap: def add(self, transfer: TransferOrder) -> None: next_stop = self.next_stop_for(transfer) - self.find_or_create_convoy(transfer.position, next_stop).add_units(transfer) + self.find_or_create_transport(transfer.position, next_stop).add_units(transfer) - def remove(self, convoy: Convoy, transfer: TransferOrder) -> None: - convoy.remove_units(transfer) - if not convoy.transfers: - self.disband_convoy(convoy) + def remove(self, transport: MultiGroupTransport, transfer: TransferOrder) -> None: + transport.remove_units(transfer) + if not transport.transfers: + self.disband_transport(transport) def disband_all(self) -> None: - for convoy in list(self): - self.disband_convoy(convoy) + for transport in list(self): + self.disband_transport(transport) - def __iter__(self) -> Iterator[Convoy]: - for destination_dict in self.convoys.values(): + def __iter__(self) -> Iterator[MultiGroupTransport]: + for destination_dict in self.transports.values(): yield from destination_dict.values() +class ConvoyMap(TransportMap): + def create_transport( + self, origin: ControlPoint, destination: ControlPoint + ) -> Convoy: + return Convoy(origin, destination) + + def network_for(self, control_point: ControlPoint) -> RoadNetwork: + return RoadNetwork.for_control_point(control_point) + + +class CargoShipMap(TransportMap): + def create_transport( + self, origin: ControlPoint, destination: ControlPoint + ) -> CargoShip: + return CargoShip(origin, destination) + + def network_for(self, control_point: ControlPoint) -> ShippingNetwork: + return ShippingNetwork.for_control_point(control_point) + + class PendingTransfers: def __init__(self, game: Game) -> None: self.game = game self.convoys = ConvoyMap() + self.cargo_ships = CargoShipMap() self.pending_transfers: List[TransferOrder] = [] def __iter__(self) -> Iterator[TransferOrder]: @@ -394,9 +471,12 @@ class PendingTransfers: return self.pending_transfers.index(transfer) def arrange_transport(self, transfer: TransferOrder) -> None: - supply_route = SupplyRoute.for_control_point(transfer.position) - if transfer.destination in supply_route: + if transfer.destination in RoadNetwork.for_control_point(transfer.position): self.convoys.add(transfer) + elif transfer.destination in ShippingNetwork.for_control_point( + transfer.position + ): + self.cargo_ships.add(transfer) else: AirliftPlanner(self.game, transfer).create_package_for_airlift() @@ -439,6 +519,11 @@ class PendingTransfers: ) -> None: self.convoys.remove(transport, transfer) + def _cancel_transport_cargo_ship( + self, transfer: TransferOrder, transport: CargoShip + ) -> None: + self.cargo_ships.remove(transport, transfer) + def cancel_transfer(self, transfer: TransferOrder) -> None: if transfer.transport is not None: self.cancel_transport(transfer, transfer.transport) diff --git a/game/unitdelivery.py b/game/unitdelivery.py index 402a25cd..d9cf3e0f 100644 --- a/game/unitdelivery.py +++ b/game/unitdelivery.py @@ -10,6 +10,7 @@ from dcs.unittype import UnitType, VehicleType from game.theater import ControlPoint, SupplyRoute from gen.flights.closestairfields import ObjectiveDistanceCache from .db import PRICES +from .theater.supplyroutes import RoadNetwork, ShippingNetwork from .transfers import TransferOrder if TYPE_CHECKING: @@ -19,7 +20,6 @@ if TYPE_CHECKING: @dataclass(frozen=True) class GroundUnitSource: control_point: ControlPoint - requires_airlift: bool class PendingUnitDeliveries: @@ -84,9 +84,9 @@ class PendingUnitDeliveries: if ( issubclass(unit_type, VehicleType) - and self.destination != ground_unit_source.control_point + and self.destination != ground_unit_source ): - source = ground_unit_source.control_point + source = ground_unit_source d = units_needing_transfer else: source = self.destination @@ -106,41 +106,45 @@ class PendingUnitDeliveries: self.destination.base.commit_losses(sold_units) if units_needing_transfer: - ground_unit_source.control_point.base.commision_units( - units_needing_transfer - ) - self.create_transfer( - game, ground_unit_source.control_point, units_needing_transfer - ) + ground_unit_source.base.commision_units(units_needing_transfer) + self.create_transfer(game, ground_unit_source, units_needing_transfer) def create_transfer( self, game: Game, source: ControlPoint, units: Dict[Type[VehicleType], int] ) -> None: game.transfers.new_transfer(TransferOrder(source, self.destination, units)) - def find_ground_unit_source(self, game: Game) -> Optional[GroundUnitSource]: + def find_ground_unit_source(self, game: Game) -> Optional[ControlPoint]: # This is running *after* the turn counter has been incremented, so this is the # reaction to turn 0. On turn zero we allow units to be recruited anywhere for # delivery on turn 1 so that turn 1 always starts with units on the front line. if game.turn == 1: - return GroundUnitSource(self.destination, requires_airlift=False) + return self.destination # Fast path if the destination is a valid source. if self.destination.can_recruit_ground_units(game): - return GroundUnitSource(self.destination, requires_airlift=False) + return self.destination - by_road = self.find_ground_unit_source_by_road(game) + by_road = self.find_ground_unit_source_in_supply_route( + RoadNetwork.for_control_point(self.destination), game + ) if by_road is not None: - return GroundUnitSource(by_road, requires_airlift=False) + return by_road + + by_ship = self.find_ground_unit_source_in_supply_route( + ShippingNetwork.for_control_point(self.destination), game + ) + if by_ship is not None: + return by_ship by_air = self.find_ground_unit_source_by_air(game) if by_air is not None: - return GroundUnitSource(by_air, requires_airlift=True) + return by_air return None - def find_ground_unit_source_by_road(self, game: Game) -> Optional[ControlPoint]: - supply_route = SupplyRoute.for_control_point(self.destination) - + def find_ground_unit_source_in_supply_route( + self, supply_route: SupplyRoute, game: Game + ) -> Optional[ControlPoint]: sources = [] for control_point in supply_route: if control_point.can_recruit_ground_units(game): diff --git a/game/unitmap.py b/game/unitmap.py index c6d8d0de..4571f2b3 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -9,7 +9,7 @@ from dcs.unittype import VehicleType from game import db from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import BuildingGroundObject -from game.transfers import Convoy, TransferOrder +from game.transfers import MultiGroupTransport, TransferOrder from gen.flights.flight import Flight @@ -29,7 +29,7 @@ class GroundObjectUnit: @dataclass(frozen=True) class ConvoyUnit: unit_type: Type[VehicleType] - convoy: Convoy + convoy: MultiGroupTransport @dataclass(frozen=True) @@ -130,7 +130,7 @@ class UnitMap: def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]: return self.ground_object_units.get(name, None) - def add_convoy_units(self, group: Group, convoy: Convoy) -> None: + def add_convoy_units(self, group: Group, convoy: MultiGroupTransport) -> None: for unit in group.units: # The actual name is a String (the pydcs translatable string), which # doesn't define __eq__. diff --git a/gen/convoys.py b/gen/convoys.py index 9c904009..f3033e9e 100644 --- a/gen/convoys.py +++ b/gen/convoys.py @@ -10,7 +10,7 @@ from dcs.unit import Vehicle from dcs.unitgroup import VehicleGroup from dcs.unittype import VehicleType -from game.transfers import Convoy +from game.transfers import MultiGroupTransport from game.unitmap import UnitMap from game.utils import kph @@ -30,7 +30,7 @@ class ConvoyGenerator: for convoy in self.game.transfers.convoys: self.generate_convoy(convoy) - def generate_convoy(self, convoy: Convoy) -> VehicleGroup: + def generate_convoy(self, convoy: MultiGroupTransport) -> VehicleGroup: group = self._create_mixed_unit_group( convoy.name, convoy.route_start, diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index a82877b0..16882bc6 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -39,7 +39,7 @@ from game.theater.theatergroundobject import ( NavalGroundObject, VehicleGroupGroundObject, ) -from game.transfers import Convoy, TransferOrder +from game.transfers import Convoy, MultiGroupTransport, TransferOrder from game.utils import Distance, nautical_miles from gen import Conflict from gen.ato import Package @@ -445,7 +445,7 @@ class ObjectiveFinder: airfields.append(control_point) return self._targets_by_range(airfields) - def convoys(self) -> Iterator[Convoy]: + def convoys(self) -> Iterator[MultiGroupTransport]: for front_line in self.front_lines(): if front_line.control_point_a.is_friendly(self.is_player): enemy_cp = front_line.control_point_a diff --git a/gen/naming.py b/gen/naming.py index d8c7f768..a2a344ba 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -251,6 +251,7 @@ class NameGenerator: infantry_number = 0 aircraft_number = 0 convoy_number = 0 + cargo_ship_number = 0 ANIMALS = ANIMALS existing_alphas: List[str] = [] @@ -260,6 +261,7 @@ class NameGenerator: cls.number = 0 cls.infantry_number = 0 cls.convoy_number = 0 + cls.cargo_ship_number = 0 cls.ANIMALS = ANIMALS cls.existing_alphas = [] @@ -269,6 +271,7 @@ class NameGenerator: cls.infantry_number = 0 cls.aircraft_number = 0 cls.convoy_number = 0 + cls.cargo_ship_number = 0 @classmethod def next_aircraft_name(cls, country: Country, parent_base_id: int, flight: Flight): @@ -335,6 +338,11 @@ class NameGenerator: cls.convoy_number += 1 return f"Convoy {cls.convoy_number:03}" + @classmethod + def next_cargo_ship_name(cls) -> str: + cls.cargo_ship_number += 1 + return f"Cargo Ship {cls.cargo_ship_number:03}" + @classmethod def random_objective_name(cls): if len(cls.ANIMALS) == 0: diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index d5e2ec2a..e353f60c 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -4,7 +4,7 @@ import datetime import logging import math from functools import singledispatchmethod -from typing import Iterable, Iterator, List, Optional, Tuple +from typing import Iterable, Iterator, List, Optional, Sequence, Tuple from PySide2 import QtCore, QtWidgets from PySide2.QtCore import QLineF, QPointF, QRectF, Qt @@ -87,7 +87,7 @@ def bernstein(t: float, i: int, n: int) -> float: return binomial(i, n) * (t ** i) * ((1 - t) ** (n - i)) -def bezier(t: float, points: Iterable[Tuple[float, float]]) -> Tuple[float, float]: +def bezier(t: float, points: Sequence[Tuple[float, float]]) -> Tuple[float, float]: """Calculate coordinate of a point in the bezier curve""" n = len(points) - 1 x = y = 0 @@ -99,7 +99,7 @@ def bezier(t: float, points: Iterable[Tuple[float, float]]) -> Tuple[float, floa def bezier_curve_range( - n: int, points: Iterable[Tuple[float, float]] + n: int, points: Sequence[Tuple[float, float]] ) -> Iterator[Tuple[float, float]]: """Range of points in a curve bezier""" for i in range(n): @@ -145,7 +145,7 @@ class QLiberationMap(QGraphicsView): QtCore.QLineF(QPointF(0, 0), QPointF(0, 0)) ) self.movement_line.setPen(QPen(CONST.COLORS["orange"], width=10.0)) - self.selected_cp: QMapControlPoint = None + self.selected_cp: Optional[QMapControlPoint] = None GameUpdateSignal.get_instance().flight_paths_changed.connect( lambda: self.draw_flight_plans(self.scene()) @@ -767,7 +767,7 @@ class QLiberationMap(QGraphicsView): scene: QGraphicsScene, number: int, waypoint: FlightWaypoint, - position: Tuple[int, int], + position: Tuple[float, float], flight_plan: FlightPlan, ) -> None: @@ -880,19 +880,28 @@ class QLiberationMap(QGraphicsView): self.draw_shipping_lane_between(cp, destination) def draw_shipping_lane_between(self, a: ControlPoint, b: ControlPoint) -> None: + ship_map = self.game.transfers.cargo_ships + ships = [] + ship = ship_map.find_transport(a, b) + if ship is not None: + ships.append(ship) + ship = ship_map.find_transport(b, a) + if ship is not None: + ships.append(ship) + scene = self.scene() for pa, pb in self.bezier_points(a.shipping_lanes[b]): - scene.addItem(ShippingLaneSegment(pa[0], pa[1], pb[0], pb[1], a, b)) + scene.addItem(ShippingLaneSegment(pa[0], pa[1], pb[0], pb[1], a, b, ships)) def draw_supply_route_between(self, a: ControlPoint, b: ControlPoint) -> None: scene = self.scene() convoy_map = self.game.transfers.convoys convoys = [] - convoy = convoy_map.find_convoy(a, b) + convoy = convoy_map.find_transport(a, b) if convoy is not None: convoys.append(convoy) - convoy = convoy_map.find_convoy(b, a) + convoy = convoy_map.find_transport(b, a) if convoy is not None: convoys.append(convoy) diff --git a/qt_ui/widgets/map/ShippingLaneSegment.py b/qt_ui/widgets/map/ShippingLaneSegment.py index 9eb111f8..02d445a4 100644 --- a/qt_ui/widgets/map/ShippingLaneSegment.py +++ b/qt_ui/widgets/map/ShippingLaneSegment.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import List, Optional from PySide2.QtCore import Qt from PySide2.QtGui import QColor, QPen @@ -8,6 +8,7 @@ from PySide2.QtWidgets import ( ) from game.theater import ControlPoint +from game.transfers import CargoShip from qt_ui.uiconstants import COLORS @@ -20,12 +21,13 @@ class ShippingLaneSegment(QGraphicsLineItem): 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 = [] + self.ships = ships self.setPen(self.make_pen()) self.setToolTip(self.make_tooltip()) self.setAcceptHoverEvents(True) diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index 591d8a07..50c44572 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -2,12 +2,10 @@ from __future__ import annotations import logging from collections import defaultdict -from dataclasses import dataclass from typing import Callable, Dict, Type from PySide2.QtCore import Qt from PySide2.QtWidgets import ( - QCheckBox, QComboBox, QDialog, QFrame, @@ -15,7 +13,6 @@ from PySide2.QtWidgets import ( QGroupBox, QHBoxLayout, QLabel, - QMessageBox, QPushButton, QScrollArea, QSizePolicy, @@ -27,7 +24,7 @@ from dcs.task import PinpointStrike from dcs.unittype import UnitType from game import Game, db -from game.theater import ControlPoint, SupplyRoute +from game.theater import ControlPoint from game.transfers import TransferOrder from qt_ui.models import GameModel from qt_ui.widgets.QLabeledWidget import QLabeledWidget From 7e40d58d04872fb4f9a079e0358d5e1ccb18e61f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 14:12:59 -0700 Subject: [PATCH 077/438] Add cargo ships to the sim, track kills. Not targetable yet. https://github.com/Khopa/dcs_liberation/issues/826 --- changelog.md | 2 +- game/debriefing.py | 28 +++ game/event/event.py | 9 + game/operation/operation.py | 8 +- game/transfers.py | 45 ++-- game/unitmap.py | 24 ++- gen/cargoshipgen.py | 47 ++++ gen/{convoys.py => convoygen.py} | 4 +- qt_ui/windows/QDebriefingWindow.py | 204 ++++++------------ .../windows/QWaitingForMissionResultWindow.py | 61 +++--- 10 files changed, 233 insertions(+), 199 deletions(-) create mode 100644 gen/cargoshipgen.py rename gen/{convoys.py => convoygen.py} (95%) diff --git a/changelog.md b/changelog.md index a28e5bd2..9a48c7d7 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ Saves from 2.5 are not compatible with 3.0. ## Features/Improvements -* **[Campaign]** Ground units can now be transferred by road and airlift. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. +* **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory and a transfer will be created on the next turn. This feature is off by default. * **[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. diff --git a/game/debriefing.py b/game/debriefing.py index 1ccafaca..1d534be2 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -22,6 +22,7 @@ from dcs.unittype import FlyingType, UnitType from game import db from game.theater import Airfield, ControlPoint +from game.transfers import CargoShip from game.unitmap import ( AirliftUnit, Building, @@ -70,6 +71,9 @@ class GroundLosses: player_convoy: List[ConvoyUnit] = field(default_factory=list) enemy_convoy: List[ConvoyUnit] = field(default_factory=list) + player_cargo_ships: List[CargoShip] = field(default_factory=list) + enemy_cargo_ships: List[CargoShip] = field(default_factory=list) + player_airlifts: List[AirliftUnit] = field(default_factory=list) enemy_airlifts: List[AirliftUnit] = field(default_factory=list) @@ -138,6 +142,11 @@ class Debriefing: yield from self.ground_losses.player_convoy yield from self.ground_losses.enemy_convoy + @property + def cargo_ship_losses(self) -> Iterator[CargoShip]: + yield from self.ground_losses.player_cargo_ships + yield from self.ground_losses.enemy_cargo_ships + @property def airlift_losses(self) -> Iterator[AirliftUnit]: yield from self.ground_losses.player_airlifts @@ -181,6 +190,17 @@ class Debriefing: losses_by_type[loss.unit_type] += 1 return losses_by_type + def cargo_ship_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]: + losses_by_type: Dict[Type[UnitType], int] = defaultdict(int) + if player: + ships = self.ground_losses.player_cargo_ships + else: + ships = self.ground_losses.enemy_cargo_ships + for ship in ships: + for unit_type, count in ship.units.items(): + losses_by_type[unit_type] += count + return losses_by_type + def airlift_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]: losses_by_type: Dict[Type[UnitType], int] = defaultdict(int) if player: @@ -237,6 +257,14 @@ class Debriefing: losses.enemy_convoy.append(convoy_unit) continue + cargo_ship = self.unit_map.cargo_ship(unit_name) + if cargo_ship is not None: + if cargo_ship.player_owned: + losses.player_cargo_ships.append(cargo_ship) + else: + losses.enemy_cargo_ships.append(cargo_ship) + continue + ground_object_unit = self.unit_map.ground_object_unit(unit_name) if ground_object_unit is not None: if ground_object_unit.ground_object.control_point.captured: diff --git a/game/event/event.py b/game/event/event.py index d37664ee..4ca70790 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -169,6 +169,15 @@ class Event: logging.info(f"{unit_type} destroyed in {convoy_name}") convoy.kill_unit(unit_type) + @staticmethod + def commit_cargo_ship_losses(debriefing: Debriefing) -> None: + for ship in debriefing.cargo_ship_losses: + logging.info( + f"All units destroyed in cargo ship from {ship.origin} to " + f"{ship.destination}." + ) + ship.kill_all() + @staticmethod def commit_airlift_losses(debriefing: Debriefing) -> None: for loss in debriefing.airlift_losses: diff --git a/game/operation/operation.py b/game/operation/operation.py index 1376be88..1304c064 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -23,7 +23,8 @@ from gen.airsupportgen import AirSupport, AirSupportConflictGenerator from gen.armor import GroundConflictGenerator, JtacInfo from gen.beacons import load_beacons_for_terrain from gen.briefinggen import BriefingGenerator, MissionInfoGenerator -from gen.convoys import ConvoyGenerator +from gen.cargoshipgen import CargoShipGenerator +from gen.convoygen import ConvoyGenerator from gen.environmentgen import EnvironmentGenerator from gen.forcedoptionsgen import ForcedOptionsGenerator from gen.groundobjectsgen import GroundObjectsGenerator @@ -304,7 +305,7 @@ class Operation: # Set mission time and weather conditions. EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate() cls._generate_ground_units() - cls._generate_convoys() + cls._generate_transports() cls._generate_destroyed_units() cls._generate_air_units() cls.assign_channels_to_flights( @@ -426,9 +427,10 @@ class Operation: cls.jtacs.extend(ground_conflict_gen.jtacs) @classmethod - def _generate_convoys(cls) -> None: + def _generate_transports(cls) -> None: """Generates convoys for unit transfers by road.""" ConvoyGenerator(cls.current_mission, cls.game, cls.unit_map).generate() + CargoShipGenerator(cls.current_mission, cls.game, cls.unit_map).generate() @classmethod def reset_naming_ids(cls): diff --git a/game/transfers.py b/game/transfers.py index 3685ed79..a22cabb6 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -4,7 +4,7 @@ import logging from collections import defaultdict from dataclasses import dataclass, field from functools import singledispatchmethod -from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Type +from typing import Dict, Generic, Iterator, List, Optional, TYPE_CHECKING, Type, TypeVar from dcs.mapping import Point from dcs.unittype import FlyingType, VehicleType @@ -271,6 +271,10 @@ class MultiGroupTransport(MissionTarget, Transport): pass raise KeyError + def kill_all(self) -> None: + for transfer in self.transfers: + transfer.kill_all() + def disband(self) -> None: for transfer in list(self.transfers): self.remove_units(transfer) @@ -298,14 +302,6 @@ class MultiGroupTransport(MissionTarget, Transport): def description(self) -> str: raise NotImplementedError - @property - def route_start(self) -> Point: - raise NotImplementedError - - @property - def route_end(self) -> Point: - raise NotImplementedError - class Convoy(MultiGroupTransport): def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: @@ -345,12 +341,8 @@ class CargoShip(MultiGroupTransport): yield from super().mission_types(for_player) @property - def route_start(self) -> Point: - return self.origin.shipping_lanes[self.destination][0] - - @property - def route_end(self) -> Point: - return self.destination.shipping_lanes[self.origin][-1] + def route(self) -> List[Point]: + return self.origin.shipping_lanes[self.destination] def description(self) -> str: return f"On a ship to {self.destination}" @@ -359,16 +351,19 @@ class CargoShip(MultiGroupTransport): return None -class TransportMap: +TransportType = TypeVar("TransportType", bound=MultiGroupTransport) + + +class TransportMap(Generic[TransportType]): def __init__(self) -> None: # Dict of origin -> destination -> transport. self.transports: Dict[ - ControlPoint, Dict[ControlPoint, MultiGroupTransport] + ControlPoint, Dict[ControlPoint, TransportType] ] = defaultdict(dict) def create_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> MultiGroupTransport: + ) -> TransportType: raise NotImplementedError def transport_exists(self, origin: ControlPoint, destination: ControlPoint) -> bool: @@ -376,27 +371,27 @@ class TransportMap: def find_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> Optional[MultiGroupTransport]: + ) -> Optional[TransportType]: return self.transports[origin].get(destination) def find_or_create_transport( self, origin: ControlPoint, destination: ControlPoint - ) -> MultiGroupTransport: + ) -> TransportType: transport = self.find_transport(origin, destination) if transport is None: transport = self.create_transport(origin, destination) self.transports[origin][destination] = transport return transport - def departing_from(self, origin: ControlPoint) -> Iterator[MultiGroupTransport]: + def departing_from(self, origin: ControlPoint) -> Iterator[TransportType]: yield from self.transports[origin].values() - def travelling_to(self, destination: ControlPoint) -> Iterator[MultiGroupTransport]: + def travelling_to(self, destination: ControlPoint) -> Iterator[TransportType]: for destination_dict in self.transports.values(): if destination in destination_dict: yield destination_dict[destination] - def disband_transport(self, transport: MultiGroupTransport) -> None: + def disband_transport(self, transport: TransportType) -> None: transport.disband() del self.transports[transport.origin][transport.destination] @@ -416,7 +411,7 @@ class TransportMap: next_stop = self.next_stop_for(transfer) self.find_or_create_transport(transfer.position, next_stop).add_units(transfer) - def remove(self, transport: MultiGroupTransport, transfer: TransferOrder) -> None: + def remove(self, transport: TransportType, transfer: TransferOrder) -> None: transport.remove_units(transfer) if not transport.transfers: self.disband_transport(transport) @@ -425,7 +420,7 @@ class TransportMap: for transport in list(self): self.disband_transport(transport) - def __iter__(self) -> Iterator[MultiGroupTransport]: + def __iter__(self) -> Iterator[TransportType]: for destination_dict in self.transports.values(): yield from destination_dict.values() diff --git a/game/unitmap.py b/game/unitmap.py index 4571f2b3..dd1f527b 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -9,7 +9,7 @@ from dcs.unittype import VehicleType from game import db from game.theater import Airfield, ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import BuildingGroundObject -from game.transfers import MultiGroupTransport, TransferOrder +from game.transfers import CargoShip, Convoy, TransferOrder from gen.flights.flight import Flight @@ -29,7 +29,7 @@ class GroundObjectUnit: @dataclass(frozen=True) class ConvoyUnit: unit_type: Type[VehicleType] - convoy: MultiGroupTransport + convoy: Convoy @dataclass(frozen=True) @@ -51,6 +51,7 @@ class UnitMap: self.ground_object_units: Dict[str, GroundObjectUnit] = {} self.buildings: Dict[str, Building] = {} self.convoys: Dict[str, ConvoyUnit] = {} + self.cargo_ships: Dict[str, CargoShip] = {} self.airlifts: Dict[str, AirliftUnit] = {} def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: @@ -130,7 +131,7 @@ class UnitMap: def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]: return self.ground_object_units.get(name, None) - def add_convoy_units(self, group: Group, convoy: MultiGroupTransport) -> None: + def add_convoy_units(self, group: Group, convoy: Convoy) -> None: for unit in group.units: # The actual name is a String (the pydcs translatable string), which # doesn't define __eq__. @@ -149,6 +150,23 @@ class UnitMap: def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: return self.convoys.get(name, None) + def add_cargo_ship(self, group: Group, ship: CargoShip) -> None: + if len(group.units) > 1: + # Cargo ship "groups" are single units. Killing the one ship kills the whole + # transfer. If we ever want to add escorts or create multiple cargo ships in + # a convoy of ships that logic needs to change. + raise ValueError("Expected cargo ship to be a single unit group.") + unit = group.units[0] + # The actual name is a String (the pydcs translatable string), which + # doesn't define __eq__. + name = str(unit.name) + if name in self.cargo_ships: + raise RuntimeError(f"Duplicate cargo ship: {name}") + self.cargo_ships[name] = ship + + def cargo_ship(self, name: str) -> Optional[CargoShip]: + return self.cargo_ships.get(name, None) + def add_airlift_units(self, group: FlyingGroup, transfer: TransferOrder) -> None: for transport, cargo_type in zip(group.units, transfer.iter_units()): # The actual name is a String (the pydcs translatable string), which diff --git a/gen/cargoshipgen.py b/gen/cargoshipgen.py new file mode 100644 index 00000000..e3161a42 --- /dev/null +++ b/gen/cargoshipgen.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import itertools +from typing import TYPE_CHECKING + +from dcs import Mission +from dcs.ships import Bulker_Handy_Wind +from dcs.unitgroup import ShipGroup + +from game.transfers import CargoShip +from game.unitmap import UnitMap +from game.utils import knots + +if TYPE_CHECKING: + from game import Game + + +class CargoShipGenerator: + def __init__(self, mission: Mission, game: Game, unit_map: UnitMap) -> None: + self.mission = mission + self.game = game + self.unit_map = unit_map + self.count = itertools.count() + + def generate(self) -> None: + # Reset the count to make generation deterministic. + for ship in self.game.transfers.cargo_ships: + self.generate_cargo_ship(ship) + + def generate_cargo_ship(self, ship: CargoShip) -> ShipGroup: + country = self.mission.country( + self.game.player_country if ship.player_owned else self.game.enemy_country + ) + waypoints = ship.route + group = self.mission.ship_group( + country, + ship.name, + Bulker_Handy_Wind, + position=waypoints[0], + group_size=1, + ) + for waypoint in waypoints[1:]: + # 12 knots is very slow but it's also nearly the max allowed by DCS for this + # type of ship. + group.add_waypoint(waypoint, speed=knots(12).kph) + self.unit_map.add_cargo_ship(group, ship) + return group diff --git a/gen/convoys.py b/gen/convoygen.py similarity index 95% rename from gen/convoys.py rename to gen/convoygen.py index f3033e9e..9c904009 100644 --- a/gen/convoys.py +++ b/gen/convoygen.py @@ -10,7 +10,7 @@ from dcs.unit import Vehicle from dcs.unitgroup import VehicleGroup from dcs.unittype import VehicleType -from game.transfers import MultiGroupTransport +from game.transfers import Convoy from game.unitmap import UnitMap from game.utils import kph @@ -30,7 +30,7 @@ class ConvoyGenerator: for convoy in self.game.transfers.convoys: self.generate_convoy(convoy) - def generate_convoy(self, convoy: MultiGroupTransport) -> VehicleGroup: + def generate_convoy(self, convoy: Convoy) -> VehicleGroup: group = self._create_mixed_unit_group( convoy.name, convoy.route_start, diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py index 7308b0e6..86e59e0d 100644 --- a/qt_ui/windows/QDebriefingWindow.py +++ b/qt_ui/windows/QDebriefingWindow.py @@ -1,4 +1,5 @@ import logging +from typing import Callable, Dict, TypeVar from PySide2.QtGui import QIcon, QPixmap from PySide2.QtWidgets import ( @@ -14,6 +15,57 @@ from game import db from game.debriefing import Debriefing +T = TypeVar("T") + + +class LossGrid(QGridLayout): + def __init__(self, debriefing: Debriefing, player: bool) -> None: + super().__init__() + + if player: + country = debriefing.player_country + else: + country = debriefing.enemy_country + + self.add_loss_rows( + debriefing.air_losses.by_type(player), + lambda u: db.unit_get_expanded_info(country, u, "name"), + ) + self.add_loss_rows( + debriefing.front_line_losses_by_type(player), + lambda u: db.unit_type_name(u), + ) + self.add_loss_rows( + debriefing.convoy_losses_by_type(player), + lambda u: f"{db.unit_type_name(u)} from convoy", + ) + self.add_loss_rows( + debriefing.cargo_ship_losses_by_type(player), + lambda u: f"{db.unit_type_name(u)} from cargo ship", + ) + self.add_loss_rows( + debriefing.airlift_losses_by_type(player), + lambda u: f"{db.unit_type_name(u)} from airlift", + ) + self.add_loss_rows( + debriefing.building_losses_by_type(player), + lambda u: u, + ) + + # TODO: Display dead ground object units and runways. + + def add_loss_rows(self, losses: Dict[T, int], make_name: Callable[[T], str]): + for unit_type, count in losses.items(): + row = self.rowCount() + try: + name = make_name(unit_type) + except AttributeError: + logging.exception(f"Could not make unit name for {unit_type}") + name = unit_type.id + self.addWidget(QLabel(name), row, 0) + self.addWidget(QLabel(str(count)), row, 1) + + class QDebriefingWindow(QDialog): def __init__(self, debriefing: Debriefing): super(QDebriefingWindow, self).__init__() @@ -24,155 +76,27 @@ class QDebriefingWindow(QDialog): self.setMinimumSize(300, 200) self.setWindowIcon(QIcon("./resources/icon.png")) - self.initUI() - - def initUI(self): - - self.layout = QVBoxLayout() + layout = QVBoxLayout() + self.setLayout(layout) header = QLabel(self) header.setGeometry(0, 0, 655, 106) pixmap = QPixmap("./resources/ui/debriefing.png") header.setPixmap(pixmap) - self.layout.addWidget(header) - self.layout.addStretch() + layout.addWidget(header) + layout.addStretch() title = QLabel("Casualty report") - self.layout.addWidget(title) + layout.addWidget(title) - # Player lost units - lostUnits = QGroupBox(f"{self.debriefing.player_country}'s lost units:") - lostUnitsLayout = QGridLayout() - lostUnits.setLayout(lostUnitsLayout) + player_lost_units = QGroupBox(f"{self.debriefing.player_country}'s lost units:") + player_lost_units.setLayout(LossGrid(debriefing, player=True)) + layout.addWidget(player_lost_units) - row = 0 - player_air_losses = self.debriefing.air_losses.by_type(player=True) - for unit_type, count in player_air_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel( - db.unit_get_expanded_info( - self.debriefing.player_country, unit_type, "name" - ) - ), - row, - 0, - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") + enemy_lost_units = QGroupBox(f"{self.debriefing.enemy_country}'s lost units:") + enemy_lost_units.setLayout(LossGrid(debriefing, player=False)) + layout.addWidget(enemy_lost_units) - front_line_losses = self.debriefing.front_line_losses_by_type(player=True) - for unit_type, count in front_line_losses.items(): - try: - lostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - convoy_losses = self.debriefing.convoy_losses_by_type(player=True) - for unit_type, count in convoy_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel(f"{db.unit_type_name(unit_type)} from convoy"), row, 0 - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - airlift_losses = self.debriefing.airlift_losses_by_type(player=True) - for unit_type, count in airlift_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel(f"{db.unit_type_name(unit_type)} from airlift"), row, 0 - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - building_losses = self.debriefing.building_losses_by_type(player=True) - for building, count in building_losses.items(): - try: - lostUnitsLayout.addWidget(QLabel(building), row, 0) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {building} to debriefing information") - - self.layout.addWidget(lostUnits) - - # Enemy lost units - enemylostUnits = QGroupBox(f"{self.debriefing.enemy_country}'s lost units:") - enemylostUnitsLayout = QGridLayout() - enemylostUnits.setLayout(enemylostUnitsLayout) - - enemy_air_losses = self.debriefing.air_losses.by_type(player=False) - for unit_type, count in enemy_air_losses.items(): - try: - enemylostUnitsLayout.addWidget( - QLabel( - db.unit_get_expanded_info( - self.debriefing.enemy_country, unit_type, "name" - ) - ), - row, - 0, - ) - enemylostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - front_line_losses = self.debriefing.front_line_losses_by_type(player=False) - for unit_type, count in front_line_losses.items(): - if count == 0: - continue - enemylostUnitsLayout.addWidget(QLabel(db.unit_type_name(unit_type)), row, 0) - enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) - row += 1 - - convoy_losses = self.debriefing.convoy_losses_by_type(player=False) - for unit_type, count in convoy_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel(f"{db.unit_type_name(unit_type)} from convoy"), row, 0 - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - airlift_losses = self.debriefing.airlift_losses_by_type(player=False) - for unit_type, count in airlift_losses.items(): - try: - lostUnitsLayout.addWidget( - QLabel(f"{db.unit_type_name(unit_type)} from airlift"), row, 0 - ) - lostUnitsLayout.addWidget(QLabel(str(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {unit_type} to debriefing information") - - building_losses = self.debriefing.building_losses_by_type(player=False) - for building, count in building_losses.items(): - try: - enemylostUnitsLayout.addWidget(QLabel(building), row, 0) - enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1) - row += 1 - except AttributeError: - logging.exception(f"Issue adding {building} to debriefing information") - - self.layout.addWidget(enemylostUnits) - - # TODO: Display dead ground object units and runways. - - # confirm button okay = QPushButton("Okay") okay.clicked.connect(self.close) - self.layout.addWidget(okay) - - self.setLayout(self.layout) + layout.addWidget(okay) diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index bd7f6163..10219007 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -4,6 +4,7 @@ import json import os import timeit from datetime import timedelta +from typing import Sized from PySide2 import QtCore from PySide2.QtCore import QObject, Qt, Signal @@ -132,38 +133,48 @@ class QWaitingForMissionResultWindow(QDialog): self.layout.addLayout(self.gridLayout, 1, 0) self.setLayout(self.layout) + @staticmethod + def add_update_row(description: str, count: Sized, layout: QGridLayout) -> None: + row = layout.rowCount() + layout.addWidget(QLabel(f"{description}"), row, 0) + layout.addWidget(QLabel(f"{len(count)}"), row, 1) + def updateLayout(self, debriefing: Debriefing) -> None: updateBox = QGroupBox("Mission status") - updateLayout = QGridLayout() - updateBox.setLayout(updateLayout) + update_layout = QGridLayout() + updateBox.setLayout(update_layout) self.debriefing = debriefing - updateLayout.addWidget(QLabel("Aircraft destroyed"), 0, 0) - updateLayout.addWidget( - QLabel(str(len(list(debriefing.air_losses.losses)))), 0, 1 + self.add_update_row( + "Aircraft destroyed", list(debriefing.air_losses.losses), update_layout ) - - updateLayout.addWidget(QLabel("Front line units destroyed"), 1, 0) - updateLayout.addWidget( - QLabel(str(len(list(debriefing.front_line_losses)))), 1, 1 + self.add_update_row( + "Front line units destroyed", + list(debriefing.front_line_losses), + update_layout, ) - - updateLayout.addWidget(QLabel("Convoy units destroyed"), 2, 0) - updateLayout.addWidget(QLabel(str(len(list(debriefing.convoy_losses)))), 2, 1) - - updateLayout.addWidget(QLabel("Airlift cargo destroyed"), 3, 0) - updateLayout.addWidget(QLabel(str(len(list(debriefing.airlift_losses)))), 3, 1) - - updateLayout.addWidget(QLabel("Other ground units destroyed"), 4, 0) - updateLayout.addWidget( - QLabel(str(len(list(debriefing.ground_object_losses)))), 4, 1 + self.add_update_row( + "Convoy units destroyed", list(debriefing.convoy_losses), update_layout + ) + self.add_update_row( + "Shipping cargo destroyed", + list(debriefing.cargo_ship_losses), + update_layout, + ) + self.add_update_row( + "Airlift cargo destroyed", list(debriefing.airlift_losses), update_layout + ) + self.add_update_row( + "Ground units lost at objective areas", + list(debriefing.ground_object_losses), + update_layout, + ) + self.add_update_row( + "Buildings destroyed", list(debriefing.building_losses), update_layout + ) + self.add_update_row( + "Base capture events", list(debriefing.base_capture_events), update_layout ) - - updateLayout.addWidget(QLabel("Buildings destroyed"), 5, 0) - updateLayout.addWidget(QLabel(str(len(list(debriefing.building_losses)))), 5, 1) - - updateLayout.addWidget(QLabel("Base Capture Events"), 6, 0) - updateLayout.addWidget(QLabel(str(len(debriefing.base_capture_events))), 6, 1) # Clear previous content of the window for i in reversed(range(self.gridLayout.count())): From e80819fc06e0c2230cc0782b0d79f3646e200d74 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 14:17:24 -0700 Subject: [PATCH 078/438] Add campaign inversion support to Abu Dhabi. --- resources/campaigns/battle_of_abu_dhabi.miz | Bin 47608 -> 61547 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/campaigns/battle_of_abu_dhabi.miz b/resources/campaigns/battle_of_abu_dhabi.miz index 39f64f72cb4b8715fb4a44a12567e6df24166b8d..e7dbdbe11d1803358cf4b1996364729535f38621 100644 GIT binary patch literal 61547 zcmZU4cT`jP6R!;sP*_nwP+%1S=_0*bLHbGyEkx-MA@p8U1e7YhgAEV@2`zL8z4sP+ z@1fU(`fdWdzjxj{!yotDJDEH4nev?(l7AF0U%7qZ(zR6 z&fbUYm$iz}`Io!yd%wjVp-ZL9N&(@|V6{k@?a49G744LxIy#mK8~4pousmqt^su(R zAD-S+EJ6J3GRW4R0&5UL`UBD0Zx2M>!9dC^@A8ilu6+zMuA;-OC ztypI3O0WHPDub$3zJpy9`gCK@?P$O9aILI}E@q)@JpcIq#L0N$XRl6Do2?Hc%ynR9 zzvdTGxPK&Zy^x6*_x1kWGHia42%2>ww7v)?GkNTeOD}@8R5)AU1Sge;6>h;A)H@qh zSH?(jhj75yiKE_)IJDco(~-T$<_RAiYyv#t*?H=IQd^G=wOB68FIw+hhvd|XB%&vn zgYNSflzX~+?Y4>_CysG5Lvsn+@C*U;adqo96$Vtx=Yiv=#}@~kEH$>yRx?Wnbq%(< z+mO@6tRs=q<&|ETelGNBCR6#O^F*Z#`k(|Ua!`cX*~s5mTbAiSjC=IX1de-INyvOy z_kwp&oh(+Ht*u1>ukk+FuE%1%%RK3KTg&)P1wB@4`|)V~a;ekpq0j~I2Nb;B6JhrL^S?jdd*x9Hr2SX;&tkm@X`A^(pqr!^Mm-e|XvEcR|XoN_r_t4-MVPh?%l-X7hg zoiN#$ipC!8F?ix*5>Dr1sL%@|`TNdvZ^xt$>TNz_WWbQh6qj2v$t~KJr@NfTgO#{< zxSFw7r(1jrpmn|u-c#Jvsxvxm^~M`CbER~0(P`&J(8g(rGd@HVw-e@d*5NY>$YZPvkVpAAG>Yx79V6wtyWb#2-^-~ z_Ck&qkS_Zmhuimo`-KUhy^Ufo`_KLpiugT&jg8i#^`Tnicw&;Yhn0i9+j}ashr5>^ zX+{yp!Bh;aBgd+uazuUfU~R;H)Qweyz4_$vBN6tDqm5P(b{(dxf%x&&Nf@L;OnxL_ zzg@nk7Sp(EAhA#c2sCoNLtiN-H+HbqE*6~)^0W_`o63j*#FTDX^Y!}Ya)>NwGgXev z%|C3dwdX5hFWH?NiSWQ#(|KwGq2OU3JvZg9X5Eoz%{aTq&0J|+BBtn$(`s(l8Lstd zvUr}AMFF|*L4pCo*8&CNb~_npIXyG~23 zNa6T=qJO$NjBn5%`FlJu^}R`wxRyM&UA|?2-&#eDDOnk?Wyozvgnfvq@}t|`mTbO> z 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 Iz#U=YmBUSQxkGjnh;IScBelKn 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 h{L|3s1^%yh$*DqYSaPLAr4b6{>85b_N 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

--7-$UsW73@*>5RH21`JLYJVR6|aweU483 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 S1Pt)&74W6J1wI7<@&5ox8y8yu literal 124162 zcmb5W2|QH$|37X?q?j3zQjD1>OSmbOE<Gm)K} zCCOH|WS8AVA=&qBY-9Pq&zza5dvD+S`TZZ~@#uJTPjBz#wLG8C*Xw;v40+dya`6ZV zaB*=-b8Vg5iJM1obIoFRaq)w{adC8acXYix`l%yghq~v$;{2*&<$?_#udwiC1f%X% zb?s=aUe_<~ZFUOv`5ELEdbYZ4M1sieU`Hc(MFNcr>^R8mcGqxIUzr6fH zn%O{NAdGE`{ic`Otk%k`N|r+Hh|Ozs<51g=%OfMZRW28=p+A%atjzZJJq^GSXJ*q2 z&bC4 zP#oY^@o0Wk#qW=`5+Z$}G__UBcJ@Fa<8k)2FIg@tJqK}~GM^+hCTed)XHtrn7M{;8 zBLcjZdfRFeSI1XeJ4jlL`*{;jZhF0ZgFy5fOPahjdMtVBQWtT)xNzRK$Y*tUs_OPpscC|2edztrl&k5wJ4KC>dXz?OALL4HY%EU40vo z_`$AdF=lPP-`Kd=cY87za<;FAkw{p({wR7rx&UEo`~+9zJ=K`krMdQ*RQ92`#Cxxg z>xBPQTg`}-$`V>b3#H|z<3C&Aig=gp`UK63_VvD0FxlHyWu;;pFh9I_GIM#w>gwDL z%GzpQO^DV6{pi|qZ0&~^11hUyQy~*2pQdyJ7TR&%!CGtHyS(Spv)#wXViuOiQrBi3 zm-{buF267Nq~T9bobRI8Rg2lJOfL6@u5=viDqb2YdxU5l8Mj;>r-yXSt50N4;HEbp z{2;ZqI#iPv;IN*P_7>uvkVAz(ShX99ilb+lHeU*S5}{vKmnjowk>48N9_izui(sch+)xd}G%+b_GX#Ya*sRdC5*TYXTwQ^VT z9{C37G>4YEc4_$qT=}>%l3ie%+-v8zSU@TEU0QCdxp`@F!hgBQMPuaiH3lPeb*duU z>Yz&TYRaXyFPH3!mZK?BMQeGTQZtm!wI?^$8uC`_r}`;UD~lCXyGsJ5XDd=EwIyqB z0;j8v_Kk{bEzX0#oNoS-cWG?s;o2<4DhfPdEsZbuc3fLa z4)|R2d3i7sU9vhpRn@A3Ca&08t(H9!SJA??uV!u(u3n>enl6TokBx^GKH^$kn9^GK zR5LWWLb*0!ykW1Gm6}r})o&%FmHzCExO{qYW3Q#*%5Ce9F2idDE6-h1k}{P;hKIz5 zkLC8v>8=cq^{z}c&QvfMnKRyAPwqdWBSYZ z?9BcSN^^E*R7dNt#h-i%ZqHC$^3ELUx;&XT(6eaedQF*9lJjf9HM80hzdT>`FNBHr zZ)a4x#((#l?Ca3ngkG-bI2-A?+E+k~oKeXuFrO%t%lpy0c*LzBqVJ|)=lF#~0Ys6m zh(9PLJ_Vsug*qkM9mJq$WJ4 zZ>n^S^)41j(lwMOiv0?rr%8jYiNen2>6 zukHRM`Nu$}%%|~$CnY!K{rCLdFm_^?zvJa6{gt2gy!xcC8PGQ5xw~p{`@_rE%(ND! zKXLo6M)h_)UhYo4_CO;5vv?W(K{00H=#!bEifcc5udeoWJU)(Ip8ZtrzIp=uTn#p= zz~D(q$KvH)bu`6we(I(vL-}xRiI2D>dUp~td940O+1SYJ<&@K=vtl~YdcsX6pEwTmE<2U@t=9FwRX+5^JAB$|Dp=T zZ`;ILUEb1oU*Y`bfab0m--AEyhgOl4oi)#0~6AFq3V zeA-pw-`;VHa#^b7)9UO{?OYeSu61>}tk$+PyDVUJu57>+TurqL=;2@WBV~o+UNsxLE?yE?nrbZZSHM zi)qF}JY}{Gya?}(J6&5pKQeZyY87AY`Ei&yThOZVg#L6eHQ-PYf2i`O+OE~{R*FO% zDRcRftN*N3$!blP-E!XAw@)L4IZY{NE6anbrO^`+aFhVDs;Rk&gA-G94y!M!l7 zKxS#z1L>M(5wnd-Ha5|eFJD}e*1m*hp5R_~5p+tf%`>J1%&Uk$o}q;I^)~pfRf>x* z7hH?Db1Yzuq~%9{G!R-+;zjDbcU^p?)BaMk3T?LDPHXM+{0G-WYrE|)roVg<4-N40 zUQ~QhG#nCfxs#%{tLLYEu3g1;l78~ymp|8-x@ukZUljRM>x{_g)0ry%<<3X1{k3zt zY6m)PRFQsO<-c@;Jhisk-)27()%xK{p^y62^{A8U=udH{1Lmg722^Z|9>pq!jIBl& z2duuGz%ATF_!(7}uo zF*@2N>vrQ_w~DYsN(G?`rZGb<^ zDOa?qw>mC z9l~}hse)&Ed5d}j7JWG^BGdLne$!gL+4H>ey&=x&uGsf6lB#HIBL;dhhwd*TV}!j! zh1(`ES?aG4WplqD!A);-o7j5(PZ!0{jowzU$Rzr_8uAxeX#nF`FK^W zo^n&vjPf(eeVnD1h0G)7$F0`uvW70RlUZj^SdmkyyGd2w-m>Z6oL zc}(d2MDY){Ii?|TV9tjf!Zx$#9Fv-M$;N)n`L1$P<4Sw1^_kuj>aJF^*}Y;he_^eR zLyj|_!g$@^pp7|`Q(o`(Aa^fxdcW&l?%#6ytVpJm z@w4T#<=bb=5r;(*Z_iJ47v~)lMih9>e=&0NNQipK_^nSN!S9XHbV75s)!6DTb&XL^ zm9kg88iU~*&#E$?>8P`A8#M3Pq$Zcfx3$ZP+l3zzO32yL)1<84C<{L-L*bjjx6@nB zx~Y0pv`pfpK0Z}emmCbgd8bJx@u5?y-LKJpj`UH>?Vc5{3JsNSD(5#a|LKQ`@cfTH ze+Ku8S{t0u3C@=}AF-!+U1mn;BC+Z6J}raVSch;&=3}+LBB4R1QD&k}Cfey#*e})N zU-F3op>dXoG`%R%9EYF^@x>cj`o%f0Eu~%Y4zb}^a(k-#jQMnC>niU(W`$Qw2rkn7 zZErzCUVYC;;rs@s5jA2|hVE~dRF$0GBI%axQT}2Ox1;0f`urWILPt}VdMwY>E{0sb zuaf#W|Ki)#Ln;%Y9lvV{KUp8ce7vjHt6OP)vU#po(Of8XIzHYcE_B#@^<(P2c7_F4Iew zcFrI0s@u)Kbb_S+^cSItFK;x5BSJMW`46^4d@zcLQdwLyjz0OpHrJFA*P^vk{gx?Z&uj(!_|8@A zy)fZqC{lGDcX}ys%)mJB;ef5%W3S_12QQO>2bYRq4?c4}Bty1&^!y%c_b8Qox`ZxXa}4#=m_tI>zEkx*j<^{o2c@`)*yqt}z(#02jl2 zyg%UDT4Nu-4-ao~8jr|bdod%Q`>?TzUJ)}O+qfHgGKV6(ZNU~v5won9E}Zv5BgT(V z;`;O`HbnW;y5VbCjNXl@monRvjB7_I(H}dy!G9f$7-BvL$0DA7II-Wth#zMhCGseB z*f~NY&$vH`wR6r(7oI#kPANP_i91frQYsAPpHDJA0ZvVmXd_XiqHn%PWAs*~fWiMT zwq1MI+qoXG)O(x)KZW)b8?|SH*5efxw8fbV$>~R<3v(oN9=Gp4$Ub&iq8qe9oiyXx zJGeFp$Y`2&x9*%Th_W`{h#|E%h=1H|XARj5(d~5C?g3MZ4SVMEqY=&_nz260k1y|Y z1gn@=`_b;q<-JB{DK@v*Tc;0yo;Mf~l3;w`4u}m>vlX&C^1E_f<)dU$^8G44+Z%^xdMPK7Yb=!9DA9VtH?rAXUwYrvvDy~s9#=5%3)>=qGp`*P;x>}6$UG(*{ z%^{Rd#`#NHlcW7t(f6Jd2OyUlpLS^xi3c)P-9C;s64>CRrFy^`|sDous z1m@mmuQpeGxAEXLSTh>TCZjKI9^FGm*8 zZ}Huk0N1QUX96Hqvf9bLnz&ob|1huC<7*SYwJ~~TwEVCB&>6kAJ~?Fhs;kB7^1|8d zX%qRyD)DE0E$#&sT_0!ja?9!_R#&fy*Cv#3#ZJ{#{~Q~X+?=AUX6hJTy+7hyrSgNG z^ABS7N5uQv#!emkq5rR+@03fclB)&7f`r13T=*@x+7@m~I*W1p7B+A6(~;!6(kpGT-%UZvA(vM0tntE6QY8%yTmAB7mYs7wW34)3LV z?0fm=kwKdw=F=q{D&cgzbFr~8#c=PnjN-CCUOqIm5OV*~it3)gd>*&uyD4!$9{F5L zFIj7~3t$xZH>`G?_nmQi8ToogTiF+%ro#2Ht;sbbr3o*hz5Xi9C>hs2Y2*3py!uq; z_~UttwPfR%uCqCll<}JvTXy=jji6fQhdwK$SAEhx?HfM#xwfpw*`T7KsBqh%n{z@0 ztN2^(y? z4a)Jq@6~HonY-ULxuS}m@_BGEw5_HJM41 zjcSR+R}&H1Q)$Zmo8RykAG2B`)E!cKI76D4WAwKLm8TtBIwt&AYvvQW)vt*{xd3tNo>C*YEh$5{AEOyG?7WqOj}O!pNdM-`X5{mEr2|`-j_1 z7d_wCcfK81(3sonFPC1c3YRWd zqR%XRG+*7Zzo65!EDB#WW$3^q>@~f^u(*W#(DJF)jhTn2yub5GYwhYKjq>D8goXK! zFCY3eLw$+OwU;+4zO{Tvy4gK)X?0nzmw)ky|8ig7v5$RE#~ ze-YpGv0#h-^7RBg<#)0BU(}hc1uS;E7@=`)XML~MmzHLWkF7TA65Hn~i?v(#K_0p72vt(mynNXYsF4?|ZQZ=57ATHPP9VKb47G)~s!9+CTqkbl_^R+n~_K zQt@ba5lZlA;q>cYPmLGdJ=EG?=oD`KXTANqVJFx0_yv_~#J1nwcs;OxXIC9wsYFUo zFIx=1`9XR1R8vUw%ebPSmEJc-`H)Ps8b9`j>pmC?QSZOD2ctQf``hBXluF~0Ur$<| zu(fognAXM^b^2Q31=3P_X?I9P$I?zBC_&%X0LOSA9pJtAyeq{ZeJp$6*p&Rszv8#ti&oL< zQqdA77HSPVU17}N%MsHzmlvw4LR~U;EUIh$IZ%>nm`+pHAFUyMuzQ`djE}CY&OhG2 zy4;=d6EKb`@rURk%E8l`^D_hJ=up?}8GL-=9cPSVu$n(NVALLc-EpbwC)SpqhNGd+ zqodJ_DxY5{2X(yq^-S~!-!st{UZsbOrSyHV+Jm?}VkZ2tr8H6We28q^_+IPcLB=ba zlOW9;oxfT^nW5I_Z4dp}dYkcTQlM`tDNJ@Ys*#RT?e!T%RTkk#c9NZOdc!VV(XVzX zGt7T`8CzMgA5mnNJO5tUc`96|!+U5ZU|ROdb+Id?BZGB^_FcN0kGQ4t;9dA3Z2{y` z2rqR~oJ)r|h`)uTKE=*(LnJ{X+m1>*uGLE!fJRjHn=SeRCsT-tj4D;18bKkXL$uUAL z!6G(>6dSq{7t|Bd`ojZ~gvL6=8~kLQh?LVLQX>a}P~%0M-VAya5-|>FTmS`|pFwZ+A9`NbdTl|~X_?C5APdax>ox9SuJlvLgpqEMysak*ScSa%) zv@&?e$-4C#qBj93TQ^23p#OZx%tD8fa;w2FEO}lkSPb4LhYg4g@Sx|qG0r#8#uF%n zn3sbO1mUOamd=<*b0aLj-2l&*K6-eq_M&;SqKo?wYA*GQ_tmyMPR2{rVLSvRbx2Wz zCInI)mFOX7tt_Rwe&O?^RQG+KUmbj=s&%)wo1D3fzxoqiX#1ac{=obq0KKS^zsS*J zf*O0>o1UHAo>EVd`a{A1o!ZmHFiX9G??xV4C#>`a!$*2eCH|_Xbm+qI$uC2i4OFtQ zQ6_+jJEhD{*Wz7XO z+M-9bWw=n<>yX?8=F4Tufzi_}=GeYJ=6aHDMZM$3shnNEC?It?<5_^;48~J88$C@P%dzM2!(!o2W&AzUhhkSqw`(Q`x$lK$Qy{U_WH2)V~(K-V9 zD>l+jUq=KB0wXqU(!O8>oemF85Xn3pytKl|b-iQSk$>CBpMy>hzrER*qPE4cUy+Y$wem*sZ$=cYcOmCykQgWVjJU%RP=*HjWR zsjs`~z%Q+^Qhnu(;Vd=ARa?~0v2D>T$Kdg+*J*OFoz;slPqUq$+{79YGMV~WC}{A| zup`%#gaY+6zS<-=VLO+pmYo^;QqBj}b9?v$%XjalmbRCrCTl~+2zf&kmnpR0#6Y=i zf#tE#(Lg7`nqy=XZ`c9~$@jN)zrRQ$7u7<5{xVYgzUy9y&Y^oLX__yip` zAnn1w0JF^M+tSQj_)cJ7@HE%aUP+Pv6d{Q^lA^hn#Mqo$N+)-}d?69YDi8TLcjuMCf+awFwA z3x}fMcm0No)^D$g%;iSvBG{oE3V`diEzA@v_$G35ezCP}(HV(&AQAE8YQTA} zP6S;ECA(0B{Y}I+-eoT@fM4F=vqONuYeE|Kkrdo1DZ)(3EGzP?qnZ(HBCOR6_H{kB z#$T?Dju&x$Qi(@#uz(n_qjnN0_%;i;;6&H4%}4Z4AL(!uJ2}CQ10otS`;grkr@e{1 z+Pxu-ac+~;6MVEyob@We>uu)59s~Azv1bv4E9(e?f8W6-TN-SzAKp2x3$lR#ND3zxOs1gyyN3{Sox+?6L?b4+5&D6oi;&_QCLCm!iFb_0 zZv&u)?S)iH4>{5fo9gB=nf`(3zNYy6`(|?@7Zeb(Ebu3*Aqsra0WBrCMGA%hi}!nA zM2RH#_()golvZG+P1ye?0~sx5ZnpT^Xq8RA*D4(x-l*L>^5{g-kz5Pr94D!=I2rhm zgZdWdUT|qi0obR&$yDm^@w41)>7~lom6 z5Nz+l!S=wyeYMvJDA-B>Nbc1S1>1~P>JJT=Bb;M~f^8MaZ}eC}Kkfrk{-We`I78k1 zjBtdjcKpA6rEeO;T=aEBayNX`0_6R`8nu$g} zJ``lb7?1=ZxBx;KC?+pFHu$b(8 zG z;jpinB*KYdg0Ok>g+MXvR}jPWK2wKE_ldhC;ja`4_k{_IeH+6L)L~4+x@AH#^&>1v zIn95?J$qSSDh$K(@NGt4=PWjdWiT=_CxxLnB2g`iu zHHF2;4Q7aUP3@G<O|16|K}!T|P$7=RKtbdmr==?QT#vjB{lL52ng)6PW> z=l}Ms`K5=m8%7(3aHPEe>B*?y>32itp3^g@T%l|P140Q%MA`s4a|BYBldI);VaSXm zH@Ha~{L^V91Cx41;`+B3Jny1fpKI=aXJ}qzV>-@D>-ma#*27SbecaGWS&7x6mDhAD zcNhz@Gh=iPFES`|*e&@v`LF1Qe3YgAyHgyT%w5gSLZ}v=A{73>6vbFmLSI?%-x` zIy}X|?cme;4UdjJ5?IY!nWBNHqv4ba0u@u$dv3sSwK#d1%96mJ-~i zV5h%z?Uek*{&1D_bw(GZD@95_ofx`tyh#755zBZD1LeTR1JWhRAzi?tSRuTWt{r)d zgV&j(wtp6+?NC!UlJ7;{6eAZde6*qTW_YCyrs z*ln`@;Wi4ze2MyI>2xk6T|P?@)3S<=5hSorW&`smLC6i~Bf}I9aCkPnbyP>%Yw^%n ztAPy|WU}yc?r*Fzbv=UIA6-jWYFtwUOJ)gTV98wYlKd) zo|GyZy@j@oe1h`xtC^8y!kij%NkNRfvHm>Hcj z=ytBKOzUR4zJY^U3{fvU*cq$Va+topnFh8F(zgu#^nevPpwVU=iXitVdk+aMBPQDi zO{TuJ8=}8t!^x+i$}OUAuZNHff|c`Z+yu>CwPg4J#{zuIVS}(NAYJYJq=vpj7pf<| zv}tBh?+O`ZJTAJ3moTVuOjyW&cvyP++G`_K$__iB(nEC@*wvMS#e3$xztaU5SW@GxDXP(jx(mguE5jxHJ94-%N zIvmE5PH_;5DHrV0>j-p3$oi>$32B;xj>g_|>0T%wK}z z&;Nt@2O~-onvU^Toa4uiHO9rcwH%)3p+R&Xvgmgt32-)JU+^_O`1f2|I{=_hft?u$ zQCYSmk9hosZ%K!yhaCUFqt?HUNn{H<23I^dY1a4yeHsICHK26^!C6!Tc(e=9g*&{; zBOyVi8!y2UqSg_Vln3XAvH2bGpKnB{wrw=I}3l`;Xebq}5!5Tv;& z>Q7Uc^MPn#pbE>g;j$cn%Nh;T8()>O!MgQ_Bb_2ftrhkC_I5ovtUon;&L!c)>1V?x zx-h7=rs1LQ8H4w?dN4znZ#1JLz#)2qXOq}BTHk? zUlB1T6|8hN`AFEr86R=3G)LP;K|mW0td($Jwf`Dex!Hkr*hgA$r!)(m!;uT8g)EI^ zaDA51GyFU~@LQ4kB?E zumu<%@;d+<>knwlbh7vm&{w%(Kzmf%;yVHvz@L~T#0df|^IXvlx#Sn4exnhua85== z{HFR}WA^_)D8@rJlh24ZS&|D&*Y3reSs9LJONRX*2DA+Ji9-hpK*_sxgcr;@4-gI5 zGEt!vxe}5+n4iKs339-PzBvbDUD~1)L4V16Ku0cJpKgwJx8voE7!03{F6(Rn?DpZ9 z<#!m2WS}17cs3oI=Lg|PQ>@SGae z-9Bt$oq?|j^^p6Wt<8gsHM@9K&sx~}tbDk+fKx9OcTAp`6>hCGJkqosz-_n+mUD}w z3Rkxl-K11^Lps8u$y>J4@tu+kn5D=$_X=l|C$8DUx>o%rzT+qh!<{fFqKZIaqUo~F(IaVdM7xx;Jo0#P)AmgRL6xi}> zj^@aC9!0{XVZ!p?3OpcX>#w}KLJi$S4Ynj%IJp-i_8xI_Houz0s$D46Q|~aNt;xQ@ z`+Fg28Ip+pLt5sSY{0#KiF~@tvEFa${s&@~$Pz)8p)u1#?dD@?gM0{GXj3~TO=elr zR&f{U24VqD)LgXgm4BwBp-rZ;c@~sl-~lAIB^nk z3&1h=CidhcIo&-8D5jZBuql4S)f9w0=fKV2N>q~@ci3K7jl#m|^`3`ZxsRk1ucTln z3#r4kUv7k>{N93C=SctFGu}!2lQRECwAV_ z!|L2ZVh95DA{Ye$luk}4QY-}7#sd^DaRxzV%0OGM@!;GI(?_3%W92F5UR& zb5B1$Xh>9$J)?q25qUb(JTHHC(tp@}{Qs5!^vEJQFqa9^%hV=Z(IW_cT{f~_7@<02 z`A(l=@5Ladn5!GP`;{L#Uu*74kP%`nWCKbKEUKMEj_o@FS5kIjQ;={BmZ7=vVH+?g zS@yD_)qB1SE_YVWXcozOc5x?GNFq)po$l^B{y60+r=*FcL#82wOwO~x1K|9$O|zT2 zwVOJWo89uuEquKlH>m;1gu>V1s`qvL_U^i&hMr*qmwNsgP4WqMF|>}OtmBN!_w?rV zQgD?EQahNe>pry(N`T0;DN7o1GO?2v((WAYj$S+UxJLyCd0zEFq5#YZ#lt3y6iPQn5L3l!aZetLa^ zxTd3=LJ3TTw%cflE>+7EAR|sv?zJLxDLTK(i{4ZCd3Z@xiS?Te{p0{d2ZKg#YDJu8 zs*!$jU+|3~mz3MN7W)z8vnB#{>CN@z8tA~3wqTFt6Ibw zXP}2f8WRR;3fc@zyKIIxA6BtU#I@seSv?c72P{DUgadlJ!j|oDlf|HkhzHzcv1lS_ zHHq1_WbzKqtZ?<47u{f!a9DmN=Q*we*CZMovBN1zy{5?H--3V|V86fSds} z)L+rd48&g{;{{>HwhnsLtv?hg`+l@=sXaEYGs-%mrO|x9)A+#gxD>n)2dt~XU_BNB z>;8v8!QFx-?GEC>3B}{lP{{h$lA2R35={%=S>MI==3b2U8^jwudi7$@Mv%in7_1YN zGJsoasJ%iO7iu6Es-y0xW4ln>cUzHv`TI zEkk67AVfkD!hBV^`1i}W*iSomn1Obcu{5>qJtl=b!hINFF+g}(#YP^|f!EXqQgpvg zbAwrV%dseXy0TBb$vJ(HeQE3yhe`>c14eJt;R`~OZ$E4HP-j;080vcuZ5tG(&YCw<{1Xz4|+vsCdO{zNkPH-?+U zI775C3}Me2bIHVd0^dWziLzY>8npcl$w{-u#H_N%{f8wJQPb9Vu_>kFXYEdEa*|pl zun?%i$*vCgC4FZ53-LfC#dfFl#T3Ph^eE@Y*@=$CZpeLK?VX=E9CLlGLtV zKV8E_GTel3`f6BgMTB;Xg%Pj~ib&HPB4Ijzi^NJP&a)NtpN}%?Gmc`zWtwx_{e;xt zC+jqU_$z?XAx8k!F@mA95%cd@yAVXpjAUJF)03VlJViR`eYzJo;CbQ3tOP7xC zbV5s~_u5ew4}A5+WId$$DeAQ-F4Q`LE&^_=Zz%UuNDf<0a&PDl-NZ$ZJ^CKM_+gY z(MLsyeQQmglNfZwY~wD5b;=ZKe&t<6PvQGhPA=vX+D!_a!xw>OT1I-u)=AI`0B`=E znP}{f;^W|M7xm8l_|vV?izc)gvz*%!!vwSlnq?zENd-X>D5-MREz!(sQx`BwO?dG_ z#H6!tNW*nZb%xcX@fm++!6aKFq^Pt4tX$BVeS_V2uAM>cijYR3Di=g5ajf$W+d998!io@*B*zG}by8~h~-nHjhs+1syY_Y){ zdOspn^bIR$0P5P&=^(Z0Ln8;z5;SQ1X9+u{OGqYW%HEbL#Ow$zqk7 z?a^)_5??;vzW5z5r@*RDqb$jAeOkmcp9eK`pDgn2h!hGrKDVbRRbq*}^C(7;h_kYh zBbr%RKU&(e55@FNBuz}mLIVIMmz{)CJ7oeuHBz8gE1U~iqx_`EVJBK7fDdheXZB}s z@*6mJ*>{5mgAt8A>Cs5Kk%gwF_0z?)tHTQW{;ehKVV@ILmsQq5m4YZDC1Uyy#2u`z zYjr-#ruraOFj?P2erPO0t;d47hY-{^B2G0) z7qxhbHuCnKtI`>Y7uw>?6(W8muHt#M3~eh5=tm(GM6#SU85Yj83H5mPnC)Op z;h-cA$$-w18`Ugo?h$Lg0hHXZ!ADXy(}`vSGd5T=7(vry3GwieCtoM0K@GFV2%;ER zvLz5qff>0F2*Vf-%>cmsI$1}#UTvy#9#v*QD0cGPdwW#3(Sk`XU=S+@u??I~{{r_7keg1Ze%hjxYIHU&w zQvHvD=>K9vgvi2M{y78wFMSzj$yu*jj33$H_1!sVsDuHe?+Af`G|fiBWeze~fxx0y zNjBHX9(sfgFmGb6ohC`vBg&o7IYBhIBtv_$+_sqVeAXx@StoKG$Z8O|xQ(<71yrL$ zz?8lP0|z)RQ$ZkcL7{z`+MC&pcO1lT`)3X|I$@=rm{#Gl!L<5_%^cSDk-_6(rf38O z%Wu0o?=rzAE}oMty!g$v@L#=$=PB*+>^C~rAmKo zjgB2RaoGqZ^ls`X2N916;w(tGCRVCqzS=*d=9AYt5=`Qa)$fK>Sx;B!Z%hwW^Hv6G zJiCS63~t$Kn!e==W?2JfsSAhz1YbX!L@9QgfY&s$i<(6%U9MajZV7i5QCs?@&`Zxx zvF5hd(x$Y<{KFPsrwKH8sDnFZ{5U+#9_l7GfCb(ow@78S(sCe@=OndX?PXNo>vWP# zi%HxP@`Sj)aS@%_T?IuyK&wC>4n(VzaIia01HQ*{#}t5wf0)IvAS8242hxw`(V%1c zbnub`F{>!9R$bhgu4GmJy^`pu%SJFZnxtY`=RHjo0f-o&Z1UewHcoV82$QVd{8x1= z)VH3CJ4OFl@dPo8Si!OjL%$@LQd)eS<}LO#8$cr8u>_@hjuGXQMvR?DOPbw3$*`$j zaA4ebzhV@gZu$6eM z0xR(|Ut`(?s*|Sz&avu^9)T!j)qJ}>QBl1mIW#|0j3c9L0C{x`PdJBRCtfj5;w|Pu zJ-~oO%n6u4MLFeg67P`lz~_}sn*!lYVz+hmksQ$&d4dv%l8`>3grbo_gWsE8f0c8Kd75A&+X$?Z0M?0fHa>h+yO>iiCfn5LMX9m#%B zd~f)w0!kPD4kTR*7#S!;Zw^z3#Mc#qXb}q=1d?e8D{1UD5h&Gja+(7`g?pQZ4K5So zrkfPGQd{H?%LP3;HI^Kn`P(y{zG zcUW&y2IZi>HBxKF@#018{)E4W$z3#D)!NF&6uzK+>qV{E|mO`{sS;ghzLNvpRjh+ z1Gp-JbNK|W1D7skAwxzs3x%bW_T$yaF9(f{o$tvTfi9mUPDFy(Enrn5jZ3PaK@}p@ zVjNagd;{@-im?2aRUMeb)b~EE0%~>|PSn-vt+-vp0m`4p(c%9g#fki<6eo=91u<^v zHPX!aJp<7!Qo5*Meg1*Q1Cl7GX&A~y;8v2;XUsnn(EW0;Uz6_rn&kCup7eZ8HId2h zagGSjo*J@G=y;{RCoe^-Mr!8ce%RBZ5PL4pjP zLzb8^(?y$LoA3^YF|;hQ*oV?JN({}3y&z;hVJf)~jxgWQA6^vPM?;o7!bX|1f&@_# z6D)INe4;-#SZ#6i?4IZLBStADL2pv8Vaggcc76aM?q5Q}Sr$`)njqOR(~S?^Kt-^4 zJV)sfaY|L*T_2mW^}3v%)@hqmV$|kyF|R6*vm2-Qpb7}HrEQXZzd|5v{VkYOp~98` zRgn-ez4Q0rQJI|3qf;NHTTO_U$PQ6^XW`tCOo(J1B%l?6Fs;~1hOd5R;AjryIRUg>uq-Z}}lwuAh1_eQD9Rdo3_&E&C0gF8Y zH9B*nWD)E+I)OPJmSJLLcABggCNvd_Ca$1~1{aBOG4G>nEYO7~wdSu})MwF|C?uo` z0SX@I_VJ-mvVfcBqEMEIZOzl%xi31^% zj}tOw;gA_Vbdmr%#zBJ#^PT}LoaA(pQqW5#ufGv^fN*qw{aE%7;`kx+=Qd;WDasXe zeZ$I?!kDE(#^}HJj~%OHU?;{&fL#*#P7d;vrs2v2GS&{tW>$D@2#fqHZk=6WsQd{r zW(tRWQV{mZQY>6*0QEgkOoarlo#@K%SA%-oMOtO(n|G(ykly7CG+fO?mc!w$C5iZ3D%freqbg}R{{>N?;%57PzoMv2e`TG!3 zxSoggc}jl{_It(DY^a#ZY#j&pbeOH<#H8;+IJu>|cL{Qu#ky(pi6@Hb&r>MM;nuN3 z_^%fan93^9;NESAJNkeIDGS<^VcHj5as!b-1Cf|c^(zNzOH|0AY5uUEZ z;-Yn9Hy;=@T0EsJ`7tEEdilS^gXaGs9*8-b+RPdmD!m`*nBV5sJZ#+}ZSA4V7J-15 zLplQR@+NK8tr`$>VY|fA6o6A6z|#8vsUC#&6t>{H$yceTm56B~6sKyX2l-4u1ffw7 ziaFR_B6Wd(11e3E z)vGm&$C{v1*`0o51T=FA7G%bo# zSTiYwyG^ZG`gkv(D6CRLJRoa_W-;Ic*eoG*!jBHGOEFZ6_wOgykSyGPR5z$e_%PCA z!NK8xSHV@arac_`7tH2W-_fzbh~UD7U)lp}C#3^uC(Do-oMMs z=vY!UhvAkyo2|*&z9CK5kyzz+m<0Z)@#h{!+}WY*%{A)ViE{C~lu-r@eKaz+#m}Qt+)?RtP*QyHy`=P|9R_vD z-*KaD85p=ajRKGWZc;}j^btIGen&A$C$oD1@;s0;WYh|f?i-(PyPeeEZGBln=ic5E zMH|DsI2Ip?VwR^Xw_t$U4)`B*A(nY#i;8Fl`?+=LKI?`z46@%UWT()Smm1=|zMM^A zs5c&y-(7s$?CA;O-53JsvVv~1$}(H!p?lU*maJ=&7?C6FB&`3E2UT&OjD;};r*lF+ z{pCj;a>6#W$J)K6QO418hI{A3oE1>^U+EQyIY4+#`I^{ou-85W)I#APy}t#eK(daO zOVeYigwL|4GO-2{S%>r!Q=ja%BmBD_wCxt$L%EJ12D)XKH9!lXcz`N8M{9;04ph-W z^=DXTQ*PhZv}j~P%OyoMy8E-WYr`&N+P~WpN_LQ|ng6!}9Z_Dmd*U8h?t4IvA8UY< zY2AHZ!MYmA*xAN5^0H13s$4I5sIYq?0J((lIas|QXY~N*{6}8`H!8Q+*@Jp}Fk$_S zLN>1GiV}ZK{@NNaCe|hapaj<%WY%NZwsf7npMAhS0av~Kcetsb_H4B9q9ZOfjO;dC z^6}Eunvm9$mqfzWbB;(9KB8dukjO9}jyzGogKEYw+d#gxP8(+r%>ws5r|%Lh43%mU zaHh4df52V{Gpu{+{gb*^= zN(1&RzjY!yL3H{4-`jdX^}?XetjW^Oz$w{UJXZg=VM6c+wbta&LSre8KmnO4NSV`p zgbOF2EBpVWv4^ZJ(0lRuiR0x~;M{=>LzYiZe7!OK}-(Vy_ zejkX2R6UQBuJPGP>&{*ncGJh@#U=8vugrt)T!YUBz-TC#%NcMCy4HETE- zUS))rPktDb6e)3b5P!T%tyYG%hn4*m$#UM%PTnDhA7tqrfn>KpcI>bZ;KLf=v6bIq z1waAw5qq0+lZ*^Q=2FQw_P3gCQmPfBF&m_qM3n~y#c!mcvJwQ|%{V;uJB3tGmZz!P zi@QbI+@PPnn463IG`9K7d5ROb1^9n9#p#g++5J&+KrF1h&!$Xlu`tZSE;a(U*<5TS zyLx}*juVN;%_*`qGBlBY!8i#PS<@zz{wIukn_X=^RnZ)Gj*@Ue5mg%7l$&DeY#w94 z?#u%4NR-K~u(=x5@c;3F0N55whqHinI2o1!v+E2=bNN9b#j`QN>vy6`v8NgN;xf0= zT)yW!oG|oF0-_US{PhUjuus543X#jBMV}htB1M~gRA_${o*Zx=eN?4NSD>-$PXHSN zRtsUn?Hrma#KwjepxNwaJ;L8;>Uu(aKK;rrL#5;oLungpa&GrpBzOneD}P6b`|3Ju zl@w(v^c~mX#Tv*uU9BivqZDP2)O$aTVOx3%ND1KatWytXs*ktMmJGMjZwNG5JG?J_dp+y) zUcq;AcvRRStQ(=1T#l-LDvfmqKRHMdE(np6EZ;DieT7i6#|Uq&GKtM6Q7AMQs$Kzm z?ik@W@(z?b`(MqG%V}a=(pv=w?i_8LD2cf%E$^U=-qiS)3~lsQ`G&B~*xiIs9dZ`* zo_E#7`!>a?9~m)mfaa0vqfBZ}2T8a*`>$!O1(}*`|{9gagSQVyKvJrwWdPhj8oo zPu#EFqSDfDUo8C~-zm6F?&-7ZWI5emndb(6@v$TD32KnZ5=hh38}q62ttivrdQ|;n zv$I{UB_R?KNd83(W&d>#m?yr8oOI}C(O}Ar&_+^Lhb*yF(54zmsCsWgo_2@SHG8fTAXID1T+T_BTt6tF!1n6oIE>@FO$*9jl= z7jcB3y3tIdZ->1DbsK^G9hfl~g*oH+4X9js=8S2hWQT>}+{|_bY!GA|*#QM8ft{=7_F^8qym_Ap3o(p?Vn7jjN z#7o});_g0Yi%EaFEs$MLusj2Uy$j62Q4AHtULS1yz=+q$>s%?{&wJsb{=>e{te%{iA;@9)sR#`(bj0)R@1zkB#4 z0htMsco%Mk=eXuIkk}u6fR0r4H!|>${)UP%R393`zO14ijjF}@B7$5IX?4N2JPxk+ z{X|e$c%1XL3Jk{M>^acMI&p-jioQd3+ayD#kpFHB3g~p3!$OAt10ci_bl}E~dIUn@ z7kLj!{FBAR6)+K`K>u<^sFOi&+Ak+7${#HKwU-edH^J|HfsL^N^)fvP@wxvP7#4&{ z_kgT|{c>~D>2iaPH7}VRt{P23eE+XELXl_o5AtyTvn!w<^&5G}L7h-UYYhr{`F9)r z0X8aOaT!g=8BGYy<8KahknF$EJ@g;&p%D-S ztu}`Jg1vw9;aUOEgnTp$g-lrIn7&m(7*H4?a$?M4nuz!h18l1Re=!Jj+*mL1snW*< z4hpIMXWV`$s2O2nWdDahpooK65kADn8QDmPYqt}i=~RETzzh4k2p>X75HN}#-*QR@ zvJ(}z`-SWwA%6i4ip7pF;^2~yKNt58^01SE>KR~FieH+Jythc^5&4|YZ`ezP5&B3z ziXmx00Tgn!$GK;| zrXxmY|Lo~zwhs;8Uq8(KnY|CCt}%Wenued*T(C#DN1TFYUxOu?4R3Rx0}8;9u4)Lz zr=kD#++QTe{*zoTf?O7EK)%dBB&Hg?THL=n6$$;KhDTES<-O3e4H8J=&q4}d#c}jt zRB|7Y9fe%>JNP%45rAZmVM1u2_~7(k@j+qyz4VxyQv)4768m7aCB(ci)FbmAU7yNSdj>KEBF^FJhE{l5Q;>9w1f0d=*m`*=V1N!CD>Tah5sPm zopTTpl7pZiL%us^IGyuv6*i5z4gMl=8k2MDsW7sq-p97r`8fgJq5 z(VFUC97DsP{hw(KK7>a0m=%u3nF1^+Ze{}rh-IT4(*8}nf8eJ7(brOtFm#@PY&0Hu zLQH(%A3@R$#a*Z{dxX0GiZ*P*AXE&~;{ggz+!So?{C!sZm!j+0AvAn{m>8;3pe7})MWwa=^H0%B0b#m$d?%k@V|A}k*C_`Y$DkZqC8m-0{-zTFFv9;RN7 z`n8Ki#7&TrJuGs0USa=waiWbTdZb!2>CsVRcs7m zqg7wSbdOBm)wX|kJ7_#GksL;f!&0U4we0!Pco@A_J3iajm=opS!~JsTm%lF{1%SaR z-|C?G+o1)I{qBCNO5V@ix2?R#g16n?e}(AdeX>Fk?>3_q*JNr(sRMz`RZ#p1zPOs< zs9Xd7!pb%)5aYTZ`X(sVh5ZQ&pm(uIf8@_*!4}Uuh7mhD-5tB#7lU5hU#x{X0Bz7R zwHR7N76C$PO^82spa~Vr02GbU+uVQA!T1^(h}7#S#0n%n#c|gDC*ZgqyFGpkk4i$e zABVje^M3OmD~pC1)fV8~$bKPuw4 zJ43f%l}@ZMvAnK$6k7i8+QvG{>p&IZP%s??N|6j|{{m(MQlMrJt!dRjG zH>gJxk4H20mp4zU+GNN{o1-wgc7`#x`iL+2ZzEGYP68Tq<^2I>$Y>*2ZGqq4 zA0cjIB=k*dC932XXH`n^ItAPIRrFUs4Q<;!$U9lBknNJQEHi`kJNt5`DN`7n+ZKE? zSQQB!|2u%fT9&_Gw5xX-PT4%w=)zEMyY9A#Ay&m2bwQDBfvZ~1?uIs zF(Z2KN4Az`?;_-0){PKW>da*@igsow1Xk{|i(tj$qFX*s9mVF#bX!{(rkPe-@Y%$o zG?QTR^N3=n3BxkEc8xbL!Q+~COl?e!^y~F~ZJ*0Mg<0CCZOeJtT8W83ZLlaJF8ST= zvqEX5*F0L&M+|X!;-0T~7uphB(?Hq-u9P)7bf$Yjr z>V?v_BKd$i>0aVnuG6+kx0QmRu_yceyIU^GOS|Y6=Z4uQ34{;I*|D)nMXt~vbKi)8 zt1I15l!VA3IrUcY4dbKj@04_r8DEk~+kFow!zp9xrYAa#D!FHpx|rN772kI7dO~i_ zHno1YTE0R*?pvSW!9iP_ z08a-G@fm`T*G5!$uF>#&DXa#8@6GOs*R}=T2PjJST?(EtKO$(eNqziQM||&{pVEql zfo0143cA1;!b;Eflka8{0RCPt{09l7(P8=F9bxgw$X*T&oFTy)CzK&juxkq=Dzd)V zc?O@iMdLTjdKczzh6{vR^xAV^O1|)xp)i93BbhJ^R+v{FW>B~59o$4h3I`RVwS@32 zLO0abui(L!#er8eaC7=XFQ352V@c%S_q&Tiwu^7@)Q+oiNOy03K+VbLCdOib{UM9A z(bLqB&afGb2_}<{iCvqCDsR1En!{~eimu745Mi@j%G zg}gpR1gYbd!IOSp{Q)&6pVjP|TAOBcL95s2MxR$dB|#FkXFZU1$0t*4#sEU+2LV|sGaPCmQ=8v>j`hg#IyWE8&wU)^nv!(7@cr_FpB zT}?R^%tK)jubaNe9pZ)@vQ3m#s5$$fs7|K>pG|R7*fWtMyVMJ3h2|ax7@wcf6HJy^ zZNLy4i0DJ@gR+QGN_+5SAz5RSzoR@(qF6L)Rtqrt;@-~GtDy`KLYmX!-{?=2>;IiK zLe}z3Wk-;%&0YN_^X(Jik<)Sfy{%mChNyi9;_K<_Z7f!HdP_Qu~e3_Znig^!F_3n?!SU3$ez_@*Q-JT(sbgoQI=L08e6NxZTT z&4qJ}a%HG})kCT?@l2Kdcnp1Mk(Yed1TURXD3DoZ;MPVBwJmZmt=J`{JyVNYZm^^j zOur(B7c=Sb#(PC%B;j~}v2Ds3@asF2T(6b`%E*7L+n@iILx0AqSu^crSrfO{ z50F(+kaoKF&A?MRWkYqAege;u>)pv(hIfVSV3!3SoLn*$HH_ND)6Pn9A#&VM>=CEb zrj|PjONHN1Mq&0;?w*0-TRASG^>HkwiUYiBw9aJ9M`7L#Ohvm>Kz%J0eHHKe>>4VS zg+cS7xFQj|lF&O>?po(T%Cv>e=hn~6qD{v8Ggx~n^LUO=R#nLDOim}Uesrn5>TI$z zl;C6mW?39~84smO(R#xdG%BFJp2YGa{gZZ{l0d4;sJcYX?=m>!s~wzZ9_-}UOCYg?A4D+| z8xf|xBo)G|d$Vks`Vn_AP_Ue$Bf6Qu|g&sbCN0S@7_N z2;=2vrU4MEKU~5rY5jn(KYecU3Ptw8+ zs6A7RoZ_u`R>}_1_y@ONZ=)Udm1u?3x<=54BUkn9D#VcIfrvKVT6aAAU@=7NpARsQ zbeL;=c|NogBtAzoQ`%eH2f5{^GhhEY)G*p~ zH?S2L{vvtM&>=>+{s*`A+RAso>Jbb1xOBdXm)I}RX=!3Qyoc!FaASh%d`2B-2l%47|+X-1{v-W=&K1U zGV-qqY1gfkBMe(G&2iZQK;s?$nw2uYUfgdPj+$%Vo*!72WIAl6%x{NB;0Kw}#b=e= zWfj@3R>EYPS?3O~rc9ZP5vvSc(=0B|PEZ)rWHct9;kBU#qq%Nh(fu3d+r+opC`Ap3F$v; z&23U~b-0ifbu>(steuY6!^e}*rHA;27QPb}4e)e9u+YAGEV9*2Z(Wrq5#}YcoP+P1 zXy_3Gs#ikn&Ca#sT!X4JPm7VG^fsx^pL2QHH{7Ja(akG;+5q=4x)AzuCBPJD*6)_n z!K;V!bT`6_TBnv}d}3p~teJM1%Exe+0vkXa{P8__>}U;rV~M$$f>tB2gcP++2Mr=K z!ljBuL~`t=umV1D-9RGDlO55v>aVX;QX3Y06f!6esIM)0gZ2!ioLNdL2HWS9ayB9c zDAe>I;6tO9ajk$G$nZ*)$sV~LtM*rcU`ARtU8AowLDmg?koYIz^CG^HBS+M0w!K}6 zO{pvLvy4Sc+piZ17nLqXB7zxRG8GkOnTpaUXUtZB`@xe+{pnkWPuPst;@v7pQZ^Sm zGPQ0$@6=Z?k1~~$TDBhK`UQ_FeXUqo;>;VIzV>Lnx#ZTuwZs*I%)Cl!_}O~F+wpLp z8XQr2zuNY&c29MG#JzH_bnftQda^q`a6Y%uBUG;Rs!dSHQ|SE4^ZY$xx2oxC5p(48 zhugiDA4e#RL#I*Dhs{1{a6*w>uGQ1`=DtErniL}SN$al9yv&~T@a_He=)=v$$-4RZ z6JNYu$o9g{+<})Kv zmqQ}9P+@r$k$TfmeFWgicFMXaQUG!NlX?>kvG~j&%2B^Bb_6fuv zY#ZPbN{IQo#!pyx5IspT;2pX!p1#ndeNYmznMdDNlS$n`mD5v#O^k?kCnE#M39W@R z@$1+Mz{m{K1&}$fCikHLnxRSpswFc0r_N3)y8wWVG6ZpD)qWjHmxyyDHg*(g-2&lg zkucVV41Yj(EtpjTtW1uE4RnTef*Ow?Y?V&$>EiR!ktaY;cl~Iq9lJ!gn1E^a4_3o8 zITd?SC(m6UCDMzjs81?d$7FDz)IuX#82E1Ubt#BPJP$&iDgQ$9xGg;_;8D{2nuu_k zh4v~Eg2=p~XgsaI@`U-!q8N_DsB1rK9C-&5`KpdK6FC!k9}T$%qY%1UsJ+u`1X@2X z=E!T6@%{_pD&F3W5BLc!nD6q4k^A581@HB#jiaYoxSk7i&);ED?6DOz?bptMb=@v!r3Q16WC#(V0I%mTsLjdK>E?lJvCWZ^Ug;It|Y5spA6 zS=bN@z}8k*{L$R6N7M1u#l~T zv^_r_*N4!tj?c<7y!pM}qOirqlEGc^xkDG^0GVO(A8b+|X%9q&8Dz4muj#oO)~4a* zxNsRwr{mBB5M?rk_8w(t1d6KKhi6Eiq*m_nz{p9}NKA&@W()5ElPMUK%uL|Q;6PTu z@V3Fm;xj7Qn0{w~swk31WZ8i{ zDozk3R-Kh}P_~#k1o%tR<00e~kPxG*McFTq2GXzw5#g>rH%CX6Baek22t_m5qYa^D z15+rkvS(G*kqN2}KCU_d8~*j~_%X}*c5PeWI$lyW&*#$Ei|E0UIcF3jTG3PT5c4%W zD||GNO33wmg((tHTqs9%Dgl&GpvzTq#*NO3>zfL8L05tlM&u$U&z=v1K5aTq1QB6MJt1hdLn7AOY761A}lMPSXxd&h_! z8WT(h@P6g41=NZ*RgX^ba^;s7;B}}V8@3GqzIeSTCcrDD{7rfNov96p0)W+0K`uy_ zBDyM0q%}8`K7_uXl}=hgfS06z#GqaXNydz50@L4U@k!4rO|*-)qF5~Fx z<&{_Awf936WvzZ6FJZnT+VTWJ1T?7(W(A3|cY|nVkhA8bZMXe9*I(E_##tX!L~K4y zgl0(VJrBcwX$BWmt#$YV4;~x-$>xc06;Emsp7F=#{G)iQB7YBs=BFV~`&m&QSAe*H zs?9HB(h-)tA5$Bn`-F+DO19q!FE$~WPGrv&NE0K!S{d<4HnCR|%j>n_-|3~e@{UEE zwYCK^6n^bRWoWTF{8~G#i%J7BLtem!#{`GKRdth#MTzhy{Y9bzs`O`SarO)JfWCNH zRLnS-)n^uY>{9tTWGc@$H@!L~RB&O*6FKUeT0H0~{!UK@Iz0;F$<1PIikrhRn2^fG zBR=J5_>`ed=IVYE`-JFjaRMv~f@kS5OwX!*#hTDnpqtoX!QRPR$b46nkY2t2S~Km? zpqor)=p8&K*T}rjvsm!UU{sqyF;n0PUiTlIEFgoJ$ky`jLlNi=LIKE~|bbjM}ym`Jc zeLjG`RX9qj(Kp20*6MQ(sqm|G4!&QU5^6K-$?dqFnY+F|2)Z0um@J9QRHUzOiAoO_ zdO-C5bnjO6fq?K%zk!udXWULqTA!Wb`#X!meyaeUT)c1VW4_K- z*3FNH4)f|4l_C;{%6jx&KCU7|ZqIfbsBt@5c)j)PG%|Iu)ExwmI?gSl>Y)kmi~Q-E zuIeYtj5A{U^*(en)LK360^Vw?2qVd6sT_S+^N3C zLq7-LJimbko;SNEcXxR^$o5(uI&&urZ<|{0J+F&MPWmbond}w6K3t7kUa?GfK<17V zN0r{TmOJALTAd49GnC6=1Z+i-NB=C+S#<<9+`G-l2%Ed3={SX6XZ6%{gkL(F4Sm7t z&e7aVRClP)T!bSzdym|Q#lV=J`vr!ls~x6HPklwr-RC2e2z z4@97N&a8RUH6`3Cyg+AZ+Bhe>iEUOLUnf};>98jLV%Ihnjl|EzSA6Duv%R~oZDijt zOO9@gyn~mW6OZx!ri>sD2YOmiyHM6{J5(+2XVHn*Vrz&U{yM6ZM`yWr^Yuxv|K-}U ze51oi+qlv;dcDJ$##)Gb%{YBw+7roYtk-%JZ|j{T7?u@|6?0AOxaKkJ&dlE`>PNKG zLZ{%hi;1O@r_U2VF<-c8HJ`pxC70RWsy=uHzdJli18`=apiJuVUf z^^v_f^rnx%_7MeoYH@=R$yY8|Pb)Li{W>FG@3L^gg$G&n7dQ8B*5D>$S{cb>v37Ft z`Htul&qc`zWd`#$NC_EtO4f|bL=%37n58KEUeM%}nfCD+qTa*4mYC|vZp!m_pV);2 z4MdHxo(=X0BuL+$Z&S<9S~Xl35OiD)+@Q-Jbd&&mA{OtvTm@xP-33Fvi_{%fta@$P zUNNgT93~#UnXYe0`l(25Sak1U|2OHGUs?9QCp}AG|1XoCd=%f4pGH|Z9sBPs>--KH zUP6P0jWWr3!=3OhR3^*C`L#YU9uCUuAS>_W>#652qmWay;9yAg4>3O1V)*a%0q@RN zSxkq>CziVcZE1Y?WG{(yH1$jAzzt;pi^R!oocvSG|Qohp^;L|WHMagzb6wp zVJaKmUQV(bOj=G-P9jxA!Yjwue%p0A@-C`24I>GV*G*#5@{<3^ zO6X<5`FE5SP!c-SqSoTjQ}YK_q}~m3C!v2K0s?s8#FyUrxN>k3O^!KA2*pCRG*nF= z??`17cz+i+9i&VNGLuw>18x!nq?H=FS<^AK;KdDu^`Z7bN%#}5Q>QU+knJ+FINQ*K zC5yFF^C=U2UM})lGGYXM-&RK3-fuw$H|ffEM@Cldd0&o^1QbU@>vu##nV4R3lDF)` zMmWB4`OTrgGz+#{B!sFFa!re9+Xl}M`{Wy5@gO+;G9F2U;$o5DLRIjyajZaPS-j8-GO})-pwg=k zVs$9eDriG;)NWNln)|QNMDFl@DHA*d3I+Du`*t8a)E^ig;7u?U@Ey@nCRk1UhCQwm zfZ%_Uw|UmdQ%fF2mI-g&-+0#SzaRaUd9vmNxQMo$#~pnu=zV>1mSe%^d3?9Ix}R-Y zPGaGM{p1y$g@r&zJ8zT6>CMTm*T=1sJC*mXcN0THS}PB`Nc0WcqY9A8Jq`Yw)VJyB znJe}lE3E=;yw^LmCM~zKEmx-wFh#o#xO+uaUq;`zyz$X|*e+$>IbRu^Tzv1NdQRde zDB!7cSFQKJ6ha{`YHC{Aoc|wmq&~eyEm-sUGV`XUPq+0#;&#FWVm$Y7sJTb}UUfsP zNwA5od~fbnu8au5=bHj|d1cXwL+O*ES0s)}4x`Iy8uqjreqGFUZpX@RjjFOpnVHKg zm_|pHQZl}~y-!MD?0_T zbgbSMOIVD2{bAkcGXd78+G~zWH69&nrlQTW4yeRfIMk-HZiN-vX-8J zA|PMOileM-g@ALOQz0(iRk=h;aWDYjU+iW-VW~-~LecG93|g$$E1_mUB(0`+pPaS# z{&<2kPal!daeLq7XuX@pfv`YK~pYqiOzkKxSo zoINcxB`2z?@o~L&ZeHnBo`8&-cFqQG_H3cq_VH5&u@~sg<0T+jWkcr}Z0~Qt=eS2y z7)+u4m0Gz3EAq^gNf$8v+3EQ+4CHX781` zydz><8@MvVPwP=vXktnoGe@=nz#MU|8U^_bOKT-$rA2}C`MQ999XNAX_JZEy^ z)HAWl>1TKPWK;Teo>8`k6YFy~2_dkYo`^*)suLl9X2H${K*@I@#XFOk1PxJ~Uh#wL zH5W20Cxr@`$Xstj@s6_8?H8`+u?SG`4e#AVb0fsNu}iSaoaOCgAtSDsKF4;29jeXTNI{ntj;=zFDyJ_A9sl}c8eCw_EqDZ>e zs3N;X(zQ)GudsVzT(j=Nf}F?4=A>ew3{#fPV28}xGZR|d*L7#ic>5#g8}R`y&dOO zO5WZM4oY7g6p?&kGYzlT?g1Q|oi7OV`Tr@fg(*H3*m7g)wQg2f)_8>*+^%C-g8($1 zJHWL9O(d}Lgs_c%s6SpYKVvU_$K~KEZHC8De9T7ZfIi9VHKEj- zm*Ha|r2J22$10I-_*Swhm6V4?4V7o%L@8FFn^a)V!<6k`Z1GM*ZD}RKZ{XDHbtC+g8uqc z)Ii~@arDfy^~Vw%_=dd3RT@HL?&bmk>RKc)W20<56bNifNrjFlpX6ZsZ1nvqz)2O6 z^2$lI->#jGOja;B>!!VV!6wHy7FGTXEY^%(4kjJ{`>|aX)@PR@f-mhENqOiP5Wbt& zU@dfIos{mn!1N14yV%$XAx=UiGDLa&gvxJ(2Wgy z%)0TIu8^W3(^z^5n-Lc1uZ(J;qXAMnnz{a$V;`j;#5Jb^&r45^%9$Jz+kVd7sQK{&zg)q-It$+t%53xT z^iruwZ}sqVak?0j(L!f$4~t>OHm%8c^(tK-nhExN(-Kr(ueZ9MDZW#6RhVVC2v){Kmm-t0W-gdxa5@CFH{%|k?hFjACNA-6iPYY`OLf zR%qexr5y9ayLtW?oK&sAWRYwKWXdg|%9vG0&E9jVaDsTABzI~9=F(PbfMnWiJa?66 zMt;Dgh@BvIW^X53zZ8YuD8&UHqa003>!q@qznJUCHlL|uUt z$LN6t4r&dgI!_S^<*N*$` zaAfAp6JaT8M5!^v{v4^sD?PsrkKd^*W3gc%Py}`8;SAYSJRC2XWd@xD&kRZpo}xde z#HyQ2wPQ{?b5i1^4xZkO;E9RSF6VDkab*@*KS5OTT}koIrKa!fPm`S;oxkj)a3)is zdG?j7eS|+f{CjA{k3}wrPe3wuifF-!o}MK1jePT*TC~zqn|je(o`jU2(dIU}na9$( zP~u3$aQHbzJk=y{GrYlSXMIDrm{cCa!=zQMy?WmoIx@teQ1`t!{Csg);e$MRf|Y>f z{wX3h4T3Psw^oIlFXEB@-;0@7*EN-R5Wkmxp1SO;CI@4xZV;_W1~81U1p+gg^_3KN zw&dolxV+K}jM4eeBVQk`&0@CO7>Q@&wHV@&{fHe&*8w!}q@)xL0Qk=((iV0LhtzEv zuBX($-LwmC1WYryOH}r0emxl<-WmxO>?4lwJS?8YS1UW6K15}8zYH!ZVd-cCb6fu0 zM2*_Qjkx}?uqD{09mezjLusUu9uU>^Go+pgz40*JTrhRVuq80=!BK75(6ZNg`IgnP z($iZ>oX)S@CPzCRjny1xOs$>2z!ge3K!?#yjL)UNq8pftRhAbvvs?~`$Yiidas2Wh z&7r^=WA&11;OE|fKrTAouee1NlBJ_t;xiUCuReBsBcfa{qZX}x|H|76eJR0RfNIN& z@?QCG9|U?ve(ALz|H$Ue7b2dJP=BgW@3E*1wnJ9RMfzi;X1_+L5oP-2>4a?yZc9OC z_;iIZsZsIK^7rGsU*2yQ;}fYdH-bq0?tVa>-F`uDs>x& zo~gk+?RTcL+_o2I$@XLPaH5Zt1I>|BlRd8^>_8WBJOzr2kU3%veX{aI$D9~{U(e|Mn;wma5&Ea@?x#V}r7C-4xR=2jx$;!$4ipV$=*3NH5J4XnOH$sT=_M3)a z&zMlgy$MF5tl1vkAOvjx?l%FWu#E!W-D-xVCSh7+6U`1P#*!W6Bvx8lbtMO&K?iKo z{xhJ)+Ag|UgkmbU8W<2dn3xl!VuuS2ZF(%a;ZqZIffbaGLF`LOND;w@QY2pK6@4Ki zYu}9LTa+7C=+od%3>O$iOkhFCc{$>)_YD z20N-qMDI@gTYS7PHcOqgik*r5ia8^Ko8iNeAs(JizHdDJ{3g!e8|Yh0ZLbeH^bH?c zeKgRo*R-f{xv96_-fi>wB=4`>@4FXn6$%+@T|Xu5_R8W~Mq~Gbe?QeW48X;rH`Z0{ zq4>uB!e{3MdN|WkMqTgzEkAoFf(8%c!4{V3uIDSV7@jQT>4w*Y92Bhud|3Ov>rJz~ z@8gF=d&?=spXB3V)u_j1zNq&2$|GCp4)J>JxlnjS&m5}og^b>LzIVOwqZG1|FLGi1 zHI`|fvc6L^!|ZvdHniXZ|LO3YrGL{Y5nnpE&QrWv*#tk5M`Q+*AT{ALY68u?y0bRNVk#p}Yatn2W`o5^)?lrZgB9Hz|u`^6j z?OrB57yM~-#gx5|p%{cd6~JbTl#A6K?arM-Ddsw~+(hHepjFkpmm~YOsU;Pcmo9;f zJuse34RG=O-rYnMtXxWlf5R2QHLn+ytpMd?@dQ z|3T;Ue9u1LFFl)ggIKb6ihIdUYYox5^x|qJ;qkT{kEU*T%E1lT)0s??8 zvWd31yW``Rmhz*S*w@N`6IsxHytnD0o#-KEI54!JL8fP;3#n@n))6%zQbDT65M&Lo zv6h|wiHpQJUvPaeSl^C+U3M>6pYzgo=Wyoa9A8U#jDPJDe3tIL$Oy))8~^Gjug8Ny z=AGEe{GY+}2I-FGf38+BEIP|~-TjXMOGq8&s z`WSxglX0oV+7dp(!jK}^UxutrvMf3oM~yjxj*IYisne@o3xam#H}CCTjVSFEzQeVYQT zSh)D)*)4a$W4p%$25yuYY{VEh61vNrm8z#P>Ol4N-Pb~eWvV`|Q>XHG!$lseCG&oB;atlk!tKXit#hnr{?hurkdPukZU+eyloE3bPl&D+5F^2dD6A{oR`JoDIA7Q5`KVpGH@1yCn_{W3j~w-bDjKiExG^Ge&GF^EZB|cdQ#TZ zLBV1k>djJ}OO~A07Z5Y~-GLbd3eTuFkA1c;u%fRejuk*7NR*oTSomb>uAggDp3~NOjUl?+T>>z8UQe5llMg5+7h6Y zG#w1r{Fy-d_LsKH_LF~^?QOfKaN%NkoLhJ4mc?$)%O(ds*%9w6MCLd;BT$8l@x#U!s`k(F3~i z$c5|s%|yMGqd(w8na=Z-(}c`qwfrtsf9I%~I|>(`xIh674Em+AQ_FhyuO94p@)fKc z$l_=^|4tl>B4k?!wZBycE~lA@!~g+A<~z6DjYtSPc+Nr=pCPiSkDb1Mnl6H$Ddhu+ z$uNReOV^xjT7zAHV$f2lZdlAbg81iU;sVEEQ@!F_k-x>|6bogDFQ8hWJ8;z&$+Xi7 z+DC#q!EWW(AAOwwfPYM@Llf)D!gr09!s37d$qS0+TkOv%r)#_3#xXPi zw!Yo6!Sz{{&PoL=eSJfE9-jC6!Uvz{$qpJ_Hmg%8FJ^=7f#HUNj(LA z`tLK9Y+@+(e-}3R%smQ;Xz9PO6$0YB;GLYiD(#TyYb8~?c_WWroY{`{oj(2B5_nBs zmUxfwuC1w^$gPUc<>q#JU~i_7xuXe!=T+h5bh;ZnS)zxr-KH7!2QZhk_Bt|^w0>T` zg-kZAyeVJ2Y7o4zb~rjX7`PtU$zuLKN&hy@BCSu~Lp$5D&sney#*p~d*E zz0qVwr&r9SdM$S$RUShr0YgI7QC}Ah=EzOi#5CbxnDH(EM)QEr=ca%MFQ=>wcPAH! zGy^OTwJgsn302fZ+Sdr4k2>S%M=b}xrHx39S-IlW8^R$Rd}THi8jHpEezl~UQ9MOZ z{d458_4`F%YA67TSZ{$;PpJmwSx-n-qcXt#tOLwOXlGBWI`g<+`7s+%qp(JN5&eA3BV#Tlx z@TU&iuW$SK7Q`vd|G6@#m8%ps2mSS|m3G%8UQ=c2#VMtv-h3REE`#$>53b- zCfzev6q3Htx|bCu5>RMiR6~c$+W7{3 z$c1s$NV?**L}5l#FYt`!@jfG%0bu!*CuKF~TJ6~hdq0~~!#?5hgKP&*n+l&pOTv?Q zkEsS|x8rq+X5xtbYlx9ZXEYn8L>DoZy&LgzBu}Fr2C$;_SMQ`K?2p`{R@jQxB7f^ZV@ax-Kc4&(~GV$u24QL{GbY zTN2hUYk{q3Ic`psDs7olKntV-eaoW7#Jiv<6pf(6OU%uUw`zF99mew!p31y#e)}K} z*8&tBxIv_*s8diOGOHkZ5ij@h4VvgCyt*MCot2VZhGZh%m?~WbiP@&lK4MAQ+&bkT zo1p>*tmzC_lr4M!jf(oBUGZFevq$vF;8J|bNaJe-yOW+N+F*E-PgABRv}|fw`T0*~ zC9!)ZBN_vs2QbvUxjx-6Ey1e3@!lDA^Mr|p`N$Y%X9DejAGtq`^v<3e6Rja}caETM za_%Q=8yg<=2=1JB5?a{C`ISPu5UnYH?U_B-r8)L=V_eitp~3 zlCo7VGN+MlP~5xE&w8J@!A`I`p6!tfpNYao!4^cq>I(+}D+|@E5JU92WW_LyXTNxy zl&f8_Qvalfi~Xlo-8!)^2M0<3T7?-mRm1~C9<8*M9rDimm8INxUlFNHZ#t*(#x>u zr0kg0KgbO+BFg0eKN@w^4b~0Kv_s2LgDTIIOHfPd2Dj@54NLAq>ISPbcM%mWL=Z5k z*URw(zFgfXhaDajc-X)?{1AAezbTrcmd8^*jtfyKAh?CS@xUU2s6NoG>U9UpC5XUga zy=t{FVyRQGusu*VKdurchBMq4(QET3XOC{ zM~0EzqgNNZr2Br3CjdWNvU;Mj*r9%hV*~pOq!ajxgYjmS*^9>?kD^id4bwu8qItyO z{+7tVit&eP|18A!@jTUs*#mN;7!S#7kcy|$AO6YV8Rk7u!pig*qifc_ut*djE6wov z$lAn454UPy_(yV0JSeJ)xxYmWtZ(wCF?&`@PzDCISe;J92)(3ocSJA_g|?|d8`X}H zLx{vulY-6syi$`yL=Wu@1^;x6rA{W@swH*IZ{`EsY#dJE0)|GcC|;0vF1dLY8$!Qm z;npiDsaoI1pNY9~e&c=X(2%5op<%lIVp`h6aqj5PY&*jY$+vh@OIRw@^5dOsa=rat zHa#qf;oZ?}lJ9C4pihH+FA?V-Dm#_w{Rv}eMXD$3syzFG3n9NJI@2gPnTS>vRT7^^ zVpqEhWSRq1uCd5!)yjQksGVbSW;?4DC^uvc7mS6EnaBS&7;j-zakSDE1O8o+!EK^4 z)4c_o*=oF7>v)nDwlgSZs$slv7$`PXy<)Xk@LaoUjd~WxdJtMi=XB3rqv?ks?g{cQa@Dbu`iM?aa6_pL_DbwbZsnWqAfXW^&xy7>ff_`f)m*dY?*VG51?_5>9r#Wn9dr6D5v5lIXb6C9oZadsHq=#8wUY% zgjK)WrKEjRCJbp|^_s?P2|`E;=uymNexO#yT z#k5h(_jfltmwN)xac+@ssb}g*BQwH4Q_SP5_cDONrd=0ImD(g?k=5Vya4V+~QR2`{anY9Piq7>>ydFn%sZxSx2bc!b4Zf!4n&fso9J~#8p=?pN~>| z1!Au^!pm;FGzbS!Qf&Gs%1RSmwoEcFfEoB|aiXG|4Ygw_L=@D%k}XgyO?jO6^9?p* zB;@O(#@slGEXd-cygcQ(24Ze5Zrmr8#N?f5g%|sGU-J_!s+7-xWvb(- zQ)M>EPzM@cn<1HfZwz^{RV`<`_QOb{dd?4^2nR%d8qm9I_FV~gJTY`6AaPTHg70g7 zaCKiWSeJ;Wt{fL9lroR5P#g%gueL)9BQdqu0-qdKpQ8cC+XB0%a zD^hPZwHQ-zR8==(wMD8)%uljr##+JZHRD?Y=oS($%{X;}en1-(4Yxl6T_##F8cPY> zYcoY-@pn!a?FE=+Ug99Y+*T)=48}i+VF<5OG51iQMBXI+adW+W`uxjzj}9B^@J0u> z4Q%Xdn6Dq6Sx~QI#4WViJR{QwsX!gExX6^I-IpWUE=;Cq(A4-)=3HsF9W7rXk0sCo*=1tzLt@ zQmf}X@{u7=U>Z$P6UgDK39;?rKHz%wguz(K0E3F(IIVR>jV@4gpF^gQ!-THfM|j8+ zAFUyBdS)GWErYImi_bW1em6XR0}EijW52wyLUt1Sq%)quetpFVtzBeJ1!3BUIwJ1f z&9&}LgPyNK9Rsu_4B~?koX+yq{xqDBIyAbm;!G0cL__EoQLQpV{b{gxWn3{zhb5Yz zh}b|z=uAv)d2D8~ zz6}rcxn3%if$ry-(G5_mWf)EMhjEfTN2HvrYd-){n#FCir;>n?RZxHVfi5SU^^gyv?X-6_h9?}%NHyJ zzkir1^$GJ2 z+IzS?X<9d`zxeR!eVedC%!a8f>8;*__*By``6~g=3O2uAPTv2$`lYV!_w_S{-%9=S zjdH)GU;f;?Stk6!`tW4yWAicP9h>|YzqgxRHe-Kl96k8`w0e5?)vr;7-#>1Cu*qw$ zzE`lFD)l?~Uh?kY&T(P>@8kVx^2`sy3hx>-U&ud`4LU-dCB6H6a>gQY=5RVH8H{It z@B8sHePO+O(sx?_zn_&Us~XIC`#bDZ*>dX@+Ye05(JylFQ8@)jA4ew+!1}5AOvlmg3I;+|`Ad zLWl$7f~WnOfjG^_!a0euW;0YG{V~C7$qv?w3o-hNZb&iID*{As4K9wTQnE{w_rRRO zJ**Ub#?3G_|Hdd1*9ZpR*!nghjALF8uj5 z$66Map+FbID+5f!Kdo&PIgV|_WNCcAPGgzFUOJTHXV<57lLEA|s+paUHc zk;ZgqRUkcXTn<~2wi?k~QJm|1FF1HKtyPq2-b9(g_o7%Pj~gyMJk)b4SN${yQU^j| zSe)I`tB7%0ho3rhCKd!f|?b}gqYTzn;L9G8N*Cmt1R%m4IUai0`@9lKR z@-d#ne{BK$1Pu5|EPFq2o;*%7s2rZmR(jcdZ=Cq^s*b8`TA14@%FOIos{q1g)w+0% z^N=zU?ZMwkzzkF)or+xS-|a)5#-K0e!kDS4l2<@KOqW9l=`BN2gsQTD*p8A4r;rM{ zHU$J;^EgP9>w#zwvy3~d1$D$dqePmvAjC!58ed?yrq_@_kNT%eOv%G zm%)vMCEL5Z3jl3XDJa5T>R2a-6n6x0^^3Y`=(@@>qa<0FV(3V}j4~yVEIp#f%%!QYZ+ z-1|Jb9!Lur^;t^bXnpTeRndZ`uFRGQXW)*h6pYBhfl)32a=A(Ou7N z+%0Z5h*B}3YJLBnn^+o&1O8Z4qaqQ79&kRyjc;mCgl0>+tC<4INswF&QCp0R(a@2> zRn{CRf7VHnlDQ*bbs^V7 zUIo;9@Y?Eh^Cs4i?-g#=f@{IA%fS+=TY*`SyA+YTtH}b?2p@;l_SiyjT*gAau20GF z35B90k!UZZ)_W%i@s|EMTEBqsrYcs$x2B91<(_0hM4}QBkd&p?Jz(uN)p#uUE=!30 zn>644z%$v}gP)3cgg4gKvtF(&Dg^+`o_$Zk6pbh_LQB*rIsX1k(SH<;> z_YWH1eCTr|Iem1*ZttuQZu<+tw2IYRZ>DHGLI@iiv4kjzId)Z)IejT&9Ny>75i>{& z(ZpDFXE!V~_VbfiD_bci87jL~PHl?Z*dlerf5jE0wF44JL54q#dasEvmqf=nv3=}2 ze@|aaaBcZzQQlPn`UasYwvS3>G~UO;OG@>NN>)DmiuZ}d8QW3x&_@Gf2C9hv%+CkR zPqSq*IvcA^U{sP!Lt)@euKR%suEFzw-V$kkFPTD%8oQTrAerQv?&~$m72xs#PWTuV z=*99Hm_7iEydu3u%sh!9AZ3y`MTFBh!uUhaS!7fiI}SEON?hVKoo}G6OJ^(J~@mrHxB=TnfjxO>nLm5Rf$;`nt+ zh?F!sLBy~9%fxwWHCk$a$wtCq{23v<7)VM z$Bj%G7R0cb1=j3q*j?y*7RMoH;>WUXa)0x3uIqe7O=ROOWQI3LaDpS z89=l$VMT@R#;YmRmG?iPj|-pnVwSrgxZkUk1#l0`uva0(TC@mq97(n1O{(wE{hltO zP|yju*i(?L>;I!UO!&)#m9tUov$c(;OksUtx7Jj;(K|vTwQMfQz=23Qa`Guw&*9$d zwR%E}?JwG&%l}yZdHAE4DCkFNW@g=s-_ztKp5ysBCuMJ|0uQBr%U)G@x$yNxP)P9I z{Ylqe|8EVxdmA4x45mK_%^2E8=5PBa;b8Sr4VW>5YMyn54BO1_V@Ffpi1*k)^8d6r zKY!v0jla=>$k*4mp?$g2!3Kh?{q#Ip-**7cpVa=>^Cw3Yno38X56*K z6$C7o7J{#p_^SLcsnxH2a1eZacAZ~j<3P=nU*aRWLrmcJ-!~?;*VkpwJH+l1{y#dz zDp^lne|Vgie->z6dcwvTP|!_7Ub3@3%BHfxn(ouBlzZ?HB1w`0wWu7vdMf9p9Y@Pf zl+hr@UmX1U=Nj$T0^y-Io(wv}Z=BSx36q$`VVQMvY1VB--#EPOc5hPHOF>d$nR4Xz z0eg><4y>WoefNs6fYBT3Mp)NSQO<0rZmRfV;xLtcLhr``i1uqDMqXM|aZD;(!N*r2 zz?+CL3CqwMDv7^#_NhEAJooX%g50{JzhKAU$Iu0M?ai*lpK7MN9&pq@O;xFzp>Wgr ze%N9pJoM~zsw(OI2iy<$KCQ>vJY4^K9FN|-s`P%B?VVcTSnHjU+_QUk_fL*C2k&?) z#4J|Xyl+2QKHM_F39g&AuUyU8GYwJwHhuDCW0L0QubaOQcLN^tOP$p`3DXf_1}&yyw6U@d0ue$R$t$=4|N&w148~5k4mTy$!6FgU;11x=4l+Q zPol{eVnRv5bnXIJAH^O8PDxkwXM1C39N5*ya(FqMgonL3ef6^-%Tz2lKuRGEKka7w>O7#);Mgh zkCA=Plh0*q@`LwkZ%&S|jrNB&-g_ZOcXm%gq&Dv^V}0|qI$lhC8zL3_#i=huDwJog zBK)>j*KEa3QzQht^p#x#|o z!CJ^`n=v7!Fr7g@zWnja0`rJql;DeQ-MWL7;?=_}SDTP-6kMskOGIDy3AX1CM&(}T zXS-K>1;))b;pyoFTVKj z+NQXiXu8I-^Ec=YkZRu-`S6qWX5r^snI|7RY#Q2wmx6m64`>F1i*{5}-_p{yWE?&y zb&`57pcdSFz0OSNd&O}`;>~KB;9j5TvM{OE=vuQzn}#X=jJd*3G=rms`R}*JMkO1; zOcVn1k)KlLR#Hap_ln)dY#wg>j49cMZ-BYE0vGh_oxrvE9g{%X71fzkT(5wE=_(WV z#g`}_L}83+2wV*Mimd^?6Pi}miV;V)JViL_p%{W0IZ5A7nZE$)ZOf^+P;ZVV+Ye6m z`g1p@Bl)b7PL1fCS2`kW5Rc^znz--QXy#X$>X5FNm=37 zhU|E+y51MjzV0f!p+12}Yk5M4a^w)4#^c)kY3J&_8SZHqf2D``6@ASek}P;hm?VaD z#@$NCD0>0WE73=|R9r+U&1qyBH`}~K7Ed}C*>=>~1bQh*IPmROTH)cURP5gsOFur! zK*-daKHa>->fyC$VC$UIO=G7V{A$q-Mw}x|;Va!M*7^xtvEw!P-u}+SJ%!+0&rj9+1DR%$ zKGOL=j~ACvF9RDf`w>r=Rp%X2B2`v9Z&`TG%)Qjo3^xi9`5-5AXP`cjwqzKUASw8z zPsUF8*lV17*JR@CO4twAS3JszoZpL zA{llBV!`cu^=s>z;)%&PwrW94U`mU(*bwS)wEp@tbso`;Wv_Ov(Tp(+0$&H$;5fy+ z1YP0rq9c5(wJCG=NA;SD@1AM}s^d*saP$te1KOo*qa-W8MygKIhZ4?H$+^iepr5E2 z&fpWAZ}k`U_c59S0Io{#HZlq(E(*@j_xQ9?z-4s>b-PuWu_QKS)3 zggfLEe#yoYz;F?o_OvtE4!h{c*o@WynfIX$uU{tYZ;t$_HtPd@5SHi zX(i3A$ZFf8%%;d`1OYH`6|l_mu`)f`vL7&+VHq=_WoM+sh@nT?*d=4cpsvP_ur!7@ zb-<_ZIBE}_1WM0kFb!ob_&?agn>4CYIC+%Ly5#jpP%2BbZI^nL)TwOdofMa)xpb}t z!TEDBdw5Mo+X^R;vNJEC*gK;3{_lAEn1=Lfes5`$FiYo#L|V#u&K;WO;|Sg&?BUf- z&mlYHjN_-*8`LFeoVfew5ec(g7D=vP-V=}cmcx5K*44W3Rl8MQ|11xil4ekM`t<~D zO0Q({D|0>}8-nlT^XF|hM^7HUSQ8^;8F74s9{0ZTiR+hq$s+KsV}+;;&1_V^t3E&f zx&`e5NFnQ_WjBcKtZND73`I0JpCVzVW z(t!*s)P%|9zwDaC4;W>YeDUne@XKlQm%iiIsdO*%1CW^cztiuCazi-1+LO z5Q@Bc+qGKvG?gE(^cRx3b0)B(qpvAdtx3%+?_Fu^mL!sR!x-rm7zcVEDu zmQ0;tfq%v@mY3~+?ZhZ}Z$Ck6aP#mjInhc&&cssJRiag54I}3Pd223ltd$#z#=zA_ zl{|bXxByC?CTFoC@R$9d%S6Fj%h0TeS#f7-zD(0yP2Ox>1-R_xe$47nE$Q!vH@Aj* zdD$W$vU>3!mHO3H-mc2CJQT;GL{E1yq2JUQCwr7Tu}0P#0uhj&USh8bMRE-7O4xiX z;LQGcuDR^P0J6oM7@w3jRgwEyvUs#Y@_)m1&l*1pHq)JmCsE z*Do3eFlJm8F#<=E_|23|c;U}}+jy+*gmHrt{s&bvjX8RO{{nOQnj zyHPe`VJH0#l3-+i6=e~nSWy&p`cJEm+_EW_*2*rGyG{#Jk6%Zt%tkl+z1^BL=Wilb zN~Zv>WK1p`smU!*XS(ifZGu3tMKQtTlOg*f>zQH z*ASP%P)W-Z^-9ciG8;Jl0UOm+D$?nt8LT4kG*B)&peBJez^pn5QwVTkxNHd~B*5BJ zudfpt5;7PnYkfwoiUmy0)d_=>a8MGCS~t6~zcz5MQ$P)+MV_6IMJEX`wo;FnN>`pqDZtyPH-GtMvppJ0b$nyx=0KG&w` zL7(N@6Elm2znVySF*JQxf5shEeKa#m!CcSW-JuNLFxP2jg-jC@c-M1_G)ccbx{ml{ z{+tiN*N-5&?jz3PT6nGhglNShsu@3=_n@Dpo3$r|a5Ju2q-{1iiAIhomO8qAn;SOO z_h(bJs8watNhx;lEk4|C3J6{d_ETyIrc7K>6OY~YRT?!PQJr5iy)h%ElPx_(=sh7Q zulq=_&^5Ki5z?MaPqF# z4;%#P^%&urELFrE@i~-@p$LK2JFk=%qRTJ9&1E_{AT-6#St|r~xTaIqM_rT%SOF#0 z)m1K^`9$L$e5Is;P|=EiX*QU| z#L2nqI98;ick^u%#ciq53vx)a(z{|&ZV#>z1{+RXj~fqSBP;A%;RLgi>ZjLaHP|R) z#bJI;a5!Zxb1w}*FAl_2vgRWz=qt0ZwmE9 zU38cmH_jr{jRnK5+js^z1l>ZEaZoMZV`f8^41(nK0hE{Pxg;cU5oidwc%&&EezYtq=1ro*XSS zW8Dk1mUVjGy5520eFIQNzFU($X5AVt5-B|0as|2j-e-rmQ$F1{exHPu ziNPtjesdJ;+w{?*6XJ1pZ&UFrXABRPwrk|lDA1!tI0Ga8luh*QUN8U0jf1cO_?&Ag zbS08>1-Zfns$OpPE4$IeYlDjycB4SJM)^zGH0hOPd@Q!xwD+J7X1?&clNb6wCub&A zgpdMnu!u!UA#sGs@YhEImIxcl}6? zWAESkCMEw1csgYAWB7(oq zG?@WEf(;CM*MKPRhE(48>VGt11{<$bo%)6T1J$b3boB^McaUleVhVCSUPBOaapOX4 z4hv^t%GY=$Tm1xqh@>Z3d~nyA1yV+qA`9E1s8E5%tE^3dugAJvB}=K&gj7<0b=n(Y zfR*4Ge#AM z&RLmgo^quUyc9;58^y34MUVtyihVvwX^o$x$O89{>6cuUo-K;I_kp5^pgZWt4?#$) zc|5^?S+Ry-k)Rn&G|DBEQ*E$?tFo(1^A+KCPg{>53-Gkb-;L46Bc_3!x?H6Ny)}cS z6D`&*%Ea^hBM?qvGgxv9!r;$V1(w;1F*&*QYpYcM(FY)F zCG@CPdr&lEeEIdRchjfA@d&{`b0?_w(9+f;MBG9D)*@O}&6x51K~7?uIpk?xrSp(| z_QmfNTOwl zA$q@YyQQt9Z>;oX65Kf3TGHR}Xbp}3HTQ$_Ww6Ba%ruKv8KY5R4lANH{Px83_e>+L zp;;%y$2`8j>OT-@S&s-MaPS}*myJ1zck0r_V&T>XK%#gTfrf?#OA!Mzq``y*au?CM zLb344cZYjvc;&Gu0%MAO>ukUBro{*wRvTPlt?7)S>OWAD;}ReYO2kOX++WKlZ|)u` z7GJ5pYhbf?ViDP2^)IA^qY-oaHG4$my>EBVl^NS#q4EdaFhH5PEr^JJSH%=&rA(9ri$33}JF9UN zgxGXKh|J?P{s$1!veR#%+;=G}1tHX4F%!&DO9f@BQ3ZdD7aD&7nr4=GgquK-78>6P zs!=9?r!IO5SLHNEgYv%_JiFJ){OXkCwB^m)plFS(k0i1QV=a^**$b-$Y`|OqQg&(Z zR?(`)RZF{DkDCy{f1EFST>#R9D^QfxgowEDNBfsnFKG)hf4InirQ}_>2(hCU0|zQ? zy&EBk{FgvdOo1^)yJoN#f2hP9R{a+wDfu5DM3U~H@INCiQQrJQl^SxAOc6~G3H58I zBNz`HTpPPvqYC>UZ8FmmHvPZ0Nw_LCR*CDYam;6C*0uAgG|+D5uGkl}e+beIG;9aL zy|fx?K7k7=Nl=BAUoP5*7Sx4B3vv?tH}#zxyrkE-0J|BK9n{fMI|G5xQP#p^ZKJJB zFUaHk5vK+O?9$vddsUk*Xi9Z$8nSf;xIBv)hz|h1v=&6}f648%rTB&6X_YE9nwS1eU4$NW`!(djKgJH&|MQm}Cr>h35Zzd`DT5X;oBuR+xN6!A zZX-8NYuCFi3m94MT{Sz3lv#=R{=z?t7-yyhnVpLyK^3RlnWK);x9)bAy19as4 zFYdWqRjVzimwpVUG4FBvE^UhTeRCBJ1Vw!nddvT;Tk%pPN{On*6&K(G1hDYI=9eX~ z;AXgL4*=}{^kkM#T{2*EjP9D!?4}kldK^RoDJ=?*$yyL%_y?r`|0!Q;GD?DOn2)C2 zx&!#NLxcG8(|1+(-Ob|>fFo0$xgg2Q<)BRy?Z~1;>FX!ky8s{COW>fv`71Fhk2VGW z-zI||fCjd4>gB2%5bZbqPR%1^3(}l{oO9&AEOMM%7MbF_YF_}KUavI7naCw1gEO=_ zfr8Er5Y01yrLUbY;wo)Q)s~A%*?+UfhX6)Iok4N`gEf{$b0a|9OfoZ)9j~GG5aiN0 zq0a|_qJPuKx#R&)mQq)baJ!HQsOqn4U)y+s=mD^8@JEJ!^a%U|My~f{31MMtF+*wo z@*FI4hVka}KC3&RhQA8|1a5aJ@E(a4#28qOgB-<9v0cBf@e6!$;}UkC$AWIPSyuYNj(v+l&tg+bzssq=xV_1rXOO*Y zl;~&YlN31D&kS1NQt<&JZ2Mc}7c*~@Kg0bAesiLt_h-t>x#!^uI3(i?Omkqv7KOAk z5tIaXxZa8Diz+4RY4+=p8l(^jgp(+ui1EVRJfOk5^ub`tyZ;-reBm93m&>tHF%QO4 zUsOEdrBpVJ<;L|Mx~on-U<2 z$v}0zdDtF7< z@WVw%f{?sqGCH?-p4gETs_q8_t@?@*e@9v%4{ff7JtSeWy zFta-;9G6jKsWYiziuV!wSH*k$fcUlVVq zwBq(|W`T72ZR`+Asw!NB$Vp!J&d&|gx({K4H}B_o?f!U~uh8)wvPf;%opUqqr~lda zNx$Hbv%|eqEzi@v>FwX`Kh~%3+?T1~YHfUjQKt}kY|F2uXHthTZ1g?YhsRCo1CN6{ zyM_1rs?`PXh(g#ef!=SZDNE`%OtU}M*|GJou+$*n>&Y^B$=>7YVzek_1|b15u}Q=B zEVz)_S7vGlCwvCu?Ox zKF0gYAIxdtOnH1f10;H4tPmNfhl<_L&iK@!n@X_G90HuKlwh%@ViHAzj5)c%_n!oh zBC@ws3&Ln`)-~pBi#HFhdywYwl7)r!heSP(cHvQh)?OWl6WX#s#MQr0yCh+{^b@gX zH_K4a5t?8iDJG)G@_O{0e-lBQLd5l`U0UyqNSl+nRMLeXn}>G1QM;M1Z(Qx<{#D0& zZJT3}V?t0a)!qDyv$UHX5!rPQ-sd^DSJRyFHW)T0(-3`;z76HdmQs0p-i#Rb7;k9JU-}k4Wsz%25SHrR_+}xl zk0B|JYO)yjRvFBf1okQe+^EF)h~9)9SyA0qphV$3`GI$QNDo>P)QL?&DwsuJ)j`la zx{^C&;qi3;1~4JTzb4!!q;>l00+E4#3MI<8Q6xiD0x#l_SAmSMP)bwSrt_A%pH%-vLJjz*!@_qX||n{EW6MD~wBUnn6iEBxNUPWut76YIsPGRSkYMAuw?9dfg12y(-MDI#_-Zb)@y-sOg^^mtr?xQ_CmIppk=g^7RODtJ=PS@ z!tAUX548=QG(?~fEcSW;GVLeotWSt#k_BhfFE+bEx4Np*=|{Mb!hQHh^e)M9dE zCW#~>RY*$~loVG5VD)Qsv5ek3y7SsW+Z=^?(!(Rr0*$7yl5pHOR4}efUU#-K@U0n_ z2hkp+NZzE7yX{wdo)J3wl!jwEKV9TxW+BF&iwb=prVbbDmnOLZtJ977vzO-h|Mwo)FmcIA@>U@UGEL zao{7KvUGy5DYdlMUET`g8y1T+>0OlyzHLxMZi|Jte$K8_D^NFtzO7)@F5{$(IuJ<-68YR`GiE_Pn zRB}H?(-E}ejhHDzg-Sl$Q8n`X_?lt&7>Y3>FuPL`Oz4vTN+x9;FPAs9@rDZu`t=C& z3+NL!a^MD4j5PLeRK-!0nHK`77)lLY4d*R+oIv&E7bCtgxJJgM6Mvst18p!$rP;fa zBtjbE7nq6j@F>U@<3(Ew8Z)ftn31^E>`Pr0o@^`J+izL+^)YzCL=Kw`Br9=FR1wWwUUbPUD?pgc>n^KY@QE@FO0w ztwo4L9M>3Uxz@GQfrq)pAHN7TUlmK!rjpH~9}n7&G+E5acWO2n#(*~2)*!dg+_3SS zcmCC9OLOKQCx~wdKdR9+K0=O>PH_s};6ogvd~dWBXCJl-7VjD5KViyzzcV0C5vg*A zVDgKzaR6Y)m}4{P@9-JnxFMz{XpWUGZd#O}&@DvPC_yi|Mhc``ixe3#)Rk7dLM=@} zg|CYzk5}R?>T}4wOEGKV<<3r8!s{BPIH3q-2*fNIrz!&_J9TlCoqLy!C`qU>wNUyw zDI6Ywzks&5Q3L7JYEsyfW&wS3B>9A}Okswy)XA+~A!` zQ%N+TVa0T#72v-h?1TwPXjtV~(~Vr@#O&MG?dsJz<=m!b^uI`=zr)zSe^P-p8)kpx zNnqsE#+$hR9P+Y_X;Q;aC<_kuInmB`x`!Hn)6F5GYc)|`VTEQbJ?CeI)i%I(S111f z4+ecU(LMXJStN`12P2j_w{6FkA9W3-Mi2fgk4Y&~{-;nJFi{q?4s1rjg`A7)bCn#% zfUVLFp)Pn3b*s5*6vb=OB!K`3;leVC255--&Y~mc6c0hLTy5Jb)?q`6L}IeJg24L+ z5y-H(7>-mW66#!4F656mwNzI%b|h}9>$x+{ot!&yK-x)IM!WpsY6#M*!ZwY;$5p!H zCq_gRb(%}kt71$q7oJDH&1%;ewL;3_eIRw!Uut@614Jo0A?GMzkXNr}+!iOwUoiK~W z-!Fj>Y8jl!H|W4&!!LtT4yi0*8S6rH=|zB*4cIn{2SVi=Ac}r@k!BsA{irooF{L|# zG!E{Q#`neWctn}yPCx3>#z^GGdx!xg=EgHU=c+4>?`_A@wC4EajA$|%QL3D0`A1%q7j zdu1LclIl_rO?%Mf@g`nUsf*8zNWzVB^;d?rTDf`{^bw1GvJX&thCDbN5D$h-1z)VM zthzcmJa~05afNXl2^Ty-b_GgC9XQP+%iA|3pVCI6%m!!Jkt_Xg)h^joJnLgQgMiDx z^Q9g`cEES=sM@nAAsId?*8l7*V{yg`&b$}KIf$fc#~DS)Lb1Ol2z(kduEQKomA=x{ zfa#*uiw?VzLhquvp3ubyH*#Ruz~Uh`rN!W*S(AskNfFMS4W!&+5l#L-l%YJ}`Y!r7 z3Joe3+9d{qJO;S4tx{fvH8%2!x0>TMN$MH?sm2>#0O?JilV0w?4Iyb%6nwB=I zcC1>`ab2pyUgQ|ll}_cS(vee9u6J}INPYpQzS5g*-ZXAZ65uK@zbIeSlSr5Vq(`1v6 z&bG)J$J68JOt?wu#P=5dQ8U-b{H>xm+V>1o|Fg7-PU@o)O>dM6Ydt<436U}Pca^smjQNRe&~$gj1J02O5A6e#vY=(GGZFSp7PJfN&bGR38!Ju^9idchj{6QftUdNN z##8%B_qjU9qNNrt4u&ui3{9FpVT2NYO_;%B4R@xkA+jeh*OJ0VUNS5p{^mF^1)W&E=8)Mc_OzZkq`O{EB!GqVOkyO_HWs&`7GpCPV2g0-I#-A0hAV(|mcHvJ`r9Y&DgXMof{I zLBJaBk|6-HYCBH{Mowbl^`hNc@y4muFTxPO8$h~}oHdABUCgS@ zRuP;&R`L&Hu7yPIBxl)wC4%~5lSa@IF3OJq>DApP7dJGzfMn=FYE1JIApG7!UeeIJ zogp~S37vgZBdoIudnLoM*;1|rjl1i;?iLqCNeSB!*hY(&Nt@SVtjt0R7kw=-T$=U1 z5-6_H;v%1;S=TvgMnxrc_qvQFq3avvn{4~6g`Rdus|MRE;w9rzI1fak<2(#XWXMYP z=y(s9<#5}&hi@lklvRRU>%b;;p2Fc!3&B8~l09vR?j>rs87Eu-o@6Qd8*;k%97R}9 zZ3UxM!9C0?;<`drM)&e(Ar-zT7S`IVWk*{F#+b{qB@jF&69@T5q9BNJ!&s8p|8~bp zrzw8BdO?mCx2eGFJBG#xmcTqdQT#7Z{_oOW+?`X;SvzpMpN5`dYv$*Ih(!@k@~EAG z@U4bM4r@ffjQ?pcntB-09p5EWS(X2bdjzFdebXbZ4K0sg5QTs1m z?jG<3ZC7Hd>S!8IX~IH6yGb=JgE0t{cqxGvJyC`LwN)D)RDUJ+P3sgPE12c!KlGYT zcw7>eOYJPXlz>nbPp_i(9}GYpCe>)RmGk^EcAqDBG%Pu!2(8@V!%UU7Smn;lA8#PoXOMKd$CnoMYUw44oLNf&cP zsCP1$9oAQPUdV~`h$bSw#e*jIU%6Ndrg;h^44v5%op1kWl4bT=9T^FC zPbVd2@i(b9DU`JM^Q-o8?2JpF;IH<7k)eK3A?Y=9RZIe$FmuOqbUBuW9!ce#qd(Ws z{O0|p3bR2IZ)YHQ{=+?Xa_J`03^8~)xdrDR z8%SUqAbHCehH+TKooI(}0Qt>d@z!(L77y1h^`N&OE$6{VJSYPstlJ`jH(J}gZ&DLIynk)euJ+bzB_m#o^D*Ss}^C088R z$?XUou8;gA5%iHhnVVBOVDUSK z^siwe4XSpUJ02w>(?3m0ve{e>Qk*bKy4I6X=aGwf;Ss5Z5m1s4`CF3EdG&0*lf<`K zmL1Ekai-DP5y@f8_<9GGbmY%h%3%6QRnr4kXI?jmiP@EG_@@q4i}Y6v9V$8jvdf@( zETS~S6W`IN6>pL{_J#bKwems_^chx{oO<{Yd-&|L&IqME1)YZOp9@0BUx0b4T73S7-LrV+m$b&Ksq(pUVwL~UKq#j2! zbuy3p5~Ya^i;C!KTxAg5Y3H&4U!5(xMvZJ%Z*bt^86VPxI!MR_p2Af8yVjO~0!I*; zv#O-zdq2LkMs&#p9lKDX6E+$`Cn@Y<_Il@VYC&Z|%1~ygN=nanp041R%G|p1*9@U~ z6Y_6P2;Q9Mz4#A1#p^joFTBE)W;iJfkl;3j?yZX|k*-c^74^_PJX9g@TbUkTHOl9@ zUVCp*%i*s6O1stCU6{(@OsZ33rxc;Ciu{qHmKv$9$Y`}chPJc(9`o!Y^mI;Dz;u`T zRZOj-8LCz+R-D9i30>k0P1TlwwrTH6u zK1l+VFNy!{6yeZx?R^wwFEuH0^cMx zf^I3BNig@~B+9xjov>qpu1Kz>N@J8v4 z5{onf8&|v~*3tPDBQtdugXYWJiP$S|ln#88P03^p$o92;4A#5Hr zM5xenrNAo4E=k;q@*!9+jp1>ClCUvL9Ge3dJ$37Nt>Je(E5uCT!;wCwfTbH`Zp-Yu zJJ+x#zZNa(EudqdCS@i@p*XrXb6Sgf=6^-cZ@|1mvPf38q|>pWLQ$vTOQNAdBE-lh zQ&Q2=&E<{gQcv|Sr9_pgr#Rg!8UMVHZP6|!GInptzr3X% zI^HOmtUBD8o$qRp?Sz3+GH*;YzVM#POcl#(6$T?Sw#b#Sx3TToS`D52V3IM!$%F5Em zeu}=RFy3b7JF|M$*-l=lTX8yz1-Kg>mdkyC>|bV+HK_G1#_rrJ0q;H&!I&6}xJFlb z9?hoD%|2qY;&wt;G}o%m*$6_?h3{NSAdBX}PZ~z_Cnw1Pgm7XmCfU2ixsfe06W8Lz@G*x6AWgUk(*Bn*Pbpi3&u{C|T3++yuK?m9eR}X1n zk5NCEV}uzo|JXtV#Q9N;t?`k?%J7jDJNm7mNZhz_k@gsj8`d~fV@2?41J}`8^95NI z>w;zu*Nd|AU2-XbT)oMbF8Z=KL2L1xhx*MNpN0|R->p{S$I4XVix*G?lINaTW`Cs5 z>cg-1Kn-J=S`~UaGZL}(!1lAwVw!WNuyj__UGZ#@xK|E_%tU{T8YV4rx-w3nJQu8^A#Qi88qIK)>7K577wGde{6j+;{LoL>hw7KD~q3D=l zg*4UD7RiTh>!cK40lAYhUyFB1rURh)TCa{c4Jo#tO4dJdt>mD>H zUQ*!gL0i=GI9FhKj!7q7i$oYv)PnrM4^O4D@NiIc1tMyo^V-|A2>`$xK+ zspHG6vpU%<<+>OdC4uyQ-I>(DZX3PgDc6WCP?TtMf0* z*up0s_1@I93O=R1jZ>xLORcjC^LnoFsaV~-@CsJR9o<Uy)dwyKnxoe-O*A)&?yMsfK zrAigG;ml~552zxAOPk>Bc{X&V*~a2TE`4B*QOAh+FPXLXZ-ol~L0P#-zi+G8;8}8%Ya_enyiKU&)@C`*W%sz`T}@OHCz zlANtz<;qIDPSJ~8>LAR(#;h|?q0h~<)je65X*4Z2$8d{K9>y@B%FrphtOL44-qrvK zrJWZR|1n(^>Ek-^PNMBfFa6FTugin4-iLKK>b3$hur{?AkrL||`V@MM=E+HbKp*D; z1Zv(G;t0HCk&A7DSbo^3FnePHl-|)fk#<3j3%BVt=@G+gS=I!cin$S`P+t;gWLFcH zJ+|zNy&8C)v`W+nDlYp>Jj@r#B6oFEy6PG9-3Eg*-B9U8Wl_zAMe^>dQ0jSQ@vrUD zP$c~#CWze2>mQFwP|ERU!<`7MUm<wgQ2LMfg=`R>Qkr;Sg0JwO3pBs|}!LF?Aj|(>3a(RrfgOob)q2Vmv zUDuE5j1w=?vDrxlXP-1)6x<3E8%hU_vU6*?puPXZg`r++44&Rpv;9%`v`S4%WbjHb z*|_NUBCysU*I#AV^@HR}8e{CV`v+NIxHbJ|4{9Ja|R2S+4smRc1MX+k{*Qp+xCpi#61;Ozg#_zKc}455tfW%3(3qDsZw z1Z>!o(i@WwVo{-cQv6oS$F3%p9570|F>d_>fbcz#m|1uG19o4d`6jegNWdir%%1n<7jab z%b-}Zn?sRQh)Ck@c^!$U=FTmon5bo(>l?^TmOTJ7DvbRQ(h;vOytdS_S!aW?9pC*( zG*+)^65uft$3E@ry%G-^NG9+;L>h`KOyWqm0q(NC!0=+L2~%QruX@XrWh}AX9$bc> z#f(GONr!Ex%r>f{sa+hdSIQ7odOtcA?NhZ?v*6I5J!y`}AxP?Z;2&%OcblqU2zR6P zbGxXNr_a+?p;b}%hwa~$$21zRI(>Ds^Ac&uhbQw)GviL{@Vaq7C=n!GWi(cigki%L z+saklXbTuS#;;*bk3BNJsFYVvX+@f%g=X+aXt#}5fytb|y72_!IoqF*PZ=+-N{8F& zdkA>@t(X=v9=kRv%aejJ_@{^TC4$RBa4NmE^AvAf5OWI-sf{j>GAT=3rc6%^6GyL; zjw(tlT6%Rsl2xLS_Hd-4{G(gouil9--V@l&G7*|!J@OB;z;J6$Stl@PvgH`ONWao& zBoxsBF_3;;2E?MI&e_~UW8Vm^9td*#A5tFu+?38`crcTj!LNE3aVAWDt%c6mwr z&wOQFnJE|kK~O!rSjGh_{0YUa954=m}wlt#OA$hHzdyLPiI3zGXw$GEyjYHvV1!H^9 zc2#7U|}Bk5~>v@4qWFT{{O?*TSmp%H0`1g2!vn>?j$(D-Q6Kb@ZcKU-3cVPySuyF0E26=;I6@8aGM$C zkmq^dZ=bWzUi;_VYfZ22tFG#ZT;u2ng1swDMOr1_r(tS`>66)!!m3Yjq?cOqm{aF^h?UX~j3<-aBi z@w}PM+hDbdFBLD1sJTCMZZ**nu`~hL2Nz_{qMtX7tRlEc=0-_ zH}loRXV@aNlNgxU!7tkX&)oX+Y~Pz)M2JO*ou&5hXLzEU`ODFT_Q(5OAhnCGHlj%fjve7KX2~dd|wEjNYF2|)(W~S&zjc_STg&s*+RTx z#_%42VTEp`sZjBUtP$fEs(f6mDEfCQtnY)09CVm+UQbH{q|t-96O6Fo9bPcBe)<2)b82(B-oFSuPU~m({cAw2PSfZ%5j0!Y7bwQ+W%AzA z)z6^5sd0KFE@(6nk5U^p_zNLYv*jh7Ww86GUglVGp5G^nTW&J%n*Z5!EfcHtqIBA| zXG(^@Cuu7n{SYiZsz+LBYtm)I_^m~2aPk+0JWGzZD?JIws>uvuVD-r(yB-OVxKC*R z-bT>v8s@{kRK90eah0P)x)>^iyZ+S9ZAFU29xpM_Wb>sdo!qX8=#tekII`sZVm0EK z8ee7W^VV$G_+Rh(i$g>r)0WF*&Uzq%`^<7T2%P{eAD`I#X9xF(oq_c7fZ9C7f0$7D z9`l6>nv^yFVFG3+k<))dlwO!nEghv3{jvyz6{8GNOM(v+H8Tp>f&eQxDn=#Dq7+eL zre&VLJZPx>hX;IJ$p|dVTK}=(UzDNpeW50$W|Nf+v58U2(O@LC!N*0UlAWDN-n+!+ z3S?E(yy%ouVkA2}#$~Ope+5bAIokanm&9K|L=!XC*oA*AR&$qS@;vr|;QZ zYGR>A+3wEsIGIpyBJr6e;B}MOU0+BQ zvXhuV0Lu%X8yx=f+05`CJ~w*%Y*B;%YZ?3_hu?qYKsrscBx@;HP}pRp4R2?Q#(XAF zRXmbsHE>;sZZ+EdWjf^lg2TV7KyH$26d(BDC5Epx(ky34T#Bwh6=T_D@c#$G;zslI zd+I5}-_W?1%k4jOlKV@in~`QQh7Cv2H@(%4A8-YOc+|d3yvttX+Dw;q6F@U~E(yjjaqMvZHfb6K!GNN#g$x(y+L_ z>ruGF{g3bVMKAvgWghx#6w4sqmhYO5c4@zBx#47)17{J+Ur!IwuS5lL+lw`o|Fb(P zIlyC(*vbFL$ogMU3QZ%?N60nV3oYSJ4;6c%Bbl$Xvf7MxzpUXOM*GV({a3Ts^9%pq zYAOBsKa4KQ&X?I;{T+C0U3q9;37#;9b+yUeyYjC)3V< zkMA}b-H5a%oO)-)SQ3+$7L&z`7!Bfyukm{$oW!>f0vDRT2HG@XU9#%E+|WMO=)PI8 za8|OLA@fFZ`ueX2)=tkwk#tAFxKq2S=FM(#!0p5;=XUP-qerb@a;pExtkz4yo&Xb- zq5dVyOKu(rFm9LO2xvB5p(1A{WvERWBgV@k-@M7>yGfP+VgF02kLN50wKnUdMHS;J zZhh;_D_}%$=nW?b2T+_JME%CdPqa?Y^-}QrbnZc|gOHaO`1V>PLZk}UUM;x`@Z{m% zHHB@j34bv0HyN_+B^ffO%#_Ph+5`fL*;KpWo~C|BkTBbRP4OU)feA%ba=hxl6YxyKzQ4=3P38 z8gQasd!6KDUz=yRgD=O_<2O zV_tfl-UW!>Qy*w7h<;d z%v&{GCp)>*ZXM=XC)+Ihka`~L79a?IZfoGwD|T-^xw{oGvn)p&wq=s8qdUZSTyZ)Y zuMDtiXL8Q+i#$CjW4^CXSkh__?Yrq35_mVWI649`cL#d;PHcvy%f6;4^gaF{Tr8ujQjP6bst`i#*=kFV;>O zDgjS+VjJ?7Fhy>EpGdt2U*;AI4bNU%+z0q*bzMJgdLL~Z6~4-rdAeKA^nt8DCN9Q;pbw5}vA4#+tHBSExv=r!HSFLf zrl+&Z(KFr5nBHMA&Ec(C({sFeh={|`QvnN7=QSMdJz4IW(C$O*@Mg|hJGg)OXg9R< zo@_(J2SNwUjh#C8y>IO;IgKW>+|+{H-A2Bsz4ybz0!!T)iLvrEC4(9S}WLZ z1|MMqTHvthC7AQum+1DqFe^*QefuiZ_x@&ehU`4!U@1Go;!x>ah`-ANWL9+mw%r0C zdN?yM$&izQXP`2)9>|65ewcOOK#y}_lGAWz>CWpntqDZ|-hHzm@~oU}sxVSCK&^j7 zwu~Zdj7LP!7qWiTb(C8|`0$Krt9o8TV(VmE;V*jW7bQ$F3)Ock^oQNHDk7V0dR9s_-f!1FXg*)= z4U!*Z!ZbIc8;*3nV0i+@o>wnbQGh}Lm&a$?41JSz4xdc%#|QI|md+*g+hc_Vy4<`E z$V)9K9g0PS`g8ao7mz=h9xl+{>(-Yu;0!XldcByLGX<8>6Kbt})1y@O?0&VYT?;em z0p#9zU<&)A?Y>`0E!^~Kngx`?+5$k^N6RATu#3YR+s@KeFq{CUr`YOdfX~Stjfd{? z@G1vnUFtA&77(j+4$Vf=HS`$n+Qj1Xd%RxWO>`g&0H0M)fGKjLn+HiAA^9G!u6K_{ zgKy#;mdGC8r9df}Fvf5(JFyc&X^BBIy^>rJIyDlx*ts%=KIVO9n zNOX&rJ4o=M8+|+Sx)h{+47g4`cuAD^Yqm>T-mTV??V9vxG1Zp>bAZo}kUz5`K(8zA zGfmjqGX;1sY&B(>1>s`w%Rrz3)B@q9Y)c!of26F<;(JM=RtEhNB5iTG6s5F`wKm<=1!5p&T?C~ zGlxBJ8@~&28^H{Er@%K;h9_Q-O+#l=D=m zB!c{AU_4h}-V*5H><(T9&|)G{WwRgwuZ=d>p1>Y3L4e2tf*;9E4!BP4W~dXxhwZ{9 za1Q|Ru+J6l^nvvrElDV3&rzj?Q<7lhUzDFFFJU z3(9(3K0y9VO&R9I-hCjK_M7i20V&%fmm7x2Be!Fo{_syXejli0p?~5;B0wlp4+srm&e30*v+Ah#K zBpkNP@yy^*nj=W^sChhkfbwZGOVHcZn|lf4YB!H1d}jG*@4Mi`HW+zV?)Ui;%Vrqq z1){CTT58OVzt8jC5lb-H@mUP?X^xzT`Zlzq=vhTfC=*`6Vr|Ro&g*ONzULDll_6+r zdGt)+rL{5gmGP1Q<~4LQ+lM^zUDT~;YKZ^*=_{bov)g<~;>+S=`|b^+TAO3~4=@8X>f1pM+jo%@iov!F+#Z){T3J%-Uf0$Y z&M;g4yvP0R+?Q_SF#e&@xnu+f{?ZP962o_AC-kJgl0|+Z7bkd|-&G+ZBT6S+(Psp9`>~UjAgXaJa;`FNF{%+_#otcbtQ7X zj^w$^+>^82^7HU_AxQGjgpJh5Ynf+qC+*a4Nx!7a2ez;|+?nQVb-1XfNPf_CSG8z$ zwrqfJarGXVDlgmE;^e)*3v8wkyh$!D)`zL=JnwYVFGCx9zDD#Q`P7e~r!?`|?Ba0p zzBG1YaXyP3K2$HS0kiQW8v1O@s(gBH0Y1S)_r8)!b-jGP$DC9m7bZC?vh-M6Ge z;Cy#K17h&=3>hCE9wZHh*NV8`?VgJ>{o><0$0d-cJpAjP)hqj2ZHLarhi8;2}f#m~CvBEBQxSuE1G8p=|9y z;ZJbm`_k3PIuQ{GuZOLho4Z|sd;87y=H+f$9zKosE517tLj93}v;%2SX!pI+ZW(fJ z?$Gn=ged*wornk=3y~c3rSt3lJYZYZt8&AS1QMzIcjM>`CNN zMm}j|Lw{&2eAcB5I-jibAp2Iq!79Ge*X@KCY6R*(XK)v?{DSG>e%m|FLu+wQs~fOY zn5rD>_~?4^99xQD5aTIwTDh_GliGiq7!$f691vAWc6TQN%Jvnx>D?NpZ5yNH_j=xe z+*YQp-iHaJHBC@HJ+uL8Az&06B%e{xu+#4j{zAnr-#ep#i|0n5WfJ*ALr|<2?Ad z1!RH&;N!E+$=uF$UhFec$6NMJ*rms82pM|H>eD*4)?*`RYjT>a0i;(E?+1fH4?IeP z-2}m``&06Mn@?a&Or&bRr@^h-6}Rl?&DPoT-TwXE^E(~`!>Bep(5*S#9eyow3(NfZ zp@Ytcqn!ol_j#EWuM&l!nO0;I5(1m8zYM4J+4P%*&`UVZiQ2^IbQ9!DA`n@V^`@K$v z^7)CG)h+xaZp99mq45DgFr~w95h%?4xwm#|v(xMO_>UG$L`4As5eM$on>TQ9a3A1U zB9|+R#Ngm^@n6B=zkIP}W3^|Il#&+H_^i%iW@YMX%Fkso zk{Re0eh}LYSq~%6i#N_{~rIsT3( zv9SZKwVMX(-A|xUa*>TS{}q_l!TA{5%7{X{{8D{ZPcJ!w` z{9-z!p4c~eg&2Ig!UBw9xmt6zIW|e2pVz-(-+ncBz}z#dk6D?zSw923?_g#TBTh8f zT48oDFrSy4FFLBA`l^2n)zvT0ynrGc`BoSo9+v<{*m+4{pDeeS#mbRBvm^$7XN-la)DFTo`G0QDmk zQ9QqWAE2)Z9t*7AjSmRx8n&nf=K^Ayk6hpIfiJ@+2hrtC*Sq-vU$vL2>$F_-gi%*9 zsW%tBH89I>YjS%+K$VK#jSGL28&tizIDwsrjWe{<^RaK0A!TaG&sVG$P*|10ox>2r z5ee#BQlC~|njDrBB%a{g6~B?-X3b^aCjZAuAGRwF>qOY$#Xz9({I)h#YRyPF%~;%@ zwj;a^Wf72TM#Dya^0>kx4Zv0RlHkf-QV%a}KO0Zt-u)4$r7x$~I-CfZ_SsM7^D{)< zyk9sJw3+{D%DnceH{H#{)q}QFbC#fmX6RphmX@#+@bAK`zr%WL8Dcv=rrE9mPTw!dej zIoV6dPKw>VAbNi6#=1Zl+WQly992t?>U)G$akXv=IrZa6YeHsm`-U)yX2FwJ&QW;( zNjaIC;m*;LJ^GesA!A$uFy38*?uOiADnR$nn?m;(^NH#5*?f+*3MjQ!A9JG@ zL?M1aN8A#SIQhkIE1zRYkIJxq#@y>z#Wsi3IQI!PQNtyA+7wl@ZR~me*XnFuZl~e9 zQ{nXzk!c^B3r<9#Y0@pQG|@3qYdWU7LbtS&h{oZoS#+`*`P#!k5!%G@1eq)DvJPKH z&F^w94XCDrH`}W*rjMKc!kyrweW=(uMoasLOHZ7%{7Mi=bZ8nmyUJO&lnd*f$ucSN zu~C;XA9s;`ZWk*FUQGD{VjNFQ*`f=eLM(Ky+40ou5Ao#bInE{@sgj`qz00%D!ZXO2 zSeSgceQJ1NFJWi|tGc`b2|}Jz=Xl3z9IB!dtxHlH{jd+!<6JeLQM-1NkL&8 zYU0we9qqyHJotJ$bTu@>5qkHuOUsY`$^f^@?-*Om{1c5s&EJS6b>0cDBMgZ|z}tfN&o1<32VU%7b+EidNnKKX zIDfg4p`&hfaR&<}(Kv9hSGIhQ5IRs)E-Lj~OSNG7$YVF$#{AGG|3_ixEE29k4ONJU zHfOGTgiuofsj1C3hp0CqcA~CzikTl#dq#`JSD|_{(cSj$*%$iMdgkSSyVx3fPre-a zny%M`Wr1X=ux?c|Pu(FkG&JQ-MFgD$F;)O7~FzW-WecE)>KXc2118I3*lc>4eOZX?!I7Pi_&UUqV|8kT)M*z)jFfN09vlbgbqRMq?K&)iYD5e`BsCVg1n3tA!V0IfFFo8=c6qUd zuTxGJJMF`-KgP&R?hcwTX_%E|PkVZ*yZ6w1JHbNmKJHceDhF9qxGlgJC=EWa>L~8A z7dp>|Jv#dMJRBX*8#+8sakq)AO=aFAc9McUy5o|wv)HPAOsEz-6_7h@?t6Vg{5!J) z06y2(>jFWJLfPwB2*Q4JUN+dXDNJCGh6`3$alPU4a%;La6o&Kh zc~TQW(c62&{-LJ1w&S9WJ*#Q?>5Qv0aQb7772&1G5(wnDaSvz4J5@L19KQ9|0@$wv zjs8gICK0_xj(Q2QT4m61?Wf!rBUr9SMCEYJlnE;ywv9gzF-)C=2_lQ&{F<=$b98Mw0N3F;Ox7H$hS)-^z zQ1@mQ`wp?*rq^_1$@YA4a-XUsN$PpK8+Evb$LeWqY*zl|7O&U!RyS3jPspK!EW|G5 z3X9Ngv6(_oRb8>t;6NN5$A`9(M$7@q>na!Bz{{bYrX~RSmD160Pvow_MT1X}QXAKi zl?{Kzraf(;gRT1GEljL1`iKBZjrmnOvF~W*hNCG^@O|Z%9^}EVUDLQbVHKlG;lg>M zPOdbdH|vjzk4PA^Tvk-lV3h19sP+&MILP*5tKlUmpD9Sv{liD4jWIXTUc#UcQ$1mb zsPH!lOb=hkGDZ{L3D`+tw(LZ|Cl_)g#WEA9Q<2!@f;rAO*zy)>7kCHMR5BP_W%bZG@jG=)1?51oR;DwH~Ov7sWv!QOD1<>4Tb%qLucYfBdX{9T+l>zd>Z`{Jf`gZDR=0rSxm(2jEwQET=l7$2%?|Z>kb+=Q~ zIYdMxNA_X9OFEVp#|A<&g>N<|y!}fL_c!|fgz{`?tv}u!prUd%2-oUFd`OO1H7BDT;lx&_Y#wZ_oUa3bVF>d5hl8KY4 z!lAnF-oSngAK|A&f&(kpkfp~tbs3bS1q0f8`$tAOx5B-A_>RYEG+ZIi!<*zAq#FUA zF3_X-0D#y1_-ZArNQf9KmhjzCDgDlOr-{pivn6=jNC78)@xkm&ICK8S2=TVw8QwH> z{&+r5mX7aSfeJReibNe7I;Ia1+ytNJ92^S!*h{dHa)rNE4xC8mdXfJ6sS5|^XtuRJ zEI%cE)X}}su_7bBv12*vt{h`#GyIg`z1ucjp<#(+WOj(dVdK~kif{kz`vQ$pwPAiq zDm^!5PxzG*EtYM(kIFTJA5Gp5inYN`bJ@FZZ=(YhKVxU6VaK10vPIv-i`JDgcAQC` zuS9DY)L~H7jP^R~m#x%qJYPO7-t&O$?bVdv@m2ziPO`S2Tq}N)qCCZBkNn{)GCn$= zY3l2_Xm*}lcbUb*M2S%_oW2hKzIev)BhG>gVPVBBgq<%qNX8bun!4#Vs!F^Lolld} z`mP(0QN8h{)kC4Ly7fyzoE9}RQw&AK&!_l>{dIT}j`~J)cG-l@-a!;!FS)d{A9q~4 z;z#Lpwv7uPGV+DwT=|11qTbw=3um5xi9@j~%DkG#$}rHAYiHsPB==btS?>h;IcV*MGJq^{1!*a)X(<1;=3-Q+$>}(RNO6u2 zC9E5Q8D49mT%8P;-?=H3uz%RVlBaLjC-k0}l$#Ar*%MiTP+T@5VtC|YRAsAwDCWn8 z39*JiNm3nMLENX2d~{_j3L8(wRjh)eANT~PzZfmsL|-9U^&D6xdPQSYf45w*!5KKv z&Fw`sf9-%x9?d+l-GIMubFZ!54c&f%hVlR!kJD8T!Idz`gU7@}LIK%zeEvjl`LD2d zloE9z1_SitV=DW2N&t?Bj>%SIDem{&#Xoa!vr!W+*itZ57!obM2T`<+>XkO}QyJIp z9}=uTdW(p%ZDd_`F>Ooe4UZqI{XD%Hjz>ohd{LP zb`|%>k5o;bF0A47&{|8|QD-5VPo|H0bK6=hDNCHhSIbDLum{wpg+dmld517*hN)Le zt~lqN-J(hFE9v_6Cdc&X`*Vjolk!t$9|+vWa3joA=zAqINskGV^E3? z5QdUba~S(F!~l}U&{vd{Mz0%Z4TR0U8Ihu}BQDjavzGJ{&ZGroY8XWeBolb|8NVUV zTp3Q8LbMqMWJtwix zAG0oSEZudsbc5#PM`v1Ui9`WBUHyr{q%SsOuM0xrz}86bGd@a=P8j#d&*ll-`x3RV z;l4P$&bA=R0KTd8E;>H5*%LZdS`M3n^xGg`{v6&nkx^jY8GT9GkD}{D8!>lDU{+hj zz)vgW`6Dx?)NizJ#j!=wr|G`y!3nhBi$%ciT=`vY4u=(Sc=bIPVEL}1On3YEwShfH zYFN5IAYmT77sIvFPbZ^H91VRDWH>U_z>CH5CQ_@_s*R%s%NDJFt3WziLY&{$F1O4e z2gk9n!;%-_W%dGO)Sz2NIm^=;N`*e4|GM} zNS)bYnAxl$)s~dYn%#HDw(E3jVhB}t1*qqty5xeeX_!|xJA^YrsVfj%?Ac>Gv=k$MKoQP zk_fps4IZCaCD26~k_R0-a1VF;+X}6srCKJ}%HpvEpL?$Ge~Ui)S^7 z&H(p>nfT5tzz_H4a(1xI=(bF$5IXwniDFpM=r@y}R-@SODCuIlV>W#qpe}g%$kfG` zjGs`vJ!v{=hko8-?0#IDvwjR2SCBWF9|%cc1%_e2-+D|0eWu7aB$mTMAuUid$5?Qu zZ_f7|;(J4&>gFhqd|CkdBes{+jBiA`s*<01Hg1X1Pe9J-@I>oVkuJ$@0Q$~%cil)Oc!-wHB`a?&s^m$2jarRjS^@^fxYX9-XG z1EF1W%l!vtk`H|NSbof3y%vf*Q?b*VKU3MsEWB=99r@fmJXsddGO3G@HgjSmpmq_S zkEOd@7D5f7?h52HZI))9{5{WjDV}o|GDYWrg!z?JR*MSvt&tOlwm;N7c` zC#lP;txc^CsLim~9k^e)6;rKYrP^U)Qq-UK&uvU9Z%J$U<;(!_LVlUMyy^{moE>kw z90%j%iWxRl14B|QNfY%%n-<{{6T8CHE(r2`yna>+;7Ql8l~VD$kVP?q!`TP=(KdadhK@7BL#f7##;$=4-_&n@tHi=#T_zLB?cqWaNgYENp7UL@iGm~U= zy+Q4e_@Tu>cpE`y&awJiSMX^N!)6Ta*>3XNyY^uxP%mndupf$1RWPZ#N~yc9Gf^c< z;F?S?@4~1q<7pf6D)~sg3ssw>EOMhbdS($8W7FiwfzxLp??D$lChrA}$vE<~0h!mG z3Kq`eQqe5v`2dm&JL{pIgj&#P$~~K)L-WUe-sQpeA*dVU$Y)l6Huf!H!>|uoWj_n# z)gv1MX842Mr~*u=DRRW7&BV=poOe7K3-(Yu_F{jgPWQXLS|k}6MkCsJBNb{kx%vqu zb*rNi?t?oQxU3f&O^rpn{D$PW7JEGD2ZW50$yS?=&#sQ7={T!L7f~5?8p4B;!n4wD zMEW*u%3k}MkB%YlU?{Pafz`M?e`x;_Nj*ZslL}*W2GH2nWP4M(Tgo6h18|9MHR99 zIC@(k2SYi)#)MnQ*vcd#Tb;N362#GcQP!ClJ!RTf!Wx2%$Pxq&<);z*!oxQ*Z6$_I zAh_*hYN<%h6-FUXdsVD@22XlYg-wLwar_j;Qp{=@N#Rq)`#zW45p!%b75|$^#94S^ zB3y=2cvY+qZ_C^XN({NHdbQHfWg<>#M&7k&LA%uP_Rg^n0$7D7hVJvb8C~;9ALnd) zDfV68QJR{QyKy2`8S$6A4l`dTMZ3hLV&=rKPO83%S99snWgHy5@(4Hk26FYADala^ zggWmMwZt&%OwF0)hYO~d{9-hItfpnXd5W=Aja}e^WywP38DL~?$-88w|`t>3!B){O!K8<;H&wm>U8oZ z0Py0V$x~sPApdcPz!kZC3MZ^Uqv@o8=*_z&AIHFw9lW?*8g#WUqt})~A6bDPk}wvI zsebnj*AP%x{_*^MU zMRQ(%{$jaW($RY(ACl&&2%EiiX^NkXdEoXX@;v#}wsomjC@ZZje#fztyH7Fk%28K@ zl=T{8r%bG3K6vtxJyALbnOtg6=Vya2@S3XB_T-8j25UxQz;#Nc!5J!|wMfLN zjWjHtG7U;H>@aV!&}0Iv$|Irr_NFL4q=cFvbO$i3MkP0 z_<3Tg%gixRskHyqJDUkmLzkmcU{sIg8l>qMtDGtu%X7YI?+@sDJUdv_dTFfAc|iI^ zmx7H=3u;I1t(<0hH4Q@h;L6jav4^hyy;k;c7U|Ku~;bsk>UtD9E_IVTP^#m8~s+pD`(DM6Bv~ zhfh(s6Z;*e%Q3O=suqE5Hrwx;L|lD_s_6hR-d3U^559bMNh>Mx_>2iXWwVC4RT111KqsL3--=N;Jr*Za5dlF^h{y|4aBvnbn9oezaRCPmwp90 zsS`XgSwt$`?26{}(%463+-`kbHMj^_o~D(C@At)zH$uqJFHB(##8tHJF-w1nsmTeh z=BIi-VQM05DH$yAGa&5*9izCd*2}JU0iG`|9Xob8b(i8}B)1!xfkX5{CJv0kk&1J4 zs(ci8o9ljpdI7RSqiM+Ao{p2iUTmEs-i2b2#i@Xt;gbIuRwX~QrS7ycJM<6E5{*xO zq@shjnEtf4ld}g2G#tBpq3e>RJeB4oj6g^@BFQ2-jjcJ;wC~pxw$3{}X3iMVT2y)h zMO|BbU(*B^XLk9=@rl}8;jX7Uavl)wy`#0QUj~p7Ix*eQi>EL#tr9vqugcTaxyT%E zaN}i7KyRvt&A}j@9m$~zZo>bCTZ|f-LokfcZDz_X9|Lb^H+^J$x_a&I?sx*yUo93A zfc}o_pC@wBu}vW4R4rkDeYQHv!i;iq@m5+~m>$JXj(LQ8=3CpM%3kN0!$|3k^Nru& zWBUiEMcEO2T`KBHBxt0}Abh$rEvcE?WS*?ybwQmA`3=U~VKIbqrB??QE#1E$^fxtf zHUxh<9l-J;5e^9Ss``Cgnd1G3-z4k1RbD6PbDH4fFaEP} z%k1_CKr!@PUDzaj#t3QJOZkfT6Q+hpwn{+&1OewW-9Lsys2F(7zmYL}2E`LU0b=sz zJG&@!7fNK5bpZy@tS_&yWNEWZPYC@Kzkkw|GjEl>s8v4cH8nuRmNIV(32{bmc>0a_ z`-~?I!!Jd7EGp`eyi=!{9rfj8^^r6&Nn>%M8r`#K{vNq$gnc?E?s!yR7JU%v&Ss|& zgQJV;e)CX~t53I2oG|f~ec765rDA%8i(hG=+wXMnz4WV;aN~|&MFJ_sC zq?3z#km>I%mlIySSnyV%(+~}^3~%Por_k)EhBm`Z6l>3@g(aYkRL&tbkYZ8XOmAxD z<4vcG_SeA!u1j z^oYquq8*Z9%E1Ofzda`R2PJZRP&^SRJav`Sv}jji>yATkq~l5LN|$adl2VJlrY96G z?EkWyhu<{Om9NB=;>?<|YD>N5YC~mNla*FYwCElkbWlcXEIgKfo5PAn!O|H-uVwQ? zuO0Zc{V*HvD4Reh{&JlBaC&Ye`v}E=m9D4ao%5=k`2F6wyp=uz2pP<7#Ps{E`J-3p zyl3rXG%dC-(udWBzR&plbSXa5LoN(*q{XJb_i$Z)(;MAZa&h-acV+nmln4^<)2h9t zNc*scj?v*N3Tr$CAL$fKrZ(@+{8Tt4HdG~Pu=m)nt-|b_<8y?+q5~h@P$~c^GB1- zj%Fmx%+T_;sj`&84}GHHv@Y%3tD6$@!!ijV`G|B*nEqQ*p=0bKX)}M$#TR3<-6`ol85wtqGGutN zDzR?j&)SE@65kza!gnBZxni)Cr&-V$CX(g}&A8^;N@>_xg;xYx>E0mQFcjxYysn}u z`nH5FWsVJ_TM}^U39zQcsg`w8FPa;r&wck^k|$M-!yH-L%{~#)9Ou}jNPTJv?Uid& z)_{uqyLMS}x=O{$F)aAOl|A6Es6zU>vemcWjEnC-!0rDCGFqii;W$}4RI4Cw+2^@l z{kYPQ>7saBp?35P9n~Dzv;hl{Nz&9xwXWS7Uj+X@!t? zUgL1INYdqeM6H;OWmN2Mf~WIwkymwyNmUfAaeVJ>UiX;P>}ae#diYi&BsGJ&>_|O~ z+QZTAFvg)*_a$;zw2*3>te&8D9viRoq6Kk6e0il_5?^1Gz!8Kcm!!vFz&d=$=wXIm9xtv|5h`sxp+aa-Xq)#%nB(EA z@BwQ9!P3|imI8ps>|Bl9r$)R)&WZ|lz zWd3}KgW~+x8)Im1FY@f`1~yP8(#@A%=I%etuqq5D%z%3h`{t;8hau#EpIGD7Zy7RJ zVY?H=u~DdaAfq4<75FJFWxAYNU%RiE7yhYbw-s^+-JV!I z8CYIwZiY+wNO=I!e6X=)wzvSM&{ZA;lEUpbh*6yz{laFiO(rU#yB|k z`bMm_bBbe({UtpYg*tRQKSN$8b1Sy>oHo%5x*ueRSXk+*ZlEF6ZVhtd8j>6xi~5MUI-~Jt8`SXbus-^A>C95^jMaCM>x;1 zfLsGZ;ABYf*Pe_SUDjam4|Y~&JHfE=B1J_ahliL;Q@^~GPzGcYe42t)$=u}z)AJAU}6A&+9!3IMHw0ZSrb<_!Lkn%p$>wXhbv`+?Bd!-bV(l1^HrR+-I<|h z)yUGBoRp3+;}_>nN||>|Jt}9NYKsysQ${;6mhETF!fMK`BZ#UgVgtuj&q5d_AhSc$ z9-ZjayN!1OuO*4WGO-MPdtf_aA>&-o;G-)|*XcN(YBw8|TP%Ire>vADA3e84OCAxwm9Y?=jr zz7l17s7Y*IDk(N0tn?I3JU6unR}~L1(S@>yhJb#EDJl)b2;XGJ3WcH9D2PeZ+9Amo zTts<(-M)M^WmU|2v@}(@<$xJv`O$S1bucGUW|+k_cMX`Ek*JN`*uv zZee1^j&bJaM9wBrPr#ISM51X^ETYx(n{+{5+TRB}^|9IbcHFTX3z77>9d&&C%^uiu zXi)_O(|Q1%T#RVu&cGneJj^Wt^PjwKBog&go_^Y6WlWd zSr9gnR{iFiwFa4VUG{a@yWcKehiV?yUkjzpI;(1@_QQAhg+6_->i5CtlQSd_ObhH( zLi_`=_iqEQe~WIKQ?Av;ocJQOXzuUZpOg4~hwyId5>HT>P*Asry6O;)SUai(ZOnvY zd)iX8rA#fblQmSCh^xAG&K5kHp&qC9y?7C+*mtM5k)XGdS`r>U2X*81fXittnPll4 z=j&bNqpR9Ox-PDYZx>_k?-Yu@lzuuv@eq+fShnQE^ysDo7I2?6eDvYB(>rI{roXy< zvZw?$bE#1IxtJYH7_GBrEI+eu#bwZ3VCXXj|6GR&=XdJ9pH zuipNhG=uecQCXh3+)N5t$b6Ma-8SFgztDwIH5FtQ(D47TcTT~T^>3SwJ5D;bZQC|G zwrzH7t7F@?ZQJT3JGPC<`=M%PYQCAXnX0M(!8-WWu3AT{YF+EOuYEu0+%^jmNMmtV z1u1zw`v(hy!`-CV+vyC33F{gYjcCJ#{bpaOx^$%b^r@GuR|gx#f>851-O7hGv7%W3KbL*<%8(Ki8t8qHHj{2O8CY9M?$!doW@#_ZxI60H zj8Y8%ioGK^)x)U0UssQTggj=s%;D}jh-i;vK(C9xa}2K4;E#Z^Jk7m0N>&2)tYUnf zJ^`vei(GL~=yu@n7-$il`_#w@dw`s6an>x^_eR^JCSU?B5IJZmhf!@uYFd?C4&CDeyD_qw(RX(u4iR`pq@w@?=1yr#@KUNf?=<1hS#kbfTNgtQb+hE%$F$uC{E@d+X~rn~$Z(I=Q>O41)| z4C+l}WowQf1J+c(u_B;mMvuJLAhyz;EGd|dydl>ubJl@KJ9RO5tdzpY9i#hfj8YNq zr~tIhn9b*7*`D2Z#6jiLDXB;9VblABD^pGiGrU}LDr}j(!kI0nLVev7gl6k0J%>hz z0j%91va|VUY1l%wj)|gny&VX;9Dcq01DYCtnIOyV@R)XIKhyroxdB!3Byy~>TtWD zC;NS@Mcs^KI<@KoncyKFu?oG+1Z1xMwzqZsSGyAlgW-j?9=dAx`HBCuLMdYF;j_vEzu?R6o|8^$hT#;2VAJ8da1@+ z_amuRrg!i@@E4LVz|#Q|l+GVPe@sskPVmp0RmzA&wDt5`fVz7feR@3rX6%}MMblxD zgH$^5?svB*Vrt6|#7Ku=>b%wXgt#JuUb@(G$^K@*pZn;OeXKLmj%#eX37s6{PG8FD z-+SwyMTIjX7eI=IvYP5DF>T~-Nzzc!N-F%QP*)sa3vAhjvB5^e-x*oGAc-zF8#d-+ zvYyUJz2!K*SwM>aon>hVM=S^vtgS_4t4Tg&JZU6|MdS>|Yf|HJd85=5fN2jL)jK8$ zm((H&8}M#DhlYY@IS$$MLHqC<2#rU@E2+uU{K}^-lX=DR)KIhP2YDPrg;mO7<8ZDY zlvM)ai6xuh3!+iInC_D|_$F03rNmDiV05cKq!ODSIkePr;wrh&?50n+BU5X5bGtZ$ zz|9qIA**>uU;7|ztAs2CwvQt@&ld>%xVA2t-ZmHUW1~V-Jaa($hq=o7h_&=oD9O?F znnk3BOWcNKnavbwNpE-8zkBLxY~dq}4e3l-RL2-=3zWIVY~WU76>f77_&51PB(!_F zKA*$$Lu>g8q@21i(gilg^8b7ey#`CQ@G_wg?Zk3;%Q9XWFjc3JuYQDZ*Uw4BRW`Pk zu5;=yKLju!WYiwOox*SlTg6zD;fya!qv#8d>||mjG;Z_pN~U>YBIm)ONp5vC`_C#S zj0hjjX$R%~47X=gNK%9sku6H(UOz+|Hk{g*_Lm*uJ(b~o)4!3Wl$q1p3E8hW1*OWh z=TK4hC=0(vpb(v=J<6M25}kXTdxdF?R5lwS0m_A-n(Nj>8kv89cb`Jr3?T2L;1@(s zL-I%lF5+LywYV1|zoBb8RZRCOLjxjU<8q3h+Xyv9A7oy~_eed%&s#x84oTdpqtHSQ zameKMJE_ju<$aUxWP# z_!LXXmF@DG5_UqfD0q8O?oD5r*`^5N&T++XyQCgB#Osu)FyfK#hWwDLI|lirv3|!1m!dQ$R08o z^e4B?+W-=n;||}5(un>*jZAx-hgayQ`^VW^h8uee{`RIe6oEOc?5b8d(PWU{j{SPvWB-9-9wht-ty0f?L4iE0IaY>=5CQa^796cs7;p3_2kR>jqxB1 z-S!w|q}1%E2yzgib5g`JHe0J=yNT6R!-!1%asN2dAnBTdztSdu2%+c!HpYRn8V4ea zGTP^UsPZh$2+@v}lWcs_RBO-i%Ag!~rnnAI8dm9>qrzbEGni^q@RJx$B&A1glSMaUEffvGA&5uz1mS5Ko4I zk0z{SkfV1!WeiN9h2k(a%lmPURkj=DaZsgEItZr;Kmdc&S3hD3#knu&Vr>>*}Pd&fo~X_p-5GkhH(~ z50Q%cm@9HvvbHlf1^P4H4tF~EDTF)gr={so>+E>iwgEAXp2+Y?%6IJ|eiNc09`b zXF3j~r7y?3v#AT&`I0`+R(RTg3ov`DX?gSp9SKIRszM2FS4FbriSh3}VbRmcvm+39 zTe}~^p)Y@Jw?A$lPx&cvnEf!-TK{64i*TD957q(SJ)b8r79rgSBOg3sKDWf5cY%T7 z;x0N0R>oO%?WDM(D#?m*9ZtoK`TD(7q>M$QC;-IMZ10Hn!Y>%gr6WNW-j*Q3V+I43 zpIn5^xUfwsByvPC?SVEXYS4E>UkPJz*w}8;U&npkX>kGRU4qWw2!6~#XXk{ zBec7o0uVGYtWyT{(7BtCMa>MwSz$KFEbVcPefpTq2NaJaZ=&OC1?7ITvpS!Mr69CxE`FINii$m135lC3T{{-3Hyc+5T-WXV-PyshDkO8IbH+?bTZm z$bfUuh}%*MFf$EUaVFePC-v`i)gILD{VHcgNNF{rO<&dQ-5X7&gyBssAZJx9+bLSY zYH(Z8=!?0WsVdDlOSNxH%}`h>%j0^c>q9yd_gU!a;_dAAar5@PJIe@^afZDne^hlh zP6dM~pM23NCUv$CA9Fh`BC{6VwIS!$;a$%ISe2CdvLdmF4xHtM%w! zE$`m3p;lQ0l;(y`O5S;Y??*sjWy+Lwu62$0v50OTen4m}%|+NKa%N6oVSlvBiFdLg zYdCoxvw$Q^IH$8OvaaZyyqiM7nW{bqBMqIgUO){D$3c+pQrCjS5-yY2fYibE+{N;` zw8i4Rq=Q_D#BR7Zo?SNs)K{-)zGF=!+njqYQZ#_(kyBUyTtmMytc1)4dZ~8RNK|U zFynSvclw4$$r4dhjZW!$hOWRLY+*@c$+>F<-e5_O!hG-W9*^LRl|756g8h=Q{o*>1 z6(W_uv(5gK+yXg_z`~{}@Xib1ii^pHjrHQWg}+*EOFD^k$LlB3S9R|Zl8M@J|Jtor zWJcDLpF1;J2=S)B0Eo1dsCeW|xGk(cpvksh6lAw@z-VVRmVnE*mU9>WEx;iNIFW>T zen^@#4e6YnqnX=mEPW5RA+HTIQy%OdO&$UkRt!)pBA$))aX%sMr^08B zRF%*NeTm?9@`(I#C3rIOg@%2#Tn0WbVBK&{v-{`zGRRfZ3lKc!A#+>SLLP1{ya-@1 z*^2PHH>?g=zbN!S5-q46HigQYf-XdGT$;y#@?lrEVfjpyvsdl*RaNKLQ~(jgRRBTk z7n6)}uwLu(CPVl&Rp`bJgj}Ey>QMHBJG(IU7=$%%UKblTGbNe9{YnD5MC_T7Sjw{7p^;&3DR|f-QzZ+X9FAF9-nQ&vf^v0qfJ7TGDWSc4$x1ZFL)D0Au8d7JaPn2uB~7Xhkfs~ zdF8`R_)Y_f^^yy04ckTc1#O+XqhPhuCqJqz2BN`G3`1OF3LT<|F1V4i$94T`)T|DE zg@pU;Ya=V$2dS5@u@~oh-L@|9q(Gk6Q0Uc@F>SOcqe7TVWo#dIf1JZUQjHdgwFZSb z>y}$=WEx`9)96qBNRVbB2!%{IFg5p;HqgohjN}`-x+hak8H|oA6`VNpd5^I+cF_Oe z+rWZtT+YSS0X?zG;c*c01uiUo%!R47nQUVAhGZ|D%{Z+dIn7HDlC9=^Vw`qz(x$*F zaAMOk6lBzAf$O)uN=`+`xlf&XRHG<3i)MDcZhVXxTwGW9iF@9fWS1{O${T`*wGYu@ zWyUXV=nMFy-!@P`Q#mgFqbWC(2!kvM4#IVl{muX zS*%;jN(pgB6&rmGx_66<$GyyKv52ADL`s))%5JT6NjeF`c|Q?i8ildO7;&cWQ) z%0bq_<93Sk)!^h}mp)i$zmHBP^1h_wD8_E;FyERsGGnw8DXw$* zpp3E|F|J+b{Bmr6^}0GfoS(>cNzKiXNY$P7Bsk19jieVxCeYm(>U*o_5G;y>r3-Kj zj^v>9$&9>8$eb-Y+~vXQCY!I4R#Jt80w2P;pG3LFA%|r`;poLl2|9BV>iLi;Il-}F zE_QOYNAjV_9BZ_l#xNxYU7MG1u{LLYX7VTZ+1K(+xNVy zch@sR`~xPd*)&;a>5@Dyr`MPsCk4KYwlTH^(N?o2!U{Q^+=tJ4`sedliTIf@?|E74 zddK@e4HcNduJ1GwX5M<2F9dZMJI=;=(2%lA)iBx#9F5pVOkC}peQrfqvi<*<<7#JW z#5^w$yJX6G4%`B-9C5BQ%(}Qzt;E;zTc?f<$O)|d(O6; zy@7mjk>+Yp`TYuyle@E@p3>`^3GtZWd}Rzy+jB{o>irUU7K zSiE&%V$`)R1Yk~J*CGb4yI6DvpQrh@BAj#H0H}8{B3zKq4=#?2?Y^Ida)R%az31g} zbqHY3nKcU7!xJc{W}2RSyL&R}`Qk)YdNtj4c$(#jeiyS7v*9_xveCd6)gez{d3k<8 zf zPf6$Cvqxqz$sp7GF-Wc0#3-yCQGPR(ta|^VmIqQXQrMCGc$CF6uB9?o0#w%ck~j|T zo6^)p^P*kbl-TA}j!PU7>^B00JV^LSy`t*l>MlEPGMccKr%?60hEz&fwt9??W>pyl z4F-WFOhDmdHl(iDZ^vCR3SvQC^_Rb{Y=coBI}byv$5>|I*hU2o<;T*4??D~9r>DZr zSjEMTUQ${=tK!4uGmi-cngWnc?nPDrz#>no!MN`tvL;8;FA>!dm63d+E5;H`N7w|y z@3H4k5r_69hBypfz38mLIVIhPYX>XHw6&&SlH%f@6;Gk;D=eftGCOKb4{35n6V~a= zsgh41!dIU=gupSs0Znr73a2�j1|&X1Dh<4?#qjTk*#)UKR>N2W(KhEQgE1cc>d2 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 From b2a551dc638d9cc54fe27d575e4beb55d70feb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Sigueros=20Fern=C3=A1ndez?= Date: Thu, 29 Apr 2021 09:57:48 +0200 Subject: [PATCH 096/438] FIX: Purchase from airfield or anywhere allows negative budget. --- qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index d6c3fa6b..46128ca8 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -93,6 +93,8 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): self.setLayout(main_layout) def enable_purchase(self, unit_type: Type[UnitType]) -> bool: + if not super().enable_purchase(unit_type): + return False if not issubclass(unit_type, FlyingType): return False if not self.cp.can_operate(unit_type): From 522495fd117091ea5bbf8c48345f46c56e067e2a Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 1 May 2021 11:05:10 -0700 Subject: [PATCH 097/438] Update pyinstaller. Need a version smart enough to handle pyproj. Fixes https://github.com/Khopa/dcs_liberation/issues/1049. --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7630d67d..d1698206 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,8 @@ pathspec==0.8.1 pefile==2019.4.18 Pillow==8.1.1 pre-commit==2.10.1 -PyInstaller==3.6 +pyinstaller==4.3 +pyinstaller-hooks-contrib==2021.1 pyproj==3.0.1 PySide2==5.15.2 pywin32-ctypes==0.2.0 From c245531d651032e211d1efb2897d5ddec348c601 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 2 May 2021 13:17:35 -0700 Subject: [PATCH 098/438] Update changelog for 2.5.1. (cherry picked from commit 4555a4968d231a3a361a73ebc8729eeaf156d45b) --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index 55ca2695..0e836160 100644 --- a/changelog.md +++ b/changelog.md @@ -22,6 +22,9 @@ Saves from 2.5 are not compatible with 3.0. ## Fixes +* **[Campaigns]** EWRs associated with a base will now only be generated near the base. +* **[Flight Planner]** Fixed error when generating AEW&C flight plans in campaigns with no front lines. + # 2.5.0 Saves from 2.4 are not compatible with 2.5. From ef1c70123ce69f25d2bda73abec29210ae8633d9 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 2 May 2021 14:19:47 -0700 Subject: [PATCH 099/438] Remove duplicate SH-60s from factions. --- resources/factions/usa_2005.json | 1 - resources/factions/usa_2005_c130.json | 1 - 2 files changed, 2 deletions(-) diff --git a/resources/factions/usa_2005.json b/resources/factions/usa_2005.json index e8af8e95..57869deb 100644 --- a/resources/factions/usa_2005.json +++ b/resources/factions/usa_2005.json @@ -21,7 +21,6 @@ "F_15E", "F_16C_50", "SH_60B", - "SH_60B", "S_3B", "UH_1H", "UH_60A" diff --git a/resources/factions/usa_2005_c130.json b/resources/factions/usa_2005_c130.json index d2ceeaf0..54e4823e 100644 --- a/resources/factions/usa_2005_c130.json +++ b/resources/factions/usa_2005_c130.json @@ -22,7 +22,6 @@ "F_16C_50", "Hercules", "SH_60B", - "SH_60B", "S_3B", "UH_1H", "UH_60A" From b9822cd5d1417d7562a6ee948667d20c32877c02 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 2 May 2021 14:31:08 -0700 Subject: [PATCH 100/438] Python 3.9 compatibility. This argument was removed and wasn't needed anyway since the file itself is already opened UTF-8. --- game/factions/faction_loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/factions/faction_loader.py b/game/factions/faction_loader.py index fa4cf04f..35fb2830 100644 --- a/game/factions/faction_loader.py +++ b/game/factions/faction_loader.py @@ -31,7 +31,7 @@ class FactionLoader: for f in files: try: with f.open("r", encoding="utf-8") as fdata: - data = json.load(fdata, encoding="utf-8") + data = json.load(fdata) factions[data["name"]] = Faction.from_json(data) logging.info("Loaded faction : " + str(f)) except Exception: From 1c31cffe4b9c6a7f58489219a1c49ffd1aa9430c Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 2 May 2021 14:33:24 -0700 Subject: [PATCH 101/438] More Python 3.9 compat. --- game/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/db.py b/game/db.py index ffeecd8c..0a46473e 100644 --- a/game/db.py +++ b/game/db.py @@ -1548,7 +1548,7 @@ def unit_get_expanded_info(country_name: str, unit_type, request_type: str) -> s default_value = None faction_value = None with UNITINFOTEXT_PATH.open("r", encoding="utf-8") as fdata: - data = json.load(fdata, encoding="utf-8") + data = json.load(fdata) type_exists = data.get(original_name) if type_exists is not None: for faction in type_exists: From b10e86e484149741b5b7482a284493dae0717117 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 2 May 2021 14:35:21 -0700 Subject: [PATCH 102/438] Add support for user faction directory. This allows users to install custom factions to their home directory rather than the Liberation install directory. Makes it easier to keep mods across Liberation downloads, and easier for us devs to keep custom factions without git always wanting us to add them. --- changelog.md | 1 + game/factions/faction_loader.py | 12 ++++++++++-- qt_ui/main.py | 3 --- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 0e836160..022a919f 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ Saves from 2.5 are not compatible with 3.0. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn. * **[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. * **[Modding]** Campaigns now choose locations for factories to spawn. +* **[Modding]** Can now install custom factions to /Liberation/Factions instead of the Liberation install directory. ## Fixes diff --git a/game/factions/faction_loader.py b/game/factions/faction_loader.py index 35fb2830..d0722a00 100644 --- a/game/factions/faction_loader.py +++ b/game/factions/faction_loader.py @@ -2,8 +2,9 @@ from __future__ import annotations import json import logging from pathlib import Path -from typing import Dict, Iterator, Optional, Type +from typing import Dict, Iterator, List, Optional, Type +from game import persistency from game.factions.faction import Faction FACTION_DIRECTORY = Path("./resources/factions/") @@ -23,9 +24,16 @@ class FactionLoader: if self._factions is None: self._factions = self.load_factions() + @staticmethod + def find_faction_files_in(path: Path) -> List[Path]: + return [f for f in path.glob("*.json") if f.is_file()] + @classmethod def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]: - files = [f for f in FACTION_DIRECTORY.glob("*.json") if f.is_file()] + user_faction_path = Path(persistency.base_path()) / "Liberation/Factions" + files = cls.find_faction_files_in( + FACTION_DIRECTORY + ) + cls.find_faction_files_in(user_faction_path) factions = {} for f in files: diff --git a/qt_ui/main.py b/qt_ui/main.py index 5acfba73..826e455b 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -213,9 +213,6 @@ def lint_weapon_data() -> None: def main(): logging_config.init_logging(VERSION) - # Load eagerly to catch errors early. - db.FACTIONS.initialize() - game: Optional[Game] = None args = parse_args() From b8e6c2fe78ca3a3d49d302239403777e46bd12ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcos=20Sigueros=20Fern=C3=A1ndez?= Date: Mon, 3 May 2021 20:01:41 +0200 Subject: [PATCH 103/438] Check for empty package before estimating TOT. Fixes https://github.com/Khopa/dcs_liberation/issues/1014. --- gen/flights/traveltime.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gen/flights/traveltime.py b/gen/flights/traveltime.py index 98e74fac..6b6f0463 100644 --- a/gen/flights/traveltime.py +++ b/gen/flights/traveltime.py @@ -72,6 +72,9 @@ class TotEstimator: return startup_time def earliest_tot(self) -> timedelta: + if not self.package.flights: + return timedelta(0) + earliest_tot = max( (self.earliest_tot_for_flight(f) for f in self.package.flights) ) From c0b4eef9485bb940ea1dc01e5d1e8606fe0c0c04 Mon Sep 17 00:00:00 2001 From: Khopa Date: Fri, 7 May 2021 01:00:41 +0200 Subject: [PATCH 104/438] Updated readme for new organization repo link --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 863fd7d4..3a032589 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,14 @@ ![Logo](https://i.imgur.com/c2k18E1.png) -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal)](https://www.paypal.com/paypalme/KhopaDCSL) [![Patreon](https://img.shields.io/badge/patreon-become%20a%20patron-orange?logo=patreon)](https://patreon.com/khopa) -[![Download](https://img.shields.io/github/downloads/khopa/dcs_liberation/total?label=Download)](https://github.com/Khopa/dcs_liberation/releases) +[![Download](https://img.shields.io/github/downloads/dcs-liberation/dcs_liberation/total?label=Download)](https://github.com/Khopa/dcs_liberation/releases) [![Discord](https://img.shields.io/discord/595702951800995872?label=Discord&logo=discord)](https://discord.gg/bKrtrkJ) -[![GitHub pull requests](https://img.shields.io/github/issues-pr/khopa/dcs_liberation)](https://github.com/Khopa/dcs_liberation) -[![GitHub issues](https://img.shields.io/github/issues/khopa/dcs_liberation)](https://github.com/Khopa/dcs_liberation/issues) -![GitHub stars](https://img.shields.io/github/stars/khopa/dcs_liberation?style=social) +[![GitHub pull requests](https://img.shields.io/github/issues-pr/dcs-liberation/dcs_liberation)](https://github.com/Khopa/dcs_liberation) +[![GitHub issues](https://img.shields.io/github/issues/dcs-liberation/dcs_liberation)](https://github.com/Khopa/dcs_liberation/issues) +![GitHub stars](https://img.shields.io/github/stars/dcs-liberation/dcs_liberation?style=social) ## About DCS Liberation DCS Liberation is a [DCS World](https://www.digitalcombatsimulator.com/en/products/world/) turn based single-player or co-op dynamic campaign. @@ -25,15 +24,15 @@ To download preview builds of the next version of DCS Liberation, see https://gi ## Bugs and feature requests -If you need to report a bug or want to suggest a new feature, you can do this on our [bug tracker](https://github.com/Khopa/dcs_liberation/issues). In either case, please use the search bar at the top of the page to see if it has already been reported. Note that you may need to remove the filter for open bugs if it's something we've recently fixed. +If you need to report a bug or want to suggest a new feature, you can do this on our [bug tracker](https://github.com/dcs-liberation/dcs_liberation/issues). In either case, please use the search bar at the top of the page to see if it has already been reported. Note that you may need to remove the filter for open bugs if it's something we've recently fixed. ## Roadmap -Our plans for future releases can be found on our [Projects page](https://github.com/Khopa/dcs_liberation/projects). Each planned release has a Project, and the page for that project has columns for to do, in progress, and done. Items in the Done column are in the [preview build](https://github.com/Khopa/dcs_liberation/wiki/Preview-builds) for that release. Items in the To do column are planned to be added to that release. +Our plans for future releases can be found on our [Projects page](https://github.com/dcs-liberation/dcs_liberation/projects). Each planned release has a Project, and the page for that project has columns for to do, in progress, and done. Items in the Done column are in the [preview build](https://github.com/Khopa/dcs_liberation/wiki/Preview-builds) for that release. Items in the To do column are planned to be added to that release. ## Resources -Tutorials, contributors and developer's guides are available in the project's [Wiki](https://github.com/Khopa/dcs_liberation/wiki/) +Tutorials, contributors and developer's guides are available in the project's [Wiki](https://github.com/dcs-liberation/dcs_liberation/wiki/) ## Special Thanks From af5584d2441589bd9736b04499c72c1ed13d0432 Mon Sep 17 00:00:00 2001 From: Khopa Date: Fri, 7 May 2021 12:49:56 +0200 Subject: [PATCH 105/438] Added a settings to control the amount smoke effects on frontlines. Default smoke spacing changed from 800 to 1600 (half the current amount) --- game/settings.py | 1 + gen/visualgen.py | 3 ++- qt_ui/windows/settings/QSettingsWindow.py | 33 +++++++++++++++++------ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/game/settings.py b/game/settings.py index d6338610..7d7bc0d1 100644 --- a/game/settings.py +++ b/game/settings.py @@ -41,6 +41,7 @@ class Settings: # Performance oriented perf_red_alert_state: bool = True perf_smoke_gen: bool = True + perf_smoke_spacing = 1600 perf_artillery: bool = True perf_moving_units: bool = True perf_infantry: bool = True diff --git a/gen/visualgen.py b/gen/visualgen.py index 8c1982fa..74d768a5 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -109,7 +109,7 @@ class VisualGenerator: if not plane_start: continue - for offset in range(0, distance, FRONT_SMOKE_SPACING): + for offset in range(0, distance, self.game.settings.perf_smoke_spacing): position = plane_start.point_from_heading(heading, offset) for k, v in FRONT_SMOKE_TYPE_CHANCES.items(): @@ -162,6 +162,7 @@ class VisualGenerator: "", _type=v, position=position, + hidden=True, ) break diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index af3f7e8e..7b42a44d 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -17,6 +17,7 @@ from PySide2.QtWidgets import ( QStackedLayout, QVBoxLayout, QWidget, + QSlider, ) from dcs.forcedoptions import ForcedOptions @@ -518,6 +519,12 @@ class QSettingsWindow(QDialog): self.smoke.setChecked(self.game.settings.perf_smoke_gen) self.smoke.toggled.connect(self.applySettings) + self.smoke_spacing = QSpinBox() + self.smoke_spacing.setMinimum(800) + self.smoke_spacing.setMaximum(24000) + self.smoke_spacing.setValue(self.game.settings.perf_smoke_spacing) + self.smoke_spacing.valueChanged.connect(self.applySettings) + self.red_alert = QCheckBox() self.red_alert.setChecked(self.game.settings.perf_red_alert_state) self.red_alert.toggled.connect(self.applySettings) @@ -558,18 +565,27 @@ class QSettingsWindow(QDialog): QLabel("Smoke visual effect on frontline"), 0, 0 ) self.performanceLayout.addWidget(self.smoke, 0, 1, alignment=Qt.AlignRight) - self.performanceLayout.addWidget(QLabel("SAM starts in RED alert mode"), 1, 0) - self.performanceLayout.addWidget(self.red_alert, 1, 1, alignment=Qt.AlignRight) - self.performanceLayout.addWidget(QLabel("Artillery strikes"), 2, 0) - self.performanceLayout.addWidget(self.arti, 2, 1, alignment=Qt.AlignRight) - self.performanceLayout.addWidget(QLabel("Moving ground units"), 3, 0) self.performanceLayout.addWidget( - self.moving_units, 3, 1, alignment=Qt.AlignRight + QLabel("Smoke generator spacing (higher means less smoke)"), + 1, + 0, + alignment=Qt.AlignRight, ) self.performanceLayout.addWidget( - QLabel("Generate infantry squads along vehicles"), 4, 0 + self.smoke_spacing, 1, 1, alignment=Qt.AlignRight ) - self.performanceLayout.addWidget(self.infantry, 4, 1, alignment=Qt.AlignRight) + self.performanceLayout.addWidget(QLabel("SAM starts in RED alert mode"), 2, 0) + self.performanceLayout.addWidget(self.red_alert, 2, 1, alignment=Qt.AlignRight) + self.performanceLayout.addWidget(QLabel("Artillery strikes"), 3, 0) + self.performanceLayout.addWidget(self.arti, 3, 1, alignment=Qt.AlignRight) + self.performanceLayout.addWidget(QLabel("Moving ground units"), 4, 0) + self.performanceLayout.addWidget( + self.moving_units, 4, 1, alignment=Qt.AlignRight + ) + self.performanceLayout.addWidget( + QLabel("Generate infantry squads along vehicles"), 5, 0 + ) + self.performanceLayout.addWidget(self.infantry, 5, 1, alignment=Qt.AlignRight) self.performanceLayout.addWidget( QLabel("Include destroyed units carcass"), 6, 0 ) @@ -681,6 +697,7 @@ class QSettingsWindow(QDialog): self.game.settings.perf_red_alert_state = self.red_alert.isChecked() self.game.settings.perf_smoke_gen = self.smoke.isChecked() + self.game.settings.perf_smoke_spacing = self.smoke_spacing.value() self.game.settings.perf_artillery = self.arti.isChecked() self.game.settings.perf_moving_units = self.moving_units.isChecked() self.game.settings.perf_infantry = self.infantry.isChecked() From 4bb2ab73c110bbbb303618024e6200cdccab601d Mon Sep 17 00:00:00 2001 From: Khopa Date: Fri, 7 May 2021 13:03:15 +0200 Subject: [PATCH 106/438] Changelog update --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 022a919f..98e91bc3 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,8 @@ Saves from 2.5 are not compatible with 3.0. * **[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. * **[Modding]** Campaigns now choose locations for factories to spawn. * **[Modding]** Can now install custom factions to /Liberation/Factions instead of the Liberation install directory. +* **[Performance Settings]** Added a settings to lower the number of smoke effects generated on frontlines. Lowered default settings for frontline smoke generators, so less smoke should be generated by default. + ## Fixes From 977845e2f493b45612944a70485faa648c382970 Mon Sep 17 00:00:00 2001 From: Khopa Date: Fri, 7 May 2021 13:08:39 +0200 Subject: [PATCH 107/438] Fixed links to github in the repo to account for the transfer to the new github organization. --- CONTRIBUTING.md | 2 +- README.md | 12 ++++++------ changelog.md | 2 +- game/procurement.py | 4 ++-- gen/fleet/ru_dd_group.py | 2 +- gen/flights/closestairfields.py | 2 +- gen/radios.py | 2 +- installer/dcs_liberation.iss | 2 +- qt_ui/uiconstants.py | 6 +++--- qt_ui/windows/QLiberationWindow.py | 6 +++--- qt_ui/windows/mission/QPackageDialog.py | 2 +- .../mission/flight/payload/QFlightPayloadTab.py | 2 +- qt_ui/windows/newgame/QNewGameWizard.py | 4 ++-- qt_ui/windows/settings/QSettingsWindow.py | 2 +- .../plugins/jtacautolase/jtacautolase-config.lua | 2 +- resources/plugins/lotatc/LotAtcExport-config.lua | 2 +- resources/plugins/skynetiads/skynetiads-config.lua | 2 +- resources/ui/templates/mission_start_EN.j2 | 2 +- 18 files changed, 29 insertions(+), 29 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f795289..d0e863f3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ Note that you may need to remove the filter for open bugs if it's something we'v ## Making content for Liberation -You can create new campaigns : See [campaign creation wiki](https://github.com/Khopa/dcs_liberation/wiki/Custom-Campaigns). +You can create new campaigns : See [campaign creation wiki](https://github.com/dcs-liberation/dcs_liberation/wiki/Custom-Campaigns). You can also improve existing campaigns. You can then submit new campaigns on the "campaigns" channel on Discord, or by making a pull request if you are comfortable with git. diff --git a/README.md b/README.md index 3a032589..67f55b18 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,12 @@ [![Patreon](https://img.shields.io/badge/patreon-become%20a%20patron-orange?logo=patreon)](https://patreon.com/khopa) -[![Download](https://img.shields.io/github/downloads/dcs-liberation/dcs_liberation/total?label=Download)](https://github.com/Khopa/dcs_liberation/releases) +[![Download](https://img.shields.io/github/downloads/dcs-liberation/dcs_liberation/total?label=Download)](https://github.com/dcs-liberation/dcs_liberation/releases) [![Discord](https://img.shields.io/discord/595702951800995872?label=Discord&logo=discord)](https://discord.gg/bKrtrkJ) -[![GitHub pull requests](https://img.shields.io/github/issues-pr/dcs-liberation/dcs_liberation)](https://github.com/Khopa/dcs_liberation) -[![GitHub issues](https://img.shields.io/github/issues/dcs-liberation/dcs_liberation)](https://github.com/Khopa/dcs_liberation/issues) +[![GitHub pull requests](https://img.shields.io/github/issues-pr/dcs-liberation/dcs_liberation)](https://github.com/dcs-liberation/dcs_liberation) +[![GitHub issues](https://img.shields.io/github/issues/dcs-liberation/dcs_liberation)](https://github.com/dcs-liberation/dcs_liberation/issues) ![GitHub stars](https://img.shields.io/github/stars/dcs-liberation/dcs_liberation?style=social) ## About DCS Liberation @@ -18,9 +18,9 @@ It is an external program that generates full and complex DCS missions and manag ## Downloads -Latest release is available here : https://github.com/Khopa/dcs_liberation/releases +Latest release is available here : https://github.com/dcs-liberation/dcs_liberation/releases -To download preview builds of the next version of DCS Liberation, see https://github.com/Khopa/dcs_liberation/wiki/Preview-builds. +To download preview builds of the next version of DCS Liberation, see https://github.com/dcs-liberation/dcs_liberation/wiki/Preview-builds. ## Bugs and feature requests @@ -28,7 +28,7 @@ If you need to report a bug or want to suggest a new feature, you can do this on ## Roadmap -Our plans for future releases can be found on our [Projects page](https://github.com/dcs-liberation/dcs_liberation/projects). Each planned release has a Project, and the page for that project has columns for to do, in progress, and done. Items in the Done column are in the [preview build](https://github.com/Khopa/dcs_liberation/wiki/Preview-builds) for that release. Items in the To do column are planned to be added to that release. +Our plans for future releases can be found on our [Projects page](https://github.com/dcs-liberation/dcs_liberation/projects). Each planned release has a Project, and the page for that project has columns for to do, in progress, and done. Items in the Done column are in the [preview build](https://github.com/dcs-liberation/dcs_liberation/wiki/Preview-builds) for that release. Items in the To do column are planned to be added to that release. ## Resources diff --git a/changelog.md b/changelog.md index 98e91bc3..20558db0 100644 --- a/changelog.md +++ b/changelog.md @@ -4,7 +4,7 @@ Saves from 2.5 are not compatible with 3.0. ## Features/Improvements -* **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. +* **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/dcs-liberation/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn. * **[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. diff --git a/game/procurement.py b/game/procurement.py index 97453d3e..28a5d9bb 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -78,14 +78,14 @@ class ProcurementAi: def sell_incomplete_squadrons(self) -> float: # Selling incomplete squadrons gives us more money to spend on the next # turn. This serves as a short term fix for - # https://github.com/Khopa/dcs_liberation/issues/41. + # https://github.com/dcs-liberation/dcs_liberation/issues/41. # # Only incomplete squadrons which are unlikely to get used will be sold # rather than all unused aircraft because the unused aircraft are what # make OCA strikes worthwhile. # # This option is only used by the AI since players cannot cancel sales - # (https://github.com/Khopa/dcs_liberation/issues/365). + # (https://github.com/dcs-liberation/dcs_liberation/issues/365). total = 0.0 for cp in self.game.theater.control_points_for(self.is_player): inventory = self.game.aircraft_inventory.for_control_point(cp) diff --git a/gen/fleet/ru_dd_group.py b/gen/fleet/ru_dd_group.py index be98274c..bbd7b176 100644 --- a/gen/fleet/ru_dd_group.py +++ b/gen/fleet/ru_dd_group.py @@ -74,7 +74,7 @@ class RussianNavyGroupGenerator(ShipGroupGenerator): if include_cc: # Only include the Moskva for now, the Pyotry Velikiy is an unkillable monster. - # See https://github.com/Khopa/dcs_liberation/issues/567 + # See https://github.com/dcs-liberation/dcs_liberation/issues/567 self.add_unit( Cruiser_1164_Moskva, "CC1", diff --git a/gen/flights/closestairfields.py b/gen/flights/closestairfields.py index 76662c81..4d6bc4fb 100644 --- a/gen/flights/closestairfields.py +++ b/gen/flights/closestairfields.py @@ -18,7 +18,7 @@ class ClosestAirfields: self.target = target # This cache is configured once on load, so it's important that it is # complete and deterministic to avoid different behaviors across loads. - # E.g. https://github.com/Khopa/dcs_liberation/issues/819 + # E.g. https://github.com/dcs-liberation/dcs_liberation/issues/819 self.closest_airfields: List[ControlPoint] = sorted( all_control_points, key=lambda c: self.target.distance_to(c) ) diff --git a/gen/radios.py b/gen/radios.py index ae72922b..d983695b 100644 --- a/gen/radios.py +++ b/gen/radios.py @@ -206,7 +206,7 @@ class RadioRegistry: except StopIteration: # In the event of too many channel users, fail gracefully by reusing # the last channel. - # https://github.com/Khopa/dcs_liberation/issues/598 + # https://github.com/dcs-liberation/dcs_liberation/issues/598 channel = radio.last_channel logging.warning( f"No more free channels for {radio.name}. Reusing {channel}." diff --git a/installer/dcs_liberation.iss b/installer/dcs_liberation.iss index cd53dda3..c20edb05 100644 --- a/installer/dcs_liberation.iss +++ b/installer/dcs_liberation.iss @@ -4,7 +4,7 @@ #define MyAppName "DCS Liberation" #define MyAppVersion "{{version}}" #define MyAppPublisher "Khopa" -#define MyAppURL "https://github.com/Khopa/dcs_liberation/wiki" +#define MyAppURL "https://github.com/dcs-liberation/dcs_liberation/wiki" #define MyAppExeName "liberation_main.exe" [Setup] diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index e34515e0..52b9300f 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -8,10 +8,10 @@ from .liberation_theme import get_theme_icons URLS: Dict[str, str] = { - "Manual": "https://github.com/khopa/dcs_liberation/wiki", - "Repository": "https://github.com/khopa/dcs_liberation", + "Manual": "https://github.com/dcs-liberation/dcs_liberation/wiki", + "Repository": "https://github.com/dcs-liberation/dcs_liberation", "ForumThread": "https://forums.eagle.ru/showthread.php?t=214834", - "Issues": "https://github.com/khopa/dcs_liberation/issues", + "Issues": "https://github.com/dcs-liberation/dcs_liberation/issues", } LABELS_OPTIONS = ["Full", "Abbreviated", "Dot Only", "Off"] diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 8098ed77..d12bbb28 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -143,7 +143,7 @@ class QLiberationWindow(QMainWindow): self.openGithubAction = QAction("&Github Repo", self) self.openGithubAction.setIcon(CONST.ICONS["Github"]) self.openGithubAction.triggered.connect( - lambda: webbrowser.open_new_tab("https://github.com/khopa/dcs_liberation") + lambda: webbrowser.open_new_tab("https://github.com/dcs-liberation/dcs_liberation") ) def initToolbar(self): @@ -202,7 +202,7 @@ class QLiberationWindow(QMainWindow): help_menu.addAction( "&Releases", lambda: webbrowser.open_new_tab( - "https://github.com/Khopa/dcs_liberation/releases" + "https://github.com/dcs-liberation/dcs_liberation/releases" ), ) help_menu.addAction( @@ -309,7 +309,7 @@ class QLiberationWindow(QMainWindow): "

DCS Liberation " + VERSION + "

" - + "Source code : https://github.com/khopa/dcs_liberation" + + "Source code : https://github.com/dcs-liberation/dcs_liberation" + "

Authors

" + "

DCS Liberation was originally developed by shdwp, DCS Liberation 2.0 is a partial rewrite based on this work by Khopa." "

Contributors

" diff --git a/qt_ui/windows/mission/QPackageDialog.py b/qt_ui/windows/mission/QPackageDialog.py index 666f8539..444fc33b 100644 --- a/qt_ui/windows/mission/QPackageDialog.py +++ b/qt_ui/windows/mission/QPackageDialog.py @@ -92,7 +92,7 @@ class QPackageDialog(QDialog): self.tot_column.addWidget(self.auto_asap) self.tot_help_label = QLabel( - '
Help' + 'Help' ) self.tot_help_label.setAlignment(Qt.AlignCenter) self.tot_help_label.setOpenExternalLinks(True) diff --git a/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py b/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py index b61fe432..131802dd 100644 --- a/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py +++ b/qt_ui/windows/mission/flight/payload/QFlightPayloadTab.py @@ -18,7 +18,7 @@ class QFlightPayloadTab(QFrame): # Docs Link docsText = QLabel( - 'How to create your own default loadout' + 'How to create your own default loadout' ) docsText.setAlignment(Qt.AlignCenter) docsText.setOpenExternalLinks(True) diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index 8f4bb54d..87e34ab9 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -208,7 +208,7 @@ class FactionSelection(QtWidgets.QWizardPage): # Docs Link docsText = QtWidgets.QLabel( - 'How to create your own faction' + 'How to create your own faction' ) docsText.setAlignment(Qt.AlignCenter) docsText.setOpenExternalLinks(True) @@ -400,7 +400,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage): # Docs Link docsText = QtWidgets.QLabel( - 'How to create your own theater' + 'How to create your own theater' ) docsText.setAlignment(Qt.AlignCenter) docsText.setOpenExternalLinks(True) diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index 7b42a44d..8af7df3d 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -85,7 +85,7 @@ NEW_GROUND_UNIT_RECRUITMENT_TOOLTIP = ( ) NEW_GROUND_UNIT_RECRUITMENT_BEHAVIOR_LABEL = ( "Enable new ground unit recruitment behavior
" - '' + '' 'More information.' ) diff --git a/resources/plugins/jtacautolase/jtacautolase-config.lua b/resources/plugins/jtacautolase/jtacautolase-config.lua index 38d02911..c8ad8301 100644 --- a/resources/plugins/jtacautolase/jtacautolase-config.lua +++ b/resources/plugins/jtacautolase/jtacautolase-config.lua @@ -2,7 +2,7 @@ -- configuration file for the JTAC Autolase framework -- -- This configuration is tailored for a mission generated by DCS Liberation --- see https://github.com/Khopa/dcs_liberation +-- see https://github.com/dcs-liberation/dcs_liberation ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- JTACAutolase plugin - configuration diff --git a/resources/plugins/lotatc/LotAtcExport-config.lua b/resources/plugins/lotatc/LotAtcExport-config.lua index 494be5f1..f7b0d876 100644 --- a/resources/plugins/lotatc/LotAtcExport-config.lua +++ b/resources/plugins/lotatc/LotAtcExport-config.lua @@ -2,7 +2,7 @@ -- configuration file for the LotATC Export script -- -- This configuration is tailored for a mission generated by DCS Liberation --- see https://github.com/Khopa/dcs_liberation +-- see https://github.com/dcs-liberation/dcs_liberation ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- LotATC Export plugin - configuration diff --git a/resources/plugins/skynetiads/skynetiads-config.lua b/resources/plugins/skynetiads/skynetiads-config.lua index fafa8104..aa0ce992 100644 --- a/resources/plugins/skynetiads/skynetiads-config.lua +++ b/resources/plugins/skynetiads/skynetiads-config.lua @@ -3,7 +3,7 @@ -- see https://github.com/walder/Skynet-IADS -- -- This configuration is tailored for a mission generated by DCS Liberation --- see https://github.com/Khopa/dcs_liberation +-- see https://github.com/dcs-liberation/dcs_liberation ------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Skynet-IADS plugin - configuration diff --git a/resources/ui/templates/mission_start_EN.j2 b/resources/ui/templates/mission_start_EN.j2 index a162635b..2dc1ce79 100644 --- a/resources/ui/templates/mission_start_EN.j2 +++ b/resources/ui/templates/mission_start_EN.j2 @@ -21,7 +21,7 @@

For more information, see the mission planning documentation on - + the wiki.

From 0ec5346574ebc0fbbf8ea9731a162dafacd845b9 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 14:55:22 -0700 Subject: [PATCH 108/438] Auto center the map on load. Stops all PG campaigns from starting with their view pointed way up north at the empty mountains. --- qt_ui/widgets/map/QLiberationMap.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index e353f60c..2d3b20c7 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -202,11 +202,12 @@ class QLiberationMap(QGraphicsView): self.setDragMode(QGraphicsView.ScrollHandDrag) def setGame(self, game: Optional[Game]): + should_recenter = self.game is None self.game = game if self.game is not None: logging.debug("Reloading Map Canvas") self.nm_to_pixel_ratio = self.distance_to_pixels(nautical_miles(1)) - self.reload_scene() + self.reload_scene(should_recenter) """ @@ -597,7 +598,13 @@ class QLiberationMap(QGraphicsView): self.draw_threat_range(scene, group, ground_object, cp) added_objects.append(ground_object.obj_name) - def reload_scene(self): + def recenter(self) -> None: + center = self._transform_point( + self.game.theater.terrain.map_view_default.position + ) + self.centerOn(QPointF(center[0], center[1])) + + def reload_scene(self, recenter: bool = False) -> None: scene = self.scene() scene.clear() @@ -605,6 +612,8 @@ class QLiberationMap(QGraphicsView): enemyColor = self.game.get_enemy_color() self.addBackground() + if recenter: + self.recenter() # Display Culling if DisplayOptions.culling and self.game.settings.perf_culling: From 1640763a7fa402c02095adc521d8b21ee15b00ab Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 15:30:09 -0700 Subject: [PATCH 109/438] Show parking status for enemy airfields. --- qt_ui/windows/basemenu/QBaseMenu2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index 73a3d261..306f4643 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -166,10 +166,12 @@ class QBaseMenu2(QDialog): self.repair_button.setDisabled(True) def update_intel_summary(self) -> None: + aircraft = self.cp.base.total_aircraft + parking = self.cp.total_aircraft_parking self.intel_summary.setText( "\n".join( [ - f"{self.cp.base.total_aircraft} aircraft", + f"{aircraft}/{parking} aircraft", f"{self.cp.base.total_armor} ground units", str(self.cp.runway_status), ] From 0d7f00aef6f2ee58cbbeb880ee6fc6e4c0d9fd4c Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 16:58:42 -0700 Subject: [PATCH 110/438] Fix lint error. --- qt_ui/windows/QLiberationWindow.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index d12bbb28..76d683ab 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -143,7 +143,9 @@ class QLiberationWindow(QMainWindow): self.openGithubAction = QAction("&Github Repo", self) self.openGithubAction.setIcon(CONST.ICONS["Github"]) self.openGithubAction.triggered.connect( - lambda: webbrowser.open_new_tab("https://github.com/dcs-liberation/dcs_liberation") + lambda: webbrowser.open_new_tab( + "https://github.com/dcs-liberation/dcs_liberation" + ) ) def initToolbar(self): From 3c9d21e38db3777097a9c53966832d66cca3ea65 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 17:13:03 -0700 Subject: [PATCH 111/438] Fix CLI campaign generator. Factions can be loaded from the user directory now so we need to know where that is. --- qt_ui/main.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qt_ui/main.py b/qt_ui/main.py index 826e455b..28582593 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -172,6 +172,12 @@ def create_game( inverted: bool, cheats: bool, ) -> Game: + first_start = liberation_install.init() + if first_start: + sys.exit( + "Cannot generate campaign without configuring DCS Liberation. Start the UI " + "for the first run configuration." + ) campaign = Campaign.from_json(campaign_path) generator = GameGenerator( blue, From 8320c6940bd5037ba2b2ee4eb5c4a275d08351df Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 17:13:53 -0700 Subject: [PATCH 112/438] Fix map centering for CLI generated games. --- qt_ui/widgets/map/QLiberationMap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 2d3b20c7..32f2934d 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -122,7 +122,7 @@ class QLiberationMap(QGraphicsView): super(QLiberationMap, self).__init__() QLiberationMap.instance = self self.game_model = game_model - self.game: Optional[Game] = game_model.game + self.game: Optional[Game] = None # Setup by setGame below. self.state = QLiberationMapState.NORMAL self.waypoint_info_font = QFont() From 2cf3b3be2b91ed5700c3168a927f2b4919819ddf Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 17:20:23 -0700 Subject: [PATCH 113/438] Fix bug causing overpurchase of aircraft. After fulfilling a request we were not exiting the loop, so we'd fulfill the request for the aircraft at _all_ the bases capable of operating it until either the bases were full or the budget ran out. In factions like Iraq 1991 this could cause the budget to be spent on tons of cheap MiG-19s while never buying the more expensive Su-17s or Su-24s that they need to actually complete a package. https://github.com/dcs-liberation/dcs_liberation/issues/1058 --- changelog.md | 5 +++-- game/procurement.py | 32 +++++++++++++++++++------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/changelog.md b/changelog.md index 20558db0..9cf83eb7 100644 --- a/changelog.md +++ b/changelog.md @@ -10,11 +10,12 @@ Saves from 2.5 are not compatible with 3.0. * **[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. * **[Modding]** Campaigns now choose locations for factories to spawn. * **[Modding]** Can now install custom factions to /Liberation/Factions instead of the Liberation install directory. -* **[Performance Settings]** Added a settings to lower the number of smoke effects generated on frontlines. Lowered default settings for frontline smoke generators, so less smoke should be generated by default. - +* **[Performance Settings]** Added a settings to lower the number of smoke effects generated on frontlines. Lowered default settings for frontline smoke generators, so less smoke should be generated by default. ## Fixes +* **[Campaign AI]** Fixed bug causing AI to over-purchase cheap aircraft. + # 2.5.1 ## Features/Improvements diff --git a/game/procurement.py b/game/procurement.py index 28a5d9bb..d3410a12 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -192,21 +192,27 @@ class ProcurementAi: aircraft_for_task(request.task_capability), airbase, request.number, budget ) + def fulfill_aircraft_request( + self, request: AircraftProcurementRequest, budget: float + ) -> float: + for airbase in self.best_airbases_for(request): + unit = self.affordable_aircraft_for(request, airbase, budget) + if unit is None: + # Can't afford any aircraft capable of performing the + # required mission that can operate from this airbase. We + # might be able to afford aircraft at other airbases though, + # in the case where the airbase we attempted to use is only + # able to operate expensive aircraft. + continue + + budget -= db.PRICES[unit] * request.number + airbase.pending_unit_deliveries.order({unit: request.number}) + break + return budget + def purchase_aircraft(self, budget: float) -> float: for request in self.game.procurement_requests_for(self.is_player): - for airbase in self.best_airbases_for(request): - unit = self.affordable_aircraft_for(request, airbase, budget) - if unit is None: - # Can't afford any aircraft capable of performing the - # required mission that can operate from this airbase. We - # might be able to afford aircraft at other airbases though, - # in the case where the airbase we attempted to use is only - # able to operate expensive aircraft. - continue - - budget -= db.PRICES[unit] * request.number - airbase.pending_unit_deliveries.order({unit: request.number}) - + budget = self.fulfill_aircraft_request(request, budget) return budget @property From 36b2f24de97bdd2905decad49d8266d0a62762b2 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 18:18:52 -0700 Subject: [PATCH 114/438] Skip planning for faction incompatible missions. Required for improving purchasing as well, since we need to not halt purchasing when a faction has no AEW&C aircraft. Fixes https://github.com/dcs-liberation/dcs_liberation/issues/683 --- changelog.md | 1 + gen/flights/ai_flight_planner.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/changelog.md b/changelog.md index 9cf83eb7..3b0feaa1 100644 --- a/changelog.md +++ b/changelog.md @@ -15,6 +15,7 @@ Saves from 2.5 are not compatible with 3.0. ## Fixes * **[Campaign AI]** Fixed bug causing AI to over-purchase cheap aircraft. +* **[Campaign AI]** Auto planner will no longer attempt to plan missions for which the faction has no compatible aircraft. # 2.5.1 diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 096de2ba..41bcc4f5 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -561,6 +561,19 @@ class CoalitionMissionPlanner: self.ato = self.game.blue_ato if is_player else self.game.red_ato self.threat_zones = self.game.threat_zone_for(not self.is_player) self.procurement_requests = self.game.procurement_requests_for(self.is_player) + self.faction = self.game.faction_for(self.is_player) + + def faction_can_plan(self, mission_type: FlightType) -> bool: + """Returns True if it is possible for the faction to plan this mission type. + + Not all mission types can be fulfilled by all factions. Many factions do not + have AEW&C aircraft, so they will never be able to plan those missions. + """ + all_compatible = aircraft_for_task(mission_type) + for aircraft in self.faction.aircrafts: + if aircraft in all_compatible: + return True + return False def critical_missions(self) -> Iterator[ProposedMission]: """Identifies the most important missions to plan this turn. @@ -853,6 +866,11 @@ class CoalitionMissionPlanner: missing_types: Set[FlightType] = set() escorts = [] for proposed_flight in mission.flights: + if not self.faction_can_plan(proposed_flight.task): + # This faction can never plan this mission type because they do not have + # compatible aircraft. Skip fulfillment so that we don't place the + # purchase request. + continue if proposed_flight.escort_type is not None: # Escorts are planned after the primary elements of the package. # If the package does not need escorts they may be pruned. @@ -866,6 +884,12 @@ class CoalitionMissionPlanner: ) return + if not builder.package.flights: + # The non-escort part of this mission is unplannable by this faction. Scrub + # the mission and do not attempt planning escorts because there's no reason + # to buy them because this mission will never be planned. + return + # Create flight plans for the main flights of the package so we can # determine threats. This is done *after* creating all of the flights # rather than as each flight is added because the flight plan for From 2ca875192af296a1a687ef309c6439e30e6841d4 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 18:17:17 -0700 Subject: [PATCH 115/438] Save budget for filling whole packages. No sense filling airbases with cheap escorts if we'll never afford the rest of the package. Filling the airbases with cheap escorts also makes it impossible to buy the rest of the package when the faction eventually does have the money since there's nowhere to park the needed aircraft. https://github.com/dcs-liberation/dcs_liberation/issues/1058 --- changelog.md | 1 + game/procurement.py | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 3b0feaa1..b743de27 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ Saves from 2.5 are not compatible with 3.0. * **[Campaign AI]** Fixed bug causing AI to over-purchase cheap aircraft. * **[Campaign AI]** Auto planner will no longer attempt to plan missions for which the faction has no compatible aircraft. +* **[Campaign AI]** Stop purchasing aircraft after the first unaffordable package to attempt to complete more packages rather than filling airfields with cheap escorts that will never be used. # 2.5.1 diff --git a/game/procurement.py b/game/procurement.py index d3410a12..c88b7218 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -3,7 +3,7 @@ from __future__ import annotations import math import random from dataclasses import dataclass -from typing import Iterator, List, Optional, TYPE_CHECKING, Type +from typing import Iterator, List, Optional, TYPE_CHECKING, Tuple, Type from dcs.unittype import FlyingType, VehicleType @@ -194,7 +194,7 @@ class ProcurementAi: def fulfill_aircraft_request( self, request: AircraftProcurementRequest, budget: float - ) -> float: + ) -> Tuple[float, bool]: for airbase in self.best_airbases_for(request): unit = self.affordable_aircraft_for(request, airbase, budget) if unit is None: @@ -207,12 +207,22 @@ class ProcurementAi: budget -= db.PRICES[unit] * request.number airbase.pending_unit_deliveries.order({unit: request.number}) - break - return budget + return budget, True + return budget, False def purchase_aircraft(self, budget: float) -> float: for request in self.game.procurement_requests_for(self.is_player): - budget = self.fulfill_aircraft_request(request, budget) + if not list(self.best_airbases_for(request)): + # No airbases in range of this request. Skip it. + continue + budget, fulfilled = self.fulfill_aircraft_request(request, budget) + if not fulfilled: + # The request was not fulfilled because we could not afford any suitable + # aircraft. Rather than continuing, which could proceed to buy tons of + # cheap escorts that will never allow us to plan a strike package, stop + # buying so we can save the budget until a turn where we *can* afford to + # fill the package. + break return budget @property From 426f06045eb3664d641cc5aba03c0dffc5d1ad30 Mon Sep 17 00:00:00 2001 From: SnappyComebacks <74509817+SnappyComebacks@users.noreply.github.com> Date: Fri, 7 May 2021 19:37:27 -0600 Subject: [PATCH 116/438] Updated pydcs version. --- gen/aircraft.py | 2 +- gen/groundobjectsgen.py | 18 ++++++++---------- pydcs | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/gen/aircraft.py b/gen/aircraft.py index 6a04bcc0..2cdf6673 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1615,7 +1615,7 @@ class PydcsWaypointBuilder: waypoint = self.group.add_waypoint( Point(self.waypoint.x, self.waypoint.y), self.waypoint.alt.meters, - name=self.mission.string(self.waypoint.name), + name=self.waypoint.name, ) if self.waypoint.flyover: diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index f2c5fa6f..b397fdad 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -93,13 +93,11 @@ class GenericGroundObjectGenerator: position=group.position, heading=group.units[0].heading, ) - vg.units[0].name = self.m.string(group.units[0].name) + vg.units[0].name = group.units[0].name vg.units[0].player_can_drive = True for i, u in enumerate(group.units): if i > 0: - vehicle = Vehicle( - self.m.next_unit_id(), self.m.string(u.name), u.type - ) + vehicle = Vehicle(self.m.next_unit_id(), u.name, u.type) vehicle.position.x = u.position.x vehicle.position.y = u.position.y vehicle.heading = u.heading @@ -330,13 +328,13 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator): heading=group.units[0].heading, ) ship_group.set_frequency(atc_channel.hertz) - ship_group.units[0].name = self.m.string(group.units[0].name) + ship_group.units[0].name = group.units[0].name return ship_group def create_ship(self, unit: Unit, atc_channel: RadioFrequency) -> Ship: ship = Ship( self.m.next_unit_id(), - self.m.string(unit.name), + unit.name, unit_type_from_name(unit.type), ) ship.position.x = unit.position.x @@ -481,11 +479,11 @@ class ShipObjectGenerator(GenericGroundObjectGenerator): position=group_def.position, heading=group_def.units[0].heading, ) - group.units[0].name = self.m.string(group_def.units[0].name) + group.units[0].name = group_def.units[0].name # TODO: Skipping the first unit looks like copy pasta from the carrier. for unit in group_def.units[1:]: unit_type = unit_type_from_name(unit.type) - ship = Ship(self.m.next_unit_id(), self.m.string(unit.name), unit_type) + ship = Ship(self.m.next_unit_id(), unit.name, unit_type) ship.position.x = unit.position.x ship.position.y = unit.position.y ship.heading = unit.heading @@ -524,11 +522,11 @@ class HelipadGenerator: for i, helipad in enumerate(self.cp.helipads): name = self.cp.name + "_helipad_" + str(i) logging.info("Generating helipad : " + name) - pad = SingleHeliPad(name=self.m.string(name + "_unit")) + pad = SingleHeliPad(name=(name + "_unit")) pad.position = Point(helipad.x, helipad.y) pad.heading = helipad.heading # pad.heliport_frequency = self.radio_registry.alloc_uhf() TODO : alloc radio & callsign - sg = unitgroup.StaticGroup(self.m.next_group_id(), self.m.string(name)) + sg = unitgroup.StaticGroup(self.m.next_group_id(), name) sg.add_unit(pad) sp = StaticPoint() sp.position = pad.position diff --git a/pydcs b/pydcs index 84247a9f..ea94bca5 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit 84247a9fa108174b4f9696136a413d53cee23533 +Subproject commit ea94bca55c83520038edc7c7fa43648376ec72a4 From 58ffabe2d6050cedc333e9e56b2075399efd5875 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 19:37:25 -0700 Subject: [PATCH 117/438] Fix aggressive objectives in Abu Dhabi. Some of the objective locations for the starting front line are too aggressive and put opfor at a disadvantage since blue ships might start so close to their bases. --- changelog.md | 1 + game/theater/start_generator.py | 2 +- resources/campaigns/battle_of_abu_dhabi.miz | Bin 61547 -> 61760 bytes 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index b743de27..7859c58e 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,7 @@ Saves from 2.5 are not compatible with 3.0. * **[Campaign AI]** Fixed bug causing AI to over-purchase cheap aircraft. * **[Campaign AI]** Auto planner will no longer attempt to plan missions for which the faction has no compatible aircraft. * **[Campaign AI]** Stop purchasing aircraft after the first unaffordable package to attempt to complete more packages rather than filling airfields with cheap escorts that will never be used. +* **[Campaign]** Fixed bug where offshore strike locations were being used to spawn ship objectives. # 2.5.1 diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 32688b1e..f2b84d25 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -204,7 +204,7 @@ class ControlPointGroundObjectGenerator: self.generate_ship() def generate_ship(self) -> None: - point = self.location_finder.location_for(LocationType.OffshoreStrikeTarget) + point = self.location_finder.location_for(LocationType.Ship) if point is None: return diff --git a/resources/campaigns/battle_of_abu_dhabi.miz b/resources/campaigns/battle_of_abu_dhabi.miz index e7dbdbe11d1803358cf4b1996364729535f38621..d1670f14afef4098c10fc0332483826838e7b128 100644 GIT binary patch delta 42926 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 z Date: Fri, 7 May 2021 19:58:39 -0700 Subject: [PATCH 118/438] Fix initialization order of turn 0. Fixes https://github.com/dcs-liberation/dcs_liberation/issues/674 --- game/game.py | 35 ++++++++++++++------------------- game/theater/start_generator.py | 1 + 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/game/game.py b/game/game.py index a5aff97e..49ab15ce 100644 --- a/game/game.py +++ b/game/game.py @@ -97,7 +97,9 @@ class Game: self.player_country = db.FACTIONS[player_name].country self.enemy_name = enemy_name self.enemy_country = db.FACTIONS[enemy_name].country - self.turn = 0 + # pass_turn() will be called when initialization is complete which will + # increment this to turn 0 before it reaches the player. + self.turn = -1 # NB: This is the *start* date. It is never updated. self.date = date(start_date.year, start_date.month, start_date.day) self.game_stats = GameStats() @@ -135,19 +137,6 @@ class Game: self.on_load() - # Turn 0 procurement. We don't actually have any missions to plan, but - # the planner will tell us what it would like to plan so we can use that - # to drive purchase decisions. - self.transfers.order_airlift_assets() - - blue_planner = CoalitionMissionPlanner(self, is_player=True) - blue_planner.plan_missions() - - red_planner = CoalitionMissionPlanner(self, is_player=False) - red_planner.plan_missions() - - self.plan_procurement() - def __getstate__(self) -> Dict[str, Any]: state = self.__dict__.copy() # Avoid persisting any volatile types that can be deterministically @@ -287,8 +276,7 @@ class Game: self.blue_ato.clear() self.red_ato.clear() - def pass_turn(self, no_action: bool = False) -> None: - logging.info("Pass turn") + def finish_turn(self, skipped: bool = False) -> None: self.informations.append( Information("End of turn #" + str(self.turn), "-" * 40, 0) ) @@ -303,10 +291,7 @@ class Game: for control_point in self.theater.controlpoints: control_point.process_turn(self) - self.process_enemy_income() - self.process_player_income() - - if not no_action and self.turn > 1: + if not skipped and self.turn > 1: for cp in self.theater.player_points(): cp.base.affect_strength(+PLAYER_BASE_STRENGTH_RECOVERY) else: @@ -316,6 +301,16 @@ class Game: self.conditions = self.generate_conditions() + self.process_enemy_income() + self.process_player_income() + + def begin_turn_0(self) -> None: + self.turn = 0 + self.initialize_turn() + + def pass_turn(self, no_action: bool = False) -> None: + logging.info("Pass turn") + self.finish_turn(no_action) self.initialize_turn() # Autosave progress diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index f2b84d25..34bc8dad 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -106,6 +106,7 @@ class GameGenerator: GroundObjectGenerator(game, self.generator_settings).generate() game.settings.version = VERSION + game.begin_turn_0() return game def prepare_theater(self) -> None: From 12f474ecbecfedfc1eca8e55070675e510e64376 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 19:59:34 -0700 Subject: [PATCH 119/438] Fix name of Al Minhad for latest DCS version. --- gen/airfields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/airfields.py b/gen/airfields.py index 2903e72d..da3c53d2 100644 --- a/gen/airfields.py +++ b/gen/airfields.py @@ -451,7 +451,7 @@ AIRFIELD_DATA = { "12": ("IMA", MHz(111, 750)), }, ), - "Al Minhad Intl": AirfieldData( + "Al Minhad AFB": AirfieldData( theater="Persian Gulf", icao="OMDM", elevation=190, From b0c24f6e5189ef186bb9f826e570eec754f5031d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 20:56:39 -0700 Subject: [PATCH 120/438] Refactor front line code to make sides explicit. A was intended to be the blue point and B was intended to be the red point. Make this a part of the name so that's clear, and clean up related code to keep that reliable. --- game/game.py | 14 ++- game/operation/operation.py | 17 ++-- game/theater/conflicttheater.py | 93 ++++++++----------- gen/airsupportgen.py | 10 +- gen/armor.py | 24 ++--- gen/briefinggen.py | 6 +- gen/conflictgen.py | 31 ++++--- gen/flights/ai_flight_planner.py | 20 ++-- gen/flights/flightplan.py | 6 +- gen/visualgen.py | 6 +- .../QPredefinedWaypointSelectionComboBox.py | 33 +++---- qt_ui/widgets/map/QFrontLine.py | 8 +- qt_ui/widgets/map/QLiberationMap.py | 11 ++- 13 files changed, 125 insertions(+), 154 deletions(-) diff --git a/game/game.py b/game/game.py index 49ab15ce..c4ac7250 100644 --- a/game/game.py +++ b/game/game.py @@ -220,11 +220,11 @@ class Game: ) def _generate_events(self): - for front_line in self.theater.conflicts(True): + for front_line in self.theater.conflicts(): self._generate_player_event( FrontlineAttackEvent, - front_line.control_point_a, - front_line.control_point_b, + front_line.blue_cp, + front_line.red_cp, ) def adjust_budget(self, amount: float, player: bool) -> None: @@ -459,12 +459,10 @@ class Game: # By default, use the existing frontline conflict position for front_line in self.theater.conflicts(): - position = Conflict.frontline_position( - front_line.control_point_a, front_line.control_point_b, self.theater - ) + position = Conflict.frontline_position(front_line, self.theater) zones.append(position[0]) - zones.append(front_line.control_point_a.position) - zones.append(front_line.control_point_b.position) + zones.append(front_line.blue_cp.position) + zones.append(front_line.red_cp.position) for cp in self.theater.controlpoints: # Don't cull missile sites - their range is long enough to make them diff --git a/game/operation/operation.py b/game/operation/operation.py index 1304c064..73eb0ca0 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -34,7 +34,7 @@ from gen.radios import RadioFrequency, RadioRegistry from gen.tacan import TacanRegistry from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator from .. import db -from ..theater import Airfield +from ..theater import Airfield, FrontLine from ..unitmap import UnitMap if TYPE_CHECKING: @@ -76,8 +76,7 @@ class Operation: for frontline in cls.game.theater.conflicts(): yield Conflict( cls.game.theater, - frontline.control_point_a, - frontline.control_point_b, + frontline, cls.game.player_name, cls.game.enemy_name, cls.game.player_country, @@ -95,8 +94,7 @@ class Operation: ) return Conflict( cls.game.theater, - player_cp, - enemy_cp, + FrontLine(player_cp, enemy_cp, cls.game.theater), cls.game.player_name, cls.game.enemy_name, cls.game.player_country, @@ -399,16 +397,15 @@ class Operation: @classmethod def _generate_ground_conflicts(cls) -> None: """For each frontline in the Operation, generate the ground conflicts and JTACs""" - for front_line in cls.game.theater.conflicts(True): - player_cp = front_line.control_point_a - enemy_cp = front_line.control_point_b + for front_line in cls.game.theater.conflicts(): + player_cp = front_line.blue_cp + enemy_cp = front_line.red_cp conflict = Conflict.frontline_cas_conflict( cls.game.player_name, cls.game.enemy_name, cls.current_mission.country(cls.game.player_country), cls.current_mission.country(cls.game.enemy_country), - player_cp, - enemy_cp, + front_line, cls.game.theater, ) # Generate frontline ops diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index dd66a5e8..d25bdcff 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -3,6 +3,7 @@ from __future__ import annotations import itertools import json import logging +import math from dataclasses import dataclass from functools import cached_property from pathlib import Path @@ -601,12 +602,12 @@ class ConflictTheater: def player_points(self) -> List[ControlPoint]: return list(self.control_points_for(player=True)) - def conflicts(self, from_player=True) -> Iterator[FrontLine]: - for cp in [x for x in self.controlpoints if x.captured == from_player]: - for connected_point in [ - x for x in cp.connected_points if x.captured != from_player + def conflicts(self) -> Iterator[FrontLine]: + for player_cp in [x for x in self.controlpoints if x.captured]: + for enemy_cp in [ + x for x in player_cp.connected_points if not x.is_friendly_to(player_cp) ]: - yield FrontLine(cp, connected_point, self) + yield FrontLine(player_cp, enemy_cp, self) def enemy_points(self) -> List[ControlPoint]: return list(self.control_points_for(player=False)) @@ -646,36 +647,26 @@ class ConflictTheater: Returns a tuple of the two nearest opposing ControlPoints in theater. (player_cp, enemy_cp) """ - all_cp_min_distances = {} - for idx, control_point in enumerate(self.controlpoints): - distances = {} - closest_distance = None - for i, cp in enumerate(self.controlpoints): - if i != idx and cp.captured is not control_point.captured: - dist = cp.position.distance_to_point(control_point.position) - if not closest_distance: - closest_distance = dist - distances[cp.id] = dist - if dist < closest_distance: - distances[cp.id] = dist - closest_cp_id = min(distances, key=distances.get) # type: ignore + seen = set() + min_distance = math.inf + closest_blue = None + closest_red = None + for blue_cp in self.player_points(): + for red_cp in self.enemy_points(): + if (blue_cp, red_cp) in seen: + continue + seen.add((blue_cp, red_cp)) + seen.add((red_cp, blue_cp)) - all_cp_min_distances[(control_point.id, closest_cp_id)] = distances[ - closest_cp_id - ] - closest_opposing_cps = [ - self.find_control_point_by_id(i) - for i in min( - all_cp_min_distances, key=all_cp_min_distances.get - ) # type: ignore - ] # type: List[ControlPoint] - assert len(closest_opposing_cps) == 2 - if closest_opposing_cps[0].captured: - return cast(Tuple[ControlPoint, ControlPoint], tuple(closest_opposing_cps)) - else: - return cast( - Tuple[ControlPoint, ControlPoint], tuple(reversed(closest_opposing_cps)) - ) + dist = red_cp.position.distance_to_point(blue_cp.position) + if dist < min_distance: + closest_red = red_cp + closest_blue = blue_cp + min_distance = dist + + assert closest_blue is not None + assert closest_red is not None + return closest_blue, closest_red def find_control_point_by_id(self, id: int) -> ControlPoint: for i in self.controlpoints: @@ -923,16 +914,16 @@ class FrontLine(MissionTarget): def __init__( self, - control_point_a: ControlPoint, - control_point_b: ControlPoint, + blue_point: ControlPoint, + red_point: ControlPoint, theater: ConflictTheater, ) -> None: - self.control_point_a = control_point_a - self.control_point_b = control_point_b + self.blue_cp = blue_point + self.red_cp = red_point self.segments: List[FrontLineSegment] = [] self.theater = theater self._build_segments() - self.name = f"Front line {control_point_a}/{control_point_b}" + self.name = f"Front line {blue_point}/{red_point}" def is_friendly(self, to_player: bool) -> bool: """Returns True if the objective is in friendly territory.""" @@ -964,7 +955,7 @@ class FrontLine(MissionTarget): @property def control_points(self) -> Tuple[ControlPoint, ControlPoint]: """Returns a tuple of the two control points.""" - return self.control_point_a, self.control_point_b + return self.blue_cp, self.red_cp @property def attack_distance(self): @@ -998,7 +989,7 @@ class FrontLine(MissionTarget): Returns a point {distance} away from control_point_a along the frontline segments. """ if distance < self.segments[0].attack_distance: - return self.control_point_a.position.point_from_heading( + return self.blue_cp.position.point_from_heading( self.segments[0].attack_heading, distance ) remaining_dist = distance @@ -1016,14 +1007,12 @@ class FrontLine(MissionTarget): The distance from point "a" where the conflict should occur according to the current strength of each control point """ - total_strength = ( - self.control_point_a.base.strength + self.control_point_b.base.strength - ) - if self.control_point_a.base.strength == 0: + total_strength = self.blue_cp.base.strength + self.red_cp.base.strength + if self.blue_cp.base.strength == 0: return self._adjust_for_min_dist(0) - if self.control_point_b.base.strength == 0: + if self.red_cp.base.strength == 0: return self._adjust_for_min_dist(self.attack_distance) - strength_pct = self.control_point_a.base.strength / total_strength + strength_pct = self.blue_cp.base.strength / total_strength return self._adjust_for_min_dist(strength_pct * self.attack_distance) def _adjust_for_min_dist(self, distance: Numeric) -> Numeric: @@ -1044,11 +1033,9 @@ class FrontLine(MissionTarget): def _build_segments(self) -> None: """Create line segments for the frontline""" control_point_ids = "|".join( - [str(self.control_point_a.id), str(self.control_point_b.id)] + [str(self.blue_cp.id), str(self.red_cp.id)] ) # from_cp.id|to_cp.id - reversed_cp_ids = "|".join( - [str(self.control_point_b.id), str(self.control_point_a.id)] - ) + reversed_cp_ids = "|".join([str(self.red_cp.id), str(self.blue_cp.id)]) complex_frontlines = self.theater.frontline_data if (complex_frontlines) and ( (control_point_ids in complex_frontlines) @@ -1071,9 +1058,7 @@ class FrontLine(MissionTarget): # If no complex frontline has been configured, fall back to the old straight line method. else: self.segments.append( - FrontLineSegment( - self.control_point_a.position, self.control_point_b.position - ) + FrontLineSegment(self.blue_cp.position, self.red_cp.position) ) @staticmethod diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index a0d9f75e..81d900aa 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -92,9 +92,9 @@ class AirSupportConflictGenerator: def generate(self): player_cp = ( - self.conflict.from_cp - if self.conflict.from_cp.captured - else self.conflict.to_cp + self.conflict.blue_cp + if self.conflict.blue_cp.captured + else self.conflict.red_cp ) fallback_tanker_number = 0 @@ -107,8 +107,8 @@ class AirSupportConflictGenerator: freq = self.radio_registry.alloc_uhf() tacan = self.tacan_registry.alloc_for_band(TacanBand.Y) tanker_heading = ( - self.conflict.to_cp.position.heading_between_point( - self.conflict.from_cp.position + self.conflict.red_cp.position.heading_between_point( + self.conflict.blue_cp.position ) + TANKER_HEADING_OFFSET * i ) diff --git a/gen/armor.py b/gen/armor.py index 8f075d38..31447e2c 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -134,10 +134,10 @@ class GroundConflictGenerator: def generate(self): position = Conflict.frontline_position( - self.conflict.from_cp, self.conflict.to_cp, self.game.theater + self.conflict.front_line, self.game.theater ) frontline_vector = Conflict.frontline_vector( - self.conflict.from_cp, self.conflict.to_cp, self.game.theater + self.conflict.front_line, self.game.theater ) # Create player groups at random position @@ -156,21 +156,21 @@ class GroundConflictGenerator: player_groups, enemy_groups, self.conflict.heading + 90, - self.conflict.from_cp, - self.conflict.to_cp, + self.conflict.blue_cp, + self.conflict.red_cp, ) self.plan_action_for_groups( self.enemy_stance, enemy_groups, player_groups, self.conflict.heading - 90, - self.conflict.to_cp, - self.conflict.from_cp, + self.conflict.red_cp, + self.conflict.blue_cp, ) # Add JTAC if self.game.player_faction.has_jtac: - n = "JTAC" + str(self.conflict.from_cp.id) + str(self.conflict.to_cp.id) + n = "JTAC" + str(self.conflict.blue_cp.id) + str(self.conflict.red_cp.id) code = 1688 - len(self.jtacs) utype = MQ_9_Reaper @@ -191,7 +191,7 @@ class GroundConflictGenerator: OrbitAction(5000, 300, OrbitAction.OrbitPattern.Circle) ) frontline = ( - f"Frontline {self.conflict.from_cp.name}/{self.conflict.to_cp.name}" + f"Frontline {self.conflict.blue_cp.name}/{self.conflict.red_cp.name}" ) # Note: Will need to change if we ever add ground based JTAC. callsign = callsign_for_support_unit(jtac) @@ -213,9 +213,9 @@ class GroundConflictGenerator: logging.warning("Could not find infantry position") return if side == self.conflict.attackers_country: - cp = self.conflict.from_cp + cp = self.conflict.blue_cp else: - cp = self.conflict.to_cp + cp = self.conflict.red_cp if is_player: faction = self.game.player_name @@ -782,9 +782,9 @@ class GroundConflictGenerator: ) -> VehicleGroup: if side == self.conflict.attackers_country: - cp = self.conflict.from_cp + cp = self.conflict.blue_cp else: - cp = self.conflict.to_cp + cp = self.conflict.red_cp logging.info("armorgen: {} for {}".format(unit, side.id)) group = self.mission.vehicle_group( diff --git a/gen/briefinggen.py b/gen/briefinggen.py index 017c4e4e..87029d7b 100644 --- a/gen/briefinggen.py +++ b/gen/briefinggen.py @@ -36,8 +36,8 @@ class CommInfo: class FrontLineInfo: def __init__(self, front_line: FrontLine): self.front_line: FrontLine = front_line - self.player_base: ControlPoint = front_line.control_point_a - self.enemy_base: ControlPoint = front_line.control_point_b + self.player_base: ControlPoint = front_line.blue_cp + self.enemy_base: ControlPoint = front_line.red_cp self.player_zero: bool = self.player_base.base.total_armor == 0 self.enemy_zero: bool = self.enemy_base.base.total_armor == 0 self.advantage: bool = ( @@ -164,7 +164,7 @@ class BriefingGenerator(MissionInfoGenerator): def _generate_frontline_info(self) -> None: """Build FrontLineInfo objects from FrontLine type and append to briefing.""" - for front_line in self.game.theater.conflicts(from_player=True): + for front_line in self.game.theater.conflicts(): self.add_frontline(FrontLineInfo(front_line)) # TODO: This should determine if runway is friendly through a method more robust than the existing string match diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 25e1fed3..d4b145d4 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -17,8 +17,7 @@ class Conflict: def __init__( self, theater: ConflictTheater, - from_cp: ControlPoint, - to_cp: ControlPoint, + front_line: FrontLine, attackers_side: str, defenders_side: str, attackers_country: Country, @@ -33,22 +32,28 @@ class Conflict: self.attackers_country = attackers_country self.defenders_country = defenders_country - self.from_cp = from_cp - self.to_cp = to_cp + self.front_line = front_line self.theater = theater self.position = position self.heading = heading self.size = size + @property + def blue_cp(self) -> ControlPoint: + return self.front_line.blue_cp + + @property + def red_cp(self) -> ControlPoint: + return self.front_line.red_cp + @classmethod def has_frontline_between(cls, from_cp: ControlPoint, to_cp: ControlPoint) -> bool: return from_cp.has_frontline and to_cp.has_frontline @classmethod def frontline_position( - cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater + cls, frontline: FrontLine, theater: ConflictTheater ) -> Tuple[Point, int]: - frontline = FrontLine(from_cp, to_cp, theater) attack_heading = frontline.attack_heading position = cls.find_ground_position( frontline.position, @@ -60,12 +65,12 @@ class Conflict: @classmethod def frontline_vector( - cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater + cls, front_line: FrontLine, theater: ConflictTheater ) -> Tuple[Point, int, int]: """ Returns a vector for a valid frontline location avoiding exclusion zones. """ - center_position, heading = cls.frontline_position(from_cp, to_cp, theater) + center_position, heading = cls.frontline_position(front_line, theater) left_heading = heading_sum(heading, -90) right_heading = heading_sum(heading, 90) left_position = cls.extend_ground_position( @@ -84,18 +89,16 @@ class Conflict: defender_name: str, attacker: Country, defender: Country, - from_cp: ControlPoint, - to_cp: ControlPoint, + front_line: FrontLine, theater: ConflictTheater, ): - assert cls.has_frontline_between(from_cp, to_cp) - position, heading, distance = cls.frontline_vector(from_cp, to_cp, theater) + assert cls.has_frontline_between(front_line.blue_cp, front_line.red_cp) + position, heading, distance = cls.frontline_vector(front_line, theater) conflict = cls( position=position, heading=heading, theater=theater, - from_cp=from_cp, - to_cp=to_cp, + front_line=front_line, attackers_side=attacker_name, defenders_side=defender_name, attackers_country=attacker, diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 41bcc4f5..90f23f4a 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -409,13 +409,7 @@ class ObjectiveFinder: def front_lines(self) -> Iterator[FrontLine]: """Iterates over all active front lines in the theater.""" - for cp in self.friendly_control_points(): - for connected in cp.connected_points: - if connected.is_friendly(self.is_player): - continue - - if Conflict.has_frontline_between(cp, connected): - yield FrontLine(cp, connected, self.game.theater) + yield from self.game.theater.conflicts() def vulnerable_control_points(self) -> Iterator[ControlPoint]: """Iterates over friendly CPs that are vulnerable to enemy CPs. @@ -447,19 +441,19 @@ class ObjectiveFinder: def convoys(self) -> Iterator[Convoy]: for front_line in self.front_lines(): - if front_line.control_point_a.is_friendly(self.is_player): - enemy_cp = front_line.control_point_a + if front_line.blue_cp.is_friendly(self.is_player): + enemy_cp = front_line.blue_cp else: - enemy_cp = front_line.control_point_b + enemy_cp = front_line.red_cp yield from self.game.transfers.convoys.travelling_to(enemy_cp) def cargo_ships(self) -> Iterator[CargoShip]: for front_line in self.front_lines(): - if front_line.control_point_a.is_friendly(self.is_player): - enemy_cp = front_line.control_point_a + if front_line.blue_cp.is_friendly(self.is_player): + enemy_cp = front_line.blue_cp else: - enemy_cp = front_line.control_point_b + enemy_cp = front_line.red_cp yield from self.game.transfers.cargo_ships.travelling_to(enemy_cp) diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index d045e3ea..e648e4bb 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -1320,11 +1320,9 @@ class FlightPlanBuilder: def racetrack_for_frontline( self, origin: Point, front_line: FrontLine ) -> Tuple[Point, Point]: - ally_cp, enemy_cp = front_line.control_points - # Find targets waypoints ingress, heading, distance = Conflict.frontline_vector( - ally_cp, enemy_cp, self.game.theater + front_line, self.game.theater ) center = ingress.point_from_heading(heading, distance / 2) orbit_center = center.point_from_heading( @@ -1533,7 +1531,7 @@ class FlightPlanBuilder: raise InvalidObjectiveLocation(flight.flight_type, location) ingress, heading, distance = Conflict.frontline_vector( - location.control_points[0], location.control_points[1], self.game.theater + location, self.game.theater ) center = ingress.point_from_heading(heading, distance / 2) egress = ingress.point_from_heading(heading, distance) diff --git a/gen/visualgen.py b/gen/visualgen.py index 74d768a5..0fa9c335 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -98,13 +98,13 @@ class VisualGenerator: def _generate_frontline_smokes(self): for front_line in self.game.theater.conflicts(): - from_cp = front_line.control_point_a - to_cp = front_line.control_point_b + from_cp = front_line.blue_cp + to_cp = front_line.red_cp if from_cp.is_global or to_cp.is_global: continue plane_start, heading, distance = Conflict.frontline_vector( - from_cp, to_cp, self.game.theater + front_line, self.game.theater ) if not plane_start: continue diff --git a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py index 5de3527e..12e3d55e 100644 --- a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py +++ b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py @@ -63,26 +63,19 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): return i + 1 if self.include_frontlines: - for cp in self.game.theater.controlpoints: - if cp.captured: - enemy_cp = [ - ecp - for ecp in cp.connected_points - if ecp.captured != cp.captured - ] - for ecp in enemy_cp: - pos = Conflict.frontline_position(cp, ecp, self.game.theater)[0] - wpt = FlightWaypoint( - FlightWaypointType.CUSTOM, - pos.x, - pos.y, - Distance.from_meters(800), - ) - wpt.name = "Frontline " + cp.name + "/" + ecp.name + " [CAS]" - wpt.alt_type = "RADIO" - wpt.pretty_name = wpt.name - wpt.description = "Frontline" - i = add_model_item(i, model, wpt.pretty_name, wpt) + for front_line in self.game.theater.conflicts(): + pos = Conflict.frontline_position(front_line, self.game.theater)[0] + wpt = FlightWaypoint( + FlightWaypointType.CUSTOM, + pos.x, + pos.y, + Distance.from_meters(800), + ) + wpt.name = f"Frontline {front_line.name} [CAS]" + wpt.alt_type = "RADIO" + wpt.pretty_name = wpt.name + wpt.description = "Frontline" + i = add_model_item(i, model, wpt.pretty_name, wpt) if self.include_targets: for cp in self.game.theater.controlpoints: diff --git a/qt_ui/widgets/map/QFrontLine.py b/qt_ui/widgets/map/QFrontLine.py index 2203bf57..0e886d5d 100644 --- a/qt_ui/widgets/map/QFrontLine.py +++ b/qt_ui/widgets/map/QFrontLine.py @@ -101,16 +101,16 @@ class QFrontLine(QGraphicsLineItem): Dialog.open_new_package_dialog(self.mission_target) def cheat_forward(self) -> None: - self.mission_target.control_point_a.base.affect_strength(0.1) - self.mission_target.control_point_b.base.affect_strength(-0.1) + 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.control_point_a.base.affect_strength(-0.1) - self.mission_target.control_point_b.base.affect_strength(0.1) + 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() diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 32f2934d..50359d19 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -865,8 +865,8 @@ class QLiberationMap(QGraphicsView): a[1], b[0], b[1], - frontline.control_point_a, - frontline.control_point_b, + frontline.blue_cp, + frontline.red_cp, convoys, ) ) @@ -914,7 +914,10 @@ class QLiberationMap(QGraphicsView): if convoy is not None: convoys.append(convoy) - frontline = FrontLine(a, b, self.game.theater) + if a.captured: + frontline = FrontLine(a, b, self.game.theater) + else: + frontline = FrontLine(b, a, self.game.theater) if a.front_is_active(b): if DisplayOptions.actual_frontline_pos: self.draw_actual_frontline(scene, frontline, convoys) @@ -947,7 +950,7 @@ class QLiberationMap(QGraphicsView): ) -> None: self.draw_bezier_frontline(scene, frontline, convoys) vector = Conflict.frontline_vector( - frontline.control_point_a, frontline.control_point_b, self.game.theater + frontline.blue_cp, frontline.red_cp, self.game.theater ) left_pos = self._transform_point(vector[0]) right_pos = self._transform_point( From 67289bbba2283aa0374192ffc2ac593e632ce3ef Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 7 May 2021 21:06:41 -0700 Subject: [PATCH 121/438] Fix now obvious reversal of convoy friendliness. Convoy attack and shipping attacks were being planned against *only* friendly targets. The renaming made the bug obvious. --- game/theater/conflicttheater.py | 5 +++++ gen/flights/ai_flight_planner.py | 18 ++++++------------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index d25bdcff..62aae3e0 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -925,6 +925,11 @@ class FrontLine(MissionTarget): self._build_segments() self.name = f"Front line {blue_point}/{red_point}" + def control_point_hostile_to(self, player: bool) -> ControlPoint: + if player: + return self.red_cp + return self.blue_cp + def is_friendly(self, to_player: bool) -> bool: """Returns True if the objective is in friendly territory.""" return False diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 90f23f4a..f2177782 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -441,21 +441,15 @@ class ObjectiveFinder: def convoys(self) -> Iterator[Convoy]: for front_line in self.front_lines(): - if front_line.blue_cp.is_friendly(self.is_player): - enemy_cp = front_line.blue_cp - else: - enemy_cp = front_line.red_cp - - yield from self.game.transfers.convoys.travelling_to(enemy_cp) + yield from self.game.transfers.convoys.travelling_to( + front_line.control_point_hostile_to(self.is_player) + ) def cargo_ships(self) -> Iterator[CargoShip]: for front_line in self.front_lines(): - if front_line.blue_cp.is_friendly(self.is_player): - enemy_cp = front_line.blue_cp - else: - enemy_cp = front_line.red_cp - - yield from self.game.transfers.cargo_ships.travelling_to(enemy_cp) + yield from self.game.transfers.cargo_ships.travelling_to( + front_line.control_point_hostile_to(self.is_player) + ) def friendly_control_points(self) -> Iterator[ControlPoint]: """Iterates over all friendly control points.""" From e721a234e138ba03092ef0cb190928047795ab0d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 8 May 2021 16:46:02 -0700 Subject: [PATCH 122/438] Clean up front line code. The routes do not need be be recreated each time we create a `FrontLine`. The front lines follow the convoy routes, which are static. Add the convoy route data to the `ControlPoint` the way we do for shipping lanes and have `FrontLine` load the data from there. --- game/operation/operation.py | 4 +- game/theater/__init__.py | 1 + game/theater/conflicttheater.py | 284 ++-------------------------- game/theater/controlpoint.py | 16 +- game/theater/frontline.py | 179 ++++++++++++++++++ game/transfers.py | 4 +- qt_ui/widgets/map/QLiberationMap.py | 4 +- 7 files changed, 217 insertions(+), 275 deletions(-) create mode 100644 game/theater/frontline.py diff --git a/game/operation/operation.py b/game/operation/operation.py index 73eb0ca0..0b43e45c 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -3,7 +3,7 @@ from __future__ import annotations import logging import os from pathlib import Path -from typing import Iterable, List, Optional, Set, TYPE_CHECKING +from typing import Iterable, List, Set, TYPE_CHECKING from dcs import Mission from dcs.action import DoScript, DoScriptFile @@ -94,7 +94,7 @@ class Operation: ) return Conflict( cls.game.theater, - FrontLine(player_cp, enemy_cp, cls.game.theater), + FrontLine(player_cp, enemy_cp), cls.game.player_name, cls.game.enemy_name, cls.game.player_country, diff --git a/game/theater/__init__.py b/game/theater/__init__.py index c5b83a16..d741edb7 100644 --- a/game/theater/__init__.py +++ b/game/theater/__init__.py @@ -1,5 +1,6 @@ from .base import * from .conflicttheater import * from .controlpoint import * +from .frontline import FrontLine from .missiontarget import MissionTarget from .theatergroundobject import SamGroundObject diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 62aae3e0..35e081b1 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -1,13 +1,12 @@ from __future__ import annotations import itertools -import json import logging import math from dataclasses import dataclass from functools import cached_property from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, Tuple, Union, cast +from typing import Any, Dict, Iterator, List, Optional, Tuple from dcs import Mission from dcs.countries import ( @@ -44,7 +43,6 @@ from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed from pyproj import CRS, Transformer from shapely import geometry, ops -from gen.flights.flight import FlightType from .controlpoint import ( Airfield, Carrier, @@ -54,12 +52,11 @@ from .controlpoint import ( MissionTarget, OffMapSpawn, ) +from .frontline import FrontLine from .landmap import Landmap, load_landmap, poly_contains from .projections import TransverseMercator from ..point_with_heading import PointWithHeading -from ..utils import Distance, meters, nautical_miles, pairwise - -Numeric = Union[int, float] +from ..utils import Distance, meters, nautical_miles SIZE_TINY = 150 SIZE_SMALL = 600 @@ -71,8 +68,6 @@ IMPORTANCE_LOW = 1 IMPORTANCE_MEDIUM = 1.2 IMPORTANCE_HIGH = 1.4 -FRONTLINE_MIN_CP_DISTANCE = 5000 - class MizCampaignLoader: BLUE_COUNTRY = CombinedJointTaskForcesBlue() @@ -313,14 +308,11 @@ class MizCampaignLoader: if group.units[0].type == self.SHIPPING_LANE_UNIT_TYPE: yield group - @cached_property - def front_lines(self) -> Dict[str, ComplexFrontLine]: - # Dict of front line ID to a front line. - front_lines = {} + def add_supply_routes(self) -> None: for group in self.front_line_path_groups: - # The unit will have its first waypoint at the source CP and the - # final waypoint at the destination CP. Intermediate waypoints - # define the curve of the front line. + # The unit will have its first waypoint at the source CP and the final + # waypoint at the destination CP. Each waypoint defines the path of the + # cargo ship. waypoints = [p.position for p in group.points] origin = self.theater.closest_control_point(waypoints[0]) if origin is None: @@ -333,21 +325,17 @@ class MizCampaignLoader: f"No control point near the final waypoint of {group.name}" ) - convoy_origin = waypoints[0] - convoy_destination = waypoints[-1] + # TODO: Snapping? Probably should be in the UI instead? + # convoy_origin = waypoints[0] + # convoy_destination = waypoints[-1] + # + # waypoints[0] = origin.position + # waypoints[-1] = destination.position - # Snap the begin and end points to the control points. - waypoints[0] = origin.position - waypoints[-1] = destination.position - front_line_id = f"{origin.id}|{destination.id}" - front_lines[front_line_id] = ComplexFrontLine(origin, waypoints) - self.control_points[origin.id].connect( - self.control_points[destination.id], convoy_origin + self.control_points[origin.id].create_convoy_route(destination, waypoints) + self.control_points[destination.id].create_convoy_route( + origin, list(reversed(waypoints)) ) - self.control_points[destination.id].connect( - self.control_points[origin.id], convoy_destination - ) - return front_lines def add_shipping_lanes(self) -> None: for group in self.shipping_lane_groups: @@ -466,8 +454,8 @@ class MizCampaignLoader: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) self.add_preset_locations() + self.add_supply_routes() self.add_shipping_lanes() - self.theater.set_frontline_data(self.front_lines) @dataclass @@ -492,35 +480,15 @@ class ConflictTheater: land_poly = None # type: Polygon """ daytime_map: Dict[str, Tuple[int, int]] - _frontline_data: Optional[Dict[str, ComplexFrontLine]] = None def __init__(self): self.controlpoints: List[ControlPoint] = [] - self._frontline_data: Optional[Dict[str, ComplexFrontLine]] = None """ self.land_poly = geometry.Polygon(self.landmap[0][0]) for x in self.landmap[1]: self.land_poly = self.land_poly.difference(geometry.Polygon(x)) """ - @property - def frontline_data(self) -> Optional[Dict[str, ComplexFrontLine]]: - if self._frontline_data is None: - self.load_frontline_data_from_file() - return self._frontline_data - - def load_frontline_data_from_file(self) -> None: - if self._frontline_data is not None: - logging.warning("Replacing existing frontline data from file") - self._frontline_data = FrontLine.load_json_frontlines(self) - if self._frontline_data is None: - self._frontline_data = {} - - def set_frontline_data(self, data: Dict[str, ComplexFrontLine]) -> None: - if self._frontline_data is not None: - logging.warning("Replacing existing frontline data") - self._frontline_data = data - def add_controlpoint(self, point: ControlPoint): self.controlpoints.append(point) @@ -607,7 +575,7 @@ class ConflictTheater: for enemy_cp in [ x for x in player_cp.connected_points if not x.is_friendly_to(player_cp) ]: - yield FrontLine(player_cp, enemy_cp, self) + yield FrontLine(player_cp, enemy_cp) def enemy_points(self) -> List[ControlPoint]: return list(self.control_points_for(player=False)) @@ -872,219 +840,3 @@ class SyriaTheater(ConflictTheater): from .syria import PARAMETERS return PARAMETERS - - -@dataclass -class ComplexFrontLine: - """ - Stores data necessary for building a multi-segment frontline. - "points" should be ordered from closest to farthest distance originating from start_cp.position - """ - - start_cp: ControlPoint - points: List[Point] - - -@dataclass -class FrontLineSegment: - """ - Describes a line segment of a FrontLine - """ - - point_a: Point - point_b: Point - - @property - def attack_heading(self) -> Numeric: - """The heading of the frontline segment from player to enemy control point""" - return self.point_a.heading_between_point(self.point_b) - - @property - def attack_distance(self) -> Numeric: - """Length of the segment""" - return self.point_a.distance_to_point(self.point_b) - - -class FrontLine(MissionTarget): - """Defines a front line location between two control points. - Front lines are the area where ground combat happens. - Overwrites the entirety of MissionTarget __init__ method to allow for - dynamic position calculation. - """ - - def __init__( - self, - blue_point: ControlPoint, - red_point: ControlPoint, - theater: ConflictTheater, - ) -> None: - self.blue_cp = blue_point - self.red_cp = red_point - self.segments: List[FrontLineSegment] = [] - self.theater = theater - self._build_segments() - self.name = f"Front line {blue_point}/{red_point}" - - def control_point_hostile_to(self, player: bool) -> ControlPoint: - if player: - return self.red_cp - return self.blue_cp - - def is_friendly(self, to_player: bool) -> bool: - """Returns True if the objective is in friendly territory.""" - return False - - def mission_types(self, for_player: bool) -> Iterator[FlightType]: - yield from [ - FlightType.CAS, - FlightType.AEWC, - # TODO: FlightType.TROOP_TRANSPORT - # TODO: FlightType.EVAC - ] - yield from super().mission_types(for_player) - - @property - def position(self): - """ - The position where the conflict should occur - according to the current strength of each control point. - """ - return self.point_from_a(self._position_distance) - - @property - def points(self) -> Iterator[Point]: - yield self.segments[0].point_a - for segment in self.segments: - yield segment.point_b - - @property - def control_points(self) -> Tuple[ControlPoint, ControlPoint]: - """Returns a tuple of the two control points.""" - return self.blue_cp, self.red_cp - - @property - def attack_distance(self): - """The total distance of all segments""" - return sum(i.attack_distance for i in self.segments) - - @property - def attack_heading(self): - """The heading of the active attack segment from player to enemy control point""" - return self.active_segment.attack_heading - - @property - def active_segment(self) -> FrontLineSegment: - """The FrontLine segment where there can be an active conflict""" - if self._position_distance <= self.segments[0].attack_distance: - return self.segments[0] - - remaining_dist = self._position_distance - for segment in self.segments: - if remaining_dist <= segment.attack_distance: - return segment - else: - remaining_dist -= segment.attack_distance - logging.error( - "Frontline attack distance is greater than the sum of its segments" - ) - return self.segments[0] - - def point_from_a(self, distance: Numeric) -> Point: - """ - Returns a point {distance} away from control_point_a along the frontline segments. - """ - if distance < self.segments[0].attack_distance: - return self.blue_cp.position.point_from_heading( - self.segments[0].attack_heading, distance - ) - remaining_dist = distance - for segment in self.segments: - if remaining_dist < segment.attack_distance: - return segment.point_a.point_from_heading( - segment.attack_heading, remaining_dist - ) - else: - remaining_dist -= segment.attack_distance - - @property - def _position_distance(self) -> float: - """ - The distance from point "a" where the conflict should occur - according to the current strength of each control point - """ - total_strength = self.blue_cp.base.strength + self.red_cp.base.strength - if self.blue_cp.base.strength == 0: - return self._adjust_for_min_dist(0) - if self.red_cp.base.strength == 0: - return self._adjust_for_min_dist(self.attack_distance) - strength_pct = self.blue_cp.base.strength / total_strength - return self._adjust_for_min_dist(strength_pct * self.attack_distance) - - def _adjust_for_min_dist(self, distance: Numeric) -> Numeric: - """ - Ensures the frontline conflict is never located within the minimum distance - constant of either end control point. - """ - if (distance > self.attack_distance / 2) and ( - distance + FRONTLINE_MIN_CP_DISTANCE > self.attack_distance - ): - distance = self.attack_distance - FRONTLINE_MIN_CP_DISTANCE - elif (distance < self.attack_distance / 2) and ( - distance < FRONTLINE_MIN_CP_DISTANCE - ): - distance = FRONTLINE_MIN_CP_DISTANCE - return distance - - def _build_segments(self) -> None: - """Create line segments for the frontline""" - control_point_ids = "|".join( - [str(self.blue_cp.id), str(self.red_cp.id)] - ) # from_cp.id|to_cp.id - reversed_cp_ids = "|".join([str(self.red_cp.id), str(self.blue_cp.id)]) - complex_frontlines = self.theater.frontline_data - if (complex_frontlines) and ( - (control_point_ids in complex_frontlines) - or (reversed_cp_ids in complex_frontlines) - ): - # The frontline segments must be stored in the correct order for the distance algorithms to work. - # The points in the frontline are ordered from the id before the | to the id after. - # First, check if control point id pair matches in order, and create segments if a match is found. - if control_point_ids in complex_frontlines: - point_pairs = pairwise(complex_frontlines[control_point_ids].points) - for i in point_pairs: - self.segments.append(FrontLineSegment(i[0], i[1])) - # Check the reverse order and build in reverse if found. - elif reversed_cp_ids in complex_frontlines: - point_pairs = pairwise( - reversed(complex_frontlines[reversed_cp_ids].points) - ) - for i in point_pairs: - self.segments.append(FrontLineSegment(i[0], i[1])) - # If no complex frontline has been configured, fall back to the old straight line method. - else: - self.segments.append( - FrontLineSegment(self.blue_cp.position, self.red_cp.position) - ) - - @staticmethod - def load_json_frontlines( - theater: ConflictTheater, - ) -> Optional[Dict[str, ComplexFrontLine]]: - """Load complex frontlines from json""" - try: - path = Path(f"resources/frontlines/{theater.terrain.name.lower()}.json") - with open(path, "r") as file: - logging.debug(f"Loading frontline from {path}...") - data = json.load(file) - return { - frontline: ComplexFrontLine( - data[frontline]["start_cp"], - [Point(i[0], i[1]) for i in data[frontline]["points"]], - ) - for frontline in data - } - except OSError: - logging.warning( - f"Unable to load preset frontlines for {theater.terrain.name}" - ) - return None diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 52937ab0..f811f059 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -273,8 +273,8 @@ class ControlPoint(MissionTarget, ABC): # TODO: Should be Airbase specific. self.has_frontline = has_frontline self.connected_points: List[ControlPoint] = [] + self.convoy_routes: Dict[ControlPoint, List[Point]] = {} self.shipping_lanes: Dict[ControlPoint, List[Point]] = {} - self.convoy_spawns: Dict[ControlPoint, Point] = {} self.base: Base = Base() self.cptype = cptype # TODO: Should be Airbase specific. @@ -416,11 +416,21 @@ class ControlPoint(MissionTarget, ABC): ... # TODO: Should be Airbase specific. - def connect(self, to: ControlPoint, convoy_location: Point) -> None: + def connect(self, to: ControlPoint) -> None: self.connected_points.append(to) - self.convoy_spawns[to] = convoy_location self.stances[to.id] = CombatStance.DEFENSIVE + def convoy_origin_for(self, destination: ControlPoint) -> Point: + return self.convoy_route_to(destination)[0] + + def convoy_route_to(self, destination: ControlPoint) -> List[Point]: + return self.convoy_routes[destination] + + def create_convoy_route(self, to: ControlPoint, waypoints: List[Point]) -> None: + self.connected_points.append(to) + self.stances[to.id] = CombatStance.DEFENSIVE + self.convoy_routes[to] = waypoints + def create_shipping_lane(self, to: ControlPoint, waypoints: List[Point]) -> None: self.shipping_lanes[to] = waypoints diff --git a/game/theater/frontline.py b/game/theater/frontline.py new file mode 100644 index 00000000..97ef6a9b --- /dev/null +++ b/game/theater/frontline.py @@ -0,0 +1,179 @@ +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import Iterator, List, Tuple + +from dcs.mapping import Point + +from gen.flights.flight import FlightType +from .controlpoint import ( + ControlPoint, + MissionTarget, +) +from ..utils import pairwise + + +FRONTLINE_MIN_CP_DISTANCE = 5000 + + +@dataclass +class FrontLineSegment: + """ + Describes a line segment of a FrontLine + """ + + point_a: Point + point_b: Point + + @property + def attack_heading(self) -> float: + """The heading of the frontline segment from player to enemy control point""" + return self.point_a.heading_between_point(self.point_b) + + @property + def attack_distance(self) -> float: + """Length of the segment""" + return self.point_a.distance_to_point(self.point_b) + + +class FrontLine(MissionTarget): + """Defines a front line location between two control points. + Front lines are the area where ground combat happens. + Overwrites the entirety of MissionTarget __init__ method to allow for + dynamic position calculation. + """ + + def __init__( + self, + blue_point: ControlPoint, + red_point: ControlPoint, + ) -> None: + self.blue_cp = blue_point + self.red_cp = red_point + try: + route = blue_point.convoy_route_to(red_point) + except KeyError: + # Some campaigns are air only and the mission generator currently relies on + # *some* "front line" being drawn between these two. In this case there will + # be no supply route to follow. Just create an arbitrary route between the + # two points. + route = [blue_point.position, red_point.position] + # Snap the beginning and end points to the CPs rather than the convoy waypoints, + # which are on roads. + route[0] = blue_point.position + route[-1] = red_point.position + self.segments: List[FrontLineSegment] = [ + FrontLineSegment(a, b) for a, b in pairwise(route) + ] + self.name = f"Front line {blue_point}/{red_point}" + + def control_point_hostile_to(self, player: bool) -> ControlPoint: + if player: + return self.red_cp + return self.blue_cp + + def is_friendly(self, to_player: bool) -> bool: + """Returns True if the objective is in friendly territory.""" + return False + + def mission_types(self, for_player: bool) -> Iterator[FlightType]: + yield from [ + FlightType.CAS, + FlightType.AEWC, + # TODO: FlightType.TROOP_TRANSPORT + # TODO: FlightType.EVAC + ] + yield from super().mission_types(for_player) + + @property + def position(self): + """ + The position where the conflict should occur + according to the current strength of each control point. + """ + return self.point_from_a(self._position_distance) + + @property + def points(self) -> Iterator[Point]: + yield self.segments[0].point_a + for segment in self.segments: + yield segment.point_b + + @property + def control_points(self) -> Tuple[ControlPoint, ControlPoint]: + """Returns a tuple of the two control points.""" + return self.blue_cp, self.red_cp + + @property + def attack_distance(self): + """The total distance of all segments""" + return sum(i.attack_distance for i in self.segments) + + @property + def attack_heading(self): + """The heading of the active attack segment from player to enemy control point""" + return self.active_segment.attack_heading + + @property + def active_segment(self) -> FrontLineSegment: + """The FrontLine segment where there can be an active conflict""" + if self._position_distance <= self.segments[0].attack_distance: + return self.segments[0] + + remaining_dist = self._position_distance + for segment in self.segments: + if remaining_dist <= segment.attack_distance: + return segment + else: + remaining_dist -= segment.attack_distance + logging.error( + "Frontline attack distance is greater than the sum of its segments" + ) + return self.segments[0] + + def point_from_a(self, distance: float) -> Point: + """ + Returns a point {distance} away from control_point_a along the frontline segments. + """ + if distance < self.segments[0].attack_distance: + return self.blue_cp.position.point_from_heading( + self.segments[0].attack_heading, distance + ) + remaining_dist = distance + for segment in self.segments: + if remaining_dist < segment.attack_distance: + return segment.point_a.point_from_heading( + segment.attack_heading, remaining_dist + ) + else: + remaining_dist -= segment.attack_distance + + @property + def _position_distance(self) -> float: + """ + The distance from point "a" where the conflict should occur + according to the current strength of each control point + """ + total_strength = self.blue_cp.base.strength + self.red_cp.base.strength + if self.blue_cp.base.strength == 0: + return self._adjust_for_min_dist(0) + if self.red_cp.base.strength == 0: + return self._adjust_for_min_dist(self.attack_distance) + strength_pct = self.blue_cp.base.strength / total_strength + return self._adjust_for_min_dist(strength_pct * self.attack_distance) + + def _adjust_for_min_dist(self, distance: float) -> float: + """ + Ensures the frontline conflict is never located within the minimum distance + constant of either end control point. + """ + if (distance > self.attack_distance / 2) and ( + distance + FRONTLINE_MIN_CP_DISTANCE > self.attack_distance + ): + distance = self.attack_distance - FRONTLINE_MIN_CP_DISTANCE + elif (distance < self.attack_distance / 2) and ( + distance < FRONTLINE_MIN_CP_DISTANCE + ): + distance = FRONTLINE_MIN_CP_DISTANCE + return distance diff --git a/game/transfers.py b/game/transfers.py index 0a2f1877..b531fce4 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -338,11 +338,11 @@ class Convoy(MultiGroupTransport): @property def route_start(self) -> Point: - return self.origin.convoy_spawns[self.destination] + return self.origin.convoy_origin_for(self.destination) @property def route_end(self) -> Point: - return self.destination.convoy_spawns[self.origin] + return self.destination.convoy_origin_for(self.origin) def description(self) -> str: return f"In a convoy from {self.origin} to {self.destination}" diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 50359d19..51c0576f 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -915,9 +915,9 @@ class QLiberationMap(QGraphicsView): convoys.append(convoy) if a.captured: - frontline = FrontLine(a, b, self.game.theater) + frontline = FrontLine(a, b) else: - frontline = FrontLine(b, a, self.game.theater) + frontline = FrontLine(b, a) if a.front_is_active(b): if DisplayOptions.actual_frontline_pos: self.draw_actual_frontline(scene, frontline, convoys) From 87e6080215b027b438cd87d2f1aad1e169626799 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 8 May 2021 16:49:05 -0700 Subject: [PATCH 123/438] Fix "show actual front line location". --- qt_ui/widgets/map/QLiberationMap.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 51c0576f..afd94e78 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -949,9 +949,7 @@ class QLiberationMap(QGraphicsView): convoys: List[Convoy], ) -> None: self.draw_bezier_frontline(scene, frontline, convoys) - vector = Conflict.frontline_vector( - frontline.blue_cp, frontline.red_cp, self.game.theater - ) + vector = Conflict.frontline_vector(frontline, self.game.theater) left_pos = self._transform_point(vector[0]) right_pos = self._transform_point( vector[0].point_from_heading(vector[1], vector[2]) From de07f10e57d9ca66c31e092d6b37fa27166e1a4f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 8 May 2021 16:50:35 -0700 Subject: [PATCH 124/438] Remove fixed TODO. --- game/theater/conflicttheater.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 35e081b1..533cac27 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -325,13 +325,6 @@ class MizCampaignLoader: f"No control point near the final waypoint of {group.name}" ) - # TODO: Snapping? Probably should be in the UI instead? - # convoy_origin = waypoints[0] - # convoy_destination = waypoints[-1] - # - # waypoints[0] = origin.position - # waypoints[-1] = destination.position - self.control_points[origin.id].create_convoy_route(destination, waypoints) self.control_points[destination.id].create_convoy_route( origin, list(reversed(waypoints)) From b7619630cfcbdd3f8770b831798e589b9d98c629 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 8 May 2021 17:55:18 -0700 Subject: [PATCH 125/438] Add logged_duration context manager for profiling. --- game/profiling.py | 13 +++++++++ game/theater/conflicttheater.py | 8 ++++-- game/theater/start_generator.py | 28 ++++++++++--------- qt_ui/main.py | 25 +++++++++-------- qt_ui/widgets/QTopPanel.py | 16 ++++------- .../windows/QWaitingForMissionResultWindow.py | 15 ++++------ 6 files changed, 59 insertions(+), 46 deletions(-) create mode 100644 game/profiling.py diff --git a/game/profiling.py b/game/profiling.py new file mode 100644 index 00000000..364cdfd3 --- /dev/null +++ b/game/profiling.py @@ -0,0 +1,13 @@ +import logging +import timeit +from contextlib import contextmanager +from datetime import timedelta +from typing import Iterator + + +@contextmanager +def logged_duration(event: str) -> Iterator[None]: + start = timeit.default_timer() + yield + end = timeit.default_timer() + logging.debug("%s took %s", event, timedelta(seconds=end - start)) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 533cac27..9933404e 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -56,6 +56,7 @@ from .frontline import FrontLine from .landmap import Landmap, load_landmap, poly_contains from .projections import TransverseMercator from ..point_with_heading import PointWithHeading +from ..profiling import logged_duration from ..utils import Distance, meters, nautical_miles SIZE_TINY = 150 @@ -114,7 +115,8 @@ class MizCampaignLoader: def __init__(self, miz: Path, theater: ConflictTheater) -> None: self.theater = theater self.mission = Mission() - self.mission.load_file(str(miz)) + with logged_duration("Loading miz"): + self.mission.load_file(str(miz)) self.control_point_id = itertools.count(1000) # If there are no red carriers there usually aren't red units. Make sure @@ -682,7 +684,9 @@ class ConflictTheater: raise RuntimeError( "Old format (non-miz) campaigns are no longer supported." ) - MizCampaignLoader(directory / miz, t).populate_theater() + + with logged_duration("Importing miz data"): + MizCampaignLoader(directory / miz, t).populate_theater() return t @property diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 34bc8dad..d560b9d9 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -46,6 +46,7 @@ from . import ( Fob, OffMapSpawn, ) +from ..profiling import logged_duration from ..settings import Settings GroundObjectTemplates = Dict[str, Dict[str, Any]] @@ -91,20 +92,21 @@ class GameGenerator: self.generator_settings = generator_settings def generate(self) -> Game: - # Reset name generator - namegen.reset() - self.prepare_theater() - game = Game( - player_name=self.player, - enemy_name=self.enemy, - theater=self.theater, - start_date=self.generator_settings.start_date, - settings=self.settings, - player_budget=self.generator_settings.player_budget, - enemy_budget=self.generator_settings.enemy_budget, - ) + with logged_duration("TGO population"): + # Reset name generator + namegen.reset() + self.prepare_theater() + game = Game( + player_name=self.player, + enemy_name=self.enemy, + theater=self.theater, + start_date=self.generator_settings.start_date, + settings=self.settings, + player_budget=self.generator_settings.player_budget, + enemy_budget=self.generator_settings.enemy_budget, + ) - GroundObjectGenerator(game, self.generator_settings).generate() + GroundObjectGenerator(game, self.generator_settings).generate() game.settings.version = VERSION game.begin_turn_0() return game diff --git a/qt_ui/main.py b/qt_ui/main.py index 28582593..08755e15 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -7,18 +7,18 @@ from pathlib import Path from typing import Optional import dcs -from dcs.weapons_data import weapon_ids - from PySide2 import QtWidgets from PySide2.QtGui import QPixmap from PySide2.QtWidgets import QApplication, QSplashScreen +from dcs.weapons_data import weapon_ids -from game import Game, db, persistency, VERSION +from game import Game, VERSION, persistency from game.data.weapons import ( WEAPON_FALLBACK_MAP, WEAPON_INTRODUCTION_YEARS, Weapon, ) +from game.profiling import logged_duration from game.settings import Settings from game.theater.start_generator import GameGenerator, GeneratorSettings from qt_ui import ( @@ -228,15 +228,16 @@ def main(): lint_weapon_data() if args.subcommand == "new-game": - game = create_game( - args.campaign, - args.blue, - args.red, - args.supercarrier, - args.auto_procurement, - args.inverted, - args.cheats, - ) + with logged_duration("New game creation"): + game = create_game( + args.campaign, + args.blue, + args.red, + args.supercarrier, + args.auto_procurement, + args.inverted, + args.cheats, + ) run_ui(game) diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index f43657f7..a49d68c1 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -1,6 +1,3 @@ -import logging -import timeit -from datetime import timedelta from typing import List, Optional from PySide2.QtWidgets import ( @@ -15,10 +12,12 @@ from PySide2.QtWidgets import ( import qt_ui.uiconstants as CONST from game import Game from game.event.airwar import AirWarEvent +from game.profiling import logged_duration from gen.ato import Package from gen.flights.traveltime import TotEstimator from qt_ui.models import GameModel from qt_ui.widgets.QBudgetBox import QBudgetBox +from qt_ui.widgets.QConditionsWidget import QConditionsWidget from qt_ui.widgets.QFactionsInfos import QFactionsInfos from qt_ui.widgets.QIntelBox import QIntelBox from qt_ui.widgets.clientslots import MaxPlayerCount @@ -27,7 +26,6 @@ from qt_ui.windows.PendingTransfersDialog import PendingTransfersDialog from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResultWindow from qt_ui.windows.settings.QSettingsWindow import QSettingsWindow from qt_ui.windows.stats.QStatsWindow import QStatsWindow -from qt_ui.widgets.QConditionsWidget import QConditionsWidget class QTopPanel(QFrame): @@ -145,12 +143,10 @@ class QTopPanel(QFrame): self.dialog.show() def passTurn(self): - start = timeit.default_timer() - self.game.pass_turn(no_action=True) - GameUpdateSignal.get_instance().updateGame(self.game) - self.proceedButton.setEnabled(True) - end = timeit.default_timer() - logging.info("Skipping turn took %s", timedelta(seconds=end - start)) + with logged_duration("Skipping turn"): + self.game.pass_turn(no_action=True) + GameUpdateSignal.get_instance().updateGame(self.game) + self.proceedButton.setEnabled(True) def negative_start_packages(self) -> List[Package]: packages = [] diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index 7f02f755..b4addd4d 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -2,8 +2,6 @@ from __future__ import annotations import json import os -import timeit -from datetime import timedelta from typing import Sized from PySide2 import QtCore @@ -25,6 +23,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape from game.debriefing import Debriefing, wait_for_debriefing from game.game import Event, Game, logging from game.persistency import base_path +from game.profiling import logged_duration from game.unitmap import UnitMap from qt_ui.windows.GameUpdateSignal import GameUpdateSignal @@ -205,14 +204,12 @@ class QWaitingForMissionResultWindow(QDialog): ) def process_debriefing(self): - start = timeit.default_timer() - self.game.finish_event(event=self.gameEvent, debriefing=self.debriefing) - self.game.pass_turn() + with logged_duration("Turn processing"): + self.game.finish_event(event=self.gameEvent, debriefing=self.debriefing) + self.game.pass_turn() - GameUpdateSignal.get_instance().sendDebriefing(self.debriefing) - GameUpdateSignal.get_instance().updateGame(self.game) - end = timeit.default_timer() - logging.info("Turn processing took %s", timedelta(seconds=end - start)) + GameUpdateSignal.get_instance().sendDebriefing(self.debriefing) + GameUpdateSignal.get_instance().updateGame(self.game) self.close() def debriefing_directory_location(self) -> str: From 5b191d72a627d34319b0492e78c3bbf40eda6b26 Mon Sep 17 00:00:00 2001 From: Hornet2041 <55866858+Hornet2041@users.noreply.github.com> Date: Mon, 10 May 2021 21:14:56 -0400 Subject: [PATCH 126/438] Add F-4E Phantom to the list of CAS/STRIKE capable planes. --- gen/flights/ai_flight_planner_db.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gen/flights/ai_flight_planner_db.py b/gen/flights/ai_flight_planner_db.py index c5d7c977..9715fb43 100644 --- a/gen/flights/ai_flight_planner_db.py +++ b/gen/flights/ai_flight_planner_db.py @@ -185,6 +185,7 @@ CAS_CAPABLE = [ Su_24M, Su_17M4, AV8BNA, + F_4E, S_3B, Su_34, Su_30, @@ -304,6 +305,7 @@ STRIKE_CAPABLE = [ MiG_29G, MiG_29A, JF_17, + F_4E, A_10C_2, A_10C, AV8BNA, From 747683e9e8738a98e82d7debd83237a20c19b408 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 10 May 2021 20:21:19 -0700 Subject: [PATCH 127/438] Allow other TGO types to be factories. The `FactoryGroundObject` is just a special case of `BuildingGroundObject` that we maybe don't actually need. For now it provides some special case logic for the layout, but this allows any TGO with the "factory" category to behave as a ground unit source. Note that the "factory" random strike targets are *not* generated anymore, so this doesn't affect campaign design currently. --- game/theater/controlpoint.py | 5 ++--- game/theater/theatergroundobject.py | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index f811f059..f7714b72 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -21,17 +21,16 @@ from dcs.terrain.terrain import Airport, ParkingSlot from dcs.unittype import FlyingType from game import db +from game.point_with_heading import PointWithHeading from gen.flights.closestairfields import ObjectiveDistanceCache from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD from gen.ground_forces.combat_stance import CombatStance from gen.runways import RunwayAssigner, RunwayData from .base import Base from .missiontarget import MissionTarget -from game.point_with_heading import PointWithHeading from .theatergroundobject import ( BaseDefenseGroundObject, EwrGroundObject, - FactoryGroundObject, GenericCarrierGroundObject, SamGroundObject, TheaterGroundObject, @@ -341,7 +340,7 @@ class ControlPoint(MissionTarget, ABC): @property def has_factory(self) -> bool: for tgo in self.connected_objectives: - if isinstance(tgo, FactoryGroundObject) and not tgo.is_dead: + if tgo.is_factory and not tgo.is_dead: return True return False diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index 949e93a7..ec12bf08 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -217,6 +217,10 @@ class TheaterGroundObject(MissionTarget): return meters(0) return self._max_range_of_type(group, "threat_range") + @property + def is_factory(self) -> bool: + return self.category == "factory" + class BuildingGroundObject(TheaterGroundObject): def __init__( From 56abd0bb7fea57189fa8083a027f89942ef0134c Mon Sep 17 00:00:00 2001 From: Schneefl0cke <60181177+Schneefl0cke@users.noreply.github.com> Date: Tue, 11 May 2021 12:13:15 +0200 Subject: [PATCH 128/438] Add option for setting desired mission length. --- changelog.md | 1 + game/settings.py | 4 ++ gen/flights/ai_flight_planner.py | 32 ++++------- qt_ui/widgets/spinsliders.py | 69 ++++++++++++++++++++++- qt_ui/windows/newgame/QNewGameWizard.py | 35 +++++------- qt_ui/windows/settings/QSettingsWindow.py | 17 +++++- 6 files changed, 114 insertions(+), 44 deletions(-) diff --git a/changelog.md b/changelog.md index 7859c58e..af6ca83c 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ Saves from 2.5 are not compatible with 3.0. * **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/dcs-liberation/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn. +* **[Campaign AI]** Every 30 minutes the AI will plan a CAP, so players can customize their mission better. * **[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. * **[Modding]** Campaigns now choose locations for factories to spawn. * **[Modding]** Can now install custom factions to /Liberation/Factions instead of the Liberation install directory. diff --git a/game/settings.py b/game/settings.py index 7d7bc0d1..192d080f 100644 --- a/game/settings.py +++ b/game/settings.py @@ -1,4 +1,5 @@ from dataclasses import dataclass, field +from datetime import timedelta from typing import Dict, Optional from dcs.forcedoptions import ForcedOptions @@ -25,6 +26,9 @@ class Settings: default_start_type: str = "Cold" + # Mission specific + desired_player_mission_duration: timedelta = timedelta(minutes=90) + # Campaign management automate_runway_repair: bool = False automate_front_line_reinforcements: bool = False diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index f2177782..868ae8ac 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -583,26 +583,18 @@ class CoalitionMissionPlanner: # Find friendly CPs within 100 nmi from an enemy airfield, plan CAP. for cp in self.objective_finder.vulnerable_control_points(): - # Plan three rounds of CAP to give ~90 minutes coverage. Spacing - # these out appropriately is done in stagger_missions. - yield ProposedMission( - cp, - [ - ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE), - ], - ) - yield ProposedMission( - cp, - [ - ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE), - ], - ) - yield ProposedMission( - cp, - [ - ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE), - ], - ) + # Plan CAP in such a way, that it is established during the whole desired mission length + for _ in range( + 0, + int(self.game.settings.desired_player_mission_duration.total_seconds()), + int(self.faction.doctrine.cap_duration.total_seconds()), + ): + yield ProposedMission( + cp, + [ + ProposedFlight(FlightType.BARCAP, 2, self.MAX_CAP_RANGE), + ], + ) # Find front lines, plan CAS. for front_line in self.objective_finder.front_lines(): diff --git a/qt_ui/widgets/spinsliders.py b/qt_ui/widgets/spinsliders.py index 90623a6d..93d3017e 100644 --- a/qt_ui/widgets/spinsliders.py +++ b/qt_ui/widgets/spinsliders.py @@ -1,7 +1,9 @@ +from PySide2 import QtWidgets from PySide2.QtCore import Qt from PySide2.QtWidgets import QGridLayout, QLabel, QSlider - +from typing import Optional from qt_ui.widgets.floatspinners import TenthsSpinner +from datetime import timedelta class TenthsSpinSlider(QGridLayout): @@ -23,3 +25,68 @@ class TenthsSpinSlider(QGridLayout): @property def value(self) -> float: return self.spinner.value() / 10 + + +class TimeInputs(QtWidgets.QGridLayout): + def __init__(self, label: str, initial: timedelta) -> None: + super().__init__() + self.addWidget(QtWidgets.QLabel(label), 0, 0) + + minimum_minutes = 30 + maximum_minutes = 150 + initial_minutes = int(initial.total_seconds() / 60) + + slider = QtWidgets.QSlider(Qt.Horizontal) + slider.setMinimum(minimum_minutes) + slider.setMaximum(maximum_minutes) + slider.setValue(initial_minutes) + self.spinner = TimeSpinner(minimum_minutes, maximum_minutes, initial_minutes) + slider.valueChanged.connect(lambda x: self.spinner.setValue(x)) + self.spinner.valueChanged.connect(lambda x: slider.setValue(x)) + + self.addWidget(slider, 1, 0) + self.addWidget(self.spinner, 1, 1) + + @property + def value(self) -> timedelta: + return timedelta(minutes=self.spinner.value()) + + +class TimeSpinner(QtWidgets.QSpinBox): + def __init__( + self, + minimum: Optional[int] = None, + maximum: Optional[int] = None, + initial: Optional[int] = None, + ) -> None: + super().__init__() + + if minimum is not None: + self.setMinimum(minimum) + if maximum is not None: + self.setMaximum(maximum) + if initial is not None: + self.setValue(initial) + + def textFromValue(self, val: int) -> str: + return f"{val} minutes" + + +class CurrencySpinner(QtWidgets.QSpinBox): + def __init__( + self, + minimum: Optional[int] = None, + maximum: Optional[int] = None, + initial: Optional[int] = None, + ) -> None: + super().__init__() + + if minimum is not None: + self.setMinimum(minimum) + if maximum is not None: + self.setMaximum(maximum) + if initial is not None: + self.setValue(initial) + + def textFromValue(self, val: int) -> str: + return f"${val}" diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index 87e34ab9..ecd64056 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -7,12 +7,13 @@ from PySide2 import QtGui, QtWidgets from PySide2.QtCore import QItemSelectionModel, QPoint, Qt, QDate from PySide2.QtWidgets import QVBoxLayout, QTextEdit, QLabel from jinja2 import Environment, FileSystemLoader, select_autoescape +from datetime import timedelta from game import db from game.settings import Settings from game.theater.start_generator import GameGenerator, GeneratorSettings from qt_ui.widgets.QLiberationCalendar import QLiberationCalendar -from qt_ui.widgets.spinsliders import TenthsSpinSlider +from qt_ui.widgets.spinsliders import TenthsSpinSlider, TimeInputs, CurrencySpinner from qt_ui.windows.newgame.QCampaignList import ( Campaign, QCampaignList, @@ -33,8 +34,8 @@ jinja_env = Environment( lstrip_blocks=True, ) - DEFAULT_BUDGET = 2000 +DEFAULT_MISSION_LENGTH: timedelta = timedelta(minutes=90) class NewGameWizard(QtWidgets.QWizard): @@ -85,6 +86,9 @@ class NewGameWizard(QtWidgets.QWizard): automate_front_line_reinforcements=self.field( "automate_front_line_purchases" ), + desired_player_mission_duration=timedelta( + minutes=self.field("desired_player_mission_duration") + ), automate_aircraft_reinforcements=self.field("automate_aircraft_purchases"), supercarrier=self.field("supercarrier"), enable_new_ground_unit_recruitment=self.field( @@ -428,26 +432,6 @@ class TheaterConfiguration(QtWidgets.QWizardPage): self.setLayout(layout) -class CurrencySpinner(QtWidgets.QSpinBox): - def __init__( - self, - minimum: Optional[int] = None, - maximum: Optional[int] = None, - initial: Optional[int] = None, - ) -> None: - super().__init__() - - if minimum is not None: - self.setMinimum(minimum) - if maximum is not None: - self.setMaximum(maximum) - if initial is not None: - self.setValue(initial) - - def textFromValue(self, val: int) -> str: - return f"${val}" - - class BudgetInputs(QtWidgets.QGridLayout): def __init__(self, label: str) -> None: super().__init__() @@ -565,6 +549,12 @@ class GeneratorOptions(QtWidgets.QWizardPage): self.registerField("no_player_navy", no_player_navy) no_enemy_navy = QtWidgets.QCheckBox() self.registerField("no_enemy_navy", no_enemy_navy) + desired_player_mission_duration = TimeInputs( + "Desired mission duration", DEFAULT_MISSION_LENGTH + ) + self.registerField( + "desired_player_mission_duration", desired_player_mission_duration.spinner + ) generatorLayout = QtWidgets.QGridLayout() generatorLayout.addWidget(QtWidgets.QLabel("No Aircraft Carriers"), 1, 0) @@ -577,6 +567,7 @@ class GeneratorOptions(QtWidgets.QWizardPage): generatorLayout.addWidget(no_player_navy, 4, 1) generatorLayout.addWidget(QtWidgets.QLabel("No Enemy Navy"), 5, 0) generatorLayout.addWidget(no_enemy_navy, 5, 1) + generatorLayout.addLayout(desired_player_mission_duration, 6, 0) generatorSettingsGroup.setLayout(generatorLayout) mlayout = QVBoxLayout() diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index 8af7df3d..c7e13e7d 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -26,7 +26,7 @@ from game.game import Game from game.infos.information import Information from game.settings import Settings from qt_ui.widgets.QLabeledWidget import QLabeledWidget -from qt_ui.widgets.spinsliders import TenthsSpinSlider +from qt_ui.widgets.spinsliders import TenthsSpinSlider, TimeInputs from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.finances.QFinancesMenu import QHorizontalSeparationLine from qt_ui.windows.settings.plugins import PluginOptionsPage, PluginsPage @@ -471,6 +471,14 @@ class QSettingsWindow(QDialog): "spawned immediately. AI wingmen may begin startup immediately." ) + self.desired_player_mission_duration = TimeInputs( + "Desired mission duration", + self.game.settings.desired_player_mission_duration, + ) + self.desired_player_mission_duration.spinner.valueChanged.connect( + self.applySettings + ) + self.gameplayLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0) self.gameplayLayout.addWidget(self.supercarrier, 0, 1, Qt.AlignRight) self.gameplayLayout.addWidget(QLabel("Put Objective Markers on Map"), 1, 0) @@ -483,6 +491,9 @@ class QSettingsWindow(QDialog): ) self.gameplayLayout.addWidget(dark_kneeboard_label, 2, 0) self.gameplayLayout.addWidget(self.generate_dark_kneeboard, 2, 1, Qt.AlignRight) + self.gameplayLayout.addLayout( + self.desired_player_mission_duration, 5, 0, Qt.AlignRight + ) spawn_players_immediately_tooltip = ( "Always spawns player aircraft immediately, even if their start time is " @@ -695,6 +706,10 @@ class QSettingsWindow(QDialog): self.generate_dark_kneeboard.isChecked() ) + self.game.settings.desired_player_mission_duration = ( + self.desired_player_mission_duration.value + ) + self.game.settings.perf_red_alert_state = self.red_alert.isChecked() self.game.settings.perf_smoke_gen = self.smoke.isChecked() self.game.settings.perf_smoke_spacing = self.smoke_spacing.value() From d9d68cd37c18f589b65904120402e4d7e54cb1a7 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 28 Apr 2021 16:13:21 -0700 Subject: [PATCH 129/438] Add a new Leaflet based map UI. This is extremely WIP. It is not usable for play yet. Enable with `--new-map`. --- changelog.md | 1 + game/theater/conflicttheater.py | 61 ++-- qt_ui/main.py | 10 +- qt_ui/widgets/map/QLiberationMap.py | 81 ++++- qt_ui/widgets/map/mapmodel.py | 323 ++++++++++++++++++ qt_ui/windows/GameUpdateSignal.py | 4 + qt_ui/windows/QLiberationWindow.py | 19 +- .../waypoints/QFlightWaypointInfoBox.py | 36 +- resources/ui/map/canvas.html | 35 ++ resources/ui/map/map.js | 220 ++++++++++++ 10 files changed, 715 insertions(+), 75 deletions(-) create mode 100644 qt_ui/widgets/map/mapmodel.py create mode 100644 resources/ui/map/canvas.html create mode 100644 resources/ui/map/map.js diff --git a/changelog.md b/changelog.md index af6ca83c..c8d4200f 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,7 @@ Saves from 2.5 are not compatible with 3.0. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. * **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn. * **[Campaign AI]** Every 30 minutes the AI will plan a CAP, so players can customize their mission better. +* **[UI]** Added (extremely WIP) new web based map UI. Enable with --new-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. * **[Modding]** Campaigns now choose locations for factories to spawn. * **[Modding]** Can now install custom factions to /Liberation/Factions instead of the Liberation install directory. diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 9933404e..8a1257ee 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -478,12 +478,36 @@ class ConflictTheater: def __init__(self): self.controlpoints: List[ControlPoint] = [] + self.point_to_ll_transformer = Transformer.from_crs( + self.projection_parameters.to_crs(), CRS("WGS84") + ) + self.ll_to_point_transformer = Transformer.from_crs( + CRS("WGS84"), self.projection_parameters.to_crs() + ) """ self.land_poly = geometry.Polygon(self.landmap[0][0]) for x in self.landmap[1]: self.land_poly = self.land_poly.difference(geometry.Polygon(x)) """ + def __getstate__(self) -> Dict[str, Any]: + state = self.__dict__.copy() + # Avoid persisting any volatile types that can be deterministically + # recomputed on load for the sake of save compatibility. + del state["point_to_ll_transformer"] + del state["ll_to_point_transformer"] + return state + + def __setstate__(self, state: Dict[str, Any]) -> None: + self.__dict__.update(state) + # Regenerate any state that was not persisted. + self.point_to_ll_transformer = Transformer.from_crs( + self.projection_parameters.to_crs(), CRS("WGS84") + ) + self.ll_to_point_transformer = Transformer.from_crs( + CRS("WGS84"), self.projection_parameters.to_crs() + ) + def add_controlpoint(self, point: ControlPoint): self.controlpoints.append(point) @@ -637,35 +661,6 @@ class ConflictTheater: return i raise KeyError(f"Cannot find ControlPoint with ID {id}") - def add_json_cp(self, theater, p: dict) -> ControlPoint: - cp: ControlPoint - if p["type"] == "airbase": - - airbase = theater.terrain.airports[p["id"]] - - if "size" in p.keys(): - size = p["size"] - else: - size = SIZE_REGULAR - - if "importance" in p.keys(): - importance = p["importance"] - else: - importance = IMPORTANCE_MEDIUM - - cp = Airfield(airbase, size, importance) - elif p["type"] == "carrier": - cp = Carrier("carrier", Point(p["x"], p["y"]), p["id"]) - else: - cp = Lha("lha", Point(p["x"], p["y"]), p["id"]) - - if "captured_invert" in p.keys(): - cp.captured_invert = p["captured_invert"] - else: - cp.captured_invert = False - - return cp - @staticmethod def from_json(directory: Path, data: Dict[str, Any]) -> ConflictTheater: theaters = { @@ -694,15 +689,11 @@ class ConflictTheater: raise NotImplementedError def point_to_ll(self, point: Point) -> LatLon: - lat, lon = Transformer.from_crs( - self.projection_parameters.to_crs(), CRS("WGS84") - ).transform(point.x, point.y) + lat, lon = self.point_to_ll_transformer.transform(point.x, point.y) return LatLon(lat, lon) def ll_to_point(self, ll: LatLon) -> Point: - x, y = Transformer.from_crs( - CRS("WGS84"), self.projection_parameters.to_crs() - ).transform(ll.latitude, ll.longitude) + x, y = self.ll_to_point_transformer.transform(ll.latitude, ll.longitude) return Point(x, y) diff --git a/qt_ui/main.py b/qt_ui/main.py index 08755e15..8f5af44c 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -36,7 +36,7 @@ from qt_ui.windows.preferences.QLiberationFirstStartWindow import ( ) -def run_ui(game: Optional[Game] = None) -> None: +def run_ui(game: Optional[Game], new_map: bool) -> None: os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" # Potential fix for 4K screens app = QApplication(sys.argv) @@ -104,7 +104,7 @@ def run_ui(game: Optional[Game] = None) -> None: GameUpdateSignal() # Start window - window = QLiberationWindow(game) + window = QLiberationWindow(game, new_map) window.showMaximized() splash.finish(window) qt_execution_code = app.exec_() @@ -132,6 +132,10 @@ def parse_args() -> argparse.Namespace: help="Emits a warning for weapons without date or fallback information.", ) + parser.add_argument( + "--new-map", action="store_true", help="Use the new map. Non functional." + ) + new_game = subparsers.add_parser("new-game") new_game.add_argument( @@ -239,7 +243,7 @@ def main(): args.cheats, ) - run_ui(game) + run_ui(game, args.new_map) if __name__ == "__main__": diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index afd94e78..b7f2020c 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -4,7 +4,15 @@ import datetime import logging import math from functools import singledispatchmethod -from typing import Iterable, Iterator, List, Optional, Sequence, Tuple +from pathlib import Path +from typing import ( + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, +) from PySide2 import QtCore, QtWidgets from PySide2.QtCore import QLineF, QPointF, QRectF, Qt @@ -17,6 +25,11 @@ from PySide2.QtGui import ( QPolygonF, QWheelEvent, ) +from PySide2.QtWebChannel import QWebChannel +from PySide2.QtWebEngineWidgets import ( + QWebEnginePage, + QWebEngineView, +) from PySide2.QtWidgets import ( QFrame, QGraphicsItem, @@ -40,7 +53,10 @@ import qt_ui.uiconstants as CONST from game import Game from game.navmesh import NavMesh from game.theater import ControlPoint, Enum -from game.theater.conflicttheater import FrontLine, ReferencePoint +from game.theater.conflicttheater import ( + FrontLine, + ReferencePoint, +) from game.theater.theatergroundobject import ( TheaterGroundObject, ) @@ -69,6 +85,7 @@ from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject from qt_ui.widgets.map.ShippingLaneSegment import ShippingLaneSegment from qt_ui.widgets.map.SupplyRouteSegment import SupplyRouteSegment +from qt_ui.widgets.map.mapmodel import MapModel from qt_ui.windows.GameUpdateSignal import GameUpdateSignal MAX_SHIP_DISTANCE = nautical_miles(80) @@ -112,17 +129,65 @@ class QLiberationMapState(Enum): MOVING_UNIT = 1 -class QLiberationMap(QGraphicsView): +class LoggingWebPage(QWebEnginePage): + def javaScriptConsoleMessage( + self, + level: QWebEnginePage.JavaScriptConsoleMessageLevel, + message: str, + line_number: int, + source: str, + ) -> None: + if level == QWebEnginePage.JavaScriptConsoleMessageLevel.ErrorMessageLevel: + logging.error(message) + elif level == QWebEnginePage.JavaScriptConsoleMessageLevel.WarningMessageLevel: + logging.warning(message) + else: + logging.info(message) + + +class LiberationMap: + def set_game(self, game: Optional[Game]) -> None: + raise NotImplementedError + + +class LeafletMap(QWebEngineView, LiberationMap): + def __init__(self, game_model: GameModel, parent) -> None: + super().__init__(parent) + self.game_model = game_model + self.setMinimumSize(800, 600) + self.map_model = MapModel(game_model) + + self.channel = QWebChannel() + self.channel.registerObject("game", self.map_model) + + self.page = LoggingWebPage(self) + self.page.setWebChannel(self.channel) + self.page.setHtml(Path("resources/ui/map/canvas.html").read_text()) + self.setPage(self.page) + + self.loadFinished.connect(self.load_finished) + + def load_finished(self) -> None: + self.page.runJavaScript(Path("resources/ui/map/map.js").read_text()) + + def set_game(self, game: Optional[Game]) -> None: + if game is None: + self.map_model.clear() + else: + self.map_model.reset() + + +class QLiberationMap(QGraphicsView, LiberationMap): WAYPOINT_SIZE = 4 reference_point_setup_mode = False instance: Optional[QLiberationMap] = None - def __init__(self, game_model: GameModel): - super(QLiberationMap, self).__init__() + def __init__(self, game_model: GameModel) -> None: + super().__init__() QLiberationMap.instance = self self.game_model = game_model - self.game: Optional[Game] = None # Setup by setGame below. + self.game: Optional[Game] = None # Setup by set_game below. self.state = QLiberationMapState.NORMAL self.waypoint_info_font = QFont() @@ -138,7 +203,7 @@ class QLiberationMap(QGraphicsView): self.factor = 1 self.factorized = 1 self.init_scene() - self.setGame(game_model.game) + self.set_game(game_model.game) # Object displayed when unit is selected self.movement_line = QtWidgets.QGraphicsLineItem( @@ -201,7 +266,7 @@ class QLiberationMap(QGraphicsView): self.setFrameShape(QFrame.NoFrame) self.setDragMode(QGraphicsView.ScrollHandDrag) - def setGame(self, game: Optional[Game]): + def set_game(self, game: Optional[Game]): should_recenter = self.game is None self.game = game if self.game is not None: diff --git a/qt_ui/widgets/map/mapmodel.py b/qt_ui/widgets/map/mapmodel.py new file mode 100644 index 00000000..726b9105 --- /dev/null +++ b/qt_ui/widgets/map/mapmodel.py @@ -0,0 +1,323 @@ +import logging +from typing import List, Optional, Tuple + +from PySide2.QtCore import Property, QObject, Signal, Slot +from dcs import Point + +from game import Game +from game.profiling import logged_duration +from game.theater import ( + ConflictTheater, + ControlPoint, + TheaterGroundObject, +) +from gen.ato import AirTaskingOrder +from gen.flights.flight import Flight, FlightWaypoint +from qt_ui.models import GameModel +from qt_ui.windows.GameUpdateSignal import GameUpdateSignal +from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 + +LeafletLatLon = List[float] + + +class ControlPointJs(QObject): + def __init__( + self, + control_point: ControlPoint, + game_model: GameModel, + theater: ConflictTheater, + ) -> None: + super().__init__() + self.control_point = control_point + self.game_model = game_model + self.theater = theater + + @Property(str) + def name(self) -> str: + return self.control_point.name + + @Property(bool) + def blue(self) -> bool: + return self.control_point.captured + + @Property(list) + def position(self) -> LeafletLatLon: + ll = self.theater.point_to_ll(self.control_point.position) + return [ll.latitude, ll.longitude] + + @Slot() + def open_base_menu(self) -> None: + self.base_details_dialog = QBaseMenu2(None, self.control_point, self.game_model) + self.base_details_dialog.show() + + +class GroundObjectJs(QObject): + def __init__(self, tgo: TheaterGroundObject, theater: ConflictTheater) -> None: + super().__init__() + self.tgo = tgo + self.theater = theater + + @Property(bool) + def blue(self) -> bool: + return self.tgo.control_point.captured + + @Property(list) + def position(self) -> LeafletLatLon: + ll = self.theater.point_to_ll(self.tgo.position) + return [ll.latitude, ll.longitude] + + @Property(list) + def samThreatRanges(self) -> List[float]: + if not self.tgo.might_have_aa: + return [] + + ranges = [] + for group in self.tgo.groups: + threat_range = self.tgo.threat_range(group) + if threat_range: + ranges.append(threat_range.meters) + return ranges + + @Property(list) + def samDetectionRanges(self) -> List[float]: + if not self.tgo.might_have_aa: + return [] + + ranges = [] + for group in self.tgo.groups: + detection_range = self.tgo.detection_range(group) + if detection_range: + ranges.append(detection_range.meters) + return ranges + + +class SupplyRouteJs(QObject): + def __init__(self, points: List[LeafletLatLon]) -> None: + super().__init__() + self._points = points + + @Property(list) + def points(self) -> List[LeafletLatLon]: + return self._points + + +class WaypointJs(QObject): + def __init__(self, waypoint: FlightWaypoint, theater: ConflictTheater) -> None: + super().__init__() + self.waypoint = waypoint + self.theater = theater + + @Property(list) + def position(self) -> LeafletLatLon: + ll = self.theater.point_to_ll(self.waypoint.position) + return [ll.latitude, ll.longitude] + + +class FlightJs(QObject): + flightPlanChanged = Signal() + + def __init__( + self, flight: Flight, selected: bool, theater: ConflictTheater + ) -> None: + super().__init__() + self.flight = flight + self._selected = selected + self.theater = theater + self._waypoints = [] + self.reset_waypoints() + + def reset_waypoints(self) -> None: + self._waypoints = [WaypointJs(p, self.theater) for p in self.flight.points] + self.flightPlanChanged.emit() + + @Property(list, notify=flightPlanChanged) + def flightPlan(self) -> List[WaypointJs]: + return self._waypoints + + @Property(bool) + def blue(self) -> bool: + return self.flight.departure.captured + + @Property(bool) + def selected(self) -> bool: + return self._selected + + +class MapModel(QObject): + cleared = Signal() + + mapCenterChanged = Signal(list) + controlPointsChanged = Signal() + groundObjectsChanged = Signal() + supplyRoutesChanged = Signal() + flightsChanged = Signal() + + def __init__(self, game_model: GameModel) -> None: + super().__init__() + self.game_model = game_model + self._map_center = [0, 0] + self._control_points = [] + self._ground_objects = [] + self._supply_routes = [] + self._flights = [] + self._selected_flight_index: Optional[Tuple[int, int]] = None + GameUpdateSignal.get_instance().game_loaded.connect(self.on_game_load) + GameUpdateSignal.get_instance().flight_paths_changed.connect(self.reset_atos) + GameUpdateSignal.get_instance().package_selection_changed.connect( + self.set_package_selection + ) + GameUpdateSignal.get_instance().flight_selection_changed.connect( + self.set_flight_selection + ) + self.reset() + + def set_package_selection(self, index: int) -> None: + # Optional[int] isn't a valid type for a Qt signal. None will be converted to + # zero automatically. We use -1 to indicate no selection. + if index == -1: + self._selected_flight_index = None + else: + self._selected_flight_index = index, 0 + self.reset_atos() + + def set_flight_selection(self, index: int) -> None: + if self._selected_flight_index is None: + if index != -1: + # We don't know what order update_package_selection and + # update_flight_selection will be called in when the last + # package is removed. If no flight is selected, it's not a + # problem to also have no package selected. + logging.error("Flight was selected with no package selected") + return + + # Optional[int] isn't a valid type for a Qt signal. None will be converted to + # zero automatically. We use -1 to indicate no selection. + if index == -1: + self._selected_flight_index = self._selected_flight_index[0], None + self._selected_flight_index = self._selected_flight_index[0], index + self.reset_atos() + + @staticmethod + def leaflet_coord_for(point: Point, theater: ConflictTheater) -> LeafletLatLon: + ll = theater.point_to_ll(point) + return [ll.latitude, ll.longitude] + + def reset(self) -> None: + if self.game_model.game is None: + self.clear() + return + with logged_duration("Map reset"): + self.reset_control_points() + self.reset_ground_objects() + self.reset_routes() + self.reset_atos() + + def on_game_load(self, game: Optional[Game]) -> None: + if game is not None: + self.reset_map_center(game.theater) + + def reset_map_center(self, theater: ConflictTheater) -> None: + ll = theater.point_to_ll(theater.terrain.map_view_default.position) + self._map_center = [ll.latitude, ll.longitude] + self.mapCenterChanged.emit(self._map_center) + + @Property(list, notify=mapCenterChanged) + def mapCenter(self) -> LeafletLatLon: + return self._map_center + + def _flights_in_ato(self, ato: AirTaskingOrder, blue: bool) -> List[FlightJs]: + flights = [] + for p_idx, package in enumerate(ato.packages): + for f_idx, flight in enumerate(package.flights): + flights.append( + FlightJs( + flight, + selected=blue and (p_idx, f_idx) == self._selected_flight_index, + theater=self.game.theater, + ) + ) + return flights + + def reset_atos(self) -> None: + self._flights = self._flights_in_ato( + self.game.blue_ato, blue=True + ) + self._flights_in_ato(self.game.red_ato, blue=False) + self.flightsChanged.emit() + + @Property(list, notify=flightsChanged) + def flights(self) -> List[FlightJs]: + return self._flights + + def reset_control_points(self) -> None: + self._control_points = [ + ControlPointJs(c, self.game_model, self.game.theater) + for c in self.game.theater.controlpoints + ] + self.controlPointsChanged.emit() + + @Property(list, notify=controlPointsChanged) + def controlPoints(self) -> List[ControlPointJs]: + return self._control_points + + def reset_ground_objects(self) -> None: + seen = set() + self._ground_objects = [] + for cp in self.game.theater.controlpoints: + for tgo in cp.ground_objects: + if tgo.name in seen: + continue + seen.add(tgo.name) + + self._ground_objects.append(GroundObjectJs(tgo, self.game.theater)) + self.groundObjectsChanged.emit() + + @Property(list, notify=groundObjectsChanged) + def groundObjects(self) -> List[GroundObjectJs]: + return self._ground_objects + + def reset_routes(self) -> None: + seen = set() + self._supply_routes = [] + for control_point in self.game.theater.controlpoints: + seen.add(control_point) + for destination, convoy_route in control_point.convoy_routes.items(): + if destination in seen: + continue + self._supply_routes.append( + SupplyRouteJs( + [ + self.leaflet_coord_for(p, self.game.theater) + for p in convoy_route + ] + ) + ) + for destination, shipping_lane in control_point.shipping_lanes.items(): + if destination in seen: + continue + if control_point.is_friendly(destination.captured): + self._supply_routes.append( + SupplyRouteJs( + [ + self.leaflet_coord_for(p, self.game.theater) + for p in shipping_lane + ] + ) + ) + self.supplyRoutesChanged.emit() + + @Property(list, notify=supplyRoutesChanged) + def supplyRoutes(self) -> List[SupplyRouteJs]: + return self._supply_routes + + def clear(self) -> None: + self._control_points = [] + self._supply_routes = [] + self._ground_objects = [] + self._flights = [] + self.cleared.emit() + + @property + def game(self) -> Game: + if self.game_model.game is None: + raise RuntimeError("No game loaded") + return self.game_model.game diff --git a/qt_ui/windows/GameUpdateSignal.py b/qt_ui/windows/GameUpdateSignal.py index f70c0e49..6f2119e6 100644 --- a/qt_ui/windows/GameUpdateSignal.py +++ b/qt_ui/windows/GameUpdateSignal.py @@ -15,6 +15,8 @@ class GameUpdateSignal(QObject): budgetupdated = Signal(Game) debriefingReceived = Signal(Debriefing) + game_loaded = Signal(Game) + flight_paths_changed = Signal() package_selection_changed = Signal(int) # -1 indicates no selection. flight_selection_changed = Signal(int) # -1 indicates no selection. @@ -23,6 +25,8 @@ class GameUpdateSignal(QObject): super(GameUpdateSignal, self).__init__() GameUpdateSignal.instance = self + self.game_loaded.connect(self.updateGame) + def select_package(self, index: Optional[int]) -> None: # noinspection PyUnresolvedReferences self.package_selection_changed.emit(-1 if index is None else index) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 76d683ab..bfad8870 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -27,7 +27,7 @@ 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 QLiberationMap +from qt_ui.widgets.map.QLiberationMap import LeafletMap, QLiberationMap, LiberationMap from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.QDebriefingWindow import QDebriefingWindow from qt_ui.windows.infos.QInfoPanel import QInfoPanel @@ -38,7 +38,7 @@ from qt_ui.windows.preferences.QLiberationPreferencesWindow import ( class QLiberationWindow(QMainWindow): - def __init__(self, game: Optional[Game]) -> None: + def __init__(self, game: Optional[Game], new_map: bool) -> None: super(QLiberationWindow, self).__init__() self.game = game @@ -46,7 +46,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 = QLiberationMap(self.game_model) + self.liberation_map: LiberationMap = self.create_map(new_map) self.setGeometry(300, 100, 270, 100) self.setWindowTitle(f"DCS Liberation - v{VERSION}") @@ -254,14 +254,13 @@ class QLiberationWindow(QMainWindow): ) if file is not None: game = persistency.load_game(file[0]) - GameUpdateSignal.get_instance().updateGame(game) + GameUpdateSignal.get_instance().game_loaded.emit(game) def saveGame(self): logging.info("Saving game") if self.game.savepath: persistency.save_game(self.game) - GameUpdateSignal.get_instance().updateGame(self.game) liberation_install.setup_last_save_file(self.game.savepath) liberation_install.save_config() else: @@ -283,7 +282,12 @@ class QLiberationWindow(QMainWindow): def onGameGenerated(self, game: Game): logging.info("On Game generated") self.game = game - GameUpdateSignal.get_instance().updateGame(self.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: @@ -291,8 +295,7 @@ class QLiberationWindow(QMainWindow): if self.info_panel is not None: self.info_panel.setGame(game) self.game_model.set(self.game) - if self.liberation_map is not None: - self.liberation_map.setGame(game) + self.liberation_map.set_game(game) except AttributeError: logging.exception("Incompatible save game") QMessageBox.critical( diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointInfoBox.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointInfoBox.py index 29834c01..9bcadd6d 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointInfoBox.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointInfoBox.py @@ -1,22 +1,19 @@ -from PySide2.QtWidgets import QGroupBox, QGridLayout, QLabel, QHBoxLayout, QVBoxLayout +from PySide2.QtWidgets import QGroupBox, QHBoxLayout, QLabel, QVBoxLayout from gen.flights.flight import FlightWaypoint class QFlightWaypointInfoBox(QGroupBox): - def __init__(self, flight_wpt: FlightWaypoint = None): + def __init__(self) -> None: super(QFlightWaypointInfoBox, self).__init__("Waypoint") - self.flight_wpt = flight_wpt - if flight_wpt is None: - self.flight_wpt = FlightWaypoint(0, 0, 0) - self.x_position_label = QLabel(str(self.flight_wpt.x)) - self.y_position_label = QLabel(str(self.flight_wpt.y)) - self.alt_label = QLabel(str(int(self.flight_wpt.alt.feet))) - self.name_label = QLabel(str(self.flight_wpt.name)) - self.desc_label = QLabel(str(self.flight_wpt.description)) + self.x_position_label = QLabel("0") + self.y_position_label = QLabel("0") + self.alt_label = QLabel("0") + self.name_label = QLabel("") + self.desc_label = QLabel("") self.init_ui() - def init_ui(self): + def init_ui(self) -> None: layout = QVBoxLayout() @@ -53,13 +50,10 @@ class QFlightWaypointInfoBox(QGroupBox): self.setLayout(layout) - def set_flight_waypoint(self, flight_wpt: FlightWaypoint): - self.flight_wpt = flight_wpt - if flight_wpt is None: - self.flight_wpt = FlightWaypoint(0, 0, 0) - self.x_position_label.setText(str(self.flight_wpt.x)) - self.y_position_label.setText(str(self.flight_wpt.y)) - self.alt_label.setText(str(int(self.flight_wpt.alt.feet))) - self.name_label.setText(str(self.flight_wpt.name)) - self.desc_label.setText(str(self.flight_wpt.description)) - self.setTitle(self.flight_wpt.name) + def set_flight_waypoint(self, flight_wpt: FlightWaypoint) -> None: + self.x_position_label.setText(str(flight_wpt.x)) + self.y_position_label.setText(str(flight_wpt.y)) + self.alt_label.setText(str(int(flight_wpt.alt.feet))) + self.name_label.setText(str(flight_wpt.name)) + self.desc_label.setText(str(flight_wpt.description)) + self.setTitle(flight_wpt.name) diff --git a/resources/ui/map/canvas.html b/resources/ui/map/canvas.html new file mode 100644 index 00000000..3c035992 --- /dev/null +++ b/resources/ui/map/canvas.html @@ -0,0 +1,35 @@ + + + + DCS Liberation Map + + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/resources/ui/map/map.js b/resources/ui/map/map.js new file mode 100644 index 00000000..6cad0155 --- /dev/null +++ b/resources/ui/map/map.js @@ -0,0 +1,220 @@ +/* + * TODO: + * + * - Culling + * - Threat zones + * - Navmeshes + * - CV waypoints + * - Time of day/weather themeing + * - Exclusion zones + * - Commit ranges + * - Waypoint info + * - Supply route status + * - Front line + * - Debug flight plan drawing + * - Icon variety + */ + +const Colors = Object.freeze({ + Blue: "#0084ff", + Red: "#c85050", +}); + +var map = L.map("map").setView([0, 0], 3); + +// https://esri.github.io/esri-leaflet/api-reference/layers/basemap-layer.html +var baseLayers = { + "Imagery Clarity": L.esri.basemapLayer("ImageryClarity", { maxZoom: 17 }), + "Imagery Firefly": L.esri.basemapLayer("ImageryFirefly", { maxZoom: 17 }), +}; + +var defaultBaseMap = baseLayers["Imagery Clarity"]; +defaultBaseMap.addTo(map); + +// Enabled by default, so addTo(map). +var controlPointsLayer = L.layerGroup().addTo(map); +var groundObjectsLayer = L.layerGroup().addTo(map); +var supplyRoutesLayer = L.layerGroup().addTo(map); +var redSamThreatLayer = L.layerGroup().addTo(map); +var blueFlightPlansLayer = L.layerGroup().addTo(map); + +// Added to map by the user via layer controls. +var blueSamThreatLayer = L.layerGroup(); +var blueSamDetectionLayer = L.layerGroup(); +var redSamDetectionLayer = L.layerGroup(); +var redFlightPlansLayer = L.layerGroup(); +var selectedFlightPlansLayer = L.layerGroup(); + +L.control + .groupedLayers( + baseLayers, + { + "Points of Interest": { + "Control points": controlPointsLayer, + "Ground objects": groundObjectsLayer, + "Supply routes": supplyRoutesLayer, + }, + "Air Defenses": { + "Ally SAM threat range": blueSamThreatLayer, + "Enemy SAM threat range": redSamThreatLayer, + "Ally SAM detection range": blueSamDetectionLayer, + "Enemy SAM detection range": redSamDetectionLayer, + }, + "Flight Plans": { + "Hide": L.layerGroup(), + "Show selected blue": selectedFlightPlansLayer, + "Show all blue": blueFlightPlansLayer, + "Show all red": redFlightPlansLayer, + }, + }, + { collapsed: false, exclusiveGroups: ["Flight Plans"] } + ) + .addTo(map); + +var friendlyCpIcon = new L.Icon({ + iconUrl: + "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png", + shadowUrl: + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png", + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + shadowSize: [41, 41], +}); + +var enemyCpIcon = new L.Icon({ + iconUrl: + "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png", + shadowUrl: + "https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png", + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + shadowSize: [41, 41], +}); + +var game; +new QWebChannel(qt.webChannelTransport, function (channel) { + game = channel.objects.game; + drawInitialMap(); + game.cleared.connect(clearAllLayers); + game.mapCenterChanged.connect(recenterMap); + game.controlPointsChanged.connect(drawControlPoints); + game.groundObjectsChanged.connect(drawGroundObjects); + game.supplyRoutesChanged.connect(drawSupplyRoutes); + game.flightsChanged.connect(drawFlightPlans); +}); + +function recenterMap(center) { + map.setView(center, 8, { animate: true, duration: 1 }); +} + +function iconFor(player) { + if (player) { + return friendlyCpIcon; + } else { + return enemyCpIcon; + } +} + +function drawControlPoints() { + controlPointsLayer.clearLayers(); + game.controlPoints.forEach((cp) => { + L.marker(cp.position, { icon: iconFor(cp.blue) }) + .on("click", function () { + cp.open_base_menu(); + }) + .addTo(controlPointsLayer); + }); +} + +function drawSamThreatsAt(tgo) { + var detectionLayer = tgo.blue ? blueSamDetectionLayer : redSamDetectionLayer; + var threatLayer = tgo.blue ? blueSamThreatLayer : redSamThreatLayer; + var threatColor = tgo.blue ? Colors.Blue : Colors.Red; + var detectionColor = tgo.blue ? "#bb89ff" : "#eee17b"; + + tgo.samDetectionRanges.forEach((range) => { + L.circle(tgo.position, { + radius: range, + color: detectionColor, + fill: false, + weight: 2, + }).addTo(detectionLayer); + }); + + tgo.samThreatRanges.forEach((range) => { + L.circle(tgo.position, { + radius: range, + color: threatColor, + fill: false, + weight: 2, + }).addTo(threatLayer); + }); +} + +function drawGroundObjects() { + groundObjectsLayer.clearLayers(); + blueSamDetectionLayer.clearLayers(); + redSamDetectionLayer.clearLayers(); + blueSamThreatLayer.clearLayers(); + redSamThreatLayer.clearLayers(); + game.groundObjects.forEach((tgo) => { + L.marker(tgo.position, { icon: iconFor(tgo.blue) }).addTo( + groundObjectsLayer + ); + drawSamThreatsAt(tgo); + }); +} + +function drawSupplyRoutes() { + supplyRoutesLayer.clearLayers(); + game.supplyRoutes.forEach((route) => { + L.polyline(route.points).addTo(supplyRoutesLayer); + }); +} + +function drawFlightPlan(flight) { + var layer = flight.blue ? blueFlightPlansLayer : redFlightPlansLayer; + var color = flight.blue ? Colors.Blue : Colors.Red; + var highlight = "#ffff00"; + var points = []; + flight.flightPlan.forEach((waypoint) => { + points.push(waypoint.position); + L.circle(waypoint.position, { radius: 50, color: color }).addTo(layer); + if (flight.selected) { + L.circle(waypoint.position, { radius: 50, color: highlight }).addTo( + selectedFlightPlansLayer + ); + } + }); + L.polyline(points, { color: color }).addTo(layer); + if (flight.selected) { + L.polyline(points, { color: highlight }).addTo(selectedFlightPlansLayer); + } +} + +function drawFlightPlans() { + blueFlightPlansLayer.clearLayers(); + redFlightPlansLayer.clearLayers(); + selectedFlightPlansLayer.clearLayers(); + game.flights.forEach((flight) => { + drawFlightPlan(flight); + }); +} + +function drawInitialMap() { + recenterMap(game.mapCenter); + drawControlPoints(); + drawGroundObjects(); + drawSupplyRoutes(); + drawFlightPlans(); +} + +function clearAllLayers() { + map.eachLayer(function (layer) { + if (layer.clearLayers !== undefined) { + layer.clearLayers(); + } + }); +} From 4e498e6932c31d25196c2c307ba78ac31c7b4af0 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 12 May 2021 23:03:53 -0700 Subject: [PATCH 130/438] Add waypoint info tooltip to the new map. --- qt_ui/widgets/map/mapmodel.py | 58 +++++++++++++++++++++++++++++++++-- resources/ui/map/map.js | 47 +++++++++++++++++++++------- 2 files changed, 91 insertions(+), 14 deletions(-) diff --git a/qt_ui/widgets/map/mapmodel.py b/qt_ui/widgets/map/mapmodel.py index 726b9105..18e76b26 100644 --- a/qt_ui/widgets/map/mapmodel.py +++ b/qt_ui/widgets/map/mapmodel.py @@ -1,4 +1,5 @@ import logging +from datetime import timedelta from typing import List, Optional, Tuple from PySide2.QtCore import Property, QObject, Signal, Slot @@ -11,8 +12,10 @@ from game.theater import ( ControlPoint, TheaterGroundObject, ) +from game.utils import meters from gen.ato import AirTaskingOrder -from gen.flights.flight import Flight, FlightWaypoint +from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType +from gen.flights.flightplan import FlightPlan from qt_ui.models import GameModel from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 @@ -102,16 +105,55 @@ class SupplyRouteJs(QObject): class WaypointJs(QObject): - def __init__(self, waypoint: FlightWaypoint, theater: ConflictTheater) -> None: + def __init__( + self, + waypoint: FlightWaypoint, + number: int, + flight_plan: FlightPlan, + theater: ConflictTheater, + ) -> None: super().__init__() self.waypoint = waypoint + self._number = number + self.flight_plan = flight_plan self.theater = theater + @Property(int) + def number(self) -> int: + return self._number + @Property(list) def position(self) -> LeafletLatLon: ll = self.theater.point_to_ll(self.waypoint.position) return [ll.latitude, ll.longitude] + @Property(int) + def altitudeFt(self) -> int: + return int(self.waypoint.alt.feet) + + @Property(str) + def altitudeReference(self) -> str: + return "AGL" if self.waypoint.alt_type == "RADIO" else "MSL" + + @Property(str) + def name(self) -> str: + return self.waypoint.name + + @Property(str) + def timing(self) -> str: + prefix = "TOT" + time = self.flight_plan.tot_for_waypoint(self.waypoint) + if time is None: + prefix = "Depart" + time = self.flight_plan.depart_time_for_waypoint(self.waypoint) + if time is None: + return "" + return f"{prefix} T+{timedelta(seconds=int(time.total_seconds()))}" + + @Property(bool) + def isDivert(self) -> bool: + return self.waypoint.waypoint_type is FlightWaypointType.DIVERT + class FlightJs(QObject): flightPlanChanged = Signal() @@ -127,7 +169,17 @@ class FlightJs(QObject): self.reset_waypoints() def reset_waypoints(self) -> None: - self._waypoints = [WaypointJs(p, self.theater) for p in self.flight.points] + departure = FlightWaypoint( + FlightWaypointType.TAKEOFF, + self.flight.departure.position.x, + self.flight.departure.position.y, + meters(0), + ) + departure.alt_type = "RADIO" + self._waypoints = [ + WaypointJs(p, i, self.flight.flight_plan, self.theater) + for i, p in enumerate([departure] + self.flight.points) + ] self.flightPlanChanged.emit() @Property(list, notify=flightPlanChanged) diff --git a/resources/ui/map/map.js b/resources/ui/map/map.js index 6cad0155..381c79f8 100644 --- a/resources/ui/map/map.js +++ b/resources/ui/map/map.js @@ -8,7 +8,6 @@ * - Time of day/weather themeing * - Exclusion zones * - Commit ranges - * - Waypoint info * - Supply route status * - Front line * - Debug flight plan drawing @@ -61,7 +60,7 @@ L.control "Enemy SAM detection range": redSamDetectionLayer, }, "Flight Plans": { - "Hide": L.layerGroup(), + Hide: L.layerGroup(), "Show selected blue": selectedFlightPlansLayer, "Show all blue": blueFlightPlansLayer, "Show all red": redFlightPlansLayer, @@ -178,19 +177,32 @@ function drawFlightPlan(flight) { var layer = flight.blue ? blueFlightPlansLayer : redFlightPlansLayer; var color = flight.blue ? Colors.Blue : Colors.Red; var highlight = "#ffff00"; - var points = []; - flight.flightPlan.forEach((waypoint) => { - points.push(waypoint.position); - L.circle(waypoint.position, { radius: 50, color: color }).addTo(layer); + // We don't need a marker for the departure waypoint (and it's likely + // coincident with the landing waypoint, so hard to see). We do want to draw + // the path from it though. + var points = [flight.flightPlan[0].position]; + flight.flightPlan.slice(1).forEach((waypoint) => { + if (!waypoint.isDivert) { + points.push(waypoint.position); + } + if (flight.selected) { - L.circle(waypoint.position, { radius: 50, color: highlight }).addTo( - selectedFlightPlansLayer - ); + L.marker(waypoint.position) + .bindTooltip( + `${waypoint.number} ${waypoint.name}
` + + `${waypoint.altitudeFt} ft ${waypoint.altitudeReference}
` + + `${waypoint.timing}`, + { permanent: true } + ) + .addTo(layer) + .addTo(selectedFlightPlansLayer); } }); - L.polyline(points, { color: color }).addTo(layer); if (flight.selected) { L.polyline(points, { color: highlight }).addTo(selectedFlightPlansLayer); + L.polyline(points, { color: highlight }).addTo(layer); + } else { + L.polyline(points, { color: color }).addTo(layer); } } @@ -198,9 +210,22 @@ function drawFlightPlans() { blueFlightPlansLayer.clearLayers(); redFlightPlansLayer.clearLayers(); selectedFlightPlansLayer.clearLayers(); + var selected = null; game.flights.forEach((flight) => { - drawFlightPlan(flight); + // Draw the selected waypoint last so it's on top. bringToFront only brings + // it to the front of the *extant* elements, so any flights drawn later will + // be drawn on top. We could fight with manual Z-indexes but leaflet does a + // lot of that automatically so it'd be error prone. + if (flight.selected) { + selected = flight; + } else { + drawFlightPlan(flight); + } }); + + if (selected != null) { + drawFlightPlan(selected); + } } function drawInitialMap() { From 45f0c3c85fe73c53018ba196d24473fc39993e3e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 12 May 2021 23:30:54 -0700 Subject: [PATCH 131/438] Add map scale widget. --- resources/ui/map/canvas.html | 2 +- resources/ui/map/map.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/ui/map/canvas.html b/resources/ui/map/canvas.html index 3c035992..b6382e92 100644 --- a/resources/ui/map/canvas.html +++ b/resources/ui/map/canvas.html @@ -18,7 +18,7 @@ + crossorigin=""> + + + +

WY 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 G;Qs*}Ud2%W From 2d64acf2996c977befb52bc84a17378c41b11d47 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 14:31:13 -0700 Subject: [PATCH 079/438] Add mission targeting for cargo ships. https://github.com/Khopa/dcs_liberation/issues/826 --- gen/aircraft.py | 4 ++-- gen/flights/flightplan.py | 24 +++++++++++-------- gen/flights/waypointbuilder.py | 4 ++-- .../windows/basemenu/DepartingConvoysMenu.py | 11 +++++---- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/gen/aircraft.py b/gen/aircraft.py index f25c82fa..6a04bcc0 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -88,7 +88,7 @@ from game.theater.controlpoint import ( OffMapSpawn, ) from game.theater.theatergroundobject import TheaterGroundObject -from game.transfers import Convoy +from game.transfers import MultiGroupTransport from game.unitmap import UnitMap from game.utils import Distance, meters, nautical_miles from gen.ato import AirTaskingOrder, Package @@ -1719,7 +1719,7 @@ class BaiIngressBuilder(PydcsWaypointBuilder): target_group = self.package.target if isinstance(target_group, TheaterGroundObject): group_name = target_group.group_name - elif isinstance(target_group, Convoy): + elif isinstance(target_group, MultiGroupTransport): group_name = target_group.name else: logging.error( diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 8e405552..f00d351a 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -1083,6 +1083,10 @@ class FlightPlanBuilder: flight, location, FlightWaypointType.INGRESS_BAI, targets ) + @staticmethod + def anti_ship_targets_for_tgo(tgo: TheaterGroundObject) -> List[StrikeTarget]: + return [StrikeTarget(f"{g.name} at {tgo.name}", g) for g in tgo.groups] + def generate_anti_ship(self, flight: Flight) -> StrikeFlightPlan: """Generates an anti-ship flight plan. @@ -1091,20 +1095,20 @@ class FlightPlanBuilder: """ location = self.package.target + from game.transfers import CargoShip + if isinstance(location, ControlPoint): - if location.is_fleet: - # The first group generated will be the carrier group itself. - location = location.ground_objects[0] - else: + if not location.is_fleet: raise InvalidObjectiveLocation(flight.flight_type, location) - - if not isinstance(location, TheaterGroundObject): + # The first group generated will be the carrier group itself. + targets = self.anti_ship_targets_for_tgo(location.ground_objects[0]) + elif isinstance(location, TheaterGroundObject): + targets = self.anti_ship_targets_for_tgo(location) + elif isinstance(location, CargoShip): + targets = [StrikeTarget(location.name, location)] + else: raise InvalidObjectiveLocation(flight.flight_type, location) - targets: List[StrikeTarget] = [] - for group in location.groups: - targets.append(StrikeTarget(f"{group.name} at {location.name}", group)) - return self.strike_flightplan( flight, location, FlightWaypointType.INGRESS_BAI, targets ) diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index 5487e2c0..d125ac9e 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -18,7 +18,7 @@ from dcs.unitgroup import Group, VehicleGroup if TYPE_CHECKING: from game import Game - from game.transfers import Convoy + from game.transfers import MultiGroupTransport from game.theater import ( ControlPoint, @@ -33,7 +33,7 @@ from .flight import Flight, FlightWaypoint, FlightWaypointType @dataclass(frozen=True) class StrikeTarget: name: str - target: Union[VehicleGroup, TheaterGroundObject, Unit, Group, Convoy] + target: Union[VehicleGroup, TheaterGroundObject, Unit, Group, MultiGroupTransport] class WaypointBuilder: diff --git a/qt_ui/windows/basemenu/DepartingConvoysMenu.py b/qt_ui/windows/basemenu/DepartingConvoysMenu.py index 3e49900a..8c1e67bd 100644 --- a/qt_ui/windows/basemenu/DepartingConvoysMenu.py +++ b/qt_ui/windows/basemenu/DepartingConvoysMenu.py @@ -12,14 +12,14 @@ from PySide2.QtWidgets import ( from game import db from game.theater import ControlPoint -from game.transfers import Convoy +from game.transfers import MultiGroupTransport from qt_ui.dialogs import Dialog from qt_ui.models import GameModel from qt_ui.uiconstants import VEHICLES_ICONS class DepartingConvoyInfo(QGroupBox): - def __init__(self, convoy: Convoy, game_model: GameModel) -> None: + def __init__(self, convoy: MultiGroupTransport, game_model: GameModel) -> None: super().__init__(f"{convoy.name} to {convoy.destination}") self.convoy = convoy @@ -78,11 +78,14 @@ class DepartingConvoysList(QFrame): task_box_layout = QGridLayout() scroll_content.setLayout(task_box_layout) - convoy_map = game_model.game.transfers.convoys - for convoy in convoy_map.departing_from(cp): + for convoy in game_model.game.transfers.convoys.departing_from(cp): group_info = DepartingConvoyInfo(convoy, game_model) task_box_layout.addWidget(group_info) + for cargo_ship in game_model.game.transfers.cargo_ships.departing_from(cp): + group_info = DepartingConvoyInfo(cargo_ship, game_model) + task_box_layout.addWidget(group_info) + scroll_content.setLayout(task_box_layout) scroll = QScrollArea() scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) From 21c35b31d411af1a5e4258efe79932cd07bf8559 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 14:37:49 -0700 Subject: [PATCH 080/438] AI planning for anti shipping missions. Same as for anti convoy, these are rarely planned due to ordering issue between mission planning and procurement. Fixes https://github.com/Khopa/dcs_liberation/issues/826 --- gen/flights/ai_flight_planner.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 16882bc6..096de2ba 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -39,7 +39,7 @@ from game.theater.theatergroundobject import ( NavalGroundObject, VehicleGroupGroundObject, ) -from game.transfers import Convoy, MultiGroupTransport, TransferOrder +from game.transfers import CargoShip, Convoy from game.utils import Distance, nautical_miles from gen import Conflict from gen.ato import Package @@ -445,7 +445,7 @@ class ObjectiveFinder: airfields.append(control_point) return self._targets_by_range(airfields) - def convoys(self) -> Iterator[MultiGroupTransport]: + def convoys(self) -> Iterator[Convoy]: for front_line in self.front_lines(): if front_line.control_point_a.is_friendly(self.is_player): enemy_cp = front_line.control_point_a @@ -454,6 +454,15 @@ class ObjectiveFinder: yield from self.game.transfers.convoys.travelling_to(enemy_cp) + def cargo_ships(self) -> Iterator[CargoShip]: + for front_line in self.front_lines(): + if front_line.control_point_a.is_friendly(self.is_player): + enemy_cp = front_line.control_point_a + else: + enemy_cp = front_line.control_point_b + + yield from self.game.transfers.cargo_ships.travelling_to(enemy_cp) + def friendly_control_points(self) -> Iterator[ControlPoint]: """Iterates over all friendly control points.""" return ( @@ -668,6 +677,21 @@ class CoalitionMissionPlanner: ], ) + for ship in self.objective_finder.cargo_ships(): + yield ProposedMission( + ship, + [ + ProposedFlight(FlightType.ANTISHIP, 2, self.MAX_ANTISHIP_RANGE), + # TODO: Max escort range. + ProposedFlight( + FlightType.ESCORT, 2, self.MAX_BAI_RANGE, EscortType.AirToAir + ), + ProposedFlight( + FlightType.SEAD, 2, self.MAX_BAI_RANGE, EscortType.Sead + ), + ], + ) + for group in self.objective_finder.threatening_ships(): yield ProposedMission( group, From 64d7953e50b71f18befc7f8628a7ede69822c813 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 14:39:32 -0700 Subject: [PATCH 081/438] Fix crash when opening FOB menus. --- qt_ui/windows/basemenu/QBaseMenu2.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index b5b9c885..f02469eb 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -12,6 +12,7 @@ from PySide2.QtWidgets import ( from game import Game, db from game.theater import ControlPoint, ControlPointType, SupplyRoute +from game.theater.supplyroutes import RoadNetwork, ShippingNetwork from gen.flights.flight import FlightType from qt_ui.dialogs import Dialog from qt_ui.models import GameModel @@ -107,7 +108,8 @@ class QBaseMenu2(QDialog): def has_transfer_destinations(self) -> bool: return ( self.cp.runway_is_operational() - or len(SupplyRoute.for_control_point(self.cp)) > 1 + or len(RoadNetwork.for_control_point(self.cp)) > 1 + or len(ShippingNetwork.for_control_point(self.cp)) > 1 ) @property From a48ef69e41edfe960c7f192741239efa3ccc4f72 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 14:43:25 -0700 Subject: [PATCH 082/438] Disband cargo ships after processing. Shipping lanes that received a ship would never lose their ships when transfers completed, so the line on the map was staying solid (and probably targetable). --- game/transfers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/game/transfers.py b/game/transfers.py index a22cabb6..661475cb 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -533,6 +533,7 @@ class PendingTransfers: incomplete.append(transfer) self.pending_transfers = incomplete self.convoys.disband_all() + self.cargo_ships.disband_all() def plan_transports(self) -> None: for transfer in self.pending_transfers: From 0779679b99be9afc85c0b37d605574a050cd7447 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 17:02:18 -0700 Subject: [PATCH 083/438] Connect networks to enable multi-mode transfers. Removing the per-transit type supply routes allows us to find the best route from A to B even if the unit needs to switch transit modes along the way. The "best" route is the one that will generate better gameplay. That is, convoys are preferred to ships (use cases for GMT are rare in DCS), and ships are preferred to airlift (reasons to attack cargo ships are also rare). Avoiding airlift is also a good strategic choice generally since it consumes aircraft that could be performing other missions. The extreme weight against airlift in the pathfinding algorithm could probably be scaled way down so that airlift would be given preference over a very long trip, possibly only for urgent transfers. Later when we add rail that will probably be given the most preference, but possibly between road and shipping. https://github.com/Khopa/dcs_liberation/issues/823 --- game/game.py | 17 +++ game/theater/__init__.py | 1 - game/theater/controlpoint.py | 3 + game/theater/supplyroutes.py | 140 --------------------- game/theater/transitnetwork.py | 178 +++++++++++++++++++++++++++ game/transfers.py | 60 ++++----- game/unitdelivery.py | 57 +++------ qt_ui/windows/basemenu/QBaseMenu2.py | 13 +- 8 files changed, 248 insertions(+), 221 deletions(-) delete mode 100644 game/theater/supplyroutes.py create mode 100644 game/theater/transitnetwork.py diff --git a/game/game.py b/game/game.py index 34ddb5bc..a5aff97e 100644 --- a/game/game.py +++ b/game/game.py @@ -33,6 +33,7 @@ from .navmesh import NavMesh from .procurement import AircraftProcurementRequest, ProcurementAi from .settings import Settings from .theater import ConflictTheater +from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder from .threatzones import ThreatZones from .transfers import PendingTransfers from .unitmap import UnitMap @@ -117,6 +118,9 @@ class Game: self.conditions = self.generate_conditions() + self.blue_transit_network = self.compute_transit_network_for(player=True) + self.red_transit_network = self.compute_transit_network_for(player=False) + self.blue_procurement_requests: List[AircraftProcurementRequest] = [] self.red_procurement_requests: List[AircraftProcurementRequest] = [] @@ -171,6 +175,11 @@ class Game: return self.blue_procurement_requests return self.red_procurement_requests + def transit_network_for(self, player: bool) -> TransitNetwork: + if player: + return self.blue_transit_network + return self.red_transit_network + def generate_conditions(self) -> Conditions: return Conditions.generate( self.theater, self.current_day, self.current_turn_time_of_day, self.settings @@ -346,6 +355,7 @@ class Game: # Plan flights & combat for next turn self.compute_conflicts_position() self.compute_threat_zones() + self.compute_transit_networks() self.ground_planners = {} self.transfers.order_airlift_assets() @@ -417,6 +427,13 @@ class Game: self.current_group_id += 1 return self.current_group_id + def compute_transit_networks(self) -> None: + self.blue_transit_network = self.compute_transit_network_for(player=True) + self.red_transit_network = self.compute_transit_network_for(player=False) + + def compute_transit_network_for(self, player: bool) -> TransitNetwork: + return TransitNetworkBuilder(self.theater, player).build() + def compute_threat_zones(self) -> None: self.blue_threat_zone = ThreatZones.for_faction(self, player=True) self.red_threat_zone = ThreatZones.for_faction(self, player=False) diff --git a/game/theater/__init__.py b/game/theater/__init__.py index f4491283..c5b83a16 100644 --- a/game/theater/__init__.py +++ b/game/theater/__init__.py @@ -2,5 +2,4 @@ from .base import * from .conflicttheater import * from .controlpoint import * from .missiontarget import MissionTarget -from .supplyroutes import SupplyRoute from .theatergroundobject import SamGroundObject diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 886c3846..9a8fb261 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -466,6 +466,9 @@ class ControlPoint(MissionTarget, ABC): def is_friendly(self, to_player: bool) -> bool: return self.captured == to_player + def is_friendly_to(self, control_point: ControlPoint) -> bool: + return control_point.is_friendly(self.captured) + # TODO: Should be Airbase specific. def clear_base_defenses(self) -> None: for base_defense in self.base_defenses: diff --git a/game/theater/supplyroutes.py b/game/theater/supplyroutes.py deleted file mode 100644 index 73254df4..00000000 --- a/game/theater/supplyroutes.py +++ /dev/null @@ -1,140 +0,0 @@ -from __future__ import annotations - -import heapq -import math -from collections import defaultdict -from dataclasses import dataclass, field -from typing import Dict, Iterable, Iterator, List, Optional - -from game.theater.controlpoint import ControlPoint - - -@dataclass(frozen=True, order=True) -class FrontierNode: - cost: float - point: ControlPoint = field(compare=False) - - -class Frontier: - def __init__(self) -> None: - self.nodes: List[FrontierNode] = [] - - def push(self, poly: ControlPoint, cost: float) -> None: - heapq.heappush(self.nodes, FrontierNode(cost, poly)) - - def pop(self) -> Optional[FrontierNode]: - try: - return heapq.heappop(self.nodes) - except IndexError: - return None - - def __bool__(self) -> bool: - return bool(self.nodes) - - -# TODO: Build a single SupplyRoute for each coalition at the start of the turn. -# Supply routes need to cover the whole network to support multi-mode links. -# -# Traverse each friendly control point and build out a network from each. Nodes create -# connections to: -# -# 1. Bases connected by road -# 2. Bases connected by rail -# 3. Bases connected by shipping lane -# 4. Airports large enough to operate cargo planes connect to each other -# 5. Airports capable of operating helicopters connect to other airports within cargo -# helicopter range, and FOBs within half of the range (since they can't be refueled -# at the drop off). -# -# The costs of each link would be set such that the above order roughly corresponds to -# the prevalence of each type of transport. Most units should move by road, rail should -# be used a little less often than road, ships a bit less often than that, cargo planes -# infrequently, and helicopters rarely. Convoys, trains, and ships make the most -# interesting targets for players (and the easiest to generate AI flight plans for). -class SupplyRoute: - def __init__(self, control_points: List[ControlPoint]) -> None: - self.control_points = control_points - - def __contains__(self, item: ControlPoint) -> bool: - return item in self.control_points - - def __iter__(self) -> Iterator[ControlPoint]: - yield from self.control_points - - def __len__(self) -> int: - return len(self.control_points) - - def connections_from(self, control_point: ControlPoint) -> Iterable: - raise NotImplementedError - - def shortest_path_between( - self, origin: ControlPoint, destination: ControlPoint - ) -> List[ControlPoint]: - if origin not in self: - raise ValueError(f"{origin} is not in supply route to {destination}") - if destination not in self: - raise ValueError(f"{destination} is not in supply route from {origin}") - - frontier = Frontier() - frontier.push(origin, 0) - - came_from: Dict[ControlPoint, Optional[ControlPoint]] = {origin: None} - - best_known: Dict[ControlPoint, float] = defaultdict(lambda: math.inf) - best_known[origin] = 0.0 - - while (node := frontier.pop()) is not None: - cost = node.cost - current = node.point - if cost > best_known[current]: - continue - - for neighbor in self.connections_from(current): - if current.captured != neighbor.captured: - continue - - new_cost = cost + 1 - if new_cost < best_known[neighbor]: - best_known[neighbor] = new_cost - frontier.push(neighbor, new_cost) - came_from[neighbor] = current - - # Reconstruct and reverse the path. - current = destination - path: List[ControlPoint] = [] - while current != origin: - path.append(current) - previous = came_from[current] - if previous is None: - raise RuntimeError( - f"Could not reconstruct path to {destination} from {origin}" - ) - current = previous - path.reverse() - return path - - -class RoadNetwork(SupplyRoute): - @classmethod - def for_control_point(cls, control_point: ControlPoint) -> RoadNetwork: - connected_friendly_points = control_point.transitive_connected_friendly_points() - if not connected_friendly_points: - return RoadNetwork([control_point]) - return RoadNetwork([control_point] + connected_friendly_points) - - def connections_from(self, control_point: ControlPoint) -> Iterable: - yield from control_point.connected_points - - -class ShippingNetwork(SupplyRoute): - @classmethod - def for_control_point(cls, control_point: ControlPoint) -> ShippingNetwork: - connected_friendly_points = ( - control_point.transitive_friendly_shipping_destinations() - ) - if not connected_friendly_points: - return ShippingNetwork([control_point]) - return ShippingNetwork([control_point] + connected_friendly_points) - - def connections_from(self, control_point: ControlPoint) -> Iterable: - yield from control_point.shipping_lanes diff --git a/game/theater/transitnetwork.py b/game/theater/transitnetwork.py new file mode 100644 index 00000000..63d0d0e1 --- /dev/null +++ b/game/theater/transitnetwork.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import heapq +import math +from collections import defaultdict +from dataclasses import dataclass, field +from enum import Enum, auto +from typing import Dict, Iterator, List, Optional, Set, Tuple + +from game.theater import ConflictTheater +from game.theater.controlpoint import ControlPoint + + +class NoPathError(RuntimeError): + def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None: + super().__init__(f"Could not reconstruct path to {destination} from {origin}") + + +@dataclass(frozen=True, order=True) +class FrontierNode: + cost: float + point: ControlPoint = field(compare=False) + + +class Frontier: + def __init__(self) -> None: + self.nodes: List[FrontierNode] = [] + + def push(self, poly: ControlPoint, cost: float) -> None: + heapq.heappush(self.nodes, FrontierNode(cost, poly)) + + def pop(self) -> Optional[FrontierNode]: + try: + return heapq.heappop(self.nodes) + except IndexError: + return None + + def __bool__(self) -> bool: + return bool(self.nodes) + + +class TransitConnection(Enum): + Road = auto() + Shipping = auto() + Airlift = auto() + + +class TransitNetwork: + def __init__(self) -> None: + self.nodes: Dict[ + ControlPoint, Dict[ControlPoint, TransitConnection] + ] = defaultdict(dict) + + def has_destinations(self, control_point: ControlPoint) -> bool: + return bool(self.nodes[control_point]) + + def has_link(self, a: ControlPoint, b: ControlPoint) -> bool: + return b in self.nodes[a] + + def link_type(self, a: ControlPoint, b: ControlPoint) -> TransitConnection: + return self.nodes[a][b] + + def link_with( + self, a: ControlPoint, b: ControlPoint, link_type: TransitConnection + ) -> None: + self.nodes[a][b] = link_type + self.nodes[b][a] = link_type + + def link_road(self, a: ControlPoint, b: ControlPoint) -> None: + self.link_with(a, b, TransitConnection.Road) + + def link_shipping(self, a: ControlPoint, b: ControlPoint) -> None: + self.link_with(a, b, TransitConnection.Shipping) + + def link_airport(self, a: ControlPoint, b: ControlPoint) -> None: + self.link_with(a, b, TransitConnection.Airlift) + + def connections_from(self, control_point: ControlPoint) -> Iterator[ControlPoint]: + yield from self.nodes[control_point] + + def cost(self, a: ControlPoint, b: ControlPoint) -> float: + return { + TransitConnection.Road: 1, + TransitConnection.Shipping: 3, + # Set arbitrarily high so that other methods are preferred, but still scaled + # by distance so that when we do need it we still pick the closest airfield. + # The units of distance are meters so there's no risk of these + TransitConnection.Airlift: a.position.distance_to_point(b.position), + }[self.link_type(a, b)] + + def shortest_path_between( + self, origin: ControlPoint, destination: ControlPoint + ) -> List[ControlPoint]: + return self.shortest_path_with_cost(origin, destination)[0] + + def shortest_path_with_cost( + self, origin: ControlPoint, destination: ControlPoint + ) -> Tuple[List[ControlPoint], float]: + if origin not in self.nodes: + raise ValueError(f"{origin} is not in the transit network.") + if destination not in self.nodes: + raise ValueError(f"{destination} is not in the transit network.") + + frontier = Frontier() + frontier.push(origin, 0) + + came_from: Dict[ControlPoint, Optional[ControlPoint]] = {origin: None} + + best_known: Dict[ControlPoint, float] = defaultdict(lambda: math.inf) + best_known[origin] = 0.0 + + while (node := frontier.pop()) is not None: + cost = node.cost + current = node.point + if cost > best_known[current]: + continue + + for neighbor in self.connections_from(current): + new_cost = cost + self.cost(node.point, neighbor) + if new_cost < best_known[neighbor]: + best_known[neighbor] = new_cost + frontier.push(neighbor, new_cost) + came_from[neighbor] = current + + # Reconstruct and reverse the path. + current = destination + path: List[ControlPoint] = [] + while current != origin: + path.append(current) + previous = came_from[current] + if previous is None: + raise NoPathError(origin, destination) + current = previous + path.reverse() + return path, best_known[destination] + + +class TransitNetworkBuilder: + def __init__(self, theater: ConflictTheater, for_player: bool) -> None: + self.control_points = list(theater.control_points_for(for_player)) + self.network = TransitNetwork() + self.airports: Set[ControlPoint] = { + cp + for cp in self.control_points + if cp.is_friendly(for_player) and cp.runway_is_operational() + } + + def build(self) -> TransitNetwork: + seen = set() + for control_point in self.control_points: + if control_point not in seen: + seen.add(control_point) + self.add_transit_links(control_point) + return self.network + + def add_transit_links(self, control_point: ControlPoint) -> None: + # Prefer road connections. + for road_connection in control_point.connected_points: + if road_connection.is_friendly_to(control_point): + self.network.link_road(control_point, road_connection) + + # Use sea connections if there's no road or rail connection. + for sea_connection in control_point.shipping_lanes: + if self.network.has_link(control_point, sea_connection): + continue + if sea_connection.is_friendly_to(control_point): + self.network.link_shipping(control_point, sea_connection) + + # And use airports as a last resort. + if control_point in self.airports: + for airport in self.airports: + if control_point == airport: + continue + if self.network.has_link(control_point, airport): + continue + if not airport.is_friendly_to(control_point): + continue + self.network.link_airport(control_point, airport) diff --git a/game/transfers.py b/game/transfers.py index 661475cb..bcd17d7b 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -10,15 +10,18 @@ from dcs.mapping import Point from dcs.unittype import FlyingType, VehicleType from game.procurement import AircraftProcurementRequest +from game.theater import ControlPoint, MissionTarget +from game.theater.transitnetwork import ( + TransitConnection, + TransitNetwork, +) from game.utils import meters, nautical_miles from gen.ato import Package from gen.flights.ai_flight_planner_db import TRANSPORT_CAPABLE from gen.flights.closestairfields import ObjectiveDistanceCache -from gen.flights.flightplan import FlightPlanBuilder -from game.theater import ControlPoint, MissionTarget -from game.theater.supplyroutes import RoadNetwork, ShippingNetwork, SupplyRoute -from gen.naming import namegen from gen.flights.flight import Flight, FlightType +from gen.flights.flightplan import FlightPlanBuilder +from gen.naming import namegen if TYPE_CHECKING: from game import Game @@ -151,11 +154,14 @@ class AirliftPlanner: #: maximum range. HELO_MAX_RANGE = nautical_miles(100) - def __init__(self, game: Game, transfer: TransferOrder) -> None: + def __init__( + self, game: Game, transfer: TransferOrder, next_stop: ControlPoint + ) -> None: self.game = game self.transfer = transfer + self.next_stop = next_stop self.for_player = transfer.destination.captured - self.package = Package(target=transfer.destination, auto_asap=True) + self.package = Package(target=next_stop, auto_asap=True) def compatible_with_mission( self, unit_type: Type[FlyingType], airfield: ControlPoint @@ -164,7 +170,7 @@ class AirliftPlanner: return False if not self.transfer.origin.can_operate(unit_type): return False - if not self.transfer.destination.can_operate(unit_type): + if not self.next_stop.can_operate(unit_type): return False # Cargo planes have no maximum range. @@ -395,20 +401,7 @@ class TransportMap(Generic[TransportType]): transport.disband() del self.transports[transport.origin][transport.destination] - def network_for(self, control_point: ControlPoint) -> SupplyRoute: - raise NotImplementedError - - def path_for(self, transfer: TransferOrder) -> List[ControlPoint]: - supply_route = self.network_for(transfer.position) - return supply_route.shortest_path_between( - transfer.position, transfer.destination - ) - - def next_stop_for(self, transfer: TransferOrder) -> ControlPoint: - return self.path_for(transfer)[0] - - def add(self, transfer: TransferOrder) -> None: - next_stop = self.next_stop_for(transfer) + def add(self, transfer: TransferOrder, next_stop: ControlPoint) -> None: self.find_or_create_transport(transfer.position, next_stop).add_units(transfer) def remove(self, transport: TransportType, transfer: TransferOrder) -> None: @@ -431,9 +424,6 @@ class ConvoyMap(TransportMap): ) -> Convoy: return Convoy(origin, destination) - def network_for(self, control_point: ControlPoint) -> RoadNetwork: - return RoadNetwork.for_control_point(control_point) - class CargoShipMap(TransportMap): def create_transport( @@ -441,9 +431,6 @@ class CargoShipMap(TransportMap): ) -> CargoShip: return CargoShip(origin, destination) - def network_for(self, control_point: ControlPoint) -> ShippingNetwork: - return ShippingNetwork.for_control_point(control_point) - class PendingTransfers: def __init__(self, game: Game) -> None: @@ -465,15 +452,22 @@ class PendingTransfers: def index_of_transfer(self, transfer: TransferOrder) -> int: return self.pending_transfers.index(transfer) + def network_for(self, control_point: ControlPoint) -> TransitNetwork: + return self.game.transit_network_for(control_point.captured) + def arrange_transport(self, transfer: TransferOrder) -> None: - if transfer.destination in RoadNetwork.for_control_point(transfer.position): - self.convoys.add(transfer) - elif transfer.destination in ShippingNetwork.for_control_point( - transfer.position + network = self.network_for(transfer.position) + path = network.shortest_path_between(transfer.position, transfer.destination) + next_stop = path[0] + if network.link_type(transfer.position, next_stop) == TransitConnection.Road: + self.convoys.add(transfer, next_stop) + elif ( + network.link_type(transfer.position, next_stop) + == TransitConnection.Shipping ): - self.cargo_ships.add(transfer) + self.cargo_ships.add(transfer, next_stop) else: - AirliftPlanner(self.game, transfer).create_package_for_airlift() + AirliftPlanner(self.game, transfer, next_stop).create_package_for_airlift() def new_transfer(self, transfer: TransferOrder) -> None: transfer.origin.base.commit_losses(transfer.units) diff --git a/game/unitdelivery.py b/game/unitdelivery.py index d9cf3e0f..d6bfa826 100644 --- a/game/unitdelivery.py +++ b/game/unitdelivery.py @@ -7,10 +7,12 @@ from typing import Dict, Optional, TYPE_CHECKING, Type from dcs.unittype import UnitType, VehicleType -from game.theater import ControlPoint, SupplyRoute -from gen.flights.closestairfields import ObjectiveDistanceCache +from game.theater import ControlPoint from .db import PRICES -from .theater.supplyroutes import RoadNetwork, ShippingNetwork +from .theater.transitnetwork import ( + NoPathError, + TransitNetwork, +) from .transfers import TransferOrder if TYPE_CHECKING: @@ -125,28 +127,18 @@ class PendingUnitDeliveries: if self.destination.can_recruit_ground_units(game): return self.destination - by_road = self.find_ground_unit_source_in_supply_route( - RoadNetwork.for_control_point(self.destination), game - ) - if by_road is not None: - return by_road + try: + return self.find_ground_unit_source_in_network( + game.transit_network_for(self.destination.captured), game + ) + except NoPathError: + return None - by_ship = self.find_ground_unit_source_in_supply_route( - ShippingNetwork.for_control_point(self.destination), game - ) - if by_ship is not None: - return by_ship - - by_air = self.find_ground_unit_source_by_air(game) - if by_air is not None: - return by_air - return None - - def find_ground_unit_source_in_supply_route( - self, supply_route: SupplyRoute, game: Game + def find_ground_unit_source_in_network( + self, network: TransitNetwork, game: Game ) -> Optional[ControlPoint]: sources = [] - for control_point in supply_route: + for control_point in game.theater.control_points_for(self.destination.captured): if control_point.can_recruit_ground_units(game): sources.append(control_point) @@ -158,23 +150,10 @@ class PendingUnitDeliveries: return sources[0] closest = sources[0] - distance = len(supply_route.shortest_path_between(self.destination, closest)) + _, cost = network.shortest_path_with_cost(self.destination, closest) for source in sources: - new_distance = len( - supply_route.shortest_path_between(self.destination, source) - ) - if new_distance < distance: + _, new_cost = network.shortest_path_with_cost(self.destination, source) + if new_cost < cost: closest = source - distance = new_distance + cost = new_cost return closest - - def find_ground_unit_source_by_air(self, game: Game) -> Optional[ControlPoint]: - closest_airfields = ObjectiveDistanceCache.get_closest_airfields( - self.destination - ) - for airfield in closest_airfields.operational_airfields: - if airfield.is_friendly( - self.destination.captured - ) and airfield.can_recruit_ground_units(game): - return airfield - return None diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index f02469eb..73a3d261 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -11,16 +11,15 @@ from PySide2.QtWidgets import ( ) from game import Game, db -from game.theater import ControlPoint, ControlPointType, SupplyRoute -from game.theater.supplyroutes import RoadNetwork, ShippingNetwork +from game.theater import ControlPoint, ControlPointType from gen.flights.flight import FlightType from qt_ui.dialogs import Dialog from qt_ui.models import GameModel from qt_ui.uiconstants import EVENT_ICONS from qt_ui.windows.GameUpdateSignal import GameUpdateSignal +from qt_ui.windows.basemenu.NewUnitTransferDialog import NewUnitTransferDialog from qt_ui.windows.basemenu.QBaseMenuTabs import QBaseMenuTabs from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour -from qt_ui.windows.basemenu.NewUnitTransferDialog import NewUnitTransferDialog class QBaseMenu2(QDialog): @@ -106,11 +105,9 @@ class QBaseMenu2(QDialog): @property def has_transfer_destinations(self) -> bool: - return ( - self.cp.runway_is_operational() - or len(RoadNetwork.for_control_point(self.cp)) > 1 - or len(ShippingNetwork.for_control_point(self.cp)) > 1 - ) + return self.game_model.game.transit_network_for( + self.cp.captured + ).has_destinations(self.cp) @property def can_repair_runway(self) -> bool: From b6cf7a45347319b7326623c3a0a015db3cdfd4d4 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 17:10:24 -0700 Subject: [PATCH 084/438] Fix cancelling convoys and cargo ships. Not sure when I broke this but all transports were being cancelled as if they were airlifts. --- game/transfers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/game/transfers.py b/game/transfers.py index bcd17d7b..f77ed1a3 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -503,11 +503,13 @@ class PendingTransfers: flight.package.remove_flight(flight) self.game.aircraft_inventory.return_from_flight(flight) + @cancel_transport.register def _cancel_transport_convoy( self, transfer: TransferOrder, transport: Convoy ) -> None: self.convoys.remove(transport, transfer) + @cancel_transport.register def _cancel_transport_cargo_ship( self, transfer: TransferOrder, transport: CargoShip ) -> None: From 028bfc11eb53db983ab45c1c756ac077504b7bf4 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 17:26:07 -0700 Subject: [PATCH 085/438] Fix convoys skipping intermediate stops. --- game/transfers.py | 29 +++++++++++++++++++++-------- gen/flights/flightplan.py | 4 ++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/game/transfers.py b/game/transfers.py index f77ed1a3..e87f2745 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -29,6 +29,9 @@ if TYPE_CHECKING: class Transport: + def __init__(self, destination: ControlPoint): + self.destination = destination + def find_escape_route(self) -> Optional[ControlPoint]: raise NotImplementedError @@ -98,6 +101,14 @@ class TransferOrder: location.base.commision_units(self.units) self.units.clear() + @property + def next_stop(self) -> ControlPoint: + if self.transport is None: + raise RuntimeError( + "TransferOrder.next_stop called with no transport assigned" + ) + return self.transport.destination + def proceed(self) -> None: if self.transport is None: return @@ -116,20 +127,22 @@ class TransferOrder: self.kill_all() return - self.position = self.destination + self.position = self.next_stop self.transport = None if self.completed: self.disband_at(self.position) -@dataclass class Airlift(Transport): """A transfer order that moves units by cargo planes and helicopters.""" - transfer: TransferOrder - - flight: Flight + def __init__( + self, transfer: TransferOrder, flight: Flight, next_stop: ControlPoint + ) -> None: + super().__init__(next_stop) + self.transfer = transfer + self.flight = flight @property def units(self) -> Dict[Type[VehicleType], int]: @@ -238,7 +251,7 @@ class AirliftPlanner: cargo=transfer, ) - transport = Airlift(transfer, flight) + transport = Airlift(transfer, flight, self.next_stop) transfer.transport = transport self.package.add_flight(flight) @@ -252,9 +265,9 @@ class MultiGroupTransport(MissionTarget, Transport): def __init__( self, name: str, origin: ControlPoint, destination: ControlPoint ) -> None: - super().__init__(name, origin.position) + MissionTarget.__init__(self, name, origin.position) + Transport.__init__(self, destination) self.origin = origin - self.destination = destination self.transfers: List[TransferOrder] = [] def is_friendly(self, to_player: bool) -> bool: diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index f00d351a..d045e3ea 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -1225,11 +1225,11 @@ class FlightPlanBuilder: pickup=pickup, nav_to_drop_off=builder.nav_path( cargo.origin.position, - cargo.destination.position, + cargo.next_stop.position, altitude, altitude_is_agl, ), - drop_off=builder.drop_off(cargo.destination), + drop_off=builder.drop_off(cargo.next_stop), nav_to_home=builder.nav_path( cargo.origin.position, flight.arrival.position, From 9e2e593825bd7724d4bfcf9200afbefed19e9fb2 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 17:29:10 -0700 Subject: [PATCH 086/438] Improve transport descriptions. --- game/transfers.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/game/transfers.py b/game/transfers.py index e87f2745..0a2f1877 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -157,7 +157,10 @@ class Airlift(Transport): return None def description(self) -> str: - return f"Being airlifted by {self.flight}" + return ( + f"Being airlifted from {self.transfer.position} to {self.destination} by " + f"{self.flight}" + ) class AirliftPlanner: @@ -342,7 +345,7 @@ class Convoy(MultiGroupTransport): return self.destination.convoy_spawns[self.origin] def description(self) -> str: - return f"In a convoy to {self.destination}" + return f"In a convoy from {self.origin} to {self.destination}" def find_escape_route(self) -> Optional[ControlPoint]: return None @@ -364,7 +367,7 @@ class CargoShip(MultiGroupTransport): return self.origin.shipping_lanes[self.destination] def description(self) -> str: - return f"On a ship to {self.destination}" + return f"On a ship from {self.origin} to {self.destination}" def find_escape_route(self) -> Optional[ControlPoint]: return None From 0f8d366e312ee92fcdbd93699ebad8295b3fdb57 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 18:09:33 -0700 Subject: [PATCH 087/438] Reformat with the latest black. --- qt_ui/windows/newgame/QNewGameWizard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index bfd34a21..9377afb6 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -229,7 +229,7 @@ class FactionSelection(QtWidgets.QWizardPage): self.redFactionSelect.activated.connect(self.updateUnitRecap) def setDefaultFactions(self, campaign: Campaign): - """ Set default faction for selected campaign """ + """Set default faction for selected campaign""" self.blueFactionSelect.clear() self.redFactionSelect.clear() From 5c0f6cf65efaaed1a7f93cdaa5912827394f65a3 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 18:20:20 -0700 Subject: [PATCH 088/438] Flip default for factory feature flag. This is feature complete, we have a handful of campaigns that work with the new mode now and it will be the only option at some point. --- changelog.md | 2 +- game/settings.py | 7 ++++--- qt_ui/windows/newgame/QNewGameWizard.py | 1 + qt_ui/windows/settings/QSettingsWindow.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 9a48c7d7..55ca2695 100644 --- a/changelog.md +++ b/changelog.md @@ -6,7 +6,7 @@ Saves from 2.5 are not compatible with 3.0. * **[Campaign]** Ground units can now be transferred by road, airlift, and cargo ship. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information. * **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them. -* **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory and a transfer will be created on the next turn. This feature is off by default. +* **[Campaign]** Ground units must now be recruited at a base with a factory and transferred to their destination. When buying units in the UI, the purchase will automatically be fulfilled at the closest factory, and a transfer will be created on the next turn. * **[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. * **[Modding]** Campaigns now choose locations for factories to spawn. diff --git a/game/settings.py b/game/settings.py index cbc6108c..d6338610 100644 --- a/game/settings.py +++ b/game/settings.py @@ -33,9 +33,10 @@ class Settings: disable_legacy_aewc: bool = False generate_dark_kneeboard: bool = False - #: Feature flag for new ground unit behavior. Old campaigns are sufficiently broken - #: that we need to implement this conditionally for the time being. - enable_new_ground_unit_recruitment: bool = False + #: Feature flag for new ground unit behavior. Old campaigns are will not work with + #: this so the old behavior remains an option until it breaks, at which point we'll + #: remove it. + enable_new_ground_unit_recruitment: bool = True # Performance oriented perf_red_alert_state: bool = True diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index 9377afb6..8f4bb54d 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -536,6 +536,7 @@ class DifficultyAndAutomationOptions(QtWidgets.QWizardPage): new_ground_unit_recruitment_label.setOpenExternalLinks(True) flags_layout.addWidget(new_ground_unit_recruitment_label, 0, 0) new_ground_unit_recruitment = QtWidgets.QCheckBox() + new_ground_unit_recruitment.setChecked(True) self.registerField("new_ground_unit_recruitment", new_ground_unit_recruitment) flags_layout.addWidget(new_ground_unit_recruitment, 0, 1, Qt.AlignRight) diff --git a/qt_ui/windows/settings/QSettingsWindow.py b/qt_ui/windows/settings/QSettingsWindow.py index d328b74c..af3f7e8e 100644 --- a/qt_ui/windows/settings/QSettingsWindow.py +++ b/qt_ui/windows/settings/QSettingsWindow.py @@ -83,7 +83,7 @@ NEW_GROUND_UNIT_RECRUITMENT_TOOLTIP = ( "travel to the front line." ) NEW_GROUND_UNIT_RECRUITMENT_BEHAVIOR_LABEL = ( - "Enable new ground unit recruitment behavior (WIP)
" + "Enable new ground unit recruitment behavior
" '
' 'More information.' ) From fa5d64022d4ea18ef213d0ae7be3f3848d99edea Mon Sep 17 00:00:00 2001 From: HerrTom Date: Sun, 25 Apr 2021 18:38:45 -0700 Subject: [PATCH 089/438] Condense budget and intel sections of the top panel. Budget and Intel panels now house a single button instead of separate Details buttons. Makes the top bar more compact and can fit in a 1080p monitor now. --- qt_ui/widgets/QBudgetBox.py | 10 +++------- qt_ui/widgets/QIntelBox.py | 31 ++++++++++++++++++++++++------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/qt_ui/widgets/QBudgetBox.py b/qt_ui/widgets/QBudgetBox.py index fe20ab44..e44713a8 100644 --- a/qt_ui/widgets/QBudgetBox.py +++ b/qt_ui/widgets/QBudgetBox.py @@ -14,18 +14,14 @@ class QBudgetBox(QGroupBox): super(QBudgetBox, self).__init__("Budget") self.game = game - self.money_icon = QLabel() - self.money_icon.setPixmap(CONST.ICONS["Money"]) - self.money_amount = QLabel() - self.finances = QPushButton("Details") + self.finances = QPushButton() self.finances.setDisabled(True) self.finances.setProperty("style", "btn-primary") + self.finances.setIcon(CONST.ICONS["Money"]) self.finances.clicked.connect(self.openFinances) self.layout = QHBoxLayout() - self.layout.addWidget(self.money_icon) - self.layout.addWidget(self.money_amount) self.layout.addWidget(self.finances) self.setLayout(self.layout) @@ -35,7 +31,7 @@ class QBudgetBox(QGroupBox): :param budget: Current money available :param reward: Planned reward for next turn """ - self.money_amount.setText( + self.finances.setText( str(round(budget, 2)) + "M (+" + str(round(reward, 2)) + "M)" ) diff --git a/qt_ui/widgets/QIntelBox.py b/qt_ui/widgets/QIntelBox.py index 2e3eb7e8..a31e8448 100644 --- a/qt_ui/widgets/QIntelBox.py +++ b/qt_ui/widgets/QIntelBox.py @@ -24,23 +24,39 @@ class QIntelBox(QGroupBox): self.setLayout(columns) summary = QGridLayout() - columns.addLayout(summary) + summary.setContentsMargins(5, 5, 5, 5) - summary.addWidget(QLabel("Air superiority:"), 0, 0) + air_superiority = QLabel("Air superiority:") + summary.addWidget(air_superiority, 0, 0) self.air_strength = QLabel() summary.addWidget(self.air_strength, 0, 1) - summary.addWidget(QLabel("Front line:"), 1, 0) + front_line = QLabel("Front line:") + summary.addWidget(front_line, 1, 0) self.ground_strength = QLabel() summary.addWidget(self.ground_strength, 1, 1) - summary.addWidget(QLabel("Economic strength:"), 2, 0) + economy = QLabel("Economic strength:") + summary.addWidget(economy, 2, 0) self.economic_strength = QLabel() summary.addWidget(self.economic_strength, 2, 1) - details = QPushButton("Details") - columns.addWidget(details) - details.clicked.connect(self.open_details_window) + # some dirty styling to make the labels show up well on the button + button_text_style = "background-color: rgba(0,0,0,0%); color: white;" + air_superiority.setStyleSheet(button_text_style) + front_line.setStyleSheet(button_text_style) + economy.setStyleSheet(button_text_style) + self.air_strength.setStyleSheet(button_text_style) + self.ground_strength.setStyleSheet(button_text_style) + self.economic_strength.setStyleSheet(button_text_style) + + self.details = QPushButton() + self.details.setMinimumHeight(50) + self.details.setMinimumWidth(210) + self.details.setLayout(summary) + columns.addWidget(self.details) + self.details.clicked.connect(self.open_details_window) + self.details.setEnabled(False) self.update_summary() @@ -48,6 +64,7 @@ class QIntelBox(QGroupBox): def set_game(self, game: Optional[Game]) -> None: self.game = game + self.details.setEnabled(True) self.update_summary() @staticmethod From 475c7fd6db8dbf575ed8205c8e855d0e2154660f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 18:45:36 -0700 Subject: [PATCH 090/438] Update black and mypy requirements. --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index de19d500..56a02f0a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ altgraph==0.17 appdirs==1.4.4 -black==20.8b1 +black==21.4b0 cfgv==3.2.0 click==7.1.2 distlib==0.3.1 @@ -9,7 +9,7 @@ future==0.18.2 identify==1.5.13 Jinja2==2.11.3 MarkupSafe==1.1.1 -mypy==0.782 +mypy==0.812 mypy-extensions==0.4.3 nodeenv==1.5.0 pathspec==0.8.1 @@ -28,4 +28,4 @@ tabulate==0.8.7 toml==0.10.2 typed-ast==1.4.2 typing-extensions==3.7.4.3 -virtualenv==20.4.2 \ No newline at end of file +virtualenv==20.4.2 From e9f25eb5629041799ebaaf8ae3f257f1761277de Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 25 Apr 2021 20:30:55 -0700 Subject: [PATCH 091/438] Remove unused file. mypy is flagging problems with this in the github action but not locally for whatever reason, but it's not used so just delete it. --- game/models/frontline_data.py | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 game/models/frontline_data.py diff --git a/game/models/frontline_data.py b/game/models/frontline_data.py deleted file mode 100644 index f60d5ccd..00000000 --- a/game/models/frontline_data.py +++ /dev/null @@ -1,13 +0,0 @@ -from game.theater import ControlPoint - - -class FrontlineData: - """ - This Data structure will store information about an existing frontline - """ - - def __init__(self, from_cp: ControlPoint, to_cp: ControlPoint): - self.to_cp = to_cp - self.from_cp = from_cp - self.enemy_units_position = [] - self.blue_units_position = [] From 2b8dfc9dbcf8f7c99997187545c80fdbcdfb4124 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 26 Apr 2021 17:52:50 -0700 Subject: [PATCH 092/438] Stop counting neutral base captures in status. These had no effect but were being counted on the waiting for mission results page. Cleaned up the implementation a bunch while I was here. Fixes https://github.com/Khopa/dcs_liberation/issues/1037 --- game/debriefing.py | 44 +++++++++--- game/event/event.py | 72 +++++++------------ game/theater/conflicttheater.py | 2 +- .../windows/QWaitingForMissionResultWindow.py | 2 +- 4 files changed, 62 insertions(+), 58 deletions(-) diff --git a/game/debriefing.py b/game/debriefing.py index 1d534be2..fcfbca9f 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -87,6 +87,12 @@ class GroundLosses: enemy_airfields: List[Airfield] = field(default_factory=list) +@dataclass(frozen=True) +class BaseCaptureEvent: + control_point: ControlPoint + captured_by_player: bool + + @dataclass(frozen=True) class StateData: #: True if the mission ended. If False, the mission exited abnormally. @@ -122,6 +128,7 @@ class Debriefing: self, state_data: Dict[str, Any], game: Game, unit_map: UnitMap ) -> None: self.state_data = StateData.from_json(state_data) + self.game = game self.unit_map = unit_map self.player_country = game.player_country @@ -131,6 +138,7 @@ class Debriefing: self.air_losses = self.dead_aircraft() self.ground_losses = self.dead_ground_units() + self.base_captures = self.base_capture_events() @property def front_line_losses(self) -> Iterator[FrontLineUnit]: @@ -314,15 +322,35 @@ class Debriefing: return losses - @property - def base_capture_events(self): + def base_capture_events(self) -> List[BaseCaptureEvent]: """Keeps only the last instance of a base capture event for each base ID.""" - reversed_captures = list(reversed(self.state_data.base_capture_events)) - last_base_cap_indexes = [] - for idx, base in enumerate(i.split("||")[0] for i in reversed_captures): - if base not in [x[1] for x in last_base_cap_indexes]: - last_base_cap_indexes.append((idx, base)) - return [reversed_captures[idx[0]] for idx in last_base_cap_indexes] + blue_coalition_id = 2 + seen = set() + captures = [] + for capture in reversed(self.state_data.base_capture_events): + cp_id_str, new_owner_id_str, _name = capture.split("||") + cp_id = int(cp_id_str) + + # Only the most recent capture event matters. + if cp_id in seen: + continue + seen.add(cp_id) + + try: + control_point = self.game.theater.find_control_point_by_id(cp_id) + except KeyError: + # Captured base is not a part of the campaign. This happens when neutral + # bases are near the conflict. Nothing to do. + continue + + captured_by_player = int(new_owner_id_str) == blue_coalition_id + if control_point.is_friendly(to_player=captured_by_player): + # Base is currently friendly to the new owner. Was captured and + # recaptured in the same mission. Nothing to do. + continue + + captures.append(BaseCaptureEvent(control_point, captured_by_player)) + return captures class PollDebriefingFileThread(threading.Thread): diff --git a/game/event/event.py b/game/event/event.py index 4ca70790..33334100 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -222,6 +222,29 @@ class Event: for damaged_runway in debriefing.damaged_runways: damaged_runway.damage_runway() + def commit_captures(self, debriefing: Debriefing) -> None: + for captured in debriefing.base_captures: + try: + if captured.captured_by_player: + info = Information( + f"{captured.control_point} captured!", + f"We took control of {captured.control_point}.", + self.game.turn, + ) + else: + info = Information( + f"{captured.control_point} lost!", + f"The enemy took control of {captured.control_point}.", + self.game.turn, + ) + + self.game.informations.append(info) + captured.control_point.capture(self.game, captured.captured_by_player) + logging.info(f"Will run redeploy for {captured.control_point}") + self.redeploy_units(captured.control_point) + except Exception: + logging.exception(f"Could not process base capture {captured}") + def commit(self, debriefing: Debriefing): logging.info("Committing mission results") @@ -232,54 +255,7 @@ class Event: self.commit_ground_object_losses(debriefing) self.commit_building_losses(debriefing) self.commit_damaged_runways(debriefing) - - # ------------------------------ - # Captured bases - # if self.game.player_country in db.BLUEFOR_FACTIONS: - coalition = 2 # Value in DCS mission event for BLUE - # else: - # coalition = 1 # Value in DCS mission event for RED - - for captured in debriefing.base_capture_events: - try: - id = int(captured.split("||")[0]) - new_owner_coalition = int(captured.split("||")[1]) - - captured_cps = [] - for cp in self.game.theater.controlpoints: - if cp.id == id: - - if cp.captured and new_owner_coalition != coalition: - for_player = False - info = Information( - cp.name + " lost !", - "The ennemy took control of " - + cp.name - + "\nShame on us !", - self.game.turn, - ) - self.game.informations.append(info) - captured_cps.append(cp) - elif not (cp.captured) and new_owner_coalition == coalition: - for_player = True - info = Information( - cp.name + " captured !", - "We took control of " + cp.name + "! Great job !", - self.game.turn, - ) - self.game.informations.append(info) - captured_cps.append(cp) - else: - continue - - cp.capture(self.game, for_player) - - for cp in captured_cps: - logging.info("Will run redeploy for " + cp.name) - self.redeploy_units(cp) - except Exception: - logging.exception(f"Could not process base capture {captured}") - + self.commit_captures(debriefing) self.complete_aircraft_transfers(debriefing) # Destroyed units carcass diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 8d59f982..cedb5b6e 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -673,7 +673,7 @@ class ConflictTheater: for i in self.controlpoints: if i.id == id: return i - raise RuntimeError(f"Cannot find ControlPoint with ID {id}") + raise KeyError(f"Cannot find ControlPoint with ID {id}") def add_json_cp(self, theater, p: dict) -> ControlPoint: cp: ControlPoint diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index 10219007..7f02f755 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -173,7 +173,7 @@ class QWaitingForMissionResultWindow(QDialog): "Buildings destroyed", list(debriefing.building_losses), update_layout ) self.add_update_row( - "Base capture events", list(debriefing.base_capture_events), update_layout + "Base capture events", debriefing.base_captures, update_layout ) # Clear previous content of the window From 8a01209ded308af429e825e48b23e3c1f9138505 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 9 Dec 2020 20:35:30 -0800 Subject: [PATCH 093/438] Add data for lat/lon conversions. --- game/theater/caucasus.py | 8 + game/theater/nevada.py | 8 + game/theater/normandy.py | 8 + game/theater/persiangulf.py | 8 + game/theater/projections.py | 31 ++++ game/theater/syria.py | 8 + game/theater/thechannel.py | 8 + requirements.txt | 2 + resources/tools/coord_export.lua | 38 ++++ resources/tools/export_coordinates.py | 255 ++++++++++++++++++++++++++ 10 files changed, 374 insertions(+) create mode 100644 game/theater/caucasus.py create mode 100644 game/theater/nevada.py create mode 100644 game/theater/normandy.py create mode 100644 game/theater/persiangulf.py create mode 100644 game/theater/projections.py create mode 100644 game/theater/syria.py create mode 100644 game/theater/thechannel.py create mode 100644 resources/tools/coord_export.lua create mode 100644 resources/tools/export_coordinates.py diff --git a/game/theater/caucasus.py b/game/theater/caucasus.py new file mode 100644 index 00000000..8d0d0adc --- /dev/null +++ b/game/theater/caucasus.py @@ -0,0 +1,8 @@ +from game.theater.projections import TransverseMercator + +PARAMETERS = TransverseMercator( + central_meridian=33, + false_easting=-99516.9999999732, + false_northing=-4998114.999999984, + scale_factor=0.9996, +) diff --git a/game/theater/nevada.py b/game/theater/nevada.py new file mode 100644 index 00000000..ad245611 --- /dev/null +++ b/game/theater/nevada.py @@ -0,0 +1,8 @@ +from game.theater.projections import TransverseMercator + +PARAMETERS = TransverseMercator( + central_meridian=-117, + false_easting=-193996.80999964548, + false_northing=-4410028.063999966, + scale_factor=0.9996, +) diff --git a/game/theater/normandy.py b/game/theater/normandy.py new file mode 100644 index 00000000..a74c692d --- /dev/null +++ b/game/theater/normandy.py @@ -0,0 +1,8 @@ +from game.theater.projections import TransverseMercator + +PARAMETERS = TransverseMercator( + central_meridian=-3, + false_easting=-195526.00000000204, + false_northing=-5484812.999999951, + scale_factor=0.9996, +) diff --git a/game/theater/persiangulf.py b/game/theater/persiangulf.py new file mode 100644 index 00000000..600801dd --- /dev/null +++ b/game/theater/persiangulf.py @@ -0,0 +1,8 @@ +from game.theater.projections import TransverseMercator + +PARAMETERS = TransverseMercator( + central_meridian=57, + false_easting=75755.99999999645, + false_northing=-2894933.0000000377, + scale_factor=0.9996, +) diff --git a/game/theater/projections.py b/game/theater/projections.py new file mode 100644 index 00000000..90f24fe2 --- /dev/null +++ b/game/theater/projections.py @@ -0,0 +1,31 @@ +from dataclasses import dataclass + +from pyproj import CRS + + +@dataclass(frozen=True) +class TransverseMercator: + central_meridian: int + false_easting: float + false_northing: float + scale_factor: float + + def to_crs(self) -> CRS: + return CRS.from_proj4( + " ".join( + [ + "+proj=tmerc", + "+lat_0=0", + f"+lon_0={self.central_meridian}", + f"+k_0={self.scale_factor}", + f"+x_0={self.false_easting}", + f"+y_0={self.false_northing}", + "+towgs84=0,0,0,0,0,0,0", + "+units=m", + "+vunits=m", + "+ellps=WGS84", + "+no_defs", + "+axis=neu", + ] + ) + ) diff --git a/game/theater/syria.py b/game/theater/syria.py new file mode 100644 index 00000000..7fe83db3 --- /dev/null +++ b/game/theater/syria.py @@ -0,0 +1,8 @@ +from game.theater.projections import TransverseMercator + +PARAMETERS = TransverseMercator( + central_meridian=39, + false_easting=282801.00000003993, + false_northing=-3879865.9999999935, + scale_factor=0.9996, +) diff --git a/game/theater/thechannel.py b/game/theater/thechannel.py new file mode 100644 index 00000000..33137bd7 --- /dev/null +++ b/game/theater/thechannel.py @@ -0,0 +1,8 @@ +from game.theater.projections import TransverseMercator + +PARAMETERS = TransverseMercator( + central_meridian=3, + false_easting=99376.00000000288, + false_northing=-5636889.00000001, + scale_factor=0.9996, +) diff --git a/requirements.txt b/requirements.txt index 56a02f0a..7630d67d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ altgraph==0.17 appdirs==1.4.4 black==21.4b0 +certifi==2020.12.5 cfgv==3.2.0 click==7.1.2 distlib==0.3.1 @@ -17,6 +18,7 @@ pefile==2019.4.18 Pillow==8.1.1 pre-commit==2.10.1 PyInstaller==3.6 +pyproj==3.0.1 PySide2==5.15.2 pywin32-ctypes==0.2.0 PyYAML==5.4.1 diff --git a/resources/tools/coord_export.lua b/resources/tools/coord_export.lua new file mode 100644 index 00000000..c5542522 --- /dev/null +++ b/resources/tools/coord_export.lua @@ -0,0 +1,38 @@ +local function dump_coords() + local coordinates = {} + local bases = world.getAirbases() + for i = 1, #bases do + local base = bases[i] + point = Airbase.getPoint(base) + lat, lon, alt = coord.LOtoLL(point) + coordinates[Airbase.getName(base)] = { + ["point"] = point, + ["LL"] = { + ["lat"] = lat, + ["lon"] = lon, + ["alt"] = alt, + }, + } + end + + zero = { + ["x"] = 0, + ["y"] = 0, + ["z"] = 0, + } + lat, lon, alt = coord.LOtoLL(zero) + coordinates["zero"] = { + ["point"] = zero, + ["LL"] = { + ["lat"] = lat, + ["lon"] = lon, + ["alt"] = alt, + }, + } + + local fp = io.open(lfs.writedir() .. "\\coords.json", 'w') + fp:write(json:encode(coordinates)) + fp:close() +end + +dump_coords() \ No newline at end of file diff --git a/resources/tools/export_coordinates.py b/resources/tools/export_coordinates.py new file mode 100644 index 00000000..d3605238 --- /dev/null +++ b/resources/tools/export_coordinates.py @@ -0,0 +1,255 @@ +"""Command line tool for exporting coordinates from DCS to derive projection data. + +DCS X/Z coordinates are meter-scale projections of a transverse mercator grid. The +projection has a few required parameters: + +1. Scale factor. Is 0.9996 for most regions: + https://proj.org/operations/projections/tmerc.html. +2. Central meridian of the projection. Easily guessed because there are only 60 UTM + zones and one of those is always used. +3. A false easting and northing (offsets from UTM's center point to DCS's). These aren't + easily guessed, but can be computed by using an offset of 0 and finding the error of + projecting the 0 point from DCS. + +This tool creates a mission that will dump the lat/lon and x/z coordinates of the 0/0 +point and also every airport in the given theater. The data for the zero point is used +to compute the false easting and northing for the map. The data for each airport is used +to test the projection for errors. + +The resulting data is exported to game/theater/.py as a TransverseMercator object. +""" +from __future__ import annotations + +import argparse +import json +import math +import sys +import textwrap +from contextlib import contextmanager +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Dict + +from dcs import Mission +from dcs.action import DoScriptFile +from dcs.terrain.caucasus import Caucasus +from dcs.terrain.nevada import Nevada +from dcs.terrain.normandy import Normandy +from dcs.terrain.persiangulf import PersianGulf +from dcs.terrain.syria import Syria +from dcs.terrain.terrain import Terrain +from dcs.terrain.thechannel import TheChannel +from dcs.triggers import TriggerStart +from pyproj import CRS, Transformer + +from game import persistency +from game.theater.projections import TransverseMercator +from qt_ui import liberation_install + +THIS_DIR = Path(__file__).resolve().parent +JSON_LUA = THIS_DIR.parent / "plugins/base/json.lua" +EXPORT_LUA = THIS_DIR / "coord_export.lua" +SAVE_DIR = THIS_DIR.parent / "coordinate_reference" + + +ARG_TO_TERRAIN_MAP = { + "caucasus": Caucasus(), + "nevada": Nevada(), + "normandy": Normandy(), + "persiangulf": PersianGulf(), + "thechannel": TheChannel(), + "syria": Syria(), +} + +# https://gisgeography.com/central-meridian/ +# UTM zones determined by guess and check. There are only a handful in the region for +# each map and getting the wrong one will be flagged with errors when processing. +CENTRAL_MERIDIANS = { + "caucasus": 33, + "nevada": -117, + "normandy": -3, + "persiangulf": 57, + "thechannel": 3, + "syria": 39, +} + + +@dataclass(frozen=True) +class Coordinates: + x: float + y: float + z: float + + latitude: float + longitude: float + altitude: float + + @classmethod + def from_json(cls, data: Dict[str, Any]) -> Coordinates: + return cls( + x=data["point"]["x"], + y=data["point"]["y"], + z=data["point"]["z"], + latitude=data["LL"]["lat"], + longitude=data["LL"]["lon"], + altitude=data["LL"]["alt"], + ) + + +def create_mission(terrain: Terrain) -> Path: + m = Mission(terrain) + + json_trigger = TriggerStart(comment=f"Load JSON") + json_lua = m.map_resource.add_resource_file(JSON_LUA) + json_trigger.add_action(DoScriptFile(json_lua)) + m.triggerrules.triggers.append(json_trigger) + + export_trigger = TriggerStart(comment=f"Load coordinate export") + export_lua = m.map_resource.add_resource_file(EXPORT_LUA) + export_trigger.add_action(DoScriptFile(export_lua)) + m.triggerrules.triggers.append(export_trigger) + + mission_path = persistency.mission_path_for(f"export_{terrain.name.lower()}.miz") + m.save(mission_path) + return Path(mission_path) + + +def load_coordinate_data(data: Dict[str, Any]) -> Dict[str, Coordinates]: + airbases = {} + for name, coord_data in data.items(): + airbases[name] = Coordinates.from_json(coord_data) + return airbases + + +def test_for_errors( + name: str, + lat_lon_to_x_z: Transformer, + x_z_to_lat_lon: Transformer, + coords: Coordinates, +) -> bool: + errors = False + + x, z = lat_lon_to_x_z.transform(coords.latitude, coords.longitude) + if not math.isclose(x, coords.x) or not math.isclose(z, coords.z): + error_x = x - coords.x + error_z = z - coords.z + error_pct_x = error_x / coords.x * 100 + error_pct_z = error_z / coords.z * 100 + print(f"{name} has error of {error_pct_x}% {error_pct_z}%") + errors = True + + lat, lon = x_z_to_lat_lon.transform(coords.x, coords.z) + if not math.isclose(lat, coords.latitude) or not math.isclose( + lon, coords.longitude + ): + error_lat = lat - coords.latitude + error_lon = lon - coords.longitude + error_pct_lon = error_lat / coords.latitude * 100 + error_pct_lat = error_lon / coords.longitude * 100 + print(f"{name} has error of {error_pct_lat}% {error_pct_lon}%") + errors = True + + return errors + + +def test_parameters( + airbases: Dict[str, Coordinates], parameters: TransverseMercator +) -> bool: + errors = False + wgs84 = CRS("WGS84") + crs = parameters.to_crs() + lat_lon_to_x_z = Transformer.from_crs(wgs84, crs) + x_z_to_lat_lon = Transformer.from_crs(crs, wgs84) + for name, coords in airbases.items(): + if name == "zero": + continue + if test_for_errors(name, lat_lon_to_x_z, x_z_to_lat_lon, coords): + errors = True + return errors + + +def compute_tmerc_parameters( + coordinates_file: Path, terrain: str +) -> TransverseMercator: + + data = json.loads(coordinates_file.read_text()) + airbases = load_coordinate_data(data) + wgs84 = CRS("WGS84") + + # Creates a transformer with 0 for the false easting and northing, but otherwise has + # the correct parameters. We'll use this to transform the zero point from the + # mission to calculate the error from the actual zero point to determine the correct + # false easting and northing. + bad = TransverseMercator( + central_meridian=CENTRAL_MERIDIANS[terrain], + false_easting=0, + false_northing=0, + scale_factor=0.9996, + ).to_crs() + zero_finder = Transformer.from_crs(wgs84, bad) + z, x = zero_finder.transform(airbases["zero"].latitude, airbases["zero"].longitude) + + parameters = TransverseMercator( + central_meridian=CENTRAL_MERIDIANS[terrain], + false_easting=-x, + false_northing=-z, + scale_factor=0.9996, + ) + + if test_parameters(airbases, parameters): + sys.exit("Found errors in projection parameters. Quitting.") + + return parameters + + +@contextmanager +def mission_scripting(): + liberation_install.replace_mission_scripting_file() + try: + yield + finally: + liberation_install.restore_original_mission_scripting() + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + + parser.add_argument("map", choices=list(ARG_TO_TERRAIN_MAP.keys())) + + return parser.parse_args() + + +def main() -> None: + if liberation_install.init(): + print("Set up Liberation first.") + return + + args = parse_args() + terrain = ARG_TO_TERRAIN_MAP[args.map] + mission = create_mission(terrain) + with mission_scripting(): + input( + f"Created {mission} and replaced MissionScript.lua. Open DCS and load the " + "mission. Once the mission starts running, close it and press enter." + ) + coords_path = Path(persistency.base_path()) / "coords.json" + parameters = compute_tmerc_parameters(coords_path, args.map) + out_file = THIS_DIR.parent.parent / "game/theater" / f"{args.map}.py" + out_file.write_text( + textwrap.dedent( + f"""\ + from game.theater.projections import TransverseMercator + + PARAMETERS = TransverseMercator( + central_meridian={parameters.central_meridian}, + false_easting={parameters.false_easting}, + false_northing={parameters.false_northing}, + scale_factor={parameters.scale_factor}, + ) + """ + ) + ) + + +if __name__ == "__main__": + main() From 2a06a1ffdf9719cb1a0138b052b78a0ebbfcbc24 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 27 Apr 2021 21:15:12 -0700 Subject: [PATCH 094/438] Add proof-of-concept target info kneeboard page. This is extremely rough and just serves as an example of how to use the map projection API. --- game/theater/conflicttheater.py | 60 +++++++++++++++++++++++++++++++++ gen/kneeboard.py | 56 ++++++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 2 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index cedb5b6e..bd98485f 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -40,6 +40,7 @@ from dcs.unitgroup import ( VehicleGroup, ) from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed +from pyproj import CRS, Transformer from shapely import geometry, ops from gen.flights.flight import FlightType @@ -53,6 +54,7 @@ from .controlpoint import ( OffMapSpawn, ) from .landmap import Landmap, load_landmap, poly_contains +from .projections import TransverseMercator from ..point_with_heading import PointWithHeading from ..utils import Distance, meters, nautical_miles, pairwise @@ -473,6 +475,12 @@ class ReferencePoint: image_coordinates: Point +@dataclass(frozen=True) +class LatLon: + latitude: float + longitude: float + + class ConflictTheater: terrain: Terrain @@ -725,6 +733,22 @@ class ConflictTheater: MizCampaignLoader(directory / miz, t).populate_theater() return t + @property + def projection_parameters(self) -> TransverseMercator: + raise NotImplementedError + + def point_to_ll(self, point: Point) -> LatLon: + lat, lon = Transformer.from_crs( + self.projection_parameters.to_crs(), CRS("WGS84") + ).transform(point.x, point.y) + return LatLon(lat, lon) + + def ll_to_point(self, ll: LatLon) -> Point: + x, y = Transformer.from_crs( + CRS("WGS84"), self.projection_parameters.to_crs() + ).transform(ll.latitude, ll.longitude) + return Point(x, y) + class CaucasusTheater(ConflictTheater): terrain = caucasus.Caucasus() @@ -742,6 +766,12 @@ class CaucasusTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .caucasus import PARAMETERS + + return PARAMETERS + class PersianGulfTheater(ConflictTheater): terrain = persiangulf.PersianGulf() @@ -758,6 +788,12 @@ class PersianGulfTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .persiangulf import PARAMETERS + + return PARAMETERS + class NevadaTheater(ConflictTheater): terrain = nevada.Nevada() @@ -774,6 +810,12 @@ class NevadaTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .nevada import PARAMETERS + + return PARAMETERS + class NormandyTheater(ConflictTheater): terrain = normandy.Normandy() @@ -790,6 +832,12 @@ class NormandyTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .normandy import PARAMETERS + + return PARAMETERS + class TheChannelTheater(ConflictTheater): terrain = thechannel.TheChannel() @@ -806,6 +854,12 @@ class TheChannelTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .thechannel import PARAMETERS + + return PARAMETERS + class SyriaTheater(ConflictTheater): terrain = syria.Syria() @@ -822,6 +876,12 @@ class SyriaTheater(ConflictTheater): "night": (0, 5), } + @property + def projection_parameters(self) -> TransverseMercator: + from .syria import PARAMETERS + + return PARAMETERS + @dataclass class ComplexFrontLine: diff --git a/gen/kneeboard.py b/gen/kneeboard.py index 8f809148..d1de5456 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -33,6 +33,7 @@ from dcs.mission import Mission from dcs.unittype import FlyingType from tabulate import tabulate +from game.theater import ConflictTheater from game.utils import meters from .aircraft import AIRCRAFT_DATA, FlightData from .airsupportgen import AwacsInfo, TankerInfo @@ -293,7 +294,6 @@ class BriefingPage(KneeboardPage): headers=["#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure"], ) - flight_plan_builder writer.table( [ [ @@ -415,6 +415,41 @@ class BriefingPage(KneeboardPage): return local_time.strftime(f"%H:%M:%S") +class TargetInfoPage(KneeboardPage): + """A kneeboard page containing target information.""" + + def __init__( + self, + flight: FlightData, + targets: List[FlightWaypoint], + dark_kneeboard: bool, + theater: ConflictTheater, + ) -> None: + self.flight = flight + self.targets = targets + self.dark_kneeboard = dark_kneeboard + self.theater = theater + + def write(self, path: Path) -> None: + writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard) + if self.flight.custom_name is not None: + custom_name_title = ' ("{}")'.format(self.flight.custom_name) + else: + custom_name_title = "" + writer.title(f"{self.flight.callsign} Target Info{custom_name_title}") + + writer.table( + [self.target_info_row(t) for t in self.targets], + headers=["Description", "Location"], + ) + + writer.write(path) + + def target_info_row(self, target: FlightWaypoint) -> List[str]: + ll = self.theater.point_to_ll(target.position) + return [target.pretty_name, f"{ll.latitude} {ll.longitude}"] + + class KneeboardGenerator(MissionInfoGenerator): """Creates kneeboard pages for each client flight in the mission.""" @@ -456,9 +491,21 @@ class KneeboardGenerator(MissionInfoGenerator): ) return all_flights + def generate_target_page(self, flight: FlightData) -> Optional[KneeboardPage]: + target_waypoints = [ + w + for w in flight.waypoints + if w.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC + ] + if not target_waypoints: + return None + return TargetInfoPage( + flight, target_waypoints, self.dark_kneeboard, self.game.theater + ) + def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]: """Returns a list of kneeboard pages for the given flight.""" - return [ + pages: List[KneeboardPage] = [ BriefingPage( flight, self.comms, @@ -469,3 +516,8 @@ class KneeboardGenerator(MissionInfoGenerator): self.dark_kneeboard, ), ] + + if (target_page := self.generate_target_page(flight)) is not None: + pages.append(target_page) + + return pages From 840107c69e731faa3463acf0b6a3bea1723579a8 Mon Sep 17 00:00:00 2001 From: SnappyComebacks <74509817+SnappyComebacks@users.noreply.github.com> Date: Wed, 28 Apr 2021 22:07:22 -0600 Subject: [PATCH 095/438] Move base EWRs into their own category. Without this we're sometimes spawning base EWRs at points far outside the base perimeter. --- .gitignore | 1 + game/theater/conflicttheater.py | 2 +- game/theater/controlpoint.py | 8 +++++++- game/theater/start_generator.py | 2 +- resources/campaigns/full_caucasus.miz | Bin 124162 -> 120569 bytes 5 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 9f22b322..5e953c36 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ a.py resources/tools/a.miz # User-specific stuff .idea/ +.env /kneeboards /liberation_preferences.json diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index bd98485f..dd66a5e8 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -399,7 +399,7 @@ class MizCampaignLoader: for group in self.ewrs: closest, distance = self.objective_info(group) if distance < self.BASE_DEFENSE_RADIUS: - closest.preset_locations.ewrs.append( + closest.preset_locations.base_ewrs.append( PointWithHeading.from_point(group.position, group.units[0].heading) ) else: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 9a8fb261..52937ab0 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -64,6 +64,7 @@ class LocationType(Enum): BaseAirDefense = "base air defense" Coastal = "coastal defense" Ewr = "EWR" + BaseEwr = "Base EWR" Garrison = "garrison" MissileSite = "missile site" OffshoreStrikeTarget = "offshore strike target" @@ -87,6 +88,9 @@ class PresetLocations: #: Locations used by EWRs. ewrs: List[PointWithHeading] = field(default_factory=list) + #: Locations used by Base EWRs. + base_ewrs: List[PointWithHeading] = field(default_factory=list) + #: Locations used by non-carrier ships. Carriers and LHAs are not random. ships: List[PointWithHeading] = field(default_factory=list) @@ -135,6 +139,8 @@ class PresetLocations: return self._random_from(self.coastal_defenses) if location_type == LocationType.Ewr: return self._random_from(self.ewrs) + if location_type == LocationType.BaseEwr: + return self._random_from(self.base_ewrs) if location_type == LocationType.Garrison: return self._random_from(self.base_garrisons) if location_type == LocationType.MissileSite: @@ -474,7 +480,7 @@ class ControlPoint(MissionTarget, ABC): for base_defense in self.base_defenses: p = PointWithHeading.from_point(base_defense.position, base_defense.heading) if isinstance(base_defense, EwrGroundObject): - self.preset_locations.ewrs.append(p) + self.preset_locations.base_ewrs.append(p) elif isinstance(base_defense, SamGroundObject): self.preset_locations.base_air_defense.append(p) elif isinstance(base_defense, VehicleGroupGroundObject): diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 28d610ff..32688b1e 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -303,7 +303,7 @@ class BaseDefenseGenerator: self.generate_base_defenses() def generate_ewr(self) -> None: - position = self.location_finder.location_for(LocationType.Ewr) + position = self.location_finder.location_for(LocationType.BaseEwr) if position is None: return diff --git a/resources/campaigns/full_caucasus.miz b/resources/campaigns/full_caucasus.miz index cbdc9dc7638f6e68506117af8c358ece06bd1486..3f27702ce34ca4a8a8bff155ed7e2fd57096c9f5 100644 GIT binary patch literal 120569 zcmb4rc|4Tu7k8ynDk2pnlLuK!%90c_St6vdwW&yyt%xD(jEd5Nv1QLB%9bP~36;uj zlBHy;jIon##+bo-&D^uq^Su4u_n&9xQ}=Vtb)D;+@A;nZId>*TJWGWaEm^T*(V|6L z7rj}0cL{3i;zbc@+ZXYIKe^?ds$#8u{3 zNR|c64z@IH#Fx)>m(6@wnEkME(t2T{cPJJaK<+K?!t|0B$`=ObypsK?zVpM`2=rt+ z;@1Lt`okCN0Jlk6iJ}#aG>5~uu2c(n=`%lTzVX*+a5r_%wfnX2+)pZ!Hs^()b&V~M z>L*d?vdVZV0q49M8qQ_MamL^Omkb%r|@} z8Gm+uj-ZCgo4G~}?Hh@inji+e_L-d^mMqNvEK$R|Seo^A2#o}tPFyk2K38 z_nV~=ZO7VhSXu>cF1mlAj^F#`8v(EP*pb!2cVB&#+gV2Kp-y!9P0xN9lEIJ88~P*e zc5C=mP`xOfRJGnxFUQd}uMJ>g+saPCe7i>ST)n<`m->$rDP93;WJ;`Jat^sCJ$Ifm zkiRjvd%kV~JxNXPF57>2j#Qn0Ty4zt*(9%v)f{CP(Pn%g-(F2|VWz%Fv20wZhbO5V0yMU(bc;A@^npiz|oD0 zGap>7I$zTQbJb>%xdZOj-s2P9xYxAN%~-|RiZ7(bEx$hCzEOUj+DPqa*BqzHER2(p z<>XfD=3T4<#z*HUGm7PN@m_&< z5|4M6`Oj92>>h%3mz9@eCI-7lW5#<&vggWO=4a~lG5wq8COY|O)cU9|nCN(eXdN_yIRv+&=#}gbiBZ( zpVdR+wzL9;krn~9p-a6Zex#}1bg>Iv^KS*L^k0#N4h?-7FYhiLI*yhengGz+J*;^b28q?#8}SSNQ@O5X3qOdxQSE1lBeIliZ0S|6yM6)R_o%hGc6FBpJh#Ct z{47Z?NBd58=Hf5`<-dHc&R4IZk+8r)#%aVTycdQxPL3I;Rkz;=pmYPXqbDb_6)zwV zKJy>41E@W*V+%Q6C6$%kx7&sn_QaNH+?=nC4e*{F9B@qrJ~oSu?Oyo4mD1GoYGIr< z@Bgp`_gcLuuRgGH^7vh8%&#x-*@xwRvxC!7UivrM(Stp?LvshqaBuTBx|Z!1Dexzp zf7(?fjF`FBt>NRjFgp?4?KfLdo9G(gJx6PKn>agq^eO*0giFBK#h>KKRjz7%iVLIF ziLSWUzLSHMspBKneZZT_EMZ{_eLQ2R|Hd?{t27+NJ!|yIy=)iS+56$A5Mvq9`pT_p)OZjYnH|7S<|u=XUX~YNXoM z_QsAK+lBtsTcR2*Fi+@|f9yu-rFtHYwH7DupFA(?wcV$9aKJ^nop)tdPTJ|Rvgq!p zSOj|7Wy|FGB?IvH0%fPi^uuGV%T87Lg9kBN5GUeGJfmyZPIT@g);3Re8WL+g@#O84 zx3Oc#Cnh=@eA~*&;OSa_N;09Oa0hG$0i%HM?(}kQ+u5DvUlcRqF5!zKoWI~(6w~Y8 zM*r``vcXQvnA&?^s5=lRNS&57=N-Twk0_~>TGt9#k?DWuG3BB#JrSxvU*&juhwHQ0 z+_W@m>R4~=WY)UcPQyXo+Lxyc=DwfsD2nu@JO_XBqE_u$M@|~%S1)x9X+j~>3+(Xt zSk!BOiD}Fh#i%D_{p8xhO!9Ug$~>^!4j64}E~0PZewNqvlZcrpvVm1u`}BMj%EuoZ zTp;Wlxy6mrLLHp>9z1p|&$_JtO1Y60X4++q;+K-noU^4J&)PfrVrr*d$Z}T8n?}`I zYHv)Hs6L4ec;4+b+DmO5Yt6HMYFlnyInj7?-dqB4VB?teb^qzJxBUX%jQ^mJa$V