From 01f83e845160f992c4ad414f4e773e640690eebc Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 00:07:58 -0700 Subject: [PATCH 01/12] Place CAP racetracks more defensively. Ensure that we're never putting a CAP race track within 20 nmi of an enemy airfield (aside from the target airfield, if the target is hostile). --- gen/flights/flightplan.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 9bc06473..ed2b7bb0 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -187,6 +187,10 @@ class FlightPlanBuilder: closest_cache = ObjectiveDistanceCache.get_closest_airfields(location) for airfield in closest_cache.closest_airfields: + # If the mission is a BARCAP of an enemy airfield, find the *next* + # closest enemy airfield. + if airfield == self.package.target: + continue if airfield.captured != self.is_player: closest_airfield = airfield break @@ -198,10 +202,19 @@ class FlightPlanBuilder: closest_airfield.position ) + min_distance_from_enemy = nm_to_meter(20) + distance_to_airfield = int(closest_airfield.position.distance_to_point( + self.package.target.position + )) + distance_to_no_fly = distance_to_airfield - min_distance_from_enemy + min_cap_distance = min(self.doctrine.cap_min_distance_from_cp, + distance_to_no_fly) + max_cap_distance = min(self.doctrine.cap_max_distance_from_cp, + distance_to_no_fly) + end = location.position.point_from_heading( heading, - random.randint(self.doctrine.cap_min_distance_from_cp, - self.doctrine.cap_max_distance_from_cp) + random.randint(min_cap_distance, max_cap_distance) ) diameter = random.randint( self.doctrine.cap_min_track_length, From aa96ce713499312712c69e94a2530ec951849e82 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 01:21:49 -0700 Subject: [PATCH 02/12] Fix cancel of new package. --- qt_ui/windows/mission/QPackageDialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/windows/mission/QPackageDialog.py b/qt_ui/windows/mission/QPackageDialog.py index 2ab035c3..e9837316 100644 --- a/qt_ui/windows/mission/QPackageDialog.py +++ b/qt_ui/windows/mission/QPackageDialog.py @@ -205,7 +205,7 @@ class QNewPackageDialog(QPackageDialog): def on_cancel(self) -> None: super().on_cancel() for flight in self.package_model.package.flights: - self.game_model.game.aircraft_inventory.return_from_flight(flight) + self.game.aircraft_inventory.return_from_flight(flight) class QEditPackageDialog(QPackageDialog): From c2d615315ea695b42e4467abec41c5ca1809e792 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 20:29:55 -0700 Subject: [PATCH 03/12] Add client slot selection to new flight window. --- qt_ui/windows/mission/flight/QFlightCreator.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index e514b9d7..07082848 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -56,7 +56,18 @@ class QFlightCreator(QDialog): layout.addLayout(QLabeledWidget("Airfield:", self.airfield_selector)) self.flight_size_spinner = QFlightSizeSpinner() - layout.addLayout(QLabeledWidget("Count:", self.flight_size_spinner)) + layout.addLayout(QLabeledWidget("Size:", self.flight_size_spinner)) + + self.client_slots_spinner = QFlightSizeSpinner( + min_size=0, + max_size=self.flight_size_spinner.value(), + default_size=0 + ) + self.flight_size_spinner.valueChanged.connect( + lambda v: self.client_slots_spinner.setMaximum(v) + ) + layout.addLayout( + QLabeledWidget("Client Slots:", self.client_slots_spinner)) layout.addStretch() @@ -96,6 +107,7 @@ class QFlightCreator(QDialog): start_type = "Warm" flight = Flight(aircraft, size, origin, task, start_type) flight.scheduled_in = self.package.delay + flight.client_count = self.client_slots_spinner.value() # noinspection PyUnresolvedReferences self.created.emit(flight) From 916d1eec9644adf9a5b326c8de88d7bb94e58ea8 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 20:46:18 -0700 Subject: [PATCH 04/12] Limit flight size to available aircraft. --- qt_ui/widgets/combos/QOriginAirfieldSelector.py | 6 ++++++ qt_ui/windows/mission/flight/QFlightCreator.py | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/qt_ui/widgets/combos/QOriginAirfieldSelector.py b/qt_ui/widgets/combos/QOriginAirfieldSelector.py index b0995a6b..904ec114 100644 --- a/qt_ui/widgets/combos/QOriginAirfieldSelector.py +++ b/qt_ui/widgets/combos/QOriginAirfieldSelector.py @@ -39,3 +39,9 @@ class QOriginAirfieldSelector(QComboBox): self.addItem(f"{origin.name} ({available} available)", origin) self.model().sort(0) self.update() + + @property + def available(self) -> int: + origin = self.currentData() + inventory = self.global_inventory.for_control_point(origin) + return inventory.available(self.aircraft) diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 07082848..a2ca14ee 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -53,9 +53,11 @@ class QFlightCreator(QDialog): [cp for cp in game.theater.controlpoints if cp.captured], self.aircraft_selector.currentData() ) + self.aircraft_selector.currentIndexChanged.connect(self.update_max_size) layout.addLayout(QLabeledWidget("Airfield:", self.airfield_selector)) self.flight_size_spinner = QFlightSizeSpinner() + self.update_max_size() layout.addLayout(QLabeledWidget("Size:", self.flight_size_spinner)) self.client_slots_spinner = QFlightSizeSpinner( @@ -116,3 +118,8 @@ class QFlightCreator(QDialog): def on_aircraft_changed(self, index: int) -> None: new_aircraft = self.aircraft_selector.itemData(index) self.airfield_selector.change_aircraft(new_aircraft) + + def update_max_size(self) -> None: + self.flight_size_spinner.setMaximum( + min(self.airfield_selector.available, 4) + ) From f65595c626aef7da79b3bd98a289ff686c0bce0b Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 21:17:24 -0700 Subject: [PATCH 05/12] Automatically select newly created packages. --- qt_ui/widgets/ato.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/qt_ui/widgets/ato.py b/qt_ui/widgets/ato.py index 32178381..bc45fac9 100644 --- a/qt_ui/widgets/ato.py +++ b/qt_ui/widgets/ato.py @@ -337,6 +337,7 @@ class QPackageList(QListView): self.setItemDelegate(PackageDelegate()) self.setIconSize(QSize(91, 24)) self.setSelectionBehavior(QAbstractItemView.SelectItems) + self.model().rowsInserted.connect(self.on_new_packages) @property def selected_item(self) -> Optional[Package]: @@ -346,6 +347,14 @@ class QPackageList(QListView): return None return self.ato_model.package_at_index(index) + def on_new_packages(self, _parent: QModelIndex, first: int, + _last: int) -> None: + # Select the newly created pacakges. This should only ever happen due to + # the player saving a new package, so selecting it helps them view/edit + # it faster. + self.selectionModel().setCurrentIndex(self.model().index(first, 0), + QItemSelectionModel.Select) + class QPackagePanel(QGroupBox): """The package display portion of the ATO panel. @@ -357,7 +366,7 @@ class QPackagePanel(QGroupBox): def __init__(self, model: AtoModel) -> None: super().__init__("Packages") self.ato_model = model - self.ato_model.layoutChanged.connect(self.on_selection_changed) + self.ato_model.layoutChanged.connect(self.on_current_changed) self.vbox = QVBoxLayout() self.setLayout(self.vbox) @@ -378,15 +387,15 @@ class QPackagePanel(QGroupBox): self.delete_button.clicked.connect(self.on_delete) self.button_row.addWidget(self.delete_button) - self.selection_changed.connect(self.on_selection_changed) - self.on_selection_changed() + self.current_changed.connect(self.on_current_changed) + self.on_current_changed() @property - def selection_changed(self): + def current_changed(self): """Returns the signal emitted when the flight selection changes.""" - return self.package_list.selectionModel().selectionChanged + return self.package_list.selectionModel().currentChanged - def on_selection_changed(self) -> None: + def on_current_changed(self) -> None: """Updates the status of the edit and delete buttons.""" index = self.package_list.currentIndex() enabled = index.isValid() @@ -436,8 +445,7 @@ class QAirTaskingOrderPanel(QSplitter): self.ato_model = game_model.ato_model self.package_panel = QPackagePanel(self.ato_model) - self.package_panel.selection_changed.connect(self.on_package_change) - self.ato_model.rowsInserted.connect(self.on_package_change) + self.package_panel.current_changed.connect(self.on_package_change) self.addWidget(self.package_panel) self.flight_panel = QFlightPanel(game_model) From 5023e0d30f031698610ca34a8f232e3f1152abec Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 21:34:17 -0700 Subject: [PATCH 06/12] Fix disabled delete button in package UI. The selection changed handler isn't called for the initial selection. --- qt_ui/windows/mission/QPackageDialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/windows/mission/QPackageDialog.py b/qt_ui/windows/mission/QPackageDialog.py index e9837316..4ee1a0a1 100644 --- a/qt_ui/windows/mission/QPackageDialog.py +++ b/qt_ui/windows/mission/QPackageDialog.py @@ -102,7 +102,7 @@ class QPackageDialog(QDialog): self.delete_flight_button = QPushButton("Delete Selected") self.delete_flight_button.setProperty("style", "btn-danger") self.delete_flight_button.clicked.connect(self.on_delete_flight) - self.delete_flight_button.setEnabled(False) + self.delete_flight_button.setEnabled(model.rowCount() > 0) self.button_layout.addWidget(self.delete_flight_button) self.button_layout.addStretch() From 4125f6ec06479226ee3e1f7f0cc33b80b6bc2639 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 22:26:31 -0700 Subject: [PATCH 07/12] Don't scale waypoint info text size when zooming. --- qt_ui/widgets/map/QLiberationMap.py | 39 ++++++++++++++++++----------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index 6ccde6da..567bce80 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -4,8 +4,16 @@ import datetime import logging from typing import List, Optional, Tuple -from PySide2.QtCore import Qt, QPointF -from PySide2.QtGui import QBrush, QColor, QPen, QPixmap, QWheelEvent, QPolygonF +from PySide2.QtCore import QPointF, Qt +from PySide2.QtGui import ( + QBrush, + QColor, + QFont, + QPen, + QPixmap, + QPolygonF, + QWheelEvent, +) from PySide2.QtWidgets import ( QFrame, QGraphicsItem, @@ -46,6 +54,9 @@ class QLiberationMap(QGraphicsView): self.game_model = game_model self.game: Optional[Game] = game_model.game + self.waypoint_info_font = QFont() + self.waypoint_info_font.setPointSize(12) + self.flight_path_items: List[QGraphicsItem] = [] # A tuple of (package index, flight index), or none. self.selected_flight: Optional[Tuple[int, int]] = None @@ -345,19 +356,19 @@ class QLiberationMap(QGraphicsView): pen = QPen(QColor("black"), 0.3) brush = QColor("white") - def draw_text(text: str, x: int, y: int) -> None: - item = scene.addSimpleText(text) - item.setBrush(brush) - item.setPen(pen) - item.moveBy(x, y) - item.setZValue(2) - self.flight_path_items.append(item) + text = "\n".join([ + f"{number} {waypoint.name}", + f"{altitude} ft {altitude_type}", + tot, + ]) - draw_text(f"{number} {waypoint.name}", position[0] + 8, - position[1] - 15) - draw_text(f"{altitude} ft {altitude_type}", position[0] + 8, - position[1] - 5) - draw_text(tot, position[0] + 8, position[1] + 5) + item = scene.addSimpleText(text, self.waypoint_info_font) + item.setFlag(QGraphicsItem.ItemIgnoresTransformations) + item.setBrush(brush) + item.setPen(pen) + item.moveBy(position[0] + 8, position[1]) + item.setZValue(2) + self.flight_path_items.append(item) def draw_flight_path(self, scene: QGraphicsScene, pos0: Tuple[int, int], pos1: Tuple[int, int], player: bool, From 1c4f255c7f470ce6be299a50ed83e37b52a9d37e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 22:44:54 -0700 Subject: [PATCH 08/12] Add waypoint departure time to the kneeboard. --- gen/aircraft.py | 5 +++-- gen/flights/flight.py | 3 ++- gen/kneeboard.py | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/gen/aircraft.py b/gen/aircraft.py index 88824e36..f700da0f 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1135,8 +1135,9 @@ class HoldPointBuilder(PydcsWaypointBuilder): altitude=waypoint.alt, pattern=OrbitAction.OrbitPattern.Circle )) - loiter.stop_after_time( - self.timing.push_time(self.flight, self.waypoint)) + push_time = self.timing.push_time(self.flight, self.waypoint) + self.waypoint.departure_time = push_time + loiter.stop_after_time(push_time) waypoint.add_task(loiter) return waypoint diff --git a/gen/flights/flight.py b/gen/flights/flight.py index bd879daa..5aba1d83 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -91,11 +91,12 @@ class FlightWaypoint: self.only_for_player = False self.data = None - # This is set very late by the air conflict generator (part of mission + # These are set very late by the air conflict generator (part of mission # generation). We do it late so that we don't need to propagate changes # to waypoint times whenever the player alters the package TOT or the # flight's offset in the UI. self.tot: Optional[int] = None + self.departure_time: Optional[int] = None @classmethod def from_pydcs(cls, point: MovingPoint, diff --git a/gen/kneeboard.py b/gen/kneeboard.py index cea2e591..3ef775ef 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -145,6 +145,7 @@ class FlightPlanBuilder: waypoint.waypoint.pretty_name, str(int(units.meters_to_feet(waypoint.waypoint.alt))), self._format_time(waypoint.waypoint.tot), + self._format_time(waypoint.waypoint.departure_time), ]) def _format_time(self, time: Optional[int]) -> str: @@ -187,7 +188,7 @@ class BriefingPage(KneeboardPage): for num, waypoint in enumerate(self.flight.waypoints): flight_plan_builder.add_waypoint(num, waypoint) writer.table(flight_plan_builder.build(), - headers=["STPT", "Action", "Alt", "TOT"]) + headers=["#", "Action", "Alt", "Time", "Departure"]) writer.heading("Comm Ladder") comms = [] From eb69d010676684bd730b0027f592120412d5864b Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 19 Oct 2020 23:25:46 -0700 Subject: [PATCH 09/12] Add distance and ground speed to the kneeboard. --- gen/flights/flight.py | 27 ++++++++++--------------- gen/kneeboard.py | 47 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 5aba1d83..378f50b8 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -1,9 +1,11 @@ from enum import Enum -from typing import Dict, Iterable, Optional +from typing import Dict, Iterable, List, Optional + +from dcs.mapping import Point +from dcs.point import MovingPoint, PointAction +from dcs.unittype import UnitType from game import db -from dcs.unittype import UnitType -from dcs.point import MovingPoint, PointAction from theater.controlpoint import ControlPoint, MissionTarget @@ -98,11 +100,15 @@ class FlightWaypoint: self.tot: Optional[int] = None self.departure_time: Optional[int] = None + @property + def position(self) -> Point: + return Point(self.x, self.y) + @classmethod def from_pydcs(cls, point: MovingPoint, from_cp: ControlPoint) -> "FlightWaypoint": - waypoint = FlightWaypoint(point.position.x, point.position.y, - point.alt) + waypoint = FlightWaypoint(FlightWaypointType.NAV, point.position.x, + point.position.y, point.alt) waypoint.alt_type = point.alt_type # Other actions exist... but none of them *should* be the first # waypoint for a flight. @@ -159,14 +165,3 @@ class Flight: if waypoint.waypoint_type in types: return waypoint return None - - -# Test -if __name__ == '__main__': - from dcs.planes import A_10C - from theater import ControlPoint, Point, List - - from_cp = ControlPoint(0, "AA", Point(0, 0), Point(0, 0), [], 0, 0) - f = Flight(A_10C(), 4, from_cp, FlightType.CAS, "Cold") - f.scheduled_in = 50 - print(f) diff --git a/gen/kneeboard.py b/gen/kneeboard.py index 3ef775ef..a0c4c7a5 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -29,16 +29,19 @@ from pathlib import Path from typing import Dict, List, Optional, Tuple from PIL import Image, ImageDraw, ImageFont +from dcs.mapping import Point from dcs.mission import Mission from dcs.unittype import FlyingType from tabulate import tabulate +from game.utils import meter_to_nm from . import units from .aircraft import AIRCRAFT_DATA, FlightData from .airfields import RunwayData from .airsupportgen import AwacsInfo, TankerInfo from .briefinggen import CommInfo, JtacInfo, MissionInfoGenerator from .flights.flight import FlightWaypoint, FlightWaypointType +from .flights.traveltime import TravelTime from .radios import RadioFrequency @@ -111,6 +114,7 @@ class FlightPlanBuilder: self.start_time = start_time self.rows: List[List[str]] = [] self.target_points: List[NumberedWaypoint] = [] + self.last_waypoint: Optional[FlightWaypoint] = None def add_waypoint(self, waypoint_num: int, waypoint: FlightWaypoint) -> None: if waypoint.waypoint_type == FlightWaypointType.TARGET_POINT: @@ -136,23 +140,59 @@ class FlightPlanBuilder: f"{first_waypoint_num}-{last_waypoint_num}", "Target points", "0", + self._waypoint_distance(self.target_points[0].waypoint), + self._ground_speed(self.target_points[0].waypoint), self._format_time(self.target_points[0].waypoint.tot), + self._format_time(self.target_points[0].waypoint.departure_time), ]) + self.last_waypoint = self.target_points[-1].waypoint def add_waypoint_row(self, waypoint: NumberedWaypoint) -> None: self.rows.append([ str(waypoint.number), waypoint.waypoint.pretty_name, str(int(units.meters_to_feet(waypoint.waypoint.alt))), + self._waypoint_distance(waypoint.waypoint), + self._ground_speed(waypoint.waypoint), self._format_time(waypoint.waypoint.tot), self._format_time(waypoint.waypoint.departure_time), ]) + self.last_waypoint = waypoint.waypoint def _format_time(self, time: Optional[int]) -> str: if time is None: return "" local_time = self.start_time + datetime.timedelta(seconds=time) - return local_time.strftime(f"%H:%M:%S LOCAL") + return local_time.strftime(f"%H:%M:%S") + + def _waypoint_distance(self, waypoint: FlightWaypoint) -> str: + if self.last_waypoint is None: + return "-" + + distance = meter_to_nm(self.last_waypoint.position.distance_to_point( + waypoint.position + )) + return f"{distance} NM" + + def _ground_speed(self, waypoint: FlightWaypoint) -> str: + if self.last_waypoint is None: + return "-" + + if waypoint.tot is None: + return "-" + + if self.last_waypoint.departure_time is not None: + last_time = self.last_waypoint.departure_time + elif self.last_waypoint.tot is not None: + last_time = self.last_waypoint.tot + else: + return "-" + + distance = meter_to_nm(self.last_waypoint.position.distance_to_point( + waypoint.position + )) + duration = (waypoint.tot - last_time) / 3600 + return f"{int(distance / duration)} kt" def build(self) -> List[List[str]]: return self.rows @@ -187,8 +227,9 @@ class BriefingPage(KneeboardPage): flight_plan_builder = FlightPlanBuilder(self.start_time) for num, waypoint in enumerate(self.flight.waypoints): flight_plan_builder.add_waypoint(num, waypoint) - writer.table(flight_plan_builder.build(), - headers=["#", "Action", "Alt", "Time", "Departure"]) + writer.table(flight_plan_builder.build(), headers=[ + "#", "Action", "Alt", "Dist", "GSPD", "Time", "Departure" + ]) writer.heading("Comm Ladder") comms = [] From f8ac39fb82e0ec5c1fa87eca21b1324933d75f5f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 20 Oct 2020 00:10:37 -0700 Subject: [PATCH 10/12] Fix min/max inversion in wind setting. --- game/weather.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/weather.py b/game/weather.py index a9ac5141..d6775614 100644 --- a/game/weather.py +++ b/game/weather.py @@ -71,7 +71,7 @@ class Weather: return WindConditions( # Always some wind to make the smoke move a bit. - at_0m=Wind(wind_direction, min(1, base_wind * at_0m_factor)), + at_0m=Wind(wind_direction, max(1, base_wind * at_0m_factor)), at_2000m=Wind(wind_direction, base_wind * at_2000m_factor), at_8000m=Wind(wind_direction, base_wind * at_8000m_factor) ) From 84beb2dfe52cde87c800cd34cbf30b531261400f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 20 Oct 2020 00:15:22 -0700 Subject: [PATCH 11/12] Remove dead code. --- gen/flights/ai_flight_planner.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index a1473433..a88534b3 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -389,9 +389,6 @@ class CoalitionMissionPlanner: MAX_SEAD_RANGE = nm_to_meter(150) MAX_STRIKE_RANGE = nm_to_meter(150) - NON_CAP_MIN_DELAY = 1 - NON_CAP_MAX_DELAY = 5 - def __init__(self, game: Game, is_player: bool) -> None: self.game = game self.is_player = is_player From cab5825b723a61a2919d232297e5d1fc06c6c9fd Mon Sep 17 00:00:00 2001 From: Justin Lovell Date: Tue, 20 Oct 2020 21:49:32 +1100 Subject: [PATCH 12/12] Flexible Dedicated Hosting Options * Fixed minor errors on the original LUA scripting * Refactored code to be self-contained to a function * Changed the search logic to use an environment variable first, then fallback into other search options --- resources/scripts/dcs_liberation.lua | 76 +++++++++++++++------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/resources/scripts/dcs_liberation.lua b/resources/scripts/dcs_liberation.lua index 70a5c239..8017b1f5 100644 --- a/resources/scripts/dcs_liberation.lua +++ b/resources/scripts/dcs_liberation.lua @@ -39,46 +39,54 @@ write_state = function() -- messageAll("Done writing DCS Liberation state.") end -debriefing_file_location = nil -if dcsLiberation then - debriefing_file_location = dcsLiberation.installPath -end -if debriefing_file_location then - logger:info("Using DCS Liberation install folder for state.json") -else + +local function discoverDebriefingFilePath() + local function insertFileName(directoryOrFilePath, overrideFileName) + if overrideFileName then + logger:info("Using LIBERATION_EXPORT_STAMPED_STATE to locate the state.json") + return directoryOrFilePath .. os.time() .. "-state.json" + end + + local filename = "state.json" + if not (directoryOrFilePath:sub(-#filename) == filename) then + return directoryOrFilePath .. filename + end + + return directoryOrFilePath + end + + -- establish a search pattern into the following modes + -- 1. Environment variable mode, to support dedicated server hosting + -- 2. Embedded DCS Liberation Generation, to support locally hosted single player + -- 3. Retain the classic TEMP directory logic + if os then - debriefing_file_location = os.getenv("LIBERATION_EXPORT_DIR") - if debriefing_file_location then debriefing_file_location = debriefing_file_location .. "\\" end - end - if debriefing_file_location then - logger:info("Using LIBERATION_EXPORT_DIR environment variable for state.json") - else - if os then - debriefing_file_location = os.getenv("TEMP") - if debriefing_file_location then debriefing_file_location = debriefing_file_location .. "\\" end - end - if debriefing_file_location then - logger:info("Using TEMP environment variable for state.json") - else - if lfs then - debriefing_file_location = lfs.writedir() - end - if debriefing_file_location then - logger:info("Using DCS working directory for state.json") - end + local exportDirectory = os.getenv("LIBERATION_EXPORT_DIR") + + if exportDirectory then + logger:info("Using LIBERATION_EXPORT_DIR to locate the state.json") + local useCurrentStamping = os.getenv("LIBERATION_EXPORT_STAMPED_STATE") + exportDirectory = exportDirectory .. "\\" + return insertFileName(exportDirectory, useCurrentStamping) end end -end -if debriefing_file_location then - local filename = "state.json" - if not debriefing_file_location:sub(-#filename) == filename then - debriefing_file_location = debriefing_file_location .. filename + + if dcsLiberation then + logger:info("Using DCS Liberation install folder for state.json") + return insertFileName(dcsLiberation.installPath) + end + + if lfs then + logger:info("Using DCS working directory for state.json") + return insertFileName(lfs.writedir()) end - logger:info(string.format("DCS Liberation state will be written as json to [[%s]]",debriefing_file_location)) -else - logger:error("No usable storage path for state.json") end + +debriefing_file_location = discoverDebriefingFilePath() +logger:info(string.format("DCS Liberation state will be written as json to [[%s]]",debriefing_file_location)) + + write_state_error_handling = function() if pcall(write_state) then -- messageAll("Written DCS Liberation state to "..debriefing_file_location)