mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge branch 'develop' into new_frontline
This commit is contained in:
commit
658d808524
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
|||||||
|
|
||||||
- name: update build number
|
- name: update build number
|
||||||
run: |
|
run: |
|
||||||
[IO.File]::WriteAllLines($pwd.path + "\buildnumber", $env:GITHUB_RUN_NUMBER)
|
[IO.File]::WriteAllLines($pwd.path + "\resources\buildnumber", $env:GITHUB_RUN_NUMBER)
|
||||||
|
|
||||||
- name: Build binaries
|
- name: Build binaries
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -29,6 +29,10 @@ jobs:
|
|||||||
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
|
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
|
||||||
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
|
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
|
||||||
|
|
||||||
|
- name: Finalize version
|
||||||
|
run: |
|
||||||
|
New-Item -ItemType file resources\final
|
||||||
|
|
||||||
- name: mypy game
|
- name: mypy game
|
||||||
run: |
|
run: |
|
||||||
./venv/scripts/activate
|
./venv/scripts/activate
|
||||||
|
|||||||
23
changelog.md
23
changelog.md
@ -1,4 +1,19 @@
|
|||||||
# 2.2.X
|
# 2.3.0
|
||||||
|
|
||||||
|
# Features/Improvements
|
||||||
|
* **[Flight Planner]** Added fighter sweep missions.
|
||||||
|
* **[Flight Planner]** Differentiated BARCAP and TARCAP. TARCAP is now for hostile areas and will arrive before the package.
|
||||||
|
|
||||||
|
# 2.2.1
|
||||||
|
|
||||||
|
# Features/Improvements
|
||||||
|
* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom
|
||||||
|
* **[Factions]** Added map Persian Gulf full by Plob
|
||||||
|
|
||||||
|
## Fixes :
|
||||||
|
* **[Flight Planner]** Hold, join, and split points are planned cautiously near enemy airfields. Ascend/descend points are no longer planned.
|
||||||
|
|
||||||
|
# 2.2.0
|
||||||
|
|
||||||
## Features/Improvements :
|
## Features/Improvements :
|
||||||
* **[Campaign Generator]** Added early warning radar generation
|
* **[Campaign Generator]** Added early warning radar generation
|
||||||
@ -13,10 +28,9 @@
|
|||||||
* **[Map]** Highlight the selected flight path on the map
|
* **[Map]** Highlight the selected flight path on the map
|
||||||
* **[Map]** Improved SAM display settings
|
* **[Map]** Improved SAM display settings
|
||||||
* **[Map]** Improved flight plan display settings
|
* **[Map]** Improved flight plan display settings
|
||||||
|
* **[Map]** Caucasus and The Channel map use a new system to generate SAM and strike target location to reduce probability of targets generated in the middle of a forests
|
||||||
* **[Misc]** Flexible Dedicated Hosting Options for Mission Files via environment variables
|
* **[Misc]** Flexible Dedicated Hosting Options for Mission Files via environment variables
|
||||||
* **[Moddability]** Custom campaigns can be designed through json files
|
* **[Moddability]** Custom campaigns can be designed through json files
|
||||||
* **[Moddability]** Custom campaigns can be designed through json files
|
|
||||||
* **[Moddability]** Custom factions can be designed through json files
|
|
||||||
* **[Moddability]** LUA plugins can now be injected into Liberation missions.
|
* **[Moddability]** LUA plugins can now be injected into Liberation missions.
|
||||||
* **[Moddability]** Optional Skynet IADS lua plugin now included
|
* **[Moddability]** Optional Skynet IADS lua plugin now included
|
||||||
* **[New Game]** Starting budget can be freely selected
|
* **[New Game]** Starting budget can be freely selected
|
||||||
@ -25,8 +39,7 @@
|
|||||||
* **[UI]** Add polygon drawing mode for map background
|
* **[UI]** Add polygon drawing mode for map background
|
||||||
* **[UI]** Added a warning if you press takeoff with no player enabled flights
|
* **[UI]** Added a warning if you press takeoff with no player enabled flights
|
||||||
* **[UI]** Packages and flights now visible in the main window sidebar
|
* **[UI]** Packages and flights now visible in the main window sidebar
|
||||||
* **[Units/Factions]** Added bombers to coalitions
|
* **[Units/Factions]** Added bombers to some coalitions
|
||||||
* **[Units/Factions]** Added frenchpack mod units
|
|
||||||
* **[Units/Factions]** Added support for SU-57 mod by Cubanace
|
* **[Units/Factions]** Added support for SU-57 mod by Cubanace
|
||||||
* **[Units]** Added Freya EWR sites to german WW2 factions
|
* **[Units]** Added Freya EWR sites to german WW2 factions
|
||||||
* **[Units]** Added support for many bombers (B-52H, B-1B, Tu-22, Tu-142)
|
* **[Units]** Added support for many bombers (B-52H, B-1B, Tu-22, Tu-142)
|
||||||
|
|||||||
@ -16,6 +16,8 @@ class Doctrine:
|
|||||||
sead_max_range: int
|
sead_max_range: int
|
||||||
|
|
||||||
rendezvous_altitude: int
|
rendezvous_altitude: int
|
||||||
|
hold_distance: int
|
||||||
|
push_distance: int
|
||||||
join_distance: int
|
join_distance: int
|
||||||
split_distance: int
|
split_distance: int
|
||||||
ingress_egress_distance: int
|
ingress_egress_distance: int
|
||||||
@ -34,6 +36,8 @@ class Doctrine:
|
|||||||
|
|
||||||
cas_duration: timedelta
|
cas_duration: timedelta
|
||||||
|
|
||||||
|
sweep_distance: int
|
||||||
|
|
||||||
|
|
||||||
MODERN_DOCTRINE = Doctrine(
|
MODERN_DOCTRINE = Doctrine(
|
||||||
cap=True,
|
cap=True,
|
||||||
@ -44,6 +48,8 @@ MODERN_DOCTRINE = Doctrine(
|
|||||||
strike_max_range=1500000,
|
strike_max_range=1500000,
|
||||||
sead_max_range=1500000,
|
sead_max_range=1500000,
|
||||||
rendezvous_altitude=feet_to_meter(25000),
|
rendezvous_altitude=feet_to_meter(25000),
|
||||||
|
hold_distance=nm_to_meter(15),
|
||||||
|
push_distance=nm_to_meter(20),
|
||||||
join_distance=nm_to_meter(20),
|
join_distance=nm_to_meter(20),
|
||||||
split_distance=nm_to_meter(20),
|
split_distance=nm_to_meter(20),
|
||||||
ingress_egress_distance=nm_to_meter(45),
|
ingress_egress_distance=nm_to_meter(45),
|
||||||
@ -58,6 +64,7 @@ MODERN_DOCTRINE = Doctrine(
|
|||||||
cap_min_distance_from_cp=nm_to_meter(10),
|
cap_min_distance_from_cp=nm_to_meter(10),
|
||||||
cap_max_distance_from_cp=nm_to_meter(40),
|
cap_max_distance_from_cp=nm_to_meter(40),
|
||||||
cas_duration=timedelta(minutes=30),
|
cas_duration=timedelta(minutes=30),
|
||||||
|
sweep_distance=nm_to_meter(60),
|
||||||
)
|
)
|
||||||
|
|
||||||
COLDWAR_DOCTRINE = Doctrine(
|
COLDWAR_DOCTRINE = Doctrine(
|
||||||
@ -69,6 +76,8 @@ COLDWAR_DOCTRINE = Doctrine(
|
|||||||
strike_max_range=1500000,
|
strike_max_range=1500000,
|
||||||
sead_max_range=1500000,
|
sead_max_range=1500000,
|
||||||
rendezvous_altitude=feet_to_meter(22000),
|
rendezvous_altitude=feet_to_meter(22000),
|
||||||
|
hold_distance=nm_to_meter(10),
|
||||||
|
push_distance=nm_to_meter(10),
|
||||||
join_distance=nm_to_meter(10),
|
join_distance=nm_to_meter(10),
|
||||||
split_distance=nm_to_meter(10),
|
split_distance=nm_to_meter(10),
|
||||||
ingress_egress_distance=nm_to_meter(30),
|
ingress_egress_distance=nm_to_meter(30),
|
||||||
@ -83,6 +92,7 @@ COLDWAR_DOCTRINE = Doctrine(
|
|||||||
cap_min_distance_from_cp=nm_to_meter(8),
|
cap_min_distance_from_cp=nm_to_meter(8),
|
||||||
cap_max_distance_from_cp=nm_to_meter(25),
|
cap_max_distance_from_cp=nm_to_meter(25),
|
||||||
cas_duration=timedelta(minutes=30),
|
cas_duration=timedelta(minutes=30),
|
||||||
|
sweep_distance=nm_to_meter(40),
|
||||||
)
|
)
|
||||||
|
|
||||||
WWII_DOCTRINE = Doctrine(
|
WWII_DOCTRINE = Doctrine(
|
||||||
@ -93,6 +103,8 @@ WWII_DOCTRINE = Doctrine(
|
|||||||
antiship=True,
|
antiship=True,
|
||||||
strike_max_range=1500000,
|
strike_max_range=1500000,
|
||||||
sead_max_range=1500000,
|
sead_max_range=1500000,
|
||||||
|
hold_distance=nm_to_meter(5),
|
||||||
|
push_distance=nm_to_meter(5),
|
||||||
join_distance=nm_to_meter(5),
|
join_distance=nm_to_meter(5),
|
||||||
split_distance=nm_to_meter(5),
|
split_distance=nm_to_meter(5),
|
||||||
rendezvous_altitude=feet_to_meter(10000),
|
rendezvous_altitude=feet_to_meter(10000),
|
||||||
@ -108,4 +120,5 @@ WWII_DOCTRINE = Doctrine(
|
|||||||
cap_min_distance_from_cp=nm_to_meter(0),
|
cap_min_distance_from_cp=nm_to_meter(0),
|
||||||
cap_max_distance_from_cp=nm_to_meter(5),
|
cap_max_distance_from_cp=nm_to_meter(5),
|
||||||
cas_duration=timedelta(minutes=30),
|
cas_duration=timedelta(minutes=30),
|
||||||
|
sweep_distance=nm_to_meter(10),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,8 +1,18 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def _build_version_string() -> str:
|
||||||
|
components = ["2.3.0"]
|
||||||
|
build_number_path = Path("resources/buildnumber")
|
||||||
|
if build_number_path.exists():
|
||||||
|
with build_number_path.open("r") as build_number_file:
|
||||||
|
components.append(build_number_file.readline())
|
||||||
|
|
||||||
|
if not Path("resources/final").exists():
|
||||||
|
components.append("preview")
|
||||||
|
|
||||||
|
return "-".join(components)
|
||||||
|
|
||||||
|
|
||||||
#: Current version of Liberation.
|
#: Current version of Liberation.
|
||||||
VERSION = "2.2.0-preview"
|
VERSION = _build_version_string()
|
||||||
if Path("buildnumber").exists():
|
|
||||||
with open("buildnumber", "r") as file:
|
|
||||||
VERSION += f"-{file.readline()}"
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import random
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Dict, List, Optional, Type, Union, TYPE_CHECKING
|
from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union
|
||||||
|
|
||||||
from dcs import helicopters
|
from dcs import helicopters
|
||||||
from dcs.action import AITaskPush, ActivateGroup
|
from dcs.action import AITaskPush, ActivateGroup
|
||||||
@ -13,10 +13,12 @@ from dcs.condition import CoalitionHasAirdrome, TimeAfter
|
|||||||
from dcs.country import Country
|
from dcs.country import Country
|
||||||
from dcs.flyingunit import FlyingUnit
|
from dcs.flyingunit import FlyingUnit
|
||||||
from dcs.helicopters import UH_1H, helicopter_map
|
from dcs.helicopters import UH_1H, helicopter_map
|
||||||
|
from dcs.mapping import Point
|
||||||
from dcs.mission import Mission, StartType
|
from dcs.mission import Mission, StartType
|
||||||
from dcs.planes import (
|
from dcs.planes import (
|
||||||
AJS37,
|
AJS37,
|
||||||
B_17G,
|
B_17G,
|
||||||
|
B_52H,
|
||||||
Bf_109K_4,
|
Bf_109K_4,
|
||||||
FW_190A8,
|
FW_190A8,
|
||||||
FW_190D9,
|
FW_190D9,
|
||||||
@ -31,7 +33,8 @@ from dcs.planes import (
|
|||||||
P_51D_30_NA,
|
P_51D_30_NA,
|
||||||
SpitfireLFMkIX,
|
SpitfireLFMkIX,
|
||||||
SpitfireLFMkIXCW,
|
SpitfireLFMkIXCW,
|
||||||
Su_33, A_20G, Tu_22M3, B_52H,
|
Su_33,
|
||||||
|
Tu_22M3,
|
||||||
)
|
)
|
||||||
from dcs.point import MovingPoint, PointAction
|
from dcs.point import MovingPoint, PointAction
|
||||||
from dcs.task import (
|
from dcs.task import (
|
||||||
@ -49,10 +52,8 @@ from dcs.task import (
|
|||||||
OptRTBOnBingoFuel,
|
OptRTBOnBingoFuel,
|
||||||
OptRTBOnOutOfAmmo,
|
OptRTBOnOutOfAmmo,
|
||||||
OptReactOnThreat,
|
OptReactOnThreat,
|
||||||
OptRestrictAfterburner,
|
|
||||||
OptRestrictJettison,
|
OptRestrictJettison,
|
||||||
OrbitAction,
|
OrbitAction,
|
||||||
PinpointStrike,
|
|
||||||
SEAD,
|
SEAD,
|
||||||
StartCommand,
|
StartCommand,
|
||||||
Targets,
|
Targets,
|
||||||
@ -71,6 +72,7 @@ from game.utils import nm_to_meter
|
|||||||
from gen.airsupportgen import AirSupport
|
from gen.airsupportgen import AirSupport
|
||||||
from gen.ato import AirTaskingOrder, Package
|
from gen.ato import AirTaskingOrder, Package
|
||||||
from gen.callsigns import create_group_callsign_from_unit
|
from gen.callsigns import create_group_callsign_from_unit
|
||||||
|
from gen.conflictgen import FRONTLINE_LENGTH
|
||||||
from gen.flights.flight import (
|
from gen.flights.flight import (
|
||||||
Flight,
|
Flight,
|
||||||
FlightType,
|
FlightType,
|
||||||
@ -79,15 +81,14 @@ from gen.flights.flight import (
|
|||||||
)
|
)
|
||||||
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
|
||||||
from gen.runways import RunwayData
|
from gen.runways import RunwayData
|
||||||
from gen.conflictgen import FRONTLINE_LENGTH
|
|
||||||
from dcs.mapping import Point
|
|
||||||
from theater import TheaterGroundObject
|
from theater import TheaterGroundObject
|
||||||
from theater.controlpoint import ControlPoint, ControlPointType
|
from theater.controlpoint import ControlPoint, ControlPointType
|
||||||
from .conflictgen import Conflict
|
from .conflictgen import Conflict
|
||||||
from .flights.flightplan import (
|
from .flights.flightplan import (
|
||||||
CasFlightPlan,
|
CasFlightPlan,
|
||||||
FormationFlightPlan,
|
LoiterFlightPlan,
|
||||||
PatrollingFlightPlan,
|
PatrollingFlightPlan,
|
||||||
|
SweepFlightPlan,
|
||||||
)
|
)
|
||||||
from .flights.traveltime import TotEstimator
|
from .flights.traveltime import TotEstimator
|
||||||
from .naming import namegen
|
from .naming import namegen
|
||||||
@ -1035,9 +1036,6 @@ class AircraftConflictGenerator:
|
|||||||
|
|
||||||
self.configure_behavior(group, rtb_winchester=ammo_type)
|
self.configure_behavior(group, rtb_winchester=ammo_type)
|
||||||
|
|
||||||
group.points[0].tasks.append(EngageTargets(max_distance=nm_to_meter(50),
|
|
||||||
targets=[Targets.All.Air]))
|
|
||||||
|
|
||||||
def configure_cas(self, group: FlyingGroup, package: Package,
|
def configure_cas(self, group: FlyingGroup, package: Package,
|
||||||
flight: Flight,
|
flight: Flight,
|
||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
@ -1118,7 +1116,7 @@ class AircraftConflictGenerator:
|
|||||||
dynamic_runways: Dict[str, RunwayData]) -> None:
|
dynamic_runways: Dict[str, RunwayData]) -> None:
|
||||||
flight_type = flight.flight_type
|
flight_type = flight.flight_type
|
||||||
if flight_type in [FlightType.BARCAP, FlightType.TARCAP,
|
if flight_type in [FlightType.BARCAP, FlightType.TARCAP,
|
||||||
FlightType.INTERCEPTION]:
|
FlightType.INTERCEPTION, FlightType.SWEEP]:
|
||||||
self.configure_cap(group, package, flight, dynamic_runways)
|
self.configure_cap(group, package, flight, dynamic_runways)
|
||||||
elif flight_type in [FlightType.CAS, FlightType.BAI]:
|
elif flight_type in [FlightType.CAS, FlightType.BAI]:
|
||||||
self.configure_cas(group, package, flight, dynamic_runways)
|
self.configure_cas(group, package, flight, dynamic_runways)
|
||||||
@ -1278,6 +1276,7 @@ class PydcsWaypointBuilder:
|
|||||||
FlightWaypointType.LANDING_POINT: LandingPointBuilder,
|
FlightWaypointType.LANDING_POINT: LandingPointBuilder,
|
||||||
FlightWaypointType.LOITER: HoldPointBuilder,
|
FlightWaypointType.LOITER: HoldPointBuilder,
|
||||||
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
|
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
|
||||||
|
FlightWaypointType.INGRESS_SWEEP: SweepIngressBuilder,
|
||||||
}
|
}
|
||||||
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
|
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
|
||||||
return builder(waypoint, group, package, flight, mission)
|
return builder(waypoint, group, package, flight, mission)
|
||||||
@ -1314,7 +1313,7 @@ class HoldPointBuilder(PydcsWaypointBuilder):
|
|||||||
altitude=waypoint.alt,
|
altitude=waypoint.alt,
|
||||||
pattern=OrbitAction.OrbitPattern.Circle
|
pattern=OrbitAction.OrbitPattern.Circle
|
||||||
))
|
))
|
||||||
if not isinstance(self.flight.flight_plan, FormationFlightPlan):
|
if not isinstance(self.flight.flight_plan, LoiterFlightPlan):
|
||||||
flight_plan_type = self.flight.flight_plan.__class__.__name__
|
flight_plan_type = self.flight.flight_plan.__class__.__name__
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Cannot configure hold for for {self.flight} because "
|
f"Cannot configure hold for for {self.flight} because "
|
||||||
@ -1458,6 +1457,24 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
|
|||||||
return waypoint
|
return waypoint
|
||||||
|
|
||||||
|
|
||||||
|
class SweepIngressBuilder(PydcsWaypointBuilder):
|
||||||
|
def build(self) -> MovingPoint:
|
||||||
|
waypoint = super().build()
|
||||||
|
|
||||||
|
if not isinstance(self.flight.flight_plan, SweepFlightPlan):
|
||||||
|
flight_plan_type = self.flight.flight_plan.__class__.__name__
|
||||||
|
logging.error(
|
||||||
|
f"Cannot create sweep for {self.flight} because "
|
||||||
|
f"{flight_plan_type} is not a sweep flight plan.")
|
||||||
|
return waypoint
|
||||||
|
|
||||||
|
waypoint.tasks.append(EngageTargets(
|
||||||
|
max_distance=nm_to_meter(50),
|
||||||
|
targets=[Targets.All.Air.Planes.Fighters]))
|
||||||
|
|
||||||
|
return waypoint
|
||||||
|
|
||||||
|
|
||||||
class JoinPointBuilder(PydcsWaypointBuilder):
|
class JoinPointBuilder(PydcsWaypointBuilder):
|
||||||
def build(self) -> MovingPoint:
|
def build(self) -> MovingPoint:
|
||||||
waypoint = super().build()
|
waypoint = super().build()
|
||||||
@ -1532,4 +1549,14 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
|
|||||||
racetrack.stop_after_time(
|
racetrack.stop_after_time(
|
||||||
int(self.flight.flight_plan.patrol_end_time.total_seconds()))
|
int(self.flight.flight_plan.patrol_end_time.total_seconds()))
|
||||||
waypoint.add_task(racetrack)
|
waypoint.add_task(racetrack)
|
||||||
|
|
||||||
|
# TODO: Move the properties of this task into the flight plan?
|
||||||
|
# CAP is the only current user of this so it's not a big deal, but might
|
||||||
|
# be good to make this usable for things like BAI when we add that
|
||||||
|
# later.
|
||||||
|
cap_types = {FlightType.BARCAP, FlightType.TARCAP}
|
||||||
|
if self.flight.flight_type in cap_types:
|
||||||
|
waypoint.tasks.append(EngageTargets(max_distance=nm_to_meter(50),
|
||||||
|
targets=[Targets.All.Air]))
|
||||||
|
|
||||||
return waypoint
|
return waypoint
|
||||||
|
|||||||
@ -159,6 +159,7 @@ class Package:
|
|||||||
FlightType.TARCAP,
|
FlightType.TARCAP,
|
||||||
FlightType.CAP,
|
FlightType.CAP,
|
||||||
FlightType.BARCAP,
|
FlightType.BARCAP,
|
||||||
|
FlightType.SWEEP,
|
||||||
FlightType.EWAR,
|
FlightType.EWAR,
|
||||||
FlightType.ESCORT,
|
FlightType.ESCORT,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -496,7 +496,11 @@ class CoalitionMissionPlanner:
|
|||||||
error = random.randint(-margin, margin)
|
error = random.randint(-margin, margin)
|
||||||
yield timedelta(minutes=max(0, time + error))
|
yield timedelta(minutes=max(0, time + error))
|
||||||
|
|
||||||
dca_types = (FlightType.BARCAP, FlightType.INTERCEPTION)
|
dca_types = {
|
||||||
|
FlightType.BARCAP,
|
||||||
|
FlightType.INTERCEPTION,
|
||||||
|
FlightType.TARCAP,
|
||||||
|
}
|
||||||
|
|
||||||
non_dca_packages = [p for p in self.ato.packages if
|
non_dca_packages = [p for p in self.ato.packages if
|
||||||
p.primary_task not in dca_types]
|
p.primary_task not in dca_types]
|
||||||
|
|||||||
@ -38,6 +38,8 @@ class FlightType(Enum):
|
|||||||
RECON = 15
|
RECON = 15
|
||||||
EWAR = 16
|
EWAR = 16
|
||||||
|
|
||||||
|
SWEEP = 17
|
||||||
|
|
||||||
|
|
||||||
class FlightWaypointType(Enum):
|
class FlightWaypointType(Enum):
|
||||||
TAKEOFF = 0 # Take off point
|
TAKEOFF = 0 # Take off point
|
||||||
@ -61,6 +63,7 @@ class FlightWaypointType(Enum):
|
|||||||
LOITER = 18
|
LOITER = 18
|
||||||
INGRESS_ESCORT = 19
|
INGRESS_ESCORT = 19
|
||||||
INGRESS_DEAD = 20
|
INGRESS_DEAD = 20
|
||||||
|
INGRESS_SWEEP = 21
|
||||||
|
|
||||||
|
|
||||||
class FlightWaypoint:
|
class FlightWaypoint:
|
||||||
|
|||||||
@ -7,6 +7,7 @@ generating the waypoints for the mission.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
import logging
|
import logging
|
||||||
@ -104,6 +105,15 @@ class FlightPlan:
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_offset(self) -> timedelta:
|
||||||
|
"""This flight's offset from the package's TOT.
|
||||||
|
|
||||||
|
Positive values represent later TOTs. An offset of -2 minutes is used
|
||||||
|
for a flight that has a TOT 2 minutes before the rest of the package.
|
||||||
|
"""
|
||||||
|
return timedelta()
|
||||||
|
|
||||||
# Not cached because changes to the package might alter the formation speed.
|
# Not cached because changes to the package might alter the formation speed.
|
||||||
@property
|
@property
|
||||||
def travel_time_to_target(self) -> Optional[timedelta]:
|
def travel_time_to_target(self) -> Optional[timedelta]:
|
||||||
@ -146,8 +156,33 @@ class FlightPlan:
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FormationFlightPlan(FlightPlan):
|
class LoiterFlightPlan(FlightPlan):
|
||||||
hold: FlightWaypoint
|
hold: FlightWaypoint
|
||||||
|
|
||||||
|
@property
|
||||||
|
def waypoints(self) -> List[FlightWaypoint]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def push_time(self) -> timedelta:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def depart_time_for_waypoint(
|
||||||
|
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
|
if waypoint == self.hold:
|
||||||
|
return self.push_time
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class FormationFlightPlan(LoiterFlightPlan):
|
||||||
join: FlightWaypoint
|
join: FlightWaypoint
|
||||||
split: FlightWaypoint
|
split: FlightWaypoint
|
||||||
|
|
||||||
@ -214,12 +249,6 @@ class FormationFlightPlan(FlightPlan):
|
|||||||
return self.split_time
|
return self.split_time
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
|
||||||
if waypoint == self.hold:
|
|
||||||
return self.push_time
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def push_time(self) -> timedelta:
|
def push_time(self) -> timedelta:
|
||||||
return self.join_time - TravelTime.between_points(
|
return self.join_time - TravelTime.between_points(
|
||||||
@ -275,18 +304,14 @@ class PatrollingFlightPlan(FlightPlan):
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class BarCapFlightPlan(PatrollingFlightPlan):
|
class BarCapFlightPlan(PatrollingFlightPlan):
|
||||||
takeoff: FlightWaypoint
|
takeoff: FlightWaypoint
|
||||||
ascent: FlightWaypoint
|
|
||||||
descent: FlightWaypoint
|
|
||||||
land: FlightWaypoint
|
land: FlightWaypoint
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def waypoints(self) -> List[FlightWaypoint]:
|
def waypoints(self) -> List[FlightWaypoint]:
|
||||||
return [
|
return [
|
||||||
self.takeoff,
|
self.takeoff,
|
||||||
self.ascent,
|
|
||||||
self.patrol_start,
|
self.patrol_start,
|
||||||
self.patrol_end,
|
self.patrol_end,
|
||||||
self.descent,
|
|
||||||
self.land,
|
self.land,
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -294,20 +319,16 @@ class BarCapFlightPlan(PatrollingFlightPlan):
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CasFlightPlan(PatrollingFlightPlan):
|
class CasFlightPlan(PatrollingFlightPlan):
|
||||||
takeoff: FlightWaypoint
|
takeoff: FlightWaypoint
|
||||||
ascent: FlightWaypoint
|
|
||||||
target: FlightWaypoint
|
target: FlightWaypoint
|
||||||
descent: FlightWaypoint
|
|
||||||
land: FlightWaypoint
|
land: FlightWaypoint
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def waypoints(self) -> List[FlightWaypoint]:
|
def waypoints(self) -> List[FlightWaypoint]:
|
||||||
return [
|
return [
|
||||||
self.takeoff,
|
self.takeoff,
|
||||||
self.ascent,
|
|
||||||
self.patrol_start,
|
self.patrol_start,
|
||||||
self.target,
|
self.target,
|
||||||
self.patrol_end,
|
self.patrol_end,
|
||||||
self.descent,
|
|
||||||
self.land,
|
self.land,
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -319,23 +340,24 @@ class CasFlightPlan(PatrollingFlightPlan):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FrontLineCapFlightPlan(PatrollingFlightPlan):
|
class TarCapFlightPlan(PatrollingFlightPlan):
|
||||||
takeoff: FlightWaypoint
|
takeoff: FlightWaypoint
|
||||||
ascent: FlightWaypoint
|
|
||||||
descent: FlightWaypoint
|
|
||||||
land: FlightWaypoint
|
land: FlightWaypoint
|
||||||
|
lead_time: timedelta
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def waypoints(self) -> List[FlightWaypoint]:
|
def waypoints(self) -> List[FlightWaypoint]:
|
||||||
return [
|
return [
|
||||||
self.takeoff,
|
self.takeoff,
|
||||||
self.ascent,
|
|
||||||
self.patrol_start,
|
self.patrol_start,
|
||||||
self.patrol_end,
|
self.patrol_end,
|
||||||
self.descent,
|
|
||||||
self.land,
|
self.land,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_offset(self) -> timedelta:
|
||||||
|
return -self.lead_time
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
if waypoint == self.patrol_end:
|
if waypoint == self.patrol_end:
|
||||||
@ -346,8 +368,8 @@ class FrontLineCapFlightPlan(PatrollingFlightPlan):
|
|||||||
def patrol_start_time(self) -> timedelta:
|
def patrol_start_time(self) -> timedelta:
|
||||||
start = self.package.escort_start_time
|
start = self.package.escort_start_time
|
||||||
if start is not None:
|
if start is not None:
|
||||||
return start
|
return start + self.tot_offset
|
||||||
return super().patrol_start_time
|
return super().patrol_start_time + self.tot_offset
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_end_time(self) -> timedelta:
|
def patrol_end_time(self) -> timedelta:
|
||||||
@ -357,31 +379,31 @@ class FrontLineCapFlightPlan(PatrollingFlightPlan):
|
|||||||
return super().patrol_end_time
|
return super().patrol_end_time
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove when breaking save compat.
|
||||||
|
FrontLineCapFlightPlan = TarCapFlightPlan
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class StrikeFlightPlan(FormationFlightPlan):
|
class StrikeFlightPlan(FormationFlightPlan):
|
||||||
takeoff: FlightWaypoint
|
takeoff: FlightWaypoint
|
||||||
ascent: FlightWaypoint
|
|
||||||
hold: FlightWaypoint
|
hold: FlightWaypoint
|
||||||
join: FlightWaypoint
|
join: FlightWaypoint
|
||||||
ingress: FlightWaypoint
|
ingress: FlightWaypoint
|
||||||
targets: List[FlightWaypoint]
|
targets: List[FlightWaypoint]
|
||||||
egress: FlightWaypoint
|
egress: FlightWaypoint
|
||||||
split: FlightWaypoint
|
split: FlightWaypoint
|
||||||
descent: FlightWaypoint
|
|
||||||
land: FlightWaypoint
|
land: FlightWaypoint
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def waypoints(self) -> List[FlightWaypoint]:
|
def waypoints(self) -> List[FlightWaypoint]:
|
||||||
return [
|
return [
|
||||||
self.takeoff,
|
self.takeoff,
|
||||||
self.ascent,
|
|
||||||
self.hold,
|
self.hold,
|
||||||
self.join,
|
self.join,
|
||||||
self.ingress
|
self.ingress
|
||||||
] + self.targets + [
|
] + self.targets + [
|
||||||
self.egress,
|
self.egress,
|
||||||
self.split,
|
self.split,
|
||||||
self.descent,
|
|
||||||
self.land,
|
self.land,
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -476,6 +498,64 @@ class StrikeFlightPlan(FormationFlightPlan):
|
|||||||
return super().tot_for_waypoint(waypoint)
|
return super().tot_for_waypoint(waypoint)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SweepFlightPlan(LoiterFlightPlan):
|
||||||
|
takeoff: FlightWaypoint
|
||||||
|
sweep_start: FlightWaypoint
|
||||||
|
sweep_end: FlightWaypoint
|
||||||
|
land: FlightWaypoint
|
||||||
|
lead_time: timedelta
|
||||||
|
|
||||||
|
@property
|
||||||
|
def waypoints(self) -> List[FlightWaypoint]:
|
||||||
|
return [
|
||||||
|
self.takeoff,
|
||||||
|
self.hold,
|
||||||
|
self.sweep_start,
|
||||||
|
self.sweep_end,
|
||||||
|
self.land,
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_waypoint(self) -> Optional[FlightWaypoint]:
|
||||||
|
return self.sweep_end
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_offset(self) -> timedelta:
|
||||||
|
return -self.lead_time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sweep_start_time(self) -> timedelta:
|
||||||
|
travel_time = self.travel_time_between_waypoints(
|
||||||
|
self.sweep_start, self.sweep_end)
|
||||||
|
return self.sweep_end_time - travel_time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sweep_end_time(self) -> timedelta:
|
||||||
|
return self.package.time_over_target + self.tot_offset
|
||||||
|
|
||||||
|
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
|
if waypoint == self.sweep_start:
|
||||||
|
return self.sweep_start_time
|
||||||
|
if waypoint == self.sweep_end:
|
||||||
|
return self.sweep_end_time
|
||||||
|
return None
|
||||||
|
|
||||||
|
def depart_time_for_waypoint(
|
||||||
|
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
|
if waypoint == self.hold:
|
||||||
|
return self.push_time
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def push_time(self) -> timedelta:
|
||||||
|
return self.sweep_end_time - TravelTime.between_points(
|
||||||
|
self.hold.position,
|
||||||
|
self.sweep_end.position,
|
||||||
|
GroundSpeed.for_flight(self.flight, self.hold.alt)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class CustomFlightPlan(FlightPlan):
|
class CustomFlightPlan(FlightPlan):
|
||||||
custom_waypoints: List[FlightWaypoint]
|
custom_waypoints: List[FlightWaypoint]
|
||||||
@ -561,8 +641,10 @@ class FlightPlanBuilder:
|
|||||||
return self.generate_sead(flight, custom_targets)
|
return self.generate_sead(flight, custom_targets)
|
||||||
elif task == FlightType.STRIKE:
|
elif task == FlightType.STRIKE:
|
||||||
return self.generate_strike(flight)
|
return self.generate_strike(flight)
|
||||||
|
elif task == FlightType.SWEEP:
|
||||||
|
return self.generate_sweep(flight)
|
||||||
elif task == FlightType.TARCAP:
|
elif task == FlightType.TARCAP:
|
||||||
return self.generate_frontline_cap(flight)
|
return self.generate_tarcap(flight)
|
||||||
elif task == FlightType.TROOP_TRANSPORT:
|
elif task == FlightType.TROOP_TRANSPORT:
|
||||||
logging.error(
|
logging.error(
|
||||||
"Troop transport flight plan generation not implemented"
|
"Troop transport flight plan generation not implemented"
|
||||||
@ -631,11 +713,57 @@ class FlightPlanBuilder:
|
|||||||
if isinstance(location, FrontLine):
|
if isinstance(location, FrontLine):
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
|
start, end = self.racetrack_for_objective(location)
|
||||||
patrol_alt = random.randint(
|
patrol_alt = random.randint(
|
||||||
self.doctrine.min_patrol_altitude,
|
self.doctrine.min_patrol_altitude,
|
||||||
self.doctrine.max_patrol_altitude
|
self.doctrine.max_patrol_altitude
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
|
start, end = builder.race_track(start, end, patrol_alt)
|
||||||
|
descent, land = builder.rtb(flight.from_cp)
|
||||||
|
|
||||||
|
return BarCapFlightPlan(
|
||||||
|
package=self.package,
|
||||||
|
flight=flight,
|
||||||
|
patrol_duration=self.doctrine.cap_duration,
|
||||||
|
takeoff=builder.takeoff(flight.from_cp),
|
||||||
|
patrol_start=start,
|
||||||
|
patrol_end=end,
|
||||||
|
land=land
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_sweep(self, flight: Flight) -> SweepFlightPlan:
|
||||||
|
"""Generate a BARCAP flight at a given location.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
target = self.package.target.position
|
||||||
|
|
||||||
|
heading = self._heading_to_package_airfield(target)
|
||||||
|
start = target.point_from_heading(heading,
|
||||||
|
-self.doctrine.sweep_distance)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
|
descent, land = builder.rtb(flight.from_cp)
|
||||||
|
|
||||||
|
start, end = builder.sweep(start, target,
|
||||||
|
self.doctrine.ingress_altitude)
|
||||||
|
|
||||||
|
return SweepFlightPlan(
|
||||||
|
package=self.package,
|
||||||
|
flight=flight,
|
||||||
|
lead_time=timedelta(minutes=5),
|
||||||
|
takeoff=builder.takeoff(flight.from_cp),
|
||||||
|
hold=builder.hold(self._hold_point(flight)),
|
||||||
|
sweep_start=start,
|
||||||
|
sweep_end=end,
|
||||||
|
land=land
|
||||||
|
)
|
||||||
|
|
||||||
|
def racetrack_for_objective(self,
|
||||||
|
location: MissionTarget) -> Tuple[Point, Point]:
|
||||||
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||||
for airfield in closest_cache.closest_airfields:
|
for airfield in closest_cache.closest_airfields:
|
||||||
# If the mission is a BARCAP of an enemy airfield, find the *next*
|
# If the mission is a BARCAP of an enemy airfield, find the *next*
|
||||||
@ -671,37 +799,11 @@ class FlightPlanBuilder:
|
|||||||
self.doctrine.cap_max_track_length
|
self.doctrine.cap_max_track_length
|
||||||
)
|
)
|
||||||
start = end.point_from_heading(heading - 180, diameter)
|
start = end.point_from_heading(heading - 180, diameter)
|
||||||
|
return start, end
|
||||||
|
|
||||||
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
def racetrack_for_frontline(self,
|
||||||
start, end = builder.race_track(start, end, patrol_alt)
|
front_line: FrontLine) -> Tuple[Point, Point]:
|
||||||
descent, land = builder.rtb(flight.from_cp)
|
ally_cp, enemy_cp = front_line.control_points
|
||||||
|
|
||||||
return BarCapFlightPlan(
|
|
||||||
package=self.package,
|
|
||||||
flight=flight,
|
|
||||||
patrol_duration=self.doctrine.cap_duration,
|
|
||||||
takeoff=builder.takeoff(flight.from_cp),
|
|
||||||
ascent=builder.ascent(flight.from_cp),
|
|
||||||
patrol_start=start,
|
|
||||||
patrol_end=end,
|
|
||||||
descent=descent,
|
|
||||||
land=land
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_frontline_cap(self, flight: Flight) -> FrontLineCapFlightPlan:
|
|
||||||
"""Generate a CAP flight plan for the given front line.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
flight: The flight to generate the flight plan for.
|
|
||||||
"""
|
|
||||||
location = self.package.target
|
|
||||||
|
|
||||||
if not isinstance(location, FrontLine):
|
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
|
||||||
|
|
||||||
ally_cp, enemy_cp = location.control_points
|
|
||||||
patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
|
|
||||||
self.doctrine.max_patrol_altitude)
|
|
||||||
|
|
||||||
# Find targets waypoints
|
# Find targets waypoints
|
||||||
ingress, heading, distance = Conflict.frontline_vector(
|
ingress, heading, distance = Conflict.frontline_vector(
|
||||||
@ -722,24 +824,41 @@ class FlightPlanBuilder:
|
|||||||
orbit0p = orbit_center.point_from_heading(heading, radius)
|
orbit0p = orbit_center.point_from_heading(heading, radius)
|
||||||
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
||||||
|
|
||||||
|
return orbit0p, orbit1p
|
||||||
|
|
||||||
|
def generate_tarcap(self, flight: Flight) -> TarCapFlightPlan:
|
||||||
|
"""Generate a CAP flight plan for the given front line.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
|
||||||
|
self.doctrine.max_patrol_altitude)
|
||||||
|
|
||||||
# Create points
|
# Create points
|
||||||
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
|
|
||||||
|
if isinstance(location, FrontLine):
|
||||||
|
orbit0p, orbit1p = self.racetrack_for_frontline(location)
|
||||||
|
else:
|
||||||
|
orbit0p, orbit1p = self.racetrack_for_objective(location)
|
||||||
|
|
||||||
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||||
descent, land = builder.rtb(flight.from_cp)
|
descent, land = builder.rtb(flight.from_cp)
|
||||||
return FrontLineCapFlightPlan(
|
return TarCapFlightPlan(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
flight=flight,
|
flight=flight,
|
||||||
|
lead_time=timedelta(minutes=2),
|
||||||
# Note that this duration only has an effect if there are no
|
# Note that this duration only has an effect if there are no
|
||||||
# flights in the package that have requested escort. If the package
|
# flights in the package that have requested escort. If the package
|
||||||
# requests an escort the CAP flight will remain on station for the
|
# requests an escort the CAP flight will remain on station for the
|
||||||
# duration of the escorted mission, or until it is winchester/bingo.
|
# duration of the escorted mission, or until it is winchester/bingo.
|
||||||
patrol_duration=self.doctrine.cap_duration,
|
patrol_duration=self.doctrine.cap_duration,
|
||||||
takeoff=builder.takeoff(flight.from_cp),
|
takeoff=builder.takeoff(flight.from_cp),
|
||||||
ascent=builder.ascent(flight.from_cp),
|
|
||||||
patrol_start=start,
|
patrol_start=start,
|
||||||
patrol_end=end,
|
patrol_end=end,
|
||||||
descent=descent,
|
|
||||||
land=land
|
land=land
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -805,14 +924,12 @@ class FlightPlanBuilder:
|
|||||||
package=self.package,
|
package=self.package,
|
||||||
flight=flight,
|
flight=flight,
|
||||||
takeoff=builder.takeoff(flight.from_cp),
|
takeoff=builder.takeoff(flight.from_cp),
|
||||||
ascent=builder.ascent(flight.from_cp),
|
|
||||||
hold=builder.hold(self._hold_point(flight)),
|
hold=builder.hold(self._hold_point(flight)),
|
||||||
join=builder.join(self.package.waypoints.join),
|
join=builder.join(self.package.waypoints.join),
|
||||||
ingress=ingress,
|
ingress=ingress,
|
||||||
targets=[target],
|
targets=[target],
|
||||||
egress=egress,
|
egress=egress,
|
||||||
split=builder.split(self.package.waypoints.split),
|
split=builder.split(self.package.waypoints.split),
|
||||||
descent=descent,
|
|
||||||
land=land
|
land=land
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -842,11 +959,9 @@ class FlightPlanBuilder:
|
|||||||
flight=flight,
|
flight=flight,
|
||||||
patrol_duration=self.doctrine.cas_duration,
|
patrol_duration=self.doctrine.cas_duration,
|
||||||
takeoff=builder.takeoff(flight.from_cp),
|
takeoff=builder.takeoff(flight.from_cp),
|
||||||
ascent=builder.ascent(flight.from_cp),
|
|
||||||
patrol_start=builder.ingress_cas(ingress, location),
|
patrol_start=builder.ingress_cas(ingress, location),
|
||||||
target=builder.cas(center),
|
target=builder.cas(center),
|
||||||
patrol_end=builder.egress(egress, location),
|
patrol_end=builder.egress(egress, location),
|
||||||
descent=descent,
|
|
||||||
land=land
|
land=land
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -871,12 +986,50 @@ class FlightPlanBuilder:
|
|||||||
return builder.strike_area(location)
|
return builder.strike_area(location)
|
||||||
|
|
||||||
def _hold_point(self, flight: Flight) -> Point:
|
def _hold_point(self, flight: Flight) -> Point:
|
||||||
heading = flight.from_cp.position.heading_between_point(
|
assert self.package.waypoints is not None
|
||||||
self.package.target.position
|
origin = flight.from_cp.position
|
||||||
)
|
target = self.package.target.position
|
||||||
return flight.from_cp.position.point_from_heading(
|
join = self.package.waypoints.join
|
||||||
heading, nm_to_meter(15)
|
origin_to_target = origin.distance_to_point(target)
|
||||||
|
join_to_target = join.distance_to_point(target)
|
||||||
|
if origin_to_target < join_to_target:
|
||||||
|
# If the origin airfield is closer to the target than the join
|
||||||
|
# point, plan the hold point such that it retreats from the origin
|
||||||
|
# airfield.
|
||||||
|
return join.point_from_heading(target.heading_between_point(origin),
|
||||||
|
self.doctrine.push_distance)
|
||||||
|
|
||||||
|
heading_to_join = origin.heading_between_point(join)
|
||||||
|
hold_point = origin.point_from_heading(heading_to_join,
|
||||||
|
self.doctrine.push_distance)
|
||||||
|
if hold_point.distance_to_point(join) >= self.doctrine.push_distance:
|
||||||
|
# Hold point is between the origin airfield and the join point and
|
||||||
|
# spaced sufficiently.
|
||||||
|
return hold_point
|
||||||
|
|
||||||
|
# The hold point is between the origin airfield and the join point, but
|
||||||
|
# the distance between the hold point and the join point is too short.
|
||||||
|
# Bend the hold point out to extend the distance while maintaining the
|
||||||
|
# minimum distance from the origin airfield to keep the AI flying
|
||||||
|
# properly.
|
||||||
|
origin_to_join = origin.distance_to_point(join)
|
||||||
|
cos_theta = (
|
||||||
|
(self.doctrine.hold_distance ** 2 +
|
||||||
|
origin_to_join ** 2 -
|
||||||
|
self.doctrine.join_distance ** 2) /
|
||||||
|
(2 * self.doctrine.hold_distance * origin_to_join)
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
theta = math.acos(cos_theta)
|
||||||
|
except ValueError:
|
||||||
|
# No solution that maintains hold and join distances. Extend the
|
||||||
|
# hold point away from the target.
|
||||||
|
return origin.point_from_heading(
|
||||||
|
target.heading_between_point(origin),
|
||||||
|
self.doctrine.hold_distance)
|
||||||
|
|
||||||
|
return origin.point_from_heading(heading_to_join - theta,
|
||||||
|
self.doctrine.hold_distance)
|
||||||
|
|
||||||
# TODO: Make a model for the waypoint builder and use that in the UI.
|
# TODO: Make a model for the waypoint builder and use that in the UI.
|
||||||
def generate_ascend_point(self, flight: Flight,
|
def generate_ascend_point(self, flight: Flight,
|
||||||
@ -944,23 +1097,37 @@ class FlightPlanBuilder:
|
|||||||
package=self.package,
|
package=self.package,
|
||||||
flight=flight,
|
flight=flight,
|
||||||
takeoff=builder.takeoff(flight.from_cp),
|
takeoff=builder.takeoff(flight.from_cp),
|
||||||
ascent=builder.ascent(flight.from_cp),
|
|
||||||
hold=builder.hold(self._hold_point(flight)),
|
hold=builder.hold(self._hold_point(flight)),
|
||||||
join=builder.join(self.package.waypoints.join),
|
join=builder.join(self.package.waypoints.join),
|
||||||
ingress=ingress,
|
ingress=ingress,
|
||||||
targets=target_waypoints,
|
targets=target_waypoints,
|
||||||
egress=builder.egress(self.package.waypoints.egress, location),
|
egress=builder.egress(self.package.waypoints.egress, location),
|
||||||
split=builder.split(self.package.waypoints.split),
|
split=builder.split(self.package.waypoints.split),
|
||||||
descent=descent,
|
|
||||||
land=land
|
land=land
|
||||||
)
|
)
|
||||||
|
|
||||||
def _join_point(self, ingress_point: Point) -> Point:
|
def _join_point(self, ingress_point: Point) -> Point:
|
||||||
|
ingress_distance = self._distance_to_package_airfield(ingress_point)
|
||||||
|
if ingress_distance < self.doctrine.join_distance:
|
||||||
|
# If the ingress point is close to the origin, plan the join point
|
||||||
|
# farther back.
|
||||||
|
return ingress_point.point_from_heading(
|
||||||
|
self.package.target.position.heading_between_point(
|
||||||
|
self.package_airfield().position),
|
||||||
|
self.doctrine.join_distance)
|
||||||
heading = self._heading_to_package_airfield(ingress_point)
|
heading = self._heading_to_package_airfield(ingress_point)
|
||||||
return ingress_point.point_from_heading(heading,
|
return ingress_point.point_from_heading(heading,
|
||||||
-self.doctrine.join_distance)
|
-self.doctrine.join_distance)
|
||||||
|
|
||||||
def _split_point(self, egress_point: Point) -> Point:
|
def _split_point(self, egress_point: Point) -> Point:
|
||||||
|
egress_distance = self._distance_to_package_airfield(egress_point)
|
||||||
|
if egress_distance < self.doctrine.split_distance:
|
||||||
|
# If the ingress point is close to the origin, plan the split point
|
||||||
|
# farther back.
|
||||||
|
return egress_point.point_from_heading(
|
||||||
|
self.package.target.position.heading_between_point(
|
||||||
|
self.package_airfield().position),
|
||||||
|
self.doctrine.split_distance)
|
||||||
heading = self._heading_to_package_airfield(egress_point)
|
heading = self._heading_to_package_airfield(egress_point)
|
||||||
return egress_point.point_from_heading(heading,
|
return egress_point.point_from_heading(heading,
|
||||||
-self.doctrine.split_distance)
|
-self.doctrine.split_distance)
|
||||||
@ -983,6 +1150,9 @@ class FlightPlanBuilder:
|
|||||||
def _heading_to_package_airfield(self, point: Point) -> int:
|
def _heading_to_package_airfield(self, point: Point) -> int:
|
||||||
return self.package_airfield().position.heading_between_point(point)
|
return self.package_airfield().position.heading_between_point(point)
|
||||||
|
|
||||||
|
def _distance_to_package_airfield(self, point: Point) -> int:
|
||||||
|
return self.package_airfield().position.distance_to_point(point)
|
||||||
|
|
||||||
def package_airfield(self) -> ControlPoint:
|
def package_airfield(self) -> ControlPoint:
|
||||||
# We'll always have a package, but if this is being planned via the UI
|
# We'll always have a package, but if this is being planned via the UI
|
||||||
# it could be the first flight in the package.
|
# it could be the first flight in the package.
|
||||||
|
|||||||
@ -128,7 +128,14 @@ class TotEstimator:
|
|||||||
f"time for {flight} will be immediate.")
|
f"time for {flight} will be immediate.")
|
||||||
return timedelta()
|
return timedelta()
|
||||||
else:
|
else:
|
||||||
tot = self.package.time_over_target
|
tot_waypoint = flight.flight_plan.tot_waypoint
|
||||||
|
if tot_waypoint is None:
|
||||||
|
tot = self.package.time_over_target
|
||||||
|
else:
|
||||||
|
tot = flight.flight_plan.tot_for_waypoint(tot_waypoint)
|
||||||
|
if tot is None:
|
||||||
|
logging.error(f"TOT waypoint for {flight} has no TOT")
|
||||||
|
tot = self.package.time_over_target
|
||||||
return tot - travel_time - self.HOLD_TIME
|
return tot - travel_time - self.HOLD_TIME
|
||||||
|
|
||||||
def earliest_tot(self) -> timedelta:
|
def earliest_tot(self) -> timedelta:
|
||||||
@ -165,9 +172,13 @@ class TotEstimator:
|
|||||||
# Return 0 so this flight's travel time does not affect the rest
|
# Return 0 so this flight's travel time does not affect the rest
|
||||||
# of the package.
|
# of the package.
|
||||||
return timedelta()
|
return timedelta()
|
||||||
|
# Account for TOT offsets for the flight plan. An offset of -2 minutes
|
||||||
|
# means the flight's TOT is 2 minutes ahead of the package's so it needs
|
||||||
|
# an extra two minutes.
|
||||||
|
offset = -flight.flight_plan.tot_offset
|
||||||
startup = self.estimate_startup(flight)
|
startup = self.estimate_startup(flight)
|
||||||
ground_ops = self.estimate_ground_ops(flight)
|
ground_ops = self.estimate_ground_ops(flight)
|
||||||
return startup + ground_ops + time_to_target
|
return startup + ground_ops + time_to_target + offset
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def estimate_startup(flight: Flight) -> timedelta:
|
def estimate_startup(flight: Flight) -> timedelta:
|
||||||
|
|||||||
@ -326,6 +326,56 @@ class WaypointBuilder:
|
|||||||
return (self.race_track_start(start, altitude),
|
return (self.race_track_start(start, altitude),
|
||||||
self.race_track_end(end, altitude))
|
self.race_track_end(end, altitude))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sweep_start(position: Point, altitude: int) -> FlightWaypoint:
|
||||||
|
"""Creates a sweep start waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: Position of the waypoint.
|
||||||
|
altitude: Altitude of the sweep in meters.
|
||||||
|
"""
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.INGRESS_SWEEP,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "SWEEP START"
|
||||||
|
waypoint.description = "Proceed to the target and engage enemy aircraft"
|
||||||
|
waypoint.pretty_name = "Sweep start"
|
||||||
|
return waypoint
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sweep_end(position: Point, altitude: int) -> FlightWaypoint:
|
||||||
|
"""Creates a sweep end waypoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
position: Position of the waypoint.
|
||||||
|
altitude: Altitude of the sweep in meters.
|
||||||
|
"""
|
||||||
|
waypoint = FlightWaypoint(
|
||||||
|
FlightWaypointType.EGRESS,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
altitude
|
||||||
|
)
|
||||||
|
waypoint.name = "SWEEP END"
|
||||||
|
waypoint.description = "End of sweep"
|
||||||
|
waypoint.pretty_name = "Sweep end"
|
||||||
|
return waypoint
|
||||||
|
|
||||||
|
def sweep(self, start: Point, end: Point,
|
||||||
|
altitude: int) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||||
|
"""Creates two waypoint for a racetrack orbit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start: The beginning of the sweep.
|
||||||
|
end: The end of the sweep.
|
||||||
|
altitude: The sweep altitude.
|
||||||
|
"""
|
||||||
|
return (self.sweep_start(start, altitude),
|
||||||
|
self.sweep_end(end, altitude))
|
||||||
|
|
||||||
def rtb(self,
|
def rtb(self,
|
||||||
arrival: ControlPoint) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
arrival: ControlPoint) -> Tuple[FlightWaypoint, FlightWaypoint]:
|
||||||
"""Creates descent ant landing waypoints for the given control point.
|
"""Creates descent ant landing waypoints for the given control point.
|
||||||
|
|||||||
2
pydcs
2
pydcs
@ -1 +1 @@
|
|||||||
Subproject commit 8e74bfb61b829477d35a80148fc295f2158902dd
|
Subproject commit fa9195fbccbf96775d108a22c13c3ee2375e4c0b
|
||||||
@ -11,7 +11,6 @@ analysis = Analysis(
|
|||||||
('resources', 'resources'),
|
('resources', 'resources'),
|
||||||
('resources/caucasus.p', 'dcs/terrain/'),
|
('resources/caucasus.p', 'dcs/terrain/'),
|
||||||
('resources/nevada.p', 'dcs/terrain/'),
|
('resources/nevada.p', 'dcs/terrain/'),
|
||||||
('buildnumber', './')
|
|
||||||
],
|
],
|
||||||
hookspath=[],
|
hookspath=[],
|
||||||
runtime_hooks=[],
|
runtime_hooks=[],
|
||||||
|
|||||||
@ -18,9 +18,11 @@ class QFlightTypeComboBox(QComboBox):
|
|||||||
"""Combo box for selecting a flight task type."""
|
"""Combo box for selecting a flight task type."""
|
||||||
|
|
||||||
COMMON_ENEMY_MISSIONS = [
|
COMMON_ENEMY_MISSIONS = [
|
||||||
|
FlightType.TARCAP,
|
||||||
FlightType.ESCORT,
|
FlightType.ESCORT,
|
||||||
FlightType.SEAD,
|
FlightType.SEAD,
|
||||||
FlightType.DEAD,
|
FlightType.DEAD,
|
||||||
|
FlightType.SWEEP,
|
||||||
# TODO: FlightType.ELINT,
|
# TODO: FlightType.ELINT,
|
||||||
# TODO: FlightType.EWAR,
|
# TODO: FlightType.EWAR,
|
||||||
# TODO: FlightType.RECON,
|
# TODO: FlightType.RECON,
|
||||||
@ -49,7 +51,6 @@ class QFlightTypeComboBox(QComboBox):
|
|||||||
]
|
]
|
||||||
|
|
||||||
ENEMY_AIRBASE_MISSIONS = [
|
ENEMY_AIRBASE_MISSIONS = [
|
||||||
FlightType.BARCAP,
|
|
||||||
# TODO: FlightType.STRIKE
|
# TODO: FlightType.STRIKE
|
||||||
] + COMMON_ENEMY_MISSIONS
|
] + COMMON_ENEMY_MISSIONS
|
||||||
|
|
||||||
@ -59,13 +60,11 @@ class QFlightTypeComboBox(QComboBox):
|
|||||||
] + COMMON_FRIENDLY_MISSIONS
|
] + COMMON_FRIENDLY_MISSIONS
|
||||||
|
|
||||||
ENEMY_GROUND_OBJECT_MISSIONS = [
|
ENEMY_GROUND_OBJECT_MISSIONS = [
|
||||||
FlightType.BARCAP,
|
|
||||||
FlightType.STRIKE,
|
FlightType.STRIKE,
|
||||||
] + COMMON_ENEMY_MISSIONS
|
] + COMMON_ENEMY_MISSIONS
|
||||||
|
|
||||||
FRONT_LINE_MISSIONS = [
|
FRONT_LINE_MISSIONS = [
|
||||||
FlightType.CAS,
|
FlightType.CAS,
|
||||||
FlightType.TARCAP,
|
|
||||||
# TODO: FlightType.TROOP_TRANSPORT
|
# TODO: FlightType.TROOP_TRANSPORT
|
||||||
# TODO: FlightType.EVAC
|
# TODO: FlightType.EVAC
|
||||||
] + COMMON_ENEMY_MISSIONS
|
] + COMMON_ENEMY_MISSIONS
|
||||||
|
|||||||
@ -149,10 +149,10 @@ class QFlightWaypointTab(QFrame):
|
|||||||
# departs, whereas BARCAP usually isn't part of a strike package and
|
# departs, whereas BARCAP usually isn't part of a strike package and
|
||||||
# has a fixed mission time.
|
# has a fixed mission time.
|
||||||
if task == FlightType.CAP:
|
if task == FlightType.CAP:
|
||||||
if isinstance(self.package.target, FrontLine):
|
if self.package.target.is_friendly(to_player=True):
|
||||||
task = FlightType.TARCAP
|
|
||||||
else:
|
|
||||||
task = FlightType.BARCAP
|
task = FlightType.BARCAP
|
||||||
|
else:
|
||||||
|
task = FlightType.TARCAP
|
||||||
self.flight.flight_type = task
|
self.flight.flight_type = task
|
||||||
self.planner.populate_flight_plan(self.flight)
|
self.planner.populate_flight_plan(self.flight)
|
||||||
self.flight_waypoint_list.update_list()
|
self.flight_waypoint_list.update_list()
|
||||||
|
|||||||
@ -16,6 +16,7 @@ We do not have a single vehicle available to hold our position. The situation i
|
|||||||
{% if frontline.enemy_zero %}
|
{% if frontline.enemy_zero %}
|
||||||
The enemy forces have been crushed, we will be able to make significant progress toward {{ frontline.enemy_base.name }}
|
The enemy forces have been crushed, we will be able to make significant progress toward {{ frontline.enemy_base.name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if not frontline.player_zero %}
|
||||||
{# Pick a random sentence to describe each frontline #}
|
{# Pick a random sentence to describe each frontline #}
|
||||||
{% set fl_sent1 %}There are combats between {{ frontline.player_base.name }} and {{frontline.enemy_base.name}}. {%+ endset %}
|
{% set fl_sent1 %}There are combats between {{ frontline.player_base.name }} and {{frontline.enemy_base.name}}. {%+ endset %}
|
||||||
{% set fl_sent2 %}The war on the ground is still going on between {{frontline.player_base.name}} and {{frontline.enemy_base.name}}. {%+ endset %}
|
{% set fl_sent2 %}The war on the ground is still going on between {{frontline.player_base.name}} and {{frontline.enemy_base.name}}. {%+ endset %}
|
||||||
@ -57,8 +58,9 @@ On this location, our ground forces have been ordered to hold still, and defend
|
|||||||
{# TODO: Write a retreat sentence #}
|
{# TODO: Write a retreat sentence #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endfor %}{% endif %}
|
{%+ endfor %}{% endif %}
|
||||||
|
|
||||||
Your flights:
|
Your flights:
|
||||||
====================
|
====================
|
||||||
|
|||||||
137
resources/campaigns/persian_gulf_full_map.json
Normal file
137
resources/campaigns/persian_gulf_full_map.json
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
"name": "Persian Gulf - Full Map",
|
||||||
|
"theater": "Persian Gulf",
|
||||||
|
"authors": "Plob",
|
||||||
|
"description": "<p>In this scenario, you start at Liwa Airfield, and must work your way north through the whole map.</p>",
|
||||||
|
"player_points": [
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Liwa Airbase",
|
||||||
|
"size": 1000,
|
||||||
|
"importance": 0.2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lha",
|
||||||
|
"id": 1002,
|
||||||
|
"x": -164000,
|
||||||
|
"y": -257000,
|
||||||
|
"captured_invert": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "carrier",
|
||||||
|
"id": 1001,
|
||||||
|
"x": -124000,
|
||||||
|
"y": -303000,
|
||||||
|
"captured_invert": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"enemy_points": [
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Al Ain International Airport",
|
||||||
|
"size": 1000,
|
||||||
|
"importance": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Al Dhafra AB",
|
||||||
|
"size": 2000,
|
||||||
|
"importance": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Al Minhad AB",
|
||||||
|
"size": 1000,
|
||||||
|
"importance": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Ras Al Khaimah",
|
||||||
|
"size": 1000,
|
||||||
|
"importance": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Khasab",
|
||||||
|
"size": 1000,
|
||||||
|
"importance": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Bandar Abbas Intl",
|
||||||
|
"size": 2000,
|
||||||
|
"importance": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Jiroft Airport",
|
||||||
|
"size": 2000,
|
||||||
|
"importance": 1.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Kerman Airport",
|
||||||
|
"size": 2000,
|
||||||
|
"importance": 1.7,
|
||||||
|
"captured_invert": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Lar Airbase",
|
||||||
|
"size": 1000,
|
||||||
|
"importance": 1.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "airbase",
|
||||||
|
"id": "Shiraz International Airport",
|
||||||
|
"size": 2000,
|
||||||
|
"importance": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
[
|
||||||
|
"Al Dhafra AB",
|
||||||
|
"Liwa Airbase"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Al Dhafra AB",
|
||||||
|
"Al Ain International Airport"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Al Ain International Airport",
|
||||||
|
"Al Minhad AB"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Al Dhafra AB",
|
||||||
|
"Al Minhad AB"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Al Minhad AB",
|
||||||
|
"Ras Al Khaimah"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Khasab",
|
||||||
|
"Ras Al Khaimah"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Bandar Abbas Intl",
|
||||||
|
"Lar Airbase"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Shiraz International Airport",
|
||||||
|
"Lar Airbase"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Shiraz International Airport",
|
||||||
|
"Kerman Airport"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Jiroft Airport",
|
||||||
|
"Lar Airbase"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Jiroft Airport",
|
||||||
|
"Kerman Airport"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -5,28 +5,37 @@ local unitPayloads = {
|
|||||||
["name"] = "CAS",
|
["name"] = "CAS",
|
||||||
["pylons"] = {
|
["pylons"] = {
|
||||||
[1] = {
|
[1] = {
|
||||||
["CLSID"] = "{ARAKM70BHE}",
|
["CLSID"] = "{RB75}",
|
||||||
["num"] = 3,
|
["num"] = 5,
|
||||||
},
|
},
|
||||||
[2] = {
|
[2] = {
|
||||||
["CLSID"] = "{ARAKM70BHE}",
|
["CLSID"] = "{RB75}",
|
||||||
["num"] = 2,
|
["num"] = 3,
|
||||||
},
|
},
|
||||||
[3] = {
|
[3] = {
|
||||||
|
["CLSID"] = "{RB75}",
|
||||||
|
["num"] = 2,
|
||||||
|
},
|
||||||
|
[4] = {
|
||||||
|
["CLSID"] = "{RB75}",
|
||||||
|
["num"] = 6,
|
||||||
|
},
|
||||||
|
[5] = {
|
||||||
|
["CLSID"] = "{Robot24J}",
|
||||||
|
["num"] = 1,
|
||||||
|
},
|
||||||
|
[6] = {
|
||||||
|
["CLSID"] = "{Robot24J}",
|
||||||
|
["num"] = 7,
|
||||||
|
},
|
||||||
|
[7] = {
|
||||||
["CLSID"] = "{VIGGEN_X-TANK}",
|
["CLSID"] = "{VIGGEN_X-TANK}",
|
||||||
["num"] = 4,
|
["num"] = 4,
|
||||||
},
|
},
|
||||||
[4] = {
|
|
||||||
["CLSID"] = "{ARAKM70BHE}",
|
|
||||||
["num"] = 5,
|
|
||||||
},
|
|
||||||
[5] = {
|
|
||||||
["CLSID"] = "{ARAKM70BHE}",
|
|
||||||
["num"] = 6,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
["tasks"] = {
|
["tasks"] = {
|
||||||
[1] = 31,
|
[1] = 32,
|
||||||
|
[2] = 31,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[2] = {
|
[2] = {
|
||||||
|
|||||||
84
resources/factions/france_2005_frenchpack.json
Normal file
84
resources/factions/france_2005_frenchpack.json
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"country": "France",
|
||||||
|
"name": "France 2005 (Frenchpack)",
|
||||||
|
"authors": "HerrTom",
|
||||||
|
"description": "<p>French equipment using the Frenchpack, but without the Rafale mod.</p>",
|
||||||
|
"aircrafts": [
|
||||||
|
"M_2000C",
|
||||||
|
"Mirage_2000_5",
|
||||||
|
"SA342M",
|
||||||
|
"SA342L",
|
||||||
|
"SA342Mistral"
|
||||||
|
],
|
||||||
|
"awacs": [
|
||||||
|
"E_3A"
|
||||||
|
],
|
||||||
|
"tankers": [
|
||||||
|
"KC_135",
|
||||||
|
"KC130"
|
||||||
|
],
|
||||||
|
"frontline_units": [
|
||||||
|
"AMX_10RCR",
|
||||||
|
"AMX_10RCR_SEPAR",
|
||||||
|
"ERC_90",
|
||||||
|
"TRM_2000_PAMELA",
|
||||||
|
"VAB__50",
|
||||||
|
"VAB_MEPHISTO",
|
||||||
|
"VAB_T20_13",
|
||||||
|
"VAB_T20_13",
|
||||||
|
"VBL__50",
|
||||||
|
"VBL_AANF1",
|
||||||
|
"VBAE_CRAB",
|
||||||
|
"VBAE_CRAB_MMP",
|
||||||
|
"AMX_30B2",
|
||||||
|
"Leclerc_Serie_XXI"
|
||||||
|
],
|
||||||
|
"artillery_units": [
|
||||||
|
"MLRS_M270",
|
||||||
|
"SPH_M109_Paladin"
|
||||||
|
],
|
||||||
|
"logistics_units": [
|
||||||
|
"Transport_M818"
|
||||||
|
],
|
||||||
|
"infantry_units": [
|
||||||
|
"Infantry_M4",
|
||||||
|
"Soldier_M249",
|
||||||
|
"Stinger_MANPADS"
|
||||||
|
],
|
||||||
|
"shorads": [
|
||||||
|
"HQ7Generator",
|
||||||
|
"RolandGenerator"
|
||||||
|
],
|
||||||
|
"sams": [
|
||||||
|
"RolandGenerator",
|
||||||
|
"HawkGenerator"
|
||||||
|
],
|
||||||
|
"aircraft_carrier": [
|
||||||
|
"CVN_74_John_C__Stennis"
|
||||||
|
],
|
||||||
|
"helicopter_carrier": [
|
||||||
|
"LHA_1_Tarawa"
|
||||||
|
],
|
||||||
|
"destroyers": [
|
||||||
|
"USS_Arleigh_Burke_IIa"
|
||||||
|
],
|
||||||
|
"cruisers": [
|
||||||
|
"Ticonderoga_class"
|
||||||
|
],
|
||||||
|
"requirements": {
|
||||||
|
"frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974"
|
||||||
|
},
|
||||||
|
"carrier_names": [
|
||||||
|
"L9013 Mistral",
|
||||||
|
"L9014 Tonerre",
|
||||||
|
"L9015 Dixmude"
|
||||||
|
],
|
||||||
|
"helicopter_carrier_names": [
|
||||||
|
"Jeanne d'Arc"
|
||||||
|
],
|
||||||
|
"navy_generators": [
|
||||||
|
"ArleighBurkeGroupGenerator"
|
||||||
|
],
|
||||||
|
"has_jtac": true,
|
||||||
|
"jtac_unit": "MQ_9_Reaper"
|
||||||
|
}
|
||||||
46
resources/factions/georgia_2008.json
Normal file
46
resources/factions/georgia_2008.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"country": "Georgia",
|
||||||
|
"name": "Georgia 2008",
|
||||||
|
"authors": "HerrTom",
|
||||||
|
"description": "<p>A faction that represents Georgia during the South Ossetian War. They will have a lot more aircraft than historically, and no real A2A capability.</p>",
|
||||||
|
"aircrafts": [
|
||||||
|
"L_39ZA",
|
||||||
|
"Su_25",
|
||||||
|
"Mi_8MT",
|
||||||
|
"Mi_24V",
|
||||||
|
"UH_1H"
|
||||||
|
],
|
||||||
|
"frontline_units": [
|
||||||
|
"APC_BTR_80",
|
||||||
|
"APC_MTLB",
|
||||||
|
"APC_Cobra",
|
||||||
|
"IFV_BMP_1",
|
||||||
|
"IFV_BMP_2",
|
||||||
|
"MBT_T_72B",
|
||||||
|
"MBT_T_55"
|
||||||
|
],
|
||||||
|
"artillery_units": [
|
||||||
|
"MLRS_BM21_Grad",
|
||||||
|
"SPH_2S1_Gvozdika",
|
||||||
|
"SPH_2S3_Akatsia"
|
||||||
|
],
|
||||||
|
"logistics_units": [
|
||||||
|
"Transport_Ural_375",
|
||||||
|
"Transport_UAZ_469"
|
||||||
|
],
|
||||||
|
"infantry_units": [
|
||||||
|
"Paratrooper_AKS",
|
||||||
|
"Paratrooper_RPG_16"
|
||||||
|
],
|
||||||
|
"shorads": [
|
||||||
|
"SA13Generator",
|
||||||
|
"SA8Generator"
|
||||||
|
],
|
||||||
|
"sams": [
|
||||||
|
"SA6Generator",
|
||||||
|
"SA11Generator"
|
||||||
|
],
|
||||||
|
"requirements": {},
|
||||||
|
"has_jtac": true,
|
||||||
|
"jtac_unit": "MQ_9_Reaper"
|
||||||
|
}
|
||||||
73
resources/factions/usn_1985.json
Normal file
73
resources/factions/usn_1985.json
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"country": "USA",
|
||||||
|
"name": "US Navy 1985",
|
||||||
|
"authors": "HerrTom",
|
||||||
|
"description": "<p>Highway to the Danger Zone! For Tomcat lovers.</p>",
|
||||||
|
"aircrafts": [
|
||||||
|
"F_4E",
|
||||||
|
"F_14B",
|
||||||
|
"S_3B",
|
||||||
|
"UH_1H",
|
||||||
|
"AH_1W"
|
||||||
|
],
|
||||||
|
"awacs": [
|
||||||
|
"E_3A"
|
||||||
|
],
|
||||||
|
"tankers": [
|
||||||
|
"S_3B_Tanker"
|
||||||
|
],
|
||||||
|
"frontline_units": [
|
||||||
|
"MBT_M60A3_Patton",
|
||||||
|
"APC_M113",
|
||||||
|
"APC_M1025_HMMWV"
|
||||||
|
],
|
||||||
|
"artillery_units": [
|
||||||
|
"SPH_M109_Paladin",
|
||||||
|
"MLRS_M270"
|
||||||
|
],
|
||||||
|
"logistics_units": [
|
||||||
|
"Transport_M818"
|
||||||
|
],
|
||||||
|
"infantry_units": [
|
||||||
|
"Infantry_M4",
|
||||||
|
"Soldier_M249"
|
||||||
|
],
|
||||||
|
"shorads": [
|
||||||
|
"VulcanGenerator",
|
||||||
|
"ChaparralGenerator"
|
||||||
|
],
|
||||||
|
"sams": [
|
||||||
|
"HawkGenerator",
|
||||||
|
"ChaparralGenerator"
|
||||||
|
],
|
||||||
|
"aircraft_carrier": [
|
||||||
|
"CVN_74_John_C__Stennis"
|
||||||
|
],
|
||||||
|
"helicopter_carrier": [
|
||||||
|
"LHA_1_Tarawa"
|
||||||
|
],
|
||||||
|
"destroyers": [
|
||||||
|
"Oliver_Hazzard_Perry_class"
|
||||||
|
],
|
||||||
|
"cruisers": [
|
||||||
|
"Ticonderoga_class"
|
||||||
|
],
|
||||||
|
"carrier_names": [
|
||||||
|
"CVN-71 Theodore Roosevelt",
|
||||||
|
"CVN-72 Abraham Lincoln",
|
||||||
|
"CVN-73 George Washington",
|
||||||
|
"CVN-74 John C. Stennis"
|
||||||
|
],
|
||||||
|
"helicopter_carrier_names": [
|
||||||
|
"LHA-1 Tarawa",
|
||||||
|
"LHA-2 Saipan",
|
||||||
|
"LHA-3 Belleau Wood",
|
||||||
|
"LHA-4 Nassau",
|
||||||
|
"LHA-5 Peleliu"
|
||||||
|
],
|
||||||
|
"navy_generators": [
|
||||||
|
"OliverHazardPerryGroupGenerator"
|
||||||
|
],
|
||||||
|
"requirements": {},
|
||||||
|
"doctrine": "coldwar"
|
||||||
|
}
|
||||||
@ -350,6 +350,10 @@ class FrontLine(MissionTarget):
|
|||||||
self._build_segments()
|
self._build_segments()
|
||||||
self.name = f"Front line {control_point_a}/{control_point_b}"
|
self.name = f"Front line {control_point_a}/{control_point_b}"
|
||||||
|
|
||||||
|
def is_friendly(self, to_player: bool) -> bool:
|
||||||
|
"""Returns True if the objective is in friendly territory."""
|
||||||
|
raise False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def position(self):
|
def position(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
"""Only here to keep compatibility for save games generated in version 2.2.0"""
|
"""Only here to keep compatibility for save games generated in version 2.2.0"""
|
||||||
from theater.conflicttheater import *
|
from theater.conflicttheater import *
|
||||||
|
|||||||
@ -17,3 +17,7 @@ class MissionTarget:
|
|||||||
def distance_to(self, other: MissionTarget) -> int:
|
def distance_to(self, other: MissionTarget) -> int:
|
||||||
"""Computes the distance to the given mission target."""
|
"""Computes the distance to the given mission target."""
|
||||||
return self.position.distance_to_point(other.position)
|
return self.position.distance_to_point(other.position)
|
||||||
|
|
||||||
|
def is_friendly(self, to_player: bool) -> bool:
|
||||||
|
"""Returns True if the objective is in friendly territory."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
@ -113,6 +113,9 @@ class TheaterGroundObject(MissionTarget):
|
|||||||
def faction_color(self) -> str:
|
def faction_color(self) -> str:
|
||||||
return "BLUE" if self.control_point.captured else "RED"
|
return "BLUE" if self.control_point.captured else "RED"
|
||||||
|
|
||||||
|
def is_friendly(self, to_player: bool) -> bool:
|
||||||
|
return not self.control_point.is_friendly(to_player)
|
||||||
|
|
||||||
|
|
||||||
class BuildingGroundObject(TheaterGroundObject):
|
class BuildingGroundObject(TheaterGroundObject):
|
||||||
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user