mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
68 Commits
develop-9.
...
update_rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef8eeeb1f1 | ||
|
|
e42a7b9a59 | ||
|
|
24c9ca5d12 | ||
|
|
678dd58e7d | ||
|
|
d6879040ad | ||
|
|
d0be4b6a29 | ||
|
|
0d71372377 | ||
|
|
86de5df21e | ||
|
|
d1daa0521c | ||
|
|
ffa88688dc | ||
|
|
5ecaeca910 | ||
|
|
e1d443b697 | ||
|
|
5af4e56f30 | ||
|
|
5b858886c0 | ||
|
|
c695e7724a | ||
|
|
2fb22e4e17 | ||
|
|
ce073c24bc | ||
|
|
8de053cc7d | ||
|
|
fe6e49b22b | ||
|
|
3653dc8cbd | ||
|
|
d2b5eea0de | ||
|
|
211ec86e2e | ||
|
|
03caddc1b4 | ||
|
|
3f7618d75d | ||
|
|
dcf23c655d | ||
|
|
ef69275f34 | ||
|
|
167cea08f6 | ||
|
|
48ae55bdc2 | ||
|
|
ff2bd3f815 | ||
|
|
ba5d0bed4d | ||
|
|
4a07b8a2d8 | ||
|
|
1efce862fb | ||
|
|
80cb440e7d | ||
|
|
e970c281e8 | ||
|
|
b863e2fb83 | ||
|
|
3007a96343 | ||
|
|
463981f4bf | ||
|
|
816d1cd787 | ||
|
|
4631ee0d74 | ||
|
|
a213215c3f | ||
|
|
b014f2e543 | ||
|
|
f3d3c5f43a | ||
|
|
5ee3afeddb | ||
|
|
88591fd18c | ||
|
|
f5573cfc19 | ||
|
|
f7141a9882 | ||
|
|
a599b503f8 | ||
|
|
6c4b8c81ee | ||
|
|
2447cc156d | ||
|
|
28954d05eb | ||
|
|
65eb10639b | ||
|
|
7bc35ef7f4 | ||
|
|
46766ecbd4 | ||
|
|
3469d08461 | ||
|
|
28d959bba0 | ||
|
|
b99eb49dcf | ||
|
|
c6f812238c | ||
|
|
cc5b5fa3bb | ||
|
|
5271b3d32c | ||
|
|
8f4192edc3 | ||
|
|
183d6df8bf | ||
|
|
a825651330 | ||
|
|
f3c02816fc | ||
|
|
c4e2e45650 | ||
|
|
6613642517 | ||
|
|
b73ca2c62e | ||
|
|
8abd3c7cf9 | ||
|
|
f8a72d8f22 |
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -31,7 +31,7 @@ body:
|
||||
If the bug was found in a development build, select "Development build"
|
||||
and provide a link to the build in the field below.
|
||||
options:
|
||||
- 8.1.0
|
||||
- 10.0.0
|
||||
- Development build
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/new-game-bug.yml
vendored
2
.github/ISSUE_TEMPLATE/new-game-bug.yml
vendored
@@ -39,7 +39,7 @@ body:
|
||||
If the bug was found in a development build, select "Development build"
|
||||
and provide a link to the build in the field below.
|
||||
options:
|
||||
- 8.1.0
|
||||
- 10.0.0
|
||||
- Development build
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: psf/black@stable
|
||||
with:
|
||||
version: ~=22.12
|
||||
version: ~=23.11
|
||||
src: "."
|
||||
options: "--check"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.12.0
|
||||
rev: 23.11.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
(Github Readme Banner and Splash screen Artwork by Andriy Dankovych, CC BY-SA 4.0)
|
||||
|
||||
[](https://patreon.com/khopa)
|
||||
[](https://patreon.com/dcsliberation)
|
||||
|
||||
[](https://github.com/dcs-liberation/dcs_liberation/releases)
|
||||
|
||||
|
||||
27
changelog.md
27
changelog.md
@@ -1,3 +1,28 @@
|
||||
# 11.0.0
|
||||
|
||||
Saves from 10.x are not compatible with 11.0.0.
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.9.3.51704 Open Beta.
|
||||
|
||||
## Fixes
|
||||
|
||||
# 10.0.0
|
||||
|
||||
Saves from 9.x are not compatible with 10.0.0.
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.9.2.49629 Open Beta. (F-15E JDAM and JSOW, F-16 AIM-9P, updated Falklands and Normandy airfields).
|
||||
* **[UI]** Improved the description of "runway" state for FARPs, FOBs, carriers, and off-map spawns.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Flight Planning]** Aircraft from even numbered flights will no longer become inaccessible when canceling a draft package.
|
||||
* **[UI]** Flight members in the loadout menu are now numbered starting from 1 instead of 0.
|
||||
* **[UI]** Flight plan paths are now drawn behind all other map elements, fixing rare cases where they could prevent other UI elements from being clickable.
|
||||
|
||||
# 9.0.0
|
||||
|
||||
Saves from 8.x are not compatible with 9.0.0.
|
||||
@@ -46,7 +71,7 @@ Saves from 8.x are not compatible with 9.0.0.
|
||||
* **[UI]** Fixed deleting waypoints in custom flight plans deleting the wrong waypoint.
|
||||
* **[UI]** Fixed flight properties UI to support F-15E S4+ laser codes.
|
||||
* **[UI]** In unit transfer dialog, only list control points that are reachable from the control point units are being transferred from.
|
||||
* **[UI]** Fixed UI bug where altering an "ahead of package" TOT offset would change the offset back to a "behind pacakge" offset.
|
||||
* **[UI]** Fixed UI bug where altering an "ahead of package" TOT offset would change the offset back to a "behind package" offset.
|
||||
* **[UI]** Fixed bug where changing TOT offsets could result in flight startup times that are in the past.
|
||||
* **[UI]** Fixed odd spacing of the finance window when there were not enough items to fill the page.
|
||||
* **[UI]** Fixed regression where waypoint altitude changes in the waypoint list screen are applied to the wrong waypoint.
|
||||
|
||||
12
client/package-lock.json
generated
12
client/package-lock.json
generated
@@ -50,9 +50,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
|
||||
"integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg=="
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz",
|
||||
"integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw=="
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
"version": "2.1.2",
|
||||
@@ -21339,9 +21339,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
|
||||
"integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg=="
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz",
|
||||
"integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw=="
|
||||
},
|
||||
"@ampproject/remapping": {
|
||||
"version": "2.1.2",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Flight } from "../../api/liberationApi";
|
||||
import { useGetCommitBoundaryForFlightQuery } from "../../api/liberationApi";
|
||||
import WaypointMarker from "../waypointmarker";
|
||||
import { ReactElement } from "react";
|
||||
import { Polyline as LPolyline } from "leaflet";
|
||||
import { ReactElement, useEffect, useRef } from "react";
|
||||
import { Polyline } from "react-leaflet";
|
||||
|
||||
const BLUE_PATH = "#0084ff";
|
||||
@@ -27,16 +28,41 @@ const pathColor = (props: FlightPlanProps) => {
|
||||
function FlightPlanPath(props: FlightPlanProps) {
|
||||
const color = pathColor(props);
|
||||
const waypoints = props.flight.waypoints;
|
||||
|
||||
const polylineRef = useRef<LPolyline | null>(null);
|
||||
|
||||
// Flight paths should be drawn under everything else. There seems to be an
|
||||
// issue where `interactive: false` doesn't do as its told (there's nuance,
|
||||
// see the bug for details). It looks better if we draw the other elements on
|
||||
// top of the flight plans anyway, so just push the flight plan to the back.
|
||||
//
|
||||
// https://github.com/dcs-liberation/dcs_liberation/issues/3295
|
||||
//
|
||||
// It's not possible to z-index a polyline (and leaflet says it never will be,
|
||||
// because this is a limitation of SVG, not leaflet:
|
||||
// https://github.com/Leaflet/Leaflet/issues/185), so we need to use
|
||||
// bringToBack() to push the flight paths to the back of the drawing once
|
||||
// they've been added to the map. They'll still draw on top of the map, but
|
||||
// behind everything than was added before them. Anything added after always
|
||||
// goes on top.
|
||||
useEffect(() => {
|
||||
if (!props.selected) {
|
||||
polylineRef.current?.bringToBack();
|
||||
}
|
||||
});
|
||||
|
||||
if (waypoints == null) {
|
||||
return <></>;
|
||||
}
|
||||
const points = waypoints
|
||||
.filter((waypoint) => waypoint.include_in_path)
|
||||
.map((waypoint) => waypoint.position);
|
||||
|
||||
return (
|
||||
<Polyline
|
||||
positions={points}
|
||||
pathOptions={{ color: color, interactive: false }}
|
||||
ref={polylineRef}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,8 +95,12 @@ describe("FlightPlansLayer", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(2);
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
|
||||
// For some reason passing ref to PolyLine causes it and its group to be
|
||||
// redrawn, so these numbers don't match what you'd expect from the test.
|
||||
// It probably needs to be rewritten without mocks.
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(3);
|
||||
expect(mockLayerGroup).toBeCalledTimes(2);
|
||||
});
|
||||
it("are not drawn if wrong coalition", () => {
|
||||
renderWithProviders(<FlightPlansLayer blue={true} />, {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
project = "DCS Liberation"
|
||||
copyright = "2023, DCS Liberation Team"
|
||||
author = "DCS Liberation Team"
|
||||
release = "9.0.0"
|
||||
release = "11.0.0"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
@@ -88,7 +88,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
||||
raise PlanningError("Air assault is only usable by helicopters")
|
||||
assert self.package.waypoints is not None
|
||||
|
||||
altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude
|
||||
altitude = self.doctrine.helicopter.air_assault_nav_altitude
|
||||
altitude_is_agl = self.flight.is_helo
|
||||
|
||||
builder = WaypointBuilder(self.flight, self.coalition)
|
||||
|
||||
@@ -19,7 +19,7 @@ class BarCapFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
||||
|
||||
@property
|
||||
def patrol_duration(self) -> timedelta:
|
||||
return self.flight.coalition.doctrine.cap_duration
|
||||
return self.flight.coalition.doctrine.cap.duration
|
||||
|
||||
@property
|
||||
def patrol_speed(self) -> Speed:
|
||||
@@ -29,7 +29,7 @@ class BarCapFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
||||
|
||||
@property
|
||||
def engagement_distance(self) -> Distance:
|
||||
return self.flight.coalition.doctrine.cap_engagement_range
|
||||
return self.flight.coalition.doctrine.cap.engagement_range
|
||||
|
||||
|
||||
class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
|
||||
@@ -44,8 +44,8 @@ class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
|
||||
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
||||
randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000)
|
||||
patrol_alt = max(
|
||||
self.doctrine.min_patrol_altitude,
|
||||
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
||||
self.doctrine.cap.min_patrol_altitude,
|
||||
min(self.doctrine.cap.max_patrol_altitude, randomized_alt),
|
||||
)
|
||||
|
||||
builder = WaypointBuilder(self.flight, self.coalition)
|
||||
|
||||
@@ -90,10 +90,10 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
||||
# buffer.
|
||||
distance_to_no_fly = (
|
||||
meters(position.distance(self.threat_zones.all))
|
||||
- self.doctrine.cap_engagement_range
|
||||
- self.doctrine.cap.engagement_range
|
||||
- nautical_miles(5)
|
||||
)
|
||||
max_track_length = self.doctrine.cap_max_track_length
|
||||
max_track_length = self.doctrine.cap.max_track_length
|
||||
else:
|
||||
# Other race tracks (TARCAPs, currently) just try to keep some
|
||||
# distance from the nearest enemy airbase, but since they are by
|
||||
@@ -108,15 +108,15 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
||||
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
||||
|
||||
# TARCAPs fly short racetracks because they need to react faster.
|
||||
max_track_length = self.doctrine.cap_min_track_length + 0.3 * (
|
||||
self.doctrine.cap_max_track_length - self.doctrine.cap_min_track_length
|
||||
max_track_length = self.doctrine.cap.min_track_length + 0.3 * (
|
||||
self.doctrine.cap.max_track_length - self.doctrine.cap.min_track_length
|
||||
)
|
||||
|
||||
min_cap_distance = min(
|
||||
self.doctrine.cap_min_distance_from_cp, distance_to_no_fly
|
||||
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
|
||||
self.doctrine.cap.max_distance_from_cp, distance_to_no_fly
|
||||
)
|
||||
|
||||
end = location.position.point_from_heading(
|
||||
@@ -125,7 +125,7 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
||||
)
|
||||
|
||||
track_length = random.randint(
|
||||
int(self.doctrine.cap_min_track_length.meters),
|
||||
int(self.doctrine.cap.min_track_length.meters),
|
||||
int(max_track_length.meters),
|
||||
)
|
||||
start = end.point_from_heading(heading.opposite.degrees, track_length)
|
||||
|
||||
@@ -44,7 +44,7 @@ class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
|
||||
|
||||
@property
|
||||
def patrol_duration(self) -> timedelta:
|
||||
return self.flight.coalition.doctrine.cas_duration
|
||||
return self.flight.coalition.doctrine.cas.duration
|
||||
|
||||
@property
|
||||
def patrol_speed(self) -> Speed:
|
||||
@@ -96,7 +96,7 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
|
||||
builder = WaypointBuilder(self.flight, self.coalition)
|
||||
|
||||
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
||||
patrol_altitude = self.doctrine.ingress_altitude if not is_helo else meters(50)
|
||||
patrol_altitude = self.doctrine.resolve_combat_altitude(is_helo)
|
||||
use_agl_patrol_altitude = is_helo
|
||||
|
||||
ip_solver = IpSolver(
|
||||
|
||||
@@ -33,7 +33,7 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
||||
departure=builder.takeoff(self.flight.departure),
|
||||
hold=hold,
|
||||
nav_to=builder.nav_path(
|
||||
hold.position, join.position, self.doctrine.ingress_altitude
|
||||
hold.position, join.position, self.doctrine.combat_altitude
|
||||
),
|
||||
join=join,
|
||||
ingress=ingress,
|
||||
@@ -43,7 +43,7 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
||||
nav_from=builder.nav_path(
|
||||
refuel.position,
|
||||
self.flight.arrival.position,
|
||||
self.doctrine.ingress_altitude,
|
||||
self.doctrine.combat_altitude,
|
||||
),
|
||||
arrival=builder.land(self.flight.arrival),
|
||||
divert=builder.divert(self.flight.divert),
|
||||
|
||||
@@ -163,7 +163,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
||||
departure=builder.takeoff(self.flight.departure),
|
||||
hold=hold,
|
||||
nav_to=builder.nav_path(
|
||||
hold.position, join.position, self.doctrine.ingress_altitude
|
||||
hold.position, join.position, self.doctrine.combat_altitude
|
||||
),
|
||||
join=join,
|
||||
ingress=ingress,
|
||||
@@ -173,7 +173,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
||||
nav_from=builder.nav_path(
|
||||
refuel.position,
|
||||
self.flight.arrival.position,
|
||||
self.doctrine.ingress_altitude,
|
||||
self.doctrine.combat_altitude,
|
||||
),
|
||||
arrival=builder.land(self.flight.arrival),
|
||||
divert=builder.divert(self.flight.divert),
|
||||
|
||||
@@ -65,7 +65,6 @@ class RecoveryTankerFlightPlan(StandardFlightPlan[RecoveryTankerLayout]):
|
||||
|
||||
class Builder(IBuilder[RecoveryTankerFlightPlan, RecoveryTankerLayout]):
|
||||
def layout(self) -> RecoveryTankerLayout:
|
||||
|
||||
builder = WaypointBuilder(self.flight, self.coalition)
|
||||
|
||||
# TODO: Propagate the ship position to the Tanker's TOT,
|
||||
|
||||
@@ -114,11 +114,11 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
|
||||
self.package.waypoints.join.heading_between_point(target)
|
||||
)
|
||||
start_pos = target.point_from_heading(
|
||||
heading.degrees, -self.doctrine.sweep_distance.meters
|
||||
heading.degrees, -self.doctrine.sweep.distance.meters
|
||||
)
|
||||
|
||||
builder = WaypointBuilder(self.flight, self.coalition)
|
||||
start, end = builder.sweep(start_pos, target, self.doctrine.ingress_altitude)
|
||||
start, end = builder.sweep(start_pos, target, self.doctrine.combat_altitude)
|
||||
|
||||
hold = builder.hold(self._hold_point())
|
||||
|
||||
@@ -126,12 +126,12 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
|
||||
departure=builder.takeoff(self.flight.departure),
|
||||
hold=hold,
|
||||
nav_to=builder.nav_path(
|
||||
hold.position, start.position, self.doctrine.ingress_altitude
|
||||
hold.position, start.position, self.doctrine.combat_altitude
|
||||
),
|
||||
nav_from=builder.nav_path(
|
||||
end.position,
|
||||
self.flight.arrival.position,
|
||||
self.doctrine.ingress_altitude,
|
||||
self.doctrine.combat_altitude,
|
||||
),
|
||||
sweep_start=start,
|
||||
sweep_end=end,
|
||||
|
||||
@@ -40,7 +40,7 @@ class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
|
||||
# flights in the package that have requested escort. If the package
|
||||
# requests an escort the CAP self.flight will remain on station for the
|
||||
# duration of the escorted mission, or until it is winchester/bingo.
|
||||
return self.flight.coalition.doctrine.cap_duration
|
||||
return self.flight.coalition.doctrine.cap.duration
|
||||
|
||||
@property
|
||||
def patrol_speed(self) -> Speed:
|
||||
@@ -50,7 +50,7 @@ class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
|
||||
|
||||
@property
|
||||
def engagement_distance(self) -> Distance:
|
||||
return self.flight.coalition.doctrine.cap_engagement_range
|
||||
return self.flight.coalition.doctrine.cap.engagement_range
|
||||
|
||||
@staticmethod
|
||||
def builder_type() -> Type[Builder]:
|
||||
@@ -90,8 +90,8 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
|
||||
preferred_alt = self.flight.unit_type.preferred_patrol_altitude
|
||||
randomized_alt = preferred_alt + feet(random.randint(-2, 1) * 1000)
|
||||
patrol_alt = max(
|
||||
self.doctrine.min_patrol_altitude,
|
||||
min(self.doctrine.max_patrol_altitude, randomized_alt),
|
||||
self.doctrine.cap.min_patrol_altitude,
|
||||
min(self.doctrine.cap.max_patrol_altitude, randomized_alt),
|
||||
)
|
||||
|
||||
builder = WaypointBuilder(self.flight, self.coalition)
|
||||
|
||||
@@ -72,7 +72,7 @@ class WaypointBuilder:
|
||||
"NAV",
|
||||
FlightWaypointType.NAV,
|
||||
position,
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
self.doctrine.resolve_rendezvous_altitude(self.is_helo),
|
||||
description="Enter theater",
|
||||
pretty_name="Enter theater",
|
||||
)
|
||||
@@ -99,7 +99,7 @@ class WaypointBuilder:
|
||||
"NAV",
|
||||
FlightWaypointType.NAV,
|
||||
position,
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
self.doctrine.resolve_rendezvous_altitude(self.is_helo),
|
||||
description="Exit theater",
|
||||
pretty_name="Exit theater",
|
||||
)
|
||||
@@ -127,10 +127,7 @@ class WaypointBuilder:
|
||||
position = divert.position
|
||||
altitude_type: AltitudeReference
|
||||
if isinstance(divert, OffMapSpawn):
|
||||
if self.is_helo:
|
||||
altitude = meters(500)
|
||||
else:
|
||||
altitude = self.doctrine.rendezvous_altitude
|
||||
altitude = self.doctrine.resolve_rendezvous_altitude(self.is_helo)
|
||||
altitude_type = "BARO"
|
||||
else:
|
||||
altitude = meters(0)
|
||||
@@ -168,10 +165,7 @@ class WaypointBuilder:
|
||||
"HOLD",
|
||||
FlightWaypointType.LOITER,
|
||||
position,
|
||||
# Bug: DCS only accepts MSL altitudes for the orbit task and 500 meters is
|
||||
# below the ground for most if not all of NTTR (and lots of places in other
|
||||
# maps).
|
||||
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude,
|
||||
self.doctrine.resolve_rendezvous_altitude(self.is_helo),
|
||||
alt_type,
|
||||
description="Wait until push time",
|
||||
pretty_name="Hold",
|
||||
@@ -186,7 +180,7 @@ class WaypointBuilder:
|
||||
"JOIN",
|
||||
FlightWaypointType.JOIN,
|
||||
position,
|
||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||
alt_type,
|
||||
description="Rendezvous with package",
|
||||
pretty_name="Join",
|
||||
@@ -201,7 +195,7 @@ class WaypointBuilder:
|
||||
"REFUEL",
|
||||
FlightWaypointType.REFUEL,
|
||||
position,
|
||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||
alt_type,
|
||||
description="Refuel from tanker",
|
||||
pretty_name="Refuel",
|
||||
@@ -229,7 +223,7 @@ class WaypointBuilder:
|
||||
"SPLIT",
|
||||
FlightWaypointType.SPLIT,
|
||||
position,
|
||||
meters(80) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||
alt_type,
|
||||
description="Depart from package",
|
||||
pretty_name="Split",
|
||||
@@ -249,7 +243,7 @@ class WaypointBuilder:
|
||||
"INGRESS",
|
||||
ingress_type,
|
||||
position,
|
||||
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||
alt_type,
|
||||
description=f"INGRESS on {objective.name}",
|
||||
pretty_name=f"INGRESS on {objective.name}",
|
||||
@@ -294,7 +288,7 @@ class WaypointBuilder:
|
||||
f"SEAD on {target.name}",
|
||||
target,
|
||||
flyover=True,
|
||||
altitude=self.doctrine.ingress_altitude,
|
||||
altitude=self.doctrine.combat_altitude,
|
||||
alt_type="BARO",
|
||||
)
|
||||
|
||||
@@ -484,7 +478,7 @@ class WaypointBuilder:
|
||||
"TARGET",
|
||||
FlightWaypointType.TARGET_GROUP_LOC,
|
||||
target.position,
|
||||
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
|
||||
self.doctrine.resolve_combat_altitude(self.is_helo),
|
||||
alt_type,
|
||||
description="Escort the package",
|
||||
pretty_name="Target area",
|
||||
|
||||
@@ -18,6 +18,9 @@ class GroundSpeed:
|
||||
# on fuel, but mission speed will be fast enough to keep the flight
|
||||
# safer.
|
||||
|
||||
if flight.squadron.aircraft.cruise_speed is not None:
|
||||
return mach(flight.squadron.aircraft.cruise_speed.mach(), altitude)
|
||||
|
||||
# DCS's max speed is in kph at 0 MSL.
|
||||
max_speed = flight.unit_type.max_speed
|
||||
if max_speed > SPEED_OF_SOUND_AT_SEA_LEVEL:
|
||||
|
||||
@@ -29,7 +29,6 @@ class DefaultSquadronAssigner:
|
||||
self.coalition.player
|
||||
):
|
||||
for squadron_config in self.config.by_location[control_point]:
|
||||
|
||||
squadron_def = self.override_squadron_defaults(
|
||||
self.find_squadron_for(squadron_config, control_point),
|
||||
squadron_config,
|
||||
@@ -162,7 +161,6 @@ class DefaultSquadronAssigner:
|
||||
def override_squadron_defaults(
|
||||
squadron_def: Optional[SquadronDef], config: SquadronConfig
|
||||
) -> Optional[SquadronDef]:
|
||||
|
||||
if squadron_def is None:
|
||||
return None
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from game.utils import meters, nautical_miles
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from game.transfers import CargoShip, Convoy
|
||||
from game.threatzones import ThreatZones
|
||||
|
||||
MissionTargetType = TypeVar("MissionTargetType", bound=MissionTarget)
|
||||
|
||||
@@ -193,17 +194,36 @@ class ObjectiveFinder:
|
||||
|
||||
def farthest_friendly_control_point(self) -> ControlPoint:
|
||||
"""Finds the friendly control point that is farthest from any threats."""
|
||||
|
||||
def find_farthest(
|
||||
control_points: Iterator[ControlPoint],
|
||||
threat_zones: ThreatZones,
|
||||
consider_off_map_spawn: bool,
|
||||
) -> ControlPoint | None:
|
||||
farthest = None
|
||||
max_distance = meters(0)
|
||||
for cp in control_points:
|
||||
if isinstance(cp, OffMapSpawn) and not consider_off_map_spawn:
|
||||
continue
|
||||
distance = threat_zones.distance_to_threat(cp.position)
|
||||
if distance > max_distance:
|
||||
farthest = cp
|
||||
max_distance = distance
|
||||
return farthest
|
||||
|
||||
threat_zones = self.game.threat_zone_for(not self.is_player)
|
||||
|
||||
farthest = None
|
||||
max_distance = meters(0)
|
||||
for cp in self.friendly_control_points():
|
||||
if isinstance(cp, OffMapSpawn):
|
||||
continue
|
||||
distance = threat_zones.distance_to_threat(cp.position)
|
||||
if distance > max_distance:
|
||||
farthest = cp
|
||||
max_distance = distance
|
||||
farthest = find_farthest(
|
||||
self.friendly_control_points(), threat_zones, consider_off_map_spawn=False
|
||||
)
|
||||
|
||||
# If there are only off-map spawn control points, fall back to the farthest amongst off map spawn points
|
||||
if farthest is None:
|
||||
farthest = find_farthest(
|
||||
self.friendly_control_points(),
|
||||
threat_zones,
|
||||
consider_off_map_spawn=True,
|
||||
)
|
||||
|
||||
if farthest is None:
|
||||
raise RuntimeError("Found no friendly control points. You probably lost.")
|
||||
|
||||
@@ -158,7 +158,7 @@ class TheaterState(WorldState["TheaterState"]):
|
||||
# Plan enough rounds of CAP that the target has coverage over the expected
|
||||
# mission duration.
|
||||
mission_duration = game.settings.desired_player_mission_duration.total_seconds()
|
||||
barcap_duration = coalition.doctrine.cap_duration.total_seconds()
|
||||
barcap_duration = coalition.doctrine.cap.duration.total_seconds()
|
||||
barcap_rounds = math.ceil(mission_duration / barcap_duration)
|
||||
|
||||
refueling_targets: list[MissionTarget] = []
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
@@ -15,19 +21,105 @@ class GroundUnitProcurementRatios:
|
||||
except KeyError:
|
||||
return 0.0
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict[str, float]) -> GroundUnitProcurementRatios:
|
||||
unit_class_enum_from_name = {unit.value: unit for unit in UnitClass}
|
||||
r = {}
|
||||
for unit_class in data:
|
||||
if unit_class not in unit_class_enum_from_name:
|
||||
raise ValueError(f"Could not find unit type {unit_class}")
|
||||
r[unit_class_enum_from_name[unit_class]] = float(data[unit_class])
|
||||
return GroundUnitProcurementRatios(r)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Helicopter:
|
||||
#: The altitude used for combat section of a flight, overrides the base combat_altitude parameter for helos
|
||||
combat_altitude: Distance
|
||||
|
||||
#: The altitude used for forming up a pacakge. Overrides the base rendezvous_altitude parameter for helos
|
||||
rendezvous_altitude: Distance
|
||||
|
||||
#: Altitude of the nav points (cruise section) of air assault missions.
|
||||
air_assault_nav_altitude: Distance
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict[str, Any]) -> Helicopter:
|
||||
return Helicopter(
|
||||
combat_altitude=feet(data["combat_altitude_ft_agl"]),
|
||||
rendezvous_altitude=feet(data["rendezvous_altitude_ft_agl"]),
|
||||
air_assault_nav_altitude=feet(data["air_assault_nav_altitude_ft_agl"]),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Cas:
|
||||
#: The duration that CAP flights will remain on-station.
|
||||
duration: timedelta
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict[str, Any]) -> Cas:
|
||||
return Cas(duration=timedelta(minutes=data["duration_minutes"]))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Sweep:
|
||||
#: Length of the sweep / patrol leg
|
||||
distance: Distance
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict[str, Any]) -> Sweep:
|
||||
return Sweep(
|
||||
distance=nautical_miles(data["distance_nm"]),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Cap:
|
||||
#: The duration that CAP flights will remain on-station.
|
||||
duration: timedelta
|
||||
|
||||
#: The minimum length of the CAP race track.
|
||||
min_track_length: Distance
|
||||
|
||||
#: The maximum length of the CAP race track.
|
||||
max_track_length: Distance
|
||||
|
||||
#: The minimum distance between the defended position and the *end* of the
|
||||
#: CAP race track.
|
||||
min_distance_from_cp: Distance
|
||||
|
||||
#: The maximum distance between the defended position and the *end* of the
|
||||
#: CAP race track.
|
||||
max_distance_from_cp: Distance
|
||||
|
||||
#: The engagement range of CAP flights. Any enemy aircraft within this range
|
||||
#: of the CAP's current position will be engaged by the CAP.
|
||||
engagement_range: Distance
|
||||
|
||||
#: Defines the range of altitudes CAP racetracks are planned at.
|
||||
min_patrol_altitude: Distance
|
||||
max_patrol_altitude: Distance
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict[str, Any]) -> Cap:
|
||||
return Cap(
|
||||
duration=timedelta(minutes=data["duration_minutes"]),
|
||||
min_track_length=nautical_miles(data["min_track_length_nm"]),
|
||||
max_track_length=nautical_miles(data["max_track_length_nm"]),
|
||||
min_distance_from_cp=nautical_miles(data["min_distance_from_cp_nm"]),
|
||||
max_distance_from_cp=nautical_miles(data["max_distance_from_cp_nm"]),
|
||||
engagement_range=nautical_miles(data["engagement_range_nm"]),
|
||||
min_patrol_altitude=feet(data["min_patrol_altitude_ft_msl"]),
|
||||
max_patrol_altitude=feet(data["max_patrol_altitude_ft_msl"]),
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Doctrine:
|
||||
#: Name of the doctrine, used to assign a doctrine in a faction.
|
||||
name: str
|
||||
|
||||
cas: bool
|
||||
cap: bool
|
||||
sead: bool
|
||||
strike: bool
|
||||
antiship: bool
|
||||
|
||||
rendezvous_altitude: Distance
|
||||
|
||||
#: The minimum distance between the departure airfield and the hold point.
|
||||
hold_distance: Distance
|
||||
|
||||
@@ -46,155 +138,87 @@ class Doctrine:
|
||||
#: target.
|
||||
min_ingress_distance: Distance
|
||||
|
||||
ingress_altitude: Distance
|
||||
#: The altitude used for combat section of a flight.
|
||||
combat_altitude: Distance
|
||||
|
||||
min_patrol_altitude: Distance
|
||||
max_patrol_altitude: Distance
|
||||
pattern_altitude: Distance
|
||||
|
||||
#: The duration that CAP flights will remain on-station.
|
||||
cap_duration: timedelta
|
||||
|
||||
#: The minimum length of the CAP race track.
|
||||
cap_min_track_length: Distance
|
||||
|
||||
#: The maximum length of the CAP race track.
|
||||
cap_max_track_length: Distance
|
||||
|
||||
#: The minimum distance between the defended position and the *end* of the
|
||||
#: CAP race track.
|
||||
cap_min_distance_from_cp: Distance
|
||||
|
||||
#: The maximum distance between the defended position and the *end* of the
|
||||
#: CAP race track.
|
||||
cap_max_distance_from_cp: Distance
|
||||
|
||||
#: The engagement range of CAP flights. Any enemy aircraft within this range
|
||||
#: of the CAP's current position will be engaged by the CAP.
|
||||
cap_engagement_range: Distance
|
||||
|
||||
cas_duration: timedelta
|
||||
|
||||
sweep_distance: Distance
|
||||
#: The altitude used for forming up a pacakge.
|
||||
rendezvous_altitude: Distance
|
||||
|
||||
#: Defines prioritization of ground unit purchases.
|
||||
ground_unit_procurement_ratios: GroundUnitProcurementRatios
|
||||
|
||||
#: Helicopter specific doctrines.
|
||||
helicopter: Helicopter
|
||||
|
||||
MODERN_DOCTRINE = Doctrine(
|
||||
"modern",
|
||||
cap=True,
|
||||
cas=True,
|
||||
sead=True,
|
||||
strike=True,
|
||||
antiship=True,
|
||||
rendezvous_altitude=feet(25000),
|
||||
hold_distance=nautical_miles(25),
|
||||
push_distance=nautical_miles(20),
|
||||
join_distance=nautical_miles(20),
|
||||
max_ingress_distance=nautical_miles(45),
|
||||
min_ingress_distance=nautical_miles(10),
|
||||
ingress_altitude=feet(20000),
|
||||
min_patrol_altitude=feet(15000),
|
||||
max_patrol_altitude=feet(33000),
|
||||
pattern_altitude=feet(5000),
|
||||
cap_duration=timedelta(minutes=30),
|
||||
cap_min_track_length=nautical_miles(15),
|
||||
cap_max_track_length=nautical_miles(40),
|
||||
cap_min_distance_from_cp=nautical_miles(10),
|
||||
cap_max_distance_from_cp=nautical_miles(40),
|
||||
cap_engagement_range=nautical_miles(50),
|
||||
cas_duration=timedelta(minutes=30),
|
||||
sweep_distance=nautical_miles(60),
|
||||
ground_unit_procurement_ratios=GroundUnitProcurementRatios(
|
||||
{
|
||||
UnitClass.TANK: 3,
|
||||
UnitClass.ATGM: 2,
|
||||
UnitClass.APC: 2,
|
||||
UnitClass.IFV: 3,
|
||||
UnitClass.ARTILLERY: 1,
|
||||
UnitClass.SHORAD: 2,
|
||||
UnitClass.RECON: 1,
|
||||
}
|
||||
),
|
||||
)
|
||||
#: Doctrine for CAS missions.
|
||||
cas: Cas
|
||||
|
||||
COLDWAR_DOCTRINE = Doctrine(
|
||||
name="coldwar",
|
||||
cap=True,
|
||||
cas=True,
|
||||
sead=True,
|
||||
strike=True,
|
||||
antiship=True,
|
||||
rendezvous_altitude=feet(22000),
|
||||
hold_distance=nautical_miles(15),
|
||||
push_distance=nautical_miles(10),
|
||||
join_distance=nautical_miles(10),
|
||||
max_ingress_distance=nautical_miles(30),
|
||||
min_ingress_distance=nautical_miles(10),
|
||||
ingress_altitude=feet(18000),
|
||||
min_patrol_altitude=feet(10000),
|
||||
max_patrol_altitude=feet(24000),
|
||||
pattern_altitude=feet(5000),
|
||||
cap_duration=timedelta(minutes=30),
|
||||
cap_min_track_length=nautical_miles(12),
|
||||
cap_max_track_length=nautical_miles(24),
|
||||
cap_min_distance_from_cp=nautical_miles(8),
|
||||
cap_max_distance_from_cp=nautical_miles(25),
|
||||
cap_engagement_range=nautical_miles(35),
|
||||
cas_duration=timedelta(minutes=30),
|
||||
sweep_distance=nautical_miles(40),
|
||||
ground_unit_procurement_ratios=GroundUnitProcurementRatios(
|
||||
{
|
||||
UnitClass.TANK: 4,
|
||||
UnitClass.ATGM: 2,
|
||||
UnitClass.APC: 3,
|
||||
UnitClass.IFV: 2,
|
||||
UnitClass.ARTILLERY: 1,
|
||||
UnitClass.SHORAD: 2,
|
||||
UnitClass.RECON: 1,
|
||||
}
|
||||
),
|
||||
)
|
||||
#: Doctrine for CAP missions.
|
||||
cap: Cap
|
||||
|
||||
WWII_DOCTRINE = Doctrine(
|
||||
name="ww2",
|
||||
cap=True,
|
||||
cas=True,
|
||||
sead=False,
|
||||
strike=True,
|
||||
antiship=True,
|
||||
hold_distance=nautical_miles(10),
|
||||
push_distance=nautical_miles(5),
|
||||
join_distance=nautical_miles(5),
|
||||
rendezvous_altitude=feet(10000),
|
||||
max_ingress_distance=nautical_miles(7),
|
||||
min_ingress_distance=nautical_miles(5),
|
||||
ingress_altitude=feet(8000),
|
||||
min_patrol_altitude=feet(4000),
|
||||
max_patrol_altitude=feet(15000),
|
||||
pattern_altitude=feet(5000),
|
||||
cap_duration=timedelta(minutes=30),
|
||||
cap_min_track_length=nautical_miles(8),
|
||||
cap_max_track_length=nautical_miles(18),
|
||||
cap_min_distance_from_cp=nautical_miles(0),
|
||||
cap_max_distance_from_cp=nautical_miles(5),
|
||||
cap_engagement_range=nautical_miles(20),
|
||||
cas_duration=timedelta(minutes=30),
|
||||
sweep_distance=nautical_miles(10),
|
||||
ground_unit_procurement_ratios=GroundUnitProcurementRatios(
|
||||
{
|
||||
UnitClass.TANK: 3,
|
||||
UnitClass.ATGM: 3,
|
||||
UnitClass.APC: 3,
|
||||
UnitClass.ARTILLERY: 1,
|
||||
UnitClass.SHORAD: 3,
|
||||
UnitClass.RECON: 1,
|
||||
}
|
||||
),
|
||||
)
|
||||
#: Doctrine for Fighter Sweep missions.
|
||||
sweep: Sweep
|
||||
|
||||
ALL_DOCTRINES = [
|
||||
COLDWAR_DOCTRINE,
|
||||
MODERN_DOCTRINE,
|
||||
WWII_DOCTRINE,
|
||||
]
|
||||
_by_name: ClassVar[dict[str, Doctrine]] = {}
|
||||
_loaded: ClassVar[bool] = False
|
||||
|
||||
def resolve_combat_altitude(self, is_helo: bool = False) -> Distance:
|
||||
if is_helo:
|
||||
return self.helicopter.combat_altitude
|
||||
return self.combat_altitude
|
||||
|
||||
def resolve_rendezvous_altitude(self, is_helo: bool = False) -> Distance:
|
||||
if is_helo:
|
||||
return self.helicopter.rendezvous_altitude
|
||||
return self.rendezvous_altitude
|
||||
|
||||
@classmethod
|
||||
def register(cls, doctrine: Doctrine) -> None:
|
||||
if doctrine.name in cls._by_name:
|
||||
duplicate = cls._by_name[doctrine.name]
|
||||
raise ValueError(f"Doctrine {doctrine.name} is already loaded")
|
||||
cls._by_name[doctrine.name] = doctrine
|
||||
|
||||
@classmethod
|
||||
def named(cls, name: str) -> Doctrine:
|
||||
if not cls._loaded:
|
||||
cls.load_all()
|
||||
return cls._by_name[name]
|
||||
|
||||
@classmethod
|
||||
def all_doctrines(cls) -> list[Doctrine]:
|
||||
if not cls._loaded:
|
||||
cls.load_all()
|
||||
return list(cls._by_name.values())
|
||||
|
||||
@classmethod
|
||||
def load_all(cls) -> None:
|
||||
if cls._loaded:
|
||||
return
|
||||
for doctrine_file_path in Path("resources/doctrines").glob("**/*.yaml"):
|
||||
with doctrine_file_path.open(encoding="utf8") as doctrine_file:
|
||||
data = yaml.safe_load(doctrine_file)
|
||||
cls.register(
|
||||
Doctrine(
|
||||
name=data["name"],
|
||||
rendezvous_altitude=feet(data["rendezvous_altitude_ft_msl"]),
|
||||
hold_distance=nautical_miles(data["hold_distance_nm"]),
|
||||
push_distance=nautical_miles(data["push_distance_nm"]),
|
||||
join_distance=nautical_miles(data["join_distance_nm"]),
|
||||
max_ingress_distance=nautical_miles(
|
||||
data["max_ingress_distance_nm"]
|
||||
),
|
||||
min_ingress_distance=nautical_miles(
|
||||
data["min_ingress_distance_nm"]
|
||||
),
|
||||
combat_altitude=feet(data["combat_altitude_ft_msl"]),
|
||||
ground_unit_procurement_ratios=GroundUnitProcurementRatios.from_dict(
|
||||
data["ground_unit_procurement_ratios"]
|
||||
),
|
||||
helicopter=Helicopter.from_dict(data["helicopter"]),
|
||||
cas=Cas.from_dict(data["cas"]),
|
||||
cap=Cap.from_dict(data["cap"]),
|
||||
sweep=Sweep.from_dict(data["sweep"]),
|
||||
)
|
||||
)
|
||||
cls._loaded = True
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, replace as dataclasses_replace
|
||||
from functools import cache, cached_property
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar, Dict, Iterator, Optional, TYPE_CHECKING, Type
|
||||
@@ -182,6 +182,9 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
#: planner will consider this aircraft usable for a mission.
|
||||
max_mission_range: Distance
|
||||
|
||||
#: Speed used for TOT calculations
|
||||
cruise_speed: Optional[Speed]
|
||||
|
||||
fuel_consumption: Optional[FuelConsumption]
|
||||
|
||||
default_livery: Optional[str]
|
||||
@@ -400,6 +403,12 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
for k in config:
|
||||
if k in aircraft.property_defaults:
|
||||
aircraft.property_defaults[k] = config[k]
|
||||
# In addition to setting the property_defaults, we have to set the "default" property in the
|
||||
# value of aircraft.properties for the key, as this is used in parts of the codebase to get
|
||||
# the default value.
|
||||
aircraft.properties[k] = dataclasses_replace(
|
||||
aircraft.properties[k], default=config[k]
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
f"'{aircraft.id}' attempted to set default prop '{k}' that does not exist"
|
||||
@@ -489,6 +498,9 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
patrol_altitude=patrol_config.altitude,
|
||||
patrol_speed=patrol_config.speed,
|
||||
max_mission_range=mission_range,
|
||||
cruise_speed=knots(data["cruise_speed_kt_indicated"])
|
||||
if "cruise_speed_kt_indicated" in data
|
||||
else None,
|
||||
fuel_consumption=fuel_consumption,
|
||||
default_livery=data.get("default_livery"),
|
||||
intra_flight_radio=radio_config.intra_flight,
|
||||
@@ -505,6 +517,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
||||
LaserCodeConfig.from_yaml(d) for d in data.get("laser_codes", [])
|
||||
],
|
||||
use_f15e_waypoint_names=data.get("use_f15e_waypoint_names", False),
|
||||
hit_points=data.get("hit_points", 1),
|
||||
)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
|
||||
@@ -133,4 +133,5 @@ class GroundUnitType(UnitType[Type[VehicleType]]):
|
||||
data.get("skynet_properties", {})
|
||||
),
|
||||
reversed_heading=data.get("reversed_heading", False),
|
||||
hit_points=data.get("hit_points", 1),
|
||||
)
|
||||
|
||||
@@ -79,4 +79,5 @@ class ShipUnitType(UnitType[Type[ShipType]]):
|
||||
manufacturer=data.get("manufacturer", "No data."),
|
||||
role=data.get("role", "No data."),
|
||||
price=data["price"],
|
||||
hit_points=data.get("hit_points", 1),
|
||||
)
|
||||
|
||||
@@ -27,6 +27,7 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
|
||||
role: str
|
||||
price: int
|
||||
unit_class: UnitClass
|
||||
hit_points: int
|
||||
|
||||
_loaded: ClassVar[bool] = False
|
||||
|
||||
|
||||
@@ -19,12 +19,7 @@ from game.data.building_data import (
|
||||
WW2_FREE,
|
||||
WW2_GERMANY_BUILDINGS,
|
||||
)
|
||||
from game.data.doctrine import (
|
||||
COLDWAR_DOCTRINE,
|
||||
Doctrine,
|
||||
MODERN_DOCTRINE,
|
||||
WWII_DOCTRINE,
|
||||
)
|
||||
from game.data.doctrine import Doctrine
|
||||
from game.data.groups import GroupRole
|
||||
from game.data.units import UnitClass
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
@@ -106,7 +101,7 @@ class Faction:
|
||||
jtac_unit: Optional[AircraftType] = field(default=None)
|
||||
|
||||
# doctrine
|
||||
doctrine: Doctrine = field(default=MODERN_DOCTRINE)
|
||||
doctrine: Doctrine = field(default=Doctrine.named("modern"))
|
||||
|
||||
# List of available building layouts for this faction
|
||||
building_set: List[str] = field(default_factory=list)
|
||||
@@ -238,14 +233,7 @@ class Faction:
|
||||
|
||||
# Load doctrine
|
||||
doctrine = json.get("doctrine", "modern")
|
||||
if doctrine == "modern":
|
||||
faction.doctrine = MODERN_DOCTRINE
|
||||
elif doctrine == "coldwar":
|
||||
faction.doctrine = COLDWAR_DOCTRINE
|
||||
elif doctrine == "ww2":
|
||||
faction.doctrine = WWII_DOCTRINE
|
||||
else:
|
||||
faction.doctrine = MODERN_DOCTRINE
|
||||
faction.doctrine = Doctrine.named(doctrine)
|
||||
|
||||
# Load the building set
|
||||
faction.building_set = []
|
||||
@@ -312,6 +300,8 @@ class Faction:
|
||||
self.remove_aircraft("Su-57")
|
||||
if not mod_settings.ov10a_bronco:
|
||||
self.remove_aircraft("Bronco-OV-10A")
|
||||
if not mod_settings.superhornet:
|
||||
self.remove_aircraft("Super-Hornet")
|
||||
# frenchpack
|
||||
if not mod_settings.frenchpack:
|
||||
self.remove_vehicle("AMX10RCR")
|
||||
|
||||
@@ -11,17 +11,14 @@ from shapely import transform
|
||||
from shapely.geometry import shape
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
|
||||
from game.data.doctrine import Doctrine, ALL_DOCTRINES
|
||||
from game.data.doctrine import Doctrine
|
||||
from .ipsolver import IpSolver
|
||||
from .waypointsolver import WaypointSolver
|
||||
from ..theater.theaterloader import TERRAINS_BY_NAME
|
||||
|
||||
|
||||
def doctrine_from_name(name: str) -> Doctrine:
|
||||
for doctrine in ALL_DOCTRINES:
|
||||
if doctrine.name == name:
|
||||
return doctrine
|
||||
raise KeyError
|
||||
return Doctrine.named(name)
|
||||
|
||||
|
||||
def geometry_ll_to_xy(geometry: BaseGeometry, terrain: Terrain) -> BaseGeometry:
|
||||
|
||||
@@ -119,7 +119,6 @@ class RequirementBuilder:
|
||||
def maximum_turn_to(
|
||||
self, turn_point: Point, next_point: Point, turn_limit: Heading
|
||||
) -> None:
|
||||
|
||||
large_distance = nautical_miles(400)
|
||||
next_heading = Heading.from_degrees(
|
||||
angle_between_points(next_point, turn_point)
|
||||
|
||||
@@ -89,7 +89,6 @@ class GroundPlanner:
|
||||
self.reserve: List[CombatGroup] = []
|
||||
|
||||
def plan_groundwar(self) -> None:
|
||||
|
||||
ground_unit_limit = self.cp.frontline_unit_count_limit
|
||||
|
||||
remaining_available_frontline_units = ground_unit_limit
|
||||
@@ -139,7 +138,6 @@ class GroundPlanner:
|
||||
remaining_available_frontline_units -= available
|
||||
|
||||
while available > 0:
|
||||
|
||||
if role == CombatGroupRole.SHORAD:
|
||||
count = 1
|
||||
else:
|
||||
|
||||
@@ -241,7 +241,6 @@ class AntiAirLayout(TgoLayout):
|
||||
location: PresetLocation,
|
||||
control_point: ControlPoint,
|
||||
) -> IadsGroundObject:
|
||||
|
||||
if GroupTask.EARLY_WARNING_RADAR in self.tasks:
|
||||
return EwrGroundObject(name, location, control_point)
|
||||
elif any(tasking in self.tasks for tasking in GroupRole.AIR_DEFENSE.tasks):
|
||||
|
||||
@@ -132,7 +132,6 @@ class LayoutLoader:
|
||||
temp_mis.country(country.name).ship_group,
|
||||
temp_mis.country(country.name).static_group,
|
||||
):
|
||||
|
||||
try:
|
||||
g_id, u_id, group_name, group_mapping = mapping.group_for_name(
|
||||
dcs_group.name
|
||||
|
||||
@@ -8,7 +8,6 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
|
||||
|
||||
class RecoveryTankerBuilder(PydcsWaypointBuilder):
|
||||
def add_tasks(self, waypoint: MovingPoint) -> None:
|
||||
|
||||
assert self.flight.flight_type == FlightType.REFUELING
|
||||
|
||||
# Tanker task required in conjunction with RecoveryTanker task.
|
||||
@@ -48,7 +47,6 @@ class RecoveryTankerBuilder(PydcsWaypointBuilder):
|
||||
)
|
||||
|
||||
def configure_tanker_tacan(self, waypoint: MovingPoint) -> None:
|
||||
|
||||
if self.flight.unit_type.dcs_unit_type.tacan:
|
||||
tanker_info = self.mission_data.tankers[-1]
|
||||
tacan = tanker_info.tacan
|
||||
|
||||
@@ -6,7 +6,6 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
|
||||
|
||||
class SplitPointBuilder(PydcsWaypointBuilder):
|
||||
def add_tasks(self, waypoint: MovingPoint) -> None:
|
||||
|
||||
if not self.flight.flight_type.is_air_to_air:
|
||||
# Capture any non A/A type to avoid issues with SPJs that use the primary radar such as the F/A-18C.
|
||||
# You can bully them with STT to not be able to fire radar guided missiles at you,
|
||||
|
||||
@@ -59,7 +59,6 @@ class DrawingsGenerator:
|
||||
if destination in seen:
|
||||
continue
|
||||
else:
|
||||
|
||||
# Determine path color
|
||||
if cp.captured and destination.captured:
|
||||
color = BLUE_PATH_COLOR
|
||||
|
||||
@@ -191,7 +191,6 @@ class FlotGenerator:
|
||||
side: Country,
|
||||
forward_heading: Heading,
|
||||
) -> None:
|
||||
|
||||
infantry_position = self.conflict.find_ground_position(
|
||||
group.points[0].position.random_point_within(250, 50),
|
||||
500,
|
||||
@@ -304,7 +303,6 @@ class FlotGenerator:
|
||||
|
||||
# Artillery will fall back when under attack
|
||||
if stance != CombatStance.RETREAT:
|
||||
|
||||
# Hold position
|
||||
dcs_group.points[1].tasks.append(Hold())
|
||||
retreat = self.find_retreat_point(
|
||||
@@ -476,7 +474,6 @@ class FlotGenerator:
|
||||
from_cp: ControlPoint,
|
||||
to_cp: ControlPoint,
|
||||
) -> None:
|
||||
|
||||
if not self.game.settings.perf_moving_units:
|
||||
return
|
||||
|
||||
|
||||
@@ -185,7 +185,6 @@ class NumberedWaypoint:
|
||||
|
||||
|
||||
class FlightPlanBuilder:
|
||||
|
||||
WAYPOINT_DESC_MAX_LEN = 25
|
||||
|
||||
def __init__(self, start_time: datetime.datetime, units: UnitSystem) -> None:
|
||||
@@ -503,7 +502,6 @@ class SupportPage(KneeboardPage):
|
||||
aewc_ladder = []
|
||||
|
||||
for single_aewc in self.awacs:
|
||||
|
||||
if single_aewc.depature_location is None:
|
||||
dep = "-"
|
||||
arr = "-"
|
||||
|
||||
@@ -402,7 +402,6 @@ class GenericCarrierGenerator(GroundObjectGenerator):
|
||||
self.mission_data = mission_data
|
||||
|
||||
def generate(self) -> None:
|
||||
|
||||
# This can also be refactored as the general generation was updated
|
||||
atc = self.radio_registry.alloc_uhf()
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ class ProcurementAi:
|
||||
manage_front_line: bool,
|
||||
manage_aircraft: bool,
|
||||
) -> None:
|
||||
|
||||
self.game = game
|
||||
self.is_player = for_player
|
||||
self.air_wing = game.air_wing_for(for_player)
|
||||
|
||||
@@ -33,15 +33,19 @@ class FrozenCombatJs(BaseModel):
|
||||
if isinstance(combat, AtIp):
|
||||
return FrozenCombatJs(
|
||||
id=combat.id,
|
||||
flight_position=combat.flight.position().latlng(),
|
||||
target_positions=[combat.flight.package.target.position.latlng()],
|
||||
flight_position=LeafletPoint.from_pydcs(combat.flight.position()),
|
||||
target_positions=[
|
||||
LeafletPoint.from_pydcs(combat.flight.package.target.position)
|
||||
],
|
||||
footprint=None,
|
||||
)
|
||||
if isinstance(combat, DefendingSam):
|
||||
return FrozenCombatJs(
|
||||
id=combat.id,
|
||||
flight_position=combat.flight.position().latlng(),
|
||||
target_positions=[sam.position.latlng() for sam in combat.air_defenses],
|
||||
flight_position=LeafletPoint.from_pydcs(combat.flight.position()),
|
||||
target_positions=[
|
||||
LeafletPoint.from_pydcs(sam.position) for sam in combat.air_defenses
|
||||
],
|
||||
footprint=None,
|
||||
)
|
||||
raise NotImplementedError(f"Unhandled FrozenCombat type: {combat.__class__}")
|
||||
|
||||
@@ -28,12 +28,12 @@ class ControlPointJs(BaseModel):
|
||||
def for_control_point(control_point: ControlPoint) -> ControlPointJs:
|
||||
destination = None
|
||||
if control_point.target_position is not None:
|
||||
destination = control_point.target_position.latlng()
|
||||
destination = LeafletPoint.from_pydcs(control_point.target_position)
|
||||
return ControlPointJs(
|
||||
id=control_point.id,
|
||||
name=control_point.name,
|
||||
blue=control_point.captured,
|
||||
position=control_point.position.latlng(),
|
||||
position=LeafletPoint.from_pydcs(control_point.position),
|
||||
mobile=control_point.moveable and control_point.captured,
|
||||
destination=destination,
|
||||
sidc=str(control_point.sidc()),
|
||||
|
||||
@@ -47,7 +47,6 @@ class GameUpdateEventsJs(BaseModel):
|
||||
def from_events(
|
||||
cls, events: GameUpdateEvents, game: Game | None
|
||||
) -> GameUpdateEventsJs:
|
||||
|
||||
# We still need to be able to send update events when there is no game loaded
|
||||
# because we need to send the unload event.
|
||||
new_combats = []
|
||||
@@ -81,9 +80,13 @@ class GameUpdateEventsJs(BaseModel):
|
||||
for f in events.updated_front_lines
|
||||
]
|
||||
|
||||
reset_on_map_center: LeafletPoint | None = None
|
||||
if events.reset_on_map_center is not None:
|
||||
reset_on_map_center = LeafletPoint.from_pydcs(events.reset_on_map_center)
|
||||
return GameUpdateEventsJs(
|
||||
updated_flight_positions={
|
||||
f[0].id: f[1].latlng() for f in events.updated_flight_positions
|
||||
f[0].id: LeafletPoint.from_pydcs(f[1])
|
||||
for f in events.updated_flight_positions
|
||||
},
|
||||
new_combats=new_combats,
|
||||
updated_combats=updated_combats,
|
||||
@@ -110,7 +113,7 @@ class GameUpdateEventsJs(BaseModel):
|
||||
],
|
||||
updated_iads=updated_iads,
|
||||
deleted_iads=events.deleted_iads_connections,
|
||||
reset_on_map_center=events.reset_on_map_center,
|
||||
reset_on_map_center=reset_on_map_center,
|
||||
game_unloaded=events.game_unloaded,
|
||||
new_turn=events.new_turn,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import asyncio
|
||||
from asyncio import wait
|
||||
from asyncio import wait, Future
|
||||
|
||||
from fastapi import APIRouter, WebSocket
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
@@ -16,9 +16,9 @@ class ConnectionManager:
|
||||
self.active_connections: list[WebSocket] = []
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
futures = []
|
||||
futures: list[Future[None]] = []
|
||||
for connection in self.active_connections:
|
||||
futures.append(connection.close())
|
||||
futures.append(asyncio.create_task(connection.close()))
|
||||
await wait(futures)
|
||||
|
||||
async def connect(self, websocket: WebSocket) -> None:
|
||||
|
||||
@@ -37,7 +37,7 @@ class FlightJs(BaseModel):
|
||||
# lost.
|
||||
position = None
|
||||
if isinstance(flight.state, InFlight) or isinstance(flight.state, Killed):
|
||||
position = flight.position().latlng()
|
||||
position = LeafletPoint.from_pydcs(flight.position())
|
||||
waypoints = None
|
||||
if with_waypoints:
|
||||
waypoints = waypoints_for_flight(flight)
|
||||
|
||||
@@ -27,7 +27,10 @@ class FrontLineJs(BaseModel):
|
||||
bounds = FrontLineConflictDescription.frontline_bounds(front_line, theater)
|
||||
return FrontLineJs(
|
||||
id=front_line.id,
|
||||
extents=[bounds.left_position.latlng(), bounds.right_position.latlng()],
|
||||
extents=[
|
||||
LeafletPoint.from_pydcs(bounds.left_position),
|
||||
LeafletPoint.from_pydcs(bounds.right_position),
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -7,12 +7,12 @@ from pydantic import BaseModel
|
||||
from game.server.controlpoints.models import ControlPointJs
|
||||
from game.server.flights.models import FlightJs
|
||||
from game.server.frontlines.models import FrontLineJs
|
||||
from game.server.iadsnetwork.models import IadsNetworkJs
|
||||
from game.server.leaflet import LeafletPoint
|
||||
from game.server.mapzones.models import ThreatZoneContainerJs, UnculledZoneJs
|
||||
from game.server.navmesh.models import NavMeshesJs
|
||||
from game.server.supplyroutes.models import SupplyRouteJs
|
||||
from game.server.tgos.models import TgoJs
|
||||
from game.server.iadsnetwork.models import IadsConnectionJs, IadsNetworkJs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -44,6 +44,8 @@ class GameJs(BaseModel):
|
||||
iads_network=IadsNetworkJs.from_network(game.theater.iads_network),
|
||||
threat_zones=ThreatZoneContainerJs.for_game(game),
|
||||
navmeshes=NavMeshesJs.from_game(game),
|
||||
map_center=game.theater.terrain.map_view_default.position.latlng(),
|
||||
map_center=LeafletPoint.from_pydcs(
|
||||
game.theater.terrain.map_view_default.position
|
||||
),
|
||||
unculled_zones=UnculledZoneJs.from_game(game),
|
||||
)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from game.server.leaflet import LeafletPoint
|
||||
from game.theater.iadsnetwork.iadsnetwork import IadsNetworkNode, IadsNetwork
|
||||
from game.theater.theatergroundobject import TheaterGroundObject
|
||||
|
||||
|
||||
class IadsConnectionJs(BaseModel):
|
||||
@@ -45,8 +45,8 @@ class IadsConnectionJs(BaseModel):
|
||||
IadsConnectionJs(
|
||||
id=id,
|
||||
points=[
|
||||
tgo.position.latlng(),
|
||||
connection.ground_object.position.latlng(),
|
||||
LeafletPoint.from_pydcs(tgo.position),
|
||||
LeafletPoint.from_pydcs(connection.ground_object.position),
|
||||
],
|
||||
node=tgo.id,
|
||||
connected=connection.ground_object.id,
|
||||
|
||||
@@ -19,6 +19,11 @@ class LeafletPoint(BaseModel):
|
||||
|
||||
title = "LatLng"
|
||||
|
||||
@staticmethod
|
||||
def from_pydcs(point: Point) -> LeafletPoint:
|
||||
latlng = point.latlng()
|
||||
return LeafletPoint(lat=latlng.lat, lng=latlng.lng)
|
||||
|
||||
|
||||
LeafletLine = list[LeafletPoint]
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class UnculledZoneJs(BaseModel):
|
||||
def from_game(game: Game) -> list[UnculledZoneJs]:
|
||||
return [
|
||||
UnculledZoneJs(
|
||||
position=zone.latlng(),
|
||||
position=LeafletPoint.from_pydcs(zone),
|
||||
radius=game.settings.perf_culling_distance * 1000,
|
||||
)
|
||||
for zone in game.get_culling_zones()
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
from pydantic import BaseSettings
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class ServerSettings(BaseSettings):
|
||||
|
||||
@@ -92,7 +92,7 @@ class SupplyRouteJs(BaseModel):
|
||||
# https://reactjs.org/docs/lists-and-keys.html#keys
|
||||
# https://github.com/dcs-liberation/dcs_liberation/issues/2167
|
||||
id=uuid.uuid4(),
|
||||
points=[p.latlng() for p in points],
|
||||
points=[LeafletPoint.from_pydcs(p) for p in points],
|
||||
front_active=not sea and a.front_is_active(b),
|
||||
is_sea=sea,
|
||||
blue=a.captured,
|
||||
|
||||
@@ -38,7 +38,7 @@ class TgoJs(BaseModel):
|
||||
control_point_name=tgo.control_point.name,
|
||||
category=tgo.category,
|
||||
blue=tgo.control_point.captured,
|
||||
position=tgo.position.latlng(),
|
||||
position=LeafletPoint.from_pydcs(tgo.position),
|
||||
units=[unit.display_name for unit in tgo.units],
|
||||
threat_ranges=threat_ranges,
|
||||
detection_ranges=detection_ranges,
|
||||
|
||||
@@ -82,7 +82,7 @@ class FlightWaypointJs(BaseModel):
|
||||
|
||||
return FlightWaypointJs(
|
||||
name=waypoint.name,
|
||||
position=waypoint.position.latlng(),
|
||||
position=LeafletPoint.from_pydcs(waypoint.position),
|
||||
altitude_ft=waypoint.alt.feet,
|
||||
altitude_reference=waypoint.alt_type,
|
||||
is_movable=is_movable,
|
||||
|
||||
@@ -5,7 +5,6 @@ from typing import TYPE_CHECKING
|
||||
from uuid import UUID
|
||||
|
||||
from dcs import Point
|
||||
from dcs.mapping import LatLng
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
@@ -38,7 +37,7 @@ class GameUpdateEvents:
|
||||
updated_control_points: set[ControlPoint] = field(default_factory=set)
|
||||
updated_iads: set[IadsNetworkNode] = field(default_factory=set)
|
||||
deleted_iads_connections: set[UUID] = field(default_factory=set)
|
||||
reset_on_map_center: LatLng | None = None
|
||||
reset_on_map_center: Point | None = None
|
||||
game_unloaded: bool = False
|
||||
new_turn: bool = False
|
||||
shutting_down: bool = False
|
||||
@@ -140,9 +139,7 @@ class GameUpdateEvents:
|
||||
self.game_unloaded = True
|
||||
self.reset_on_map_center = None
|
||||
else:
|
||||
self.reset_on_map_center = (
|
||||
game.theater.terrain.map_view_default.position.latlng()
|
||||
)
|
||||
self.reset_on_map_center = game.theater.terrain.map_view_default.position
|
||||
self.game_unloaded = False
|
||||
return self
|
||||
|
||||
|
||||
@@ -245,7 +245,6 @@ class MissionResultsProcessor:
|
||||
delta = DEFEAT_INFLUENCE
|
||||
status_msg = f"Enemy casualties outnumber allied casualties along the {cp.name}-{enemy_cp.name} frontline. Allied forces claim a victory."
|
||||
elif ally_casualties > enemy_casualties:
|
||||
|
||||
if (
|
||||
ally_units_alive > 2 * enemy_units_alive
|
||||
and player_aggresive
|
||||
|
||||
@@ -54,7 +54,6 @@ class SquadronDef:
|
||||
|
||||
@classmethod
|
||||
def from_yaml(cls, path: Path) -> SquadronDef:
|
||||
|
||||
with path.open(encoding="utf8") as squadron_file:
|
||||
data = yaml.safe_load(squadron_file)
|
||||
|
||||
|
||||
@@ -271,15 +271,15 @@ class RunwayStatus:
|
||||
def needs_repair(self) -> bool:
|
||||
return self.damaged and self.repair_turns_remaining is None
|
||||
|
||||
def __str__(self) -> str:
|
||||
def describe(self) -> str:
|
||||
if not self.damaged:
|
||||
return "Runway operational"
|
||||
return "operational"
|
||||
|
||||
turns_remaining = self.repair_turns_remaining
|
||||
if turns_remaining is None:
|
||||
return "Runway damaged"
|
||||
return "damaged"
|
||||
|
||||
return f"Runway repairing, {turns_remaining} turns remaining"
|
||||
return f"repairing, {turns_remaining} turns remaining"
|
||||
|
||||
|
||||
@total_ordering
|
||||
@@ -915,6 +915,10 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def describe_runway_status(self) -> str | None:
|
||||
"""Description of the runway status suitable for UI use."""
|
||||
|
||||
@property
|
||||
def runway_can_be_repaired(self) -> bool:
|
||||
return self.runway_status.needs_repair
|
||||
@@ -1157,6 +1161,9 @@ class Airfield(ControlPoint):
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
return self._runway_status
|
||||
|
||||
def describe_runway_status(self) -> str:
|
||||
return f"Runway {self.runway_status.describe()}"
|
||||
|
||||
def damage_runway(self) -> None:
|
||||
self.runway_status.damage()
|
||||
|
||||
@@ -1275,6 +1282,9 @@ class NavalControlPoint(ControlPoint, ABC):
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
return RunwayStatus(damaged=not self.runway_is_operational())
|
||||
|
||||
def describe_runway_status(self) -> str:
|
||||
return f"Flight deck {self.runway_status.describe()}"
|
||||
|
||||
@property
|
||||
def runway_can_be_repaired(self) -> bool:
|
||||
return False
|
||||
@@ -1428,6 +1438,9 @@ class OffMapSpawn(ControlPoint):
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
return RunwayStatus()
|
||||
|
||||
def describe_runway_status(self) -> str:
|
||||
return f"Off-map airport {self.runway_status.describe()}"
|
||||
|
||||
@property
|
||||
def can_deploy_ground_units(self) -> bool:
|
||||
return False
|
||||
@@ -1474,6 +1487,11 @@ class Fob(ControlPoint):
|
||||
def runway_status(self) -> RunwayStatus:
|
||||
return RunwayStatus()
|
||||
|
||||
def describe_runway_status(self) -> str | None:
|
||||
if not self.has_helipads:
|
||||
return None
|
||||
return f"FARP {self.runway_status.describe()}"
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from game.ato import FlightType
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ class ModSettings:
|
||||
frenchpack: bool = False
|
||||
high_digit_sams: bool = False
|
||||
ov10a_bronco: bool = False
|
||||
superhornet: bool = False
|
||||
|
||||
def save_player_settings(self) -> None:
|
||||
"""Saves the player's global settings to the user directory."""
|
||||
|
||||
@@ -196,7 +196,8 @@ class TheaterGroup:
|
||||
|
||||
def max_threat_range(self, radar_only: bool = False) -> Distance:
|
||||
"""Calculate the maximum threat range of the TheaterGroup.
|
||||
This also checks for Launcher and Tracker Pairs and if they are functioning or not. Allows to also use only radar emitting units for the calculation with the parameter."""
|
||||
This also checks for Launcher and Tracker Pairs and if they are functioning or not. Allows to also use only radar emitting units for the calculation with the parameter.
|
||||
"""
|
||||
max_non_radar = meters(0)
|
||||
max_telar_range = meters(0)
|
||||
max_tel_range = meters(0)
|
||||
|
||||
@@ -165,7 +165,7 @@ class ThreatZones:
|
||||
cls, doctrine: Doctrine, control_point: ControlPoint
|
||||
) -> Distance:
|
||||
cap_threat_range = (
|
||||
doctrine.cap_max_distance_from_cp + doctrine.cap_engagement_range
|
||||
doctrine.cap.max_distance_from_cp + doctrine.cap.engagement_range
|
||||
)
|
||||
opposing_airfield = cls.closest_enemy_airbase(
|
||||
control_point, cap_threat_range * 2
|
||||
|
||||
@@ -718,7 +718,6 @@ class PendingTransfers:
|
||||
self.order_airlift_assets_at(control_point)
|
||||
|
||||
def desired_airlift_capacity(self, control_point: ControlPoint) -> int:
|
||||
|
||||
if control_point.has_factory:
|
||||
is_major_hub = control_point.total_aircraft_parking > 0
|
||||
# Check if there is a CP which is only reachable via Airlift
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
MAJOR_VERSION = 9
|
||||
MAJOR_VERSION = 11
|
||||
MINOR_VERSION = 0
|
||||
MICRO_VERSION = 0
|
||||
VERSION_NUMBER = ".".join(str(v) for v in (MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION))
|
||||
|
||||
@@ -190,7 +190,6 @@ class Weather(ABC):
|
||||
def interpolate_solar_activity(
|
||||
time_of_day: TimeOfDay, high: float, low: float
|
||||
) -> float:
|
||||
|
||||
scale: float = 0
|
||||
|
||||
match time_of_day:
|
||||
|
||||
@@ -32,7 +32,7 @@ def init():
|
||||
|
||||
if os.path.isfile(THEME_PREFERENCES_FILE_PATH):
|
||||
try:
|
||||
with (open(THEME_PREFERENCES_FILE_PATH)) as prefs:
|
||||
with open(THEME_PREFERENCES_FILE_PATH) as prefs:
|
||||
pref_data = json.loads(prefs.read())
|
||||
__theme_index = pref_data["theme_index"]
|
||||
set_theme_index(__theme_index)
|
||||
@@ -83,5 +83,5 @@ def get_theme_css_file():
|
||||
# save current theme index to json file
|
||||
def save_theme_config():
|
||||
pref_data = {"theme_index": get_theme_index()}
|
||||
with (open(THEME_PREFERENCES_FILE_PATH, "w")) as prefs:
|
||||
with open(THEME_PREFERENCES_FILE_PATH, "w") as prefs:
|
||||
prefs.write(json.dumps(pref_data))
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import typing
|
||||
from collections.abc import Iterator
|
||||
|
||||
LogHook = typing.Callable[[str], None]
|
||||
|
||||
@@ -15,6 +18,16 @@ class HookableInMemoryHandler(logging.Handler):
|
||||
self._log = ""
|
||||
self._hook = None
|
||||
|
||||
@staticmethod
|
||||
def iter_registered_handlers(
|
||||
logger: logging.Logger | None = None,
|
||||
) -> Iterator[HookableInMemoryHandler]:
|
||||
if logger is None:
|
||||
logger = logging.getLogger()
|
||||
for handler in logger.handlers:
|
||||
if isinstance(handler, HookableInMemoryHandler):
|
||||
yield handler
|
||||
|
||||
@property
|
||||
def log(self) -> str:
|
||||
return self._log
|
||||
|
||||
@@ -288,7 +288,7 @@ class AtoModel(QAbstractListModel):
|
||||
return
|
||||
|
||||
package_model = self.find_matching_package_model(package)
|
||||
for flight in package.flights:
|
||||
for flight in list(package.flights):
|
||||
if flight.state.cancelable:
|
||||
package_model.delete_flight(flight)
|
||||
events.delete_flight(flight)
|
||||
|
||||
@@ -54,7 +54,6 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
|
||||
return waypoints
|
||||
|
||||
def find_possible_waypoints(self):
|
||||
|
||||
self.wpts = []
|
||||
model = QStandardItemModel()
|
||||
i = 0
|
||||
|
||||
@@ -9,7 +9,6 @@ from game.debriefing import Debriefing
|
||||
|
||||
|
||||
class GameUpdateSignal(QObject):
|
||||
|
||||
instance = None
|
||||
gameupdated = Signal(Game)
|
||||
budgetupdated = Signal(Game)
|
||||
|
||||
@@ -27,6 +27,7 @@ from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
|
||||
from game.turnstate import TurnState
|
||||
from qt_ui import liberation_install
|
||||
from qt_ui.dialogs import Dialog
|
||||
from qt_ui.logging_handler import HookableInMemoryHandler
|
||||
from qt_ui.models import GameModel
|
||||
from qt_ui.simcontroller import SimController
|
||||
from qt_ui.uiflags import UiFlags
|
||||
@@ -576,6 +577,10 @@ class QLiberationWindow(QMainWindow):
|
||||
self._cp_dialog = QBaseMenu2(None, cp, self.game_model)
|
||||
self._cp_dialog.show()
|
||||
|
||||
def _disconnect_log_signals(self) -> None:
|
||||
for handler in HookableInMemoryHandler.iter_registered_handlers():
|
||||
handler.clearHook()
|
||||
|
||||
def _qsettings(self) -> QSettings:
|
||||
return QSettings("DCS Liberation", "Qt UI")
|
||||
|
||||
@@ -597,6 +602,7 @@ class QLiberationWindow(QMainWindow):
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
)
|
||||
if result == QMessageBox.Yes:
|
||||
self._disconnect_log_signals()
|
||||
self._save_window_geometry()
|
||||
super().closeEvent(event)
|
||||
self.dialog = None
|
||||
|
||||
@@ -28,7 +28,6 @@ from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
|
||||
|
||||
class DebriefingFileWrittenSignal(QObject):
|
||||
|
||||
instance = None
|
||||
debriefingReceived = Signal(Debriefing)
|
||||
|
||||
|
||||
@@ -254,19 +254,22 @@ class QBaseMenu2(QDialog):
|
||||
f" (Up to {ground_unit_limit} deployable, {unit_overage} reserve)"
|
||||
)
|
||||
|
||||
self.intel_summary.setText(
|
||||
"\n".join(
|
||||
[
|
||||
f"{aircraft}/{parking} aircraft",
|
||||
f"{self.cp.base.total_armor} ground units" + deployable_unit_info,
|
||||
f"{allocated.total_transferring} more ground units en route, {allocated.total_ordered} ordered",
|
||||
str(self.cp.runway_status),
|
||||
f"{self.cp.active_ammo_depots_count}/{self.cp.total_ammo_depots_count} ammo depots",
|
||||
f"{'Factory can produce units' if self.cp.has_factory else 'Does not have a factory'}",
|
||||
]
|
||||
)
|
||||
intel_lines = [
|
||||
f"{aircraft}/{parking} aircraft",
|
||||
f"{self.cp.base.total_armor} ground units" + deployable_unit_info,
|
||||
f"{allocated.total_transferring} more ground units en route, {allocated.total_ordered} ordered",
|
||||
]
|
||||
if (runway_description := self.cp.describe_runway_status()) is not None:
|
||||
intel_lines.append(runway_description)
|
||||
intel_lines.extend(
|
||||
[
|
||||
f"{self.cp.active_ammo_depots_count}/{self.cp.total_ammo_depots_count} ammo depots",
|
||||
f"{'Factory can produce units' if self.cp.has_factory else 'Does not have a factory'}",
|
||||
]
|
||||
)
|
||||
|
||||
self.intel_summary.setText("\n".join(intel_lines))
|
||||
|
||||
def generate_intel_tooltip(self) -> str:
|
||||
tooltip = (
|
||||
f"Deployable unit limit ({self.cp.frontline_unit_count_limit}) = {FREE_FRONTLINE_UNIT_SUPPLY} (base) + "
|
||||
|
||||
@@ -70,7 +70,6 @@ class QGroundObjectMenu(QDialog):
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
|
||||
self.mainLayout = QVBoxLayout()
|
||||
self.budget = QBudgetBox(self.game)
|
||||
self.budget.setGame(self.game)
|
||||
@@ -105,7 +104,6 @@ class QGroundObjectMenu(QDialog):
|
||||
self.setLayout(self.mainLayout)
|
||||
|
||||
def doLayout(self):
|
||||
|
||||
self.update_total_value()
|
||||
self.intelBox = QGroupBox("Units :")
|
||||
self.intelLayout = QGridLayout()
|
||||
|
||||
@@ -160,7 +160,6 @@ class IntelWindow(QDialog):
|
||||
self.refresh_layout()
|
||||
|
||||
def refresh_layout(self) -> None:
|
||||
|
||||
# Clear the existing layout
|
||||
if self.layout():
|
||||
idx = 0
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from PySide6.QtCore import Signal
|
||||
from PySide6.QtGui import QTextCursor, QIcon
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QPlainTextEdit,
|
||||
QVBoxLayout,
|
||||
QPushButton,
|
||||
)
|
||||
from PySide6.QtGui import QTextCursor, QIcon
|
||||
|
||||
from qt_ui.logging_handler import HookableInMemoryHandler
|
||||
|
||||
@@ -50,12 +49,17 @@ class QLogsWindow(QDialog):
|
||||
|
||||
self.appendLogSignal.connect(self.appendLog)
|
||||
|
||||
self._logging_handler = None
|
||||
logger = logging.getLogger()
|
||||
for handler in logger.handlers:
|
||||
if isinstance(handler, HookableInMemoryHandler):
|
||||
self._logging_handler = handler
|
||||
break
|
||||
try:
|
||||
# This assumes that there's never more than one in memory handler. We don't
|
||||
# configure more than one by default, but logging is customizable with
|
||||
# resources/logging.yaml. If someone adds a second in-memory handler, only
|
||||
# the first one (in arbitrary order) will be shown.
|
||||
self._logging_handler = next(
|
||||
HookableInMemoryHandler.iter_registered_handlers()
|
||||
)
|
||||
except StopIteration:
|
||||
self._logging_handler = None
|
||||
|
||||
if self._logging_handler is not None:
|
||||
self.textbox.setPlainText(self._logging_handler.log)
|
||||
self.textbox.moveCursor(QTextCursor.End)
|
||||
|
||||
@@ -261,7 +261,7 @@ class QNewPackageDialog(QPackageDialog):
|
||||
|
||||
def on_cancel(self) -> None:
|
||||
super().on_cancel()
|
||||
for flight in self.package_model.package.flights:
|
||||
for flight in list(self.package_model.package.flights):
|
||||
self.package_model.cancel_or_abort_flight(flight)
|
||||
|
||||
|
||||
|
||||
@@ -38,12 +38,12 @@ class FlightMemberSelector(QSpinBox):
|
||||
def __init__(self, flight: Flight, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.flight = flight
|
||||
self.setMinimum(0)
|
||||
self.setMaximum(flight.count - 1)
|
||||
self.setMinimum(1)
|
||||
self.setMaximum(flight.count)
|
||||
|
||||
@property
|
||||
def selected_member(self) -> FlightMember:
|
||||
return self.flight.roster.members[self.value()]
|
||||
return self.flight.roster.members[self.value() - 1]
|
||||
|
||||
|
||||
class QFlightPayloadTab(QFrame):
|
||||
|
||||
@@ -14,7 +14,6 @@ class QFlightWaypointInfoBox(QGroupBox):
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self) -> None:
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
x_pos_layout = QHBoxLayout()
|
||||
|
||||
@@ -60,7 +60,6 @@ class QFlightWaypointTab(QFrame):
|
||||
|
||||
self.recreate_buttons.clear()
|
||||
for task in self.package.target.mission_types(for_player=True):
|
||||
|
||||
if (
|
||||
task == FlightType.AIR_ASSAULT
|
||||
and not self.game.lua_plugin_manager.is_plugin_enabled("ctld")
|
||||
|
||||
@@ -27,7 +27,6 @@ PREDEFINED_WAYPOINT_CATEGORIES = [
|
||||
|
||||
|
||||
class QPredefinedWaypointSelectionWindow(QDialog):
|
||||
|
||||
# List of FlightWaypoint
|
||||
waypoints_added = Signal(list)
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ class NewGameWizard(QtWidgets.QWizard):
|
||||
ov10a_bronco=self.field("ov10a_bronco"),
|
||||
frenchpack=self.field("frenchpack"),
|
||||
high_digit_sams=self.field("high_digit_sams"),
|
||||
superhornet=self.field("superhornet"),
|
||||
)
|
||||
mod_settings.save_player_settings()
|
||||
|
||||
@@ -826,6 +827,10 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
||||
high_digit_sams.setChecked(mod_settings.high_digit_sams)
|
||||
self.registerField("high_digit_sams", high_digit_sams)
|
||||
|
||||
superhornet = QtWidgets.QCheckBox()
|
||||
superhornet.setChecked(mod_settings.superhornet)
|
||||
self.registerField("superhornet", superhornet)
|
||||
|
||||
modHelpText = QtWidgets.QLabel(
|
||||
"<p>Select the mods you have installed. If your chosen factions support them, you'll be able to use these mods in your campaign.</p>"
|
||||
)
|
||||
@@ -877,6 +882,11 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
||||
modLayout.addWidget(QtWidgets.QLabel("High Digit SAMs"), modLayout_row, 0)
|
||||
modLayout.addWidget(high_digit_sams, modLayout_row, 1)
|
||||
modSettingsGroup.setLayout(modLayout)
|
||||
modLayout_row += 1
|
||||
modLayout.addWidget(QtWidgets.QLabel("Super Hornet"), modLayout_row, 0)
|
||||
modLayout.addWidget(superhornet, modLayout_row, 1)
|
||||
modSettingsGroup.setLayout(modLayout)
|
||||
modLayout_row += 1
|
||||
|
||||
mlayout = QVBoxLayout()
|
||||
mlayout.addWidget(generatorSettingsGroup)
|
||||
|
||||
@@ -87,7 +87,6 @@ class QLiberationPreferences(QFrame):
|
||||
self.edit_dcs_install_dir.setText(install_dir)
|
||||
|
||||
def apply(self):
|
||||
|
||||
print("Applying changes")
|
||||
self.saved_game_dir = self.edit_saved_game_dir.text()
|
||||
self.dcs_install_dir = self.edit_dcs_install_dir.text()
|
||||
|
||||
@@ -345,7 +345,6 @@ class QSettingsWindow(QDialog):
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def initCheatLayout(self):
|
||||
|
||||
self.cheatPage = QWidget()
|
||||
self.cheatLayout = QVBoxLayout()
|
||||
self.cheatPage.setLayout(self.cheatLayout)
|
||||
|
||||
@@ -18,7 +18,6 @@ class QAircraftChart(QFrame):
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def generateUnitCharts(self):
|
||||
|
||||
self.alliedAircraft = [
|
||||
d.allied_units.aircraft_count for d in self.game.game_stats.data_per_turn
|
||||
]
|
||||
|
||||
@@ -18,7 +18,6 @@ class QArmorChart(QFrame):
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def generateUnitCharts(self):
|
||||
|
||||
self.alliedArmor = [
|
||||
d.allied_units.vehicles_count for d in self.game.game_stats.data_per_turn
|
||||
]
|
||||
|
||||
@@ -1,53 +1,56 @@
|
||||
altgraph==0.17.3
|
||||
anyio==3.6.2
|
||||
asgiref==3.6.0
|
||||
attrs==22.2.0
|
||||
black==22.12.0
|
||||
certifi==2023.7.22
|
||||
cfgv==3.3.1
|
||||
click==8.1.3
|
||||
altgraph==0.17.4
|
||||
annotated-types==0.6.0
|
||||
anyio==3.7.1
|
||||
asgiref==3.7.2
|
||||
attrs==23.1.0
|
||||
black==23.11.0
|
||||
certifi==2023.11.17
|
||||
cfgv==3.4.0
|
||||
click==8.1.7
|
||||
colorama==0.4.6
|
||||
coverage==7.0.5
|
||||
distlib==0.3.6
|
||||
exceptiongroup==1.1.0
|
||||
Faker==15.3.4
|
||||
fastapi==0.95.2
|
||||
filelock==3.9.0
|
||||
coverage==7.3.2
|
||||
distlib==0.3.7
|
||||
exceptiongroup==1.2.0
|
||||
Faker==20.1.0
|
||||
fastapi==0.104.1
|
||||
filelock==3.13.1
|
||||
future==0.18.3
|
||||
h11==0.14.0
|
||||
httptools==0.5.0
|
||||
identify==2.5.11
|
||||
idna==3.4
|
||||
iniconfig==1.1.1
|
||||
Jinja2==3.1.2
|
||||
MarkupSafe==2.1.1
|
||||
mypy==1.2.0
|
||||
httptools==0.6.1
|
||||
identify==2.5.32
|
||||
idna==3.6
|
||||
iniconfig==2.0.0
|
||||
Jinja2==3.1.3
|
||||
MarkupSafe==2.1.3
|
||||
mypy==1.7.1
|
||||
mypy-extensions==1.0.0
|
||||
nodeenv==1.7.0
|
||||
numpy==1.25.1
|
||||
packaging==22.0
|
||||
pathspec==0.10.3
|
||||
pefile==2022.5.30
|
||||
Pillow==10.0.1
|
||||
platformdirs==2.6.2
|
||||
pluggy==1.0.0
|
||||
pre-commit==2.21.0
|
||||
pydantic==1.10.7
|
||||
git+https://github.com/pydcs/dcs@f8232606a21eaef82af7ba78c2013403da4a86f5#egg=pydcs
|
||||
pyinstaller==5.13.0
|
||||
nodeenv==1.8.0
|
||||
numpy==1.26.2
|
||||
packaging==23.2
|
||||
pathspec==0.11.2
|
||||
pefile==2023.2.7
|
||||
Pillow==10.2.0
|
||||
platformdirs==4.0.0
|
||||
pluggy==1.3.0
|
||||
pre-commit==3.5.0
|
||||
pydantic==2.5.2
|
||||
pydantic-settings==2.1.0
|
||||
pydantic_core==2.14.5
|
||||
pydcs @ git+https://github.com/pydcs/dcs@7eeec23ea428846ebbbd0ea4c746f8eafea04e0d
|
||||
pyinstaller==5.13.1
|
||||
pyinstaller-hooks-contrib==2023.6
|
||||
pyproj==3.4.1
|
||||
pyproj==3.6.1
|
||||
PySide6==6.4.1
|
||||
PySide6-Addons==6.4.1
|
||||
PySide6-Essentials==6.4.1
|
||||
pytest==7.2.0
|
||||
pytest-cov==4.0.0
|
||||
pytest-mock==3.10.0
|
||||
pytest==7.4.3
|
||||
pytest-cov==4.1.0
|
||||
pytest-mock==3.12.0
|
||||
python-dateutil==2.8.2
|
||||
python-dotenv==0.21.0
|
||||
python-dotenv==1.0.0
|
||||
pywin32-ctypes==0.2.2
|
||||
PyYAML==6.0
|
||||
shapely==2.0.1
|
||||
PyYAML==6.0.1
|
||||
shapely==2.0.2
|
||||
shiboken6==6.4.1
|
||||
six==1.16.0
|
||||
sniffio==1.3.0
|
||||
@@ -57,10 +60,10 @@ tomli==2.0.1
|
||||
types-Jinja2==2.11.9
|
||||
types-MarkupSafe==1.1.10
|
||||
types-Pillow==9.3.0.4
|
||||
types-PyYAML==6.0.12.2
|
||||
types-tabulate==0.9.0.0
|
||||
typing_extensions==4.4.0
|
||||
uvicorn==0.20.0
|
||||
virtualenv==20.17.1
|
||||
watchfiles==0.18.1
|
||||
websockets==10.4
|
||||
types-PyYAML==6.0.12.12
|
||||
types-tabulate==0.9.0.3
|
||||
typing_extensions==4.8.0
|
||||
uvicorn==0.24.0.post1
|
||||
virtualenv==20.24.7
|
||||
watchfiles==0.21.0
|
||||
websockets==12.0
|
||||
|
||||
Binary file not shown.
@@ -1,14 +1,18 @@
|
||||
---
|
||||
name: Syria - The Falcon went over the mountain
|
||||
theater: Syria
|
||||
authors: Sith1144
|
||||
authors: Sith1144, updated by Astro
|
||||
description: <p>Campaign about a task force attacking northern Syria from Incirlik. Culling recommended. Do you love SEAD? Know no greater joy in than showing SAMs who truly rules the skies? this is the campaign for you!</p>
|
||||
recommended_player_faction: USA 2005
|
||||
recommended_enemy_faction: Syria 2012'ish
|
||||
recommended_start_date: 2012-06-01
|
||||
recommended_player_money: 400
|
||||
recommended_enemy_money: 400
|
||||
recommended_player_income_multiplier: 1.0
|
||||
recommended_enemy_income_multiplier: 1.0
|
||||
miz: TheFalconWentOverTheMountain.miz
|
||||
performance: 2
|
||||
version: "10.4"
|
||||
version: "11.0"
|
||||
advanced_iads: true # Campaign has connection_nodes / power_sources / command_centers
|
||||
#IADS: EWR and C2 get power generators. batteries have their own generators.
|
||||
iads_config:
|
||||
@@ -43,7 +47,7 @@ iads_config:
|
||||
- YellowEWRS: #mountainrange (center)
|
||||
- YellowPPW
|
||||
- YellowControlW
|
||||
- YellowEWRC: # internal
|
||||
- YellowEWRC: #internal
|
||||
- HamidiyeControl
|
||||
- GaziantepControl
|
||||
- GaziantepPP
|
||||
@@ -243,94 +247,201 @@ iads_config:
|
||||
- Aleppo Control
|
||||
- Aleppo Control:
|
||||
- Aleppo Power
|
||||
control_points:
|
||||
From Reserves:
|
||||
ferry_only: true
|
||||
squadrons:
|
||||
#Incirlik
|
||||
#Incirlik (120)
|
||||
16:
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
size: 12
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- primary: DEAD
|
||||
size: 12
|
||||
- primary: Strike
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15E Strike Eagle
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- primary: CAS
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
size: 8
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
size: 8
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
size: 8
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- F-117A Nighthawk
|
||||
size: 4
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-1B Lancer
|
||||
size: 2
|
||||
- primary: AEW&C
|
||||
secondary: any
|
||||
size: 1
|
||||
- primary: Refueling
|
||||
secondary: any
|
||||
aircraft:
|
||||
- KC-135 Stratotanker MPRS
|
||||
size: 1
|
||||
#carrier
|
||||
Blue Carrier:
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-14B Tomcat
|
||||
size: 12
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- F-14B Tomcat
|
||||
- primary: Strike
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- primary: Strike
|
||||
size: 12
|
||||
- primary: AEW&C
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
size: 1
|
||||
- primary: Refueling
|
||||
secondary: any
|
||||
size: 2
|
||||
#LHA
|
||||
Blue LHA:
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- AV-8B Harrier II Night Attack
|
||||
#Abu Al-Duhur
|
||||
1:
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- MiG-29S Fulcrum-C
|
||||
- primary: BAI
|
||||
aircraft:
|
||||
- Su-24M Fencer-D
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- Su-30 Flanker-C
|
||||
#Hatay
|
||||
size: 8
|
||||
#Ferry-only
|
||||
From Reserves:
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 12
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
size: 12
|
||||
# REDFOR squadrons
|
||||
# Smaller number of modern fighters in forward airfields (Hatay, Minakh and Gaziantep)
|
||||
# Larger number of older fighters in the rear (Aleppo, Abu Al-Duhur and Jirah)
|
||||
# CAS aircraft distributed over all airfields, helos more forward
|
||||
# Aleppo is main airfield for AWACS, Refueling and Transport, for protection it has some modern fighters
|
||||
#Hatay (10)
|
||||
15:
|
||||
- primary: BARCAP
|
||||
aircraft:
|
||||
- MiG-23MLD Flogger-K
|
||||
#Aleppo
|
||||
27:
|
||||
- primary: AEW&C
|
||||
- primary: Refueling
|
||||
secondary: any
|
||||
aircraft:
|
||||
- MiG-29S Fulcrum-C
|
||||
size: 4
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-25 Frogfoot
|
||||
size: 4
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Mi-24P Hind-F
|
||||
- primary: Transport
|
||||
#Jirah
|
||||
17:
|
||||
size: 2
|
||||
#Minakh (20)
|
||||
26:
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-30 Flanker-C
|
||||
size: 8
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-34 Fullback
|
||||
size: 4
|
||||
- primary: Strike
|
||||
#Gaziantep
|
||||
11:
|
||||
secondary: any
|
||||
size: 4
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-25 Frogfoot
|
||||
- Su-25 Frogfoot
|
||||
size: 4
|
||||
#Gaziantep (12)
|
||||
11:
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- MiG-29S Fulcrum-C
|
||||
size: 4
|
||||
- primary: CAS
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-25 Frogfoot
|
||||
size: 4
|
||||
- primary: Strike
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-24M Fencer-D
|
||||
size: 4
|
||||
#Aleppo (14)
|
||||
27:
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- MiG-29S Fulcrum-C
|
||||
size: 4
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- MiG-23MLD Flogger-K
|
||||
size: 4
|
||||
- primary: AEW&C
|
||||
secondary: any
|
||||
size: 1
|
||||
- primary: Refueling
|
||||
secondary: any
|
||||
size: 1
|
||||
- primary: Transport
|
||||
secondary: any
|
||||
size: 2
|
||||
#Abu Al-Duhur (36)
|
||||
1:
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- MiG-23MLD Flogger-K
|
||||
size: 12
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-34 Fullback
|
||||
size: 8
|
||||
- primary: Strike
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-24M Fencer-D
|
||||
size: 8
|
||||
#Kuweires (37) ID: 31
|
||||
#Jirah (28)
|
||||
17:
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- MiG-23MLD Flogger-K
|
||||
size: 12
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Su-24M Fencer-D
|
||||
size: 8
|
||||
#
|
||||
# air-to-air: Barcap, Tarcap, Escort, and Fighter Sweep
|
||||
BIN
resources/campaigns/battle_for_no_mans_land.miz
Normal file
BIN
resources/campaigns/battle_for_no_mans_land.miz
Normal file
Binary file not shown.
69
resources/campaigns/battle_for_no_mans_land.yaml
Normal file
69
resources/campaigns/battle_for_no_mans_land.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: Falklands - Battle for No Man's Land
|
||||
theater: Falklands
|
||||
authors: Starfire
|
||||
recommended_player_faction: USA 2005
|
||||
recommended_enemy_faction: Private Military Company - Russian (Hard)
|
||||
description:
|
||||
<p><strong>Note:</strong> This campaign was designed for helicopters.</p><p>
|
||||
Set against the rugged and windswept backdrop of the Falkland Islands,
|
||||
this fictional campaign scenario unfolds with a dramatic dawn sneak attack
|
||||
on RAF Mount Pleasant Airbase. Orchestrated by a Russia-backed private
|
||||
military company, the deadly offensive with helicopter gunships and ground troops
|
||||
has left the airbase's runways in ruins and its defences obliterated. This brutal
|
||||
incursion resulted in significant casualties among the RAF personnel, with many
|
||||
killed or wounded in the unexpected onslaught. The carrier HMS Queen Elizabeth and
|
||||
its task force are on their way to evacuate the survivors and retake Mount Pleasant.
|
||||
However, they are eight days away at full steam.</p><p>
|
||||
Amidst this chaos, a beacon of hope emerges in the heart of the Falklands. At Port
|
||||
Stanley, a small detachment of US military personnel, including helicopter pilots
|
||||
and armor units, find themselves inadvertently thrust into the fray. Originally at
|
||||
Port Stanley for some R&R following a training exercise, these soldiers now face
|
||||
an unexpected and urgent call to action. Their mission is daunting but clear - to
|
||||
prevent the capture of Port Stanley and liberate East Falkland from the clutches
|
||||
of the PMC forces.</p><p>
|
||||
This small group must strategically push the PMC forces back through the treacherous
|
||||
valley lying between Wickham Heights and the Onion Ranges, an area ominously known
|
||||
as No Man's Land. Their plan involves a daring assault to destroy the enemy's
|
||||
helicopter gunships stationed at San Carlos FOB. Following this, they aim to force
|
||||
the PMC ground forces into a strategic retreat southward, along the 1.6 mile wide
|
||||
isthmus into Lafonia. This calculated offensive is designed to create a defensible
|
||||
position at Goose Green on the narrow isthmus, which can be held against a numerically
|
||||
superior force until the arrival of Big Lizzie.</p>
|
||||
miz: battle_for_no_mans_land.miz
|
||||
performance: 1
|
||||
recommended_start_date: 2001-11-10
|
||||
version: "11.0"
|
||||
squadrons:
|
||||
#Port Stanley
|
||||
1:
|
||||
- primary: DEAD
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
size: 6
|
||||
- primary: BAI
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
size: 6
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
aircraft:
|
||||
- UH-60L
|
||||
- UH-60A
|
||||
size: 4
|
||||
#San Carlos FOB
|
||||
3:
|
||||
- primary: BAI
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Mi-24P Hind-F
|
||||
size: 6
|
||||
#Goose Green
|
||||
24:
|
||||
- primary: DEAD
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Ka-50 Hokum (Blackshark 3)
|
||||
size: 6
|
||||
@@ -1,160 +1,155 @@
|
||||
---
|
||||
name: Sinai - Exercise Bright Star
|
||||
theater: Sinai
|
||||
authors: Starfire
|
||||
recommended_player_faction: Bluefor Modern
|
||||
recommended_enemy_faction: Egypt 2000s
|
||||
description:
|
||||
<p>For over 4 decades, the United States and Egypt have run a series of
|
||||
biannual joint military exercises called Bright Star. Over the years, the
|
||||
number of participating countries has grown substantially. Exercise Bright
|
||||
Star 2025 boasts 8 participant nations and 14 observer nations. The United
|
||||
States and a portion of the exercise coalition will play the part of a
|
||||
fictional hostile nation dubbed Orangeland, staging a mock invasion against
|
||||
Cairo. Israel, having for the first time accepted the invitation to observe,
|
||||
is hosting the aggressor faction of the exercise coalition at its
|
||||
airfields.</p>
|
||||
miz: exercise_bright_star.miz
|
||||
performance: 1
|
||||
recommended_start_date: 2025-09-01
|
||||
version: "11.0"
|
||||
squadrons:
|
||||
Blue CV-1:
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
size: 24
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2C Hawkeye
|
||||
size: 2
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- S-3B Tanker
|
||||
size: 4
|
||||
Bombers from RAF Fairford:
|
||||
- primary: Anti-ship
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-52H Stratofortress
|
||||
size: 8
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-1B Lancer
|
||||
size: 8
|
||||
# Hatzerim (141)
|
||||
7:
|
||||
- primary: Escort
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
size: 20
|
||||
- primary: OCA/Runway
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
size: 8
|
||||
- primary: Strike
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15E Strike Eagle
|
||||
size: 8
|
||||
- primary: DEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 20
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
aircraft:
|
||||
- JF-17 Thunder
|
||||
size: 16
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Mirage 2000C
|
||||
size: 12
|
||||
# Kedem
|
||||
12:
|
||||
- primary: Transport
|
||||
secondary: any
|
||||
aircraft:
|
||||
- CH-47D
|
||||
size: 20
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
aircraft:
|
||||
- UH-60L
|
||||
- UH-60A
|
||||
size: 4
|
||||
# Nevatim (106)
|
||||
8:
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
size: 2
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
size: 2
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
size: 8
|
||||
# Melez (30)
|
||||
5:
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Ka-50 Hokum (Blackshark 3)
|
||||
size: 4
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Mirage 2000C
|
||||
size: 12
|
||||
- primary: Escort
|
||||
secondary: any
|
||||
aircraft:
|
||||
- MiG-21bis Fishbed-N
|
||||
size: 12
|
||||
# Wadi al Jandali (72)
|
||||
13:
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2C Hawkeye
|
||||
size: 2
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-4E Phantom II
|
||||
size: 20
|
||||
- primary: DEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 20
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Mi-24P Hind-F
|
||||
size: 4
|
||||
- primary: OCA/Aircraft
|
||||
secondary: any
|
||||
aircraft:
|
||||
- SA 342L Gazelle
|
||||
size: 4
|
||||
# Cairo West (95)
|
||||
18:
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
size: 8
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- MiG-29S Fulcrum-C
|
||||
---
|
||||
name: Sinai - Exercise Bright Star
|
||||
theater: Sinai
|
||||
authors: Starfire
|
||||
recommended_player_faction: Bluefor Modern
|
||||
recommended_enemy_faction: Egypt 2000s
|
||||
description:
|
||||
<p>For over 4 decades, the United States and Egypt have run a series of
|
||||
biannual joint military exercises called Bright Star. Over the years, the
|
||||
number of participating countries has grown substantially. Exercise Bright
|
||||
Star 2025 boasts 8 participant nations and 14 observer nations. The United
|
||||
States and a portion of the exercise coalition will play the part of a
|
||||
fictional hostile nation dubbed Orangeland, staging a mock invasion against
|
||||
Cairo. Israel, having for the first time accepted the invitation to observe,
|
||||
is hosting the aggressor faction of the exercise coalition at its
|
||||
airfields.</p>
|
||||
miz: exercise_bright_star.miz
|
||||
performance: 1
|
||||
recommended_start_date: 2025-09-01
|
||||
version: "11.0"
|
||||
squadrons:
|
||||
Blue CV-1:
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
size: 24
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2D Advanced Hawkeye
|
||||
size: 2
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- S-3B Tanker
|
||||
size: 4
|
||||
Bombers from RAF Fairford:
|
||||
- primary: Anti-ship
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-52H Stratofortress
|
||||
size: 8
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-1B Lancer
|
||||
size: 8
|
||||
# Hatzerim (141)
|
||||
7:
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
size: 6
|
||||
- primary: Escort
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
size: 20
|
||||
- primary: OCA/Runway
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
size: 16
|
||||
- primary: DEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 20
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
aircraft:
|
||||
- JF-17 Thunder
|
||||
size: 16
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Mirage 2000C
|
||||
size: 12
|
||||
# Kedem
|
||||
12:
|
||||
- primary: Transport
|
||||
secondary: any
|
||||
aircraft:
|
||||
- CH-47D
|
||||
size: 20
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
aircraft:
|
||||
- UH-60L
|
||||
- UH-60A
|
||||
size: 4
|
||||
# Nevatim (106)
|
||||
# 8:
|
||||
# - primary: AEW&C
|
||||
# aircraft:
|
||||
# - E-3A
|
||||
# size: 2
|
||||
# - primary: Refueling
|
||||
# aircraft:
|
||||
# - KC-135 Stratotanker
|
||||
# size: 2
|
||||
# Melez (30)
|
||||
5:
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Ka-50 Hokum (Blackshark 3)
|
||||
size: 4
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Mirage 2000C
|
||||
size: 12
|
||||
- primary: Escort
|
||||
secondary: any
|
||||
aircraft:
|
||||
- MiG-21bis Fishbed-N
|
||||
size: 12
|
||||
# Wadi al Jandali (72)
|
||||
13:
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2C Hawkeye
|
||||
size: 2
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-4E Phantom II
|
||||
size: 20
|
||||
- primary: DEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 20
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Mi-24P Hind-F
|
||||
size: 4
|
||||
- primary: OCA/Aircraft
|
||||
secondary: any
|
||||
aircraft:
|
||||
- SA 342L Gazelle
|
||||
size: 4
|
||||
# Cairo West (95)
|
||||
18:
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
size: 8
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- MiG-29S Fulcrum-C
|
||||
size: 20
|
||||
Binary file not shown.
@@ -17,17 +17,29 @@ performance: 1
|
||||
recommended_start_date: 2011-02-24
|
||||
version: "11.0"
|
||||
squadrons:
|
||||
Bombers from Minot AFB:
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-52H Stratofortress
|
||||
size: 4
|
||||
Bombers from Ellsworth AFB:
|
||||
- primary: OCA/Runway
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-1B Lancer
|
||||
size: 4
|
||||
# Tonopah Airport
|
||||
17:
|
||||
- primary: TARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-15C Eagle
|
||||
size: 12
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
size: 12
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
@@ -58,7 +70,8 @@ squadrons:
|
||||
- primary: Air Assault
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
- UH-60L
|
||||
- UH-60A
|
||||
size: 2
|
||||
# Groom Lake
|
||||
2:
|
||||
|
||||
@@ -19,6 +19,7 @@ recommended_player_faction:
|
||||
- F-14B Tomcat
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- S-3B Viking
|
||||
- UH-60L
|
||||
- UH-60A
|
||||
awacs:
|
||||
- E-2C Hawkeye
|
||||
@@ -107,8 +108,9 @@ squadrons:
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
aircraft:
|
||||
- UH-60L
|
||||
- UH-60A
|
||||
size: 4
|
||||
size: 6
|
||||
#Stoney Cross (39)
|
||||
58:
|
||||
- primary: OCA/Runway
|
||||
|
||||
Binary file not shown.
@@ -35,11 +35,6 @@ squadrons:
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
size: 8
|
||||
- primary: BAI
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- F-15E Strike Eagle
|
||||
size: 8
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
@@ -90,12 +85,12 @@ squadrons:
|
||||
- AV-8B Harrier II Night Attack
|
||||
size: 18
|
||||
- primary: Air Assault
|
||||
secondary: air-to-ground
|
||||
secondary: any
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
size: 2
|
||||
Bombers from Edwards AFB:
|
||||
- primary: DEAD
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-52H Stratofortress
|
||||
|
||||
BIN
resources/campaigns/operation_aegean_aegis.miz
Normal file
BIN
resources/campaigns/operation_aegean_aegis.miz
Normal file
Binary file not shown.
100
resources/campaigns/operation_aegean_aegis.yaml
Normal file
100
resources/campaigns/operation_aegean_aegis.yaml
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
name: Syria - Operation Aegean Aegis
|
||||
theater: Syria
|
||||
authors: Starfire
|
||||
recommended_player_faction: USA 2005
|
||||
recommended_enemy_faction: Turkey 2005
|
||||
description:
|
||||
<p><strong>Note:</strong> This fictional campaign was designed for the Apache
|
||||
and Harrier. It requires manual flight planning. While enemy aircraft are present
|
||||
at airfields, there will be no enemy flights as their aircraft are grounded.</p>
|
||||
<p>
|
||||
In a sudden and alarming escalation of tensions in Cyprus, the Anatolian Order,
|
||||
a North Cypriot insurgent faction, has carried out a bold night-time assault on
|
||||
three crucial airfields in the Republic of Cyprus; Paphos, Akrotiri, and Larnaca.
|
||||
The insurgents, equipped with stolen surplus Turkish military hardware, used
|
||||
chemical weapons in their attack, forcing the evacuation of all three airfields.
|
||||
Notably, the capture of Akrotiri, a British Overseas Territory hosting a Royal
|
||||
Air Force base, has drawn significant international attention and concern.</p>
|
||||
<p>
|
||||
The EU has strongly condemned this unprovoked attack against one of its member
|
||||
states. Turkey, despite its historical connections with North Cyprus, has also
|
||||
denounced the Anatolian Order's actions and is investigating how its aircraft,
|
||||
ground vehicles, and weaponry held in storage ended up in insurgent hands.</p>
|
||||
<p>
|
||||
Amidst the crisis, a lone US Navy LHA, strategically positioned in the Aegean Sea,
|
||||
is preparing a response to the crisis. Its mission is to deploy Apache helicopters
|
||||
to neutralise the hastily erected air defenses around the captured airfields, before
|
||||
Harrier jumpjets neutralise the Anatolian Order's aircraft. These aircraft, a
|
||||
selection of mothballed Turkish F-4s and helicopters, are currently grounded due to
|
||||
lack of suitable fuel and spare parts. It is imperative that they are dealt with
|
||||
swiftly before ground troops are air-lifted in to reclaim the airfields.</p>
|
||||
<p>
|
||||
The operation's final phase involves targeting North Cyprus's only airport at Ercan.
|
||||
The plan is to bomb its runway, preventing any further airborne reinforcements by the
|
||||
insurgents. However, due to the air defenses established along the northern edge of
|
||||
the Green Line (the UN-patrolled demilitarised zone) there are strict advisories against
|
||||
overflying North Cyprus unless absolutely necessary, to minimise the risk of losses.
|
||||
</p>
|
||||
miz: operation_aegean_aegis.miz
|
||||
performance: 1
|
||||
recommended_start_date: 2017-04-20
|
||||
recommended_player_money: 1000
|
||||
recommended_enemy_money: 0
|
||||
recommended_player_income_multiplier: 1.0
|
||||
recommended_enemy_income_multiplier: 0.0
|
||||
version: "11.0"
|
||||
squadrons:
|
||||
#Tarawa Class LHA
|
||||
Blue-LHA:
|
||||
- primary: DEAD
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- AH-64D Apache Longbow
|
||||
size: 12
|
||||
- primary: DEAD
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- AV-8B Harrier II Night Attack
|
||||
size: 6
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
size: 2
|
||||
#Paphos
|
||||
46:
|
||||
- primary: DEAD
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- F-4E Phantom II
|
||||
size: 12
|
||||
#Akrotiri
|
||||
44:
|
||||
- primary: BAI
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- AH-1W SuperCobra
|
||||
size: 4
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- OH-58D Kiowa Warrior
|
||||
size: 4
|
||||
- primary: Air Assault
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- UH-60A
|
||||
size: 4
|
||||
#Larnaca
|
||||
47:
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
size: 6
|
||||
#Ercan
|
||||
49:
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- CH-47D
|
||||
size: 6
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user