Allow deletion of certain types of waypoints

Resolves #60
NAV/REFUEL/DIVERT waypoints should have no effect on the timings.
This commit is contained in:
Raffson 2023-05-29 00:11:27 +02:00
parent 8363a7e8fa
commit 198ff7d8a3
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
17 changed files with 96 additions and 24 deletions

View File

@ -16,6 +16,7 @@
* **[Modding]** Support for Iron Dome v1.2 by IDF Mods Project
* **[New Game Wizard]** Re-organized generator options & show the regular settings menu instead of the limited "Difficulty & Automation" page.
* **[Campaign Management]** Ability to operate harriers from FOBs/FARPs for <ins>__human pilots only__</ins>. Please note that the autoplanner won't generate flights for harriers at FOBs/FARPs, which means you need to plan your missions manually.
* **[Mission Planning]** Allow NAV/REFUEL/DIVERT waypoints to be deleted without degrading to a custom flight-plan, also warning the user before actually degrading the flight-plan.
## Fixes
* **[New Game Wizard]** Settings would not persist when going back to a previous page (obsolete due to overhaul).

View File

@ -4,11 +4,11 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import Iterator, TYPE_CHECKING, Type
from ._common_ctld import generate_random_ctld_point
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
from game.theater.controlpoint import ControlPointType
from game.theater.missiontarget import MissionTarget
from game.utils import Distance, feet, meters
from ._common_ctld import generate_random_ctld_point
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .uizonedisplay import UiZone, UiZoneDisplay
@ -21,7 +21,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class AirAssaultLayout(StandardLayout):
# The pickup point is optional because we don't always need to load the cargo. When
# departing from a carrier, LHA, or off-map spawn, the cargo is pre-loaded.

View File

@ -5,9 +5,9 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, Type
from ._common_ctld import generate_random_ctld_point
from game.theater.missiontarget import MissionTarget
from game.utils import feet
from ._common_ctld import generate_random_ctld_point
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .standard import StandardFlightPlan, StandardLayout
@ -19,7 +19,7 @@ if TYPE_CHECKING:
from dcs import Point
@dataclass(frozen=True)
@dataclass
class AirliftLayout(StandardLayout):
nav_to_pickup: list[FlightWaypoint]
# There will not be a pickup waypoint when the pickup airfield is the departure
@ -38,6 +38,20 @@ class AirliftLayout(StandardLayout):
ctld_drop_off_zone: FlightWaypoint | None
nav_to_home: list[FlightWaypoint]
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if super().delete_waypoint(waypoint):
return True
if waypoint in self.nav_to_pickup:
self.nav_to_pickup.remove(waypoint)
return True
elif waypoint in self.nav_to_drop_off:
self.nav_to_drop_off.remove(waypoint)
return True
elif waypoint in self.nav_to_home:
self.nav_to_home.remove(waypoint)
return True
return False
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to_pickup

View File

@ -18,7 +18,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class CasLayout(PatrollingLayout):
target: FlightWaypoint

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class CustomLayout(Layout):
custom_waypoints: list[FlightWaypoint]

View File

@ -15,10 +15,18 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class FerryLayout(StandardLayout):
nav_to_destination: list[FlightWaypoint]
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if super().delete_waypoint(waypoint):
return True
if waypoint in self.nav_to_destination:
self.nav_to_destination.remove(waypoint)
return True
return False
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to_destination

View File

@ -41,7 +41,7 @@ INGRESS_TYPES = {
}
@dataclass(frozen=True)
@dataclass
class Layout(ABC):
departure: FlightWaypoint
@ -50,6 +50,9 @@ class Layout(ABC):
"""A list of all waypoints in the flight plan, in order."""
return list(self.iter_waypoints())
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
return False
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
"""Iterates over all waypoints in the flight plan, in order."""
raise NotImplementedError

View File

