From ac2fbc2940dc452bb4a0637a610414326af140e2 Mon Sep 17 00:00:00 2001 From: zhexu14 <64713351+zhexu14@users.noreply.github.com> Date: Thu, 2 Nov 2023 10:52:35 +1100 Subject: [PATCH] Support planning TARCAP at last airfield. This PR addresses #771 by adding special handling for the scenario where there is only one remaining enemy airfield. An example of the race track generated using this logic is shown below. ![Screenshot 2023-10-31 230938](https://github.com/dcs-liberation/dcs_liberation/assets/64713351/3fb2027e-f496-4325-a3c5-2abe2a45b58f) Fixes https://github.com/dcs-liberation/dcs_liberation/issues/771. --- changelog.md | 1 + game/ato/flightplans/capbuilder.py | 41 +++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 7fdba213..f0d09d6e 100644 --- a/changelog.md +++ b/changelog.md @@ -37,6 +37,7 @@ Saves from 8.x are not compatible with 9.0.0. * **[Mission Generation]** Fixed Recovery Tanker mission type intermittently failing due to not being able to find the CVN. * **[Mission Generation]** Fixed "division by zero" error on mission generation when a flight has an "In-Flight" start type and starts on top of a mission waypoint. * **[Mission Generation]** Fixed flights not being selectable in the mission editor if fast-forward was used and they were generated at a waypoint that had a fixed TOT (such as a BARCAP that was on-station). +* **[Mission Generation]** Fixed error when planning TARCAPs on the sole remaining enemy airfield. * **[Modding]** Unit variants can now actually override base unit type properties. * **[New Game Wizard]** Factions are reset to default after clicking "Back" to Theater Configuration screen. * **[Plugins]** Fixed Lua errors in Skynet plugin that would occur whenever one coalition had no IADS nodes. diff --git a/game/ato/flightplans/capbuilder.py b/game/ato/flightplans/capbuilder.py index 0d8ad965..260c6d44 100644 --- a/game/ato/flightplans/capbuilder.py +++ b/game/ato/flightplans/capbuilder.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy import random from abc import ABC from typing import Any, TYPE_CHECKING, TypeVar @@ -26,6 +27,9 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC): self, location: MissionTarget, barcap: bool ) -> tuple[Point, Point]: closest_cache = ObjectiveDistanceCache.get_closest_airfields(location) + closest_friendly_field = ( + None # keep track of closest frieldly airfield in case we need it + ) for airfield in closest_cache.operational_airfields: # If the mission is a BARCAP of an enemy airfield, find the *next* # closest enemy airfield. @@ -34,8 +38,43 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC): if airfield.captured != self.is_player: closest_airfield = airfield break + elif closest_friendly_field is None: + closest_friendly_field = airfield else: - raise PlanningError("Could not find any enemy airfields") + if barcap: + # If planning a BARCAP, we should be able to find at least one enemy + # airfield. If we can't, it's an error. + raise PlanningError("Could not find any enemy airfields") + else: + # if we cannot find any friendly or enemy airfields other than the target, + # there's nothing we can do + if closest_friendly_field is None: + raise PlanningError( + "Could not find any enemy or friendly airfields" + ) + + # If planning other race tracks (TARCAPs, currently), the target may be + # the only enemy airfield. In this case, set the race track orientation using + # a virtual point equi-distant from but opposite to the target from the closest + # friendly airfield like below, where F is the closest friendly airfield, T is + # the sole enemy airfield and V the virtual point + # + # F ---- T ----- V + # + # We need to create this virtual point, rather than using F to make sure + # the race track is aligned towards the target. + closest_friendly_field_position = copy.deepcopy( + closest_friendly_field.position + ) + closest_airfield = closest_friendly_field + closest_airfield.position.x = ( + 2 * self.package.target.position.x + - closest_friendly_field_position.x + ) + closest_airfield.position.y = ( + 2 * self.package.target.position.y + - closest_friendly_field_position.y + ) heading = Heading.from_degrees( location.position.heading_between_point(closest_airfield.position)