mirror of
https://github.com/dcs-retribution/dcs-retribution.git
synced 2025-11-10 15:41:24 +00:00
Merge remote-tracking branch 'remotes/dcs-retribution/dcs-retribution/dev' into pretense-generator
This commit is contained in:
commit
c795ed01a0
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -31,9 +31,8 @@ body:
|
|||||||
If the bug was found in a test/development build, select "Test build" or "Development build"
|
If the bug was found in a test/development build, select "Test build" or "Development build"
|
||||||
and provide a link to the applicable build in the field below.
|
and provide a link to the applicable build in the field below.
|
||||||
options:
|
options:
|
||||||
- v1.2.1
|
- v1.3.1
|
||||||
- Test build
|
- Test build
|
||||||
- Development build
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Test/Development build
|
label: Test/Development build
|
||||||
|
|||||||
3
.github/ISSUE_TEMPLATE/new-game-bug.yml
vendored
3
.github/ISSUE_TEMPLATE/new-game-bug.yml
vendored
@ -39,9 +39,8 @@ body:
|
|||||||
If the bug was found in a test/development build, select "Test build" or "Development build"
|
If the bug was found in a test/development build, select "Test build" or "Development build"
|
||||||
and provide a link to the applicable build in the field below.
|
and provide a link to the applicable build in the field below.
|
||||||
options:
|
options:
|
||||||
- 1.0.0
|
- 1.3.1
|
||||||
- Test build
|
- Test build
|
||||||
- Development build
|
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: Test/Development build
|
label: Test/Development build
|
||||||
|
|||||||
@ -6,7 +6,7 @@ runs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.11"
|
||||||
cache: pip
|
cache: pip
|
||||||
|
|
||||||
- name: Install environment
|
- name: Install environment
|
||||||
|
|||||||
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
|||||||
include_image: true
|
include_image: true
|
||||||
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
|
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
|
||||||
username: GitHub Build Notifier
|
username: GitHub Build Notifier
|
||||||
if: startsWith(github.ref_name, 'test')
|
if: github.repository == 'dcs-retribution/dcs-retribution' && startsWith(github.ref_name, 'test')
|
||||||
|
|
||||||
- name: Send status to Discord
|
- name: Send status to Discord
|
||||||
uses: stegzilla/discord-notify@v2
|
uses: stegzilla/discord-notify@v2
|
||||||
@ -60,4 +60,4 @@ jobs:
|
|||||||
include_image: true
|
include_image: true
|
||||||
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
|
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
|
||||||
username: GitHub Build Notifier
|
username: GitHub Build Notifier
|
||||||
if: github.ref_name == 'dev'
|
if: github.repository == 'dcs-retribution/dcs-retribution' && github.ref_name == 'dev'
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
(Github Readme Banner and Splash screen Artwork by Andriy Dankovych, CC BY-SA 4.0)
|
(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-retribution/dcs-retribution/releases)
|
[](https://github.com/dcs-retribution/dcs-retribution/releases)
|
||||||
|
|
||||||
|
|||||||
36
changelog.md
36
changelog.md
@ -1,3 +1,30 @@
|
|||||||
|
# Retribution v1.4.0
|
||||||
|
#### Note: Re-save your missions in DCS' Mission Editor to avoid possible crashes due to datalink (usually the case when F-16C blk50s are used) when hosting missions on a dedicated server.
|
||||||
|
|
||||||
|
## Features/Improvements
|
||||||
|
* **[Payload Editor]** Ability to configure liveries on flight/flight-member level
|
||||||
|
* **[Factions]** Support for definitions in yml/yaml format
|
||||||
|
* **[Campaigns/Factions]** Support for inline recommended faction in campaign's yaml file
|
||||||
|
* **[Squadrons]** Ability to define a livery-set for each squadron from which Retribution will randomly choose during mission generation
|
||||||
|
* **[Modding]** Updated support for F/A-18E/F/G mod version 2.2.5
|
||||||
|
* **[Campaign Setup]** Allow adjustments to naval TGOs (except carriers) on turn 0
|
||||||
|
* **[Campaign Design]** Ability to configure specific carrier names & types in campaign's yaml file
|
||||||
|
* **[Mission Generation]** Ability to inject custom kneeboards
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
* **[UI/UX]** A-10A flights can be edited again.
|
||||||
|
* **[Mission Generation]** IADS bug sometimes triggering "no skynet usable units" error during mission generation
|
||||||
|
* **[New Game Wizard]** Campaign errors show a dialog again and avoid CTDs
|
||||||
|
|
||||||
|
|
||||||
|
# Retribution v1.3.1
|
||||||
|
#### Note: Re-save your missions in DCS' Mission Editor to avoid possible crashes due to datalink (usually the case when F-16C blk50s are used) when hosting missions on a dedicated server.
|
||||||
|
|
||||||
|
## Fixes
|
||||||
|
* **[UX]** Fix save-compatibility issue
|
||||||
|
* **[UX]** Avoid crash on startup due to incompatible save
|
||||||
|
|
||||||
|
|
||||||
# Retribution v1.3.0
|
# Retribution v1.3.0
|
||||||
#### Note: Re-save your missions in DCS' Mission Editor to avoid possible crashes due to datalink (usually the case when F-16C blk50s are used) when hosting missions on a dedicated server.
|
#### Note: Re-save your missions in DCS' Mission Editor to avoid possible crashes due to datalink (usually the case when F-16C blk50s are used) when hosting missions on a dedicated server.
|
||||||
|
|
||||||
@ -15,6 +42,8 @@
|
|||||||
* **[Modding]** Updated support for F-4B/C Phantom mod to 2.8.7.204
|
* **[Modding]** Updated support for F-4B/C Phantom mod to 2.8.7.204
|
||||||
* **[Modding]** Updated Community A-4E-C mod version support to 2.2.0 release.
|
* **[Modding]** Updated Community A-4E-C mod version support to 2.2.0 release.
|
||||||
* **[Modding]** Added F/A-18E/F Super Hornet AI Tanker mod support (Chiller Juice Studios SuperBug Tanker AI version 1.4)
|
* **[Modding]** Added F/A-18E/F Super Hornet AI Tanker mod support (Chiller Juice Studios SuperBug Tanker AI version 1.4)
|
||||||
|
* **[Modding]** Added VSN Super Étendard mod support (v2.5.5)
|
||||||
|
* **[Modding]** Added F9F Panther mod support (version v2.8.7.101)
|
||||||
* **[Modding]** Updated Irondome support to IDF Assets Pack V1.1, adding support for the David's Sling
|
* **[Modding]** Updated Irondome support to IDF Assets Pack V1.1, adding support for the David's Sling
|
||||||
* **[Radios]** Added HF-FM band for AN/ARC-222
|
* **[Radios]** Added HF-FM band for AN/ARC-222
|
||||||
* **[Radios]** Ability to define preset channels for radios on squadron level (for human pilots only)
|
* **[Radios]** Ability to define preset channels for radios on squadron level (for human pilots only)
|
||||||
@ -41,6 +70,12 @@
|
|||||||
* **[Config]** Preference setting to use custom Liberation payloads instead of prioritizing Retribution's default
|
* **[Config]** Preference setting to use custom Liberation payloads instead of prioritizing Retribution's default
|
||||||
* **[Config]** Preference setting to configure the server-port on which Retribution's back-end will run
|
* **[Config]** Preference setting to configure the server-port on which Retribution's back-end will run
|
||||||
* **[Options]** Made AI jettisoning empty fuel tanks optional (disabled by default)
|
* **[Options]** Made AI jettisoning empty fuel tanks optional (disabled by default)
|
||||||
|
* **[Options]** Add option (so it can be disabled when fixed in DCS) to force air-starts (except for the slots that work) at Nevatim due to https://forum.dcs.world/topic/335545-29-nevatim-ramp-starts-still-bugged/
|
||||||
|
* **[Cheat]** Add cheat option to manually manage REDFOR's TGOs
|
||||||
|
* **[UX]** Buy/Replace TGOs for free before the campaign has started
|
||||||
|
* **[Data]** Ability to define "cruise" & "combat" altitudes for airplanes
|
||||||
|
* **[Options]** Option to randomize altitudes for flights with airplanes
|
||||||
|
* **[Options]** Options to configure/override maximum mission distance for airplanes & helicopters
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
* **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task
|
* **[Mission Generation]** Anti-ship strikes should use "group attack" in their attack-task
|
||||||
@ -266,6 +301,7 @@ BAI/ANTISHIP/DEAD/STRIKE/BARCAP/CAS/OCA/AIR-ASSAULT (main) missions
|
|||||||
* **[UI]** Fixed flight properties UI to support F-15E S4+ laser codes.
|
* **[UI]** Fixed flight properties UI to support F-15E S4+ laser codes.
|
||||||
* **[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 pacakge" offset.
|
||||||
* **[UI]** Fixed bug where changing TOT offsets could result in flight startup times that are in the past.
|
* **[UI]** Fixed bug where changing TOT offsets could result in flight startup times that are in the past.
|
||||||
|
* **[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.
|
||||||
|
|
||||||
# 8.1.0
|
# 8.1.0
|
||||||
|
|
||||||
|
|||||||
13173
client/package-lock.json
generated
13173
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,26 +6,26 @@
|
|||||||
"license": "LGPL-3.0-or-later",
|
"license": "LGPL-3.0-or-later",
|
||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@reduxjs/toolkit": "^1.8.5",
|
"@reduxjs/toolkit": "^1.9.7",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
"@testing-library/react": "^13.4.0",
|
"@testing-library/react": "^13.4.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/jest": "^29.1.2",
|
"@types/jest": "^29.5.12",
|
||||||
"@types/node": "^18.8.3",
|
"@types/node": "^18.19.26",
|
||||||
"@types/react": "^18.0.21",
|
"@types/react": "^18.2.69",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-dom": "^18.2.22",
|
||||||
"@types/react-redux": "^7.1.24",
|
"@types/react-redux": "^7.1.33",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.8",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"esri-leaflet": "^3.0.8",
|
"esri-leaflet": "^3.0.12",
|
||||||
"leaflet": "^1.9.2",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet-ruler": "^1.0.0",
|
"leaflet-ruler": "^1.0.0",
|
||||||
"milsymbol": "^2.0.0",
|
"milsymbol": "^2.2.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-esri-leaflet": "^2.0.1",
|
"react-esri-leaflet": "^2.0.1",
|
||||||
"react-leaflet": "^4.1.0",
|
"react-leaflet": "^4.2.1",
|
||||||
"react-redux": "^8.0.4",
|
"react-redux": "^8.1.3",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"typescript": "~4.8.4"
|
"typescript": "~4.8.4"
|
||||||
},
|
},
|
||||||
@ -61,20 +61,20 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rtk-query/codegen-openapi": "^1.0.0",
|
"@rtk-query/codegen-openapi": "^1.2.0",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/leaflet": "^1.8.0",
|
"@types/leaflet": "^1.9.8",
|
||||||
"@types/redux-logger": "^3.0.9",
|
"@types/redux-logger": "^3.0.13",
|
||||||
"@types/websocket": "^1.0.5",
|
"@types/websocket": "^1.0.10",
|
||||||
"electron": "^22.3.25",
|
"electron": "^22.3.27",
|
||||||
"electron-is-dev": "^2.0.0",
|
"electron-is-dev": "^2.0.0",
|
||||||
"generate-license-file": "^2.0.0",
|
"generate-license-file": "^2.0.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"nth-check": "^2.0.1",
|
"nth-check": "^2.1.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.2",
|
||||||
"wait-on": "^6.0.1"
|
"wait-on": "^7.2.0"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"transformIgnorePatterns": [
|
"transformIgnorePatterns": [
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { Flight } from "../../api/liberationApi";
|
import { Flight } from "../../api/liberationApi";
|
||||||
import { Icon, Point } from "leaflet";
|
import { Icon, Point } from "leaflet";
|
||||||
import { Symbol } from "milsymbol";
|
import ms from "milsymbol";
|
||||||
import { Marker } from "react-leaflet";
|
import { Marker } from "react-leaflet";
|
||||||
|
|
||||||
function iconForFlight(flight: Flight) {
|
function iconForFlight(flight: Flight) {
|
||||||
const symbol = new Symbol(flight.sidc, {
|
const symbol = new ms.Symbol(flight.sidc, {
|
||||||
size: 20,
|
size: 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { ControlPoint } from "../../api/_liberationApi";
|
import { ControlPoint } from "../../api/_liberationApi";
|
||||||
import { Icon, Point } from "leaflet";
|
import { Icon, Point } from "leaflet";
|
||||||
import { Symbol } from "milsymbol";
|
import ms from "milsymbol";
|
||||||
|
|
||||||
export const iconForControlPoint = (cp: ControlPoint) => {
|
export const iconForControlPoint = (cp: ControlPoint) => {
|
||||||
const symbol = new Symbol(cp.sidc, {
|
const symbol = new ms.Symbol(cp.sidc, {
|
||||||
size: 24,
|
size: 24,
|
||||||
colorMode: "Dark",
|
colorMode: "Dark",
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { Flight } from "../../api/liberationApi";
|
import { Flight } from "../../api/liberationApi";
|
||||||
import { useGetCommitBoundaryForFlightQuery } from "../../api/liberationApi";
|
import { useGetCommitBoundaryForFlightQuery } from "../../api/liberationApi";
|
||||||
import WaypointMarker from "../waypointmarker";
|
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";
|
import { Polyline } from "react-leaflet";
|
||||||
|
|
||||||
const BLUE_PATH = "#0084ff";
|
const BLUE_PATH = "#0084ff";
|
||||||
@ -27,16 +28,44 @@ const pathColor = (props: FlightPlanProps) => {
|
|||||||
function FlightPlanPath(props: FlightPlanProps) {
|
function FlightPlanPath(props: FlightPlanProps) {
|
||||||
const color = pathColor(props);
|
const color = pathColor(props);
|
||||||
const waypoints = props.flight.waypoints;
|
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?.bringToFront();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
polylineRef.current?.bringToBack();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (waypoints == null) {
|
if (waypoints == null) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
const points = waypoints
|
const points = waypoints
|
||||||
.filter((waypoint) => waypoint.include_in_path)
|
.filter((waypoint) => waypoint.include_in_path)
|
||||||
.map((waypoint) => waypoint.position);
|
.map((waypoint) => waypoint.position);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Polyline
|
<Polyline
|
||||||
positions={points}
|
positions={points}
|
||||||
pathOptions={{ color: color, interactive: false }}
|
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", () => {
|
it("are not drawn if wrong coalition", () => {
|
||||||
renderWithProviders(<FlightPlansLayer blue={true} />, {
|
renderWithProviders(<FlightPlansLayer blue={true} />, {
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import {
|
|||||||
import { Tgo as TgoModel } from "../../api/liberationApi";
|
import { Tgo as TgoModel } from "../../api/liberationApi";
|
||||||
import SplitLines from "../splitlines/SplitLines";
|
import SplitLines from "../splitlines/SplitLines";
|
||||||
import { Icon, Point } from "leaflet";
|
import { Icon, Point } from "leaflet";
|
||||||
import { Symbol as MilSymbol } from "milsymbol";
|
import ms from "milsymbol";
|
||||||
import { Marker, Tooltip } from "react-leaflet";
|
import { Marker, Tooltip } from "react-leaflet";
|
||||||
|
|
||||||
function iconForTgo(cp: TgoModel) {
|
function iconForTgo(cp: TgoModel) {
|
||||||
const symbol = new MilSymbol(cp.sidc, {
|
const symbol = new ms.Symbol(cp.sidc, {
|
||||||
size: 24,
|
size: 24,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,10 @@
|
|||||||
# -- Project information -----------------------------------------------------
|
# -- Project information -----------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||||
|
|
||||||
project = "DCS Liberation"
|
project = "DCS Retribution"
|
||||||
copyright = "2023, DCS Liberation Team"
|
copyright = "2024, DCS Retribution Team"
|
||||||
author = "DCS Liberation Team"
|
author = "DCS Retribution Team"
|
||||||
release = "7.0.0"
|
release = "1.4.0"
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import random
|
||||||
import uuid
|
import uuid
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
@ -9,6 +10,7 @@ from dcs import Point
|
|||||||
from dcs.planes import C_101CC, C_101EB, Su_33, FA_18C_hornet
|
from dcs.planes import C_101CC, C_101EB, Su_33, FA_18C_hornet
|
||||||
|
|
||||||
from game.dcs.aircrafttype import AircraftType
|
from game.dcs.aircrafttype import AircraftType
|
||||||
|
from game.theater import ControlPoint, MissionTarget
|
||||||
from pydcs_extensions.hercules.hercules import Hercules
|
from pydcs_extensions.hercules.hercules import Hercules
|
||||||
from .flightmembers import FlightMembers
|
from .flightmembers import FlightMembers
|
||||||
from .flightroster import FlightRoster
|
from .flightroster import FlightRoster
|
||||||
@ -33,7 +35,6 @@ if TYPE_CHECKING:
|
|||||||
from game.sim.gameupdateevents import GameUpdateEvents
|
from game.sim.gameupdateevents import GameUpdateEvents
|
||||||
from game.sim.simulationresults import SimulationResults
|
from game.sim.simulationresults import SimulationResults
|
||||||
from game.squadrons import Squadron, Pilot
|
from game.squadrons import Squadron, Pilot
|
||||||
from game.theater import ControlPoint
|
|
||||||
from game.transfers import TransferOrder
|
from game.transfers import TransferOrder
|
||||||
from game.data.weapons import WeaponType
|
from game.data.weapons import WeaponType
|
||||||
from .flightmember import FlightMember
|
from .flightmember import FlightMember
|
||||||
@ -88,6 +89,7 @@ class Flight(
|
|||||||
|
|
||||||
self.initialize_fuel()
|
self.initialize_fuel()
|
||||||
self.use_same_loadout_for_all_members = True
|
self.use_same_loadout_for_all_members = True
|
||||||
|
self.use_same_livery_for_all_members = True
|
||||||
|
|
||||||
# Only used by transport missions.
|
# Only used by transport missions.
|
||||||
self.cargo = cargo
|
self.cargo = cargo
|
||||||
@ -123,6 +125,11 @@ class Flight(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# altitude offset for planes
|
||||||
|
offset_factor = self.coalition.game.settings.max_plane_altitude_offset
|
||||||
|
offset_factor = random.randint(0, offset_factor)
|
||||||
|
self.plane_altitude_offset = 1000 * offset_factor * random.choice([-1, 1])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available_callsigns(self) -> List[str]:
|
def available_callsigns(self) -> List[str]:
|
||||||
callsigns = set()
|
callsigns = set()
|
||||||
@ -218,6 +225,13 @@ class Flight(
|
|||||||
def points(self) -> List[FlightWaypoint]:
|
def points(self) -> List[FlightWaypoint]:
|
||||||
return self.flight_plan.waypoints[1:]
|
return self.flight_plan.waypoints[1:]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def custom_targets(self) -> List[MissionTarget]:
|
||||||
|
return [
|
||||||
|
MissionTarget(wpt.name, wpt.position)
|
||||||
|
for wpt in self.flight_plan.layout.custom_waypoints
|
||||||
|
]
|
||||||
|
|
||||||
def position(self) -> Point:
|
def position(self) -> Point:
|
||||||
return self.state.estimate_position()
|
return self.state.estimate_position()
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
from game.ato.loadouts import Loadout
|
from game.ato.loadouts import Loadout
|
||||||
from game.lasercodes import LaserCode
|
from game.lasercodes import LaserCode
|
||||||
@ -17,6 +17,7 @@ class FlightMember:
|
|||||||
self.tgp_laser_code: LaserCode | None = None
|
self.tgp_laser_code: LaserCode | None = None
|
||||||
self.weapon_laser_code: LaserCode | None = None
|
self.weapon_laser_code: LaserCode | None = None
|
||||||
self.properties: dict[str, bool | float | int] = {}
|
self.properties: dict[str, bool | float | int] = {}
|
||||||
|
self.livery: Optional[str] = None
|
||||||
|
|
||||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||||
if "tgp_laser_code" not in state:
|
if "tgp_laser_code" not in state:
|
||||||
|
|||||||
@ -95,6 +95,13 @@ class FlightMembers(IFlightRoster):
|
|||||||
# across all flight members.
|
# across all flight members.
|
||||||
member.loadout = loadout
|
member.loadout = loadout
|
||||||
|
|
||||||
|
def use_same_livery_for_all_members(self) -> None:
|
||||||
|
if not self.members:
|
||||||
|
return
|
||||||
|
livery = self.members[0].livery
|
||||||
|
for member in self.members[1:]:
|
||||||
|
member.livery = livery
|
||||||
|
|
||||||
def use_distinct_loadouts_for_each_member(self) -> None:
|
def use_distinct_loadouts_for_each_member(self) -> None:
|
||||||
for member in self.members:
|
for member in self.members:
|
||||||
member.loadout = member.loadout.clone()
|
member.loadout = member.loadout.clone()
|
||||||
|
|||||||
@ -6,7 +6,7 @@ from typing import Type
|
|||||||
from game.ato.flightplans.ibuilder import IBuilder
|
from game.ato.flightplans.ibuilder import IBuilder
|
||||||
from game.ato.flightplans.patrolling import PatrollingFlightPlan, PatrollingLayout
|
from game.ato.flightplans.patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
from game.ato.flightplans.waypointbuilder import WaypointBuilder
|
||||||
from game.utils import Distance, Heading, Speed, feet, knots, meters, nautical_miles
|
from game.utils import Distance, Heading, Speed, knots, meters, nautical_miles
|
||||||
|
|
||||||
|
|
||||||
class AewcFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
class AewcFlightPlan(PatrollingFlightPlan[PatrollingLayout]):
|
||||||
@ -70,10 +70,7 @@ class Builder(IBuilder[AewcFlightPlan, PatrollingLayout]):
|
|||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
if self.flight.unit_type.patrol_altitude is not None:
|
altitude = builder.get_patrol_altitude
|
||||||
altitude = self.flight.unit_type.patrol_altitude
|
|
||||||
else:
|
|
||||||
altitude = feet(25000)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
@ -90,6 +87,7 @@ class Builder(IBuilder[AewcFlightPlan, PatrollingLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> AewcFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> AewcFlightPlan:
|
||||||
|
|||||||
@ -49,6 +49,7 @@ class AirAssaultLayout(FormationAttackLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
class AirAssaultFlightPlan(FormationAttackFlightPlan, UiZoneDisplay):
|
class AirAssaultFlightPlan(FormationAttackFlightPlan, UiZoneDisplay):
|
||||||
@ -111,12 +112,11 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
)
|
)
|
||||||
assert self.package.waypoints is not None
|
assert self.package.waypoints is not None
|
||||||
|
|
||||||
heli_alt = feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
|
||||||
altitude = heli_alt if self.flight.is_helo else self.doctrine.ingress_altitude
|
|
||||||
altitude_is_agl = self.flight.is_helo
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
|
altitude = builder.get_cruise_altitude
|
||||||
|
altitude_is_agl = self.flight.is_helo
|
||||||
|
|
||||||
if self.flight.is_hercules or self.flight.departure.cptype in [
|
if self.flight.is_hercules or self.flight.departure.cptype in [
|
||||||
ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
ControlPointType.AIRCRAFT_CARRIER_GROUP,
|
||||||
ControlPointType.LHA_GROUP,
|
ControlPointType.LHA_GROUP,
|
||||||
@ -133,13 +133,21 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
self._generate_ctld_pickup(),
|
self._generate_ctld_pickup(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pickup.alt = heli_alt
|
pickup.alt = altitude
|
||||||
pickup_position = pickup.position
|
pickup_position = pickup.position
|
||||||
|
|
||||||
ingress = builder.ingress(
|
ingress = (
|
||||||
FlightWaypointType.INGRESS_AIR_ASSAULT,
|
builder.ingress(
|
||||||
self.package.waypoints.ingress,
|
FlightWaypointType.INGRESS_AIR_ASSAULT,
|
||||||
self.package.target,
|
self.package.waypoints.ingress,
|
||||||
|
self.package.target,
|
||||||
|
)
|
||||||
|
if not self.flight.is_hercules
|
||||||
|
else builder.ingress(
|
||||||
|
FlightWaypointType.INGRESS_AIR_ASSAULT,
|
||||||
|
self.package.waypoints.initial,
|
||||||
|
self.package.target,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assault_area = builder.assault_area(self.package.target)
|
assault_area = builder.assault_area(self.package.target)
|
||||||
@ -159,8 +167,6 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
drop_pos = tgt.position.point_from_heading(heading, 1200)
|
drop_pos = tgt.position.point_from_heading(heading, 1200)
|
||||||
drop_off_zone = MissionTarget("Dropoff zone", drop_pos)
|
drop_off_zone = MissionTarget("Dropoff zone", drop_pos)
|
||||||
dz = builder.dropoff_zone(drop_off_zone) if self.flight.is_helo else None
|
dz = builder.dropoff_zone(drop_off_zone) if self.flight.is_helo else None
|
||||||
if dz:
|
|
||||||
dz.alt = heli_alt
|
|
||||||
|
|
||||||
return AirAssaultLayout(
|
return AirAssaultLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
@ -184,9 +190,10 @@ class Builder(FormationAttackBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
|
|||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
hold=None,
|
hold=None,
|
||||||
join=builder.join(ingress.position),
|
join=builder.join(self.package.waypoints.ingress),
|
||||||
split=builder.split(self.flight.arrival.position),
|
split=builder.split(self.flight.arrival.position),
|
||||||
refuel=None,
|
refuel=None,
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> AirAssaultFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> AirAssaultFlightPlan:
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from typing import Optional
|
|||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.theater.missiontarget import MissionTarget
|
from game.theater.missiontarget import MissionTarget
|
||||||
from game.utils import feet, Distance
|
from game.utils import Distance
|
||||||
from ._common_ctld import generate_random_ctld_point
|
from ._common_ctld import generate_random_ctld_point
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .planningerror import PlanningError
|
from .planningerror import PlanningError
|
||||||
@ -92,6 +92,7 @@ class AirliftLayout(StandardLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
class AirliftFlightPlan(StandardFlightPlan[AirliftLayout]):
|
class AirliftFlightPlan(StandardFlightPlan[AirliftLayout]):
|
||||||
@ -132,12 +133,11 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
|
|||||||
"Cannot plan transport mission for flight with no cargo."
|
"Cannot plan transport mission for flight with no cargo."
|
||||||
)
|
)
|
||||||
|
|
||||||
heli_alt = feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
|
||||||
altitude = heli_alt if self.flight.is_helo else self.doctrine.ingress_altitude
|
|
||||||
altitude_is_agl = self.flight.is_helo
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
|
altitude = builder.get_cruise_altitude
|
||||||
|
altitude_is_agl = self.flight.is_helo
|
||||||
|
|
||||||
pickup_ascent = None
|
pickup_ascent = None
|
||||||
pickup_descent = None
|
pickup_descent = None
|
||||||
pickup = None
|
pickup = None
|
||||||
@ -246,6 +246,7 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> AirliftFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> AirliftFlightPlan:
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.theater import FrontLine
|
from game.theater import FrontLine
|
||||||
from game.utils import Distance, Speed, feet
|
from game.utils import Distance, Speed
|
||||||
from .capbuilder import CapBuilder
|
from .capbuilder import CapBuilder
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
@ -41,14 +40,9 @@ class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
|
|||||||
|
|
||||||
start_pos, end_pos = self.cap_racetrack_for_objective(location, barcap=True)
|
start_pos, end_pos = self.cap_racetrack_for_objective(location, barcap=True)
|
||||||
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
patrol_alt = builder.get_patrol_altitude
|
||||||
|
|
||||||
start, end = builder.race_track(start_pos, end_pos, patrol_alt)
|
start, end = builder.race_track(start_pos, end_pos, patrol_alt)
|
||||||
|
|
||||||
return PatrollingLayout(
|
return PatrollingLayout(
|
||||||
@ -64,6 +58,7 @@ class Builder(CapBuilder[BarCapFlightPlan, PatrollingLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> BarCapFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> BarCapFlightPlan:
|
||||||
|
|||||||
@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Type
|
|||||||
|
|
||||||
from game.theater import FrontLine
|
from game.theater import FrontLine
|
||||||
from game.utils import Distance, Speed, kph, dcs_to_shapely_point
|
from game.utils import Distance, Speed, kph, dcs_to_shapely_point
|
||||||
from game.utils import feet, nautical_miles
|
from game.utils import nautical_miles
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
@ -37,6 +37,7 @@ class CasLayout(PatrollingLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
|
class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
|
||||||
@ -104,11 +105,7 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
|
|||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
is_helo = self.flight.unit_type.dcs_unit_type.helicopter
|
||||||
ingress_egress_altitude = (
|
ingress_egress_altitude = builder.get_combat_altitude
|
||||||
self.doctrine.ingress_altitude
|
|
||||||
if not is_helo
|
|
||||||
else feet(self.coalition.game.settings.heli_combat_alt_agl)
|
|
||||||
)
|
|
||||||
use_agl_patrol_altitude = is_helo
|
use_agl_patrol_altitude = is_helo
|
||||||
|
|
||||||
ip_solver = IpSolver(
|
ip_solver = IpSolver(
|
||||||
@ -167,6 +164,7 @@ class Builder(IBuilder[CasFlightPlan, CasLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> CasFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> CasFlightPlan:
|
||||||
|
|||||||
@ -17,8 +17,6 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CustomLayout(Layout):
|
class CustomLayout(Layout):
|
||||||
custom_waypoints: list[FlightWaypoint]
|
|
||||||
|
|
||||||
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
|
||||||
yield self.departure
|
yield self.departure
|
||||||
yield from self.custom_waypoints
|
yield from self.custom_waypoints
|
||||||
|
|||||||
@ -11,7 +11,6 @@ from .formationattack import (
|
|||||||
)
|
)
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
from .. import FlightType
|
from .. import FlightType
|
||||||
from ...utils import feet
|
|
||||||
|
|
||||||
|
|
||||||
class EscortFlightPlan(FormationAttackFlightPlan):
|
class EscortFlightPlan(FormationAttackFlightPlan):
|
||||||
@ -43,12 +42,9 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
|
|
||||||
split = builder.split(self._get_split())
|
split = builder.split(self._get_split())
|
||||||
|
|
||||||
ingress_alt = self.doctrine.ingress_altitude
|
|
||||||
is_helo = builder.flight.is_helo
|
is_helo = builder.flight.is_helo
|
||||||
heli_alt = feet(self.coalition.game.settings.heli_combat_alt_agl)
|
|
||||||
initial = builder.escort_hold(
|
initial = builder.escort_hold(
|
||||||
target.position if is_helo else self.package.waypoints.initial,
|
target.position if is_helo else self.package.waypoints.initial,
|
||||||
min(heli_alt, ingress_alt) if is_helo else ingress_alt,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
pf = self.package.primary_flight
|
pf = self.package.primary_flight
|
||||||
@ -69,9 +65,6 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
if layout.drop_off:
|
if layout.drop_off:
|
||||||
initial = builder.escort_hold(
|
initial = builder.escort_hold(
|
||||||
layout.drop_off.position,
|
layout.drop_off.position,
|
||||||
min(feet(200), ingress_alt)
|
|
||||||
if builder.flight.is_helo
|
|
||||||
else ingress_alt,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
refuel = self._build_refuel(builder)
|
refuel = self._build_refuel(builder)
|
||||||
@ -80,13 +73,13 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
nav_to = builder.nav_path(
|
nav_to = builder.nav_path(
|
||||||
hold.position if hold else departure.position,
|
hold.position if hold else departure.position,
|
||||||
join.position,
|
join.position,
|
||||||
self.doctrine.ingress_altitude,
|
builder.get_cruise_altitude,
|
||||||
)
|
)
|
||||||
|
|
||||||
nav_from = builder.nav_path(
|
nav_from = builder.nav_path(
|
||||||
refuel.position if refuel else split.position,
|
refuel.position if refuel else split.position,
|
||||||
self.flight.arrival.position,
|
self.flight.arrival.position,
|
||||||
self.doctrine.ingress_altitude,
|
builder.get_cruise_altitude,
|
||||||
)
|
)
|
||||||
|
|
||||||
return FormationAttackLayout(
|
return FormationAttackLayout(
|
||||||
@ -103,6 +96,7 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> EscortFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> EscortFlightPlan:
|
||||||
|
|||||||
@ -24,6 +24,7 @@ class FerryLayout(StandardLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
class FerryFlightPlan(StandardFlightPlan[FerryLayout]):
|
class FerryFlightPlan(StandardFlightPlan[FerryLayout]):
|
||||||
@ -60,14 +61,14 @@ class Builder(IBuilder[FerryFlightPlan, FerryLayout]):
|
|||||||
f"{self.flight.departure}"
|
f"{self.flight.departure}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.flight)
|
||||||
altitude_is_agl = self.flight.is_helo
|
altitude_is_agl = self.flight.is_helo
|
||||||
altitude = (
|
altitude = (
|
||||||
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
||||||
if altitude_is_agl
|
if altitude_is_agl
|
||||||
else self.flight.unit_type.preferred_patrol_altitude
|
else builder.get_patrol_altitude
|
||||||
)
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
|
||||||
return FerryLayout(
|
return FerryLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(
|
||||||
@ -80,6 +81,7 @@ class Builder(IBuilder[FerryFlightPlan, FerryLayout]):
|
|||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
nav_from=[],
|
nav_from=[],
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> FerryFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> FerryFlightPlan:
|
||||||
|
|||||||
@ -34,6 +34,7 @@ if TYPE_CHECKING:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Layout(ABC):
|
class Layout(ABC):
|
||||||
departure: FlightWaypoint
|
departure: FlightWaypoint
|
||||||
|
custom_waypoints: list[FlightWaypoint]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def waypoints(self) -> list[FlightWaypoint]:
|
def waypoints(self) -> list[FlightWaypoint]:
|
||||||
|
|||||||
@ -11,7 +11,7 @@ from dcs import Point
|
|||||||
|
|
||||||
from game.flightplan import HoldZoneGeometry
|
from game.flightplan import HoldZoneGeometry
|
||||||
from game.theater import MissionTarget
|
from game.theater import MissionTarget
|
||||||
from game.utils import Speed, meters, nautical_miles, feet
|
from game.utils import Speed, meters, nautical_miles
|
||||||
from .flightplan import FlightPlan
|
from .flightplan import FlightPlan
|
||||||
from .formation import FormationFlightPlan, FormationLayout
|
from .formation import FormationFlightPlan, FormationLayout
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
@ -157,6 +157,7 @@ class FormationAttackLayout(FormationLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
FlightPlanT = TypeVar("FlightPlanT", bound=FlightPlan[FormationAttackLayout])
|
FlightPlanT = TypeVar("FlightPlanT", bound=FlightPlan[FormationAttackLayout])
|
||||||
@ -209,14 +210,10 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
if self.flight.flight_type == FlightType.STRIKE:
|
if self.flight.flight_type == FlightType.STRIKE:
|
||||||
hdg = self.package.target.position.heading_between_point(ingress.position)
|
hdg = self.package.target.position.heading_between_point(ingress.position)
|
||||||
pos = ingress.position.point_from_heading(hdg, nautical_miles(10).meters)
|
pos = ingress.position.point_from_heading(hdg, nautical_miles(10).meters)
|
||||||
lineup = builder.nav(pos, self.flight.coalition.doctrine.ingress_altitude)
|
lineup = builder.nav(pos, builder.get_combat_altitude)
|
||||||
|
|
||||||
is_helo = self.flight.is_helo
|
is_helo = self.flight.is_helo
|
||||||
ingress_egress_altitude = (
|
ingress_egress_altitude = builder.get_combat_altitude
|
||||||
self.doctrine.ingress_altitude
|
|
||||||
if not is_helo
|
|
||||||
else feet(self.coalition.game.settings.heli_combat_alt_agl)
|
|
||||||
)
|
|
||||||
use_agl_ingress_egress = is_helo
|
use_agl_ingress_egress = is_helo
|
||||||
|
|
||||||
return FormationAttackLayout(
|
return FormationAttackLayout(
|
||||||
@ -244,6 +241,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_refuel(self, builder: WaypointBuilder) -> Optional[FlightWaypoint]:
|
def _build_refuel(self, builder: WaypointBuilder) -> Optional[FlightWaypoint]:
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class IBuilder(ABC, Generic[FlightPlanT, LayoutT]):
|
|||||||
def __init__(self, flight: Flight) -> None:
|
def __init__(self, flight: Flight) -> None:
|
||||||
self.flight = flight
|
self.flight = flight
|
||||||
self._flight_plan: FlightPlanT | None = None
|
self._flight_plan: FlightPlanT | None = None
|
||||||
|
self.settings = self.flight.coalition.game.settings
|
||||||
|
|
||||||
def get_or_build(self) -> FlightPlanT:
|
def get_or_build(self) -> FlightPlanT:
|
||||||
if self._flight_plan is None:
|
if self._flight_plan is None:
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from typing import Type
|
|||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
|
|
||||||
from game.utils import Distance, Heading, feet, meters
|
from game.utils import Distance, Heading, meters
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .patrolling import PatrollingLayout
|
from .patrolling import PatrollingLayout
|
||||||
from .refuelingflightplan import RefuelingFlightPlan
|
from .refuelingflightplan import RefuelingFlightPlan
|
||||||
@ -98,11 +98,7 @@ class Builder(IBuilder[PackageRefuelingFlightPlan, PatrollingLayout]):
|
|||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
tanker_type = self.flight.unit_type
|
altitude = builder.get_patrol_altitude
|
||||||
if tanker_type.patrol_altitude is not None:
|
|
||||||
altitude = tanker_type.patrol_altitude
|
|
||||||
else:
|
|
||||||
altitude = feet(21000)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
@ -119,6 +115,7 @@ class Builder(IBuilder[PackageRefuelingFlightPlan, PatrollingLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> PackageRefuelingFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> PackageRefuelingFlightPlan:
|
||||||
|
|||||||
@ -31,6 +31,7 @@ class PatrollingLayout(StandardLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
LayoutT = TypeVar("LayoutT", bound=PatrollingLayout)
|
LayoutT = TypeVar("LayoutT", bound=PatrollingLayout)
|
||||||
|
|||||||
@ -27,6 +27,7 @@ class RtbLayout(StandardLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
class RtbFlightPlan(StandardFlightPlan[RtbLayout]):
|
class RtbFlightPlan(StandardFlightPlan[RtbLayout]):
|
||||||
@ -65,13 +66,13 @@ class Builder(IBuilder[RtbFlightPlan, RtbLayout]):
|
|||||||
current_position = self.flight.state.estimate_position()
|
current_position = self.flight.state.estimate_position()
|
||||||
current_altitude, altitude_reference = self.flight.state.estimate_altitude()
|
current_altitude, altitude_reference = self.flight.state.estimate_altitude()
|
||||||
|
|
||||||
|
builder = WaypointBuilder(self.flight)
|
||||||
altitude_is_agl = self.flight.is_helo
|
altitude_is_agl = self.flight.is_helo
|
||||||
altitude = (
|
altitude = (
|
||||||
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
feet(self.coalition.game.settings.heli_cruise_alt_agl)
|
||||||
if altitude_is_agl
|
if altitude_is_agl
|
||||||
else self.flight.unit_type.preferred_patrol_altitude
|
else builder.get_patrol_altitude
|
||||||
)
|
)
|
||||||
builder = WaypointBuilder(self.flight)
|
|
||||||
abort_point = builder.nav(
|
abort_point = builder.nav(
|
||||||
current_position, current_altitude, altitude_reference == "RADIO"
|
current_position, current_altitude, altitude_reference == "RADIO"
|
||||||
)
|
)
|
||||||
@ -91,6 +92,7 @@ class Builder(IBuilder[RtbFlightPlan, RtbLayout]):
|
|||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
nav_from=[],
|
nav_from=[],
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> RtbFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> RtbFlightPlan:
|
||||||
|
|||||||
@ -70,6 +70,9 @@ class StandardLayout(Layout, ABC):
|
|||||||
elif waypoint in self.nav_from:
|
elif waypoint in self.nav_from:
|
||||||
self.nav_from.remove(waypoint)
|
self.nav_from.remove(waypoint)
|
||||||
return True
|
return True
|
||||||
|
elif waypoint in self.custom_waypoints:
|
||||||
|
self.custom_waypoints.remove(waypoint)
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from .formationattack import (
|
|||||||
from .invalidobjectivelocation import InvalidObjectiveLocation
|
from .invalidobjectivelocation import InvalidObjectiveLocation
|
||||||
from .waypointbuilder import StrikeTarget
|
from .waypointbuilder import StrikeTarget
|
||||||
from ..flightwaypointtype import FlightWaypointType
|
from ..flightwaypointtype import FlightWaypointType
|
||||||
|
from ...theater.theatergroup import SceneryUnit
|
||||||
|
|
||||||
|
|
||||||
class StrikeFlightPlan(FormationAttackFlightPlan):
|
class StrikeFlightPlan(FormationAttackFlightPlan):
|
||||||
@ -28,7 +29,10 @@ class Builder(FormationAttackBuilder[StrikeFlightPlan, FormationAttackLayout]):
|
|||||||
|
|
||||||
targets: list[StrikeTarget] = []
|
targets: list[StrikeTarget] = []
|
||||||
for idx, unit in enumerate(location.strike_targets):
|
for idx, unit in enumerate(location.strike_targets):
|
||||||
targets.append(StrikeTarget(f"{unit.type.id} #{idx}", unit))
|
name = unit.type.id
|
||||||
|
if isinstance(unit, SceneryUnit):
|
||||||
|
name = unit.name
|
||||||
|
targets.append(StrikeTarget(f"{name} #{idx}", unit))
|
||||||
|
|
||||||
return self._build(FlightWaypointType.INGRESS_STRIKE, targets)
|
return self._build(FlightWaypointType.INGRESS_STRIKE, targets)
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ class SweepLayout(LoiterLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
|
|
||||||
class SweepFlightPlan(LoiterFlightPlan):
|
class SweepFlightPlan(LoiterFlightPlan):
|
||||||
@ -104,26 +105,27 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
start, end = builder.sweep(start_pos, target, self.doctrine.ingress_altitude)
|
altitude = builder.get_patrol_altitude
|
||||||
|
|
||||||
|
start, end = builder.sweep(start_pos, target, altitude)
|
||||||
|
|
||||||
hold = builder.hold(self._hold_point())
|
hold = builder.hold(self._hold_point())
|
||||||
|
|
||||||
return SweepLayout(
|
return SweepLayout(
|
||||||
departure=builder.takeoff(self.flight.departure),
|
departure=builder.takeoff(self.flight.departure),
|
||||||
hold=hold,
|
hold=hold,
|
||||||
nav_to=builder.nav_path(
|
nav_to=builder.nav_path(hold.position, start.position, altitude),
|
||||||
hold.position, start.position, self.doctrine.ingress_altitude
|
|
||||||
),
|
|
||||||
nav_from=builder.nav_path(
|
nav_from=builder.nav_path(
|
||||||
end.position,
|
end.position,
|
||||||
self.flight.arrival.position,
|
self.flight.arrival.position,
|
||||||
self.doctrine.ingress_altitude,
|
altitude,
|
||||||
),
|
),
|
||||||
sweep_start=start,
|
sweep_start=start,
|
||||||
sweep_end=end,
|
sweep_end=end,
|
||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _hold_point(self) -> Point:
|
def _hold_point(self) -> Point:
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import TYPE_CHECKING, Type
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
from game.utils import Distance, Speed, feet
|
from game.utils import Distance, Speed
|
||||||
from .capbuilder import CapBuilder
|
from .capbuilder import CapBuilder
|
||||||
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
from .patrolling import PatrollingFlightPlan, PatrollingLayout
|
||||||
from .waypointbuilder import WaypointBuilder
|
from .waypointbuilder import WaypointBuilder
|
||||||
@ -31,6 +30,7 @@ class TarCapLayout(PatrollingLayout):
|
|||||||
if self.divert is not None:
|
if self.divert is not None:
|
||||||
yield self.divert
|
yield self.divert
|
||||||
yield self.bullseye
|
yield self.bullseye
|
||||||
|
yield from self.custom_waypoints
|
||||||
|
|
||||||
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
|
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
|
||||||
if waypoint == self.refuel:
|
if waypoint == self.refuel:
|
||||||
@ -95,14 +95,9 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
|
|||||||
def layout(self) -> TarCapLayout:
|
def layout(self) -> TarCapLayout:
|
||||||
location = self.package.target
|
location = self.package.target
|
||||||
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
patrol_alt = builder.get_patrol_altitude
|
||||||
|
|
||||||
orbit0p, orbit1p = self.cap_racetrack_for_objective(location, barcap=False)
|
orbit0p, orbit1p = self.cap_racetrack_for_objective(location, barcap=False)
|
||||||
|
|
||||||
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||||
@ -128,6 +123,7 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> TarCapFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> TarCapFlightPlan:
|
||||||
|
|||||||
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Type
|
from typing import Type
|
||||||
|
|
||||||
from game.utils import Heading, feet, meters, nautical_miles
|
from game.utils import Heading, meters, nautical_miles
|
||||||
from .ibuilder import IBuilder
|
from .ibuilder import IBuilder
|
||||||
from .patrolling import PatrollingLayout
|
from .patrolling import PatrollingLayout
|
||||||
from .refuelingflightplan import RefuelingFlightPlan
|
from .refuelingflightplan import RefuelingFlightPlan
|
||||||
@ -58,11 +58,7 @@ class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):
|
|||||||
|
|
||||||
builder = WaypointBuilder(self.flight)
|
builder = WaypointBuilder(self.flight)
|
||||||
|
|
||||||
tanker_type = self.flight.unit_type
|
altitude = builder.get_patrol_altitude
|
||||||
if tanker_type.patrol_altitude is not None:
|
|
||||||
altitude = tanker_type.patrol_altitude
|
|
||||||
else:
|
|
||||||
altitude = feet(21000)
|
|
||||||
|
|
||||||
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
racetrack = builder.race_track(racetrack_start, racetrack_end, altitude)
|
||||||
|
|
||||||
@ -79,6 +75,7 @@ class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):
|
|||||||
arrival=builder.land(self.flight.arrival),
|
arrival=builder.land(self.flight.arrival),
|
||||||
divert=builder.divert(self.flight.divert),
|
divert=builder.divert(self.flight.divert),
|
||||||
bullseye=builder.bullseye(),
|
bullseye=builder.bullseye(),
|
||||||
|
custom_waypoints=list(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def build(self, dump_debug_info: bool = False) -> TheaterRefuelingFlightPlan:
|
def build(self, dump_debug_info: bool = False) -> TheaterRefuelingFlightPlan:
|
||||||
|
|||||||
@ -26,6 +26,8 @@ from game.theater import (
|
|||||||
)
|
)
|
||||||
from game.utils import Distance, meters, nautical_miles, feet
|
from game.utils import Distance, meters, nautical_miles, feet
|
||||||
|
|
||||||
|
AGL_TRANSITION_ALT = 5000
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game.transfers import MultiGroupTransport
|
from game.transfers import MultiGroupTransport
|
||||||
from game.theater.theatergroup import TheaterGroup
|
from game.theater.theatergroup import TheaterGroup
|
||||||
@ -51,11 +53,34 @@ class WaypointBuilder:
|
|||||||
self.navmesh = coalition.nav_mesh
|
self.navmesh = coalition.nav_mesh
|
||||||
self.targets = targets
|
self.targets = targets
|
||||||
self._bullseye = coalition.bullseye
|
self._bullseye = coalition.bullseye
|
||||||
|
self.settings = self.flight.coalition.game.settings
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_helo(self) -> bool:
|
def is_helo(self) -> bool:
|
||||||
return self.flight.is_helo
|
return self.flight.is_helo
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_patrol_altitude(self) -> Distance:
|
||||||
|
return self.get_altitude(self.flight.unit_type.preferred_patrol_altitude)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_cruise_altitude(self) -> Distance:
|
||||||
|
return self.get_altitude(self.flight.unit_type.preferred_cruise_altitude)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_combat_altitude(self) -> Distance:
|
||||||
|
return self.get_altitude(self.flight.unit_type.preferred_combat_altitude)
|
||||||
|
|
||||||
|
def get_altitude(self, alt: Distance) -> Distance:
|
||||||
|
randomized_alt = feet(round(alt.feet + self.flight.plane_altitude_offset))
|
||||||
|
altitude = max(
|
||||||
|
self.doctrine.min_combat_altitude,
|
||||||
|
min(self.doctrine.max_combat_altitude, randomized_alt),
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
feet(self.settings.heli_combat_alt_agl) if self.flight.is_helo else altitude
|
||||||
|
)
|
||||||
|
|
||||||
def takeoff(self, departure: ControlPoint) -> FlightWaypoint:
|
def takeoff(self, departure: ControlPoint) -> FlightWaypoint:
|
||||||
"""Create takeoff waypoint for the given arrival airfield or carrier.
|
"""Create takeoff waypoint for the given arrival airfield or carrier.
|
||||||
|
|
||||||
@ -72,9 +97,7 @@ class WaypointBuilder:
|
|||||||
"NAV",
|
"NAV",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
self.get_cruise_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.rendezvous_altitude,
|
|
||||||
description="Enter theater",
|
description="Enter theater",
|
||||||
pretty_name="Enter theater",
|
pretty_name="Enter theater",
|
||||||
)
|
)
|
||||||
@ -101,9 +124,7 @@ class WaypointBuilder:
|
|||||||
"NAV",
|
"NAV",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
self.get_cruise_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.rendezvous_altitude,
|
|
||||||
description="Exit theater",
|
description="Exit theater",
|
||||||
pretty_name="Exit theater",
|
pretty_name="Exit theater",
|
||||||
)
|
)
|
||||||
@ -129,14 +150,10 @@ class WaypointBuilder:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
position = divert.position
|
position = divert.position
|
||||||
altitude_type: AltitudeReference
|
altitude_type: AltitudeReference = "BARO"
|
||||||
if isinstance(divert, OffMapSpawn):
|
if isinstance(divert, OffMapSpawn):
|
||||||
altitude = (
|
altitude = self.get_cruise_altitude
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
altitude_type = "RADIO" if self.is_helo else altitude_type
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.rendezvous_altitude
|
|
||||||
)
|
|
||||||
altitude_type = "BARO"
|
|
||||||
else:
|
else:
|
||||||
altitude = meters(0)
|
altitude = meters(0)
|
||||||
altitude_type = "RADIO"
|
altitude_type = "RADIO"
|
||||||
@ -166,16 +183,15 @@ class WaypointBuilder:
|
|||||||
|
|
||||||
def hold(self, position: Point) -> FlightWaypoint:
|
def hold(self, position: Point) -> FlightWaypoint:
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo:
|
if self.is_helo or self.get_combat_altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"HOLD",
|
"HOLD",
|
||||||
FlightWaypointType.LOITER,
|
FlightWaypointType.LOITER,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
# TODO: dedicated altitude setting for holding
|
||||||
if self.is_helo
|
self.get_cruise_altitude if self.is_helo else self.get_combat_altitude,
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Wait until push time",
|
description="Wait until push time",
|
||||||
pretty_name="Hold",
|
pretty_name="Hold",
|
||||||
@ -183,16 +199,14 @@ class WaypointBuilder:
|
|||||||
|
|
||||||
def join(self, position: Point) -> FlightWaypoint:
|
def join(self, position: Point) -> FlightWaypoint:
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo:
|
if self.is_helo or self.get_cruise_altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"JOIN",
|
"JOIN",
|
||||||
FlightWaypointType.JOIN,
|
FlightWaypointType.JOIN,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
self.get_cruise_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Rendezvous with package",
|
description="Rendezvous with package",
|
||||||
pretty_name="Join",
|
pretty_name="Join",
|
||||||
@ -200,16 +214,14 @@ class WaypointBuilder:
|
|||||||
|
|
||||||
def refuel(self, position: Point) -> FlightWaypoint:
|
def refuel(self, position: Point) -> FlightWaypoint:
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo:
|
if self.is_helo or self.get_cruise_altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"REFUEL",
|
"REFUEL",
|
||||||
FlightWaypointType.REFUEL,
|
FlightWaypointType.REFUEL,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
self.get_cruise_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Refuel from tanker",
|
description="Refuel from tanker",
|
||||||
pretty_name="Refuel",
|
pretty_name="Refuel",
|
||||||
@ -217,16 +229,14 @@ class WaypointBuilder:
|
|||||||
|
|
||||||
def split(self, position: Point) -> FlightWaypoint:
|
def split(self, position: Point) -> FlightWaypoint:
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo:
|
if self.is_helo or self.get_combat_altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"SPLIT",
|
"SPLIT",
|
||||||
FlightWaypointType.SPLIT,
|
FlightWaypointType.SPLIT,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Depart from package",
|
description="Depart from package",
|
||||||
pretty_name="Split",
|
pretty_name="Split",
|
||||||
@ -238,7 +248,7 @@ class WaypointBuilder:
|
|||||||
position: Point,
|
position: Point,
|
||||||
objective: MissionTarget,
|
objective: MissionTarget,
|
||||||
) -> FlightWaypoint:
|
) -> FlightWaypoint:
|
||||||
alt = self.doctrine.ingress_altitude
|
alt = self.get_combat_altitude
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo or self.flight.is_hercules:
|
if self.is_helo or self.flight.is_hercules:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
@ -247,6 +257,8 @@ class WaypointBuilder:
|
|||||||
if self.is_helo
|
if self.is_helo
|
||||||
else feet(1000)
|
else feet(1000)
|
||||||
)
|
)
|
||||||
|
elif alt.feet <= AGL_TRANSITION_ALT:
|
||||||
|
alt_type = "RADIO"
|
||||||
|
|
||||||
heading = objective.position.heading_between_point(position)
|
heading = objective.position.heading_between_point(position)
|
||||||
|
|
||||||
@ -265,16 +277,14 @@ class WaypointBuilder:
|
|||||||
|
|
||||||
def egress(self, position: Point, target: MissionTarget) -> FlightWaypoint:
|
def egress(self, position: Point, target: MissionTarget) -> FlightWaypoint:
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo:
|
if self.is_helo or self.get_combat_altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"EGRESS",
|
"EGRESS",
|
||||||
FlightWaypointType.EGRESS,
|
FlightWaypointType.EGRESS,
|
||||||
position,
|
position,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description=f"EGRESS from {target.name}",
|
description=f"EGRESS from {target.name}",
|
||||||
pretty_name=f"EGRESS from {target.name}",
|
pretty_name=f"EGRESS from {target.name}",
|
||||||
@ -312,11 +322,15 @@ class WaypointBuilder:
|
|||||||
return self._target_area(f"STRIKE {target.name}", target)
|
return self._target_area(f"STRIKE {target.name}", target)
|
||||||
|
|
||||||
def sead_area(self, target: MissionTarget) -> FlightWaypoint:
|
def sead_area(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
|
alt_type: AltitudeReference = "BARO"
|
||||||
|
if self.get_combat_altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return self._target_area(
|
return self._target_area(
|
||||||
f"SEAD on {target.name}",
|
f"SEAD on {target.name}",
|
||||||
target,
|
target,
|
||||||
altitude=self.doctrine.ingress_altitude,
|
altitude=self.get_combat_altitude,
|
||||||
alt_type="BARO",
|
alt_type=alt_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
def dead_area(self, target: MissionTarget) -> FlightWaypoint:
|
def dead_area(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
@ -387,11 +401,13 @@ class WaypointBuilder:
|
|||||||
position: Position of the waypoint.
|
position: Position of the waypoint.
|
||||||
altitude: Altitude of the racetrack.
|
altitude: Altitude of the racetrack.
|
||||||
"""
|
"""
|
||||||
|
baro: AltitudeReference = "BARO"
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"RACETRACK START",
|
"RACETRACK START",
|
||||||
FlightWaypointType.PATROL_TRACK,
|
FlightWaypointType.PATROL_TRACK,
|
||||||
position,
|
position,
|
||||||
altitude,
|
altitude,
|
||||||
|
"RADIO" if altitude.feet <= AGL_TRANSITION_ALT else baro,
|
||||||
description="Orbit between this point and the next point",
|
description="Orbit between this point and the next point",
|
||||||
pretty_name="Race-track start",
|
pretty_name="Race-track start",
|
||||||
)
|
)
|
||||||
@ -404,11 +420,13 @@ class WaypointBuilder:
|
|||||||
position: Position of the waypoint.
|
position: Position of the waypoint.
|
||||||
altitude: Altitude of the racetrack.
|
altitude: Altitude of the racetrack.
|
||||||
"""
|
"""
|
||||||
|
baro: AltitudeReference = "BARO"
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"RACETRACK END",
|
"RACETRACK END",
|
||||||
FlightWaypointType.PATROL,
|
FlightWaypointType.PATROL,
|
||||||
position,
|
position,
|
||||||
altitude,
|
altitude,
|
||||||
|
"RADIO" if altitude.feet <= AGL_TRANSITION_ALT else baro,
|
||||||
description="Orbit between this point and the previous point",
|
description="Orbit between this point and the previous point",
|
||||||
pretty_name="Race-track end",
|
pretty_name="Race-track end",
|
||||||
)
|
)
|
||||||
@ -436,42 +454,39 @@ class WaypointBuilder:
|
|||||||
start: Position of the waypoint.
|
start: Position of the waypoint.
|
||||||
altitude: Altitude of the racetrack.
|
altitude: Altitude of the racetrack.
|
||||||
"""
|
"""
|
||||||
|
baro: AltitudeReference = "BARO"
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"ORBIT",
|
"ORBIT",
|
||||||
FlightWaypointType.LOITER,
|
FlightWaypointType.LOITER,
|
||||||
start,
|
start,
|
||||||
altitude,
|
altitude,
|
||||||
|
"RADIO" if altitude.feet <= AGL_TRANSITION_ALT else baro,
|
||||||
description="Anchor and hold at this point",
|
description="Anchor and hold at this point",
|
||||||
pretty_name="Orbit",
|
pretty_name="Orbit",
|
||||||
)
|
)
|
||||||
|
|
||||||
def sead_search(self, target: MissionTarget) -> FlightWaypoint:
|
def sead_search(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
hold = self._sead_search_point(target)
|
hold = self._sead_search_point(target)
|
||||||
|
baro: AltitudeReference = "BARO"
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"SEAD Search",
|
"SEAD Search",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
hold,
|
hold,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
"RADIO" if self.get_combat_altitude.feet <= AGL_TRANSITION_ALT else baro,
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type="BARO",
|
|
||||||
description="Anchor and search from this point",
|
description="Anchor and search from this point",
|
||||||
pretty_name="SEAD Search",
|
pretty_name="SEAD Search",
|
||||||
)
|
)
|
||||||
|
|
||||||
def sead_sweep(self, target: MissionTarget) -> FlightWaypoint:
|
def sead_sweep(self, target: MissionTarget) -> FlightWaypoint:
|
||||||
hold = self._sead_search_point(target)
|
hold = self._sead_search_point(target)
|
||||||
|
baro: AltitudeReference = "BARO"
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"SEAD Sweep",
|
"SEAD Sweep",
|
||||||
FlightWaypointType.NAV,
|
FlightWaypointType.NAV,
|
||||||
hold,
|
hold,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
"RADIO" if self.get_combat_altitude.feet <= AGL_TRANSITION_ALT else baro,
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type="BARO",
|
|
||||||
description="Anchor and search from this point",
|
description="Anchor and search from this point",
|
||||||
pretty_name="SEAD Sweep",
|
pretty_name="SEAD Sweep",
|
||||||
)
|
)
|
||||||
@ -499,15 +514,16 @@ class WaypointBuilder:
|
|||||||
)
|
)
|
||||||
return hold
|
return hold
|
||||||
|
|
||||||
def escort_hold(self, start: Point, altitude: Distance) -> FlightWaypoint:
|
def escort_hold(self, start: Point) -> FlightWaypoint:
|
||||||
"""Creates custom waypoint for escort flights that need to hold.
|
"""Creates custom waypoint for escort flights that need to hold.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
start: Position of the waypoint.
|
start: Position of the waypoint.
|
||||||
altitude: Altitude of the holding pattern.
|
|
||||||
"""
|
"""
|
||||||
|
altitude = self.get_combat_altitude
|
||||||
|
|
||||||
alt_type: Literal["BARO", "RADIO"] = "BARO"
|
alt_type: Literal["BARO", "RADIO"] = "BARO"
|
||||||
if self.is_helo:
|
if self.is_helo or altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
@ -528,11 +544,13 @@ class WaypointBuilder:
|
|||||||
position: Position of the waypoint.
|
position: Position of the waypoint.
|
||||||
altitude: Altitude of the sweep in meters.
|
altitude: Altitude of the sweep in meters.
|
||||||
"""
|
"""
|
||||||
|
baro: AltitudeReference = "BARO"
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"SWEEP START",
|
"SWEEP START",
|
||||||
FlightWaypointType.INGRESS_SWEEP,
|
FlightWaypointType.INGRESS_SWEEP,
|
||||||
position,
|
position,
|
||||||
altitude,
|
altitude,
|
||||||
|
"RADIO" if altitude.feet <= AGL_TRANSITION_ALT else baro,
|
||||||
description="Proceed to the target and engage enemy aircraft",
|
description="Proceed to the target and engage enemy aircraft",
|
||||||
pretty_name="Sweep start",
|
pretty_name="Sweep start",
|
||||||
)
|
)
|
||||||
@ -545,11 +563,13 @@ class WaypointBuilder:
|
|||||||
position: Position of the waypoint.
|
position: Position of the waypoint.
|
||||||
altitude: Altitude of the sweep in meters.
|
altitude: Altitude of the sweep in meters.
|
||||||
"""
|
"""
|
||||||
|
baro: AltitudeReference = "BARO"
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"SWEEP END",
|
"SWEEP END",
|
||||||
FlightWaypointType.EGRESS,
|
FlightWaypointType.EGRESS,
|
||||||
position,
|
position,
|
||||||
altitude,
|
altitude,
|
||||||
|
"RADIO" if altitude.feet <= AGL_TRANSITION_ALT else baro,
|
||||||
description="End of sweep",
|
description="End of sweep",
|
||||||
pretty_name="Sweep end",
|
pretty_name="Sweep end",
|
||||||
)
|
)
|
||||||
@ -578,7 +598,7 @@ class WaypointBuilder:
|
|||||||
target: The mission target.
|
target: The mission target.
|
||||||
"""
|
"""
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if self.is_helo:
|
if self.is_helo or self.get_combat_altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
|
|
||||||
# This would preferably be no points at all, and instead the Escort task
|
# This would preferably be no points at all, and instead the Escort task
|
||||||
@ -592,9 +612,7 @@ class WaypointBuilder:
|
|||||||
"TARGET",
|
"TARGET",
|
||||||
FlightWaypointType.TARGET_GROUP_LOC,
|
FlightWaypointType.TARGET_GROUP_LOC,
|
||||||
target.position,
|
target.position,
|
||||||
feet(self.flight.coalition.game.settings.heli_combat_alt_agl)
|
self.get_combat_altitude,
|
||||||
if self.is_helo
|
|
||||||
else self.doctrine.ingress_altitude,
|
|
||||||
alt_type,
|
alt_type,
|
||||||
description="Escort the package",
|
description="Escort the package",
|
||||||
pretty_name="Target area",
|
pretty_name="Target area",
|
||||||
@ -616,17 +634,19 @@ class WaypointBuilder:
|
|||||||
pretty_name="Pick-up zone",
|
pretty_name="Pick-up zone",
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
def dropoff_zone(self, drop_off: MissionTarget) -> FlightWaypoint:
|
||||||
def dropoff_zone(drop_off: MissionTarget) -> FlightWaypoint:
|
|
||||||
"""Creates a dropoff landing zone waypoint
|
"""Creates a dropoff landing zone waypoint
|
||||||
This waypoint is used to generate the Trigger Zone used for AirAssault and
|
This waypoint is used to generate the Trigger Zone used for AirAssault and
|
||||||
AirLift using the CTLD plugin (see LogisticsGenerator)
|
AirLift using the CTLD plugin (see LogisticsGenerator)
|
||||||
"""
|
"""
|
||||||
|
heli_alt = feet(self.flight.coalition.game.settings.heli_cruise_alt_agl)
|
||||||
|
altitude = heli_alt if self.flight.is_helo else meters(0)
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
"DROPOFFZONE",
|
"DROPOFFZONE",
|
||||||
FlightWaypointType.DROPOFF_ZONE,
|
FlightWaypointType.DROPOFF_ZONE,
|
||||||
drop_off.position,
|
drop_off.position,
|
||||||
meters(0),
|
altitude,
|
||||||
"RADIO",
|
"RADIO",
|
||||||
description=f"Drop off cargo at {drop_off.name}",
|
description=f"Drop off cargo at {drop_off.name}",
|
||||||
pretty_name="Drop-off zone",
|
pretty_name="Drop-off zone",
|
||||||
@ -660,7 +680,7 @@ class WaypointBuilder:
|
|||||||
altitude_is_agl: True for altitude is AGL. False if altitude is MSL.
|
altitude_is_agl: True for altitude is AGL. False if altitude is MSL.
|
||||||
"""
|
"""
|
||||||
alt_type: AltitudeReference = "BARO"
|
alt_type: AltitudeReference = "BARO"
|
||||||
if altitude_is_agl:
|
if altitude_is_agl or altitude.feet <= AGL_TRANSITION_ALT:
|
||||||
alt_type = "RADIO"
|
alt_type = "RADIO"
|
||||||
|
|
||||||
return FlightWaypoint(
|
return FlightWaypoint(
|
||||||
|
|||||||
@ -137,11 +137,15 @@ class Loadout:
|
|||||||
continue
|
continue
|
||||||
name = payload["name"]
|
name = payload["name"]
|
||||||
pylons = payload["pylons"]
|
pylons = payload["pylons"]
|
||||||
yield Loadout(
|
try:
|
||||||
name,
|
yield Loadout(
|
||||||
{p["num"]: Weapon.with_clsid(p["CLSID"]) for p in pylons.values()},
|
name,
|
||||||
date=None,
|
{p["num"]: Weapon.with_clsid(p["CLSID"]) for p in pylons.values()},
|
||||||
)
|
date=None,
|
||||||
|
)
|
||||||
|
except KeyError:
|
||||||
|
# invalid loadout
|
||||||
|
continue
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def valid_payload(pylons: Dict[int, Dict[str, str]]) -> bool:
|
def valid_payload(pylons: Dict[int, Dict[str, str]]) -> bool:
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import logging
|
|||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Tuple
|
from typing import Any, Dict, Tuple, Optional
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from packaging.version import Version
|
from packaging.version import Version
|
||||||
@ -19,8 +19,10 @@ from game.theater.iadsnetwork.iadsnetwork import IadsNetwork
|
|||||||
from game.theater.theaterloader import TheaterLoader
|
from game.theater.theaterloader import TheaterLoader
|
||||||
from game.version import CAMPAIGN_FORMAT_VERSION
|
from game.version import CAMPAIGN_FORMAT_VERSION
|
||||||
from .campaignairwingconfig import CampaignAirWingConfig
|
from .campaignairwingconfig import CampaignAirWingConfig
|
||||||
|
from .campaigncarrierconfig import CampaignCarrierConfig
|
||||||
from .campaigngroundconfig import TgoConfig
|
from .campaigngroundconfig import TgoConfig
|
||||||
from .mizcampaignloader import MizCampaignLoader
|
from .mizcampaignloader import MizCampaignLoader
|
||||||
|
from ..factions import FACTIONS, Faction
|
||||||
|
|
||||||
PERF_FRIENDLY = 0
|
PERF_FRIENDLY = 0
|
||||||
PERF_MEDIUM = 1
|
PERF_MEDIUM = 1
|
||||||
@ -90,6 +92,16 @@ class Campaign:
|
|||||||
f"Invalid value for recommended_start_date in {path}: {start_date_raw}"
|
f"Invalid value for recommended_start_date in {path}: {start_date_raw}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
player_faction = data.get("recommended_player_faction", "USA 2005")
|
||||||
|
if isinstance(player_faction, dict):
|
||||||
|
faction_name = cls.register_faction(campaign_file.name, player_faction)
|
||||||
|
player_faction = faction_name if faction_name else "USA 2005"
|
||||||
|
|
||||||
|
enemy_faction = data.get("recommended_enemy_faction", "Russia 1990")
|
||||||
|
if isinstance(enemy_faction, dict):
|
||||||
|
faction_name = cls.register_faction(campaign_file.name, enemy_faction)
|
||||||
|
enemy_faction = faction_name if faction_name else "Russia 1990"
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
data["name"],
|
data["name"],
|
||||||
TheaterLoader(data["theater"].lower()).menu_thumbnail_dcs_relative_path,
|
TheaterLoader(data["theater"].lower()).menu_thumbnail_dcs_relative_path,
|
||||||
@ -97,8 +109,8 @@ class Campaign:
|
|||||||
data.get("authors", "???"),
|
data.get("authors", "???"),
|
||||||
data.get("description", ""),
|
data.get("description", ""),
|
||||||
(version.major, version.minor),
|
(version.major, version.minor),
|
||||||
data.get("recommended_player_faction", "USA 2005"),
|
player_faction,
|
||||||
data.get("recommended_enemy_faction", "Russia 1990"),
|
enemy_faction,
|
||||||
start_date,
|
start_date,
|
||||||
start_time,
|
start_time,
|
||||||
data.get("recommended_player_money", DEFAULT_BUDGET),
|
data.get("recommended_player_money", DEFAULT_BUDGET),
|
||||||
@ -112,6 +124,19 @@ class Campaign:
|
|||||||
data.get("settings", {}),
|
data.get("settings", {}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_faction(
|
||||||
|
cls, filename: str, player_faction: dict[str, Any]
|
||||||
|
) -> Optional[str]:
|
||||||
|
try:
|
||||||
|
f = Faction.from_dict(player_faction)
|
||||||
|
FACTIONS.factions[f.name] = f
|
||||||
|
logging.info(f"Loaded faction from campaign: {filename}")
|
||||||
|
return f.name
|
||||||
|
except Exception:
|
||||||
|
logging.exception(f"Unable to load faction from campaign: {filename}")
|
||||||
|
return None
|
||||||
|
|
||||||
def load_theater(self, advanced_iads: bool) -> ConflictTheater:
|
def load_theater(self, advanced_iads: bool) -> ConflictTheater:
|
||||||
t = TheaterLoader(self.data["theater"].lower()).load()
|
t = TheaterLoader(self.data["theater"].lower()).load()
|
||||||
|
|
||||||
@ -140,6 +165,13 @@ class Campaign:
|
|||||||
return CampaignAirWingConfig({})
|
return CampaignAirWingConfig({})
|
||||||
return CampaignAirWingConfig.from_campaign_data(squadron_data, theater)
|
return CampaignAirWingConfig.from_campaign_data(squadron_data, theater)
|
||||||
|
|
||||||
|
def load_carrier_config(self) -> CampaignCarrierConfig:
|
||||||
|
try:
|
||||||
|
carrier_data = self.data["carriers"]
|
||||||
|
except KeyError:
|
||||||
|
return CampaignCarrierConfig({})
|
||||||
|
return CampaignCarrierConfig.from_campaign_data(carrier_data)
|
||||||
|
|
||||||
def load_ground_forces_config(self) -> TgoConfig:
|
def load_ground_forces_config(self) -> TgoConfig:
|
||||||
ground_forces = self.data.get("ground_forces", {})
|
ground_forces = self.data.get("ground_forces", {})
|
||||||
if not ground_forces:
|
if not ground_forces:
|
||||||
|
|||||||
42
game/campaignloader/campaigncarrierconfig.py
Normal file
42
game/campaignloader/campaigncarrierconfig.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from collections import defaultdict
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, TYPE_CHECKING
|
||||||
|
|
||||||
|
from game.dcs.shipunittype import ShipUnitType
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CarrierConfig:
|
||||||
|
preferred_name: str
|
||||||
|
preferred_type: ShipUnitType
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: dict[str, Any]) -> CarrierConfig:
|
||||||
|
return CarrierConfig(
|
||||||
|
str(data["preferred_name"]), ShipUnitType.named(data["preferred_type"])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CampaignCarrierConfig:
|
||||||
|
by_original_name: dict[str, CarrierConfig]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_campaign_data(cls, data: dict[str, Any]) -> CampaignCarrierConfig:
|
||||||
|
by_original_name: dict[str, CarrierConfig] = defaultdict()
|
||||||
|
for original_name, carrier_config_data in data.items():
|
||||||
|
try:
|
||||||
|
carrier_config = CarrierConfig.from_data(carrier_config_data)
|
||||||
|
by_original_name[original_name] = carrier_config
|
||||||
|
except KeyError:
|
||||||
|
logging.warning(
|
||||||
|
f"Skipping invalid carrier config for '{original_name}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
return CampaignCarrierConfig(by_original_name)
|
||||||
@ -47,6 +47,7 @@ class SquadronDefGenerator:
|
|||||||
role="Flying Squadron",
|
role="Flying Squadron",
|
||||||
aircraft=aircraft,
|
aircraft=aircraft,
|
||||||
livery=None,
|
livery=None,
|
||||||
|
livery_set=[],
|
||||||
auto_assignable_mission_types=set(aircraft.iter_task_capabilities()),
|
auto_assignable_mission_types=set(aircraft.iter_task_capabilities()),
|
||||||
radio_presets={},
|
radio_presets={},
|
||||||
operating_bases=OperatingBases.default_for_aircraft(aircraft),
|
operating_bases=OperatingBases.default_for_aircraft(aircraft),
|
||||||
|
|||||||
@ -38,7 +38,7 @@ class PackageBuilder:
|
|||||||
self.laser_code_registry = laser_code_registry
|
self.laser_code_registry = laser_code_registry
|
||||||
self.start_type = start_type
|
self.start_type = start_type
|
||||||
|
|
||||||
def plan_flight(self, plan: ProposedFlight) -> bool:
|
def plan_flight(self, plan: ProposedFlight, ignore_range: bool) -> bool:
|
||||||
"""Allocates aircraft for the given flight and adds them to the package.
|
"""Allocates aircraft for the given flight and adds them to the package.
|
||||||
|
|
||||||
If no suitable aircraft are available, False is returned. If the failed
|
If no suitable aircraft are available, False is returned. If the failed
|
||||||
@ -55,6 +55,7 @@ class PackageBuilder:
|
|||||||
heli,
|
heli,
|
||||||
this_turn=True,
|
this_turn=True,
|
||||||
preferred_type=plan.preferred_type,
|
preferred_type=plan.preferred_type,
|
||||||
|
ignore_range=ignore_range,
|
||||||
)
|
)
|
||||||
if squadron is None:
|
if squadron is None:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -81,8 +81,9 @@ class PackageFulfiller:
|
|||||||
builder: PackageBuilder,
|
builder: PackageBuilder,
|
||||||
missing_types: Set[FlightType],
|
missing_types: Set[FlightType],
|
||||||
purchase_multiplier: int,
|
purchase_multiplier: int,
|
||||||
|
ignore_range: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not builder.plan_flight(flight):
|
if not builder.plan_flight(flight, ignore_range):
|
||||||
pf = builder.package.primary_flight
|
pf = builder.package.primary_flight
|
||||||
heli = pf.is_helo if pf else False
|
heli = pf.is_helo if pf else False
|
||||||
missing_types.add(flight.task)
|
missing_types.add(flight.task)
|
||||||
@ -138,6 +139,7 @@ class PackageFulfiller:
|
|||||||
purchase_multiplier: int,
|
purchase_multiplier: int,
|
||||||
now: datetime,
|
now: datetime,
|
||||||
tracer: MultiEventTracer,
|
tracer: MultiEventTracer,
|
||||||
|
ignore_range: bool = False,
|
||||||
) -> Optional[Package]:
|
) -> Optional[Package]:
|
||||||
"""Allocates aircraft for a proposed mission and adds it to the ATO."""
|
"""Allocates aircraft for a proposed mission and adds it to the ATO."""
|
||||||
builder = PackageBuilder(
|
builder = PackageBuilder(
|
||||||
@ -175,6 +177,7 @@ class PackageFulfiller:
|
|||||||
builder,
|
builder,
|
||||||
missing_types,
|
missing_types,
|
||||||
purchase_multiplier,
|
purchase_multiplier,
|
||||||
|
ignore_range,
|
||||||
)
|
)
|
||||||
|
|
||||||
if missing_types:
|
if missing_types:
|
||||||
|
|||||||
@ -26,8 +26,6 @@ class Doctrine:
|
|||||||
strike: bool
|
strike: bool
|
||||||
antiship: bool
|
antiship: bool
|
||||||
|
|
||||||
rendezvous_altitude: Distance
|
|
||||||
|
|
||||||
#: The minimum distance between the departure airfield and the hold point.
|
#: The minimum distance between the departure airfield and the hold point.
|
||||||
hold_distance: Distance
|
hold_distance: Distance
|
||||||
|
|
||||||
@ -46,11 +44,14 @@ class Doctrine:
|
|||||||
#: target.
|
#: target.
|
||||||
min_ingress_distance: Distance
|
min_ingress_distance: Distance
|
||||||
|
|
||||||
ingress_altitude: Distance
|
|
||||||
|
|
||||||
min_patrol_altitude: Distance
|
min_patrol_altitude: Distance
|
||||||
max_patrol_altitude: Distance
|
max_patrol_altitude: Distance
|
||||||
pattern_altitude: Distance
|
|
||||||
|
min_cruise_altitude: Distance
|
||||||
|
max_cruise_altitude: Distance
|
||||||
|
|
||||||
|
min_combat_altitude: Distance
|
||||||
|
max_combat_altitude: Distance
|
||||||
|
|
||||||
#: The duration that CAP flights will remain on-station.
|
#: The duration that CAP flights will remain on-station.
|
||||||
cap_duration: timedelta
|
cap_duration: timedelta
|
||||||
@ -97,16 +98,17 @@ MODERN_DOCTRINE = Doctrine(
|
|||||||
sead=True,
|
sead=True,
|
||||||
strike=True,
|
strike=True,
|
||||||
antiship=True,
|
antiship=True,
|
||||||
rendezvous_altitude=feet(25000),
|
|
||||||
hold_distance=nautical_miles(25),
|
hold_distance=nautical_miles(25),
|
||||||
push_distance=nautical_miles(20),
|
push_distance=nautical_miles(20),
|
||||||
join_distance=nautical_miles(20),
|
join_distance=nautical_miles(20),
|
||||||
max_ingress_distance=nautical_miles(45),
|
max_ingress_distance=nautical_miles(45),
|
||||||
min_ingress_distance=nautical_miles(10),
|
min_ingress_distance=nautical_miles(10),
|
||||||
ingress_altitude=feet(20000),
|
|
||||||
min_patrol_altitude=feet(15000),
|
min_patrol_altitude=feet(15000),
|
||||||
max_patrol_altitude=feet(33000),
|
max_patrol_altitude=feet(33000),
|
||||||
pattern_altitude=feet(5000),
|
min_cruise_altitude=feet(10000),
|
||||||
|
max_cruise_altitude=feet(40000),
|
||||||
|
min_combat_altitude=feet(1000),
|
||||||
|
max_combat_altitude=feet(35000),
|
||||||
cap_duration=timedelta(minutes=30),
|
cap_duration=timedelta(minutes=30),
|
||||||
cap_min_track_length=nautical_miles(15),
|
cap_min_track_length=nautical_miles(15),
|
||||||
cap_max_track_length=nautical_miles(40),
|
cap_max_track_length=nautical_miles(40),
|
||||||
@ -140,16 +142,17 @@ COLDWAR_DOCTRINE = Doctrine(
|
|||||||
sead=True,
|
sead=True,
|
||||||
strike=True,
|
strike=True,
|
||||||
antiship=True,
|
antiship=True,
|
||||||
rendezvous_altitude=feet(22000),
|
|
||||||
hold_distance=nautical_miles(15),
|
hold_distance=nautical_miles(15),
|
||||||
push_distance=nautical_miles(10),
|
push_distance=nautical_miles(10),
|
||||||
join_distance=nautical_miles(10),
|
join_distance=nautical_miles(10),
|
||||||
max_ingress_distance=nautical_miles(30),
|
max_ingress_distance=nautical_miles(30),
|
||||||
min_ingress_distance=nautical_miles(10),
|
min_ingress_distance=nautical_miles(10),
|
||||||
ingress_altitude=feet(18000),
|
|
||||||
min_patrol_altitude=feet(10000),
|
min_patrol_altitude=feet(10000),
|
||||||
max_patrol_altitude=feet(24000),
|
max_patrol_altitude=feet(24000),
|
||||||
pattern_altitude=feet(5000),
|
min_cruise_altitude=feet(10000),
|
||||||
|
max_cruise_altitude=feet(30000),
|
||||||
|
min_combat_altitude=feet(1000),
|
||||||
|
max_combat_altitude=feet(25000),
|
||||||
cap_duration=timedelta(minutes=30),
|
cap_duration=timedelta(minutes=30),
|
||||||
cap_min_track_length=nautical_miles(12),
|
cap_min_track_length=nautical_miles(12),
|
||||||
cap_max_track_length=nautical_miles(24),
|
cap_max_track_length=nautical_miles(24),
|
||||||
@ -186,13 +189,14 @@ WWII_DOCTRINE = Doctrine(
|
|||||||
hold_distance=nautical_miles(10),
|
hold_distance=nautical_miles(10),
|
||||||
push_distance=nautical_miles(5),
|
push_distance=nautical_miles(5),
|
||||||
join_distance=nautical_miles(5),
|
join_distance=nautical_miles(5),
|
||||||
rendezvous_altitude=feet(10000),
|
|
||||||
max_ingress_distance=nautical_miles(7),
|
max_ingress_distance=nautical_miles(7),
|
||||||
min_ingress_distance=nautical_miles(5),
|
min_ingress_distance=nautical_miles(5),
|
||||||
ingress_altitude=feet(8000),
|
|
||||||
min_patrol_altitude=feet(4000),
|
min_patrol_altitude=feet(4000),
|
||||||
max_patrol_altitude=feet(15000),
|
max_patrol_altitude=feet(15000),
|
||||||
pattern_altitude=feet(5000),
|
min_cruise_altitude=feet(5000),
|
||||||
|
max_cruise_altitude=feet(30000),
|
||||||
|
min_combat_altitude=feet(1000),
|
||||||
|
max_combat_altitude=feet(10000),
|
||||||
cap_duration=timedelta(minutes=30),
|
cap_duration=timedelta(minutes=30),
|
||||||
cap_min_track_length=nautical_miles(8),
|
cap_min_track_length=nautical_miles(8),
|
||||||
cap_max_track_length=nautical_miles(18),
|
cap_max_track_length=nautical_miles(18),
|
||||||
|
|||||||
@ -132,6 +132,21 @@ class PatrolConfig:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AltitudesConfig:
|
||||||
|
cruise: Optional[Distance]
|
||||||
|
combat: Optional[Distance]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data: dict[str, Any]) -> AltitudesConfig:
|
||||||
|
cruise = data.get("cruise", None)
|
||||||
|
combat = data.get("combat", None)
|
||||||
|
return AltitudesConfig(
|
||||||
|
feet(cruise) if cruise is not None else None,
|
||||||
|
feet(combat) if combat is not None else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FuelConsumption:
|
class FuelConsumption:
|
||||||
#: The estimated taxi fuel requirement, in pounds.
|
#: The estimated taxi fuel requirement, in pounds.
|
||||||
@ -182,6 +197,9 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
patrol_altitude: Optional[Distance]
|
patrol_altitude: Optional[Distance]
|
||||||
patrol_speed: Optional[Speed]
|
patrol_speed: Optional[Speed]
|
||||||
|
|
||||||
|
cruise_altitude: Optional[Distance]
|
||||||
|
combat_altitude: Optional[Distance]
|
||||||
|
|
||||||
#: The maximum range between the origin airfield and the target for which the auto-
|
#: The maximum range between the origin airfield and the target for which the auto-
|
||||||
#: planner will consider this aircraft usable for a mission.
|
#: planner will consider this aircraft usable for a mission.
|
||||||
max_mission_range: Distance
|
max_mission_range: Distance
|
||||||
@ -245,33 +263,13 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
def max_speed(self) -> Speed:
|
def max_speed(self) -> Speed:
|
||||||
return kph(self.dcs_unit_type.max_speed)
|
return kph(self.dcs_unit_type.max_speed)
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def preferred_patrol_altitude(self) -> Distance:
|
def preferred_patrol_altitude(self) -> Distance:
|
||||||
if self.patrol_altitude is not None:
|
if self.patrol_altitude:
|
||||||
return self.patrol_altitude
|
return self.patrol_altitude
|
||||||
else:
|
else:
|
||||||
# Estimate based on max speed.
|
# TODO: somehow make the upper and lower limit configurable
|
||||||
# Aircaft with max speed 600 kph will prefer patrol at 10 000 ft
|
return self.preferred_altitude(10, 33, "patrol")
|
||||||
# Aircraft with max speed 2800 kph will prefer pratrol at 33 000 ft
|
|
||||||
altitude_for_lowest_speed = feet(10 * 1000)
|
|
||||||
altitude_for_highest_speed = feet(33 * 1000)
|
|
||||||
lowest_speed = kph(600)
|
|
||||||
highest_speed = kph(2800)
|
|
||||||
factor = (self.max_speed - lowest_speed).kph / (
|
|
||||||
highest_speed - lowest_speed
|
|
||||||
).kph
|
|
||||||
altitude = (
|
|
||||||
altitude_for_lowest_speed
|
|
||||||
+ (altitude_for_highest_speed - altitude_for_lowest_speed) * factor
|
|
||||||
)
|
|
||||||
logging.debug(
|
|
||||||
f"Preferred patrol altitude for {self.dcs_unit_type.id}: {altitude.feet}"
|
|
||||||
)
|
|
||||||
rounded_altitude = feet(round(1000 * round(altitude.feet / 1000)))
|
|
||||||
return max(
|
|
||||||
altitude_for_lowest_speed,
|
|
||||||
min(altitude_for_highest_speed, rounded_altitude),
|
|
||||||
)
|
|
||||||
|
|
||||||
def preferred_patrol_speed(self, altitude: Distance) -> Speed:
|
def preferred_patrol_speed(self, altitude: Distance) -> Speed:
|
||||||
"""Preferred true airspeed when patrolling"""
|
"""Preferred true airspeed when patrolling"""
|
||||||
@ -309,6 +307,46 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
)
|
)
|
||||||
return min(Speed.from_mach(0.35, altitude), max_speed * 0.5)
|
return min(Speed.from_mach(0.35, altitude), max_speed * 0.5)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def preferred_cruise_altitude(self) -> Distance:
|
||||||
|
if self.cruise_altitude:
|
||||||
|
return self.cruise_altitude
|
||||||
|
else:
|
||||||
|
# TODO: somehow make the upper and lower limit configurable
|
||||||
|
return self.preferred_altitude(20, 20, "cruise")
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def preferred_combat_altitude(self) -> Distance:
|
||||||
|
if self.combat_altitude:
|
||||||
|
return self.combat_altitude
|
||||||
|
else:
|
||||||
|
# TODO: somehow make the upper and lower limit configurable
|
||||||
|
return self.preferred_altitude(20, 20, "combat")
|
||||||
|
|
||||||
|
def preferred_altitude(self, low: int, high: int, type: str) -> Distance:
|
||||||
|
# Estimate based on max speed.
|
||||||
|
# Aircraft with max speed 600 kph will prefer low
|
||||||
|
# Aircraft with max speed 2800 kph will prefer high
|
||||||
|
altitude_for_lowest_speed = feet(low * 1000)
|
||||||
|
altitude_for_highest_speed = feet(high * 1000)
|
||||||
|
lowest_speed = kph(600)
|
||||||
|
highest_speed = kph(2800)
|
||||||
|
factor = (self.max_speed - lowest_speed).kph / (
|
||||||
|
highest_speed - lowest_speed
|
||||||
|
).kph
|
||||||
|
altitude = (
|
||||||
|
altitude_for_lowest_speed
|
||||||
|
+ (altitude_for_highest_speed - altitude_for_lowest_speed) * factor
|
||||||
|
)
|
||||||
|
logging.debug(
|
||||||
|
f"Preferred {type} altitude for {self.dcs_unit_type.id}: {altitude.feet}"
|
||||||
|
)
|
||||||
|
rounded_altitude = feet(round(1000 * round(altitude.feet / 1000)))
|
||||||
|
return max(
|
||||||
|
altitude_for_lowest_speed,
|
||||||
|
min(altitude_for_highest_speed, rounded_altitude),
|
||||||
|
)
|
||||||
|
|
||||||
def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency:
|
def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency:
|
||||||
from game.radio.radios import ChannelInUseError, kHz
|
from game.radio.radios import ChannelInUseError, kHz
|
||||||
|
|
||||||
@ -442,6 +480,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
|
|
||||||
radio_config = RadioConfig.from_data(data.get("radios", {}))
|
radio_config = RadioConfig.from_data(data.get("radios", {}))
|
||||||
patrol_config = PatrolConfig.from_data(data.get("patrol", {}))
|
patrol_config = PatrolConfig.from_data(data.get("patrol", {}))
|
||||||
|
altitudes_config = AltitudesConfig.from_data(data.get("altitudes", {}))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mission_range = nautical_miles(int(data["max_range"]))
|
mission_range = nautical_miles(int(data["max_range"]))
|
||||||
@ -510,6 +549,8 @@ class AircraftType(UnitType[Type[FlyingType]]):
|
|||||||
max_group_size=data.get("max_group_size", aircraft.group_size_max),
|
max_group_size=data.get("max_group_size", aircraft.group_size_max),
|
||||||
patrol_altitude=patrol_config.altitude,
|
patrol_altitude=patrol_config.altitude,
|
||||||
patrol_speed=patrol_config.speed,
|
patrol_speed=patrol_config.speed,
|
||||||
|
cruise_altitude=altitudes_config.cruise,
|
||||||
|
combat_altitude=altitudes_config.combat,
|
||||||
max_mission_range=mission_range,
|
max_mission_range=mission_range,
|
||||||
fuel_consumption=fuel_consumption,
|
fuel_consumption=fuel_consumption,
|
||||||
default_livery=data.get("default_livery"),
|
default_livery=data.get("default_livery"),
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import Optional, Dict, Type, List, Any, Iterator, TYPE_CHECKING, Set
|
from typing import Optional, Dict, Type, List, Any, Iterator, TYPE_CHECKING, Set
|
||||||
@ -93,11 +94,8 @@ class Faction:
|
|||||||
# Required mods or asset packs
|
# Required mods or asset packs
|
||||||
requirements: Dict[str, str] = field(default_factory=dict)
|
requirements: Dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
# Possible carrier names
|
# Possible carrier units mapped to names
|
||||||
carrier_names: Set[str] = field(default_factory=set)
|
carriers: Dict[ShipUnitType, Set[str]] = field(default_factory=dict)
|
||||||
|
|
||||||
# Possible helicopter carrier names
|
|
||||||
helicopter_carrier_names: Set[str] = field(default_factory=set)
|
|
||||||
|
|
||||||
# Available Naval Units
|
# Available Naval Units
|
||||||
naval_units: Set[ShipUnitType] = field(default_factory=set)
|
naval_units: Set[ShipUnitType] = field(default_factory=set)
|
||||||
@ -241,8 +239,30 @@ class Faction:
|
|||||||
|
|
||||||
faction.requirements = json.get("requirements", {})
|
faction.requirements = json.get("requirements", {})
|
||||||
|
|
||||||
faction.carrier_names = json.get("carrier_names", [])
|
# First try to load the carriers in the new format which
|
||||||
faction.helicopter_carrier_names = json.get("helicopter_carrier_names", [])
|
# specifies different names for different carrier types
|
||||||
|
loaded_carriers = load_carriers(json)
|
||||||
|
|
||||||
|
carriers: List[ShipUnitType] = [
|
||||||
|
unit
|
||||||
|
for unit in faction.naval_units
|
||||||
|
if unit.unit_class
|
||||||
|
in [
|
||||||
|
UnitClass.AIRCRAFT_CARRIER,
|
||||||
|
UnitClass.HELICOPTER_CARRIER,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
carrier_names = json.get("carrier_names", [])
|
||||||
|
helicopter_carrier_names = json.get("helicopter_carrier_names", [])
|
||||||
|
for c in carriers:
|
||||||
|
if c.variant_id not in loaded_carriers:
|
||||||
|
if c.unit_class == UnitClass.AIRCRAFT_CARRIER:
|
||||||
|
loaded_carriers[c] = carrier_names
|
||||||
|
elif c.unit_class == UnitClass.HELICOPTER_CARRIER:
|
||||||
|
loaded_carriers[c] = helicopter_carrier_names
|
||||||
|
|
||||||
|
faction.carriers = loaded_carriers
|
||||||
|
faction.naval_units.union(faction.carriers.keys())
|
||||||
|
|
||||||
faction.has_jtac = json.get("has_jtac", False)
|
faction.has_jtac = json.get("has_jtac", False)
|
||||||
jtac_name = json.get("jtac_unit", None)
|
jtac_name = json.get("jtac_unit", None)
|
||||||
@ -340,6 +360,8 @@ class Faction:
|
|||||||
if not mod_settings.f4bc_phantom:
|
if not mod_settings.f4bc_phantom:
|
||||||
self.remove_aircraft("VSN_F4B")
|
self.remove_aircraft("VSN_F4B")
|
||||||
self.remove_aircraft("VSN_F4C")
|
self.remove_aircraft("VSN_F4C")
|
||||||
|
if not mod_settings.f9f_panther:
|
||||||
|
self.remove_aircraft("VSN_F9F")
|
||||||
if not mod_settings.f15d_baz:
|
if not mod_settings.f15d_baz:
|
||||||
self.remove_aircraft("F-15D")
|
self.remove_aircraft("F-15D")
|
||||||
if not mod_settings.f_15_idf:
|
if not mod_settings.f_15_idf:
|
||||||
@ -381,6 +403,8 @@ class Faction:
|
|||||||
self.remove_aircraft("JAS39Gripen")
|
self.remove_aircraft("JAS39Gripen")
|
||||||
self.remove_aircraft("JAS39Gripen_BVR")
|
self.remove_aircraft("JAS39Gripen_BVR")
|
||||||
self.remove_aircraft("JAS39Gripen_AG")
|
self.remove_aircraft("JAS39Gripen_AG")
|
||||||
|
if not mod_settings.super_etendard:
|
||||||
|
self.remove_aircraft("VSN_SEM")
|
||||||
if not mod_settings.su30_flanker_h:
|
if not mod_settings.su30_flanker_h:
|
||||||
self.remove_aircraft("Su-30MKA")
|
self.remove_aircraft("Su-30MKA")
|
||||||
self.remove_aircraft("Su-30MKI")
|
self.remove_aircraft("Su-30MKI")
|
||||||
@ -592,3 +616,14 @@ def load_all_ships(data: list[str]) -> List[Type[ShipType]]:
|
|||||||
if item is not None:
|
if item is not None:
|
||||||
items.append(item)
|
items.append(item)
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def load_carriers(json: Dict[str, Any]) -> Dict[ShipUnitType, Set[str]]:
|
||||||
|
# Load carriers
|
||||||
|
items: Dict[ShipUnitType, Set[str]] = defaultdict(Set[str])
|
||||||
|
carriers = json.get("carriers", {})
|
||||||
|
for carrier_shiptype, shipnames in carriers.items():
|
||||||
|
shiptype = ShipUnitType.named(carrier_shiptype)
|
||||||
|
if shiptype is not None:
|
||||||
|
items[shiptype] = shipnames
|
||||||
|
return items
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, Iterator, List, Optional, Type
|
from typing import Dict, Iterator, List, Optional, Type
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
from game import persistency
|
from game import persistency
|
||||||
from game.factions.faction import Faction
|
from game.factions.faction import Faction
|
||||||
|
|
||||||
@ -27,7 +29,11 @@ class FactionLoader:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find_faction_files_in(path: Path) -> List[Path]:
|
def find_faction_files_in(path: Path) -> List[Path]:
|
||||||
return [f for f in path.glob("*.json") if f.is_file()]
|
return (
|
||||||
|
[f for f in path.glob("*.json") if f.is_file()]
|
||||||
|
+ [f for f in path.glob("*.yaml") if f.is_file()]
|
||||||
|
+ [f for f in path.glob("*.yml") if f.is_file()]
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]:
|
def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]:
|
||||||
@ -40,7 +46,10 @@ class FactionLoader:
|
|||||||
for f in files:
|
for f in files:
|
||||||
try:
|
try:
|
||||||
with f.open("r", encoding="utf-8") as fdata:
|
with f.open("r", encoding="utf-8") as fdata:
|
||||||
data = json.load(fdata)
|
if "yml" in f.name or "yaml" in f.name:
|
||||||
|
data = yaml.safe_load(fdata)
|
||||||
|
else:
|
||||||
|
data = json.load(fdata)
|
||||||
factions[data["name"]] = Faction.from_dict(data)
|
factions[data["name"]] = Faction.from_dict(data)
|
||||||
logging.info("Loaded faction : " + str(f))
|
logging.info("Loaded faction : " + str(f))
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@ -52,7 +52,7 @@ class Migrator:
|
|||||||
continue
|
continue
|
||||||
found = False
|
found = False
|
||||||
for d in doctrines:
|
for d in doctrines:
|
||||||
if c.faction.doctrine.rendezvous_altitude == d.rendezvous_altitude:
|
if c.faction.doctrine.max_patrol_altitude == d.max_patrol_altitude:
|
||||||
c.faction.doctrine = d
|
c.faction.doctrine = d
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
@ -78,6 +78,7 @@ class Migrator:
|
|||||||
def _update_control_points(self) -> None:
|
def _update_control_points(self) -> None:
|
||||||
is_sinai = self.game.theater.terrain.name == "SinaiMap"
|
is_sinai = self.game.theater.terrain.name == "SinaiMap"
|
||||||
for cp in self.game.theater.controlpoints:
|
for cp in self.game.theater.controlpoints:
|
||||||
|
cp.release_parking_slots()
|
||||||
is_carrier = cp.is_carrier
|
is_carrier = cp.is_carrier
|
||||||
is_lha = cp.is_lha
|
is_lha = cp.is_lha
|
||||||
is_fob = cp.category == "fob"
|
is_fob = cp.category == "fob"
|
||||||
@ -107,6 +108,7 @@ class Migrator:
|
|||||||
layout = f.flight_plan.layout
|
layout = f.flight_plan.layout
|
||||||
try_set_attr(layout, "nav_to", [])
|
try_set_attr(layout, "nav_to", [])
|
||||||
try_set_attr(layout, "nav_from", [])
|
try_set_attr(layout, "nav_from", [])
|
||||||
|
try_set_attr(layout, "custom_waypoints", [])
|
||||||
if f.flight_type == FlightType.CAS:
|
if f.flight_type == FlightType.CAS:
|
||||||
try_set_attr(layout, "ingress", None)
|
try_set_attr(layout, "ingress", None)
|
||||||
if isinstance(layout, FormationLayout):
|
if isinstance(layout, FormationLayout):
|
||||||
@ -121,10 +123,14 @@ class Migrator:
|
|||||||
try_set_attr(f, "tacan")
|
try_set_attr(f, "tacan")
|
||||||
try_set_attr(f, "tcn_name")
|
try_set_attr(f, "tcn_name")
|
||||||
try_set_attr(f, "fuel", f.unit_type.max_fuel)
|
try_set_attr(f, "fuel", f.unit_type.max_fuel)
|
||||||
|
try_set_attr(f, "plane_altitude_offset", 0)
|
||||||
|
try_set_attr(f, "use_same_livery_for_all_members", True)
|
||||||
if f.package in f.squadron.coalition.ato.packages:
|
if f.package in f.squadron.coalition.ato.packages:
|
||||||
self._update_flight_plan(f)
|
self._update_flight_plan(f)
|
||||||
else:
|
else:
|
||||||
to_remove.append(f.id)
|
to_remove.append(f.id)
|
||||||
|
for m in f.roster.members:
|
||||||
|
try_set_attr(m, "livery", None)
|
||||||
for fid in to_remove:
|
for fid in to_remove:
|
||||||
self.game.db.flights.remove(fid)
|
self.game.db.flights.remove(fid)
|
||||||
|
|
||||||
@ -156,6 +162,7 @@ class Migrator:
|
|||||||
try_set_attr(s, "primary_task", preferred_task)
|
try_set_attr(s, "primary_task", preferred_task)
|
||||||
try_set_attr(s, "max_size", 12)
|
try_set_attr(s, "max_size", 12)
|
||||||
try_set_attr(s, "radio_presets", {})
|
try_set_attr(s, "radio_presets", {})
|
||||||
|
try_set_attr(s, "livery_set", [])
|
||||||
if isinstance(s.country, str):
|
if isinstance(s.country, str):
|
||||||
c = country_dict.get(s.country, s.country)
|
c = country_dict.get(s.country, s.country)
|
||||||
s.country = countries_by_name[c]()
|
s.country = countries_by_name[c]()
|
||||||
@ -201,12 +208,6 @@ class Migrator:
|
|||||||
c.faction.air_defense_units = set(c.faction.air_defense_units)
|
c.faction.air_defense_units = set(c.faction.air_defense_units)
|
||||||
if isinstance(c.faction.missiles, list):
|
if isinstance(c.faction.missiles, list):
|
||||||
c.faction.missiles = set(c.faction.missiles)
|
c.faction.missiles = set(c.faction.missiles)
|
||||||
if isinstance(c.faction.carrier_names, list):
|
|
||||||
c.faction.carrier_names = set(c.faction.carrier_names)
|
|
||||||
if isinstance(c.faction.helicopter_carrier_names, list):
|
|
||||||
c.faction.helicopter_carrier_names = set(
|
|
||||||
c.faction.helicopter_carrier_names
|
|
||||||
)
|
|
||||||
if isinstance(c.faction.naval_units, list):
|
if isinstance(c.faction.naval_units, list):
|
||||||
c.faction.naval_units = set(c.faction.naval_units)
|
c.faction.naval_units = set(c.faction.naval_units)
|
||||||
if isinstance(c.faction.building_set, list):
|
if isinstance(c.faction.building_set, list):
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Optional, Type, List
|
from typing import Any, Optional, Type, List
|
||||||
|
|
||||||
|
from dcs.point import MovingPoint
|
||||||
from dcs.task import (
|
from dcs.task import (
|
||||||
AWACS,
|
AWACS,
|
||||||
AWACSTaskAction,
|
AWACSTaskAction,
|
||||||
@ -31,8 +32,10 @@ from dcs.unitgroup import FlyingGroup
|
|||||||
|
|
||||||
from game.ato import Flight, FlightType
|
from game.ato import Flight, FlightType
|
||||||
from game.ato.flightplans.aewc import AewcFlightPlan
|
from game.ato.flightplans.aewc import AewcFlightPlan
|
||||||
|
from game.ato.flightplans.formationattack import FormationAttackLayout
|
||||||
from game.ato.flightplans.packagerefueling import PackageRefuelingFlightPlan
|
from game.ato.flightplans.packagerefueling import PackageRefuelingFlightPlan
|
||||||
from game.ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan
|
from game.ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan
|
||||||
|
from game.utils import nautical_miles
|
||||||
|
|
||||||
|
|
||||||
class AircraftBehavior:
|
class AircraftBehavior:
|
||||||
@ -100,8 +103,25 @@ class AircraftBehavior:
|
|||||||
flight.squadron.coalition.game.settings.ai_unlimited_fuel
|
flight.squadron.coalition.game.settings.ai_unlimited_fuel
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# at IP, insert waypoint to orient aircraft in correct direction
|
||||||
|
layout = flight.flight_plan.layout
|
||||||
|
at_ip_or_combat = flight.state.is_at_ip or flight.state.in_combat
|
||||||
|
if at_ip_or_combat and isinstance(layout, FormationAttackLayout):
|
||||||
|
a = group.points[0].position
|
||||||
|
b = layout.targets[0].position
|
||||||
|
pos = a.point_from_heading(
|
||||||
|
a.heading_between_point(b), nautical_miles(1).meters
|
||||||
|
)
|
||||||
|
point = MovingPoint(pos)
|
||||||
|
point.alt = group.points[0].alt
|
||||||
|
point.alt_type = group.points[0].alt_type
|
||||||
|
point.ETA_locked = False
|
||||||
|
point.speed = group.points[0].speed
|
||||||
|
point.name = "Orientation WPT"
|
||||||
|
group.points.insert(1, point)
|
||||||
|
|
||||||
# Activate AI unlimited fuel for all flights at startup
|
# Activate AI unlimited fuel for all flights at startup
|
||||||
if ai_unlimited_fuel and not (flight.state.is_at_ip or flight.state.in_combat):
|
if ai_unlimited_fuel and not at_ip_or_combat:
|
||||||
group.points[0].tasks.append(SetUnlimitedFuelCommand(True))
|
group.points[0].tasks.append(SetUnlimitedFuelCommand(True))
|
||||||
|
|
||||||
group.points[0].tasks.append(OptReactOnThreat(react_on_threat))
|
group.points[0].tasks.append(OptReactOnThreat(react_on_threat))
|
||||||
|
|||||||
@ -27,7 +27,14 @@ class AircraftPainter:
|
|||||||
def livery_from_squadron(self) -> Optional[str]:
|
def livery_from_squadron(self) -> Optional[str]:
|
||||||
return self.flight.squadron.livery
|
return self.flight.squadron.livery
|
||||||
|
|
||||||
|
def livery_from_squadron_set(self) -> Optional[str]:
|
||||||
|
if not self.flight.squadron.livery_set:
|
||||||
|
return None
|
||||||
|
return random.choice(self.flight.squadron.livery_set)
|
||||||
|
|
||||||
def determine_livery(self) -> Optional[str]:
|
def determine_livery(self) -> Optional[str]:
|
||||||
|
if (livery := self.livery_from_squadron_set()) is not None:
|
||||||
|
return livery
|
||||||
if (livery := self.livery_from_squadron()) is not None:
|
if (livery := self.livery_from_squadron()) is not None:
|
||||||
return livery
|
return livery
|
||||||
if (livery := self.livery_from_faction()) is not None:
|
if (livery := self.livery_from_faction()) is not None:
|
||||||
@ -37,8 +44,10 @@ class AircraftPainter:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def apply_livery(self) -> None:
|
def apply_livery(self) -> None:
|
||||||
livery = self.determine_livery()
|
for unit, member in zip(self.group.units, self.flight.iter_members()):
|
||||||
if livery is None:
|
livery = self.determine_livery()
|
||||||
return
|
if not (livery or member.livery):
|
||||||
for unit in self.group.units:
|
continue
|
||||||
unit.livery_id = livery
|
unit.livery_id = member.livery if member.livery else livery
|
||||||
|
assert isinstance(unit.livery_id, str)
|
||||||
|
unit.livery_id = unit.livery_id.lower()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from typing import Any, Union, Tuple, Optional
|
from typing import Any, Union, Tuple, Optional, List
|
||||||
|
|
||||||
from dcs import Mission
|
from dcs import Mission
|
||||||
from dcs.country import Country
|
from dcs.country import Country
|
||||||
@ -19,7 +19,8 @@ from dcs.planes import (
|
|||||||
)
|
)
|
||||||
from dcs.point import PointAction
|
from dcs.point import PointAction
|
||||||
from dcs.ships import KUZNECOW
|
from dcs.ships import KUZNECOW
|
||||||
from dcs.terrain import NoParkingSlotError
|
from dcs.terrain import NoParkingSlotError, Sinai, ParkingSlot
|
||||||
|
from dcs.terrain.sinai.airports import Nevatim
|
||||||
from dcs.unitgroup import (
|
from dcs.unitgroup import (
|
||||||
FlyingGroup,
|
FlyingGroup,
|
||||||
ShipGroup,
|
ShipGroup,
|
||||||
@ -110,24 +111,31 @@ class FlightGroupSpawner:
|
|||||||
|
|
||||||
def create_idle_aircraft(self) -> Optional[FlyingGroup[Any]]:
|
def create_idle_aircraft(self) -> Optional[FlyingGroup[Any]]:
|
||||||
group = None
|
group = None
|
||||||
if (
|
cp = self.flight.squadron.location
|
||||||
self.flight.is_helo
|
if self.flight.is_helo or self.flight.is_lha and isinstance(cp, Fob):
|
||||||
or self.flight.is_lha
|
|
||||||
and isinstance(self.flight.squadron.location, Fob)
|
|
||||||
):
|
|
||||||
group = self._generate_at_cp_helipad(
|
group = self._generate_at_cp_helipad(
|
||||||
name=namegen.next_aircraft_name(self.country, self.flight),
|
name=namegen.next_aircraft_name(self.country, self.flight),
|
||||||
cp=self.flight.squadron.location,
|
cp=self.flight.squadron.location,
|
||||||
)
|
)
|
||||||
elif isinstance(self.flight.squadron.location, Fob):
|
elif isinstance(cp, Fob):
|
||||||
group = self._generate_at_cp_ground_spawn(
|
group = self._generate_at_cp_ground_spawn(
|
||||||
name=namegen.next_aircraft_name(self.country, self.flight),
|
name=namegen.next_aircraft_name(self.country, self.flight),
|
||||||
cp=self.flight.squadron.location,
|
cp=self.flight.squadron.location,
|
||||||
)
|
)
|
||||||
elif isinstance(self.flight.squadron.location, Airfield):
|
elif isinstance(cp, Airfield):
|
||||||
|
# TODO: remove hack when fixed in DCS
|
||||||
|
slots = None
|
||||||
|
if self._check_nevatim_hack(cp):
|
||||||
|
ac_type = self.flight.unit_type.dcs_unit_type
|
||||||
|
slots = [
|
||||||
|
slot
|
||||||
|
for slot in cp.dcs_airport.free_parking_slots(ac_type)
|
||||||
|
if slot.slot_name in [str(n) for n in range(55, 66)]
|
||||||
|
]
|
||||||
group = self._generate_at_airfield(
|
group = self._generate_at_airfield(
|
||||||
name=namegen.next_aircraft_name(self.country, self.flight),
|
name=namegen.next_aircraft_name(self.country, self.flight),
|
||||||
airfield=self.flight.squadron.location,
|
airfield=cp,
|
||||||
|
parking_slots=slots,
|
||||||
)
|
)
|
||||||
if group:
|
if group:
|
||||||
group.uncontrolled = True
|
group.uncontrolled = True
|
||||||
@ -196,7 +204,19 @@ class FlightGroupSpawner:
|
|||||||
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
pad_group = self._generate_at_cp_ground_spawn(name, cp)
|
||||||
if pad_group is not None:
|
if pad_group is not None:
|
||||||
return pad_group
|
return pad_group
|
||||||
return self._generate_at_airfield(name, cp)
|
|
||||||
|
# TODO: get rid of the nevatim hack once fixed in DCS...
|
||||||
|
if self._check_nevatim_hack(cp):
|
||||||
|
slots = [
|
||||||
|
slot
|
||||||
|
for slot in cp.dcs_airport.free_parking_slots(
|
||||||
|
self.flight.squadron.aircraft.dcs_unit_type
|
||||||
|
)
|
||||||
|
if slot.slot_name in [str(n) for n in range(55, 66)]
|
||||||
|
]
|
||||||
|
return self._generate_at_airfield(name, cp, slots)
|
||||||
|
else:
|
||||||
|
return self._generate_at_airfield(name, cp)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f"Aircraft spawn behavior not implemented for {cp} ({cp.__class__})"
|
f"Aircraft spawn behavior not implemented for {cp} ({cp.__class__})"
|
||||||
@ -210,6 +230,13 @@ class FlightGroupSpawner:
|
|||||||
group = self._generate_over_departure(name, cp)
|
group = self._generate_over_departure(name, cp)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
def _check_nevatim_hack(self, cp: ControlPoint) -> bool:
|
||||||
|
# TODO: get rid of the nevatim hack once fixed in DCS...
|
||||||
|
nevatim_hack = self.flight.coalition.game.settings.nevatim_parking_fix
|
||||||
|
nevatim_hack &= isinstance(self.mission.terrain, Sinai)
|
||||||
|
nevatim_hack &= isinstance(cp.dcs_airport, Nevatim)
|
||||||
|
return nevatim_hack
|
||||||
|
|
||||||
def generate_mid_mission(self) -> FlyingGroup[Any]:
|
def generate_mid_mission(self) -> FlyingGroup[Any]:
|
||||||
assert isinstance(self.flight.state, InFlight)
|
assert isinstance(self.flight.state, InFlight)
|
||||||
name = namegen.next_aircraft_name(self.country, self.flight)
|
name = namegen.next_aircraft_name(self.country, self.flight)
|
||||||
@ -251,7 +278,12 @@ class FlightGroupSpawner:
|
|||||||
group.points[0].alt_type = alt_type
|
group.points[0].alt_type = alt_type
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def _generate_at_airfield(self, name: str, airfield: Airfield) -> FlyingGroup[Any]:
|
def _generate_at_airfield(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
airfield: Airfield,
|
||||||
|
parking_slots: Optional[List[ParkingSlot]] = None,
|
||||||
|
) -> FlyingGroup[Any]:
|
||||||
# TODO: Delayed runway starts should be converted to air starts for multiplayer.
|
# TODO: Delayed runway starts should be converted to air starts for multiplayer.
|
||||||
# Runway starts do not work with late activated aircraft in multiplayer. Instead
|
# Runway starts do not work with late activated aircraft in multiplayer. Instead
|
||||||
# of spawning on the runway the aircraft will spawn on the taxiway, potentially
|
# of spawning on the runway the aircraft will spawn on the taxiway, potentially
|
||||||
@ -268,7 +300,7 @@ class FlightGroupSpawner:
|
|||||||
maintask=None,
|
maintask=None,
|
||||||
start_type=self._start_type_at_airfield(airfield),
|
start_type=self._start_type_at_airfield(airfield),
|
||||||
group_size=self.flight.count,
|
group_size=self.flight.count,
|
||||||
parking_slots=None,
|
parking_slots=parking_slots,
|
||||||
callsign_name=self.flight.callsign.name if self.flight.callsign else None,
|
callsign_name=self.flight.callsign.name if self.flight.callsign else None,
|
||||||
callsign_nr=self.flight.callsign.nr if self.flight.callsign else None,
|
callsign_nr=self.flight.callsign.nr if self.flight.callsign else None,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from dcs.point import MovingPoint
|
from dcs.point import MovingPoint
|
||||||
from dcs.task import Expend, WeaponType, CarpetBombing, OptROE
|
from dcs.task import Expend, WeaponType, CarpetBombing
|
||||||
|
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightplans.airassault import AirAssaultLayout
|
||||||
from game.utils import feet, knots
|
from game.utils import feet, knots
|
||||||
from pydcs_extensions.hercules.hercules import Hercules
|
from pydcs_extensions.hercules.hercules import Hercules
|
||||||
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
from .pydcswaypointbuilder import PydcsWaypointBuilder
|
||||||
@ -12,14 +12,14 @@ class AirAssaultIngressBuilder(PydcsWaypointBuilder):
|
|||||||
self.register_special_ingress_points()
|
self.register_special_ingress_points()
|
||||||
air_drop = self.group.units[0].unit_type in [Hercules]
|
air_drop = self.group.units[0].unit_type in [Hercules]
|
||||||
if air_drop:
|
if air_drop:
|
||||||
waypoint.speed = knots(230).meters_per_second
|
waypoint.speed = knots(200).meters_per_second
|
||||||
waypoint.speed_locked = True
|
waypoint.speed_locked = True
|
||||||
waypoint.ETA_locked = False
|
waypoint.ETA_locked = False
|
||||||
tgt = self.flight.flight_plan.package.target.position
|
tgt = self.flight.package.target.position
|
||||||
for wpt in self.flight.flight_plan.waypoints:
|
layout = self.flight.flight_plan.layout
|
||||||
if wpt.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC:
|
assert isinstance(layout, AirAssaultLayout)
|
||||||
tgt = wpt.position
|
heading = layout.ingress.position.heading_between_point(tgt)
|
||||||
break
|
tgt = tgt.point_from_heading(heading, feet(6000).meters)
|
||||||
bombing = CarpetBombing(
|
bombing = CarpetBombing(
|
||||||
tgt,
|
tgt,
|
||||||
weapon_type=WeaponType.Bombs,
|
weapon_type=WeaponType.Bombs,
|
||||||
|
|||||||
@ -44,7 +44,16 @@ class DeadIngressBuilder(PydcsWaypointBuilder):
|
|||||||
|
|
||||||
task = AttackGroup(
|
task = AttackGroup(
|
||||||
miz_group.id,
|
miz_group.id,
|
||||||
weapon_type=WeaponType.Guided,
|
weapon_type=WeaponType.ASM,
|
||||||
|
expend=Expend.All,
|
||||||
|
altitude=waypoint.alt,
|
||||||
|
group_attack=True,
|
||||||
|
)
|
||||||
|
waypoint.tasks.append(task)
|
||||||
|
|
||||||
|
task = AttackGroup(
|
||||||
|
miz_group.id,
|
||||||
|
weapon_type=WeaponType.GuidedBombs,
|
||||||
altitude=waypoint.alt,
|
altitude=waypoint.alt,
|
||||||
)
|
)
|
||||||
waypoint.tasks.append(task)
|
waypoint.tasks.append(task)
|
||||||
|
|||||||
@ -40,4 +40,4 @@ class HoldPointBuilder(PydcsWaypointBuilder):
|
|||||||
if self.flight.is_helo:
|
if self.flight.is_helo:
|
||||||
waypoint.add_task(OptFormation.rotary_column())
|
waypoint.add_task(OptFormation.rotary_column())
|
||||||
else:
|
else:
|
||||||
waypoint.add_task(OptFormation.finger_four_close())
|
waypoint.add_task(OptFormation.finger_four_open())
|
||||||
|
|||||||
@ -8,9 +8,7 @@ class LandingPointBuilder(PydcsWaypointBuilder):
|
|||||||
def build(self) -> MovingPoint:
|
def build(self) -> MovingPoint:
|
||||||
waypoint = super().build()
|
waypoint = super().build()
|
||||||
if self.ai_despawn(waypoint):
|
if self.ai_despawn(waypoint):
|
||||||
waypoint.alt = round(
|
waypoint.alt = round(self.flight.unit_type.preferred_patrol_altitude.meters)
|
||||||
self.flight.coalition.doctrine.max_patrol_altitude.meters
|
|
||||||
)
|
|
||||||
waypoint.alt_type = "BARO"
|
waypoint.alt_type = "BARO"
|
||||||
else:
|
else:
|
||||||
waypoint.type = "Land"
|
waypoint.type = "Land"
|
||||||
|
|||||||
@ -124,7 +124,9 @@ class PydcsWaypointBuilder:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def register_special_strike_points(
|
def register_special_strike_points(
|
||||||
self, targets: Iterable[Union[MissionTarget, TheaterUnit]]
|
self,
|
||||||
|
targets: Iterable[Union[MissionTarget, TheaterUnit]],
|
||||||
|
start: int = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create special strike waypoints for various aircraft"""
|
"""Create special strike waypoints for various aircraft"""
|
||||||
for i, t in enumerate(targets):
|
for i, t in enumerate(targets):
|
||||||
@ -135,7 +137,7 @@ class PydcsWaypointBuilder:
|
|||||||
# Add F-15E mission target points as mission 1 (for JDAM for instance)
|
# Add F-15E mission target points as mission 1 (for JDAM for instance)
|
||||||
if self.group.units[0].unit_type == F_15ESE:
|
if self.group.units[0].unit_type == F_15ESE:
|
||||||
self.group.add_nav_target_point(
|
self.group.add_nav_target_point(
|
||||||
t.position, f"M{(i//8)+1}.{i%8+1}" f"\nH-1" f"\nA0" f"\nV0"
|
t.position, f"M{(i//8)+start}.{i%8+1}\nH-1\nA0\nV0"
|
||||||
)
|
)
|
||||||
|
|
||||||
def register_special_ingress_points(self) -> None:
|
def register_special_ingress_points(self) -> None:
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class SplitPointBuilder(PydcsWaypointBuilder):
|
|||||||
if self.flight.is_helo:
|
if self.flight.is_helo:
|
||||||
waypoint.tasks.append(OptFormation.rotary_wedge())
|
waypoint.tasks.append(OptFormation.rotary_wedge())
|
||||||
else:
|
else:
|
||||||
waypoint.tasks.append(OptFormation.finger_four_close())
|
waypoint.tasks.append(OptFormation.finger_four_open())
|
||||||
waypoint.speed_locked = True
|
waypoint.speed_locked = True
|
||||||
waypoint.ETA_locked = False
|
waypoint.ETA_locked = False
|
||||||
if self.flight.is_helo:
|
if self.flight.is_helo:
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import copy
|
|||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
from dcs import Point
|
from dcs import Point
|
||||||
from dcs.planes import B_17G, B_52H, Tu_22M3, B_1B
|
from dcs.planes import B_17G, B_52H, Tu_22M3, B_1B, F_15ESE
|
||||||
from dcs.point import MovingPoint
|
from dcs.point import MovingPoint
|
||||||
from dcs.task import Bombing, Expend, OptFormation, WeaponType, CarpetBombing
|
from dcs.task import Bombing, Expend, OptFormation, WeaponType, CarpetBombing
|
||||||
|
|
||||||
@ -62,13 +62,31 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
|
|||||||
def add_strike_tasks(
|
def add_strike_tasks(
|
||||||
self, waypoint: MovingPoint, weapon_type: WeaponType = WeaponType.Auto
|
self, waypoint: MovingPoint, weapon_type: WeaponType = WeaponType.Auto
|
||||||
) -> None:
|
) -> None:
|
||||||
|
bomber = self.group.units[0].unit_type in [B_1B, B_52H]
|
||||||
|
ratio = len(self.group.units) / len(self.waypoint.targets)
|
||||||
for target in self.waypoint.targets:
|
for target in self.waypoint.targets:
|
||||||
bombing = Bombing(target.position, weapon_type=weapon_type)
|
bombing = Bombing(target.position, weapon_type=weapon_type)
|
||||||
# If there is only one target, drop all ordnance in one pass.
|
# If there is only one target, drop all ordnance in one pass with group attack.
|
||||||
if len(self.waypoint.targets) == 1:
|
if len(self.waypoint.targets) == 1:
|
||||||
bombing.params["expend"] = Expend.All.value
|
bombing.params["expend"] = Expend.All.value
|
||||||
elif target.is_static:
|
bombing.params["groupAttack"] = True
|
||||||
|
elif ratio >= 1:
|
||||||
|
# #TGTs > 1 & #AC >= #TGTs => each AC drops entire payload per TGT
|
||||||
|
bombing.params["expend"] = Expend.All.value
|
||||||
|
elif 1 > ratio >= 0.5:
|
||||||
|
# #TGTs > 1 & 2 * #AC >= #TGTs => each AC drops half payload per TGT
|
||||||
bombing.params["expend"] = Expend.Half.value
|
bombing.params["expend"] = Expend.Half.value
|
||||||
|
elif 0.5 > ratio >= 0.25:
|
||||||
|
# #TGTs > 1 & 4 * #AC >= #TGTs => each AC drops quarter payload per TGT
|
||||||
|
bombing.params["expend"] = Expend.Quarter.value
|
||||||
|
elif 0.25 > ratio >= (1.0 / 6) and bomber:
|
||||||
|
# #TGTs > 1 & 4 * #AC < #TGTs & bomber => each AC drops 4 bombs per TGT
|
||||||
|
bombing.params["expend"] = Expend.Four.value
|
||||||
|
elif bomber:
|
||||||
|
# #TGTs > 1 & 6 * #AC < #TGTs & bomber => each AC drops 2 bombs per TGT
|
||||||
|
bombing.params["expend"] = Expend.Two.value
|
||||||
|
# else => Auto QTY
|
||||||
|
|
||||||
waypoint.tasks.append(bombing)
|
waypoint.tasks.append(bombing)
|
||||||
|
|
||||||
waypoint.speed = mach(0.85, meters(waypoint.alt)).meters_per_second
|
waypoint.speed = mach(0.85, meters(waypoint.alt)).meters_per_second
|
||||||
@ -76,4 +94,6 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
|
|||||||
# Register special waypoints
|
# Register special waypoints
|
||||||
if not self._special_wpts_injected:
|
if not self._special_wpts_injected:
|
||||||
self.register_special_strike_points(self.waypoint.targets)
|
self.register_special_strike_points(self.waypoint.targets)
|
||||||
|
if self.flight.unit_type.dcs_unit_type == F_15ESE:
|
||||||
|
self.register_special_strike_points(self.flight.custom_targets, 2)
|
||||||
self._special_wpts_injected = True
|
self._special_wpts_injected = True
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from dcs.task import StartCommand
|
|||||||
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
from dcs.triggers import Event, TriggerOnce, TriggerRule
|
||||||
from dcs.unitgroup import FlyingGroup
|
from dcs.unitgroup import FlyingGroup
|
||||||
|
|
||||||
from game.ato import Flight, FlightWaypoint
|
from game.ato import Flight, FlightWaypoint, FlightType
|
||||||
from game.ato.flightstate import InFlight, WaitingForStart
|
from game.ato.flightstate import InFlight, WaitingForStart
|
||||||
from game.ato.flightwaypointtype import FlightWaypointType
|
from game.ato.flightwaypointtype import FlightWaypointType
|
||||||
from game.ato.starttype import StartType
|
from game.ato.starttype import StartType
|
||||||
@ -73,6 +73,18 @@ class WaypointGenerator:
|
|||||||
if point.only_for_player and not self.flight.client_count:
|
if point.only_for_player and not self.flight.client_count:
|
||||||
continue
|
continue
|
||||||
if isinstance(self.flight.state, InFlight):
|
if isinstance(self.flight.state, InFlight):
|
||||||
|
if self.flight.flight_type in [
|
||||||
|
FlightType.ESCORT,
|
||||||
|
FlightType.SEAD_ESCORT,
|
||||||
|
]:
|
||||||
|
is_join = point.waypoint_type == FlightWaypointType.JOIN
|
||||||
|
join_passed = self.flight.state.has_passed_waypoint(point)
|
||||||
|
if (
|
||||||
|
is_join
|
||||||
|
and join_passed
|
||||||
|
and point != self.flight.state.current_waypoint
|
||||||
|
):
|
||||||
|
self.builder_for_waypoint(point).add_tasks(self.group.points[0])
|
||||||
if point == self.flight.state.current_waypoint:
|
if point == self.flight.state.current_waypoint:
|
||||||
# We don't need to build this waypoint because pydcs did that for
|
# We don't need to build this waypoint because pydcs did that for
|
||||||
# us, but we do need to configure the tasks for it so that mid-
|
# us, but we do need to configure the tasks for it so that mid-
|
||||||
|
|||||||
@ -50,6 +50,7 @@ from game.weather.weather import Weather
|
|||||||
from .aircraft.flightdata import FlightData
|
from .aircraft.flightdata import FlightData
|
||||||
from .briefinggenerator import CommInfo, JtacInfo, MissionInfoGenerator
|
from .briefinggenerator import CommInfo, JtacInfo, MissionInfoGenerator
|
||||||
from .missiondata import AwacsInfo, TankerInfo
|
from .missiondata import AwacsInfo, TankerInfo
|
||||||
|
from ..persistency import kneeboards_dir
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -440,13 +441,14 @@ class BriefingPage(KneeboardPage):
|
|||||||
sun = Sun(start_pos.lat, start_pos.lng)
|
sun = Sun(start_pos.lat, start_pos.lng)
|
||||||
|
|
||||||
date = fl.squadron.coalition.game.date
|
date = fl.squadron.coalition.game.date
|
||||||
|
dt = datetime.datetime(date.year, date.month, date.day)
|
||||||
tz = fl.squadron.coalition.game.theater.timezone
|
tz = fl.squadron.coalition.game.theater.timezone
|
||||||
|
|
||||||
# Get today's sunrise and sunset in UTC
|
# Get today's sunrise and sunset in UTC
|
||||||
sr_utc = sun.get_sunrise_time(date)
|
sr_utc = sun.get_sunrise_time(dt)
|
||||||
ss_utc = sun.get_sunset_time(date)
|
ss_utc = sun.get_sunset_time(dt)
|
||||||
sr = sr_utc + tz.utcoffset(sun.get_sunrise_time(date))
|
sr = sr_utc + tz.utcoffset(sun.get_sunrise_time(dt))
|
||||||
ss = ss_utc + tz.utcoffset(sun.get_sunset_time(date))
|
ss = ss_utc + tz.utcoffset(sun.get_sunset_time(dt))
|
||||||
|
|
||||||
writer.text(
|
writer.text(
|
||||||
f"Sunrise - Sunset: {sr.strftime('%H:%M')} - {ss.strftime('%H:%M')}"
|
f"Sunrise - Sunset: {sr.strftime('%H:%M')} - {ss.strftime('%H:%M')}"
|
||||||
@ -820,6 +822,14 @@ class KneeboardGenerator(MissionInfoGenerator):
|
|||||||
page_path = aircraft_dir / f"page{idx:02}.png"
|
page_path = aircraft_dir / f"page{idx:02}.png"
|
||||||
page.write(page_path)
|
page.write(page_path)
|
||||||
self.mission.add_aircraft_kneeboard(aircraft.dcs_unit_type, page_path)
|
self.mission.add_aircraft_kneeboard(aircraft.dcs_unit_type, page_path)
|
||||||
|
if not kneeboards_dir().exists():
|
||||||
|
return
|
||||||
|
for type in kneeboards_dir().iterdir():
|
||||||
|
if type.is_dir():
|
||||||
|
for kneeboard in type.iterdir():
|
||||||
|
self.mission.custom_kneeboards[type.name].append(kneeboard)
|
||||||
|
else:
|
||||||
|
self.mission.custom_kneeboards[""].append(type)
|
||||||
|
|
||||||
def pages_by_airframe(self) -> Dict[AircraftType, List[KneeboardPage]]:
|
def pages_by_airframe(self) -> Dict[AircraftType, List[KneeboardPage]]:
|
||||||
"""Returns a list of kneeboard pages per airframe in the mission.
|
"""Returns a list of kneeboard pages per airframe in the mission.
|
||||||
|
|||||||
@ -39,7 +39,13 @@ from dcs.task import (
|
|||||||
OptAlarmState,
|
OptAlarmState,
|
||||||
)
|
)
|
||||||
from dcs.translation import String
|
from dcs.translation import String
|
||||||
from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone
|
from dcs.triggers import (
|
||||||
|
Event,
|
||||||
|
TriggerOnce,
|
||||||
|
TriggerStart,
|
||||||
|
TriggerZone,
|
||||||
|
TriggerZoneQuadPoint,
|
||||||
|
)
|
||||||
from dcs.unit import Unit, InvisibleFARP, BaseFARP, SingleHeliPad, FARP
|
from dcs.unit import Unit, InvisibleFARP, BaseFARP, SingleHeliPad, FARP
|
||||||
from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup
|
from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup
|
||||||
from dcs.unittype import ShipType, VehicleType
|
from dcs.unittype import ShipType, VehicleType
|
||||||
@ -404,14 +410,24 @@ class GroundObjectGenerator:
|
|||||||
# is minimized. As long as the triggerzone is over the scenery object, we're ok.
|
# is minimized. As long as the triggerzone is over the scenery object, we're ok.
|
||||||
smallest_valid_radius = feet(16).meters
|
smallest_valid_radius = feet(16).meters
|
||||||
|
|
||||||
trigger_zone = self.m.triggers.add_triggerzone(
|
if isinstance(scenery.zone, TriggerZoneQuadPoint):
|
||||||
scenery.zone.position,
|
trigger_zone: TriggerZone = self.m.triggers.add_triggerzone_quad(
|
||||||
smallest_valid_radius,
|
scenery.zone.position,
|
||||||
scenery.zone.hidden,
|
scenery.zone.verticies,
|
||||||
scenery.zone.name,
|
scenery.zone.hidden,
|
||||||
color,
|
scenery.zone.name,
|
||||||
scenery.zone.properties,
|
color,
|
||||||
)
|
scenery.zone.properties,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
trigger_zone = self.m.triggers.add_triggerzone(
|
||||||
|
scenery.zone.position,
|
||||||
|
smallest_valid_radius,
|
||||||
|
scenery.zone.hidden,
|
||||||
|
scenery.zone.name,
|
||||||
|
color,
|
||||||
|
scenery.zone.properties,
|
||||||
|
)
|
||||||
# DCS only visually shows a scenery object is dead when
|
# DCS only visually shows a scenery object is dead when
|
||||||
# this trigger rule is applied. Otherwise you can kill a
|
# this trigger rule is applied. Otherwise you can kill a
|
||||||
# structure twice.
|
# structure twice.
|
||||||
|
|||||||
@ -111,6 +111,10 @@ def airwing_dir() -> Path:
|
|||||||
return base_path() / "Retribution" / "AirWing"
|
return base_path() / "Retribution" / "AirWing"
|
||||||
|
|
||||||
|
|
||||||
|
def kneeboards_dir() -> Path:
|
||||||
|
return base_path() / "Retribution" / "Kneeboards"
|
||||||
|
|
||||||
|
|
||||||
def payloads_dir(backup: bool = False) -> Path:
|
def payloads_dir(backup: bool = False) -> Path:
|
||||||
payloads = base_path() / "MissionEditor" / "UnitPayloads"
|
payloads = base_path() / "MissionEditor" / "UnitPayloads"
|
||||||
if backup:
|
if backup:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from asyncio import wait
|
from asyncio import wait, tasks
|
||||||
|
|
||||||
from fastapi import APIRouter, WebSocket
|
from fastapi import APIRouter, WebSocket
|
||||||
from fastapi.encoders import jsonable_encoder
|
from fastapi.encoders import jsonable_encoder
|
||||||
@ -17,7 +17,7 @@ class ConnectionManager:
|
|||||||
async def shutdown(self) -> None:
|
async def shutdown(self) -> None:
|
||||||
futures = []
|
futures = []
|
||||||
for connection in self.active_connections:
|
for connection in self.active_connections:
|
||||||
futures.append(connection.close())
|
futures.append(tasks.create_task(connection.close()))
|
||||||
await wait(futures)
|
await wait(futures)
|
||||||
|
|
||||||
async def connect(self, websocket: WebSocket) -> None:
|
async def connect(self, websocket: WebSocket) -> None:
|
||||||
@ -30,7 +30,9 @@ class ConnectionManager:
|
|||||||
async def broadcast(self, events: GameUpdateEventsJs) -> None:
|
async def broadcast(self, events: GameUpdateEventsJs) -> None:
|
||||||
futures = []
|
futures = []
|
||||||
for connection in self.active_connections:
|
for connection in self.active_connections:
|
||||||
futures.append(connection.send_json(jsonable_encoder(events)))
|
futures.append(
|
||||||
|
tasks.create_task(connection.send_json(jsonable_encoder(events)))
|
||||||
|
)
|
||||||
await wait(futures)
|
await wait(futures)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -314,8 +314,18 @@ class Settings:
|
|||||||
page=CAMPAIGN_DOCTRINE_PAGE,
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
section=GENERAL_SECTION,
|
section=GENERAL_SECTION,
|
||||||
default=False,
|
default=False,
|
||||||
detail=("AI will jettison their fuel tanks as soon as they're empty."),
|
detail="AI will jettison their fuel tanks as soon as they're empty.",
|
||||||
)
|
)
|
||||||
|
max_plane_altitude_offset: int = bounded_int_option(
|
||||||
|
"Maximum randomized altitude offset (x1000 ft) for airplanes.",
|
||||||
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
|
section=GENERAL_SECTION,
|
||||||
|
min=0,
|
||||||
|
max=5,
|
||||||
|
default=2,
|
||||||
|
detail="Creates a randomized altitude offset for airplanes.",
|
||||||
|
)
|
||||||
|
# Doctrine Distances Section
|
||||||
airbase_threat_range: int = bounded_int_option(
|
airbase_threat_range: int = bounded_int_option(
|
||||||
"Airbase threat range (nmi)",
|
"Airbase threat range (nmi)",
|
||||||
page=CAMPAIGN_DOCTRINE_PAGE,
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
@ -388,6 +398,32 @@ class Settings:
|
|||||||
"planned to known threat zones."
|
"planned to known threat zones."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
max_mission_range_planes: int = bounded_int_option(
|
||||||
|
"Auto-planner maximum mission range for airplanes (NM)",
|
||||||
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
|
section=DOCTRINE_DISTANCES_SECTION,
|
||||||
|
default=150,
|
||||||
|
min=150,
|
||||||
|
max=1000,
|
||||||
|
detail=(
|
||||||
|
"The maximum mission distance that's used by the auto-planner for airplanes. "
|
||||||
|
"This setting won't take effect when a larger "
|
||||||
|
"range is defined in the airplane's yaml specification."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
max_mission_range_helicopters: int = bounded_int_option(
|
||||||
|
"Auto-planner maximum mission range for helicopters (NM)",
|
||||||
|
page=CAMPAIGN_DOCTRINE_PAGE,
|
||||||
|
section=DOCTRINE_DISTANCES_SECTION,
|
||||||
|
default=100,
|
||||||
|
min=50,
|
||||||
|
max=1000,
|
||||||
|
detail=(
|
||||||
|
"The maximum mission distance that's used by the auto-planner for helicopters. "
|
||||||
|
"This setting won't take effect when a larger "
|
||||||
|
"range is defined in the helicopter's yaml specification."
|
||||||
|
),
|
||||||
|
)
|
||||||
# Pilots and Squadrons
|
# Pilots and Squadrons
|
||||||
ai_pilot_levelling: bool = boolean_option(
|
ai_pilot_levelling: bool = boolean_option(
|
||||||
"Allow AI pilot leveling",
|
"Allow AI pilot leveling",
|
||||||
@ -720,6 +756,16 @@ class Settings:
|
|||||||
"will not be included in automatically planned OCA packages."
|
"will not be included in automatically planned OCA packages."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
nevatim_parking_fix: bool = boolean_option(
|
||||||
|
"Force air-starts for all aircraft at Nevatim",
|
||||||
|
page=MISSION_GENERATOR_PAGE,
|
||||||
|
section=GAMEPLAY_SECTION,
|
||||||
|
default=True, # TODO: set to False or remove this when DCS is fixed
|
||||||
|
detail=(
|
||||||
|
"Air-starts forced for all aircraft at Nevatim except parking slots "
|
||||||
|
"55 till 65, since those are the only ones that work."
|
||||||
|
),
|
||||||
|
)
|
||||||
# Mission specific
|
# Mission specific
|
||||||
desired_player_mission_duration: timedelta = minutes_option(
|
desired_player_mission_duration: timedelta = minutes_option(
|
||||||
"Desired mission duration",
|
"Desired mission duration",
|
||||||
@ -1150,6 +1196,7 @@ class Settings:
|
|||||||
enable_transfer_cheat: bool = False
|
enable_transfer_cheat: bool = False
|
||||||
enable_runway_state_cheat: bool = False
|
enable_runway_state_cheat: bool = False
|
||||||
enable_air_wing_adjustments: bool = False
|
enable_air_wing_adjustments: bool = False
|
||||||
|
enable_enemy_buy_sell: bool = False
|
||||||
|
|
||||||
# LUA Plugins system
|
# LUA Plugins system
|
||||||
plugins: Dict[str, bool] = field(default_factory=dict)
|
plugins: Dict[str, bool] = field(default_factory=dict)
|
||||||
|
|||||||
@ -52,6 +52,7 @@ class AirWing:
|
|||||||
heli: bool,
|
heli: bool,
|
||||||
this_turn: bool,
|
this_turn: bool,
|
||||||
preferred_type: Optional[AircraftType] = None,
|
preferred_type: Optional[AircraftType] = None,
|
||||||
|
ignore_range: bool = False,
|
||||||
) -> list[Squadron]:
|
) -> list[Squadron]:
|
||||||
airfield_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
airfield_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||||
best_aircraft = AircraftType.priority_list_for_task(task)
|
best_aircraft = AircraftType.priority_list_for_task(task)
|
||||||
@ -68,7 +69,7 @@ class AirWing:
|
|||||||
]
|
]
|
||||||
for squadron in squadrons:
|
for squadron in squadrons:
|
||||||
if squadron.can_auto_assign_mission(
|
if squadron.can_auto_assign_mission(
|
||||||
location, task, size, heli, this_turn
|
location, task, size, heli, this_turn, ignore_range
|
||||||
):
|
):
|
||||||
capable_at_base.append(squadron)
|
capable_at_base.append(squadron)
|
||||||
if squadron.aircraft not in best_aircraft:
|
if squadron.aircraft not in best_aircraft:
|
||||||
@ -100,9 +101,10 @@ class AirWing:
|
|||||||
heli: bool,
|
heli: bool,
|
||||||
this_turn: bool,
|
this_turn: bool,
|
||||||
preferred_type: Optional[AircraftType] = None,
|
preferred_type: Optional[AircraftType] = None,
|
||||||
|
ignore_range: bool = False,
|
||||||
) -> Optional[Squadron]:
|
) -> Optional[Squadron]:
|
||||||
for squadron in self.best_squadrons_for(
|
for squadron in self.best_squadrons_for(
|
||||||
location, task, size, heli, this_turn, preferred_type
|
location, task, size, heli, this_turn, preferred_type, ignore_range
|
||||||
):
|
):
|
||||||
return squadron
|
return squadron
|
||||||
return None
|
return None
|
||||||
|
|||||||
@ -18,7 +18,7 @@ from game.theater import ParkingType
|
|||||||
from .pilot import Pilot, PilotStatus
|
from .pilot import Pilot, PilotStatus
|
||||||
from ..db.database import Database
|
from ..db.database import Database
|
||||||
from ..radio.radios import RadioFrequency
|
from ..radio.radios import RadioFrequency
|
||||||
from ..utils import meters
|
from ..utils import meters, nautical_miles
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from game import Game
|
from game import Game
|
||||||
@ -40,6 +40,7 @@ class Squadron:
|
|||||||
aircraft: AircraftType
|
aircraft: AircraftType
|
||||||
max_size: int
|
max_size: int
|
||||||
livery: Optional[str]
|
livery: Optional[str]
|
||||||
|
livery_set: list[str] # will override livery if not empty
|
||||||
primary_task: FlightType
|
primary_task: FlightType
|
||||||
auto_assignable_mission_types: set[FlightType]
|
auto_assignable_mission_types: set[FlightType]
|
||||||
radio_presets: dict[Union[str, int], list[RadioFrequency]]
|
radio_presets: dict[Union[str, int], list[RadioFrequency]]
|
||||||
@ -281,6 +282,7 @@ class Squadron:
|
|||||||
size: int,
|
size: int,
|
||||||
heli: bool,
|
heli: bool,
|
||||||
this_turn: bool,
|
this_turn: bool,
|
||||||
|
ignore_range: bool = False,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if (
|
if (
|
||||||
self.location.cptype.name in ["FOB", "FARP"]
|
self.location.cptype.name in ["FOB", "FARP"]
|
||||||
@ -304,8 +306,23 @@ class Squadron:
|
|||||||
if heli and task == FlightType.REFUELING:
|
if heli and task == FlightType.REFUELING:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if ignore_range:
|
||||||
|
return True
|
||||||
|
|
||||||
distance_to_target = meters(location.distance_to(self.location))
|
distance_to_target = meters(location.distance_to(self.location))
|
||||||
return distance_to_target <= self.aircraft.max_mission_range
|
max_plane_dist = nautical_miles(
|
||||||
|
self.coalition.game.settings.max_mission_range_planes
|
||||||
|
)
|
||||||
|
max_heli_dist = nautical_miles(
|
||||||
|
self.coalition.game.settings.max_mission_range_helicopters
|
||||||
|
)
|
||||||
|
if self.aircraft.helicopter:
|
||||||
|
return distance_to_target <= max(
|
||||||
|
self.aircraft.max_mission_range, max_heli_dist
|
||||||
|
)
|
||||||
|
return distance_to_target <= max(
|
||||||
|
self.aircraft.max_mission_range, max_plane_dist
|
||||||
|
)
|
||||||
|
|
||||||
def operates_from(self, control_point: ControlPoint) -> bool:
|
def operates_from(self, control_point: ControlPoint) -> bool:
|
||||||
if not control_point.can_operate(self.aircraft):
|
if not control_point.can_operate(self.aircraft):
|
||||||
@ -487,6 +504,7 @@ class Squadron:
|
|||||||
squadron_def.aircraft,
|
squadron_def.aircraft,
|
||||||
max_size,
|
max_size,
|
||||||
squadron_def.livery,
|
squadron_def.livery,
|
||||||
|
squadron_def.livery_set,
|
||||||
primary_task,
|
primary_task,
|
||||||
squadron_def.auto_assignable_mission_types,
|
squadron_def.auto_assignable_mission_types,
|
||||||
squadron_def.radio_presets,
|
squadron_def.radio_presets,
|
||||||
|
|||||||
@ -27,6 +27,7 @@ class SquadronDef:
|
|||||||
role: str
|
role: str
|
||||||
aircraft: AircraftType
|
aircraft: AircraftType
|
||||||
livery: Optional[str]
|
livery: Optional[str]
|
||||||
|
livery_set: list[str]
|
||||||
auto_assignable_mission_types: set[FlightType]
|
auto_assignable_mission_types: set[FlightType]
|
||||||
radio_presets: dict[Union[str, int], list[RadioFrequency]]
|
radio_presets: dict[Union[str, int], list[RadioFrequency]]
|
||||||
operating_bases: OperatingBases
|
operating_bases: OperatingBases
|
||||||
@ -103,6 +104,7 @@ class SquadronDef:
|
|||||||
role=data["role"],
|
role=data["role"],
|
||||||
aircraft=unit_type,
|
aircraft=unit_type,
|
||||||
livery=data.get("livery"),
|
livery=data.get("livery"),
|
||||||
|
livery_set=data.get("livery_set", []),
|
||||||
auto_assignable_mission_types=set(unit_type.iter_task_capabilities()),
|
auto_assignable_mission_types=set(unit_type.iter_task_capabilities()),
|
||||||
radio_presets=radio_presets,
|
radio_presets=radio_presets,
|
||||||
operating_bases=OperatingBases.from_yaml(unit_type, data.get("bases", {})),
|
operating_bases=OperatingBases.from_yaml(unit_type, data.get("bases", {})),
|
||||||
|
|||||||
@ -1021,6 +1021,8 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
|
|||||||
# clear the ATO and replan the airlifts with the correct time.
|
# clear the ATO and replan the airlifts with the correct time.
|
||||||
self.ground_unit_orders.process(game, game.conditions.start_time)
|
self.ground_unit_orders.process(game, game.conditions.start_time)
|
||||||
|
|
||||||
|
self.release_parking_slots()
|
||||||
|
|
||||||
runway_status = self.runway_status
|
runway_status = self.runway_status
|
||||||
if runway_status is not None:
|
if runway_status is not None:
|
||||||
runway_status.process_turn()
|
runway_status.process_turn()
|
||||||
@ -1393,6 +1395,11 @@ class NavalControlPoint(
|
|||||||
L02,
|
L02,
|
||||||
L52,
|
L52,
|
||||||
L61,
|
L61,
|
||||||
|
CV_1143_5,
|
||||||
|
CVN_71,
|
||||||
|
CVN_72,
|
||||||
|
CVN_73,
|
||||||
|
CVN_75,
|
||||||
]:
|
]:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
@ -130,6 +130,8 @@ class IadsNetwork:
|
|||||||
# but if it does, we want to know because it's supposed to be impossible afaict
|
# but if it does, we want to know because it's supposed to be impossible afaict
|
||||||
skynet_node = SkynetNode.from_group(node.group)
|
skynet_node = SkynetNode.from_group(node.group)
|
||||||
for connection in node.connections.values():
|
for connection in node.connections.values():
|
||||||
|
if not any([x.alive for x in connection.units]):
|
||||||
|
continue
|
||||||
if connection.ground_object.is_friendly(
|
if connection.ground_object.is_friendly(
|
||||||
skynet_node.player
|
skynet_node.player
|
||||||
) and not game.iads_considerate_culling(connection.ground_object):
|
) and not game.iads_considerate_culling(connection.ground_object):
|
||||||
|
|||||||
@ -7,12 +7,13 @@ from datetime import datetime, time
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import dcs.statics
|
import dcs.statics
|
||||||
|
from dcs.countries import country_dict
|
||||||
|
|
||||||
from game import Game
|
from game import Game
|
||||||
from game.factions.faction import Faction
|
from game.factions.faction import Faction
|
||||||
from game.naming import namegen
|
from game.naming import namegen
|
||||||
from game.scenery_group import SceneryGroup
|
from game.scenery_group import SceneryGroup
|
||||||
from game.theater import PointWithHeading, PresetLocation
|
from game.theater import PointWithHeading, PresetLocation, NavalControlPoint
|
||||||
from game.theater.theatergroundobject import (
|
from game.theater.theatergroundobject import (
|
||||||
BuildingGroundObject,
|
BuildingGroundObject,
|
||||||
IadsBuildingGroundObject,
|
IadsBuildingGroundObject,
|
||||||
@ -26,12 +27,21 @@ from . import (
|
|||||||
Fob,
|
Fob,
|
||||||
OffMapSpawn,
|
OffMapSpawn,
|
||||||
)
|
)
|
||||||
from .theatergroup import IadsGroundGroup, IadsRole, SceneryUnit, TheaterGroup
|
from .theatergroup import (
|
||||||
|
IadsGroundGroup,
|
||||||
|
IadsRole,
|
||||||
|
SceneryUnit,
|
||||||
|
TheaterGroup,
|
||||||
|
TheaterUnit,
|
||||||
|
)
|
||||||
from ..armedforces.armedforces import ArmedForces
|
from ..armedforces.armedforces import ArmedForces
|
||||||
from ..armedforces.forcegroup import ForceGroup
|
from ..armedforces.forcegroup import ForceGroup
|
||||||
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
|
||||||
|
from ..campaignloader.campaigncarrierconfig import CampaignCarrierConfig
|
||||||
from ..campaignloader.campaigngroundconfig import TgoConfig
|
from ..campaignloader.campaigngroundconfig import TgoConfig
|
||||||
from ..data.groups import GroupTask
|
from ..data.groups import GroupTask
|
||||||
|
from ..data.units import UnitClass
|
||||||
|
from ..dcs.shipunittype import ShipUnitType
|
||||||
from ..profiling import logged_duration
|
from ..profiling import logged_duration
|
||||||
from ..settings import Settings
|
from ..settings import Settings
|
||||||
|
|
||||||
@ -49,6 +59,7 @@ class GeneratorSettings:
|
|||||||
no_player_navy: bool
|
no_player_navy: bool
|
||||||
no_enemy_navy: bool
|
no_enemy_navy: bool
|
||||||
tgo_config: TgoConfig
|
tgo_config: TgoConfig
|
||||||
|
carrier_config: CampaignCarrierConfig
|
||||||
squadrons_start_full: bool
|
squadrons_start_full: bool
|
||||||
|
|
||||||
|
|
||||||
@ -58,6 +69,7 @@ class ModSettings:
|
|||||||
a6a_intruder: bool = False
|
a6a_intruder: bool = False
|
||||||
a7e_corsair2: bool = False
|
a7e_corsair2: bool = False
|
||||||
f4bc_phantom: bool = False
|
f4bc_phantom: bool = False
|
||||||
|
f9f_panther: bool = False
|
||||||
f15d_baz: bool = False
|
f15d_baz: bool = False
|
||||||
f_15_idf: bool = False
|
f_15_idf: bool = False
|
||||||
f_16_idf: bool = False
|
f_16_idf: bool = False
|
||||||
@ -72,6 +84,7 @@ class ModSettings:
|
|||||||
irondome: bool = False
|
irondome: bool = False
|
||||||
uh_60l: bool = False
|
uh_60l: bool = False
|
||||||
jas39_gripen: bool = False
|
jas39_gripen: bool = False
|
||||||
|
super_etendard: bool = False
|
||||||
su30_flanker_h: bool = False
|
su30_flanker_h: bool = False
|
||||||
su57_felon: bool = False
|
su57_felon: bool = False
|
||||||
frenchpack: bool = False
|
frenchpack: bool = False
|
||||||
@ -125,11 +138,13 @@ class GameGenerator:
|
|||||||
|
|
||||||
def should_remove_carrier(self, player: bool) -> bool:
|
def should_remove_carrier(self, player: bool) -> bool:
|
||||||
faction = self.player if player else self.enemy
|
faction = self.player if player else self.enemy
|
||||||
return self.generator_settings.no_carrier or not faction.carrier_names
|
return self.generator_settings.no_carrier or not faction.carriers
|
||||||
|
|
||||||
def should_remove_lha(self, player: bool) -> bool:
|
def should_remove_lha(self, player: bool) -> bool:
|
||||||
faction = self.player if player else self.enemy
|
faction = self.player if player else self.enemy
|
||||||
return self.generator_settings.no_lha or not faction.helicopter_carrier_names
|
return self.generator_settings.no_lha or not [
|
||||||
|
x for x in faction.carriers if x.unit_class == UnitClass.HELICOPTER_CARRIER
|
||||||
|
]
|
||||||
|
|
||||||
def prepare_theater(self) -> None:
|
def prepare_theater(self) -> None:
|
||||||
to_remove: List[ControlPoint] = []
|
to_remove: List[ControlPoint] = []
|
||||||
@ -221,14 +236,63 @@ class GenericCarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
|
|||||||
carrier = next(self.control_point.ground_objects[-1].units)
|
carrier = next(self.control_point.ground_objects[-1].units)
|
||||||
carrier.name = carrier_name
|
carrier.name = carrier_name
|
||||||
|
|
||||||
|
def apply_carrier_config(self) -> None:
|
||||||
|
assert isinstance(self.control_point, NavalControlPoint)
|
||||||
|
# If the campaign designer has specified a preferred name, use that
|
||||||
|
# Note that the preferred name needs to exist in the faction, so we
|
||||||
|
# don't end up with Kuznetsov carriers called CV-59 Forrestal
|
||||||
|
preferred_name = None
|
||||||
|
preferred_type = None
|
||||||
|
carrier_map = self.generator_settings.carrier_config.by_original_name
|
||||||
|
if ccfg := carrier_map.get(self.control_point.name):
|
||||||
|
preferred_name = ccfg.preferred_name
|
||||||
|
preferred_type = ccfg.preferred_type
|
||||||
|
carrier_unit = self.get_carrier_unit()
|
||||||
|
if preferred_type and preferred_type.dcs_unit_type in [
|
||||||
|
v
|
||||||
|
for k, v in country_dict[self.faction.country.id].Ship.__dict__.items() # type: ignore
|
||||||
|
if "__" not in k
|
||||||
|
]:
|
||||||
|
carrier_unit.type = preferred_type.dcs_unit_type
|
||||||
|
if preferred_name:
|
||||||
|
self.control_point.name = preferred_name
|
||||||
|
else:
|
||||||
|
carrier_type = preferred_type if preferred_type else carrier_unit.unit_type
|
||||||
|
assert isinstance(carrier_type, ShipUnitType)
|
||||||
|
# Otherwise pick randomly from the names specified for that particular carrier type
|
||||||
|
carrier_names = self.faction.carriers.get(carrier_type)
|
||||||
|
if carrier_names:
|
||||||
|
self.control_point.name = random.choice(list(carrier_names))
|
||||||
|
else:
|
||||||
|
self.control_point.name = carrier_type.display_name
|
||||||
|
carrier_unit.name = self.control_point.name
|
||||||
|
# Prevents duplicate carrier or LHA names in campaigns with more that one of either.
|
||||||
|
for carrier_type_key in self.faction.carriers:
|
||||||
|
for carrier_name in self.faction.carriers[carrier_type_key]:
|
||||||
|
if carrier_name == self.control_point.name:
|
||||||
|
self.faction.carriers[carrier_type_key].remove(
|
||||||
|
self.control_point.name
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_carrier_unit(self) -> TheaterUnit:
|
||||||
|
carrier_go = [
|
||||||
|
go
|
||||||
|
for go in self.control_point.ground_objects
|
||||||
|
if go.category in ["CARRIER", "LHA"]
|
||||||
|
][0]
|
||||||
|
groups = [
|
||||||
|
g for g in carrier_go.groups if "Carrier" in g.name or "LHA" in g.name
|
||||||
|
]
|
||||||
|
return groups[0].units[0]
|
||||||
|
|
||||||
|
|
||||||
class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
|
class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
|
||||||
def generate(self) -> bool:
|
def generate(self) -> bool:
|
||||||
if not super().generate():
|
if not super().generate():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
carrier_names = self.faction.carrier_names
|
carriers = self.faction.carriers
|
||||||
if not carrier_names:
|
if not carriers:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Skipping generation of {self.control_point.name} because "
|
f"Skipping generation of {self.control_point.name} because "
|
||||||
f"{self.faction_name} has no carriers"
|
f"{self.faction_name} has no carriers"
|
||||||
@ -239,6 +303,7 @@ class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
|
|||||||
if not unit_group:
|
if not unit_group:
|
||||||
logging.error(f"{self.faction_name} has no access to AircraftCarrier")
|
logging.error(f"{self.faction_name} has no access to AircraftCarrier")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.generate_ground_object_from_group(
|
self.generate_ground_object_from_group(
|
||||||
unit_group,
|
unit_group,
|
||||||
PresetLocation(
|
PresetLocation(
|
||||||
@ -248,7 +313,7 @@ class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
|
|||||||
),
|
),
|
||||||
GroupTask.AIRCRAFT_CARRIER,
|
GroupTask.AIRCRAFT_CARRIER,
|
||||||
)
|
)
|
||||||
self.update_carrier_name(random.choice(list(carrier_names)))
|
self.apply_carrier_config()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -257,8 +322,8 @@ class LhaGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
|
|||||||
if not super().generate():
|
if not super().generate():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
lha_names = self.faction.helicopter_carrier_names
|
lhas = self.faction.carriers
|
||||||
if not lha_names:
|
if not lhas:
|
||||||
logging.info(
|
logging.info(
|
||||||
f"Skipping generation of {self.control_point.name} because "
|
f"Skipping generation of {self.control_point.name} because "
|
||||||
f"{self.faction_name} has no LHAs"
|
f"{self.faction_name} has no LHAs"
|
||||||
@ -280,7 +345,7 @@ class LhaGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
|
|||||||
),
|
),
|
||||||
GroupTask.HELICOPTER_CARRIER,
|
GroupTask.HELICOPTER_CARRIER,
|
||||||
)
|
)
|
||||||
self.update_carrier_name(random.choice(list(lha_names)))
|
self.apply_carrier_config()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
MAJOR_VERSION = 1
|
MAJOR_VERSION = 1
|
||||||
MINOR_VERSION = 3
|
MINOR_VERSION = 4
|
||||||
MICRO_VERSION = 0
|
MICRO_VERSION = 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from .SWPack import *
|
|||||||
from .a4ec import *
|
from .a4ec import *
|
||||||
from .a7e import *
|
from .a7e import *
|
||||||
from .a6a import *
|
from .a6a import *
|
||||||
|
from .f9f import *
|
||||||
from .f100 import *
|
from .f100 import *
|
||||||
from .f104 import *
|
from .f104 import *
|
||||||
from .f105 import *
|
from .f105 import *
|
||||||
@ -20,6 +21,7 @@ from .irondome import *
|
|||||||
from .jas39 import *
|
from .jas39 import *
|
||||||
from .ov10a import *
|
from .ov10a import *
|
||||||
from .spanishnavypack import *
|
from .spanishnavypack import *
|
||||||
|
from .super_etendard import *
|
||||||
from .su30 import *
|
from .su30 import *
|
||||||
from .su57 import *
|
from .su57 import *
|
||||||
from .swedishmilitaryassetspack import *
|
from .swedishmilitaryassetspack import *
|
||||||
|
|||||||
1
pydcs_extensions/f9f/__init__.py
Normal file
1
pydcs_extensions/f9f/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .f9f import *
|
||||||
699
pydcs_extensions/f9f/f9f.py
Normal file
699
pydcs_extensions/f9f/f9f.py
Normal file
@ -0,0 +1,699 @@
|
|||||||
|
from typing import Set
|
||||||
|
|
||||||
|
from dcs import task
|
||||||
|
from dcs.planes import PlaneType
|
||||||
|
from dcs.weapons_data import Weapons
|
||||||
|
|
||||||
|
from game.modsupport import planemod
|
||||||
|
|
||||||
|
|
||||||
|
@planemod
|
||||||
|
class VSN_F9F(PlaneType):
|
||||||
|
id = "VSN_F9F"
|
||||||
|
flyable = True
|
||||||
|
height = 3.73
|
||||||
|
width = 11.58
|
||||||
|
length = 11.84
|
||||||
|
fuel_max = 2310
|
||||||
|
max_speed = 961.2
|
||||||
|
category = "Interceptor" # {78EFB7A2-FD52-4b57-A6A6-3BF0E1D6555F}
|
||||||
|
radio_frequency = 127.5
|
||||||
|
|
||||||
|
livery_name = "VSN_F9F" # from type
|
||||||
|
|
||||||
|
class Pylon1:
|
||||||
|
Smoke_Generator___red_ = (1, Weapons.Smoke_Generator___red_)
|
||||||
|
Smoke_Generator___green_ = (1, Weapons.Smoke_Generator___green_)
|
||||||
|
Smoke_Generator___blue_ = (1, Weapons.Smoke_Generator___blue_)
|
||||||
|
Smoke_Generator___white_ = (1, Weapons.Smoke_Generator___white_)
|
||||||
|
Smoke_Generator___yellow_ = (1, Weapons.Smoke_Generator___yellow_)
|
||||||
|
Smoke_Generator___orange_ = (1, Weapons.Smoke_Generator___orange_)
|
||||||
|
|
||||||
|
class Pylon2:
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
AN_M64___500lb_GP_Bomb_LD_ = (2, Weapons.AN_M64___500lb_GP_Bomb_LD_)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (2, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (2, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
HVAR__UnGd_Rkt = (2, Weapons.HVAR__UnGd_Rkt)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||||
|
)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
|
||||||
|
class Pylon3:
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
AN_M64___500lb_GP_Bomb_LD_ = (3, Weapons.AN_M64___500lb_GP_Bomb_LD_)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (3, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (3, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
HVAR__UnGd_Rkt = (3, Weapons.HVAR__UnGd_Rkt)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||||
|
)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
3,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
|
||||||
|
class Pylon4:
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
AN_M64___500lb_GP_Bomb_LD_ = (4, Weapons.AN_M64___500lb_GP_Bomb_LD_)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (4, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (4, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
HVAR__UnGd_Rkt = (4, Weapons.HVAR__UnGd_Rkt)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||||
|
)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
4,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
|
||||||
|
class Pylon5:
|
||||||
|
AIM_9B_Sidewinder_IR_AAM = (5, Weapons.AIM_9B_Sidewinder_IR_AAM)
|
||||||
|
Mk_83___1000lb_GP_Bomb_LD = (5, Weapons.Mk_83___1000lb_GP_Bomb_LD)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
5,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
M117___750lb_GP_Bomb_LD = (5, Weapons.M117___750lb_GP_Bomb_LD)
|
||||||
|
AN_M64___500lb_GP_Bomb_LD_ = (5, Weapons.AN_M64___500lb_GP_Bomb_LD_)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (5, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (5, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
HVAR__UnGd_Rkt = (5, Weapons.HVAR__UnGd_Rkt)
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
Smoke_Generator___red_ = (5, Weapons.Smoke_Generator___red_)
|
||||||
|
Smoke_Generator___green_ = (5, Weapons.Smoke_Generator___green_)
|
||||||
|
Smoke_Generator___blue_ = (5, Weapons.Smoke_Generator___blue_)
|
||||||
|
Smoke_Generator___white_ = (5, Weapons.Smoke_Generator___white_)
|
||||||
|
Smoke_Generator___yellow_ = (5, Weapons.Smoke_Generator___yellow_)
|
||||||
|
Smoke_Generator___orange_ = (5, Weapons.Smoke_Generator___orange_)
|
||||||
|
|
||||||
|
class Pylon6:
|
||||||
|
Smoke_Generator___red_ = (6, Weapons.Smoke_Generator___red_)
|
||||||
|
Smoke_Generator___green_ = (6, Weapons.Smoke_Generator___green_)
|
||||||
|
Smoke_Generator___blue_ = (6, Weapons.Smoke_Generator___blue_)
|
||||||
|
Smoke_Generator___white_ = (6, Weapons.Smoke_Generator___white_)
|
||||||
|
Smoke_Generator___yellow_ = (6, Weapons.Smoke_Generator___yellow_)
|
||||||
|
Smoke_Generator___orange_ = (6, Weapons.Smoke_Generator___orange_)
|
||||||
|
|
||||||
|
class Pylon7:
|
||||||
|
AIM_9B_Sidewinder_IR_AAM = (7, Weapons.AIM_9B_Sidewinder_IR_AAM)
|
||||||
|
Mk_83___1000lb_GP_Bomb_LD = (7, Weapons.Mk_83___1000lb_GP_Bomb_LD)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
7,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
M117___750lb_GP_Bomb_LD = (7, Weapons.M117___750lb_GP_Bomb_LD)
|
||||||
|
AN_M64___500lb_GP_Bomb_LD_ = (7, Weapons.AN_M64___500lb_GP_Bomb_LD_)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (7, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (7, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
HVAR__UnGd_Rkt = (7, Weapons.HVAR__UnGd_Rkt)
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
Smoke_Generator___red_ = (7, Weapons.Smoke_Generator___red_)
|
||||||
|
Smoke_Generator___green_ = (7, Weapons.Smoke_Generator___green_)
|
||||||
|
Smoke_Generator___blue_ = (7, Weapons.Smoke_Generator___blue_)
|
||||||
|
Smoke_Generator___white_ = (7, Weapons.Smoke_Generator___white_)
|
||||||
|
Smoke_Generator___yellow_ = (7, Weapons.Smoke_Generator___yellow_)
|
||||||
|
Smoke_Generator___orange_ = (7, Weapons.Smoke_Generator___orange_)
|
||||||
|
|
||||||
|
class Pylon8:
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
AN_M64___500lb_GP_Bomb_LD_ = (8, Weapons.AN_M64___500lb_GP_Bomb_LD_)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (8, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (8, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
HVAR__UnGd_Rkt = (8, Weapons.HVAR__UnGd_Rkt)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||||
|
)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
8,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
|
||||||
|
class Pylon9:
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
AN_M64___500lb_GP_Bomb_LD_ = (9, Weapons.AN_M64___500lb_GP_Bomb_LD_)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (9, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (9, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
HVAR__UnGd_Rkt = (9, Weapons.HVAR__UnGd_Rkt)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||||
|
)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
9,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
|
||||||
|
class Pylon10:
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum,
|
||||||
|
)
|
||||||
|
LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_131_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||||
|
)
|
||||||
|
AN_M64___500lb_GP_Bomb_LD_ = (10, Weapons.AN_M64___500lb_GP_Bomb_LD_)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (10, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (10, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
HVAR__UnGd_Rkt = (10, Weapons.HVAR__UnGd_Rkt)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||||
|
)
|
||||||
|
LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_3_pod___19_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
|
||||||
|
class Pylon11:
|
||||||
|
Smoke_Generator___red_ = (11, Weapons.Smoke_Generator___red_)
|
||||||
|
Smoke_Generator___green_ = (11, Weapons.Smoke_Generator___green_)
|
||||||
|
Smoke_Generator___blue_ = (11, Weapons.Smoke_Generator___blue_)
|
||||||
|
Smoke_Generator___white_ = (11, Weapons.Smoke_Generator___white_)
|
||||||
|
Smoke_Generator___yellow_ = (11, Weapons.Smoke_Generator___yellow_)
|
||||||
|
Smoke_Generator___orange_ = (11, Weapons.Smoke_Generator___orange_)
|
||||||
|
|
||||||
|
pylons: Set[int] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
|
||||||
|
|
||||||
|
tasks = [
|
||||||
|
task.CAP,
|
||||||
|
task.CAS,
|
||||||
|
task.Escort,
|
||||||
|
task.FighterSweep,
|
||||||
|
task.GroundAttack,
|
||||||
|
task.Intercept,
|
||||||
|
task.AntishipStrike,
|
||||||
|
]
|
||||||
|
task_default = task.GroundAttack
|
||||||
File diff suppressed because it is too large
Load Diff
1
pydcs_extensions/super_etendard/__init__.py
Normal file
1
pydcs_extensions/super_etendard/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .super_etendard import *
|
||||||
144
pydcs_extensions/super_etendard/super_etendard.py
Normal file
144
pydcs_extensions/super_etendard/super_etendard.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
from dcs import task
|
||||||
|
from dcs.planes import PlaneType
|
||||||
|
from dcs.weapons_data import Weapons
|
||||||
|
|
||||||
|
|
||||||
|
from game.modsupport import planemod
|
||||||
|
from pydcs_extensions.weapon_injector import inject_weapons
|
||||||
|
|
||||||
|
|
||||||
|
class WeaponsSEM:
|
||||||
|
Fuel_Tank_1100_Liter = {
|
||||||
|
"clsid": "{SEM1100_PTB}",
|
||||||
|
"name": "Fuel Tank 1100 Liter",
|
||||||
|
"weight": 1150,
|
||||||
|
}
|
||||||
|
Fuel_Tank_625_Liter = {
|
||||||
|
"clsid": "{SEM625_PTB}",
|
||||||
|
"name": "Fuel Tank 625 Liter",
|
||||||
|
"weight": 1150,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inject_weapons(WeaponsSEM)
|
||||||
|
|
||||||
|
|
||||||
|
@planemod
|
||||||
|
class VSN_SEM(PlaneType):
|
||||||
|
id = "VSN_SEM"
|
||||||
|
flyable = True
|
||||||
|
height = 4
|
||||||
|
width = 13.05
|
||||||
|
length = 18
|
||||||
|
fuel_max = 6103
|
||||||
|
max_speed = 2649.996
|
||||||
|
chaff = 90
|
||||||
|
flare = 45
|
||||||
|
charge_total = 180
|
||||||
|
chaff_charge_size = 1
|
||||||
|
flare_charge_size = 2
|
||||||
|
category = "Interceptor" # {78EFB7A2-FD52-4b57-A6A6-3BF0E1D6555F}
|
||||||
|
radio_frequency = 127.5
|
||||||
|
|
||||||
|
livery_name = "VSN_SEM" # from type
|
||||||
|
|
||||||
|
class Pylon1:
|
||||||
|
Smokewinder___red = (1, Weapons.Smokewinder___red)
|
||||||
|
Smokewinder___green = (1, Weapons.Smokewinder___green)
|
||||||
|
Smokewinder___blue = (1, Weapons.Smokewinder___blue)
|
||||||
|
Smokewinder___white = (1, Weapons.Smokewinder___white)
|
||||||
|
Smokewinder___yellow = (1, Weapons.Smokewinder___yellow)
|
||||||
|
AN_ASQ_T50_TCTS_Pod___ACMI_Pod = (1, Weapons.AN_ASQ_T50_TCTS_Pod___ACMI_Pod)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (1, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (1, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
R550_Magic_2_IR_AAM = (1, Weapons.R550_Magic_2_IR_AAM)
|
||||||
|
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
1,
|
||||||
|
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
ALQ_131___ECM_Pod = (1, Weapons.ALQ_131___ECM_Pod)
|
||||||
|
AIM_9M_Sidewinder_IR_AAM = (1, Weapons.AIM_9M_Sidewinder_IR_AAM)
|
||||||
|
AIM_9P_Sidewinder_IR_AAM = (1, Weapons.AIM_9P_Sidewinder_IR_AAM)
|
||||||
|
|
||||||
|
class Pylon2:
|
||||||
|
AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
|
||||||
|
2,
|
||||||
|
Weapons.AGM_65K___Maverick_K__CCD_Imp_ASM_,
|
||||||
|
)
|
||||||
|
GBU_12___500lb_Laser_Guided_Bomb = (2, Weapons.GBU_12___500lb_Laser_Guided_Bomb)
|
||||||
|
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
2,
|
||||||
|
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (2, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (2, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Fuel_Tank_1100_Liter = (2, WeaponsSEM.Fuel_Tank_1100_Liter)
|
||||||
|
Fuel_Tank_625_Liter = (2, WeaponsSEM.Fuel_Tank_625_Liter)
|
||||||
|
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
|
||||||
|
class Pylon5:
|
||||||
|
L_081_Fantasmagoria_ELINT_pod = (5, Weapons.L_081_Fantasmagoria_ELINT_pod)
|
||||||
|
|
||||||
|
class Pylon6:
|
||||||
|
MER2_with_2_x_Mk_82___500lb_GP_Bombs_LD = (
|
||||||
|
6,
|
||||||
|
Weapons.MER2_with_2_x_Mk_82___500lb_GP_Bombs_LD,
|
||||||
|
)
|
||||||
|
AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
|
||||||
|
6,
|
||||||
|
Weapons.AGM_65K___Maverick_K__CCD_Imp_ASM_,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ERRR <CLEAN>
|
||||||
|
|
||||||
|
class Pylon10:
|
||||||
|
AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
|
||||||
|
10,
|
||||||
|
Weapons.AGM_65K___Maverick_K__CCD_Imp_ASM_,
|
||||||
|
)
|
||||||
|
GBU_12___500lb_Laser_Guided_Bomb = (
|
||||||
|
10,
|
||||||
|
Weapons.GBU_12___500lb_Laser_Guided_Bomb,
|
||||||
|
)
|
||||||
|
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
10,
|
||||||
|
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (10, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (10, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
Fuel_Tank_1100_Liter = (10, WeaponsSEM.Fuel_Tank_1100_Liter)
|
||||||
|
Fuel_Tank_625_Liter = (10, WeaponsSEM.Fuel_Tank_625_Liter)
|
||||||
|
|
||||||
|
class Pylon11:
|
||||||
|
Smokewinder___red = (11, Weapons.Smokewinder___red)
|
||||||
|
Smokewinder___green = (11, Weapons.Smokewinder___green)
|
||||||
|
Smokewinder___blue = (11, Weapons.Smokewinder___blue)
|
||||||
|
Smokewinder___white = (11, Weapons.Smokewinder___white)
|
||||||
|
Smokewinder___yellow = (11, Weapons.Smokewinder___yellow)
|
||||||
|
AN_ASQ_T50_TCTS_Pod___ACMI_Pod = (11, Weapons.AN_ASQ_T50_TCTS_Pod___ACMI_Pod)
|
||||||
|
Mk_81___250lb_GP_Bomb_LD = (11, Weapons.Mk_81___250lb_GP_Bomb_LD)
|
||||||
|
Mk_82___500lb_GP_Bomb_LD = (11, Weapons.Mk_82___500lb_GP_Bomb_LD)
|
||||||
|
R550_Magic_2_IR_AAM = (11, Weapons.R550_Magic_2_IR_AAM)
|
||||||
|
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||||
|
11,
|
||||||
|
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||||
|
)
|
||||||
|
ALQ_131___ECM_Pod = (11, Weapons.ALQ_131___ECM_Pod)
|
||||||
|
AIM_9M_Sidewinder_IR_AAM = (11, Weapons.AIM_9M_Sidewinder_IR_AAM)
|
||||||
|
AIM_9P_Sidewinder_IR_AAM = (11, Weapons.AIM_9P_Sidewinder_IR_AAM)
|
||||||
|
|
||||||
|
pylons = {1, 2, 3, 5, 6, 9, 10, 11}
|
||||||
|
|
||||||
|
tasks = [
|
||||||
|
task.CAP,
|
||||||
|
task.Escort,
|
||||||
|
task.FighterSweep,
|
||||||
|
task.Intercept,
|
||||||
|
task.Reconnaissance,
|
||||||
|
task.GroundAttack,
|
||||||
|
task.CAS,
|
||||||
|
task.AFAC,
|
||||||
|
task.RunwayAttack,
|
||||||
|
]
|
||||||
|
task_default = task.FighterSweep
|
||||||
@ -137,7 +137,8 @@ def run_ui(game: Optional[Game], ui_flags: UiFlags) -> None:
|
|||||||
# Replace DCS Mission scripting file to allow DCS Retribution to work
|
# Replace DCS Mission scripting file to allow DCS Retribution to work
|
||||||
try:
|
try:
|
||||||
liberation_install.replace_mission_scripting_file()
|
liberation_install.replace_mission_scripting_file()
|
||||||
except:
|
except Exception as e:
|
||||||
|
logging.error(e)
|
||||||
error_dialog = QtWidgets.QErrorMessage()
|
error_dialog = QtWidgets.QErrorMessage()
|
||||||
error_dialog.setWindowTitle("Wrong DCS installation directory.")
|
error_dialog.setWindowTitle("Wrong DCS installation directory.")
|
||||||
error_dialog.showMessage(
|
error_dialog.showMessage(
|
||||||
@ -317,6 +318,7 @@ def create_game(
|
|||||||
no_player_navy=False,
|
no_player_navy=False,
|
||||||
no_enemy_navy=False,
|
no_enemy_navy=False,
|
||||||
tgo_config=campaign.load_ground_forces_config(),
|
tgo_config=campaign.load_ground_forces_config(),
|
||||||
|
carrier_config=campaign.load_carrier_config(),
|
||||||
),
|
),
|
||||||
ModSettings(
|
ModSettings(
|
||||||
a4_skyhawk=False,
|
a4_skyhawk=False,
|
||||||
@ -325,6 +327,7 @@ def create_game(
|
|||||||
fa_18efg=False,
|
fa_18efg=False,
|
||||||
fa18ef_tanker=False,
|
fa18ef_tanker=False,
|
||||||
f4bc_phantom=False,
|
f4bc_phantom=False,
|
||||||
|
f9f_panther=False,
|
||||||
f22_raptor=False,
|
f22_raptor=False,
|
||||||
f84g_thunderjet=False,
|
f84g_thunderjet=False,
|
||||||
f100_supersabre=False,
|
f100_supersabre=False,
|
||||||
|
|||||||
@ -141,6 +141,7 @@ class QFlightList(QListView):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
self.package_model.add_flight(clone)
|
self.package_model.add_flight(clone)
|
||||||
|
EventStream.put_nowait(GameUpdateEvents().new_flight(clone))
|
||||||
|
|
||||||
def cancel_or_abort_flight(self, index: QModelIndex) -> None:
|
def cancel_or_abort_flight(self, index: QModelIndex) -> None:
|
||||||
self.package_model.cancel_or_abort_flight_at_index(index)
|
self.package_model.cancel_or_abort_flight_at_index(index)
|
||||||
@ -338,6 +339,10 @@ class QPackageList(QListView):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
self.ato_model.add_package(clone)
|
self.ato_model.add_package(clone)
|
||||||
|
events = GameUpdateEvents()
|
||||||
|
for f in clone.flights:
|
||||||
|
events.new_flight(f)
|
||||||
|
EventStream.put_nowait(events)
|
||||||
|
|
||||||
def delete_package(self, index: QModelIndex) -> None:
|
def delete_package(self, index: QModelIndex) -> None:
|
||||||
self.ato_model.cancel_or_abort_package_at_index(index)
|
self.ato_model.cancel_or_abort_package_at_index(index)
|
||||||
|
|||||||
@ -97,6 +97,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
|
|||||||
wpt.description = f"Friendly unit: {target.name}"
|
wpt.description = f"Friendly unit: {target.name}"
|
||||||
else:
|
else:
|
||||||
wpt.description = f"Enemy unit: {target.name}"
|
wpt.description = f"Enemy unit: {target.name}"
|
||||||
|
wpt.waypoint_type = FlightWaypointType.TARGET_POINT
|
||||||
i = add_model_item(i, model, wpt.pretty_name, wpt)
|
i = add_model_item(i, model, wpt.pretty_name, wpt)
|
||||||
|
|
||||||
if self.include_airbases:
|
if self.include_airbases:
|
||||||
|
|||||||
@ -109,7 +109,7 @@ class QLiberationWindow(QMainWindow):
|
|||||||
if last_save_file:
|
if last_save_file:
|
||||||
logging.info("Loading last saved game : " + str(last_save_file))
|
logging.info("Loading last saved game : " + str(last_save_file))
|
||||||
game = persistency.load_game(last_save_file)
|
game = persistency.load_game(last_save_file)
|
||||||
self.migrate_game(game, last_save_file)
|
game = self.migrate_game(game, last_save_file)
|
||||||
self.onGameGenerated(game)
|
self.onGameGenerated(game)
|
||||||
self.updateWindowTitle(last_save_file if game else None)
|
self.updateWindowTitle(last_save_file if game else None)
|
||||||
else:
|
else:
|
||||||
@ -342,7 +342,7 @@ class QLiberationWindow(QMainWindow):
|
|||||||
)
|
)
|
||||||
if file is not None and file[0] != "":
|
if file is not None and file[0] != "":
|
||||||
game = persistency.load_game(file[0])
|
game = persistency.load_game(file[0])
|
||||||
self.migrate_game(game, file[0])
|
game = self.migrate_game(game, file[0])
|
||||||
GameUpdateSignal.get_instance().game_loaded.emit(game)
|
GameUpdateSignal.get_instance().game_loaded.emit(game)
|
||||||
|
|
||||||
self.updateWindowTitle(file[0])
|
self.updateWindowTitle(file[0])
|
||||||
@ -350,15 +350,23 @@ class QLiberationWindow(QMainWindow):
|
|||||||
def migrate_game(self, game, path):
|
def migrate_game(self, game, path):
|
||||||
if game:
|
if game:
|
||||||
is_liberation = ".liberation" in path
|
is_liberation = ".liberation" in path
|
||||||
Migrator(game, is_liberation)
|
try:
|
||||||
|
Migrator(game, is_liberation)
|
||||||
|
return game
|
||||||
|
except Exception:
|
||||||
|
self.incompatible_save_popup(path)
|
||||||
else:
|
else:
|
||||||
relative_path = Path(path)
|
self.incompatible_save_popup(path)
|
||||||
QMessageBox.critical(
|
return None
|
||||||
self,
|
|
||||||
"Incompatible save",
|
def incompatible_save_popup(self, path):
|
||||||
"Incompatible save file detected, please report the issue on GitHub or Discord.\n"
|
relative_path = Path(path)
|
||||||
f"Make sure to include the campaign that fails to load, i.e.:\n\n{relative_path}",
|
QMessageBox.critical(
|
||||||
)
|
self,
|
||||||
|
"Incompatible save",
|
||||||
|
"Incompatible save file detected, please report the issue on GitHub or Discord.\n"
|
||||||
|
f"Make sure to include the campaign that fails to load, i.e.:\n\n{relative_path}",
|
||||||
|
)
|
||||||
|
|
||||||
def saveGame(self):
|
def saveGame(self):
|
||||||
logging.info("Saving game")
|
logging.info("Saving game")
|
||||||
|
|||||||
@ -239,5 +239,7 @@ class QWaitingForMissionResultWindow(QDialog):
|
|||||||
self.sim_controller.set_game(self.game)
|
self.sim_controller.set_game(self.game)
|
||||||
for _, f in self.game.db.flights.objects.items():
|
for _, f in self.game.db.flights.objects.items():
|
||||||
f.state.reinitialize(self.game.conditions.start_time)
|
f.state.reinitialize(self.game.conditions.start_time)
|
||||||
|
for cp in self.game.theater.controlpoints:
|
||||||
|
cp.release_parking_slots()
|
||||||
GameUpdateSignal.get_instance().updateGame(self.game)
|
GameUpdateSignal.get_instance().updateGame(self.game)
|
||||||
self.close()
|
self.close()
|
||||||
|
|||||||
@ -32,6 +32,7 @@ from game.theater.theatergroundobject import (
|
|||||||
EwrGroundObject,
|
EwrGroundObject,
|
||||||
SamGroundObject,
|
SamGroundObject,
|
||||||
VehicleGroupGroundObject,
|
VehicleGroupGroundObject,
|
||||||
|
ShipGroundObject,
|
||||||
)
|
)
|
||||||
from qt_ui.uiconstants import EVENT_ICONS
|
from qt_ui.uiconstants import EVENT_ICONS
|
||||||
|
|
||||||
@ -188,7 +189,8 @@ class QGroundObjectTemplateLayout(QGroupBox):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def affordable(self) -> bool:
|
def affordable(self) -> bool:
|
||||||
return self.cost <= self.game.blue.budget
|
coalition = self.ground_object.coalition
|
||||||
|
return self.cost <= coalition.budget or self.game.turn == 0
|
||||||
|
|
||||||
def add_theater_group(
|
def add_theater_group(
|
||||||
self, group_name: str, force_group: ForceGroup, groups: list[TgoLayoutUnitGroup]
|
self, group_name: str, force_group: ForceGroup, groups: list[TgoLayoutUnitGroup]
|
||||||
@ -226,7 +228,8 @@ class QGroundObjectTemplateLayout(QGroupBox):
|
|||||||
self.game.theater.heading_to_conflict_from(self.ground_object.position)
|
self.game.theater.heading_to_conflict_from(self.ground_object.position)
|
||||||
or self.ground_object.heading
|
or self.ground_object.heading
|
||||||
)
|
)
|
||||||
self.game.blue.budget -= self.cost
|
coalition = self.ground_object.coalition
|
||||||
|
coalition.budget -= self.cost if self.game.turn else 0
|
||||||
self.ground_object.groups = []
|
self.ground_object.groups = []
|
||||||
for group_name, groups in self.layout_model.groups.items():
|
for group_name, groups in self.layout_model.groups.items():
|
||||||
for group in groups:
|
for group in groups:
|
||||||
@ -276,13 +279,17 @@ class QGroundObjectBuyMenu(QDialog):
|
|||||||
elif isinstance(ground_object, EwrGroundObject):
|
elif isinstance(ground_object, EwrGroundObject):
|
||||||
role = GroupRole.AIR_DEFENSE
|
role = GroupRole.AIR_DEFENSE
|
||||||
tasks.append(GroupTask.EARLY_WARNING_RADAR)
|
tasks.append(GroupTask.EARLY_WARNING_RADAR)
|
||||||
|
elif isinstance(ground_object, ShipGroundObject):
|
||||||
|
role = GroupRole.NAVAL
|
||||||
|
tasks.append(GroupTask.NAVY)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError(f"Unhandled TGO type {ground_object.__class__}")
|
raise NotImplementedError(f"Unhandled TGO type {ground_object.__class__}")
|
||||||
|
|
||||||
if not tasks:
|
if not tasks:
|
||||||
tasks = role.tasks
|
tasks = role.tasks
|
||||||
|
|
||||||
for group in game.blue.armed_forces.groups_for_tasks(tasks):
|
coalition = ground_object.coalition
|
||||||
|
for group in coalition.armed_forces.groups_for_tasks(tasks):
|
||||||
self.force_group_selector.addItem(group.name, userData=group)
|
self.force_group_selector.addItem(group.name, userData=group)
|
||||||
self.force_group_selector.setEnabled(self.force_group_selector.count() > 1)
|
self.force_group_selector.setEnabled(self.force_group_selector.count() > 1)
|
||||||
self.force_group_selector.adjustSize()
|
self.force_group_selector.adjustSize()
|
||||||
|
|||||||
@ -104,15 +104,23 @@ class QGroundObjectMenu(QDialog):
|
|||||||
self.buy_replace.clicked.connect(self.buy_group)
|
self.buy_replace.clicked.connect(self.buy_group)
|
||||||
self.buy_replace.setProperty("style", "btn-success")
|
self.buy_replace.setProperty("style", "btn-success")
|
||||||
|
|
||||||
if self.ground_object.purchasable:
|
if self.ground_object.purchasable or self.game.turn == 0:
|
||||||
if self.total_value > 0:
|
if self.total_value > 0:
|
||||||
self.actionLayout.addWidget(self.sell_all_button)
|
self.actionLayout.addWidget(self.sell_all_button)
|
||||||
self.actionLayout.addWidget(self.buy_replace)
|
self.actionLayout.addWidget(self.buy_replace)
|
||||||
|
|
||||||
if self.cp.captured and self.ground_object.purchasable:
|
if self.show_buy_sell_actions and (
|
||||||
|
self.ground_object.purchasable or self.game.turn == 0
|
||||||
|
):
|
||||||
self.mainLayout.addLayout(self.actionLayout)
|
self.mainLayout.addLayout(self.actionLayout)
|
||||||
self.setLayout(self.mainLayout)
|
self.setLayout(self.mainLayout)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_buy_sell_actions(self) -> bool:
|
||||||
|
buysell_allowed = self.game.settings.enable_enemy_buy_sell
|
||||||
|
buysell_allowed |= self.cp.captured
|
||||||
|
return buysell_allowed
|
||||||
|
|
||||||
def doLayout(self):
|
def doLayout(self):
|
||||||
self.update_total_value()
|
self.update_total_value()
|
||||||
self.intelBox = QGroupBox("Units :")
|
self.intelBox = QGroupBox("Units :")
|
||||||
@ -240,7 +248,7 @@ class QGroundObjectMenu(QDialog):
|
|||||||
self.actionLayout.addWidget(self.sell_all_button)
|
self.actionLayout.addWidget(self.sell_all_button)
|
||||||
self.actionLayout.addWidget(self.buy_replace)
|
self.actionLayout.addWidget(self.buy_replace)
|
||||||
|
|
||||||
if self.cp.captured and self.ground_object.purchasable:
|
if self.show_buy_sell_actions and self.ground_object.purchasable:
|
||||||
self.mainLayout.addLayout(self.actionLayout)
|
self.mainLayout.addLayout(self.actionLayout)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
@ -276,7 +284,8 @@ class QGroundObjectMenu(QDialog):
|
|||||||
|
|
||||||
def sell_all(self):
|
def sell_all(self):
|
||||||
self.update_total_value()
|
self.update_total_value()
|
||||||
self.game.blue.budget += self.total_value
|
coalition = self.ground_object.coalition
|
||||||
|
coalition.budget += self.total_value
|
||||||
self.ground_object.groups = []
|
self.ground_object.groups = []
|
||||||
self.update_game()
|
self.update_game()
|
||||||
|
|
||||||
@ -296,7 +305,10 @@ class QGroundObjectMenu(QDialog):
|
|||||||
for package in self.game.ato_for(player=False).packages
|
for package in self.game.ato_for(player=False).packages
|
||||||
):
|
):
|
||||||
# Replan if the tgo was a target of the redfor
|
# Replan if the tgo was a target of the redfor
|
||||||
self.game.initialize_turn(events, for_red=True, for_blue=False)
|
coalition = self.ground_object.coalition
|
||||||
|
self.game.initialize_turn(
|
||||||
|
events, for_red=coalition.player, for_blue=not coalition.player
|
||||||
|
)
|
||||||
EventStream.put_nowait(events)
|
EventStream.put_nowait(events)
|
||||||
GameUpdateSignal.get_instance().updateGame(self.game)
|
GameUpdateSignal.get_instance().updateGame(self.game)
|
||||||
# Refresh the dialog
|
# Refresh the dialog
|
||||||
|
|||||||
@ -223,7 +223,7 @@ class QAutoCreateDialog(QDialog):
|
|||||||
self.game.settings,
|
self.game.settings,
|
||||||
)
|
)
|
||||||
now = self.package_model.game_model.sim_controller.current_time_in_sim
|
now = self.package_model.game_model.sim_controller.current_time_in_sim
|
||||||
package = pff.plan_mission(pm, 1, now, tracer)
|
package = pff.plan_mission(pm, 1, now, tracer, ignore_range=True)
|
||||||
if package:
|
if package:
|
||||||
package.set_tot_asap(now)
|
package.set_tot_asap(now)
|
||||||
self.package = package
|
self.package = package
|
||||||
|
|||||||
@ -17,6 +17,7 @@ from game.ato.flight import Flight
|
|||||||
from game.ato.flightmember import FlightMember
|
from game.ato.flightmember import FlightMember
|
||||||
from game.ato.loadouts import Loadout
|
from game.ato.loadouts import Loadout
|
||||||
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
||||||
|
from qt_ui.widgets.combos.QSquadronLiverySelector import SquadronLiverySelector
|
||||||
from .QLoadoutEditor import QLoadoutEditor
|
from .QLoadoutEditor import QLoadoutEditor
|
||||||
from .ownlasercodeinfo import OwnLaserCodeInfo
|
from .ownlasercodeinfo import OwnLaserCodeInfo
|
||||||
from .propertyeditor import PropertyEditor
|
from .propertyeditor import PropertyEditor
|
||||||
@ -141,6 +142,24 @@ class QFlightPayloadTab(QFrame):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hbox = QHBoxLayout()
|
||||||
|
self.same_livery_for_all_checkbox = QCheckBox(
|
||||||
|
"Use same livery for all flight members"
|
||||||
|
)
|
||||||
|
self.same_livery_for_all_checkbox.setChecked(
|
||||||
|
self.flight.use_same_livery_for_all_members
|
||||||
|
)
|
||||||
|
self.same_livery_for_all_checkbox.toggled.connect(self.on_same_livery_toggled)
|
||||||
|
hbox.addWidget(self.same_livery_for_all_checkbox)
|
||||||
|
self.livery_selector = SquadronLiverySelector(self.flight.squadron)
|
||||||
|
self.livery_selector.insertItem(0, "Default", None)
|
||||||
|
self.livery_selector.setCurrentIndex(
|
||||||
|
self.livery_selector.findData(self.member_selector.selected_member.livery)
|
||||||
|
)
|
||||||
|
self.livery_selector.currentIndexChanged.connect(self.on_livery_change)
|
||||||
|
hbox.addWidget(self.livery_selector)
|
||||||
|
layout.addLayout(hbox)
|
||||||
|
|
||||||
scroll_content = QWidget()
|
scroll_content = QWidget()
|
||||||
scrolling_layout = QVBoxLayout()
|
scrolling_layout = QVBoxLayout()
|
||||||
scroll_content.setLayout(scrolling_layout)
|
scroll_content.setLayout(scrolling_layout)
|
||||||
@ -212,6 +231,9 @@ class QFlightPayloadTab(QFrame):
|
|||||||
self.property_editor.set_flight_member(member)
|
self.property_editor.set_flight_member(member)
|
||||||
self.loadout_selector.setCurrentText(member.loadout.name)
|
self.loadout_selector.setCurrentText(member.loadout.name)
|
||||||
self.loadout_selector.setDisabled(member.loadout.is_custom)
|
self.loadout_selector.setDisabled(member.loadout.is_custom)
|
||||||
|
self.livery_selector.setCurrentIndex(
|
||||||
|
self.livery_selector.findData(member.livery)
|
||||||
|
)
|
||||||
self.payload_editor.set_flight_member(member)
|
self.payload_editor.set_flight_member(member)
|
||||||
self.weapon_laser_code_selector.set_flight_member(member)
|
self.weapon_laser_code_selector.set_flight_member(member)
|
||||||
self.own_laser_code_info.set_flight_member(member)
|
self.own_laser_code_info.set_flight_member(member)
|
||||||
@ -222,9 +244,13 @@ class QFlightPayloadTab(QFrame):
|
|||||||
self.payload_editor.setDisabled(
|
self.payload_editor.setDisabled(
|
||||||
self.flight.use_same_loadout_for_all_members
|
self.flight.use_same_loadout_for_all_members
|
||||||
)
|
)
|
||||||
|
self.livery_selector.setDisabled(
|
||||||
|
self.flight.use_same_livery_for_all_members
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.loadout_selector.setEnabled(True)
|
self.loadout_selector.setEnabled(True)
|
||||||
self.payload_editor.setEnabled(True)
|
self.payload_editor.setEnabled(True)
|
||||||
|
self.livery_selector.setEnabled(True)
|
||||||
|
|
||||||
def loadout_at(self, index: int) -> Loadout:
|
def loadout_at(self, index: int) -> Loadout:
|
||||||
loadout = self.loadout_selector.itemData(index)
|
loadout = self.loadout_selector.itemData(index)
|
||||||
@ -273,3 +299,20 @@ class QFlightPayloadTab(QFrame):
|
|||||||
self.rebind_to_selected_member()
|
self.rebind_to_selected_member()
|
||||||
else:
|
else:
|
||||||
self.flight.roster.use_distinct_loadouts_for_each_member()
|
self.flight.roster.use_distinct_loadouts_for_each_member()
|
||||||
|
|
||||||
|
def on_same_livery_toggled(self, checked: bool) -> None:
|
||||||
|
self.flight.use_same_livery_for_all_members = checked
|
||||||
|
if self.member_selector.value():
|
||||||
|
self.livery_selector.setDisabled(checked)
|
||||||
|
if checked:
|
||||||
|
self.flight.roster.use_same_livery_for_all_members()
|
||||||
|
if self.member_selector.value():
|
||||||
|
self.rebind_to_selected_member()
|
||||||
|
|
||||||
|
def on_livery_change(self) -> None:
|
||||||
|
livery = self.livery_selector.currentData()
|
||||||
|
if self.flight.use_same_livery_for_all_members:
|
||||||
|
for m in self.flight.roster.members:
|
||||||
|
m.livery = livery
|
||||||
|
else:
|
||||||
|
self.member_selector.selected_member.livery = livery
|
||||||
|
|||||||
@ -86,7 +86,7 @@ class QFlightWaypointList(QTableView):
|
|||||||
|
|
||||||
self.model.setItem(row, 0, QWaypointItem(waypoint, row))
|
self.model.setItem(row, 0, QWaypointItem(waypoint, row))
|
||||||
|
|
||||||
altitude = int(waypoint.alt.feet)
|
altitude = round(waypoint.alt.feet)
|
||||||
altitude_item = QStandardItem(f"{altitude}")
|
altitude_item = QStandardItem(f"{altitude}")
|
||||||
altitude_item.setEditable(True)
|
altitude_item.setEditable(True)
|
||||||
self.model.setItem(row, 1, altitude_item)
|
self.model.setItem(row, 1, altitude_item)
|
||||||
|
|||||||
@ -162,9 +162,9 @@ class QFlightWaypointTab(QFrame):
|
|||||||
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
|
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
|
||||||
self.flight.flight_plan.layout.custom_waypoints.remove(waypoint)
|
self.flight.flight_plan.layout.custom_waypoints.remove(waypoint)
|
||||||
|
|
||||||
def confirm_degrade(self) -> bool:
|
def confirm_degrade(self, parent: Optional[QWidget] = None) -> bool:
|
||||||
result = QMessageBox.warning(
|
result = QMessageBox.warning(
|
||||||
self,
|
parent if parent else self,
|
||||||
"Degrade flight-plan?",
|
"Degrade flight-plan?",
|
||||||
"Deleting the selected waypoint(s) will require degradation to a custom flight-plan. "
|
"Deleting the selected waypoint(s) will require degradation to a custom flight-plan. "
|
||||||
"A custom flight-plan will no longer respect the TOTs of the package.<br><br>"
|
"A custom flight-plan will no longer respect the TOTs of the package.<br><br>"
|
||||||
@ -184,12 +184,6 @@ class QFlightWaypointTab(QFrame):
|
|||||||
def on_waypoints_added(self, waypoints: Iterable[FlightWaypoint]) -> None:
|
def on_waypoints_added(self, waypoints: Iterable[FlightWaypoint]) -> None:
|
||||||
if not waypoints:
|
if not waypoints:
|
||||||
return
|
return
|
||||||
if not self.flight.flight_plan.is_custom:
|
|
||||||
confirmed = self.confirm_degrade()
|
|
||||||
if not confirmed:
|
|
||||||
return
|
|
||||||
self.degrade_to_custom_flight_plan()
|
|
||||||
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
|
|
||||||
self.flight.flight_plan.layout.custom_waypoints.extend(waypoints)
|
self.flight.flight_plan.layout.custom_waypoints.extend(waypoints)
|
||||||
self.add_rows(len(list(waypoints)))
|
self.add_rows(len(list(waypoints)))
|
||||||
|
|
||||||
|
|||||||
@ -84,9 +84,11 @@ class NewGameWizard(QtWidgets.QWizard):
|
|||||||
no_player_navy=self.field("no_player_navy"),
|
no_player_navy=self.field("no_player_navy"),
|
||||||
no_enemy_navy=self.field("no_enemy_navy"),
|
no_enemy_navy=self.field("no_enemy_navy"),
|
||||||
tgo_config=campaign.load_ground_forces_config(),
|
tgo_config=campaign.load_ground_forces_config(),
|
||||||
|
carrier_config=campaign.load_carrier_config(),
|
||||||
squadrons_start_full=self.field("squadrons_start_full"),
|
squadrons_start_full=self.field("squadrons_start_full"),
|
||||||
)
|
)
|
||||||
mod_settings = ModSettings(
|
mod_settings = ModSettings(
|
||||||
|
f9f_panther=self.field("f9f_panther"),
|
||||||
a4_skyhawk=self.field("a4_skyhawk"),
|
a4_skyhawk=self.field("a4_skyhawk"),
|
||||||
a6a_intruder=self.field("a6a_intruder"),
|
a6a_intruder=self.field("a6a_intruder"),
|
||||||
a7e_corsair2=self.field("a7e_corsair2"),
|
a7e_corsair2=self.field("a7e_corsair2"),
|
||||||
@ -105,6 +107,7 @@ class NewGameWizard(QtWidgets.QWizard):
|
|||||||
irondome=self.field("irondome"),
|
irondome=self.field("irondome"),
|
||||||
uh_60l=self.field("uh_60l"),
|
uh_60l=self.field("uh_60l"),
|
||||||
jas39_gripen=self.field("jas39_gripen"),
|
jas39_gripen=self.field("jas39_gripen"),
|
||||||
|
super_etendard=self.field("super_etendard"),
|
||||||
su30_flanker_h=self.field("su30_flanker_h"),
|
su30_flanker_h=self.field("su30_flanker_h"),
|
||||||
su57_felon=self.field("su57_felon"),
|
su57_felon=self.field("su57_felon"),
|
||||||
ov10a_bronco=self.field("ov10a_bronco"),
|
ov10a_bronco=self.field("ov10a_bronco"),
|
||||||
|
|||||||
@ -100,6 +100,8 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
|||||||
self.registerField("uh_60l", self.uh_60l)
|
self.registerField("uh_60l", self.uh_60l)
|
||||||
self.f4bc_phantom = QtWidgets.QCheckBox()
|
self.f4bc_phantom = QtWidgets.QCheckBox()
|
||||||
self.registerField("f4bc_phantom", self.f4bc_phantom)
|
self.registerField("f4bc_phantom", self.f4bc_phantom)
|
||||||
|
self.f9f_panther = QtWidgets.QCheckBox()
|
||||||
|
self.registerField("f9f_panther", self.f9f_panther)
|
||||||
self.f15d_baz = QtWidgets.QCheckBox()
|
self.f15d_baz = QtWidgets.QCheckBox()
|
||||||
self.registerField("f15d_baz", self.f15d_baz)
|
self.registerField("f15d_baz", self.f15d_baz)
|
||||||
self.f_15_idf = QtWidgets.QCheckBox()
|
self.f_15_idf = QtWidgets.QCheckBox()
|
||||||
@ -122,6 +124,8 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
|||||||
self.registerField("f105_thunderchief", self.f105_thunderchief)
|
self.registerField("f105_thunderchief", self.f105_thunderchief)
|
||||||
self.jas39_gripen = QtWidgets.QCheckBox()
|
self.jas39_gripen = QtWidgets.QCheckBox()
|
||||||
self.registerField("jas39_gripen", self.jas39_gripen)
|
self.registerField("jas39_gripen", self.jas39_gripen)
|
||||||
|
self.super_etendard = QtWidgets.QCheckBox()
|
||||||
|
self.registerField("super_etendard", self.super_etendard)
|
||||||
self.su30_flanker_h = QtWidgets.QCheckBox()
|
self.su30_flanker_h = QtWidgets.QCheckBox()
|
||||||
self.registerField("su30_flanker_h", self.su30_flanker_h)
|
self.registerField("su30_flanker_h", self.su30_flanker_h)
|
||||||
self.su57_felon = QtWidgets.QCheckBox()
|
self.su57_felon = QtWidgets.QCheckBox()
|
||||||
@ -150,6 +154,7 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
|||||||
modLayout_row = 1
|
modLayout_row = 1
|
||||||
|
|
||||||
mod_pairs = [
|
mod_pairs = [
|
||||||
|
("F9F Panther (v2.8.7.101)", self.f9f_panther),
|
||||||
("A-4E Skyhawk (v2.2.0)", self.a4_skyhawk),
|
("A-4E Skyhawk (v2.2.0)", self.a4_skyhawk),
|
||||||
("A-6A Intruder (v2.7.5.01)", self.a6a_intruder),
|
("A-6A Intruder (v2.7.5.01)", self.a6a_intruder),
|
||||||
("A-7E Corsair II", self.a7e_corsair2),
|
("A-7E Corsair II", self.a7e_corsair2),
|
||||||
@ -158,7 +163,7 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
|||||||
("F-15D Baz (v1.0)", self.f15d_baz),
|
("F-15D Baz (v1.0)", self.f15d_baz),
|
||||||
("F-15I Ra'am (v1.0 by IDF Mods Project)", self.f_15_idf),
|
("F-15I Ra'am (v1.0 by IDF Mods Project)", self.f_15_idf),
|
||||||
("F-16I Sufa & F-16D (v3.6 by IDF Mods Project)", self.f_16_idf),
|
("F-16I Sufa & F-16D (v3.6 by IDF Mods Project)", self.f_16_idf),
|
||||||
("F/A-18E/F/G Super Hornet (version 2.1)", self.fa_18efg),
|
("F/A-18E/F/G Super Hornet (version 2.2.5)", self.fa_18efg),
|
||||||
("F/A-18E/F Super Hornet AI Tanker (version 1.4)", self.fa18ef_tanker),
|
("F/A-18E/F Super Hornet AI Tanker (version 1.4)", self.fa18ef_tanker),
|
||||||
("F-22A Raptor", self.f22_raptor),
|
("F-22A Raptor", self.f22_raptor),
|
||||||
("F-84G Thunderjet (v2.5.7.01)", self.f84g_thunderjet),
|
("F-84G Thunderjet (v2.5.7.01)", self.f84g_thunderjet),
|
||||||
@ -170,6 +175,7 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
|||||||
("Swedish Military Assets pack (1.10)", self.swedishmilitaryassetspack),
|
("Swedish Military Assets pack (1.10)", self.swedishmilitaryassetspack),
|
||||||
("JAS 39 Gripen (v1.8.5-beta)", self.jas39_gripen),
|
("JAS 39 Gripen (v1.8.5-beta)", self.jas39_gripen),
|
||||||
("OV-10A Bronco", self.ov10a_bronco),
|
("OV-10A Bronco", self.ov10a_bronco),
|
||||||
|
("Super Étendard (v2.5.5)", self.super_etendard),
|
||||||
("Su-30 Flanker-H (V2.7.3 beta)", self.su30_flanker_h),
|
("Su-30 Flanker-H (V2.7.3 beta)", self.su30_flanker_h),
|
||||||
("Su-57 Felon (build-04)", self.su57_felon),
|
("Su-57 Felon (build-04)", self.su57_felon),
|
||||||
("UH-60L Black Hawk (v1.3.1)", self.uh_60l),
|
("UH-60L Black Hawk (v1.3.1)", self.uh_60l),
|
||||||
@ -212,6 +218,7 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
|||||||
self.no_enemy_navy.setChecked(s.get("no_enemy_navy", False))
|
self.no_enemy_navy.setChecked(s.get("no_enemy_navy", False))
|
||||||
self.squadrons_start_full.setChecked(s.get("squadron_start_full", False))
|
self.squadrons_start_full.setChecked(s.get("squadron_start_full", False))
|
||||||
|
|
||||||
|
self.f9f_panther.setChecked(s.get("f9f_panther", False))
|
||||||
self.a4_skyhawk.setChecked(s.get("a4_skyhawk", False))
|
self.a4_skyhawk.setChecked(s.get("a4_skyhawk", False))
|
||||||
self.a6a_intruder.setChecked(s.get("a6a_intruder", False))
|
self.a6a_intruder.setChecked(s.get("a6a_intruder", False))
|
||||||
self.a7e_corsair2.setChecked(s.get("a7e_corsair2", False))
|
self.a7e_corsair2.setChecked(s.get("a7e_corsair2", False))
|
||||||
@ -228,6 +235,7 @@ class GeneratorOptions(QtWidgets.QWizardPage):
|
|||||||
self.f104_starfighter.setChecked(s.get("f104_starfighter", False))
|
self.f104_starfighter.setChecked(s.get("f104_starfighter", False))
|
||||||
self.f105_thunderchief.setChecked(s.get("f105_thunderchief", False))
|
self.f105_thunderchief.setChecked(s.get("f105_thunderchief", False))
|
||||||
self.jas39_gripen.setChecked(s.get("jas39_gripen", False))
|
self.jas39_gripen.setChecked(s.get("jas39_gripen", False))
|
||||||
|
self.super_etendard.setChecked(s.get("super_etendard", False))
|
||||||
self.su30_flanker_h.setChecked(s.get("su30_flanker_h", False))
|
self.su30_flanker_h.setChecked(s.get("su30_flanker_h", False))
|
||||||
self.su57_felon.setChecked(s.get("su57_felon", False))
|
self.su57_felon.setChecked(s.get("su57_felon", False))
|
||||||
self.ov10a_bronco.setChecked(s.get("ov10a_bronco", False))
|
self.ov10a_bronco.setChecked(s.get("ov10a_bronco", False))
|
||||||
|
|||||||
@ -113,6 +113,15 @@ class CheatSettingsBox(QGroupBox):
|
|||||||
)
|
)
|
||||||
self.main_layout.addLayout(self.air_wing_cheat)
|
self.main_layout.addLayout(self.air_wing_cheat)
|
||||||
|
|
||||||
|
# Buy/Sell actions for OPFOR
|
||||||
|
self.opfor_buysell_checkbox = QCheckBox()
|
||||||
|
self.opfor_buysell_checkbox.setChecked(sc.settings.enable_enemy_buy_sell)
|
||||||
|
self.opfor_buysell_checkbox.toggled.connect(apply_settings)
|
||||||
|
self.redfor_buysell_cheat = QLabeledWidget(
|
||||||
|
"Enable OPFOR Buy/Sell actions Cheat:", self.opfor_buysell_checkbox
|
||||||
|
)
|
||||||
|
self.main_layout.addLayout(self.redfor_buysell_cheat)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def show_red_ato(self) -> bool:
|
def show_red_ato(self) -> bool:
|
||||||
return self.red_ato_checkbox.isChecked()
|
return self.red_ato_checkbox.isChecked()
|
||||||
@ -137,6 +146,10 @@ class CheatSettingsBox(QGroupBox):
|
|||||||
def enable_air_wing_cheats(self) -> bool:
|
def enable_air_wing_cheats(self) -> bool:
|
||||||
return self.air_wing_adjustments_checkbox.isChecked()
|
return self.air_wing_adjustments_checkbox.isChecked()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enable_redfor_buysell(self) -> bool:
|
||||||
|
return self.opfor_buysell_checkbox.isChecked()
|
||||||
|
|
||||||
|
|
||||||
class AutoSettingsLayout(QGridLayout):
|
class AutoSettingsLayout(QGridLayout):
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -495,6 +508,7 @@ class QSettingsWidget(QtWidgets.QWizardPage, SettingsContainer):
|
|||||||
self.settings.enable_air_wing_adjustments = (
|
self.settings.enable_air_wing_adjustments = (
|
||||||
self.cheat_options.enable_air_wing_cheats
|
self.cheat_options.enable_air_wing_cheats
|
||||||
)
|
)
|
||||||
|
self.settings.enable_enemy_buy_sell = self.cheat_options.enable_redfor_buysell
|
||||||
|
|
||||||
if self.game:
|
if self.game:
|
||||||
events = GameUpdateEvents()
|
events = GameUpdateEvents()
|
||||||
@ -521,6 +535,15 @@ class QSettingsWidget(QtWidgets.QWizardPage, SettingsContainer):
|
|||||||
self.cheat_options.transfer_cheat_checkbox.setChecked(
|
self.cheat_options.transfer_cheat_checkbox.setChecked(
|
||||||
self.settings.enable_transfer_cheat
|
self.settings.enable_transfer_cheat
|
||||||
)
|
)
|
||||||
|
self.cheat_options.base_runway_state_cheat_checkbox.setChecked(
|
||||||
|
self.settings.enable_runway_state_cheat
|
||||||
|
)
|
||||||
|
self.cheat_options.air_wing_adjustments_checkbox.setChecked(
|
||||||
|
self.settings.enable_air_wing_adjustments
|
||||||
|
)
|
||||||
|
self.cheat_options.opfor_buysell_checkbox.setChecked(
|
||||||
|
self.settings.enable_enemy_buy_sell
|
||||||
|
)
|
||||||
|
|
||||||
self.pluginsPage.update_from_settings()
|
self.pluginsPage.update_from_settings()
|
||||||
self.pluginsOptionsPage.update_from_settings()
|
self.pluginsOptionsPage.update_from_settings()
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
altgraph==0.17.4
|
altgraph==0.17.4
|
||||||
anyio==3.7.1
|
anyio==3.7.1
|
||||||
asgiref==3.7.2
|
asgiref==3.8.1
|
||||||
atomicwrites==1.4.1
|
atomicwrites==1.4.1
|
||||||
attrs==23.2.0
|
attrs==23.2.0
|
||||||
black==23.9.1
|
black==23.9.1
|
||||||
@ -9,61 +9,61 @@ cfgv==3.4.0
|
|||||||
click==8.1.7
|
click==8.1.7
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
distlib==0.3.8
|
distlib==0.3.8
|
||||||
Faker==22.6.0
|
Faker==24.14.0
|
||||||
fastapi==0.109.2
|
fastapi==0.110.2
|
||||||
filelock==3.13.1
|
filelock==3.13.4
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
httptools==0.6.1
|
httptools==0.6.1
|
||||||
identify==2.5.33
|
identify==2.5.36
|
||||||
idna==3.6
|
idna==3.7
|
||||||
iniconfig==2.0.0
|
iniconfig==2.0.0
|
||||||
Jinja2==3.1.3
|
Jinja2==3.1.3
|
||||||
lupa==2.0
|
lupa==2.1
|
||||||
MarkupSafe==2.1.5
|
MarkupSafe==2.1.5
|
||||||
mypy==1.8.0
|
mypy==1.10.0
|
||||||
mypy-extensions==1.0.0
|
mypy-extensions==1.0.0
|
||||||
nodeenv==1.8.0
|
nodeenv==1.8.0
|
||||||
packaging==23.2
|
packaging==24.0
|
||||||
pathspec==0.12.1
|
pathspec==0.12.1
|
||||||
pefile==2023.2.7
|
pefile==2023.2.7
|
||||||
Pillow==10.2.0
|
Pillow==10.3.0
|
||||||
platformdirs==4.2.0
|
platformdirs==4.2.1
|
||||||
pluggy==1.4.0
|
pluggy==1.5.0
|
||||||
pre-commit==3.6.0
|
pre-commit==3.7.0
|
||||||
pydantic==2.6.0
|
pydantic==2.7.1
|
||||||
pydantic-settings==2.1.0
|
pydantic-settings==2.2.1
|
||||||
-e git+https://github.com/dcs-retribution/pydcs@353f5b177dd406122a83e8572fd6ca54adf84389#egg=pydcs
|
pydcs @ git+https://github.com/dcs-retribution/pydcs@ab2f274c0882f13918ca6f4be3c6c8bdc71818e0
|
||||||
pyinstaller==5.13.2
|
pyinstaller==5.13.2
|
||||||
pyinstaller-hooks-contrib==2024.0
|
pyinstaller-hooks-contrib==2024.0
|
||||||
pyparsing==3.1.1
|
pyparsing==3.1.2
|
||||||
pyproj==3.6.1
|
pyproj==3.6.1
|
||||||
pyshp==2.3.1
|
pyshp==2.3.1
|
||||||
PySide6==6.4.3
|
PySide6==6.4.2
|
||||||
PySide6-Addons==6.4.3
|
PySide6-Addons==6.4.2
|
||||||
PySide6-Essentials==6.4.3
|
PySide6-Essentials==6.4.2
|
||||||
pytest==8.0.0
|
pytest==8.1.2
|
||||||
pytest-cov==4.1.0
|
pytest-cov==5.0.0
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.9.0.post0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
pywin32-ctypes==0.2.2
|
pywin32-ctypes==0.2.2
|
||||||
PyYAML==6.0.1
|
PyYAML==6.0.1
|
||||||
Shapely==2.0.2
|
Shapely==2.0.4
|
||||||
shiboken6==6.4.3
|
shiboken6==6.4.2
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
sniffio==1.3.0
|
sniffio==1.3.1
|
||||||
starlette==0.36.3
|
starlette==0.37.2
|
||||||
suntime==1.2.5
|
suntime==1.3.2
|
||||||
tabulate==0.9.0
|
tabulate==0.9.0
|
||||||
text-unidecode==1.3
|
text-unidecode==1.3
|
||||||
toml==0.10.2
|
toml==0.10.2
|
||||||
tomli==2.0.1
|
tomli==2.0.1
|
||||||
types-Jinja2==2.11.9
|
types-Jinja2==2.11.9
|
||||||
types-MarkupSafe==1.1.10
|
types-MarkupSafe==1.1.10
|
||||||
types-Pillow==10.2.0.20240125
|
types-Pillow==10.2.0.20240423
|
||||||
types-PyYAML==6.0.12.12
|
types-PyYAML==6.0.12.20240311
|
||||||
types-tabulate==0.9.0.20240106
|
types-tabulate==0.9.0.20240106
|
||||||
typing_extensions==4.9.0
|
typing_extensions==4.11.0
|
||||||
uvicorn==0.27.0.post1
|
uvicorn==0.29.0
|
||||||
virtualenv==20.25.0
|
virtualenv==20.26.0
|
||||||
watchgod==0.8.2
|
watchgod==0.8.2
|
||||||
websockets==12.0
|
websockets==12.0
|
||||||
|
|||||||
@ -38,12 +38,12 @@ squadrons:
|
|||||||
#Port Stanley
|
#Port Stanley
|
||||||
1:
|
1:
|
||||||
- primary: DEAD
|
- primary: DEAD
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- AH-64D Apache Longbow
|
- AH-64D Apache Longbow
|
||||||
size: 6
|
size: 6
|
||||||
- primary: BAI
|
- primary: BAI
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- AH-64D Apache Longbow
|
- AH-64D Apache Longbow
|
||||||
size: 6
|
size: 6
|
||||||
@ -56,14 +56,15 @@ squadrons:
|
|||||||
#San Carlos FOB
|
#San Carlos FOB
|
||||||
3:
|
3:
|
||||||
- primary: BAI
|
- primary: BAI
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- Mi-24P Hind-F
|
- Mi-24P Hind-F
|
||||||
size: 6
|
size: 6
|
||||||
#Goose Green
|
#Goose Green
|
||||||
24:
|
24:
|
||||||
- primary: DEAD
|
- primary: DEAD
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
|
- Ka-50 Hokum III
|
||||||
- Ka-50 Hokum (Blackshark 3)
|
- Ka-50 Hokum (Blackshark 3)
|
||||||
size: 6
|
size: 6
|
||||||
Binary file not shown.
@ -17,7 +17,7 @@ description:
|
|||||||
adversarial nation, dubbed Orangeland. This scenario is designed to
|
adversarial nation, dubbed Orangeland. This scenario is designed to
|
||||||
simulate a mock invasion against Cairo, and presents a valuable
|
simulate a mock invasion against Cairo, and presents a valuable
|
||||||
opportunity for participating nations to refine their joint operational
|
opportunity for participating nations to refine their joint operational
|
||||||
capabilities and improve logistical and tactical interoperability</p><p>
|
capabilities and improve logistical and tactical interoperability.</p><p>
|
||||||
A historic addition to Exercise Bright Star 2025 is the participation of
|
A historic addition to Exercise Bright Star 2025 is the participation of
|
||||||
Israel as an observer nation. This marks a significant milestone, given
|
Israel as an observer nation. This marks a significant milestone, given
|
||||||
the complex historical relations in the region, and symbolises a step
|
the complex historical relations in the region, and symbolises a step
|
||||||
@ -25,11 +25,17 @@ description:
|
|||||||
hosting the aggressor faction of the exercise coalition at its airfields,
|
hosting the aggressor faction of the exercise coalition at its airfields,
|
||||||
not only demonstrates the broadening scope of the exercise but also highlights
|
not only demonstrates the broadening scope of the exercise but also highlights
|
||||||
the value of fostering an environment of mutual cooperation and shared
|
the value of fostering an environment of mutual cooperation and shared
|
||||||
security objectives.</p>
|
security objectives.</p><p>
|
||||||
|
<strong>Note:</strong> There is no overland supply route between Melez and
|
||||||
|
Wadi al Jandali due to the simulated destruction of the Al Salam Bridge spanning
|
||||||
|
the Suez Canal. Consequently, ground units will have to be ferried across by
|
||||||
|
air during the exercise.
|
||||||
miz: exercise_bright_star.miz
|
miz: exercise_bright_star.miz
|
||||||
performance: 1
|
performance: 1
|
||||||
recommended_start_date: 2025-09-01
|
recommended_start_date: 2025-09-01
|
||||||
version: "10.7"
|
version: "10.7"
|
||||||
|
settings:
|
||||||
|
hercules: true
|
||||||
squadrons:
|
squadrons:
|
||||||
Blue CV-1:
|
Blue CV-1:
|
||||||
- primary: SEAD
|
- primary: SEAD
|
||||||
@ -45,7 +51,7 @@ squadrons:
|
|||||||
aircraft:
|
aircraft:
|
||||||
- S-3B Tanker
|
- S-3B Tanker
|
||||||
size: 4
|
size: 4
|
||||||
Bombers from RAF Fairford:
|
Bombers from Al Udeid Air Base:
|
||||||
- primary: Anti-ship
|
- primary: Anti-ship
|
||||||
secondary: air-to-ground
|
secondary: air-to-ground
|
||||||
aircraft:
|
aircraft:
|
||||||
@ -56,8 +62,13 @@ squadrons:
|
|||||||
aircraft:
|
aircraft:
|
||||||
- B-1B Lancer
|
- B-1B Lancer
|
||||||
size: 8
|
size: 8
|
||||||
# Hatzerim (141)
|
# Ovda
|
||||||
7:
|
10:
|
||||||
|
- primary: CAS
|
||||||
|
secondary: air-to-ground
|
||||||
|
aircraft:
|
||||||
|
- A-10C Thunderbolt II (Suite 7)
|
||||||
|
size: 8
|
||||||
- primary: Escort
|
- primary: Escort
|
||||||
secondary: any
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
@ -68,36 +79,47 @@ squadrons:
|
|||||||
aircraft:
|
aircraft:
|
||||||
- F-15E Strike Eagle (Suite 4+)
|
- F-15E Strike Eagle (Suite 4+)
|
||||||
size: 16
|
size: 16
|
||||||
|
- primary: BAI
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- JF-17 Thunder
|
||||||
|
size: 12
|
||||||
|
# Ramon Airbase
|
||||||
|
9:
|
||||||
- primary: DEAD
|
- primary: DEAD
|
||||||
secondary: any
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- F-16CM Fighting Falcon (Block 50)
|
- F-16CM Fighting Falcon (Block 50)
|
||||||
size: 20
|
size: 20
|
||||||
- primary: BAI
|
|
||||||
secondary: any
|
|
||||||
aircraft:
|
|
||||||
- JF-17 Thunder
|
|
||||||
size: 16
|
|
||||||
- primary: BARCAP
|
- primary: BARCAP
|
||||||
secondary: any
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- Mirage 2000C
|
- Mirage 2000C
|
||||||
size: 12
|
size: 12
|
||||||
# Kedem
|
|
||||||
12:
|
|
||||||
- primary: Transport
|
- primary: Transport
|
||||||
secondary: any
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- CH-47D
|
- CH-47D
|
||||||
size: 20
|
size: 12
|
||||||
|
- primary: BAI
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- AH-64D Apache Longbow
|
||||||
|
size: 8
|
||||||
- primary: Air Assault
|
- primary: Air Assault
|
||||||
secondary: any
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- UH-60L
|
- UH-60L
|
||||||
- UH-60A
|
- UH-60A
|
||||||
size: 4
|
size: 4
|
||||||
# Nevatim (106)
|
# Ben-Gurion
|
||||||
8:
|
24:
|
||||||
|
- primary: Transport
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- C-130J-30 Super Hercules
|
||||||
|
- C-130
|
||||||
|
size: 8
|
||||||
- primary: AEW&C
|
- primary: AEW&C
|
||||||
aircraft:
|
aircraft:
|
||||||
- E-3A
|
- E-3A
|
||||||
@ -106,16 +128,12 @@ squadrons:
|
|||||||
aircraft:
|
aircraft:
|
||||||
- KC-135 Stratotanker
|
- KC-135 Stratotanker
|
||||||
size: 2
|
size: 2
|
||||||
- primary: CAS
|
|
||||||
secondary: air-to-ground
|
|
||||||
aircraft:
|
|
||||||
- A-10C Thunderbolt II (Suite 7)
|
|
||||||
size: 8
|
|
||||||
# Melez (30)
|
# Melez (30)
|
||||||
5:
|
5:
|
||||||
- primary: CAS
|
- primary: CAS
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
|
- Ka-50 Hokum III
|
||||||
- Ka-50 Hokum (Blackshark 3)
|
- Ka-50 Hokum (Blackshark 3)
|
||||||
size: 4
|
size: 4
|
||||||
- primary: BAI
|
- primary: BAI
|
||||||
@ -140,7 +158,17 @@ squadrons:
|
|||||||
- F-4E Phantom II
|
- F-4E Phantom II
|
||||||
size: 20
|
size: 20
|
||||||
- primary: DEAD
|
- primary: DEAD
|
||||||
secondary: any
|
secondary:
|
||||||
|
- BAI
|
||||||
|
- BARCAP
|
||||||
|
- CAS
|
||||||
|
- Escort
|
||||||
|
- Fighter sweep
|
||||||
|
- Intercept
|
||||||
|
- OCA/Aircraft
|
||||||
|
- OCA/Runway
|
||||||
|
- Strike
|
||||||
|
- TARCAP
|
||||||
aircraft:
|
aircraft:
|
||||||
- F-16CM Fighting Falcon (Block 50)
|
- F-16CM Fighting Falcon (Block 50)
|
||||||
size: 20
|
size: 20
|
||||||
@ -157,7 +185,9 @@ squadrons:
|
|||||||
# Cairo West (95)
|
# Cairo West (95)
|
||||||
18:
|
18:
|
||||||
- primary: Transport
|
- primary: Transport
|
||||||
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
|
- C-130J-30 Super Hercules
|
||||||
- C-130
|
- C-130
|
||||||
size: 8
|
size: 8
|
||||||
- primary: BARCAP
|
- primary: BARCAP
|
||||||
|
|||||||
@ -53,7 +53,7 @@ squadrons:
|
|||||||
- A-10C Thunderbolt II (Suite 7)
|
- A-10C Thunderbolt II (Suite 7)
|
||||||
size: 8
|
size: 8
|
||||||
- primary: CAS
|
- primary: CAS
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- AH-64D Apache Longbow
|
- AH-64D Apache Longbow
|
||||||
size: 10
|
size: 10
|
||||||
@ -88,12 +88,13 @@ squadrons:
|
|||||||
# Creech
|
# Creech
|
||||||
1:
|
1:
|
||||||
- primary: CAS
|
- primary: CAS
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
|
- Ka-50 Hokum III
|
||||||
- Ka-50 Hokum (Blackshark 3)
|
- Ka-50 Hokum (Blackshark 3)
|
||||||
size: 8
|
size: 8
|
||||||
- primary: Air Assault
|
- primary: Air Assault
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- Mi-24P Hind-F
|
- Mi-24P Hind-F
|
||||||
size: 4
|
size: 4
|
||||||
|
|||||||
Binary file not shown.
@ -17,6 +17,8 @@ miz: grabthars_hammer.miz
|
|||||||
performance: 2
|
performance: 2
|
||||||
recommended_start_date: 1999-12-25
|
recommended_start_date: 1999-12-25
|
||||||
version: "10.7"
|
version: "10.7"
|
||||||
|
settings:
|
||||||
|
hercules: true
|
||||||
squadrons:
|
squadrons:
|
||||||
#Mount Pleasant
|
#Mount Pleasant
|
||||||
2:
|
2:
|
||||||
@ -35,10 +37,21 @@ squadrons:
|
|||||||
aircraft:
|
aircraft:
|
||||||
- F-15C Eagle
|
- F-15C Eagle
|
||||||
size: 8
|
size: 8
|
||||||
|
- primary: CAS
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- AH-64D Apache Longbow
|
||||||
|
size: 8
|
||||||
- primary: Refueling
|
- primary: Refueling
|
||||||
aircraft:
|
aircraft:
|
||||||
- KC-135 Stratotanker
|
- KC-135 Stratotanker
|
||||||
size: 2
|
size: 1
|
||||||
|
- primary: Air Assault
|
||||||
|
secondary: any
|
||||||
|
aircraft:
|
||||||
|
- C-130J-30 Super Hercules
|
||||||
|
- C-130
|
||||||
|
size: 1
|
||||||
#San Julian
|
#San Julian
|
||||||
11:
|
11:
|
||||||
- primary: BAI
|
- primary: BAI
|
||||||
@ -142,14 +155,15 @@ squadrons:
|
|||||||
#Ushuaia
|
#Ushuaia
|
||||||
7:
|
7:
|
||||||
- primary: CAS
|
- primary: CAS
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
|
- Ka-50 Hokum III
|
||||||
- Ka-50 Hokum (Blackshark 3)
|
- Ka-50 Hokum (Blackshark 3)
|
||||||
size: 8
|
size: 8
|
||||||
#Ushuaia Helo Port
|
#Ushuaia Helo Port
|
||||||
8:
|
8:
|
||||||
- primary: Air Assault
|
- primary: Air Assault
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- Mi-24P Hind-F
|
- Mi-24P Hind-F
|
||||||
size: 8
|
size: 8
|
||||||
|
|||||||
@ -48,7 +48,7 @@ squadrons:
|
|||||||
#Tarawa Class LHA
|
#Tarawa Class LHA
|
||||||
Blue-LHA:
|
Blue-LHA:
|
||||||
- primary: DEAD
|
- primary: DEAD
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- AH-64D Apache Longbow
|
- AH-64D Apache Longbow
|
||||||
size: 12
|
size: 12
|
||||||
@ -72,7 +72,7 @@ squadrons:
|
|||||||
#Akrotiri
|
#Akrotiri
|
||||||
44:
|
44:
|
||||||
- primary: BAI
|
- primary: BAI
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- AH-1W SuperCobra
|
- AH-1W SuperCobra
|
||||||
size: 4
|
size: 4
|
||||||
@ -81,8 +81,8 @@ squadrons:
|
|||||||
aircraft:
|
aircraft:
|
||||||
- OH-58D Kiowa Warrior
|
- OH-58D Kiowa Warrior
|
||||||
size: 4
|
size: 4
|
||||||
- primary: Air Assault
|
- primary: Transport
|
||||||
secondary: air-to-ground
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- UH-60A
|
- UH-60A
|
||||||
size: 4
|
size: 4
|
||||||
@ -95,6 +95,7 @@ squadrons:
|
|||||||
#Ercan
|
#Ercan
|
||||||
49:
|
49:
|
||||||
- primary: Transport
|
- primary: Transport
|
||||||
|
secondary: any
|
||||||
aircraft:
|
aircraft:
|
||||||
- CH-47D
|
- CH-47D
|
||||||
size: 6
|
size: 6
|
||||||
|
|||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user