@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import timedelta
from functools import cached_property
from typing import Any, TYPE_CHECKING, TypeGuard
from typing import Any, TYPE_CHECKING, TypeGuard, Optional
from game.typeguard import self_type_guard
from game.utils import Speed
@ -16,14 +16,28 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class FormationLayout(LoiterLayout, ABC):
nav_to: list[FlightWaypoint]
join: FlightWaypoint
split: FlightWaypoint
refuel: FlightWaypoint
refuel: Optional[FlightWaypoint]
nav_from: list[FlightWaypoint]
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if super().delete_waypoint(waypoint):
return True
if waypoint in self.nav_to:
self.nav_to.remove(waypoint)
return True
elif waypoint in self.nav_from:
self.nav_from.remove(waypoint)
return True
elif waypoint == self.refuel:
self.refuel = None
return True
return False
class FormationFlightPlan(LoiterFlightPlan, ABC):
@property

View File

@ -10,7 +10,7 @@ from dcs import Point
from game.flightplan import HoldZoneGeometry
from game.theater import MissionTarget
from game.utils import Speed, meters, Distance
from game.utils import Speed, meters
from .flightplan import FlightPlan
from .formation import FormationFlightPlan, FormationLayout
from .ibuilder import IBuilder
@ -130,7 +130,7 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
return super().tot_for_waypoint(waypoint)
@dataclass(frozen=True)
@dataclass
class FormationAttackLayout(FormationLayout):
ingress: FlightWaypoint
targets: list[FlightWaypoint]

View File

@ -13,7 +13,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class LoiterLayout(StandardLayout, ABC):
hold: FlightWaypoint

View File

@ -16,13 +16,24 @@ if TYPE_CHECKING:
from .flightplan import FlightPlan
@dataclass(frozen=True)
@dataclass
class PatrollingLayout(StandardLayout):
nav_to: list[FlightWaypoint]
patrol_start: FlightWaypoint
patrol_end: FlightWaypoint
nav_from: list[FlightWaypoint]
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if super().delete_waypoint(waypoint):
return True
if waypoint in self.nav_to:
self.nav_to.remove(waypoint)
return True
elif waypoint in self.nav_from:
self.nav_from.remove(waypoint)
return True
return False
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class RtbLayout(StandardLayout):
abort_location: FlightWaypoint
nav_to_destination: list[FlightWaypoint]

View File

@ -10,12 +10,18 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class StandardLayout(Layout, ABC):
arrival: FlightWaypoint
divert: FlightWaypoint | None
bullseye: FlightWaypoint
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if waypoint is self.divert:
self.divert = None
return True
return False
LayoutT = TypeVar("LayoutT", bound=StandardLayout)

View File

@ -17,7 +17,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class SweepLayout(LoiterLayout):
nav_to: list[FlightWaypoint]
sweep_start: FlightWaypoint

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class TarCapLayout(PatrollingLayout):
refuel: FlightWaypoint | None

View File

@ -437,7 +437,7 @@ class WaypointBuilder:
return FlightWaypoint(
"SEAD Search",
FlightWaypointType.CUSTOM,
FlightWaypointType.INGRESS_SEAD,
hold,
self.doctrine.ingress_altitude,
alt_type="BARO",

View File

@ -109,13 +109,28 @@ class QFlightWaypointTab(QFrame):
# Need to degrade to a custom flight plan and remove the waypoint.
# If the waypoint is a target waypoint and is not the last target
# waypoint, we don't need to degrade.
if isinstance(self.flight.flight_plan, FormationAttackFlightPlan):
is_target = waypoint in self.flight.flight_plan.target_area_waypoint.targets
count = len(self.flight.flight_plan.target_area_waypoint.targets)
fp = self.flight.flight_plan
if isinstance(fp, FormationAttackFlightPlan):
is_target = waypoint in fp.target_area_waypoint.targets
count = len(fp.target_area_waypoint.targets)
if is_target and count > 1:
self.flight.flight_plan.target_area_waypoint.targets.remove(waypoint)
fp.target_area_waypoint.targets.remove(waypoint)
return
if fp.layout.delete_waypoint(waypoint):
return
result = QMessageBox.warning(
self,
"Degrade flight-plan?",
"Deleting the selected waypoint(s) will require degradation to a custom flight-plan. "
"A custom flight-plan will no longer respect the TOTs of the package.<br><br>"
"<b>Are you sure you wish to continue?</b>",
QMessageBox.Yes,
QMessageBox.No,
)
if result == QMessageBox.No:
return
self.degrade_to_custom_flight_plan()
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
self.flight.flight_plan.layout.custom_waypoints.remove(waypoint)