mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Compare commits
9 Commits
develop-8.
...
develop-7.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7614017828 | ||
|
|
61879aeafa | ||
|
|
f5b9052257 | ||
|
|
b27d2be0d1 | ||
|
|
e1a1eca5da | ||
|
|
c695db0f98 | ||
|
|
ff20f16109 | ||
|
|
8af3dc6965 | ||
|
|
e6cf253e45 |
13
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
13
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -31,7 +31,7 @@ body:
|
||||
If the bug was found in a development build, select "Development build"
|
||||
and provide a link to the build in the field below.
|
||||
options:
|
||||
- 7.1.0
|
||||
- 6.1.1
|
||||
- Development build
|
||||
- type: textarea
|
||||
attributes:
|
||||
@@ -53,9 +53,9 @@ body:
|
||||
description: >
|
||||
Attach any files needed to reproduce the bug here. **A save game is
|
||||
required.** We typically cannot help without a save game (the
|
||||
`.liberation.zip` file found in `%USERPROFILE%/Saved
|
||||
Games/DCS/Liberation/Saves`), so most bugs filed without saved games
|
||||
will be closed without investigation.
|
||||
`.liberation` (or `.liberation.zip`, for 7.x) file found in
|
||||
`%USERPROFILE%/Saved Games/DCS/Liberation/Saves`), so most bugs filed
|
||||
without saved games will be closed without investigation.
|
||||
|
||||
|
||||
Other useful files to include are:
|
||||
@@ -73,7 +73,10 @@ body:
|
||||
|
||||
The `state.json` file for the most recently completed turn, located at
|
||||
`<Liberation install directory>/state.json`. This file is essential for
|
||||
investigating any issues with end-of-turn results processing.
|
||||
investigating any issues with end-of-turn results processing. **If you
|
||||
include this file, also include `last_turn.liberation`** (unless the
|
||||
save is from 7.x or newer, which includes that information in the save
|
||||
automatically).
|
||||
|
||||
|
||||
You can attach files to the bug by dragging and dropping the file into
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/new-game-bug.yml
vendored
2
.github/ISSUE_TEMPLATE/new-game-bug.yml
vendored
@@ -39,7 +39,7 @@ body:
|
||||
If the bug was found in a development build, select "Development build"
|
||||
and provide a link to the build in the field below.
|
||||
options:
|
||||
- 7.1.0
|
||||
- 6.1.1
|
||||
- Development build
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: run tests
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
pytest --cov --cov-report=xml tests
|
||||
pytest --cov-report=xml tests
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,7 +9,6 @@ venv
|
||||
.vscode/settings.json
|
||||
dist/**
|
||||
/.coverage
|
||||
/coverage.xml
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
.env
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
[](https://discord.gg/bKrtrkJ)
|
||||
|
||||
[](https://codecov.io/gh/dcs-liberation/dcs_liberation)
|
||||
[](https://github.com/dcs-liberation/dcs_liberation)
|
||||
[](https://github.com/dcs-liberation/dcs_liberation/issues)
|
||||

|
||||
|
||||
53
changelog.md
53
changelog.md
@@ -1,56 +1,3 @@
|
||||
# 8.1.0
|
||||
|
||||
Saves from 8.0.0 are compatible with 8.1.0
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.8.6.41363, including F-15E support.
|
||||
* **[UI]** Flight loadout/properties tab is now scrollable.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Campaign]** Fixed liveries for premade squadrons all being off-by-one.
|
||||
* **[UI]** Fixed numbering of waypoints in the map and flight dialog (first waypoint is now 0 rather than 1).
|
||||
|
||||
# 8.0.0
|
||||
|
||||
Saves from 7.x are not compatible with 8.0.
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for DCS 2.8.6.41066, including the new Sinai map.
|
||||
* **[UI]** Limited size of overfull airbase display and added scrollbar.
|
||||
* **[UI]** Waypoint altitudes can be edited in Waypoints tab of Edit Flight window.
|
||||
* **[UI]** Moved air wing and transfer menus to the toolbar to improve UI fit on low resolution displays.
|
||||
* **[UI]** Added basic game over dialog.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Campaign]** Fix bug introduced in 7.0 where map strike target deaths are no longer tracked.
|
||||
* **[Mission Generation]** Fix crash during mission generation caused by out of date DCS data for the Gazelle.
|
||||
* **[Mission Generation]** Fix crash during mission generation when DCS beacon data is inconsistent.
|
||||
|
||||
# 7.1.0
|
||||
|
||||
Saves from 7.0.0 are compatible with 7.1.0
|
||||
|
||||
## Features/Improvements
|
||||
|
||||
* **[Engine]** Support for Normandy 2 airfields.
|
||||
* **[Factions]** Replaced Patriot STRs "EWRs" with AN/FPS-117 for blue factions 1980 or newer.
|
||||
* **[Mission Generation]** Added option to prevent scud and V2 sites from firing at the start of the mission.
|
||||
* **[Mission Generation]** Added settings for controlling number of tactical commander, observer, JTAC, and game master slots.
|
||||
* **[Mission Planning]** Per-flight TOT offsets can now be set in the flight details UI. This allows individual flights to be scheduled ahead of or behind the rest of the package.
|
||||
* **[New Game Wizard]** The air wing configuration dialog will check for and reject overfull airbases before continuing when the new squadron rules are used.
|
||||
* **[New Game Wizard]** Closing the air wing configuration dialog will now cancel and return to the new game wizard rather than reverting changes and continuing.
|
||||
* **[New Game Wizard]** A warning will be displayed next to the new squadron rules button if the campaign predates the new rules and will likely require user intervention before continuing.
|
||||
* **[UI]** Parking capacity of each squadron's base is now shown during air wing configuration to avoid overcrowding bases when beginning the game with full squadrons.
|
||||
|
||||
## Fixes
|
||||
|
||||
* **[Mission Planning]** BAI is once again plannable against missile sites and coastal defense batteries.
|
||||
* **[UI]** Fixed formatting of departure time in flight details dialog.
|
||||
|
||||
# 7.0.0
|
||||
|
||||
Saves from 6.x are not compatible with 7.0.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import App from "./App";
|
||||
import { setupStore } from "./app/store";
|
||||
import { store } from "./app/store";
|
||||
import { render } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
test("app renders", () => {
|
||||
render(
|
||||
<Provider store={setupStore()}>
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
@@ -3,48 +3,36 @@ import combatReducer from "../api/combatSlice";
|
||||
import controlPointsReducer from "../api/controlPointsSlice";
|
||||
import flightsReducer from "../api/flightsSlice";
|
||||
import frontLinesReducer from "../api/frontLinesSlice";
|
||||
import iadsNetworkReducer from "../api/iadsNetworkSlice";
|
||||
import mapReducer from "../api/mapSlice";
|
||||
import navMeshReducer from "../api/navMeshSlice";
|
||||
import supplyRoutesReducer from "../api/supplyRoutesSlice";
|
||||
import tgosReducer from "../api/tgosSlice";
|
||||
import iadsNetworkReducer from "../api/iadsNetworkSlice";
|
||||
import threatZonesReducer from "../api/threatZonesSlice";
|
||||
import unculledZonesReducer from "../api/unculledZonesSlice";
|
||||
import {
|
||||
Action,
|
||||
PreloadedState,
|
||||
ThunkAction,
|
||||
combineReducers,
|
||||
configureStore,
|
||||
} from "@reduxjs/toolkit";
|
||||
import unculledZonesReducer from "../api/unculledZonesSlice";
|
||||
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
combat: combatReducer,
|
||||
controlPoints: controlPointsReducer,
|
||||
flights: flightsReducer,
|
||||
frontLines: frontLinesReducer,
|
||||
map: mapReducer,
|
||||
navmeshes: navMeshReducer,
|
||||
supplyRoutes: supplyRoutesReducer,
|
||||
iadsNetwork: iadsNetworkReducer,
|
||||
tgos: tgosReducer,
|
||||
threatZones: threatZonesReducer,
|
||||
[baseApi.reducerPath]: baseApi.reducer,
|
||||
unculledZones: unculledZonesReducer,
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
combat: combatReducer,
|
||||
controlPoints: controlPointsReducer,
|
||||
flights: flightsReducer,
|
||||
frontLines: frontLinesReducer,
|
||||
map: mapReducer,
|
||||
navmeshes: navMeshReducer,
|
||||
supplyRoutes: supplyRoutesReducer,
|
||||
iadsNetwork: iadsNetworkReducer,
|
||||
tgos: tgosReducer,
|
||||
threatZones: threatZonesReducer,
|
||||
[baseApi.reducerPath]: baseApi.reducer,
|
||||
unculledZones: unculledZonesReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(baseApi.middleware),
|
||||
});
|
||||
|
||||
export function setupStore(preloadedState?: PreloadedState<RootState>) {
|
||||
return configureStore({
|
||||
reducer: rootReducer,
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(baseApi.middleware),
|
||||
preloadedState: preloadedState,
|
||||
});
|
||||
}
|
||||
|
||||
export type AppStore = ReturnType<typeof setupStore>;
|
||||
export type AppDispatch = AppStore["dispatch"];
|
||||
export type RootState = ReturnType<typeof rootReducer>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppThunk<ReturnType = void> = ThunkAction<
|
||||
ReturnType,
|
||||
RootState,
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import AircraftLayer from "./AircraftLayer";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockLayerGroup = jest.fn();
|
||||
const mockMarker = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
LayerGroup: (props: PropsWithChildren<any>) => {
|
||||
mockLayerGroup(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
Marker: (props: any) => {
|
||||
mockMarker(props);
|
||||
},
|
||||
}));
|
||||
|
||||
test("layer is empty by default", async () => {
|
||||
renderWithProviders(<AircraftLayer />);
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
expect(mockMarker).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("layer has aircraft if non-empty", async () => {
|
||||
renderWithProviders(<AircraftLayer />, {
|
||||
preloadedState: {
|
||||
flights: {
|
||||
flights: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
blue: true,
|
||||
sidc: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
},
|
||||
bar: {
|
||||
id: "bar",
|
||||
blue: false,
|
||||
sidc: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
selected: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
expect(mockMarker).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
@@ -1,146 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import AirDefenseRangeLayer, { colorFor } from "./AirDefenseRangeLayer";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockLayerGroup = jest.fn();
|
||||
const mockCircle = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
LayerGroup: (props: PropsWithChildren<any>) => {
|
||||
mockLayerGroup(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
Circle: (props: any) => {
|
||||
mockCircle(props);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("colorFor", () => {
|
||||
it("has a unique color for each configuration", () => {
|
||||
const params = [
|
||||
[false, false],
|
||||
[false, true],
|
||||
[true, false],
|
||||
[true, true],
|
||||
];
|
||||
var colors = new Set<string>();
|
||||
for (const [blue, detection] of params) {
|
||||
colors.add(colorFor(blue, detection));
|
||||
}
|
||||
expect(colors.size).toEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AirDefenseRangeLayer", () => {
|
||||
it("draws nothing when there are no TGOs", () => {
|
||||
renderWithProviders(<AirDefenseRangeLayer blue={true} />);
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
expect(mockCircle).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not draw wrong range types", () => {
|
||||
renderWithProviders(<AirDefenseRangeLayer blue={true} />, {
|
||||
preloadedState: {
|
||||
tgos: {
|
||||
tgos: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
name: "Foo",
|
||||
control_point_name: "Bar",
|
||||
category: "AA",
|
||||
blue: false,
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
units: [],
|
||||
threat_ranges: [],
|
||||
detection_ranges: [20],
|
||||
dead: false,
|
||||
sidc: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
expect(mockCircle).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("draws threat ranges", () => {
|
||||
renderWithProviders(<AirDefenseRangeLayer blue={true} />, {
|
||||
preloadedState: {
|
||||
tgos: {
|
||||
tgos: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
name: "Foo",
|
||||
control_point_name: "Bar",
|
||||
category: "AA",
|
||||
blue: true,
|
||||
position: {
|
||||
lat: 10,
|
||||
lng: 20,
|
||||
},
|
||||
units: [],
|
||||
threat_ranges: [10],
|
||||
detection_ranges: [20],
|
||||
dead: false,
|
||||
sidc: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
expect(mockCircle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
center: {
|
||||
lat: 10,
|
||||
lng: 20,
|
||||
},
|
||||
radius: 10,
|
||||
color: colorFor(true, false),
|
||||
interactive: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("draws detection ranges", () => {
|
||||
renderWithProviders(<AirDefenseRangeLayer blue={true} detection />, {
|
||||
preloadedState: {
|
||||
tgos: {
|
||||
tgos: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
name: "Foo",
|
||||
control_point_name: "Bar",
|
||||
category: "AA",
|
||||
blue: true,
|
||||
position: {
|
||||
lat: 10,
|
||||
lng: 20,
|
||||
},
|
||||
units: [],
|
||||
threat_ranges: [10],
|
||||
detection_ranges: [20],
|
||||
dead: false,
|
||||
sidc: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
expect(mockCircle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
center: {
|
||||
lat: 10,
|
||||
lng: 20,
|
||||
},
|
||||
radius: 20,
|
||||
color: colorFor(true, true),
|
||||
interactive: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@ interface TgoRangeCirclesProps {
|
||||
detection?: boolean;
|
||||
}
|
||||
|
||||
export function colorFor(blue: boolean, detection: boolean) {
|
||||
function colorFor(blue: boolean, detection: boolean) {
|
||||
if (blue) {
|
||||
return detection ? "#bb89ff" : "#0084ff";
|
||||
}
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import Combat from "./Combat";
|
||||
import { LatLng } from "leaflet";
|
||||
|
||||
const mockPolyline = jest.fn();
|
||||
const mockPolygon = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
Polyline: (props: any) => {
|
||||
mockPolyline(props);
|
||||
},
|
||||
Polygon: (props: any) => {
|
||||
mockPolygon(props);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("Combat", () => {
|
||||
describe("footprint", () => {
|
||||
it("is not interactive", () => {
|
||||
renderWithProviders(
|
||||
<Combat
|
||||
combat={{
|
||||
id: "foo",
|
||||
flight_position: null,
|
||||
target_positions: null,
|
||||
footprint: [[new LatLng(0, 0), new LatLng(0, 1), new LatLng(1, 0)]],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolygon).toBeCalledWith(
|
||||
expect.objectContaining({ interactive: false })
|
||||
);
|
||||
});
|
||||
|
||||
// Fails because we don't handle multi-poly combat footprints correctly.
|
||||
it.skip("renders single polygons", () => {
|
||||
const boundary = [new LatLng(0, 0), new LatLng(0, 1), new LatLng(1, 0)];
|
||||
renderWithProviders(
|
||||
<Combat
|
||||
combat={{
|
||||
id: "foo",
|
||||
flight_position: null,
|
||||
target_positions: null,
|
||||
footprint: [boundary],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolygon).toBeCalledWith(
|
||||
expect.objectContaining({ positions: boundary })
|
||||
);
|
||||
});
|
||||
|
||||
// Fails because we don't handle multi-poly combat footprints correctly.
|
||||
it.skip("renders multiple polygons", () => {
|
||||
const boundary = [new LatLng(0, 0), new LatLng(0, 1), new LatLng(1, 0)];
|
||||
renderWithProviders(
|
||||
<Combat
|
||||
combat={{
|
||||
id: "foo",
|
||||
flight_position: null,
|
||||
target_positions: null,
|
||||
footprint: [boundary, boundary],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolygon).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("lines", () => {
|
||||
it("is not interactive", () => {
|
||||
renderWithProviders(
|
||||
<Combat
|
||||
combat={{
|
||||
id: "foo",
|
||||
flight_position: new LatLng(0, 0),
|
||||
target_positions: [new LatLng(1, 0)],
|
||||
footprint: null,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toBeCalledWith(
|
||||
expect.objectContaining({ interactive: false })
|
||||
);
|
||||
});
|
||||
|
||||
it("renders single line", () => {
|
||||
renderWithProviders(
|
||||
<Combat
|
||||
combat={{
|
||||
id: "foo",
|
||||
flight_position: new LatLng(0, 0),
|
||||
target_positions: [new LatLng(0, 1)],
|
||||
footprint: null,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
positions: [new LatLng(0, 0), new LatLng(0, 1)],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("renders multiple lines", () => {
|
||||
renderWithProviders(
|
||||
<Combat
|
||||
combat={{
|
||||
id: "foo",
|
||||
flight_position: new LatLng(0, 0),
|
||||
target_positions: [new LatLng(0, 1), new LatLng(1, 0)],
|
||||
footprint: null,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("renders nothing if no footprint or targets", () => {
|
||||
const { container } = renderWithProviders(
|
||||
<Combat
|
||||
combat={{
|
||||
id: "foo",
|
||||
flight_position: new LatLng(0, 0),
|
||||
target_positions: null,
|
||||
footprint: null,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import CombatLayer from "./CombatLayer";
|
||||
import { LatLng } from "leaflet";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockPolyline = jest.fn();
|
||||
const mockLayerGroup = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
LayerGroup: (props: PropsWithChildren<any>) => {
|
||||
mockLayerGroup(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
Polyline: (props: any) => {
|
||||
mockPolyline(props);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("CombatLayer", () => {
|
||||
it("renders each combat", () => {
|
||||
renderWithProviders(<CombatLayer />, {
|
||||
preloadedState: {
|
||||
combat: {
|
||||
combat: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
flight_position: new LatLng(0, 0),
|
||||
target_positions: [new LatLng(0, 1)],
|
||||
footprint: null,
|
||||
},
|
||||
bar: {
|
||||
id: "foo",
|
||||
flight_position: new LatLng(0, 0),
|
||||
target_positions: [new LatLng(0, 1)],
|
||||
footprint: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it("renders LayerGroup but no contents if no combat", () => {
|
||||
renderWithProviders(<CombatLayer />);
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
expect(mockPolyline).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,52 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import ControlPointsLayer from "./ControlPointsLayer";
|
||||
import { LatLng } from "leaflet";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockMarker = jest.fn();
|
||||
const mockLayerGroup = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
LayerGroup: (props: PropsWithChildren<any>) => {
|
||||
mockLayerGroup(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
Marker: (props: any) => {
|
||||
mockMarker(props);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("ControlPointsLayer", () => {
|
||||
it("renders each control point", () => {
|
||||
renderWithProviders(<ControlPointsLayer />, {
|
||||
preloadedState: {
|
||||
controlPoints: {
|
||||
controlPoints: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
name: "Foo",
|
||||
blue: true,
|
||||
position: new LatLng(0, 0),
|
||||
mobile: false,
|
||||
sidc: "",
|
||||
},
|
||||
bar: {
|
||||
id: "bar",
|
||||
name: "Bar",
|
||||
blue: false,
|
||||
position: new LatLng(1, 0),
|
||||
mobile: false,
|
||||
sidc: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockMarker).toBeCalledTimes(2);
|
||||
});
|
||||
|
||||
it("renders LayerGroup but no contents if no combat", () => {
|
||||
renderWithProviders(<ControlPointsLayer />);
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
expect(mockMarker).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,78 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import CullingExclusionZones from "./CullingExclusionZones";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockCircle = jest.fn();
|
||||
const mockLayerGroup = jest.fn();
|
||||
const mockLayerControlOverlay = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
LayerGroup: (props: PropsWithChildren<any>) => {
|
||||
mockLayerGroup(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
LayersControl: {
|
||||
Overlay: (props: PropsWithChildren<any>) => {
|
||||
mockLayerControlOverlay(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
},
|
||||
Circle: (props: any) => {
|
||||
mockCircle(props);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("CullingExclusionZones", () => {
|
||||
it("is empty there are no exclusion zones", () => {
|
||||
renderWithProviders(<CullingExclusionZones />);
|
||||
expect(mockCircle).not.toHaveBeenCalled();
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
expect(mockLayerControlOverlay).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("zone circles", () => {
|
||||
it("are drawn in the correct locations", () => {
|
||||
renderWithProviders(<CullingExclusionZones />, {
|
||||
preloadedState: {
|
||||
unculledZones: {
|
||||
zones: [
|
||||
{
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
radius: 10,
|
||||
},
|
||||
{
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
radius: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockCircle).toHaveBeenCalledTimes(2);
|
||||
expect(mockCircle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
center: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
radius: 10,
|
||||
})
|
||||
);
|
||||
expect(mockCircle).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
center: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
radius: 2,
|
||||
})
|
||||
);
|
||||
});
|
||||
it("are not interactive", () => {});
|
||||
});
|
||||
});
|
||||
@@ -30,10 +30,18 @@ const CullingExclusionCircles = (props: CullingExclusionCirclesProps) => {
|
||||
|
||||
export default function CullingExclusionZones() {
|
||||
const data = useAppSelector(selectUnculledZones).zones;
|
||||
var cez = <></>;
|
||||
|
||||
if (!data) {
|
||||
console.log("Empty response when loading culling exclusion zones");
|
||||
} else {
|
||||
cez = (
|
||||
<CullingExclusionCircles zones={data}></CullingExclusionCircles>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<LayersControl.Overlay name="Culling exclusion zones">
|
||||
<CullingExclusionCircles zones={data}></CullingExclusionCircles>
|
||||
{cez}
|
||||
</LayersControl.Overlay>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,405 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import FlightPlansLayer from "./FlightPlansLayer";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockPolyline = jest.fn();
|
||||
const mockLayerGroup = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
LayerGroup: (props: PropsWithChildren<any>) => {
|
||||
mockLayerGroup(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
Polyline: (props: any) => {
|
||||
mockPolyline(props);
|
||||
},
|
||||
}));
|
||||
|
||||
// The waypoints in test data below should all use `should_make: false`. Markers
|
||||
// need useMap() to check the zoom level to decide if they should be drawn or
|
||||
// not, and we don't have good options here for mocking that behavior.
|
||||
describe("FlightPlansLayer", () => {
|
||||
describe("unselected flights", () => {
|
||||
it("are drawn", () => {
|
||||
renderWithProviders(<FlightPlansLayer blue={true} />, {
|
||||
preloadedState: {
|
||||
flights: {
|
||||
flights: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
blue: true,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
bar: {
|
||||
id: "bar",
|
||||
blue: true,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
selected: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(2);
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
});
|
||||
it("are not drawn if wrong coalition", () => {
|
||||
renderWithProviders(<FlightPlansLayer blue={true} />, {
|
||||
preloadedState: {
|
||||
flights: {
|
||||
flights: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
blue: true,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
bar: {
|
||||
id: "bar",
|
||||
blue: false,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
selected: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(1);
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
});
|
||||
it("are not drawn when only selected flights are to be drawn", () => {
|
||||
renderWithProviders(<FlightPlansLayer blue={true} selectedOnly />, {
|
||||
preloadedState: {
|
||||
flights: {
|
||||
flights: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
blue: true,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
selected: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).not.toHaveBeenCalled();
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
describe("selected flights", () => {
|
||||
it("are drawn", () => {
|
||||
renderWithProviders(<FlightPlansLayer blue={true} />, {
|
||||
preloadedState: {
|
||||
flights: {
|
||||
flights: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
blue: true,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
bar: {
|
||||
id: "bar",
|
||||
blue: true,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
selected: "foo",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(2);
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
});
|
||||
it("are not drawn twice", () => {
|
||||
renderWithProviders(<FlightPlansLayer blue={true} />, {
|
||||
preloadedState: {
|
||||
flights: {
|
||||
flights: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
blue: true,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
selected: "foo",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(1);
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
});
|
||||
it("are not drawn if red", () => {
|
||||
renderWithProviders(<FlightPlansLayer blue={false} selectedOnly />, {
|
||||
preloadedState: {
|
||||
flights: {
|
||||
flights: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
blue: false,
|
||||
sidc: "",
|
||||
waypoints: [
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 0,
|
||||
lng: 0,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
position: {
|
||||
lat: 1,
|
||||
lng: 1,
|
||||
},
|
||||
altitude_ft: 0,
|
||||
altitude_reference: "MSL",
|
||||
is_movable: true,
|
||||
should_mark: false,
|
||||
include_in_path: true,
|
||||
timing: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
selected: "foo",
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).not.toHaveBeenCalled();
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
it("are not drawn if there are no flights", () => {
|
||||
renderWithProviders(<FlightPlansLayer blue={true} />);
|
||||
expect(mockPolyline).not.toHaveBeenCalled();
|
||||
expect(mockLayerGroup).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import FrontLine from "./FrontLine";
|
||||
import { PolylineProps } from "react-leaflet";
|
||||
|
||||
const mockPolyline = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
Polyline: (props: PolylineProps) => {
|
||||
mockPolyline(props);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("FrontLine", () => {
|
||||
it("is drawn in the correct location", () => {
|
||||
const extents = [
|
||||
{ lat: 0, lng: 0 },
|
||||
{ lat: 1, lng: 0 },
|
||||
];
|
||||
renderWithProviders(
|
||||
<FrontLine
|
||||
front={{
|
||||
id: "",
|
||||
extents: extents,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
positions: extents,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import FrontLinesLayer from "./FrontLinesLayer";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockPolyline = jest.fn();
|
||||
const mockLayerGroup = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
LayerGroup: (props: PropsWithChildren<any>) => {
|
||||
mockLayerGroup(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
Polyline: (props: any) => {
|
||||
mockPolyline(props);
|
||||
},
|
||||
}));
|
||||
|
||||
// The waypoints in test data below should all use `should_make: false`. Markers
|
||||
// need useMap() to check the zoom level to decide if they should be drawn or
|
||||
// not, and we don't have good options here for mocking that behavior.
|
||||
describe("FrontLinesLayer", () => {
|
||||
it("draws nothing when there are no front lines", () => {
|
||||
renderWithProviders(<FrontLinesLayer />);
|
||||
expect(mockPolyline).not.toHaveBeenCalled();
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("draws front lines", () => {
|
||||
const extents = [
|
||||
{ lat: 0, lng: 0 },
|
||||
{ lat: 1, lng: 1 },
|
||||
];
|
||||
renderWithProviders(<FrontLinesLayer />, {
|
||||
preloadedState: {
|
||||
frontLines: {
|
||||
fronts: {
|
||||
foo: {
|
||||
id: "foo",
|
||||
extents: extents,
|
||||
},
|
||||
bar: {
|
||||
id: "bar",
|
||||
extents: extents,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(2);
|
||||
expect(mockPolyline).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
positions: extents,
|
||||
})
|
||||
);
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { useAppSelector } from "../../app/hooks";
|
||||
import FrontLine from "../frontline";
|
||||
import { LayerGroup } from "react-leaflet";
|
||||
|
||||
export default function FrontLinesLayer() {
|
||||
export default function SupplyRoutesLayer() {
|
||||
const fronts = useAppSelector(selectFrontLines).fronts;
|
||||
return (
|
||||
<LayerGroup>
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import NavMeshLayer from "./NavMeshLayer";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockPolygon = jest.fn();
|
||||
const mockLayerGroup = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
LayerGroup: (props: PropsWithChildren<any>) => {
|
||||
mockLayerGroup(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
Polygon: (props: any) => {
|
||||
mockPolygon(props);
|
||||
},
|
||||
}));
|
||||
|
||||
// The waypoints in test data below should all use `should_make: false`. Markers
|
||||
// need useMap() to check the zoom level to decide if they should be drawn or
|
||||
// not, and we don't have good options here for mocking that behavior.
|
||||
describe("NavMeshLayer", () => {
|
||||
it("draws blue meshes", () => {
|
||||
const poly1 = [
|
||||
[
|
||||
{ lat: -1, lng: 0 },
|
||||
{ lat: 0, lng: 1 },
|
||||
{ lat: 1, lng: 0 },
|
||||
],
|
||||
];
|
||||
const poly2 = [
|
||||
[
|
||||
{ lat: -1, lng: 0 },
|
||||
{ lat: 0, lng: -1 },
|
||||
{ lat: 1, lng: 0 },
|
||||
],
|
||||
];
|
||||
renderWithProviders(<NavMeshLayer blue={true} />, {
|
||||
preloadedState: {
|
||||
navmeshes: {
|
||||
blue: [
|
||||
{
|
||||
poly: poly1,
|
||||
threatened: false,
|
||||
},
|
||||
{
|
||||
poly: poly2,
|
||||
threatened: true,
|
||||
},
|
||||
],
|
||||
red: [
|
||||
{
|
||||
poly: [
|
||||
[
|
||||
{ lat: -1, lng: 0 },
|
||||
{ lat: 0, lng: 2 },
|
||||
{ lat: 1, lng: 0 },
|
||||
],
|
||||
],
|
||||
threatened: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolygon).toHaveBeenCalledTimes(2);
|
||||
expect(mockPolygon).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
fillColor: "#00ff00",
|
||||
positions: poly1,
|
||||
interactive: false,
|
||||
})
|
||||
);
|
||||
expect(mockPolygon).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
fillColor: "#ff0000",
|
||||
positions: poly2,
|
||||
interactive: false,
|
||||
})
|
||||
);
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it("draws red navmesh", () => {
|
||||
renderWithProviders(<NavMeshLayer blue={false} />, {
|
||||
preloadedState: {
|
||||
navmeshes: {
|
||||
blue: [
|
||||
{
|
||||
poly: [
|
||||
[
|
||||
{ lat: -1, lng: 0 },
|
||||
{ lat: 0, lng: 1 },
|
||||
{ lat: 1, lng: 0 },
|
||||
],
|
||||
],
|
||||
threatened: false,
|
||||
},
|
||||
{
|
||||
poly: [
|
||||
[
|
||||
{ lat: -1, lng: 0 },
|
||||
{ lat: 0, lng: -1 },
|
||||
{ lat: 1, lng: 0 },
|
||||
],
|
||||
],
|
||||
threatened: true,
|
||||
},
|
||||
],
|
||||
red: [
|
||||
{
|
||||
poly: [
|
||||
[
|
||||
{ lat: -1, lng: 0 },
|
||||
{ lat: 0, lng: 2 },
|
||||
{ lat: 1, lng: 0 },
|
||||
],
|
||||
],
|
||||
threatened: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(mockPolygon).toHaveBeenCalledTimes(1);
|
||||
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import SplitLines from "./SplitLines";
|
||||
import { screen } from "@testing-library/dom";
|
||||
import { render } from "@testing-library/react";
|
||||
|
||||
describe("SplitLines", () => {
|
||||
it("joins items with line break tags", () => {
|
||||
render(
|
||||
<div data-testid={"container"}>
|
||||
<SplitLines items={["foo", "bar", "baz"]} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const container = screen.getByTestId("container");
|
||||
expect(container).toContainHTML("foo<br />bar<br />baz<br />");
|
||||
});
|
||||
});
|
||||
@@ -1,159 +0,0 @@
|
||||
import { renderWithProviders } from "../../testutils";
|
||||
import SupplyRoute, { RouteColor } from "./SupplyRoute";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { PropsWithChildren } from "react";
|
||||
|
||||
const mockPolyline = jest.fn();
|
||||
jest.mock("react-leaflet", () => ({
|
||||
Polyline: (props: PropsWithChildren<any>) => {
|
||||
mockPolyline(props);
|
||||
return <>{props.children}</>;
|
||||
},
|
||||
Tooltip: (props: PropsWithChildren<any>) => {
|
||||
return <p data-testid="tooltip">{props.children}</p>;
|
||||
},
|
||||
}));
|
||||
|
||||
describe("SupplyRoute", () => {
|
||||
it("is red when inactive and owned by opfor", () => {
|
||||
renderWithProviders(
|
||||
<SupplyRoute
|
||||
route={{
|
||||
id: "",
|
||||
points: [],
|
||||
front_active: false,
|
||||
is_sea: false,
|
||||
blue: false,
|
||||
active_transports: [],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
color: RouteColor.Red,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("is blue when inactive and owned by bluefor", () => {
|
||||
renderWithProviders(
|
||||
<SupplyRoute
|
||||
route={{
|
||||
id: "",
|
||||
points: [],
|
||||
front_active: false,
|
||||
is_sea: false,
|
||||
blue: true,
|
||||
active_transports: [],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
color: RouteColor.Blue,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("is orange when contested", () => {
|
||||
renderWithProviders(
|
||||
<SupplyRoute
|
||||
route={{
|
||||
id: "",
|
||||
points: [],
|
||||
front_active: true,
|
||||
is_sea: false,
|
||||
blue: false,
|
||||
active_transports: [],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
color: RouteColor.Contested,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("is highlighted when the route has active transports", () => {
|
||||
renderWithProviders(
|
||||
<SupplyRoute
|
||||
route={{
|
||||
id: "",
|
||||
points: [],
|
||||
front_active: false,
|
||||
is_sea: false,
|
||||
blue: false,
|
||||
active_transports: ["foo"],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(2);
|
||||
expect(mockPolyline).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
color: RouteColor.Highlight,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("is drawn in the right place", () => {
|
||||
const points = [
|
||||
{ lat: 0, lng: 0 },
|
||||
{ lat: 1, lng: 1 },
|
||||
];
|
||||
renderWithProviders(
|
||||
<SupplyRoute
|
||||
route={{
|
||||
id: "",
|
||||
points: points,
|
||||
front_active: false,
|
||||
is_sea: false,
|
||||
blue: false,
|
||||
active_transports: ["foo"],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
expect(mockPolyline).toHaveBeenCalledTimes(2);
|
||||
expect(mockPolyline).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
positions: points,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("has a tooltip describing an inactive supply route", () => {
|
||||
renderWithProviders(
|
||||
<SupplyRoute
|
||||
route={{
|
||||
id: "",
|
||||
points: [],
|
||||
front_active: false,
|
||||
is_sea: false,
|
||||
blue: false,
|
||||
active_transports: [],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const tooltip = screen.getByTestId("tooltip");
|
||||
expect(tooltip).toHaveTextContent("This supply route is inactive.");
|
||||
});
|
||||
|
||||
it("has a tooltip describing active supply routes", () => {
|
||||
renderWithProviders(
|
||||
<SupplyRoute
|
||||
route={{
|
||||
id: "",
|
||||
points: [],
|
||||
front_active: false,
|
||||
is_sea: false,
|
||||
blue: false,
|
||||
active_transports: ["foo", "bar"],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const tooltip = screen.getByTestId("tooltip");
|
||||
expect(tooltip).toContainHTML("foo<br />bar");
|
||||
});
|
||||
});
|
||||
@@ -4,13 +4,6 @@ import { Polyline as LPolyline } from "leaflet";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Polyline, Tooltip } from "react-leaflet";
|
||||
|
||||
export enum RouteColor {
|
||||
Blue = "#2d3e50",
|
||||
Contested = "#c85050",
|
||||
Highlight = "#ffffff",
|
||||
Red = "#8c1414",
|
||||
}
|
||||
|
||||
interface SupplyRouteProps {
|
||||
route: SupplyRouteModel;
|
||||
}
|
||||
@@ -33,22 +26,18 @@ function ActiveSupplyRouteHighlight(props: SupplyRouteProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Polyline
|
||||
positions={props.route.points}
|
||||
color={RouteColor.Highlight}
|
||||
weight={2}
|
||||
/>
|
||||
<Polyline positions={props.route.points} color={"#ffffff"} weight={2} />
|
||||
);
|
||||
}
|
||||
|
||||
function colorFor(route: SupplyRouteModel) {
|
||||
if (route.front_active) {
|
||||
return RouteColor.Contested;
|
||||
return "#c85050";
|
||||
}
|
||||
if (route.blue) {
|
||||
return RouteColor.Blue;
|
||||
return "#2d3e50";
|
||||
}
|
||||
return RouteColor.Red;
|
||||
return "#8c1414";
|
||||
}
|
||||
|
||||
export default function SupplyRoute(props: SupplyRouteProps) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import App from "./App";
|
||||
import { setupStore } from "./app/store";
|
||||
import { store } from "./app/store";
|
||||
import { SocketProvider } from "./components/socketprovider/socketprovider";
|
||||
import "./index.css";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
@@ -12,7 +12,7 @@ const root = ReactDOM.createRoot(
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Provider store={setupStore()}>
|
||||
<Provider store={store}>
|
||||
<SocketProvider>
|
||||
<App />
|
||||
</SocketProvider>
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
// https://redux.js.org/usage/writing-tests
|
||||
import { setupStore } from "../app/store";
|
||||
import type { AppStore, RootState } from "../app/store";
|
||||
import type { PreloadedState } from "@reduxjs/toolkit";
|
||||
import { render } from "@testing-library/react";
|
||||
import type { RenderOptions } from "@testing-library/react";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
// This type interface extends the default options for render from RTL, as well
|
||||
// as allows the user to specify other things such as initialState, store.
|
||||
interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
|
||||
preloadedState?: PreloadedState<RootState>;
|
||||
store?: AppStore;
|
||||
}
|
||||
|
||||
export function renderWithProviders(
|
||||
ui: React.ReactElement,
|
||||
{
|
||||
preloadedState = {},
|
||||
// Automatically create a store instance if no store was passed in
|
||||
store = setupStore(preloadedState),
|
||||
...renderOptions
|
||||
}: ExtendedRenderOptions = {}
|
||||
) {
|
||||
function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
|
||||
return <Provider store={store}>{children}</Provider>;
|
||||
}
|
||||
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
|
||||
}
|
||||
@@ -2,7 +2,7 @@ coverage:
|
||||
status:
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
informational: false
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
project = "DCS Liberation"
|
||||
copyright = "2023, DCS Liberation Team"
|
||||
author = "DCS Liberation Team"
|
||||
release = "8.1.0"
|
||||
release = "7.0.1"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
@@ -21,7 +21,6 @@ from .planningerror import PlanningError
|
||||
from ..flightwaypointtype import FlightWaypointType
|
||||
from ..starttype import StartType
|
||||
from ..traveltime import GroundSpeed, TravelTime
|
||||
from ...savecompat import has_save_compat_for
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game.dcs.aircrafttype import FuelConsumption
|
||||
@@ -63,13 +62,6 @@ class FlightPlan(ABC, Generic[LayoutT]):
|
||||
def __init__(self, flight: Flight, layout: LayoutT) -> None:
|
||||
self.flight = flight
|
||||
self.layout = layout
|
||||
self.tot_offset = self.default_tot_offset()
|
||||
|
||||
@has_save_compat_for(7)
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
if "tot_offset" not in state:
|
||||
state["tot_offset"] = self.default_tot_offset()
|
||||
self.__dict__.update(state)
|
||||
|
||||
@property
|
||||
def package(self) -> Package:
|
||||
@@ -203,7 +195,8 @@ class FlightPlan(ABC, Generic[LayoutT]):
|
||||
[meters(cp.position.distance_to_point(w.position)) for w in self.waypoints]
|
||||
)
|
||||
|
||||
def default_tot_offset(self) -> timedelta:
|
||||
@property
|
||||
def tot_offset(self) -> timedelta:
|
||||
"""This flight's offset from the package's TOT.
|
||||
|
||||
Positive values represent later TOTs. An offset of -2 minutes is used
|
||||
|
||||
@@ -25,6 +25,10 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class FormationAttackFlightPlan(FormationFlightPlan, ABC):
|
||||
@property
|
||||
def lead_time(self) -> timedelta:
|
||||
return timedelta()
|
||||
|
||||
@property
|
||||
def package_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||
return {
|
||||
@@ -46,6 +50,13 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
|
||||
def tot_waypoint(self) -> FlightWaypoint:
|
||||
return self.layout.targets[0]
|
||||
|
||||
@property
|
||||
def tot_offset(self) -> timedelta:
|
||||
try:
|
||||
return -self.lead_time
|
||||
except AttributeError:
|
||||
return timedelta()
|
||||
|
||||
@property
|
||||
def target_area_waypoint(self) -> FlightWaypoint:
|
||||
return FlightWaypoint(
|
||||
|
||||
@@ -16,8 +16,9 @@ class SeadFlightPlan(FormationAttackFlightPlan):
|
||||
def builder_type() -> Type[Builder]:
|
||||
return Builder
|
||||
|
||||
def default_tot_offset(self) -> timedelta:
|
||||
return -timedelta(minutes=1)
|
||||
@property
|
||||
def lead_time(self) -> timedelta:
|
||||
return timedelta(minutes=1)
|
||||
|
||||
|
||||
class Builder(FormationAttackBuilder[SeadFlightPlan, FormationAttackLayout]):
|
||||
|
||||
@@ -38,6 +38,10 @@ class SweepLayout(LoiterLayout):
|
||||
|
||||
|
||||
class SweepFlightPlan(LoiterFlightPlan):
|
||||
@property
|
||||
def lead_time(self) -> timedelta:
|
||||
return timedelta(minutes=5)
|
||||
|
||||
@staticmethod
|
||||
def builder_type() -> Type[Builder]:
|
||||
return Builder
|
||||
@@ -50,8 +54,9 @@ class SweepFlightPlan(LoiterFlightPlan):
|
||||
def tot_waypoint(self) -> FlightWaypoint:
|
||||
return self.layout.sweep_end
|
||||
|
||||
def default_tot_offset(self) -> timedelta:
|
||||
return -timedelta(minutes=5)
|
||||
@property
|
||||
def tot_offset(self) -> timedelta:
|
||||
return -self.lead_time
|
||||
|
||||
@property
|
||||
def sweep_start_time(self) -> datetime:
|
||||
|
||||
@@ -34,6 +34,10 @@ class TarCapLayout(PatrollingLayout):
|
||||
|
||||
|
||||
class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
|
||||
@property
|
||||
def lead_time(self) -> timedelta:
|
||||
return timedelta(minutes=2)
|
||||
|
||||
@property
|
||||
def patrol_duration(self) -> timedelta:
|
||||
# Note that this duration only has an effect if there are no
|
||||
@@ -60,8 +64,9 @@ class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
|
||||
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
|
||||
return {self.layout.patrol_start, self.layout.patrol_end}
|
||||
|
||||
def default_tot_offset(self) -> timedelta:
|
||||
return -timedelta(minutes=2)
|
||||
@property
|
||||
def tot_offset(self) -> timedelta:
|
||||
return -self.lead_time
|
||||
|
||||
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> datetime | None:
|
||||
if waypoint == self.layout.patrol_end:
|
||||
|
||||
@@ -133,7 +133,6 @@ class StateData:
|
||||
+ data["kill_events"]
|
||||
+ data["crash_events"]
|
||||
+ data["dead_events"]
|
||||
+ data["killed_ground_units"]
|
||||
)
|
||||
for unit in killed_units: # organize killed units into aircraft vs ground
|
||||
if unit_map.flight(unit) is not None:
|
||||
|
||||
@@ -5,6 +5,7 @@ import logging
|
||||
import math
|
||||
from collections.abc import Iterator
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from enum import Enum
|
||||
from typing import Any, List, TYPE_CHECKING, Type, Union, cast
|
||||
|
||||
from dcs.countries import Switzerland, USAFAggressors, UnitedNationsPeacekeepers
|
||||
@@ -36,7 +37,6 @@ from .theater.theatergroundobject import (
|
||||
)
|
||||
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
|
||||
from .timeofday import TimeOfDay
|
||||
from .turnstate import TurnState
|
||||
from .weather.conditions import Conditions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -81,6 +81,12 @@ AWACS_BUDGET_COST = 4
|
||||
PLAYER_BUDGET_IMPORTANCE_LOG = 2
|
||||
|
||||
|
||||
class TurnState(Enum):
|
||||
WIN = 0
|
||||
LOSS = 1
|
||||
CONTINUE = 2
|
||||
|
||||
|
||||
class Game:
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -353,14 +353,6 @@ class MissionGenerator:
|
||||
gen.generate()
|
||||
|
||||
def setup_combined_arms(self) -> None:
|
||||
self.mission.groundControl.blue_game_masters = (
|
||||
self.game.settings.game_master_slots
|
||||
)
|
||||
self.mission.groundControl.blue_tactical_commander = (
|
||||
self.game.settings.tactical_commander_slots
|
||||
)
|
||||
self.mission.groundControl.pilot_can_control_vehicles = (
|
||||
self.mission.groundControl.blue_tactical_commander > 0
|
||||
)
|
||||
self.mission.groundControl.blue_jtac = self.game.settings.jtac_operator_slots
|
||||
self.mission.groundControl.blue_observer = self.game.settings.observer_slots
|
||||
self.mission.groundControl.pilot_can_control_vehicles = COMBINED_ARMS_SLOTS > 0
|
||||
self.mission.groundControl.blue_tactical_commander = COMBINED_ARMS_SLOTS
|
||||
self.mission.groundControl.blue_observer = 1
|
||||
|
||||
@@ -315,10 +315,6 @@ class MissileSiteGenerator(GroundObjectGenerator):
|
||||
|
||||
def generate(self) -> None:
|
||||
super(MissileSiteGenerator, self).generate()
|
||||
|
||||
if not self.game.settings.generate_fire_tasks_for_missile_sites:
|
||||
return
|
||||
|
||||
# Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now)
|
||||
# TODO : Should be pre-planned ?
|
||||
# TODO : Add delay to task to spread fire task over mission duration ?
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Runway information and selection."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator, Optional, TYPE_CHECKING
|
||||
|
||||
@@ -52,20 +51,7 @@ class RunwayData:
|
||||
atc = atc_radio.uhf
|
||||
|
||||
for beacon_data in airport.beacons:
|
||||
try:
|
||||
beacon = Beacons.with_id(beacon_data.id, theater)
|
||||
except KeyError:
|
||||
# DCS data is not always correct. At time of writing, Hatzor in Sinai
|
||||
# claims to have a beacon named airfield20_0, but the Sinai beacons.lua
|
||||
# has no such beacon.
|
||||
# See https://github.com/dcs-liberation/dcs_liberation/issues/3021.
|
||||
logging.exception(
|
||||
"Airport %s claims to have beacon %s but the map has no beacon "
|
||||
"with that ID",
|
||||
airport.name,
|
||||
beacon_data.id,
|
||||
)
|
||||
continue
|
||||
beacon = Beacons.with_id(beacon_data.id, theater)
|
||||
if beacon.is_tacan:
|
||||
tacan = beacon.tacan_channel
|
||||
tacan_callsign = beacon.callsign
|
||||
|
||||
@@ -41,8 +41,7 @@ def has_save_compat_for(
|
||||
"""
|
||||
|
||||
def decorator(func: Callable[..., ReturnT]) -> Callable[..., ReturnT]:
|
||||
# Allow current and previous version to ease cherry-picking.
|
||||
if major not in {MAJOR_VERSION - 1, MAJOR_VERSION}:
|
||||
if major != MAJOR_VERSION:
|
||||
raise DeprecatedSaveCompatError(func.__name__)
|
||||
return func
|
||||
|
||||
|
||||
@@ -6,16 +6,30 @@ from starlette.responses import Response
|
||||
|
||||
from game import Game
|
||||
from game.ato import Flight
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.server import GameContext
|
||||
from game.server.leaflet import LeafletPoint
|
||||
from game.server.waypoints.models import FlightWaypointJs
|
||||
from game.sim import GameUpdateEvents
|
||||
from game.utils import meters
|
||||
|
||||
router: APIRouter = APIRouter(prefix="/waypoints")
|
||||
|
||||
|
||||
def waypoints_for_flight(flight: Flight) -> list[FlightWaypointJs]:
|
||||
return [
|
||||
departure = FlightWaypointJs.for_waypoint(
|
||||
FlightWaypoint(
|
||||
"TAKEOFF",
|
||||
FlightWaypointType.TAKEOFF,
|
||||
flight.departure.position,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
),
|
||||
flight,
|
||||
0,
|
||||
)
|
||||
return [departure] + [
|
||||
FlightWaypointJs.for_waypoint(w, flight, i)
|
||||
for i, w in enumerate(flight.flight_plan.waypoints, 1)
|
||||
]
|
||||
@@ -50,7 +64,7 @@ def set_position(
|
||||
if waypoint_idx == 0:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
waypoint = flight.flight_plan.waypoints[waypoint_idx]
|
||||
waypoint = flight.flight_plan.waypoints[waypoint_idx - 1]
|
||||
waypoint.position = Point.from_latlng(
|
||||
LatLng(position.lat, position.lng), game.theater.terrain
|
||||
)
|
||||
|
||||
@@ -42,8 +42,6 @@ HQ_AUTOMATION_SECTION = "HQ Automation"
|
||||
|
||||
MISSION_GENERATOR_PAGE = "Mission Generator"
|
||||
|
||||
COMMANDERS_SECTION = "Battlefield Commanders"
|
||||
|
||||
GAMEPLAY_SECTION = "Gameplay"
|
||||
|
||||
# TODO: Make sections a type and add headers.
|
||||
@@ -312,41 +310,6 @@ class Settings:
|
||||
reserves_procurement_target: int = 10
|
||||
|
||||
# Mission Generator
|
||||
|
||||
# Commanders
|
||||
game_master_slots: int = bounded_int_option(
|
||||
"Game master",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=COMMANDERS_SECTION,
|
||||
default=0,
|
||||
min=0,
|
||||
max=100,
|
||||
)
|
||||
tactical_commander_slots: int = bounded_int_option(
|
||||
"Tactical commander",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=COMMANDERS_SECTION,
|
||||
default=1,
|
||||
min=0,
|
||||
max=100,
|
||||
)
|
||||
jtac_operator_slots: int = bounded_int_option(
|
||||
"JTAC/Operator",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=COMMANDERS_SECTION,
|
||||
default=0,
|
||||
min=0,
|
||||
max=100,
|
||||
)
|
||||
observer_slots: int = bounded_int_option(
|
||||
"Observer",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=COMMANDERS_SECTION,
|
||||
default=1,
|
||||
min=0,
|
||||
max=100,
|
||||
)
|
||||
|
||||
# Gameplay
|
||||
fast_forward_to_first_contact: bool = boolean_option(
|
||||
"Fast forward mission to first contact (WIP)",
|
||||
@@ -491,16 +454,6 @@ class Settings:
|
||||
section=PERFORMANCE_SECTION,
|
||||
default=True,
|
||||
)
|
||||
generate_fire_tasks_for_missile_sites: bool = boolean_option(
|
||||
"Generate fire tasks for missile sites",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
section=PERFORMANCE_SECTION,
|
||||
detail=(
|
||||
"If enabled, missile sites like V2s and Scuds will fire on random targets "
|
||||
"at the start of the mission."
|
||||
),
|
||||
default=True,
|
||||
)
|
||||
perf_moving_units: bool = boolean_option(
|
||||
"Moving ground units",
|
||||
page=MISSION_GENERATOR_PAGE,
|
||||
@@ -581,10 +534,6 @@ class Settings:
|
||||
with settings_path.open(encoding="utf-8") as settings_file:
|
||||
data = yaml.safe_load(settings_file)
|
||||
|
||||
if data is None:
|
||||
logging.warning("Saved settings file %s is empty", settings_path)
|
||||
return
|
||||
|
||||
expected_types = get_type_hints(Settings)
|
||||
for key, value in data.items():
|
||||
if key not in self.__dict__:
|
||||
|
||||
@@ -5,8 +5,7 @@ import random
|
||||
from collections.abc import Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Optional, Sequence, TYPE_CHECKING, Any
|
||||
from uuid import uuid4, UUID
|
||||
from typing import Optional, Sequence, TYPE_CHECKING
|
||||
|
||||
from faker import Faker
|
||||
|
||||
@@ -14,7 +13,6 @@ from game.ato import Flight, FlightType, Package
|
||||
from game.settings import AutoAtoBehavior, Settings
|
||||
from .pilot import Pilot, PilotStatus
|
||||
from ..db.database import Database
|
||||
from ..savecompat import has_save_compat_for
|
||||
from ..utils import meters
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -28,8 +26,6 @@ if TYPE_CHECKING:
|
||||
|
||||
@dataclass
|
||||
class Squadron:
|
||||
id: UUID = field(init=False, default_factory=uuid4)
|
||||
|
||||
name: str
|
||||
nickname: Optional[str]
|
||||
country: str
|
||||
@@ -65,24 +61,21 @@ class Squadron:
|
||||
untasked_aircraft: int = field(init=False, hash=False, compare=False, default=0)
|
||||
pending_deliveries: int = field(init=False, hash=False, compare=False, default=0)
|
||||
|
||||
@has_save_compat_for(7)
|
||||
def __setstate__(self, state: dict[str, Any]) -> None:
|
||||
if "id" not in state:
|
||||
state["id"] = uuid4()
|
||||
self.__dict__.update(state)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.nickname is None:
|
||||
return self.name
|
||||
return f'{self.name} "{self.nickname}"'
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Squadron):
|
||||
return False
|
||||
return self.id == other.id
|
||||
return hash(
|
||||
(
|
||||
self.name,
|
||||
self.nickname,
|
||||
self.country,
|
||||
self.role,
|
||||
self.aircraft,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def player(self) -> bool:
|
||||
|
||||
@@ -4,17 +4,10 @@ from functools import cached_property
|
||||
from typing import Optional, Tuple, Union
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from shapely import geometry
|
||||
from shapely.geometry import MultiPolygon, Polygon
|
||||
|
||||
from dcs.drawing.drawing import LineStyle, Rgba
|
||||
from dcs.drawing.polygon import FreeFormPolygon
|
||||
from dcs.mapping import Point
|
||||
from dcs.mission import Mission
|
||||
from dcs.terrain.terrain import Terrain
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Landmap:
|
||||
@@ -46,94 +39,3 @@ def load_landmap(filename: Path) -> Optional[Landmap]:
|
||||
|
||||
def poly_contains(x: float, y: float, poly: Union[MultiPolygon, Polygon]) -> bool:
|
||||
return poly.contains(geometry.Point(x, y))
|
||||
|
||||
|
||||
def to_miz(landmap: Landmap, terrain: Terrain, mission_filename: str) -> None:
|
||||
"""
|
||||
Writes landmap to .miz file so that zones can be visualized and edited in the
|
||||
mission editor.
|
||||
"""
|
||||
|
||||
def multi_polygon_to_miz(
|
||||
mission: Mission,
|
||||
terrain: Terrain,
|
||||
multi_polygon: MultiPolygon,
|
||||
color: Rgba,
|
||||
prefix: str,
|
||||
layer_index: int = 4,
|
||||
layer_name: str = "Author",
|
||||
) -> None:
|
||||
reference_position = Point(0, 0, terrain)
|
||||
for i in range(len(multi_polygon.geoms)):
|
||||
polygon = multi_polygon.geoms[i]
|
||||
if len(polygon.interiors) > 0:
|
||||
raise ValueError(
|
||||
f"Polygon hole found when trying to export {prefix} {i}. to_miz() does not support landmap zones with holes."
|
||||
)
|
||||
coordinates = polygon.exterior.xy
|
||||
points = []
|
||||
for j in range(len(coordinates[0])):
|
||||
points.append(Point(coordinates[0][j], coordinates[1][j], terrain))
|
||||
polygon_drawing = FreeFormPolygon(
|
||||
visible=True,
|
||||
position=reference_position,
|
||||
name=f"{prefix}-{i}",
|
||||
color=color,
|
||||
layer_name=layer_name,
|
||||
fill=color,
|
||||
line_thickness=10,
|
||||
line_style=LineStyle.Solid,
|
||||
points=points,
|
||||
)
|
||||
mission.drawings.layers[layer_index].objects.append(polygon_drawing)
|
||||
|
||||
mission = Mission(terrain=terrain)
|
||||
multi_polygon_to_miz(
|
||||
mission, terrain, landmap.exclusion_zones, Rgba(255, 0, 0, 128), "Exclusion"
|
||||
)
|
||||
multi_polygon_to_miz(
|
||||
mission, terrain, landmap.sea_zones, Rgba(0, 0, 255, 128), "Sea"
|
||||
)
|
||||
multi_polygon_to_miz(
|
||||
mission, terrain, landmap.inclusion_zones, Rgba(0, 255, 0, 128), "Inclusion"
|
||||
)
|
||||
mission.save(mission_filename)
|
||||
|
||||
|
||||
def from_miz(mission_filename: str, layer_index: int = 4) -> Landmap:
|
||||
"""
|
||||
Generate Landmap object from Free Form Polygons drawn in a .miz file.
|
||||
Landmap.inclusion_zones are expected to be named Inclusion-<suffix>
|
||||
Landmap.exclusion_zones are expected to be named Exclusion-<suffix>
|
||||
Landmap.sea_zones are expected to be named Sea-<suffix>
|
||||
"""
|
||||
mission = Mission()
|
||||
mission.load_file(mission_filename)
|
||||
polygons: dict[str, List[Polygon]] = {"Inclusion": [], "Exclusion": [], "Sea": []}
|
||||
for draw_object in mission.drawings.layers[layer_index].objects:
|
||||
if type(draw_object) != FreeFormPolygon:
|
||||
logging.debug(
|
||||
f"Object {draw_object.name} is not a FreeFormPolygon, ignoring"
|
||||
)
|
||||
continue
|
||||
name_split = draw_object.name.split(
|
||||
"-"
|
||||
) # names are in the format <Inclusion|Exclusion|Sea>-<suffix>
|
||||
zone_type = name_split[0]
|
||||
if len(name_split) != 2 or zone_type not in ("Exclusion", "Sea", "Inclusion"):
|
||||
logging.debug(
|
||||
f"Object name {draw_object.name} does not conform to expected format <Exclusion|Sea|Inclusion>-<suffix>, ignoring"
|
||||
)
|
||||
continue
|
||||
polygon_points = []
|
||||
for point in draw_object.points:
|
||||
polygon_points.append(
|
||||
(point.x + draw_object.position.x, point.y + draw_object.position.y)
|
||||
)
|
||||
polygons[zone_type].append(Polygon(polygon_points))
|
||||
landmap = Landmap(
|
||||
inclusion_zones=MultiPolygon(polygons["Inclusion"]),
|
||||
exclusion_zones=MultiPolygon(polygons["Exclusion"]),
|
||||
sea_zones=MultiPolygon(polygons["Sea"]),
|
||||
)
|
||||
return landmap
|
||||
|
||||
@@ -434,14 +434,6 @@ class MissileSiteGroundObject(TheaterGroundObject):
|
||||
def should_head_to_conflict(self) -> bool:
|
||||
return True
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from game.ato import FlightType
|
||||
|
||||
if not self.is_friendly(for_player):
|
||||
yield FlightType.BAI
|
||||
for mission_type in super().mission_types(for_player):
|
||||
yield mission_type
|
||||
|
||||
|
||||
class CoastalSiteGroundObject(TheaterGroundObject):
|
||||
def __init__(
|
||||
@@ -474,14 +466,6 @@ class CoastalSiteGroundObject(TheaterGroundObject):
|
||||
def should_head_to_conflict(self) -> bool:
|
||||
return True
|
||||
|
||||
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
|
||||
from game.ato import FlightType
|
||||
|
||||
if not self.is_friendly(for_player):
|
||||
yield FlightType.BAI
|
||||
for mission_type in super().mission_types(for_player):
|
||||
yield mission_type
|
||||
|
||||
|
||||
class IadsGroundObject(TheaterGroundObject, ABC):
|
||||
def __init__(
|
||||
|
||||
@@ -14,7 +14,6 @@ from dcs.terrain import (
|
||||
Nevada,
|
||||
Normandy,
|
||||
PersianGulf,
|
||||
Sinai,
|
||||
Syria,
|
||||
TheChannel,
|
||||
)
|
||||
@@ -32,7 +31,6 @@ ALL_TERRAINS = [
|
||||
MarianaIslands(),
|
||||
Nevada(),
|
||||
TheChannel(),
|
||||
Sinai(),
|
||||
Syria(),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TurnState(Enum):
|
||||
WIN = 0
|
||||
LOSS = 1
|
||||
CONTINUE = 2
|
||||
@@ -1,9 +1,9 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
MAJOR_VERSION = 8
|
||||
MINOR_VERSION = 1
|
||||
MICRO_VERSION = 0
|
||||
MAJOR_VERSION = 7
|
||||
MINOR_VERSION = 0
|
||||
MICRO_VERSION = 1
|
||||
VERSION_NUMBER = ".".join(str(v) for v in (MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION))
|
||||
|
||||
|
||||
@@ -175,14 +175,4 @@ VERSION = _build_version_string()
|
||||
#:
|
||||
#: Version 10.7
|
||||
#: * Support for defining squadron sizes.
|
||||
#:
|
||||
#: Version 10.8
|
||||
#: * Support for Normandy 2.
|
||||
#:
|
||||
#: Version 10.9
|
||||
#: * Campaign is compatible with new squadron rules. The default air wing configuration
|
||||
#: has enough parking available at each base when squadrons begin at full strength.
|
||||
#:
|
||||
#: Version 10.10
|
||||
#: * Support for Sinai.
|
||||
CAMPAIGN_FORMAT_VERSION = (10, 10)
|
||||
CAMPAIGN_FORMAT_VERSION = (10, 7)
|
||||
|
||||
@@ -9,14 +9,8 @@ from pydcs_extensions.weapon_injector import inject_weapons
|
||||
|
||||
|
||||
class WeaponsOV10A:
|
||||
LAU_33A = {"clsid": "{LAU-33A}", "name": "LAU-33A", "weight": 155}
|
||||
Mk4_mod_0 = {"clsid": "{MK4_Mod0_OV10}", "name": "Mk4 mod 0", "weight": 612.35}
|
||||
OV10_SMOKE = {"clsid": "{OV10_SMOKE}", "name": "OV10_SMOKE", "weight": 1}
|
||||
OV10_Paratrooper = {
|
||||
"clsid": "OV10_Paratrooper",
|
||||
"name": "OV10_Paratrooper",
|
||||
"weight": 400,
|
||||
}
|
||||
ParaTrooper = {"clsid": "{PARA}", "name": "ParaTrooper", "weight": 80}
|
||||
Fuel_Tank_150_gallons_ = {
|
||||
"clsid": "{150gal}",
|
||||
"name": "Fuel Tank 150 gallons",
|
||||
@@ -53,11 +47,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
1,
|
||||
Weapons.LAU_7_with_AIM_9P_Sidewinder_IR_AAM,
|
||||
)
|
||||
LAU_7_with_AIM_9B_Sidewinder_IR_AAM = (
|
||||
1,
|
||||
Weapons.LAU_7_with_AIM_9B_Sidewinder_IR_AAM,
|
||||
)
|
||||
LAU_33A = (1, Weapons.LAU_33A)
|
||||
|
||||
# ERRR {MK-81}
|
||||
|
||||
@@ -72,7 +61,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
LAU3_HE5 = (2, Weapons.LAU3_HE5)
|
||||
LAU3_HE151 = (2, Weapons.LAU3_HE151)
|
||||
M260_HYDRA = (2, Weapons.M260_HYDRA)
|
||||
M260_HYDRA_WP = (2, Weapons.M260_HYDRA_WP)
|
||||
LAU_10R_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
|
||||
2,
|
||||
Weapons.LAU_10R_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
|
||||
@@ -81,62 +69,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
2,
|
||||
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
|
||||
)
|
||||
LAU_61R_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||
2,
|
||||
Weapons.LAU_61R_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||
)
|
||||
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,
|
||||
)
|
||||
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||
2,
|
||||
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_M156__Wht_Phos = (
|
||||
2,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||
2,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||
2,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||
)
|
||||
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_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_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_WTU_1_B__Practice = (
|
||||
2,
|
||||
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||
)
|
||||
|
||||
# ERRR {MK-81}
|
||||
|
||||
@@ -151,7 +83,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
LAU3_HE5 = (3, Weapons.LAU3_HE5)
|
||||
LAU3_HE151 = (3, Weapons.LAU3_HE151)
|
||||
M260_HYDRA = (3, Weapons.M260_HYDRA)
|
||||
M260_HYDRA_WP = (3, Weapons.M260_HYDRA_WP)
|
||||
LAU_10R_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
|
||||
3,
|
||||
Weapons.LAU_10R_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
|
||||
@@ -160,62 +91,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
3,
|
||||
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
|
||||
)
|
||||
LAU_61R_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||
3,
|
||||
Weapons.LAU_61R_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||
)
|
||||
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||
3,
|
||||
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||
)
|
||||
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||
3,
|
||||
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_M156__Wht_Phos = (
|
||||
3,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||
3,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||
3,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||
)
|
||||
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_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_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_WTU_1_B__Practice = (
|
||||
3,
|
||||
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||
)
|
||||
|
||||
class Pylon4:
|
||||
Fuel_Tank_150_gallons_ = (4, Weapons.Fuel_Tank_150_gallons_)
|
||||
@@ -224,7 +99,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
Mk_82_Snakeye___500lb_GP_Bomb_HD = (4, Weapons.Mk_82_Snakeye___500lb_GP_Bomb_HD)
|
||||
Mk_83___1000lb_GP_Bomb_LD = (4, Weapons.Mk_83___1000lb_GP_Bomb_LD)
|
||||
M117___750lb_GP_Bomb_LD = (4, Weapons.M117___750lb_GP_Bomb_LD)
|
||||
Mk4_mod_0 = (4, Weapons.Mk4_mod_0)
|
||||
|
||||
# ERRR {MK-81}
|
||||
|
||||
@@ -239,7 +113,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
LAU3_HE5 = (5, Weapons.LAU3_HE5)
|
||||
LAU3_HE151 = (5, Weapons.LAU3_HE151)
|
||||
M260_HYDRA = (5, Weapons.M260_HYDRA)
|
||||
M260_HYDRA_WP = (5, Weapons.M260_HYDRA_WP)
|
||||
LAU_10R_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
|
||||
5,
|
||||
Weapons.LAU_10R_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
|
||||
@@ -248,62 +121,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
5,
|
||||
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
|
||||
)
|
||||
LAU_61R_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||
5,
|
||||
Weapons.LAU_61R_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||
)
|
||||
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||
5,
|
||||
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||
)
|
||||
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||
5,
|
||||
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_M156__Wht_Phos = (
|
||||
5,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||
5,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||
5,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||
)
|
||||
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_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_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_WTU_1_B__Practice = (
|
||||
5,
|
||||
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||
)
|
||||
|
||||
# ERRR {MK-81}
|
||||
|
||||
@@ -318,7 +135,6 @@ class Bronco_OV_10A(PlaneType):
|
||||
LAU3_HE5 = (6, Weapons.LAU3_HE5)
|
||||
LAU3_HE151 = (6, Weapons.LAU3_HE151)
|
||||
M260_HYDRA = (6, Weapons.M260_HYDRA)
|
||||
M260_HYDRA_WP = (6, Weapons.M260_HYDRA_WP)
|
||||
LAU_10R_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
|
||||
6,
|
||||
Weapons.LAU_10R_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
|
||||
@@ -327,76 +143,15 @@ class Bronco_OV_10A(PlaneType):
|
||||
6,
|
||||
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
|
||||
)
|
||||
LAU_61R_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||
6,
|
||||
Weapons.LAU_61R_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||
)
|
||||
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||
6,
|
||||
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
|
||||
)
|
||||
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
|
||||
6,
|
||||
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_M156__Wht_Phos = (
|
||||
6,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk1__HE = (
|
||||
6,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk1__HE,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT = (
|
||||
6,
|
||||
Weapons.LAU_68_pod___7_x_2_75_FFAR__UnGd_Rkts_Mk5__HEAT,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
|
||||
6,
|
||||
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 = (
|
||||
6,
|
||||
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M257__Para_Illum = (
|
||||
6,
|
||||
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 = (
|
||||
6,
|
||||
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_M274__Practice_Smk,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk1__Practice = (
|
||||
6,
|
||||
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 = (
|
||||
6,
|
||||
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 = (
|
||||
6,
|
||||
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_Mk61__Practice,
|
||||
)
|
||||
LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice = (
|
||||
6,
|
||||
Weapons.LAU_68_pod___7_x_2_75_Hydra__UnGd_Rkts_WTU_1_B__Practice,
|
||||
)
|
||||
|
||||
class Pylon7:
|
||||
LAU_7_with_AIM_9P_Sidewinder_IR_AAM = (
|
||||
7,
|
||||
Weapons.LAU_7_with_AIM_9P_Sidewinder_IR_AAM,
|
||||
)
|
||||
LAU_7_with_AIM_9B_Sidewinder_IR_AAM = (
|
||||
7,
|
||||
Weapons.LAU_7_with_AIM_9B_Sidewinder_IR_AAM,
|
||||
)
|
||||
LAU_33A = (7, Weapons.LAU_33A)
|
||||
|
||||
class Pylon8:
|
||||
OV10_Paratrooper = (8, Weapons.OV10_Paratrooper)
|
||||
ParaTrooper = (8, Weapons.ParaTrooper)
|
||||
|
||||
class Pylon9:
|
||||
OV10_SMOKE = (9, Weapons.OV10_SMOKE)
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from contextlib import contextmanager
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from game.server import EventStream
|
||||
from game.turnstate import TurnState
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.gameoverdialog import GameOverDialog
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from game import Game
|
||||
from game.sim import GameUpdateEvents
|
||||
|
||||
|
||||
@contextmanager
|
||||
def game_state_modifying_cheat_context(game: Game) -> Iterator[GameUpdateEvents]:
|
||||
with EventStream.event_context() as events:
|
||||
yield events
|
||||
|
||||
state = game.check_win_loss()
|
||||
if state is not TurnState.CONTINUE:
|
||||
dialog = GameOverDialog(won=state is TurnState.WIN)
|
||||
dialog.exec()
|
||||
else:
|
||||
game.initialize_turn(events)
|
||||
GameUpdateSignal.get_instance().updateGame(game)
|
||||
137
qt_ui/main.py
137
qt_ui/main.py
@@ -1,19 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import ntpath
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import yaml
|
||||
from PySide6 import QtWidgets
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtWidgets import QApplication, QCheckBox, QSplashScreen, QDialog
|
||||
from PySide6.QtWidgets import QApplication, QCheckBox, QSplashScreen
|
||||
from dcs.payloads import PayloadDirectories
|
||||
|
||||
from game import Game, VERSION, logging_config, persistence
|
||||
@@ -36,7 +34,6 @@ from qt_ui import (
|
||||
uiconstants,
|
||||
)
|
||||
from qt_ui.uiflags import UiFlags
|
||||
from qt_ui.windows.AirWingConfigurationDialog import AirWingConfigurationDialog
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.QLiberationWindow import QLiberationWindow
|
||||
from qt_ui.windows.preferences.QLiberationFirstStartWindow import (
|
||||
@@ -70,7 +67,7 @@ def on_game_load(game: Game | None) -> None:
|
||||
EventStream.put_nowait(GameUpdateEvents().game_loaded(game))
|
||||
|
||||
|
||||
def run_ui(create_game_params: CreateGameParams | None, ui_flags: UiFlags) -> None:
|
||||
def run_ui(game: Game | None, ui_flags: UiFlags) -> None:
|
||||
os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" # Potential fix for 4K screens
|
||||
QApplication.setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
|
||||
@@ -111,6 +108,8 @@ def run_ui(create_game_params: CreateGameParams | None, ui_flags: UiFlags) -> No
|
||||
uiconstants.load_event_icons()
|
||||
uiconstants.load_aircraft_icons()
|
||||
uiconstants.load_vehicle_icons()
|
||||
uiconstants.load_aircraft_banners()
|
||||
uiconstants.load_vehicle_banners()
|
||||
|
||||
# Show warning if no DCS Installation directory was set
|
||||
if liberation_install.get_dcs_install_directory() == "":
|
||||
@@ -152,11 +151,6 @@ def run_ui(create_game_params: CreateGameParams | None, ui_flags: UiFlags) -> No
|
||||
GameUpdateSignal()
|
||||
GameUpdateSignal.get_instance().game_loaded.connect(on_game_load)
|
||||
|
||||
game: Game | None = None
|
||||
if create_game_params is not None:
|
||||
with logged_duration("New game creation"):
|
||||
game = create_game(create_game_params)
|
||||
|
||||
# Start window
|
||||
window = QLiberationWindow(game, ui_flags)
|
||||
window.showMaximized()
|
||||
@@ -259,12 +253,6 @@ def parse_args() -> argparse.Namespace:
|
||||
"--advanced-iads", action="store_true", help="Enable advanced IADS."
|
||||
)
|
||||
|
||||
new_game.add_argument(
|
||||
"--show-air-wing-config",
|
||||
action="store_true",
|
||||
help="Show the air wing configuration dialog after generating the game.",
|
||||
)
|
||||
|
||||
lint_weapons = subparsers.add_parser("lint-weapons")
|
||||
lint_weapons.add_argument("aircraft", help="Name of the aircraft variant to lint.")
|
||||
|
||||
@@ -273,68 +261,60 @@ def parse_args() -> argparse.Namespace:
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CreateGameParams:
|
||||
campaign_path: Path
|
||||
blue: str
|
||||
red: str
|
||||
supercarrier: bool
|
||||
auto_procurement: bool
|
||||
inverted: bool
|
||||
cheats: bool
|
||||
start_date: datetime
|
||||
restrict_weapons_by_date: bool
|
||||
advanced_iads: bool
|
||||
use_new_squadron_rules: bool
|
||||
show_air_wing_config: bool
|
||||
|
||||
@staticmethod
|
||||
def from_args(args: argparse.Namespace) -> CreateGameParams | None:
|
||||
if args.subcommand != "new-game":
|
||||
return None
|
||||
return CreateGameParams(
|
||||
args.campaign,
|
||||
args.blue,
|
||||
args.red,
|
||||
args.supercarrier,
|
||||
args.auto_procurement,
|
||||
args.inverted,
|
||||
args.cheats,
|
||||
args.date,
|
||||
args.restrict_weapons_by_date,
|
||||
args.advanced_iads,
|
||||
args.use_new_squadron_rules,
|
||||
args.show_air_wing_config,
|
||||
def create_game(
|
||||
campaign_path: Path,
|
||||
blue: str,
|
||||
red: str,
|
||||
supercarrier: bool,
|
||||
auto_procurement: bool,
|
||||
inverted: bool,
|
||||
cheats: bool,
|
||||
start_date: datetime,
|
||||
restrict_weapons_by_date: bool,
|
||||
advanced_iads: bool,
|
||||
use_new_squadron_rules: bool,
|
||||
) -> Game:
|
||||
first_start = liberation_install.init()
|
||||
if first_start:
|
||||
sys.exit(
|
||||
"Cannot generate campaign without configuring DCS Liberation. Start the UI "
|
||||
"for the first run configuration."
|
||||
)
|
||||
|
||||
|
||||
def create_game(params: CreateGameParams) -> Game:
|
||||
campaign = Campaign.from_file(params.campaign_path)
|
||||
theater = campaign.load_theater(params.advanced_iads)
|
||||
# This needs to run before the pydcs payload cache is created, which happens
|
||||
# extremely early. It's not a problem that we inject these paths twice because we'll
|
||||
# get the same answers each time.
|
||||
#
|
||||
# Without this, it is not possible to use next turn (or anything that needs to check
|
||||
# for loadouts) without saving the generated campaign and reloading it the normal
|
||||
# way.
|
||||
inject_custom_payloads(Path(persistence.base_path()))
|
||||
campaign = Campaign.from_file(campaign_path)
|
||||
theater = campaign.load_theater(advanced_iads)
|
||||
faction_loader = Factions.load()
|
||||
lua_plugin_manager = LuaPluginManager.load()
|
||||
lua_plugin_manager.merge_player_settings()
|
||||
generator = GameGenerator(
|
||||
faction_loader.get_by_name(params.blue),
|
||||
faction_loader.get_by_name(params.red),
|
||||
faction_loader.get_by_name(blue),
|
||||
faction_loader.get_by_name(red),
|
||||
theater,
|
||||
campaign.load_air_wing_config(theater),
|
||||
Settings(
|
||||
supercarrier=params.supercarrier,
|
||||
automate_runway_repair=params.auto_procurement,
|
||||
automate_front_line_reinforcements=params.auto_procurement,
|
||||
automate_aircraft_reinforcements=params.auto_procurement,
|
||||
enable_frontline_cheats=params.cheats,
|
||||
enable_base_capture_cheat=params.cheats,
|
||||
restrict_weapons_by_date=params.restrict_weapons_by_date,
|
||||
enable_squadron_aircraft_limits=params.use_new_squadron_rules,
|
||||
supercarrier=supercarrier,
|
||||
automate_runway_repair=auto_procurement,
|
||||
automate_front_line_reinforcements=auto_procurement,
|
||||
automate_aircraft_reinforcements=auto_procurement,
|
||||
enable_frontline_cheats=cheats,
|
||||
enable_base_capture_cheat=cheats,
|
||||
restrict_weapons_by_date=restrict_weapons_by_date,
|
||||
enable_squadron_aircraft_limits=use_new_squadron_rules,
|
||||
),
|
||||
GeneratorSettings(
|
||||
start_date=params.start_date,
|
||||
start_date=start_date,
|
||||
start_time=campaign.recommended_start_time,
|
||||
player_budget=DEFAULT_BUDGET,
|
||||
enemy_budget=DEFAULT_BUDGET,
|
||||
inverted=params.inverted,
|
||||
inverted=inverted,
|
||||
advanced_iads=theater.iads_network.advanced_iads,
|
||||
no_carrier=False,
|
||||
no_lha=False,
|
||||
@@ -354,10 +334,7 @@ def create_game(params: CreateGameParams) -> Game:
|
||||
lua_plugin_manager,
|
||||
)
|
||||
game = generator.generate()
|
||||
if params.show_air_wing_config:
|
||||
if AirWingConfigurationDialog(game, None).exec() == QDialog.DialogCode.Rejected:
|
||||
sys.exit("Aborted air wing configuration")
|
||||
game.begin_turn_0(squadrons_start_full=params.use_new_squadron_rules)
|
||||
game.begin_turn_0(squadrons_start_full=use_new_squadron_rules)
|
||||
return game
|
||||
|
||||
|
||||
@@ -428,6 +405,8 @@ def main():
|
||||
"Installation path contains non-ASCII characters. This is known to cause problems."
|
||||
)
|
||||
|
||||
game: Optional[Game] = None
|
||||
|
||||
args = parse_args()
|
||||
|
||||
# TODO: Flesh out data and then make unconditional.
|
||||
@@ -436,6 +415,21 @@ def main():
|
||||
|
||||
load_mods()
|
||||
|
||||
if args.subcommand == "new-game":
|
||||
with logged_duration("New game creation"):
|
||||
game = create_game(
|
||||
args.campaign,
|
||||
args.blue,
|
||||
args.red,
|
||||
args.supercarrier,
|
||||
args.auto_procurement,
|
||||
args.inverted,
|
||||
args.cheats,
|
||||
args.date,
|
||||
args.restrict_weapons_by_date,
|
||||
args.advanced_iads,
|
||||
args.use_new_squadron_rules,
|
||||
)
|
||||
if args.subcommand == "lint-weapons":
|
||||
lint_weapon_data_for_aircraft(AircraftType.named(args.aircraft))
|
||||
return
|
||||
@@ -444,10 +438,7 @@ def main():
|
||||
return
|
||||
|
||||
with Server().run_in_thread():
|
||||
run_ui(
|
||||
CreateGameParams.from_args(args),
|
||||
UiFlags(args.dev, args.show_sim_speed_controls),
|
||||
)
|
||||
run_ui(game, UiFlags(args.dev, args.show_sim_speed_controls))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -8,12 +8,15 @@ from .liberation_theme import get_theme_icons
|
||||
LABELS_OPTIONS = ["Full", "Abbreviated", "Dot Only", "Neutral Dot", "Off"]
|
||||
SKILL_OPTIONS = ["Average", "Good", "High", "Excellent"]
|
||||
|
||||
AIRCRAFT_BANNERS: Dict[str, QPixmap] = {}
|
||||
AIRCRAFT_ICONS: Dict[str, QPixmap] = {}
|
||||
VEHICLE_BANNERS: Dict[str, QPixmap] = {}
|
||||
VEHICLES_ICONS: Dict[str, QPixmap] = {}
|
||||
ICONS: Dict[str, QPixmap] = {}
|
||||
|
||||
|
||||
def load_icons():
|
||||
|
||||
ICONS["New"] = QPixmap("./resources/ui/misc/" + get_theme_icons() + "/new.png")
|
||||
ICONS["Open"] = QPixmap("./resources/ui/misc/" + get_theme_icons() + "/open.png")
|
||||
ICONS["Save"] = QPixmap("./resources/ui/misc/" + get_theme_icons() + "/save.png")
|
||||
@@ -202,7 +205,6 @@ def load_aircraft_icons():
|
||||
AIRCRAFT_ICONS[f1] = AIRCRAFT_ICONS["Mirage-F1C-200"]
|
||||
AIRCRAFT_ICONS["Mirage-F1M-CE"] = AIRCRAFT_ICONS["Mirage-F1CE"]
|
||||
AIRCRAFT_ICONS["MB-339A"] = AIRCRAFT_ICONS["MB-339A PAN"]
|
||||
AIRCRAFT_ICONS["F-15ESE"] = AIRCRAFT_ICONS["F-15E"]
|
||||
|
||||
|
||||
def load_vehicle_icons():
|
||||
@@ -211,3 +213,25 @@ def load_vehicle_icons():
|
||||
VEHICLES_ICONS[vehicle[:-7]] = QPixmap(
|
||||
os.path.join("./resources/ui/units/vehicles/icons/", vehicle)
|
||||
)
|
||||
|
||||
|
||||
def load_aircraft_banners():
|
||||
for aircraft in os.listdir("./resources/ui/units/aircrafts/banners/"):
|
||||
if aircraft.endswith(".jpg"):
|
||||
AIRCRAFT_BANNERS[aircraft[:-7]] = QPixmap(
|
||||
os.path.join("./resources/ui/units/aircrafts/banners/", aircraft)
|
||||
)
|
||||
variants = ["Mirage-F1CT", "Mirage-F1EE", "Mirage-F1M-EE", "Mirage-F1EQ"]
|
||||
for f1 in variants:
|
||||
AIRCRAFT_BANNERS[f1] = AIRCRAFT_BANNERS["Mirage-F1C-200"]
|
||||
variants = ["Mirage-F1CE", "Mirage-F1M-CE"]
|
||||
for f1 in variants:
|
||||
AIRCRAFT_BANNERS[f1] = AIRCRAFT_BANNERS["Mirage-F1C"]
|
||||
|
||||
|
||||
def load_vehicle_banners():
|
||||
for aircraft in os.listdir("./resources/ui/units/vehicles/banners/"):
|
||||
if aircraft.endswith(".jpg"):
|
||||
VEHICLE_BANNERS[aircraft[:-7]] = QPixmap(
|
||||
os.path.join("./resources/ui/units/vehicles/banners/", aircraft)
|
||||
)
|
||||
|
||||
@@ -25,7 +25,9 @@ from qt_ui.widgets.QFactionsInfos import QFactionsInfos
|
||||
from qt_ui.widgets.QIntelBox import QIntelBox
|
||||
from qt_ui.widgets.clientslots import MaxPlayerCount
|
||||
from qt_ui.widgets.simspeedcontrols import SimSpeedControls
|
||||
from qt_ui.windows.AirWingDialog import AirWingDialog
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.PendingTransfersDialog import PendingTransfersDialog
|
||||
from qt_ui.windows.QWaitingForMissionResultWindow import QWaitingForMissionResultWindow
|
||||
|
||||
|
||||
@@ -67,8 +69,24 @@ class QTopPanel(QFrame):
|
||||
|
||||
self.factionsInfos = QFactionsInfos(self.game)
|
||||
|
||||
self.air_wing = QPushButton("Air Wing")
|
||||
self.air_wing.setDisabled(True)
|
||||
self.air_wing.setProperty("style", "btn-primary")
|
||||
self.air_wing.clicked.connect(self.open_air_wing)
|
||||
|
||||
self.transfers = QPushButton("Transfers")
|
||||
self.transfers.setDisabled(True)
|
||||
self.transfers.setProperty("style", "btn-primary")
|
||||
self.transfers.clicked.connect(self.open_transfers)
|
||||
|
||||
self.intel_box = QIntelBox(self.game)
|
||||
|
||||
self.buttonBox = QGroupBox("Misc")
|
||||
self.buttonBoxLayout = QHBoxLayout()
|
||||
self.buttonBoxLayout.addWidget(self.air_wing)
|
||||
self.buttonBoxLayout.addWidget(self.transfers)
|
||||
self.buttonBox.setLayout(self.buttonBoxLayout)
|
||||
|
||||
self.proceedBox = QGroupBox("Proceed")
|
||||
self.proceedBoxLayout = QHBoxLayout()
|
||||
if ui_flags.show_sim_speed_controls:
|
||||
@@ -84,6 +102,7 @@ class QTopPanel(QFrame):
|
||||
self.layout.addWidget(self.conditionsWidget)
|
||||
self.layout.addWidget(self.budgetBox)
|
||||
self.layout.addWidget(self.intel_box)
|
||||
self.layout.addWidget(self.buttonBox)
|
||||
self.layout.addStretch(1)
|
||||
self.layout.addWidget(self.proceedBox)
|
||||
|
||||
@@ -102,6 +121,9 @@ class QTopPanel(QFrame):
|
||||
if game is None:
|
||||
return
|
||||
|
||||
self.air_wing.setEnabled(True)
|
||||
self.transfers.setEnabled(True)
|
||||
|
||||
self.conditionsWidget.setCurrentTurn(game.turn, game.conditions)
|
||||
|
||||
if game.conditions.weather.clouds:
|
||||
@@ -125,6 +147,14 @@ class QTopPanel(QFrame):
|
||||
else:
|
||||
self.proceedButton.setEnabled(True)
|
||||
|
||||
def open_air_wing(self):
|
||||
self.dialog = AirWingDialog(self.game_model, self.window())
|
||||
self.dialog.show()
|
||||
|
||||
def open_transfers(self):
|
||||
self.dialog = PendingTransfersDialog(self.game_model)
|
||||
self.dialog.show()
|
||||
|
||||
def passTurn(self):
|
||||
with logged_duration("Skipping turn"):
|
||||
self.game.pass_turn(no_action=True)
|
||||
|
||||
@@ -24,8 +24,7 @@ class LiverySelector(QComboBox):
|
||||
for idx, livery in enumerate(
|
||||
squadron.aircraft.dcs_unit_type.iter_liveries_for_country(
|
||||
dcs.countries.get_by_name(squadron.country)
|
||||
),
|
||||
1, # First entry is "Default".
|
||||
)
|
||||
):
|
||||
self.addItem(livery.name, livery)
|
||||
if squadron.livery == livery.id:
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import textwrap
|
||||
from collections import defaultdict
|
||||
from typing import Iterable, Iterator, Optional
|
||||
|
||||
from PySide6.QtCore import (
|
||||
@@ -152,49 +150,6 @@ class SquadronSizeSpinner(QSpinBox):
|
||||
# return size
|
||||
|
||||
|
||||
class AirWingConfigParkingTracker(QWidget):
|
||||
allocation_changed = Signal()
|
||||
|
||||
def __init__(self, game: Game) -> None:
|
||||
super().__init__()
|
||||
self.theater = game.theater
|
||||
self.by_cp: dict[ControlPoint, set[Squadron]] = defaultdict(set)
|
||||
for coalition in game.coalitions:
|
||||
for squadron in coalition.air_wing.iter_squadrons():
|
||||
self.add_squadron(squadron)
|
||||
|
||||
def add_squadron(self, squadron: Squadron) -> None:
|
||||
self.by_cp[squadron.location].add(squadron)
|
||||
self.signal_change()
|
||||
|
||||
def remove_squadron(self, squadron: Squadron) -> None:
|
||||
self.by_cp[squadron.location].remove(squadron)
|
||||
self.signal_change()
|
||||
|
||||
def relocate_squadron(
|
||||
self,
|
||||
squadron: Squadron,
|
||||
prior_location: ControlPoint,
|
||||
new_location: ControlPoint,
|
||||
) -> None:
|
||||
self.by_cp[prior_location].remove(squadron)
|
||||
self.by_cp[new_location].add(squadron)
|
||||
squadron.relocate_to(new_location)
|
||||
self.signal_change()
|
||||
|
||||
def used_parking_at(self, control_point: ControlPoint) -> int:
|
||||
return sum(s.max_size for s in self.by_cp[control_point])
|
||||
|
||||
def iter_overfull(self) -> Iterator[tuple[ControlPoint, int, list[Squadron]]]:
|
||||
for control_point in self.theater.controlpoints:
|
||||
used = self.used_parking_at(control_point)
|
||||
if used > control_point.total_aircraft_parking:
|
||||
yield control_point, used, list(self.by_cp[control_point])
|
||||
|
||||
def signal_change(self) -> None:
|
||||
self.allocation_changed.emit()
|
||||
|
||||
|
||||
class SquadronConfigurationBox(QGroupBox):
|
||||
remove_squadron_signal = Signal(Squadron)
|
||||
|
||||
@@ -203,13 +158,11 @@ class SquadronConfigurationBox(QGroupBox):
|
||||
game: Game,
|
||||
coalition: Coalition,
|
||||
squadron: Squadron,
|
||||
parking_tracker: AirWingConfigParkingTracker,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.game = game
|
||||
self.coalition = coalition
|
||||
self.squadron = squadron
|
||||
self.parking_tracker = parking_tracker
|
||||
|
||||
columns = QHBoxLayout()
|
||||
self.setLayout(columns)
|
||||
@@ -247,7 +200,6 @@ class SquadronConfigurationBox(QGroupBox):
|
||||
left_column.addLayout(size_column)
|
||||
size_column.addWidget(QLabel("Max size:"))
|
||||
self.max_size_selector = SquadronSizeSpinner(self.squadron.max_size, self)
|
||||
self.max_size_selector.valueChanged.connect(self.update_max_size)
|
||||
size_column.addWidget(self.max_size_selector)
|
||||
|
||||
task_column = QVBoxLayout()
|
||||
@@ -262,14 +214,8 @@ class SquadronConfigurationBox(QGroupBox):
|
||||
squadron.location,
|
||||
squadron.aircraft,
|
||||
)
|
||||
self.base_selector.currentIndexChanged.connect(self.relocate_squadron)
|
||||
left_column.addWidget(self.base_selector)
|
||||
|
||||
self.parking_label = QLabel()
|
||||
self.update_parking_label()
|
||||
self.parking_tracker.allocation_changed.connect(self.update_parking_label)
|
||||
left_column.addWidget(self.parking_label)
|
||||
|
||||
if not squadron.player and squadron.aircraft.flyable:
|
||||
player_label = QLabel("Player slots not available for opfor")
|
||||
elif not squadron.aircraft.flyable:
|
||||
@@ -320,26 +266,9 @@ class SquadronConfigurationBox(QGroupBox):
|
||||
self.player_list.setText(
|
||||
"<br />".join(p.name for p in self.claim_players_from_squadron())
|
||||
)
|
||||
self.update_parking_label()
|
||||
finally:
|
||||
self.blockSignals(old_state)
|
||||
|
||||
def update_parking_label(self) -> None:
|
||||
self.parking_label.setText(
|
||||
f"{self.parking_tracker.used_parking_at(self.squadron.location)}/"
|
||||
f"{self.squadron.location.total_aircraft_parking}"
|
||||
)
|
||||
|
||||
def update_max_size(self) -> None:
|
||||
self.squadron.max_size = self.max_size_selector.value()
|
||||
self.parking_tracker.signal_change()
|
||||
|
||||
def relocate_squadron(self) -> None:
|
||||
location = self.base_selector.currentData()
|
||||
self.parking_tracker.relocate_squadron(
|
||||
self.squadron, self.squadron.location, location
|
||||
)
|
||||
|
||||
def remove_from_squadron_config(self) -> None:
|
||||
self.remove_squadron_signal.emit(self.squadron)
|
||||
|
||||
@@ -392,7 +321,6 @@ class SquadronConfigurationBox(QGroupBox):
|
||||
self.squadron = new_squadron
|
||||
self.bind_data()
|
||||
self.mission_types.replace_squadron(self.squadron)
|
||||
self.parking_tracker.signal_change()
|
||||
|
||||
def reset_title(self) -> None:
|
||||
self.setTitle(f"{self.name_edit.text()} - {self.squadron.aircraft}")
|
||||
@@ -433,13 +361,11 @@ class SquadronConfigurationLayout(QVBoxLayout):
|
||||
game: Game,
|
||||
coalition: Coalition,
|
||||
squadrons: list[Squadron],
|
||||
parking_tracker: AirWingConfigParkingTracker,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.game = game
|
||||
self.coalition = coalition
|
||||
self.squadron_configs = []
|
||||
self.parking_tracker = parking_tracker
|
||||
for squadron in squadrons:
|
||||
self.add_squadron(squadron)
|
||||
|
||||
@@ -450,7 +376,6 @@ class SquadronConfigurationLayout(QVBoxLayout):
|
||||
return keep_squadrons
|
||||
|
||||
def remove_squadron(self, squadron: Squadron) -> None:
|
||||
self.parking_tracker.remove_squadron(squadron)
|
||||
for squadron_config in self.squadron_configs:
|
||||
if squadron_config.squadron == squadron:
|
||||
squadron_config.deleteLater()
|
||||
@@ -461,32 +386,23 @@ class SquadronConfigurationLayout(QVBoxLayout):
|
||||
return
|
||||
|
||||
def add_squadron(self, squadron: Squadron) -> None:
|
||||
squadron_config = SquadronConfigurationBox(
|
||||
self.game, self.coalition, squadron, self.parking_tracker
|
||||
)
|
||||
squadron_config = SquadronConfigurationBox(self.game, self.coalition, squadron)
|
||||
squadron_config.remove_squadron_signal.connect(self.remove_squadron)
|
||||
self.squadron_configs.append(squadron_config)
|
||||
self.addWidget(squadron_config)
|
||||
self.parking_tracker.add_squadron(squadron)
|
||||
|
||||
|
||||
class AircraftSquadronsPage(QWidget):
|
||||
remove_squadron_page = Signal(AircraftType)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
game: Game,
|
||||
coalition: Coalition,
|
||||
squadrons: list[Squadron],
|
||||
parking_tracker: AirWingConfigParkingTracker,
|
||||
self, game: Game, coalition: Coalition, squadrons: list[Squadron]
|
||||
) -> None:
|
||||
super().__init__()
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.squadrons_config = SquadronConfigurationLayout(
|
||||
game, coalition, squadrons, parking_tracker
|
||||
)
|
||||
self.squadrons_config = SquadronConfigurationLayout(game, coalition, squadrons)
|
||||
self.squadrons_config.config_changed.connect(self.on_squadron_config_changed)
|
||||
|
||||
scrolling_widget = QWidget()
|
||||
@@ -514,16 +430,10 @@ class AircraftSquadronsPage(QWidget):
|
||||
class AircraftSquadronsPanel(QStackedLayout):
|
||||
page_removed = Signal(AircraftType)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
game: Game,
|
||||
coalition: Coalition,
|
||||
parking_tracker: AirWingConfigParkingTracker,
|
||||
) -> None:
|
||||
def __init__(self, game: Game, coalition: Coalition) -> None:
|
||||
super().__init__()
|
||||
self.game = game
|
||||
self.coalition = coalition
|
||||
self.parking_tracker = parking_tracker
|
||||
self.squadrons_pages: dict[AircraftType, AircraftSquadronsPage] = {}
|
||||
for aircraft, squadrons in self.air_wing.squadrons.items():
|
||||
self.new_page_for_type(aircraft, squadrons)
|
||||
@@ -543,9 +453,7 @@ class AircraftSquadronsPanel(QStackedLayout):
|
||||
def new_page_for_type(
|
||||
self, aircraft_type: AircraftType, squadrons: list[Squadron]
|
||||
) -> None:
|
||||
page = AircraftSquadronsPage(
|
||||
self.game, self.coalition, squadrons, self.parking_tracker
|
||||
)
|
||||
page = AircraftSquadronsPage(self.game, self.coalition, squadrons)
|
||||
page.remove_squadron_page.connect(self.remove_page_for_type)
|
||||
self.addWidget(page)
|
||||
self.squadrons_pages[aircraft_type] = page
|
||||
@@ -631,77 +539,14 @@ class AircraftTypeList(QListView):
|
||||
self.update(self.selectionModel().currentIndex())
|
||||
|
||||
|
||||
def describe_overfull_airbases(
|
||||
overfull: Iterable[tuple[ControlPoint, int, list[Squadron]]]
|
||||
) -> str:
|
||||
string_builder = []
|
||||
for (
|
||||
control_point,
|
||||
used_parking,
|
||||
squadrons,
|
||||
) in overfull:
|
||||
capacity = control_point.total_aircraft_parking
|
||||
base_description = f"{control_point.name} {used_parking}/{capacity}"
|
||||
string_builder.append(f"<p><strong>{base_description}</strong></p>")
|
||||
squadron_descriptions = []
|
||||
for squadron in squadrons:
|
||||
squadron_details = (
|
||||
f"{squadron.aircraft} {squadron.name} {squadron.max_size} aircraft"
|
||||
)
|
||||
squadron_descriptions.append(f"<li>{squadron_details}</li>")
|
||||
string_builder.append(f"<ul>{''.join(squadron_descriptions)}</ul>")
|
||||
|
||||
if not string_builder:
|
||||
string_builder.append("All airbases are within parking limits.")
|
||||
|
||||
return "".join(string_builder)
|
||||
|
||||
|
||||
class OverfullAirbasesDisplay(QGroupBox):
|
||||
def __init__(
|
||||
self,
|
||||
parking_tracker: AirWingConfigParkingTracker,
|
||||
parent: QWidget | None = None,
|
||||
) -> None:
|
||||
super().__init__("Overfull airbases", parent)
|
||||
self.setMaximumHeight(200)
|
||||
|
||||
self.parking_tracker = parking_tracker
|
||||
self.parking_tracker.allocation_changed.connect(self.on_allocation_changed)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self.label = QLabel()
|
||||
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setWidget(self.label)
|
||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
layout.addWidget(scroll)
|
||||
|
||||
self.on_allocation_changed()
|
||||
|
||||
def on_allocation_changed(self) -> None:
|
||||
self.label.setText(
|
||||
describe_overfull_airbases(self.parking_tracker.iter_overfull())
|
||||
)
|
||||
|
||||
|
||||
class AirWingConfigurationTab(QWidget):
|
||||
def __init__(
|
||||
self,
|
||||
coalition: Coalition,
|
||||
game: Game,
|
||||
parking_tracker: AirWingConfigParkingTracker,
|
||||
) -> None:
|
||||
def __init__(self, coalition: Coalition, game: Game) -> None:
|
||||
super().__init__()
|
||||
|
||||
layout = QGridLayout()
|
||||
self.setLayout(layout)
|
||||
self.game = game
|
||||
self.coalition = coalition
|
||||
self.parking_tracker = parking_tracker
|
||||
|
||||
self.type_list = AircraftTypeList(coalition.air_wing)
|
||||
|
||||
@@ -711,9 +556,7 @@ class AirWingConfigurationTab(QWidget):
|
||||
add_button.clicked.connect(lambda state: self.add_squadron())
|
||||
layout.addWidget(add_button, 2, 1, 1, 1)
|
||||
|
||||
self.squadrons_panel = AircraftSquadronsPanel(
|
||||
game, coalition, self.parking_tracker
|
||||
)
|
||||
self.squadrons_panel = AircraftSquadronsPanel(game, coalition)
|
||||
self.squadrons_panel.page_removed.connect(self.type_list.remove_aircraft_type)
|
||||
layout.addLayout(self.squadrons_panel, 1, 3, 2, 1)
|
||||
|
||||
@@ -787,9 +630,6 @@ class AirWingConfigurationDialog(QDialog):
|
||||
|
||||
def __init__(self, game: Game, parent) -> None:
|
||||
super().__init__(parent)
|
||||
self.game = game
|
||||
self.parking_tracker = AirWingConfigParkingTracker(game)
|
||||
|
||||
self.setMinimumSize(1024, 768)
|
||||
self.setWindowTitle(f"Air Wing Configuration")
|
||||
# TODO: self.setWindowIcon()
|
||||
@@ -811,18 +651,11 @@ class AirWingConfigurationDialog(QDialog):
|
||||
|
||||
self.tabs = []
|
||||
for coalition in game.coalitions:
|
||||
coalition_tab = AirWingConfigurationTab(
|
||||
coalition, game, self.parking_tracker
|
||||
)
|
||||
coalition_tab = AirWingConfigurationTab(coalition, game)
|
||||
name = "Blue" if coalition.player else "Red"
|
||||
self.tab_widget.addTab(coalition_tab, name)
|
||||
self.tabs.append(coalition_tab)
|
||||
|
||||
self.overfull_airbases_display = OverfullAirbasesDisplay(
|
||||
self.parking_tracker, self
|
||||
)
|
||||
layout.addWidget(self.overfull_airbases_display)
|
||||
|
||||
buttons_layout = QHBoxLayout()
|
||||
apply_button = QPushButton("Accept Changes && Start Campaign")
|
||||
apply_button.setProperty("style", "btn-accept")
|
||||
@@ -838,29 +671,7 @@ class AirWingConfigurationDialog(QDialog):
|
||||
for tab in self.tabs:
|
||||
tab.revert()
|
||||
|
||||
def can_continue(self) -> bool:
|
||||
if not self.game.settings.enable_squadron_aircraft_limits:
|
||||
return True
|
||||
|
||||
overfull = list(self.parking_tracker.iter_overfull())
|
||||
if not overfull:
|
||||
return True
|
||||
|
||||
description = (
|
||||
"<p>The following airbases are over capacity:</p>"
|
||||
f"{describe_overfull_airbases(overfull)}"
|
||||
)
|
||||
QMessageBox().critical(
|
||||
self,
|
||||
"Cannot continue with overfull bases",
|
||||
description,
|
||||
QMessageBox.Ok,
|
||||
)
|
||||
return False
|
||||
|
||||
def accept(self) -> None:
|
||||
if not self.can_continue():
|
||||
return
|
||||
for tab in self.tabs:
|
||||
tab.apply()
|
||||
super().accept()
|
||||
@@ -868,16 +679,8 @@ class AirWingConfigurationDialog(QDialog):
|
||||
def reject(self) -> None:
|
||||
result = QMessageBox.information(
|
||||
None,
|
||||
"Abort new game?",
|
||||
"<br />".join(
|
||||
textwrap.wrap(
|
||||
"Are you sure you want to cancel air wing configuration and "
|
||||
"return to the new game wizard? If you instead want to revert your "
|
||||
"air wing changes and continue, use the revert and accept buttons "
|
||||
"below.",
|
||||
width=55,
|
||||
)
|
||||
),
|
||||
"Discard changes?",
|
||||
"Are you sure you want to discard your changes and start the campaign?",
|
||||
QMessageBox.Yes,
|
||||
QMessageBox.No,
|
||||
)
|
||||
|
||||
@@ -16,7 +16,6 @@ class GameUpdateSignal(QObject):
|
||||
debriefingReceived = Signal(Debriefing)
|
||||
|
||||
game_loaded = Signal(Game)
|
||||
game_generated = Signal(Game)
|
||||
|
||||
def __init__(self):
|
||||
super(GameUpdateSignal, self).__init__()
|
||||
|
||||
@@ -24,7 +24,6 @@ from game.persistence import SaveManager
|
||||
from game.server import EventStream, GameContext
|
||||
from game.server.dependencies import QtCallbacks, QtContext
|
||||
from game.theater import ControlPoint, MissionTarget, TheaterGroundObject
|
||||
from game.turnstate import TurnState
|
||||
from qt_ui import liberation_install
|
||||
from qt_ui.dialogs import Dialog
|
||||
from qt_ui.models import GameModel
|
||||
@@ -34,13 +33,10 @@ from qt_ui.uncaughtexceptionhandler import UncaughtExceptionHandler
|
||||
from qt_ui.widgets.QTopPanel import QTopPanel
|
||||
from qt_ui.widgets.ato import QAirTaskingOrderPanel
|
||||
from qt_ui.widgets.map.QLiberationMap import QLiberationMap
|
||||
from qt_ui.windows.AirWingDialog import AirWingDialog
|
||||
from qt_ui.windows.BugReportDialog import BugReportDialog
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.PendingTransfersDialog import PendingTransfersDialog
|
||||
from qt_ui.windows.QDebriefingWindow import QDebriefingWindow
|
||||
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
|
||||
from qt_ui.windows.gameoverdialog import GameOverDialog
|
||||
from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu
|
||||
from qt_ui.windows.infos.QInfoPanel import QInfoPanel
|
||||
from qt_ui.windows.logs.QLogsWindow import QLogsWindow
|
||||
@@ -154,7 +150,6 @@ class QLiberationWindow(QMainWindow):
|
||||
def connectSignals(self):
|
||||
GameUpdateSignal.get_instance().gameupdated.connect(self.setGame)
|
||||
GameUpdateSignal.get_instance().debriefingReceived.connect(self.onDebriefing)
|
||||
GameUpdateSignal.get_instance().game_generated.connect(self.onGameGenerated)
|
||||
|
||||
def initActions(self):
|
||||
self.newGameAction = QAction("&New Game", self)
|
||||
@@ -226,12 +221,6 @@ class QLiberationWindow(QMainWindow):
|
||||
self.openNotesAction.setIcon(CONST.ICONS["Notes"])
|
||||
self.openNotesAction.triggered.connect(self.showNotesDialog)
|
||||
|
||||
self.openAirWingAction = QAction("Air Wing", self)
|
||||
self.openAirWingAction.triggered.connect(self.showAirWingDialog)
|
||||
|
||||
self.openTransfersAction = QAction("Transfers", self)
|
||||
self.openTransfersAction.triggered.connect(self.showTransfersDialog)
|
||||
|
||||
self.importTemplatesAction = QAction("Import Layouts", self)
|
||||
self.importTemplatesAction.triggered.connect(self.import_templates)
|
||||
|
||||
@@ -264,8 +253,6 @@ class QLiberationWindow(QMainWindow):
|
||||
self.actions_bar.addAction(self.openSettingsAction)
|
||||
self.actions_bar.addAction(self.openStatsAction)
|
||||
self.actions_bar.addAction(self.openNotesAction)
|
||||
self.actions_bar.addAction(self.openAirWingAction)
|
||||
self.actions_bar.addAction(self.openTransfersAction)
|
||||
|
||||
def initMenuBar(self):
|
||||
self.menu = self.menuBar()
|
||||
@@ -335,6 +322,7 @@ class QLiberationWindow(QMainWindow):
|
||||
def newGame(self):
|
||||
wizard = NewGameWizard(self)
|
||||
wizard.show()
|
||||
wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame))
|
||||
|
||||
def openFile(self):
|
||||
if (
|
||||
@@ -538,14 +526,6 @@ class QLiberationWindow(QMainWindow):
|
||||
self.dialog = QNotesWindow(self.game)
|
||||
self.dialog.show()
|
||||
|
||||
def showAirWingDialog(self) -> None:
|
||||
self.dialog = AirWingDialog(self.game_model, self)
|
||||
self.dialog.show()
|
||||
|
||||
def showTransfersDialog(self) -> None:
|
||||
self.dialog = PendingTransfersDialog(self.game_model)
|
||||
self.dialog.show()
|
||||
|
||||
def import_templates(self):
|
||||
LAYOUTS.import_templates()
|
||||
|
||||
@@ -560,14 +540,7 @@ class QLiberationWindow(QMainWindow):
|
||||
def onDebriefing(self, debrief: Debriefing):
|
||||
logging.info("On Debriefing")
|
||||
self.debriefing = QDebriefingWindow(debrief)
|
||||
self.debriefing.exec()
|
||||
|
||||
state = self.game.check_win_loss()
|
||||
if state is not TurnState.CONTINUE:
|
||||
GameOverDialog(won=state is TurnState.WIN, parent=self).exec()
|
||||
else:
|
||||
self.game.pass_turn()
|
||||
GameUpdateSignal.get_instance().updateGame(self.game)
|
||||
self.debriefing.show()
|
||||
|
||||
def open_tgo_info_dialog(self, tgo: TheaterGroundObject) -> None:
|
||||
QGroundObjectMenu(self, tgo, tgo.control_point, self.game).show()
|
||||
|
||||
@@ -1,48 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QIcon, QPixmap
|
||||
from PySide6.QtGui import QIcon
|
||||
from PySide6.QtWidgets import QDialog, QFrame, QGridLayout, QLabel, QTextBrowser
|
||||
|
||||
from game.dcs.aircrafttype import AircraftType
|
||||
from game.dcs.groundunittype import GroundUnitType
|
||||
from game.dcs.unittype import UnitType
|
||||
from game.game import Game
|
||||
|
||||
AIRCRAFT_BANNERS_BASE = Path("resources/ui/units/aircrafts/banners")
|
||||
VEHICLE_BANNERS_BASE = Path("resources/ui/units/vehicles/banners")
|
||||
MISSING_BANNER_PATH = AIRCRAFT_BANNERS_BASE / "Missing.jpg"
|
||||
|
||||
|
||||
def aircraft_banner_for(unit_type: AircraftType) -> Path:
|
||||
if unit_type.dcs_id in {
|
||||
"Mirage-F1CT",
|
||||
"Mirage-F1EE",
|
||||
"Mirage-F1M-EE",
|
||||
"Mirage-F1EQ",
|
||||
}:
|
||||
name = "Mirage-F1C-200"
|
||||
elif unit_type.dcs_id in {"Mirage-F1CE", "Mirage-F1M-CE"}:
|
||||
name = "Mirage-F1C"
|
||||
elif unit_type.dcs_id == "F-15ESE":
|
||||
name = "F-15E"
|
||||
else:
|
||||
name = unit_type.dcs_id
|
||||
return AIRCRAFT_BANNERS_BASE / f"{name}.jpg"
|
||||
|
||||
|
||||
def vehicle_banner_for(unit_type: GroundUnitType) -> Path:
|
||||
return VEHICLE_BANNERS_BASE / f"{unit_type.dcs_id}.jpg"
|
||||
|
||||
|
||||
def banner_path_for(unit_type: UnitType) -> Path:
|
||||
if isinstance(unit_type, AircraftType):
|
||||
return aircraft_banner_for(unit_type)
|
||||
if isinstance(unit_type, GroundUnitType):
|
||||
return vehicle_banner_for(unit_type)
|
||||
raise NotImplementedError(f"Unhandled UnitType subclass: {unit_type.__class__}")
|
||||
from qt_ui.uiconstants import AIRCRAFT_BANNERS, VEHICLE_BANNERS
|
||||
|
||||
|
||||
class QUnitInfoWindow(QDialog):
|
||||
@@ -63,10 +29,14 @@ class QUnitInfoWindow(QDialog):
|
||||
header = QLabel(self)
|
||||
header.setGeometry(0, 0, 720, 360)
|
||||
|
||||
banner_path = banner_path_for(unit_type)
|
||||
if not banner_path.exists():
|
||||
banner_path = MISSING_BANNER_PATH
|
||||
pixmap = QPixmap(banner_path)
|
||||
pixmap = None
|
||||
|
||||
if isinstance(self.unit_type, AircraftType):
|
||||
pixmap = AIRCRAFT_BANNERS.get(self.unit_type.dcs_id)
|
||||
elif isinstance(self.unit_type, GroundUnitType):
|
||||
pixmap = VEHICLE_BANNERS.get(self.unit_type.dcs_id)
|
||||
if pixmap is None:
|
||||
pixmap = AIRCRAFT_BANNERS.get("Missing")
|
||||
header.setPixmap(pixmap.scaled(header.width(), header.height()))
|
||||
self.layout.addWidget(header, 0, 0)
|
||||
|
||||
|
||||
@@ -220,7 +220,10 @@ class QWaitingForMissionResultWindow(QDialog):
|
||||
def process_debriefing(self):
|
||||
with logged_duration("Turn processing"):
|
||||
self.sim_controller.process_results(self.debriefing)
|
||||
self.game.pass_turn()
|
||||
|
||||
GameUpdateSignal.get_instance().sendDebriefing(self.debriefing)
|
||||
GameUpdateSignal.get_instance().updateGame(self.game)
|
||||
self.accept()
|
||||
|
||||
def closeEvent(self, evt):
|
||||
|
||||
@@ -9,17 +9,19 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from dcs.ships import Stennis, KUZNECOW
|
||||
|
||||
from game import Game
|
||||
from game.ato.flighttype import FlightType
|
||||
from game.config import RUNWAY_REPAIR_COST
|
||||
from game.server import EventStream
|
||||
from game.sim import GameUpdateEvents
|
||||
from game.theater import (
|
||||
AMMO_DEPOT_FRONTLINE_UNIT_CONTRIBUTION,
|
||||
ControlPoint,
|
||||
ControlPointType,
|
||||
FREE_FRONTLINE_UNIT_SUPPLY,
|
||||
)
|
||||
from qt_ui.cheatcontext import game_state_modifying_cheat_context
|
||||
from qt_ui.dialogs import Dialog
|
||||
from qt_ui.models import GameModel
|
||||
from qt_ui.uiconstants import EVENT_ICONS
|
||||
@@ -117,11 +119,13 @@ class QBaseMenu2(QDialog):
|
||||
return self.game_model.game.settings.enable_base_capture_cheat
|
||||
|
||||
def cheat_capture(self) -> None:
|
||||
with game_state_modifying_cheat_context(self.game_model.game) as events:
|
||||
self.cp.capture(
|
||||
self.game_model.game, events, for_player=not self.cp.captured
|
||||
)
|
||||
self.close()
|
||||
events = GameUpdateEvents()
|
||||
self.cp.capture(self.game_model.game, events, for_player=not self.cp.captured)
|
||||
# Reinitialized ground planners and the like. The ATO needs to be reset because
|
||||
# missions planned against the flipped base are no longer valid.
|
||||
self.game_model.game.initialize_turn(events)
|
||||
EventStream.put_nowait(events)
|
||||
GameUpdateSignal.get_instance().updateGame(self.game_model.game)
|
||||
|
||||
@property
|
||||
def has_transfer_destinations(self) -> bool:
|
||||
|
||||
@@ -3,8 +3,10 @@ from collections.abc import Callable
|
||||
from PySide6.QtWidgets import QGroupBox, QLabel, QPushButton, QVBoxLayout
|
||||
|
||||
from game import Game
|
||||
from game.server import EventStream
|
||||
from game.sim.gameupdateevents import GameUpdateEvents
|
||||
from game.theater import ControlPoint
|
||||
from qt_ui.cheatcontext import game_state_modifying_cheat_context
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import (
|
||||
QGroundForcesStrategySelector,
|
||||
)
|
||||
@@ -50,12 +52,15 @@ class QGroundForcesStrategy(QGroupBox):
|
||||
self.setLayout(layout)
|
||||
|
||||
def cheat_alter_front_line(self, enemy_point: ControlPoint, advance: bool) -> None:
|
||||
with game_state_modifying_cheat_context(self.game) as events:
|
||||
amount = 0.2
|
||||
if not advance:
|
||||
amount *= -1
|
||||
self.cp.base.affect_strength(amount)
|
||||
enemy_point.base.affect_strength(-amount)
|
||||
front_line = self.cp.front_line_with(enemy_point)
|
||||
front_line.update_position()
|
||||
events.update_front_line(front_line)
|
||||
amount = 0.2
|
||||
if not advance:
|
||||
amount *= -1
|
||||
self.cp.base.affect_strength(amount)
|
||||
enemy_point.base.affect_strength(-amount)
|
||||
front_line = self.cp.front_line_with(enemy_point)
|
||||
front_line.update_position()
|
||||
events = GameUpdateEvents().update_front_line(front_line)
|
||||
# Clear the ATO to replan missions affected by the front line.
|
||||
self.game.initialize_turn(events)
|
||||
EventStream.put_nowait(events)
|
||||
GameUpdateSignal.get_instance().updateGame(self.game)
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from qt_ui.windows.newgame.QNewGameWizard import NewGameWizard
|
||||
|
||||
|
||||
class GameOverDialog(QDialog):
|
||||
def __init__(self, won: bool, parent: QWidget | None = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setModal(True)
|
||||
self.setWindowTitle("Game Over")
|
||||
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
layout.addWidget(
|
||||
QLabel(
|
||||
f"<strong>You {'won' if won else 'lost'}!</strong><br />"
|
||||
"<br />"
|
||||
"Click below to start a new game."
|
||||
)
|
||||
)
|
||||
button_row = QHBoxLayout()
|
||||
layout.addLayout(button_row)
|
||||
|
||||
button_row.addStretch()
|
||||
|
||||
new_game = QPushButton("New Game")
|
||||
new_game.clicked.connect(self.on_new_game)
|
||||
button_row.addWidget(new_game)
|
||||
|
||||
def on_new_game(self) -> None:
|
||||
wizard = NewGameWizard(self)
|
||||
wizard.show()
|
||||
wizard.accepted.connect(self.accept)
|
||||
@@ -29,7 +29,6 @@ class QEditFlightDialog(QDialog):
|
||||
|
||||
self.setWindowTitle("Edit flight")
|
||||
self.setWindowIcon(EVENT_ICONS["strike"])
|
||||
self.setModal(True)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ from PySide6.QtWidgets import (
|
||||
QFrame,
|
||||
QLabel,
|
||||
QVBoxLayout,
|
||||
QScrollArea,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from game import Game
|
||||
@@ -37,16 +35,6 @@ class QFlightPayloadTab(QFrame):
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
scroll_content = QWidget()
|
||||
scrolling_layout = QVBoxLayout()
|
||||
scroll_content.setLayout(scrolling_layout)
|
||||
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setWidget(scroll_content)
|
||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
layout.addWidget(scroll)
|
||||
|
||||
# Docs Link
|
||||
docsText = QLabel(
|
||||
'<a href="https://github.com/dcs-liberation/dcs_liberation/wiki/Custom-Loadouts"><span style="color:#FFFFFF;">How to create your own default loadout</span></a>'
|
||||
@@ -54,12 +42,12 @@ class QFlightPayloadTab(QFrame):
|
||||
docsText.setAlignment(Qt.AlignCenter)
|
||||
docsText.setOpenExternalLinks(True)
|
||||
|
||||
scrolling_layout.addLayout(PropertyEditor(self.flight))
|
||||
layout.addLayout(PropertyEditor(self.flight))
|
||||
self.loadout_selector = DcsLoadoutSelector(flight)
|
||||
self.loadout_selector.currentIndexChanged.connect(self.on_new_loadout)
|
||||
scrolling_layout.addWidget(self.loadout_selector)
|
||||
scrolling_layout.addWidget(self.payload_editor)
|
||||
scrolling_layout.addWidget(docsText)
|
||||
layout.addWidget(self.loadout_selector)
|
||||
layout.addWidget(self.payload_editor)
|
||||
layout.addWidget(docsText)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from PySide6.QtCore import QTime
|
||||
from PySide6.QtWidgets import (
|
||||
QGroupBox,
|
||||
QLabel,
|
||||
QMessageBox,
|
||||
QVBoxLayout,
|
||||
QTimeEdit,
|
||||
QHBoxLayout,
|
||||
QCheckBox,
|
||||
)
|
||||
from PySide6.QtWidgets import QGroupBox, QLabel, QMessageBox, QVBoxLayout
|
||||
|
||||
from game import Game
|
||||
from game.ato.flight import Flight
|
||||
@@ -20,9 +10,9 @@ from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
||||
from qt_ui.widgets.combos.QArrivalAirfieldSelector import QArrivalAirfieldSelector
|
||||
|
||||
|
||||
class FlightPlanPropertiesGroup(QGroupBox):
|
||||
class FlightAirfieldDisplay(QGroupBox):
|
||||
def __init__(self, game: Game, package_model: PackageModel, flight: Flight) -> None:
|
||||
super().__init__("Flight plan properties")
|
||||
super().__init__("Departure/Arrival")
|
||||
self.game = game
|
||||
self.package_model = package_model
|
||||
self.flight = flight
|
||||
@@ -38,31 +28,6 @@ class FlightPlanPropertiesGroup(QGroupBox):
|
||||
self.package_model.tot_changed.connect(self.update_departure_time)
|
||||
self.update_departure_time()
|
||||
|
||||
tot_offset_layout = QHBoxLayout()
|
||||
layout.addLayout(tot_offset_layout)
|
||||
|
||||
delay = int(self.flight.flight_plan.tot_offset.total_seconds())
|
||||
negative = delay < 0
|
||||
if negative:
|
||||
delay = -delay
|
||||
hours = delay // 3600
|
||||
minutes = delay // 60 % 60
|
||||
seconds = delay % 60
|
||||
|
||||
tot_offset_layout.addWidget(QLabel("TOT Offset (minutes:seconds)"))
|
||||
tot_offset_layout.addStretch()
|
||||
negative_offset_checkbox = QCheckBox("Ahead of package")
|
||||
negative_offset_checkbox.setChecked(negative)
|
||||
negative_offset_checkbox.toggled.connect(self.toggle_negative_offset)
|
||||
tot_offset_layout.addWidget(negative_offset_checkbox)
|
||||
|
||||
self.tot_offset_spinner = QTimeEdit(QTime(hours, minutes, seconds))
|
||||
self.tot_offset_spinner.setMaximumTime(QTime(59, 0))
|
||||
self.tot_offset_spinner.setDisplayFormat("mm:ss")
|
||||
self.tot_offset_spinner.timeChanged.connect(self.set_tot_offset)
|
||||
self.tot_offset_spinner.setToolTip("Flight TOT offset from package TOT")
|
||||
tot_offset_layout.addWidget(self.tot_offset_spinner)
|
||||
|
||||
layout.addWidget(
|
||||
QLabel(
|
||||
"Determined based on the package TOT. Edit the "
|
||||
@@ -93,7 +58,7 @@ class FlightPlanPropertiesGroup(QGroupBox):
|
||||
# is an invalid state for calling anything in TotEstimator.
|
||||
return
|
||||
self.departure_time.setText(
|
||||
f"At {self.flight.flight_plan.startup_time():%H:%M:%S}"
|
||||
f"At {self.flight.flight_plan.startup_time():%H:%M%S}"
|
||||
)
|
||||
|
||||
def set_divert(self, index: int) -> None:
|
||||
@@ -111,13 +76,3 @@ class FlightPlanPropertiesGroup(QGroupBox):
|
||||
QMessageBox.critical(
|
||||
self, "Could not update flight plan", str(ex), QMessageBox.Ok
|
||||
)
|
||||
|
||||
def set_tot_offset(self, offset: QTime) -> None:
|
||||
self.flight.flight_plan.tot_offset = timedelta(
|
||||
hours=offset.hour(), minutes=offset.minute(), seconds=offset.second()
|
||||
)
|
||||
self.update_departure_time()
|
||||
|
||||
def toggle_negative_offset(self) -> None:
|
||||
self.flight.flight_plan.tot_offset = -self.flight.flight_plan.tot_offset
|
||||
self.update_departure_time()
|
||||
@@ -4,15 +4,15 @@ from PySide6.QtWidgets import QFrame, QGridLayout, QVBoxLayout
|
||||
from game import Game
|
||||
from game.ato.flight import Flight
|
||||
from qt_ui.models import PackageModel
|
||||
from qt_ui.windows.mission.flight.settings.FlightPlanPropertiesGroup import (
|
||||
FlightPlanPropertiesGroup,
|
||||
from qt_ui.windows.mission.flight.settings.FlightAirfieldDisplay import (
|
||||
FlightAirfieldDisplay,
|
||||
)
|
||||
from qt_ui.windows.mission.flight.settings.QCustomName import QFlightCustomName
|
||||
from qt_ui.windows.mission.flight.settings.QFlightSlotEditor import QFlightSlotEditor
|
||||
from qt_ui.windows.mission.flight.settings.QFlightStartType import QFlightStartType
|
||||
from qt_ui.windows.mission.flight.settings.QFlightTypeTaskInfo import (
|
||||
QFlightTypeTaskInfo,
|
||||
)
|
||||
from qt_ui.windows.mission.flight.settings.QCustomName import QFlightCustomName
|
||||
|
||||
|
||||
class QGeneralFlightSettingsTab(QFrame):
|
||||
@@ -23,7 +23,7 @@ class QGeneralFlightSettingsTab(QFrame):
|
||||
|
||||
layout = QGridLayout()
|
||||
layout.addWidget(QFlightTypeTaskInfo(flight), 0, 0)
|
||||
layout.addWidget(FlightPlanPropertiesGroup(game, package_model, flight), 1, 0)
|
||||
layout.addWidget(FlightAirfieldDisplay(game, package_model, flight), 1, 0)
|
||||
layout.addWidget(QFlightSlotEditor(package_model, flight, game), 2, 0)
|
||||
layout.addWidget(QFlightStartType(package_model, flight), 3, 0)
|
||||
layout.addWidget(QFlightCustomName(flight), 4, 0)
|
||||
|
||||
@@ -1,35 +1,14 @@
|
||||
from PySide6.QtCore import QItemSelectionModel, QPoint, QModelIndex
|
||||
from PySide6.QtCore import QItemSelectionModel, QPoint
|
||||
from PySide6.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide6.QtWidgets import (
|
||||
QHeaderView,
|
||||
QTableView,
|
||||
QStyledItemDelegate,
|
||||
QDoubleSpinBox,
|
||||
QWidget,
|
||||
QStyleOptionViewItem,
|
||||
)
|
||||
from PySide6.QtWidgets import QHeaderView, QTableView
|
||||
|
||||
from game.ato.flight import Flight
|
||||
from game.ato.flightwaypoint import FlightWaypoint
|
||||
from game.ato.flightwaypointtype import FlightWaypointType
|
||||
from game.ato.package import Package
|
||||
from game.utils import Distance
|
||||
from qt_ui.windows.mission.flight.waypoints.QFlightWaypointItem import QWaypointItem
|
||||
|
||||
|
||||
HEADER_LABELS = ["Name", "Alt (ft)", "Alt Type", "TOT/DEPART"]
|
||||
|
||||
|
||||
class AltitudeEditorDelegate(QStyledItemDelegate):
|
||||
def createEditor(
|
||||
self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex
|
||||
) -> QDoubleSpinBox:
|
||||
editor = QDoubleSpinBox(parent)
|
||||
editor.setMinimum(0)
|
||||
editor.setMaximum(40000)
|
||||
return editor
|
||||
|
||||
|
||||
class QFlightWaypointList(QTableView):
|
||||
def __init__(self, package: Package, flight: Flight):
|
||||
super().__init__()
|
||||
@@ -37,9 +16,8 @@ class QFlightWaypointList(QTableView):
|
||||
self.flight = flight
|
||||
|
||||
self.model = QStandardItemModel(self)
|
||||
self.model.itemChanged.connect(self.on_changed)
|
||||
self.setModel(self.model)
|
||||
self.model.setHorizontalHeaderLabels(HEADER_LABELS)
|
||||
self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])
|
||||
|
||||
header = self.horizontalHeader()
|
||||
header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
||||
@@ -49,52 +27,27 @@ class QFlightWaypointList(QTableView):
|
||||
self.indexAt(QPoint(1, 1)), QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
self.altitude_editor_delegate = AltitudeEditorDelegate(self)
|
||||
self.setItemDelegateForColumn(1, self.altitude_editor_delegate)
|
||||
def update_list(self):
|
||||
# We need to keep just the row and rebuild the index later because the
|
||||
# QModelIndex will not be valid after the model is cleared.
|
||||
current_index = self.currentIndex().row()
|
||||
self.model.clear()
|
||||
|
||||
def update_list(self) -> None:
|
||||
# ignore signals when updating list so on_changed does not fire
|
||||
self.model.blockSignals(True)
|
||||
try:
|
||||
# We need to keep just the row and rebuild the index later because the
|
||||
# QModelIndex will not be valid after the model is cleared.
|
||||
current_index = self.currentIndex().row()
|
||||
self.model.clear()
|
||||
self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])
|
||||
|
||||
self.model.setHorizontalHeaderLabels(HEADER_LABELS)
|
||||
waypoints = self.flight.flight_plan.waypoints
|
||||
for row, waypoint in enumerate(waypoints):
|
||||
self.add_waypoint_row(row, self.flight, waypoint)
|
||||
self.selectionModel().setCurrentIndex(
|
||||
self.model.index(current_index, 0), QItemSelectionModel.Select
|
||||
)
|
||||
self.resizeColumnsToContents()
|
||||
total_column_width = self.verticalHeader().width() + self.lineWidth()
|
||||
for i in range(0, self.model.columnCount()):
|
||||
total_column_width += self.columnWidth(i) + self.lineWidth()
|
||||
self.setFixedWidth(total_column_width)
|
||||
|
||||
waypoints = self.flight.flight_plan.waypoints
|
||||
# Why [1:]? Qt starts indexing at 1 rather than 0, whereas DCS numbers
|
||||
# waypoints starting with 0, and for whatever reason Qt crashes whenever I
|
||||
# set the vertical labels manually.
|
||||
#
|
||||
# Starting with the second waypoint is a bit of a hack, but it's also the
|
||||
# historical behavior anyway. This view used to have waypoints starting at 1
|
||||
# and just didn't show the departure waypoint because the departure waypoint
|
||||
# wasn't actually part of the flight plan tracked by Liberation. That
|
||||
# changed at some point, so now we need to skip it manually to preserve that
|
||||
# behavior.
|
||||
#
|
||||
# It really ought to show the departure waypoint and start indexing at 0,
|
||||
# but since this all pending a move to React anyway, it's not worth fighting
|
||||
# the Qt crashes for now.
|
||||
#
|
||||
# https://github.com/dcs-liberation/dcs_liberation/issues/3037
|
||||
for row, waypoint in enumerate(waypoints[1:]):
|
||||
self._add_waypoint_row(row, self.flight, waypoint)
|
||||
self.selectionModel().setCurrentIndex(
|
||||
self.model.index(current_index, 0), QItemSelectionModel.Select
|
||||
)
|
||||
self.resizeColumnsToContents()
|
||||
total_column_width = self.verticalHeader().width() + self.lineWidth()
|
||||
for i in range(0, self.model.columnCount()):
|
||||
total_column_width += self.columnWidth(i) + self.lineWidth()
|
||||
self.setFixedWidth(total_column_width)
|
||||
finally:
|
||||
# stop ignoring signals
|
||||
self.model.blockSignals(False)
|
||||
|
||||
def _add_waypoint_row(
|
||||
def add_waypoint_row(
|
||||
self, row: int, flight: Flight, waypoint: FlightWaypoint
|
||||
) -> None:
|
||||
self.model.insertRow(self.model.rowCount())
|
||||
@@ -102,25 +55,15 @@ class QFlightWaypointList(QTableView):
|
||||
self.model.setItem(row, 0, QWaypointItem(waypoint, row))
|
||||
|
||||
altitude = int(waypoint.alt.feet)
|
||||
altitude_item = QStandardItem(f"{altitude}")
|
||||
altitude_item.setEditable(True)
|
||||
self.model.setItem(row, 1, altitude_item)
|
||||
|
||||
altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL"
|
||||
altitude_type_item = QStandardItem(f"{altitude_type}")
|
||||
altitude_type_item.setEditable(False)
|
||||
self.model.setItem(row, 2, altitude_type_item)
|
||||
altitude_item = QStandardItem(f"{altitude} ft {altitude_type}")
|
||||
altitude_item.setEditable(False)
|
||||
self.model.setItem(row, 1, altitude_item)
|
||||
|
||||
tot = self.tot_text(flight, waypoint)
|
||||
tot_item = QStandardItem(tot)
|
||||
tot_item.setEditable(False)
|
||||
self.model.setItem(row, 3, tot_item)
|
||||
|
||||
def on_changed(self) -> None:
|
||||
for i in range(self.model.rowCount()):
|
||||
altitude = self.model.item(i, 1).text()
|
||||
altitude_feet = float(altitude)
|
||||
self.flight.flight_plan.waypoints[i].alt = Distance.from_feet(altitude_feet)
|
||||
self.model.setItem(row, 2, tot_item)
|
||||
|
||||
def tot_text(self, flight: Flight, waypoint: FlightWaypoint) -> str:
|
||||
if waypoint.waypoint_type == FlightWaypointType.TAKEOFF:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import textwrap
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
|
||||
@@ -14,7 +13,6 @@ from PySide6.QtWidgets import (
|
||||
QTextEdit,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
QDialog,
|
||||
)
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
|
||||
@@ -28,7 +26,6 @@ from game.theater.start_generator import GameGenerator, GeneratorSettings, ModSe
|
||||
from qt_ui.widgets.QLiberationCalendar import QLiberationCalendar
|
||||
from qt_ui.widgets.spinsliders import CurrencySpinner, FloatSpinSlider, TimeInputs
|
||||
from qt_ui.windows.AirWingConfigurationDialog import AirWingConfigurationDialog
|
||||
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.windows.newgame.QCampaignList import QCampaignList
|
||||
|
||||
jinja_env = Environment(
|
||||
@@ -42,6 +39,7 @@ jinja_env = Environment(
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
|
||||
|
||||
"""
|
||||
Possible time periods for new games
|
||||
|
||||
@@ -88,14 +86,9 @@ TIME_PERIODS = {
|
||||
}
|
||||
|
||||
|
||||
def wrap_label_text(text: str, width: int = 100) -> str:
|
||||
return "<br />".join(textwrap.wrap(text, width=width))
|
||||
|
||||
|
||||
class NewGameWizard(QtWidgets.QWizard):
|
||||
def __init__(self, parent=None):
|
||||
super(NewGameWizard, self).__init__(parent)
|
||||
self.setModal(True)
|
||||
|
||||
# The wizard should probably be refactored to edit this directly, but for now we
|
||||
# just create a Settings object so that we can load the player's preserved
|
||||
@@ -121,9 +114,7 @@ class NewGameWizard(QtWidgets.QWizard):
|
||||
self.addPage(self.theater_page)
|
||||
self.addPage(self.faction_selection_page)
|
||||
self.addPage(GeneratorOptions(default_settings, mod_settings))
|
||||
self.difficulty_page = DifficultyAndAutomationOptions(
|
||||
default_settings, self.theater_page.campaignList.selected_campaign
|
||||
)
|
||||
self.difficulty_page = DifficultyAndAutomationOptions(default_settings)
|
||||
self.plugins_page = PluginsPage(self.lua_plugin_manager)
|
||||
|
||||
# Update difficulty page on campaign select
|
||||
@@ -141,6 +132,7 @@ class NewGameWizard(QtWidgets.QWizard):
|
||||
self.setWizardStyle(QtWidgets.QWizard.ModernStyle)
|
||||
|
||||
self.setWindowTitle("New Game")
|
||||
self.generatedGame = None
|
||||
|
||||
def accept(self):
|
||||
logging.info("New Game Wizard accept")
|
||||
@@ -229,14 +221,11 @@ class NewGameWizard(QtWidgets.QWizard):
|
||||
mod_settings,
|
||||
self.lua_plugin_manager,
|
||||
)
|
||||
game = generator.generate()
|
||||
self.generatedGame = generator.generate()
|
||||
|
||||
if AirWingConfigurationDialog(game, self).exec() == QDialog.DialogCode.Rejected:
|
||||
logging.info("Aborted air wing configuration")
|
||||
return
|
||||
AirWingConfigurationDialog(self.generatedGame, self).exec_()
|
||||
|
||||
game.begin_turn_0(squadrons_start_full=use_new_squadron_rules)
|
||||
GameUpdateSignal.get_instance().game_generated.emit(game)
|
||||
self.generatedGame.begin_turn_0(squadrons_start_full=use_new_squadron_rules)
|
||||
|
||||
super(NewGameWizard, self).accept()
|
||||
|
||||
@@ -578,39 +567,8 @@ class BudgetInputs(QtWidgets.QGridLayout):
|
||||
self.addWidget(self.starting_money, 1, 1)
|
||||
|
||||
|
||||
class NewSquadronRulesWarning(QLabel):
|
||||
def __init__(
|
||||
self, campaign: Campaign | None, parent: QWidget | None = None
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.set_campaign(campaign)
|
||||
|
||||
def set_campaign(self, campaign: Campaign | None) -> None:
|
||||
if campaign is None:
|
||||
self.setText("No campaign selected")
|
||||
return
|
||||
if campaign.version >= (10, 9):
|
||||
text = f"{campaign.name} is compatible with the new squadron rules."
|
||||
elif campaign.version >= (10, 7):
|
||||
text = (
|
||||
f"{campaign.name} has been updated since the new squadron rules were "
|
||||
"introduced, but support for those rules was still optional. You may "
|
||||
"need to remove, resize, or relocate squadrons before beginning the "
|
||||
"game."
|
||||
)
|
||||
else:
|
||||
text = (
|
||||
f"{campaign.name} has not been updated since the new squadron rules. "
|
||||
"Were introduced. You may need to remove, resize, or relocate "
|
||||
"squadrons before beginning the game."
|
||||
)
|
||||
self.setText(wrap_label_text(text))
|
||||
|
||||
|
||||
class DifficultyAndAutomationOptions(QtWidgets.QWizardPage):
|
||||
def __init__(
|
||||
self, default_settings: Settings, current_campaign: Campaign | None, parent=None
|
||||
) -> None:
|
||||
def __init__(self, default_settings: Settings, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self.setTitle("Difficulty and automation options")
|
||||
@@ -651,15 +609,10 @@ class DifficultyAndAutomationOptions(QtWidgets.QWizardPage):
|
||||
new_squadron_rules.setChecked(default_settings.enable_squadron_aircraft_limits)
|
||||
self.registerField("use_new_squadron_rules", new_squadron_rules)
|
||||
economy_layout.addWidget(new_squadron_rules)
|
||||
self.new_squadron_rules_warning = NewSquadronRulesWarning(current_campaign)
|
||||
economy_layout.addWidget(self.new_squadron_rules_warning)
|
||||
economy_layout.addWidget(
|
||||
QLabel(
|
||||
wrap_label_text(
|
||||
"With new squadron rules enabled, squadrons will not be able to "
|
||||
"exceed a maximum number of aircraft (configurable), and the "
|
||||
"campaign will begin with all squadrons at full strength."
|
||||
)
|
||||
"With new squadron rules enabled, squadrons will not be able to exceed a maximum number of aircraft "
|
||||
"(configurable), and the campaign will begin with all squadrons at full strength."
|
||||
)
|
||||
)
|
||||
|
||||
@@ -697,7 +650,6 @@ class DifficultyAndAutomationOptions(QtWidgets.QWizardPage):
|
||||
self.enemy_income.spinner.setValue(
|
||||
int(campaign.recommended_enemy_income_multiplier * 10)
|
||||
)
|
||||
self.new_squadron_rules_warning.set_campaign(campaign)
|
||||
|
||||
|
||||
class PluginOptionCheckbox(QCheckBox):
|
||||
|
||||
@@ -32,8 +32,8 @@ platformdirs==2.6.2
|
||||
pluggy==1.0.0
|
||||
pre-commit==2.21.0
|
||||
pydantic==1.10.7
|
||||
git+https://github.com/pydcs/dcs@e006f0df6db933fa34b2d5cb04db41653537503e#egg=pydcs
|
||||
pyinstaller==5.12.0
|
||||
git+https://github.com/pydcs/dcs@8fdeda106ba7e847a5d0a1ed358a1463636b513d#egg=pydcs
|
||||
pyinstaller==5.7.0
|
||||
pyinstaller-hooks-contrib==2022.14
|
||||
pyproj==3.4.1
|
||||
PySide6==6.4.1
|
||||
|
||||
@@ -9,7 +9,7 @@ description:
|
||||
pushing south.</p>
|
||||
miz: battle_of_abu_dhabi.miz
|
||||
performance: 2
|
||||
version: "10.9"
|
||||
version: "10.2"
|
||||
squadrons:
|
||||
# Blue CPs:
|
||||
# The default faction is Iran, but the F-14B is given higher precedence so
|
||||
@@ -41,20 +41,16 @@ squadrons:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F-4E Phantom II
|
||||
- primary: AEW&C
|
||||
size: 2
|
||||
aircraft:
|
||||
- E-3A
|
||||
- primary: Refueling
|
||||
size: 2
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
- primary: Transport
|
||||
size: 4
|
||||
aircraft:
|
||||
- C-17A
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
size: 4
|
||||
aircraft:
|
||||
- B-1B Lancer
|
||||
- Su-24MK Fencer-D
|
||||
@@ -76,7 +72,6 @@ squadrons:
|
||||
- Su-25 Frogfoot
|
||||
- primary: BAI
|
||||
secondary: air-to-ground
|
||||
size: 8
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- Su-24MK Fencer-D
|
||||
@@ -107,7 +102,6 @@ squadrons:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- F-14A Tomcat (Block 135-GR Late)
|
||||
- primary: Refueling
|
||||
size: 2
|
||||
aircraft:
|
||||
- S-3B Tanker
|
||||
|
||||
@@ -117,7 +111,6 @@ squadrons:
|
||||
aircraft:
|
||||
- AV-8B Harrier II Night Attack
|
||||
- primary: CAS
|
||||
size: 8
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
|
||||
@@ -9,7 +9,7 @@ recommended_enemy_faction: Russia 2010
|
||||
recommended_start_date: 2004-01-07
|
||||
miz: black_sea.miz
|
||||
performance: 2
|
||||
version: "10.9"
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
# Anapa-Vityazevo
|
||||
12:
|
||||
@@ -148,7 +148,6 @@ squadrons:
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
size: 8
|
||||
Red CV:
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
@@ -165,4 +164,3 @@ squadrons:
|
||||
secondary: air-to-ground
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
size: 8
|
||||
|
||||
@@ -12,7 +12,7 @@ recommended_enemy_faction: Germany 1944
|
||||
recommended_start_date: 1944-07-04
|
||||
miz: caen_to_evreux.miz
|
||||
performance: 1
|
||||
version: "10.9"
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
# Evreux
|
||||
26:
|
||||
|
||||
Binary file not shown.
@@ -1,133 +0,0 @@
|
||||
---
|
||||
name: Sinai - Exercise Bright Star
|
||||
theater: Sinai
|
||||
authors: Starfire
|
||||
recommended_player_faction: Bluefor Modern
|
||||
recommended_enemy_faction: Egypt 2000s
|
||||
description: <p>For over 4 decades, the United States and Egypt have run a series of biannual joint military exercises called Bright Star. Over the years, the number of participating countries has grown substantially. Exercise Bright Star 2025 boasts 8 participant nations and 14 observer nations. The United States and a portion of the exercise coalition will play the part of a fictional hostile nation dubbed Orangeland, staging a mock invasion against Cairo. Israel, having for the first time accepted the invitation to observe, is hosting the aggressor faction of the exercise coalition at its airfields.</p>
|
||||
miz: exercise_bright_star.miz
|
||||
performance: 1
|
||||
recommended_start_date: 2025-09-01
|
||||
version: "10.9"
|
||||
squadrons:
|
||||
# Hatzerim (141)
|
||||
7:
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
size: 24
|
||||
- primary: TARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
size: 20
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- F-15E Strike Eagle
|
||||
size: 12
|
||||
- primary: DEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 20
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
aircraft:
|
||||
- JF-17 Thunder
|
||||
size: 16
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Mirage 2000C
|
||||
size: 12
|
||||
# Kedem
|
||||
12:
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- CH-47D
|
||||
size: 20
|
||||
- primary: Air Assault
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- UH-1H Iroquois
|
||||
size: 4
|
||||
# Nevatim (106)
|
||||
8:
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-3A
|
||||
size: 2
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker
|
||||
size: 1
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- KC-135 Stratotanker MPRS
|
||||
size: 1
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- A-10C Thunderbolt II (Suite 7)
|
||||
size: 8
|
||||
# Melez (30)
|
||||
5:
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Ka-50 Hokum (Blackshark 3)
|
||||
size: 4
|
||||
- primary: TARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- Mirage 2000C
|
||||
size: 12
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Mirage 2000C
|
||||
size: 12
|
||||
# Wadi al Jandali (72)
|
||||
13:
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2C Hawkeye
|
||||
size: 2
|
||||
- primary: SEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 20
|
||||
- primary: DEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 20
|
||||
- primary: Air Assault
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Mi-24P Hind-F
|
||||
size: 4
|
||||
- primary: OCA/Aircraft
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- SA 342L Gazelle
|
||||
size: 4
|
||||
# Cairo West (95)
|
||||
18:
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- C-130
|
||||
size: 8
|
||||
- primary: Escort
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- MiG-29S Fulcrum-C
|
||||
size: 20
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- J-7B
|
||||
size: 20
|
||||
@@ -8,14 +8,14 @@ description: <p>Welcome to Vegas Nerve, an asymmetrical Red Flag Exercise scenar
|
||||
miz: exercise_vegas_nerve.miz
|
||||
performance: 1
|
||||
recommended_start_date: 2011-02-24
|
||||
version: "10.9"
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
# Tonopah Airport
|
||||
17:
|
||||
- primary: TARCAP
|
||||
secondary: any
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-15C Eagle
|
||||
size: 12
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
|
||||
Binary file not shown.
@@ -1,175 +0,0 @@
|
||||
---
|
||||
name: Normandy - The Final Countdown II
|
||||
theater: Normandy
|
||||
authors: Starfire
|
||||
recommended_player_faction:
|
||||
country: Combined Joint Task Forces Blue
|
||||
name: D-Day Allied Forces 1944 and 1990
|
||||
authors: Starfire
|
||||
description:
|
||||
<p>Faction for Final Countdown II</p>
|
||||
locales:
|
||||
- en_US
|
||||
aircrafts:
|
||||
- Boston Mk.III
|
||||
- Fortress Mk.III
|
||||
- Mustang Mk.IV (Late)
|
||||
- Spitfire LF Mk IX
|
||||
- Thunderbolt Mk.II (Late)
|
||||
- MosquitoFBMkVI
|
||||
- F-14B Tomcat
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- SH-60B Seahawk
|
||||
awacs:
|
||||
- E-2C Hawkeye
|
||||
tankers:
|
||||
- S-3B Tanker
|
||||
frontline_units:
|
||||
- A17 Light Tank Mk VII Tetrarch
|
||||
- A22 Infantry Tank MK IV Churchill VII
|
||||
- A27L Cruiser Tank MK VIII Centaur IV
|
||||
- A27M Cruiser Tank MK VIII Cromwell IV
|
||||
- Daimler Armoured Car Mk I
|
||||
- M2A1 Half-Track
|
||||
- QF 40 mm Mark III
|
||||
- Sherman Firefly VC
|
||||
- Sherman III
|
||||
artillery_units:
|
||||
- M12 Gun Motor Carriage
|
||||
logistics_units:
|
||||
- Truck Bedford
|
||||
- Truck GMC "Jimmy" 6x6 Truck
|
||||
infantry_units:
|
||||
- Infantry M1 Garand
|
||||
naval_units:
|
||||
- DDG Arleigh Burke IIa
|
||||
- CG Ticonderoga
|
||||
- CVN-74 John C. Stennis
|
||||
missiles: []
|
||||
air_defense_units:
|
||||
- Bofors 40 mm Gun
|
||||
preset_groups:
|
||||
- Ally Flak
|
||||
requirements:
|
||||
WW2 Asset Pack: https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/
|
||||
carrier_names:
|
||||
- CVN-71 Theodore Roosevelt
|
||||
has_jtac: true
|
||||
jtac_unit: MQ-9 Reaper
|
||||
unrestricted_satnav: true
|
||||
doctrine: ww2
|
||||
building_set: ww2ally
|
||||
recommended_enemy_faction: Germany 1944
|
||||
description:
|
||||
<p>While enroute to the Persian Gulf for Operation Desert Shield, the USS Theodore Roosevelt and its carrier strike group are engufled by an electrical vortex and transported through time and space to the English channel on the morning of the Normandy Landings - June 6th 1944. Seeking to reduce the cost in lives to the Allied Forces about to storm the beaches, the captain of the Roosevelt has elected to provide air support for the landings.</p><p><strong>Note:</strong> This campaign has a custom faction that combines modern US naval forces with WW2 Allied forces. To play it as intended, you should carefully ration your use of modern aircraft and not replenish them if shot down (as you cannot get new Tomcats and Hornets in 1944). You can also choose to play it as a purely WW2 campaign by switching to one of the WW2 Ally factions.</p>
|
||||
miz: final_countdown_2.miz
|
||||
performance: 2
|
||||
recommended_start_date: 1944-06-06
|
||||
version: "10.9"
|
||||
squadrons:
|
||||
#Blue CV (90)
|
||||
Blue-CV:
|
||||
- primary: TARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-14B Tomcat
|
||||
size: 24
|
||||
- primary: DEAD
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
size: 24
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- E-2C Hawkeye
|
||||
size: 2
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- S-3B Tanker
|
||||
size: 2
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
aircraft:
|
||||
- SH-60B Seahawk
|
||||
size: 4
|
||||
#Stoney Cross (39)
|
||||
58:
|
||||
- primary: OCA/Runway
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- A-20G Havoc
|
||||
- Boston Mk.III
|
||||
size: 20
|
||||
#Needs Oar Point (55)
|
||||
28:
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Spitfire LF Mk IX
|
||||
size: 20
|
||||
- primary: DEAD
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- MosquitoFBMkVI
|
||||
size: 20
|
||||
#RAF Grafton Underwood (1000)
|
||||
From RAF Grafton Underwood:
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- B-17G Flying Fortress
|
||||
- Fortress Mk.III
|
||||
size: 20
|
||||
#Lymington (56)
|
||||
37:
|
||||
- primary: Escort
|
||||
secondary: any
|
||||
aircraft:
|
||||
- P-51D-30-NA Mustang
|
||||
- Mustang Mk.IV (Late)
|
||||
size: 20
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
aircraft:
|
||||
- P-47D-40 Thunderbolt
|
||||
- Thunderbolt Mk.II (Late)
|
||||
size: 20
|
||||
|
||||
#Carpiquet (47)
|
||||
19:
|
||||
- primary: TARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- Fw 190 D-9 Dora
|
||||
size: 12
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Ju 88 A-4
|
||||
size: 8
|
||||
#Broglie (32)
|
||||
68:
|
||||
- primary: Escort
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Bf 109 K-4 Kurfürst
|
||||
size: 24
|
||||
#Saint-Andre-de-lEure (30)
|
||||
70:
|
||||
- primary: BAI
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Ju 88 A-4
|
||||
size: 12
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Ju 88 A-4
|
||||
size: 12
|
||||
#Vilacoublay (76)
|
||||
42:
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- Fw 190 A-8 Anton
|
||||
size: 20
|
||||
@@ -7,7 +7,7 @@ recommended_enemy_faction: Syria 2011
|
||||
description: <p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.<br/><br/>This scenario is designed to be performance and helicopter friendly.</p>
|
||||
miz: golan_heights_lite.miz
|
||||
performance: 1
|
||||
version: "10.9"
|
||||
version: "10.7"
|
||||
advanced_iads: true # Campaign has connection_nodes / power_sources / command_centers
|
||||
iads_config:
|
||||
- LHA-1 Tarawa # A Naval Group without connections but still participating as EWR
|
||||
|
||||
@@ -16,7 +16,7 @@ description:
|
||||
miz: grabthars_hammer.miz
|
||||
performance: 2
|
||||
recommended_start_date: 1999-12-25
|
||||
version: "10.9"
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
#Mount Pleasant
|
||||
2:
|
||||
@@ -42,9 +42,9 @@ squadrons:
|
||||
#San Julian
|
||||
11:
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-15E Strike Eagle
|
||||
size: 8
|
||||
- primary: Air Assault
|
||||
secondary: any
|
||||
|
||||
@@ -14,7 +14,7 @@ description:
|
||||
fighting to the west, a USN battle group is dispatched from the east coast of
|
||||
the US to clear the Chinese forces from the continent and crush their carrier
|
||||
group.</p>
|
||||
version: "10.9"
|
||||
version: "10.4"
|
||||
recommended_player_faction: US Navy 2005
|
||||
recommended_enemy_faction: China 2010
|
||||
miz: gran_polvorin.miz
|
||||
@@ -111,6 +111,10 @@ squadrons:
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- H-6J Badger
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 2
|
||||
# Rio Gallegos
|
||||
7:
|
||||
- primary: BARCAP
|
||||
|
||||
@@ -176,7 +176,7 @@ description:
|
||||
northern border. With the arrival of a US carrier group, Israel prepares its
|
||||
counterattack. The US Navy will handle the Beirut region's coastal arena,
|
||||
while the IAF will push through Damascus and the inland mountain ranges.</p>
|
||||
version: "10.9"
|
||||
version: "10.6"
|
||||
miz: operation_allied_sword.miz
|
||||
performance: 2
|
||||
recommended_start_date: 2004-07-17
|
||||
@@ -325,6 +325,10 @@ squadrons:
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Su-24M Fencer-D
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 2
|
||||
# Bassel Al-Assad
|
||||
21:
|
||||
- primary: TARCAP
|
||||
@@ -338,15 +342,15 @@ squadrons:
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 1
|
||||
size: 2
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- IL-76MD
|
||||
size: 1
|
||||
size: 2
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- A-50
|
||||
size: 1
|
||||
size: 2
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
@@ -395,4 +399,4 @@ squadrons:
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Mi-8MTV2 Hip
|
||||
size: 2
|
||||
size: 4
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Syria - Operation Blackball
|
||||
theater: Syria
|
||||
authors: Fuzzle
|
||||
description: <p>A lightweight fictional showcase of Cyprus for the Syria terrain. A US Navy force must deploy from a carrier group to push through the island. <strong>This is a purely naval campaign, meaning you will need to use the Air Assault mission type with transports to take the first FOB. Ensure you soften it up enough first!</strong></p><p><strong>Backstory:</strong> The world is at war. With the help of her eastern allies Russia has taken the Suez Canal and deployed a large naval force to the Mediterranean, trapping a US carrier group near the Turkish-Syrian border. Now they must break out by taking Cyprus back.</p>
|
||||
version: "10.9"
|
||||
version: "10.1"
|
||||
recommended_player_faction: US Navy 2005
|
||||
recommended_enemy_faction: Russia 2010
|
||||
miz: operation_blackball.miz
|
||||
@@ -123,15 +123,15 @@ squadrons:
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- A-50
|
||||
size: 1
|
||||
size: 2
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 1
|
||||
size: 2
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- IL-78MD
|
||||
size: 1
|
||||
size: 2
|
||||
# OPFOR First FOB
|
||||
FOB Gecitkale:
|
||||
- primary: CAS
|
||||
|
||||
@@ -8,7 +8,7 @@ description: <p>This is a semi-fictional what-if scenario for Operation Peace Sp
|
||||
miz: operation_peace_spring.miz
|
||||
performance: 1
|
||||
recommended_start_date: 2019-12-23
|
||||
version: "10.9"
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
# Ramat David
|
||||
30:
|
||||
@@ -43,10 +43,10 @@ squadrons:
|
||||
aircraft:
|
||||
- AV-8B Harrier II Night Attack
|
||||
size: 8
|
||||
- primary: TARCAP
|
||||
secondary: any
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
aircraft:
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-15C Eagle
|
||||
size: 12
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
|
||||
@@ -8,7 +8,7 @@ description: <p>United Nations Observer Mission in Georgia (UNOMIG) observers st
|
||||
miz: operation_vectrons_claw.miz
|
||||
performance: 1
|
||||
recommended_start_date: 2008-08-08
|
||||
version: "10.9"
|
||||
version: "10.7"
|
||||
squadrons:
|
||||
Blue CV-1:
|
||||
- primary: BARCAP
|
||||
@@ -66,11 +66,6 @@ squadrons:
|
||||
aircraft:
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
size: 16
|
||||
- primary: BAI
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
size: 12
|
||||
- primary: BAI
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Marianas - Pacific Repartee
|
||||
theater: MarianaIslands
|
||||
authors: Fuzzle
|
||||
description: <p>A naval campaign where a US carrier group must retake Guam, Saipan and the Marianas Islands from the Chinese. <strong>This is a purely naval campaign, meaning you will need to use the Air Assault mission type with transports to take FOBs/airbases. Ensure you soften them up enough first!</strong></p><p><strong>Backstory:</strong> After an escalation in the South China Sea, the PLAN has taken the US by surprise and invaded Guam, setting up supporting positions throughout the Marianas island chain. With the rest of the US Navy engaged near Japan, a carrier task group must push through China's forces, assist a small Marine contingent holding out on Farallon de Pajaros and liberate Guam.</p>
|
||||
version: "10.9"
|
||||
version: "10.4"
|
||||
recommended_player_faction: US Navy 2005
|
||||
recommended_enemy_faction: China 2010
|
||||
miz: pacific_repartee.miz
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Persian Gulf - Scenic Route 2 - Dust To Dust
|
||||
theater: Persian Gulf
|
||||
authors: Fuzzle
|
||||
description: <p>A continuation of Scenic Route. A NATO coalition pushes inland along a protracted axis of advance. Built with helicopters/FOB-based gameplay in mind. <p><strong>Backstory:</strong> With Iran's coastal defences pacified and their forces pushed inland, a beleaguered US Navy is reinforced by a NATO coalition task force. The going will not be easy however; Iran has assembled the full might of its armoured and mechanized divisions alongside rotary support to defend their heartland. The conflict intensifies.</p>
|
||||
version: "10.9"
|
||||
version: "10.1"
|
||||
advanced_iads: true
|
||||
recommended_player_faction: NATO OIF
|
||||
recommended_enemy_faction: Iran 2015
|
||||
@@ -260,4 +260,4 @@ squadrons:
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Mi-24P Hind-F
|
||||
- Mi-24P Hind-F
|
||||
|
||||
@@ -3,7 +3,7 @@ name: Persian Gulf - Scenic Route
|
||||
theater: Persian Gulf
|
||||
authors: Fuzzle
|
||||
description: <p>A lightweight naval campaign involving a US Navy carrier group pushing across the coast of Iran. <strong>This is a purely naval campaign, meaning you will need to use the Air Assault mission type with transports to take the first FOB. Ensure you soften it up enough first!</strong></p><p><strong>Backstory:</strong> Iran has declared war on all US forces in the Gulf resulting in all local allies withdrawing their support for American troops. A lone carrier group must pacify the southern coast of Iran and hold out until backup can arrive lest the US and her interests be ejected from the region permanently.</p>
|
||||
version: "10.9"
|
||||
version: "10.4"
|
||||
advanced_iads: true
|
||||
recommended_player_faction: US Navy 2005
|
||||
recommended_enemy_faction: Iran 2015
|
||||
@@ -77,7 +77,15 @@ squadrons:
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- A-50
|
||||
size: 1
|
||||
size: 2
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 2
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- IL-78MD
|
||||
size: 2
|
||||
- primary: BARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
|
||||
@@ -7,7 +7,7 @@ recommended_player_faction: USA 2005
|
||||
recommended_enemy_faction: Iraq 1991
|
||||
miz: tripoint_hostility.miz
|
||||
performance: 2
|
||||
version: "10.9"
|
||||
version: "10.1"
|
||||
recommended_start_date: 2006-08-03
|
||||
recommended_player_money: 900
|
||||
recommended_enemy_money: 1200
|
||||
|
||||
@@ -1,560 +0,0 @@
|
||||
local unitPayloads = {
|
||||
["name"] = "F-15ESE",
|
||||
["payloads"] = {
|
||||
[1] = {
|
||||
["displayName"] = "Liberation Strike",
|
||||
["name"] = "Liberation Strike",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{CFT_R_MK84LD_x_2}",
|
||||
["num"] = 12,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{AB8B8299-F1CC-4359-89B5-2172E0CF4A5A}",
|
||||
["num"] = 8,
|
||||
["settings"] = {
|
||||
["GUI_fuze_type"] = 1,
|
||||
["arm_delay_ctrl_FMU139CB_LD"] = 1,
|
||||
["function_delay_ctrl_FMU139CB_LD"] = 0,
|
||||
},
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{CFT_L_MK84LD_x_2}",
|
||||
["num"] = 4,
|
||||
},
|
||||
[10] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 14,
|
||||
},
|
||||
[11] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 2,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[2] = {
|
||||
["name"] = "Liberation BAI",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{CFT_L_CBU_97_x_6}",
|
||||
["num"] = 4,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{CFT_R_CBU_97_x_6}",
|
||||
["num"] = 12,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[3] = {
|
||||
["name"] = "Liberation BARCAP",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 14,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 11,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[10] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 5,
|
||||
},
|
||||
[11] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[12] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 2,
|
||||
},
|
||||
[13] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[4] = {
|
||||
["displayName"] = "Liberation Escort",
|
||||
["name"] = "Liberation Escort",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 14,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 11,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[10] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 5,
|
||||
},
|
||||
[11] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[12] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 2,
|
||||
},
|
||||
[13] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[5] = {
|
||||
["displayName"] = "Liberation OCA/Runway",
|
||||
["name"] = "Liberation OCA/Runway",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{CFT_R_MK84LD_x_2}",
|
||||
["num"] = 12,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{AB8B8299-F1CC-4359-89B5-2172E0CF4A5A}",
|
||||
["num"] = 8,
|
||||
["settings"] = {
|
||||
["GUI_fuze_type"] = 1,
|
||||
["arm_delay_ctrl_FMU139CB_LD"] = 1,
|
||||
["function_delay_ctrl_FMU139CB_LD"] = 0,
|
||||
},
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{CFT_L_MK84LD_x_2}",
|
||||
["num"] = 4,
|
||||
},
|
||||
[10] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 14,
|
||||
},
|
||||
[11] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 2,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[6] = {
|
||||
["displayName"] = "Liberation OCA/Aircraft",
|
||||
["name"] = "Liberation OCA/Aircraft",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{CFT_R_MK82LD_x_6}",
|
||||
["num"] = 12,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["num"] = 8,
|
||||
["settings"] = {
|
||||
["GUI_fuze_type"] = 1,
|
||||
["arm_delay_ctrl_FMU139CB_LD"] = 1,
|
||||
["function_delay_ctrl_FMU139CB_LD"] = 0,
|
||||
},
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{CFT_L_MK82LD_x_6}",
|
||||
["num"] = 4,
|
||||
},
|
||||
[10] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 14,
|
||||
},
|
||||
[11] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 2,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[7] = {
|
||||
["displayName"] = "Liberation Fighter Sweep",
|
||||
["name"] = "Liberation Fighter Sweep",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 14,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 11,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[10] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 5,
|
||||
},
|
||||
[11] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[12] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 2,
|
||||
},
|
||||
[13] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[8] = {
|
||||
["displayName"] = "Liberation DEAD",
|
||||
["name"] = "Liberation DEAD",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{CFT_L_CBU_97_x_6}",
|
||||
["num"] = 4,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{CFT_R_CBU_97_x_6}",
|
||||
["num"] = 12,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[9] = {
|
||||
["displayName"] = "Liberation TARCAP",
|
||||
["name"] = "Liberation TARCAP",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 14,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 11,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[10] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 5,
|
||||
},
|
||||
[11] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[12] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 2,
|
||||
},
|
||||
[13] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
[10] = {
|
||||
["displayName"] = "Liberation CAS",
|
||||
["name"] = "Liberation CAS",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 15,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{40EF17B7-F508-45de-8566-6FFECC0C1AB8}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 13,
|
||||
},
|
||||
[4] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[5] = {
|
||||
["CLSID"] = "{CFT_R_CBU_97_x_6}",
|
||||
["num"] = 12,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{F-15E_AAQ-13_LANTIRN}",
|
||||
["num"] = 9,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{F15E_EXTTANK}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F-15E_AAQ-14_LANTIRN}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{CFT_L_CBU_97_x_6}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
["tasks"] = {
|
||||
},
|
||||
["unitType"] = "F-15ESE",
|
||||
}
|
||||
return unitPayloads
|
||||
@@ -5,15 +5,15 @@ local unitPayloads = {
|
||||
["name"] = "CAP",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "ALQ_184",
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{8D399DDA-FF81-4F14-904D-099B34FE7918}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{8D399DDA-FF81-4F14-904D-099B34FE7918}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[4] = {
|
||||
@@ -25,21 +25,14 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 4,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 7,
|
||||
}, },
|
||||
},
|
||||
["tasks"] = {
|
||||
[1] = 11,
|
||||
},
|
||||
@@ -48,7 +41,7 @@ local unitPayloads = {
|
||||
["name"] = "CAS",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "ALQ_184",
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
@@ -68,19 +61,19 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
@@ -92,15 +85,15 @@ local unitPayloads = {
|
||||
["name"] = "STRIKE",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "ALQ_184",
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{AB8B8299-F1CC-4359-89B5-2172E0CF4A5A}",
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{AB8B8299-F1CC-4359-89B5-2172E0CF4A5A}",
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[4] = {
|
||||
@@ -112,19 +105,19 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
@@ -136,7 +129,7 @@ local unitPayloads = {
|
||||
["name"] = "ANTISHIP",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "ALQ_184",
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
@@ -156,19 +149,19 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
@@ -180,7 +173,7 @@ local unitPayloads = {
|
||||
["name"] = "SEAD",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "ALQ_184",
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
@@ -188,7 +181,7 @@ local unitPayloads = {
|
||||
["num"] = 8,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{E6A6262A-CA08-4B3D-B030-E1A993B98452}",
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[4] = {
|
||||
@@ -200,19 +193,19 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
},
|
||||
"airfield32_3": {
|
||||
"name": "Beslan",
|
||||
"callsign": "ICH",
|
||||
"callsign": "",
|
||||
"beacon_type": 14,
|
||||
"hertz": 110500000,
|
||||
"channel": null
|
||||
|
||||
@@ -167,13 +167,6 @@
|
||||
"hertz": 1000000,
|
||||
"channel": 31
|
||||
},
|
||||
"airfield20_0": {
|
||||
"name": "BIO",
|
||||
"callsign": "BIO",
|
||||
"beacon_type": 9,
|
||||
"hertz": 205000000,
|
||||
"channel": null
|
||||
},
|
||||
"airfield11_0": {
|
||||
"name": "San Julian",
|
||||
"callsign": "",
|
||||
|
||||
@@ -1,193 +1,18 @@
|
||||
{
|
||||
"world_0": {
|
||||
"name": "Kish",
|
||||
"callsign": "KIS",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117400000,
|
||||
"channel": 121
|
||||
},
|
||||
"world_1": {
|
||||
"name": "DohaAirport",
|
||||
"callsign": "DIA",
|
||||
"beacon_type": 3,
|
||||
"hertz": 112400000,
|
||||
"channel": 71
|
||||
},
|
||||
"world_2": {
|
||||
"name": "HamadInternationalAirport",
|
||||
"callsign": "DOH",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114400000,
|
||||
"channel": 91
|
||||
},
|
||||
"world_3": {
|
||||
"name": "DezfulAirport",
|
||||
"callsign": "DZF",
|
||||
"beacon_type": 8,
|
||||
"hertz": 293000,
|
||||
"channel": null
|
||||
},
|
||||
"world_4": {
|
||||
"name": "AbadanIntAirport",
|
||||
"callsign": "ABD",
|
||||
"beacon_type": 3,
|
||||
"hertz": 115100000,
|
||||
"channel": 98
|
||||
},
|
||||
"world_5": {
|
||||
"name": "AhvazIntAirport",
|
||||
"callsign": "AWZ",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114000000,
|
||||
"channel": 87
|
||||
},
|
||||
"world_6": {
|
||||
"name": "AghajariAirport",
|
||||
"callsign": "AJR",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114900000,
|
||||
"channel": 96
|
||||
},
|
||||
"world_7": {
|
||||
"name": "BirjandIntAirport",
|
||||
"callsign": "BJD",
|
||||
"beacon_type": 3,
|
||||
"hertz": 113500000,
|
||||
"channel": 82
|
||||
},
|
||||
"world_8": {
|
||||
"name": "BushehrIntAirport",
|
||||
"callsign": "BUZ",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117450000,
|
||||
"channel": 121
|
||||
},
|
||||
"world_9": {
|
||||
"name": "KonarakAirport",
|
||||
"callsign": "CBH",
|
||||
"beacon_type": 3,
|
||||
"hertz": 115600000,
|
||||
"channel": 103
|
||||
},
|
||||
"world_10": {
|
||||
"name": "IsfahanIntAirport",
|
||||
"callsign": "ISN",
|
||||
"beacon_type": 3,
|
||||
"hertz": 113200000,
|
||||
"channel": 79
|
||||
},
|
||||
"world_11": {
|
||||
"name": "KhoramabadAirport",
|
||||
"callsign": "KRD",
|
||||
"beacon_type": 3,
|
||||
"hertz": 113750000,
|
||||
"channel": 84
|
||||
},
|
||||
"world_12": {
|
||||
"name": "PersianGulfIntAirport",
|
||||
"callsign": "PRG",
|
||||
"beacon_type": 3,
|
||||
"hertz": 112100000,
|
||||
"channel": 58
|
||||
},
|
||||
"world_13": {
|
||||
"name": "YasoujAirport",
|
||||
"callsign": "YSJ",
|
||||
"beacon_type": 3,
|
||||
"hertz": 116550000,
|
||||
"channel": 112
|
||||
},
|
||||
"world_14": {
|
||||
"name": "BamAirport",
|
||||
"callsign": "BAM",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114900000,
|
||||
"channel": 96
|
||||
},
|
||||
"world_15": {
|
||||
"name": "MahshahrAirport",
|
||||
"callsign": "MAH",
|
||||
"beacon_type": 3,
|
||||
"hertz": 115800000,
|
||||
"channel": 105
|
||||
},
|
||||
"world_16": {
|
||||
"name": "IranShahrAirport",
|
||||
"callsign": "ISR",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117000000,
|
||||
"channel": 117
|
||||
},
|
||||
"world_17": {
|
||||
"name": "LamerdAirport",
|
||||
"callsign": "LAM",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117000000,
|
||||
"channel": 117
|
||||
},
|
||||
"world_18": {
|
||||
"name": "SirjanAirport",
|
||||
"callsign": "SRJ",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114600000,
|
||||
"channel": 93
|
||||
},
|
||||
"world_19": {
|
||||
"name": "YazdIntAirport",
|
||||
"callsign": "YZD",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117700000,
|
||||
"channel": 124
|
||||
},
|
||||
"world_20": {
|
||||
"name": "ZabolAirport",
|
||||
"callsign": "ZAL",
|
||||
"beacon_type": 3,
|
||||
"hertz": 113100000,
|
||||
"channel": 78
|
||||
},
|
||||
"world_21": {
|
||||
"name": "ZahedanIntAirport",
|
||||
"callsign": "ZDN",
|
||||
"beacon_type": 3,
|
||||
"hertz": 116000000,
|
||||
"channel": 107
|
||||
},
|
||||
"world_22": {
|
||||
"name": "RafsanjanAirport",
|
||||
"callsign": "RAF",
|
||||
"beacon_type": 3,
|
||||
"hertz": 112300000,
|
||||
"channel": 70
|
||||
},
|
||||
"world_23": {
|
||||
"name": "SaravanAirport",
|
||||
"callsign": "SRN",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114100000,
|
||||
"channel": 88
|
||||
},
|
||||
"world_24": {
|
||||
"name": "BuHasa",
|
||||
"callsign": "BH",
|
||||
"beacon_type": 2,
|
||||
"hertz": 309000000,
|
||||
"channel": null
|
||||
},
|
||||
"airfield22_0": {
|
||||
"name": "AbuDhabiInt",
|
||||
"callsign": "ADV",
|
||||
"beacon_type": 2,
|
||||
"hertz": 114250000,
|
||||
"channel": 119
|
||||
},
|
||||
"airfield22_1": {
|
||||
"name": "ABUDHABI",
|
||||
"callsign": "ADV",
|
||||
"beacon_type": 1,
|
||||
"hertz": 114250000,
|
||||
"channel": null
|
||||
},
|
||||
"airfield22_1": {
|
||||
"name": "AbuDhabiInt",
|
||||
"callsign": "ADV",
|
||||
"beacon_type": 2,
|
||||
"hertz": 114250000,
|
||||
"channel": 119
|
||||
},
|
||||
"airfield1_0": {
|
||||
"name": "Abumusa",
|
||||
"callsign": "ABM",
|
||||
@@ -705,5 +530,180 @@
|
||||
"beacon_type": 4,
|
||||
"hertz": 114200000,
|
||||
"channel": 89
|
||||
},
|
||||
"world_0": {
|
||||
"name": "Kish",
|
||||
"callsign": "KIS",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117400000,
|
||||
"channel": 121
|
||||
},
|
||||
"world_1": {
|
||||
"name": "DohaAirport",
|
||||
"callsign": "DIA",
|
||||
"beacon_type": 3,
|
||||
"hertz": 112400000,
|
||||
"channel": 71
|
||||
},
|
||||
"world_2": {
|
||||
"name": "HamadInternationalAirport",
|
||||
"callsign": "DOH",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114400000,
|
||||
"channel": 91
|
||||
},
|
||||
"world_3": {
|
||||
"name": "DezfulAirport",
|
||||
"callsign": "DZF",
|
||||
"beacon_type": 8,
|
||||
"hertz": 293000,
|
||||
"channel": null
|
||||
},
|
||||
"world_4": {
|
||||
"name": "AbadanIntAirport",
|
||||
"callsign": "ABD",
|
||||
"beacon_type": 3,
|
||||
"hertz": 115100000,
|
||||
"channel": 98
|
||||
},
|
||||
"world_5": {
|
||||
"name": "AhvazIntAirport",
|
||||
"callsign": "AWZ",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114000000,
|
||||
"channel": 87
|
||||
},
|
||||
"world_6": {
|
||||
"name": "AghajariAirport",
|
||||
"callsign": "AJR",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114900000,
|
||||
"channel": 96
|
||||
},
|
||||
"world_7": {
|
||||
"name": "BirjandIntAirport",
|
||||
"callsign": "BJD",
|
||||
"beacon_type": 3,
|
||||
"hertz": 113500000,
|
||||
"channel": 82
|
||||
},
|
||||
"world_8": {
|
||||
"name": "BushehrIntAirport",
|
||||
"callsign": "BUZ",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117450000,
|
||||
"channel": 121
|
||||
},
|
||||
"world_9": {
|
||||
"name": "KonarakAirport",
|
||||
"callsign": "CBH",
|
||||
"beacon_type": 3,
|
||||
"hertz": 115600000,
|
||||
"channel": 103
|
||||
},
|
||||
"world_10": {
|
||||
"name": "IsfahanIntAirport",
|
||||
"callsign": "ISN",
|
||||
"beacon_type": 3,
|
||||
"hertz": 113200000,
|
||||
"channel": 79
|
||||
},
|
||||
"world_11": {
|
||||
"name": "KhoramabadAirport",
|
||||
"callsign": "KRD",
|
||||
"beacon_type": 3,
|
||||
"hertz": 113750000,
|
||||
"channel": 84
|
||||
},
|
||||
"world_12": {
|
||||
"name": "PersianGulfIntAirport",
|
||||
"callsign": "PRG",
|
||||
"beacon_type": 3,
|
||||
"hertz": 112100000,
|
||||
"channel": 58
|
||||
},
|
||||
"world_13": {
|
||||
"name": "YasoujAirport",
|
||||
"callsign": "YSJ",
|
||||
"beacon_type": 3,
|
||||
"hertz": 116550000,
|
||||
"channel": 112
|
||||
},
|
||||
"world_14": {
|
||||
"name": "BamAirport",
|
||||
"callsign": "BAM",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114900000,
|
||||
"channel": 96
|
||||
},
|
||||
"world_15": {
|
||||
"name": "MahshahrAirport",
|
||||
"callsign": "MAH",
|
||||
"beacon_type": 3,
|
||||
"hertz": 115800000,
|
||||
"channel": 105
|
||||
},
|
||||
"world_16": {
|
||||
"name": "IranShahrAirport",
|
||||
"callsign": "ISR",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117000000,
|
||||
"channel": 117
|
||||
},
|
||||
"world_17": {
|
||||
"name": "LamerdAirport",
|
||||
"callsign": "LAM",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117000000,
|
||||
"channel": 117
|
||||
},
|
||||
"world_18": {
|
||||
"name": "SirjanAirport",
|
||||
"callsign": "SRJ",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114600000,
|
||||
"channel": 93
|
||||
},
|
||||
"world_19": {
|
||||
"name": "YazdIntAirport",
|
||||
"callsign": "YZD",
|
||||
"beacon_type": 3,
|
||||
"hertz": 117700000,
|
||||
"channel": 124
|
||||
},
|
||||
"world_20": {
|
||||
"name": "ZabolAirport",
|
||||
"callsign": "ZAL",
|
||||
"beacon_type": 3,
|
||||
"hertz": 113100000,
|
||||
"channel": 78
|
||||
},
|
||||
"world_21": {
|
||||
"name": "ZahedanIntAirport",
|
||||
"callsign": "ZDN",
|
||||
"beacon_type": 3,
|
||||
"hertz": 116000000,
|
||||
"channel": 107
|
||||
},
|
||||
"world_22": {
|
||||
"name": "RafsanjanAirport",
|
||||
"callsign": "RAF",
|
||||
"beacon_type": 3,
|
||||
"hertz": 112300000,
|
||||
"channel": 70
|
||||
},
|
||||
"world_23": {
|
||||
"name": "SaravanAirport",
|
||||
"callsign": "SRN",
|
||||
"beacon_type": 3,
|
||||
"hertz": 114100000,
|
||||
"channel": 88
|
||||
},
|
||||
"world_24": {
|
||||
"name": "BuHasa",
|
||||
"callsign": "BH",
|
||||
"beacon_type": 2,
|
||||
"hertz": 309000000,
|
||||
"channel": null
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,8 @@ country: Combined Joint Task Forces Blue
|
||||
name: NATO Desert Storm
|
||||
authors: Hawkmoon
|
||||
description:
|
||||
<p>A faction to recreate the actual unit lineup during Desert Storm as closely
|
||||
as possible</p>
|
||||
<p>A faction to recreate the actual unit lineup during Desert Storm as
|
||||
closely as possible</p>
|
||||
aircrafts:
|
||||
- A-10A Thunderbolt II
|
||||
- AH-64A Apache
|
||||
@@ -18,7 +18,6 @@ aircrafts:
|
||||
- F-14B Tomcat
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F-4E Phantom II
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
@@ -73,7 +72,7 @@ naval_units:
|
||||
- CVN-74 John C. Stennis
|
||||
missiles: []
|
||||
air_defense_units:
|
||||
- EWR AN/FPS-117 Radar
|
||||
- SAM Patriot STR
|
||||
- M163 Vulcan Air Defense System
|
||||
- M1097 Heavy HMMWV Avenger
|
||||
- M48 Chaparral
|
||||
|
||||
@@ -3,8 +3,8 @@ country: Combined Joint Task Forces Blue
|
||||
name: NATO OIF
|
||||
authors: Fuzzle
|
||||
description:
|
||||
<p>A more modern NATO mixed faction reflecting the units involved in Operation
|
||||
Iraqi Freedom.</p>
|
||||
<p>A more modern NATO mixed faction reflecting the units involved in
|
||||
Operation Iraqi Freedom.</p>
|
||||
aircrafts:
|
||||
- A-10C Thunderbolt II (Suite 3)
|
||||
- AH-64D Apache Longbow
|
||||
@@ -19,7 +19,6 @@ aircrafts:
|
||||
- F-14B Tomcat
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F-22A Raptor
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
@@ -76,7 +75,7 @@ naval_units:
|
||||
- CVN-74 John C. Stennis
|
||||
missiles: []
|
||||
air_defense_units:
|
||||
- EWR AN/FPS-117 Radar
|
||||
- SAM Patriot STR
|
||||
- M163 Vulcan Air Defense System
|
||||
- M1097 Heavy HMMWV Avenger
|
||||
- M48 Chaparral
|
||||
|
||||
@@ -30,7 +30,6 @@ frontline_units:
|
||||
- QF 3.7-inch AA Gun
|
||||
artillery_units:
|
||||
- M12 Gun Motor Carriage
|
||||
- FH M2A1 105mm
|
||||
logistics_units:
|
||||
- Truck Bedford
|
||||
- Truck GMC "Jimmy" 6x6 Truck
|
||||
|
||||
@@ -17,14 +17,9 @@ aircrafts:
|
||||
- B-52H Stratofortress
|
||||
- C-130
|
||||
- C-130J-30 Super Hercules
|
||||
- C-17A
|
||||
- CH-47D
|
||||
- CH-53E
|
||||
- F-117A Nighthawk
|
||||
- F-14B Tomcat
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F-22A Raptor
|
||||
- F-5E Tiger II
|
||||
@@ -49,11 +44,7 @@ aircrafts:
|
||||
- SA 342M Gazelle
|
||||
- Su-25T Frogfoot
|
||||
- Su-27 Flanker-B
|
||||
- S-3B Viking
|
||||
- SH-60B Seahawk
|
||||
- UH-1H Iroquois
|
||||
- UH-60A
|
||||
- UH-60L
|
||||
awacs:
|
||||
- E-2D Advanced Hawkeye
|
||||
- E-3A
|
||||
@@ -84,7 +75,6 @@ infantry_units:
|
||||
- Infantry M249
|
||||
- Infantry M4
|
||||
- MANPADS Stinger
|
||||
- Mortar 2B11 120mm
|
||||
preset_groups:
|
||||
- Hawk
|
||||
- Patriot
|
||||
@@ -96,7 +86,7 @@ naval_units:
|
||||
- CVN-74 John C. Stennis
|
||||
missiles: []
|
||||
air_defense_units:
|
||||
- EWR AN/FPS-117 Radar
|
||||
- SAM Patriot STR
|
||||
- M1097 Heavy HMMWV Avenger
|
||||
requirements: {}
|
||||
carrier_names:
|
||||
@@ -104,7 +94,6 @@ carrier_names:
|
||||
- CVN-72 Abraham Lincoln
|
||||
- CVN-73 George Washington
|
||||
- CVN-74 John C. Stennis
|
||||
- CVN-75 Harry S. Truman
|
||||
helicopter_carrier_names:
|
||||
- LHA-1 Tarawa
|
||||
- LHA-2 Saipan
|
||||
|
||||
@@ -22,7 +22,7 @@ tankers:
|
||||
frontline_units:
|
||||
- BMP-1
|
||||
- HQ-7 Launcher
|
||||
- MT Type 59
|
||||
- T-55A
|
||||
- Type 04A (ZBD-04A)
|
||||
- Type 96B (ZTZ-96B)
|
||||
artillery_units:
|
||||
@@ -52,7 +52,6 @@ naval_units:
|
||||
- Type 052B Destroyer
|
||||
- Type 052C Destroyer
|
||||
- Type 054A Frigate
|
||||
- Type 093 Attack Submarine
|
||||
- CV 1143.5 Admiral Kuznetsov
|
||||
- Type 071 Amphibious Transport Dock
|
||||
air_defense_units:
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
country: Egypt
|
||||
name: Egypt 2000s
|
||||
authors: Starfire
|
||||
description: <p>Egyptian military in the 21st century.</p>
|
||||
locales:
|
||||
- ar_SA
|
||||
aircrafts:
|
||||
- MiG-29S Fulcrum-C
|
||||
- J-7B
|
||||
- Mirage 2000C
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- IL-76MD
|
||||
- C-130
|
||||
- C-130J-30 Super Hercules
|
||||
- AH-64D Apache Longbow
|
||||
- AH-64D Apache Longbow (AI)
|
||||
- SA 342L Gazelle
|
||||
- SA 342M Gazelle
|
||||
- CH-47D
|
||||
- Ka-50 Hokum
|
||||
- Ka-50 Hokum (Blackshark 3)
|
||||
- Mi-24V Hind-E
|
||||
- Mi-24P Hind-F
|
||||
- Mi-8MTV2 Hip
|
||||
awacs:
|
||||
- E-2C Hawkeye
|
||||
frontline_units:
|
||||
- M1A2 Abrams
|
||||
- M60A3 "Patton"
|
||||
- T-90A
|
||||
- T-55A
|
||||
- BMP-1
|
||||
- M113
|
||||
- BTR-80
|
||||
- M1043 HMMWV (M2 HMG)
|
||||
- M1045 HMMWV (BGM-71 TOW)
|
||||
- M1097 Heavy HMMWV Avenger
|
||||
- ZSU-23-4 Shilka
|
||||
- M163 Vulcan Air Defense System
|
||||
artillery_units:
|
||||
- M109A6 Paladin
|
||||
- M270 Multiple Launch Rocket System
|
||||
logistics_units:
|
||||
- Truck Ural-375
|
||||
- Truck Ural-4320T
|
||||
- Truck GAZ-66
|
||||
infantry_units:
|
||||
- Infantry RPG
|
||||
- Infantry AK-74 Rus
|
||||
- MANPADS SA-18 Igla "Grouse"
|
||||
- MANPADS Stinger
|
||||
- Mortar 2B11 120mm
|
||||
- Paratrooper AKS
|
||||
- Paratrooper RPG-16
|
||||
preset_groups:
|
||||
- SA-2/S-75
|
||||
- SA-6
|
||||
- SA-17
|
||||
- SA-10/S-300PS
|
||||
- SA-23/S-300VM
|
||||
- Patriot
|
||||
- Hawk
|
||||
missiles:
|
||||
- SSM SS-1C Scud-B
|
||||
air_defense_units:
|
||||
- EWR AN/FPS-117 Radar
|
||||
- EWR 55G6
|
||||
- M1097 Heavy HMMWV Avenger
|
||||
- M48 Chaparral
|
||||
- M163 Vulcan Air Defense System
|
||||
- SA-9 Strela
|
||||
- SA-15 Tor
|
||||
- ZSU-23-4 Shilka
|
||||
- AAA ZU-23 Closed Emplacement
|
||||
- ZU-23 on Ural-375
|
||||
- ZSU-57-2 'Sparka'
|
||||
has_jtac: true
|
||||
jtac_unit: MQ-9 Reaper
|
||||
@@ -42,7 +42,7 @@ naval_units:
|
||||
- CG Ticonderoga
|
||||
missiles: []
|
||||
air_defense_units:
|
||||
- EWR AN/FPS-117 Radar
|
||||
- SAM Patriot STR
|
||||
- Flakpanzer Gepard
|
||||
requirements: {}
|
||||
carrier_names: []
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user