Compare commits
121 Commits
develop-7.
...
develop-8.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4da4956df8 | ||
|
|
618159c1fa | ||
|
|
d8c662e7f8 | ||
|
|
12c41b57c9 | ||
|
|
85a27845bc | ||
|
|
e3f6347e16 | ||
|
|
fffe1b6e94 | ||
|
|
5a7a730e23 | ||
|
|
576f777320 | ||
|
|
87f7fe5307 | ||
|
|
e1434378a8 | ||
|
|
e03b0d99d8 | ||
|
|
e4eb3dec1b | ||
|
|
b365016496 | ||
|
|
c359b3f7fc | ||
|
|
302613069e | ||
|
|
5a22b62e3b | ||
|
|
001e7dfed9 | ||
|
|
cf985d3d37 | ||
|
|
1044a1f45f | ||
|
|
9fe31859d3 | ||
|
|
09417322e7 | ||
|
|
136a9b5f02 | ||
|
|
02f22d4930 | ||
|
|
ca133b9fd1 | ||
|
|
b1af6dfbe1 | ||
|
|
647d1f57f9 | ||
|
|
b250fe2f1e | ||
|
|
3be57bf6bb | ||
|
|
3c8d0b023e | ||
|
|
adceb3a224 | ||
|
|
ab02cd34c5 | ||
|
|
c74b603d81 | ||
|
|
5815401e73 | ||
|
|
f463fe50f2 | ||
|
|
f60bf62897 | ||
|
|
9d43eb8f03 | ||
|
|
8f0ca08b89 | ||
|
|
0534f66b30 | ||
|
|
1162e0aa29 | ||
|
|
36c4bb88be | ||
|
|
dc6624a159 | ||
|
|
8b55331326 | ||
|
|
33ca77e3d1 | ||
|
|
b92b01b245 | ||
|
|
b18b371904 | ||
|
|
9c7e16d121 | ||
|
|
87e869d963 | ||
|
|
4a059a4f8b | ||
|
|
674254e55b | ||
|
|
9fd0e06c05 | ||
|
|
ecaf84ea55 | ||
|
|
e4028cb013 | ||
|
|
c45ac50370 | ||
|
|
6640609caf | ||
|
|
e44b6b416b | ||
|
|
8a861d3da5 | ||
|
|
380d6551be | ||
|
|
4cb035b955 | ||
|
|
e50be9bbde | ||
|
|
ec49a10135 | ||
|
|
23e3630169 | ||
|
|
e20ab5fbc0 | ||
|
|
4fd2bb131b | ||
|
|
42a7102948 | ||
|
|
d271ff17c2 | ||
|
|
cb61dfccc4 | ||
|
|
56f93c76eb | ||
|
|
36cb3a386c | ||
|
|
c25e830e6c | ||
|
|
5d08990cd0 | ||
|
|
2a45cd8899 | ||
|
|
90b880ec3c | ||
|
|
5f0c570d65 | ||
|
|
ce102fcc50 | ||
|
|
30c792c15a | ||
|
|
2f45b856d6 | ||
|
|
31d2b756ab | ||
|
|
b5cf889c09 | ||
|
|
19958f91ca | ||
|
|
c775a898a4 | ||
|
|
535244f6f3 | ||
|
|
9d1d3bdcfa | ||
|
|
36eef2b1b9 | ||
|
|
7788425c5c | ||
|
|
ee0c21b3e5 | ||
|
|
54cd619f75 | ||
|
|
051940e23c | ||
|
|
4fbd7defa3 | ||
|
|
90bda9383d | ||
|
|
7798e2970c | ||
|
|
410c25b331 | ||
|
|
cff74525d6 | ||
|
|
8b7f107044 | ||
|
|
c365a0d739 | ||
|
|
1f4fd0fd04 | ||
|
|
4bb60cb500 | ||
|
|
fe96a415be | ||
|
|
6699289bf7 | ||
|
|
a85d3243fb | ||
|
|
7f2607cf08 | ||
|
|
e50ee976ed | ||
|
|
29ffb526f2 | ||
|
|
e024013093 | ||
|
|
257dabe4fa | ||
|
|
406fb61fa4 | ||
|
|
49dfa95c61 | ||
|
|
c80e5b259f | ||
|
|
64e2213f28 | ||
|
|
ced93afd49 | ||
|
|
f719a5ec34 | ||
|
|
6f4ac1dc39 | ||
|
|
f831c8efdd | ||
|
|
e3c6b03603 | ||
|
|
7a2e8279cd | ||
|
|
24e72475b4 | ||
|
|
f10350dac4 | ||
|
|
f068976749 | ||
|
|
4b4c45e90f | ||
|
|
527eac1f4a | ||
|
|
92c3087187 |
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:
|
||||
- 6.1.1
|
||||
- 7.1.0
|
||||
- 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` (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.
|
||||
`.liberation.zip` 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,10 +73,7 @@ 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. **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).
|
||||
investigating any issues with end-of-turn results processing.
|
||||
|
||||
|
||||
You can attach files to the bug by dragging and dropping the file into
|
||||
|
||||
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:
|
||||
- 6.1.1
|
||||
- 7.1.0
|
||||
- Development build
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
2
.github/workflows/test.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
- name: run tests
|
||||
run: |
|
||||
./venv/scripts/activate
|
||||
pytest --cov-report=xml tests
|
||||
pytest --cov --cov-report=xml tests
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
|
||||
1
.gitignore
vendored
@@ -9,6 +9,7 @@ venv
|
||||
.vscode/settings.json
|
||||
dist/**
|
||||
/.coverage
|
||||
/coverage.xml
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
.env
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
[](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)
|
||||

|
||||
|
||||
28
changelog.md
@@ -1,11 +1,35 @@
|
||||
# 7.1.1
|
||||
# 8.1.0
|
||||
|
||||
Saves from 7.1.0 are compatible with 7.1.1
|
||||
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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import App from "./App";
|
||||
import { store } from "./app/store";
|
||||
import { setupStore } from "./app/store";
|
||||
import { render } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
|
||||
test("app renders", () => {
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<Provider store={setupStore()}>
|
||||
<App />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
@@ -3,36 +3,48 @@ 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, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
||||
import unculledZonesReducer from "../api/unculledZonesSlice";
|
||||
import {
|
||||
Action,
|
||||
PreloadedState,
|
||||
ThunkAction,
|
||||
combineReducers,
|
||||
configureStore,
|
||||
} from "@reduxjs/toolkit";
|
||||
|
||||
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),
|
||||
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 type AppDispatch = typeof store.dispatch;
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
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 AppThunk<ReturnType = void> = ThunkAction<
|
||||
ReturnType,
|
||||
RootState,
|
||||
|
||||
53
client/src/components/aircraftlayer/AircraftLayer.test.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
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);
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
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;
|
||||
}
|
||||
|
||||
function colorFor(blue: boolean, detection: boolean) {
|
||||
export function colorFor(blue: boolean, detection: boolean) {
|
||||
if (blue) {
|
||||
return detection ? "#bb89ff" : "#0084ff";
|
||||
}
|
||||
|
||||
132
client/src/components/combat/Combat.test.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
48
client/src/components/combatlayer/CombatLayer.test.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
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,18 +30,10 @@ 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">
|
||||
{cez}
|
||||
<CullingExclusionCircles zones={data}></CullingExclusionCircles>
|
||||
</LayersControl.Overlay>
|
||||
);
|
||||
}
|
||||
|
||||
405
client/src/components/flightplanslayer/FlightPlansLayer.test.tsx
Normal file
@@ -0,0 +1,405 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
32
client/src/components/frontline/FrontLine.test.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
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,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
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 SupplyRoutesLayer() {
|
||||
export default function FrontLinesLayer() {
|
||||
const fronts = useAppSelector(selectFrontLines).fronts;
|
||||
return (
|
||||
<LayerGroup>
|
||||
|
||||
125
client/src/components/navmesh/NavMeshLayer.test.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
16
client/src/components/splitlines/SplitLines.test.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
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 />");
|
||||
});
|
||||
});
|
||||
159
client/src/components/supplyroute/SupplyRoute.test.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
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,6 +4,13 @@ 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;
|
||||
}
|
||||
@@ -26,18 +33,22 @@ function ActiveSupplyRouteHighlight(props: SupplyRouteProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Polyline positions={props.route.points} color={"#ffffff"} weight={2} />
|
||||
<Polyline
|
||||
positions={props.route.points}
|
||||
color={RouteColor.Highlight}
|
||||
weight={2}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function colorFor(route: SupplyRouteModel) {
|
||||
if (route.front_active) {
|
||||
return "#c85050";
|
||||
return RouteColor.Contested;
|
||||
}
|
||||
if (route.blue) {
|
||||
return "#2d3e50";
|
||||
return RouteColor.Blue;
|
||||
}
|
||||
return "#8c1414";
|
||||
return RouteColor.Red;
|
||||
}
|
||||
|
||||
export default function SupplyRoute(props: SupplyRouteProps) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import App from "./App";
|
||||
import { store } from "./app/store";
|
||||
import { setupStore } 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={store}>
|
||||
<Provider store={setupStore()}>
|
||||
<SocketProvider>
|
||||
<App />
|
||||
</SocketProvider>
|
||||
|
||||
30
client/src/testutils/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
// 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: false
|
||||
informational: true
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
project = "DCS Liberation"
|
||||
copyright = "2023, DCS Liberation Team"
|
||||
author = "DCS Liberation Team"
|
||||
release = "7.1.1"
|
||||
release = "8.1.0"
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
@@ -133,6 +133,7 @@ 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,7 +5,6 @@ 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
|
||||
@@ -37,6 +36,7 @@ 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,12 +81,6 @@ AWACS_BUDGET_COST = 4
|
||||
PLAYER_BUDGET_IMPORTANCE_LOG = 2
|
||||
|
||||
|
||||
class TurnState(Enum):
|
||||
WIN = 0
|
||||
LOSS = 1
|
||||
CONTINUE = 2
|
||||
|
||||
|
||||
class Game:
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Runway information and selection."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterator, Optional, TYPE_CHECKING
|
||||
|
||||
@@ -51,7 +52,20 @@ class RunwayData:
|
||||
atc = atc_radio.uhf
|
||||
|
||||
for beacon_data in airport.beacons:
|
||||
beacon = Beacons.with_id(beacon_data.id, theater)
|
||||
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
|
||||
if beacon.is_tacan:
|
||||
tacan = beacon.tacan_channel
|
||||
tacan_callsign = beacon.callsign
|
||||
|
||||
@@ -6,30 +6,16 @@ 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]:
|
||||
departure = FlightWaypointJs.for_waypoint(
|
||||
FlightWaypoint(
|
||||
"TAKEOFF",
|
||||
FlightWaypointType.TAKEOFF,
|
||||
flight.departure.position,
|
||||
meters(0),
|
||||
"RADIO",
|
||||
),
|
||||
flight,
|
||||
0,
|
||||
)
|
||||
return [departure] + [
|
||||
return [
|
||||
FlightWaypointJs.for_waypoint(w, flight, i)
|
||||
for i, w in enumerate(flight.flight_plan.waypoints, 1)
|
||||
]
|
||||
@@ -64,7 +50,7 @@ def set_position(
|
||||
if waypoint_idx == 0:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
waypoint = flight.flight_plan.waypoints[waypoint_idx - 1]
|
||||
waypoint = flight.flight_plan.waypoints[waypoint_idx]
|
||||
waypoint.position = Point.from_latlng(
|
||||
LatLng(position.lat, position.lng), game.theater.terrain
|
||||
)
|
||||
|
||||
@@ -4,10 +4,17 @@ 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:
|
||||
@@ -39,3 +46,94 @@ 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
|
||||
|
||||
@@ -14,6 +14,7 @@ from dcs.terrain import (
|
||||
Nevada,
|
||||
Normandy,
|
||||
PersianGulf,
|
||||
Sinai,
|
||||
Syria,
|
||||
TheChannel,
|
||||
)
|
||||
@@ -31,6 +32,7 @@ ALL_TERRAINS = [
|
||||
MarianaIslands(),
|
||||
Nevada(),
|
||||
TheChannel(),
|
||||
Sinai(),
|
||||
Syria(),
|
||||
]
|
||||
|
||||
|
||||
9
game/turnstate.py
Normal file
@@ -0,0 +1,9 @@
|
||||
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 = 7
|
||||
MAJOR_VERSION = 8
|
||||
MINOR_VERSION = 1
|
||||
MICRO_VERSION = 1
|
||||
MICRO_VERSION = 0
|
||||
VERSION_NUMBER = ".".join(str(v) for v in (MAJOR_VERSION, MINOR_VERSION, MICRO_VERSION))
|
||||
|
||||
|
||||
@@ -182,4 +182,7 @@ VERSION = _build_version_string()
|
||||
#: 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.
|
||||
CAMPAIGN_FORMAT_VERSION = (10, 9)
|
||||
#:
|
||||
#: Version 10.10
|
||||
#: * Support for Sinai.
|
||||
CAMPAIGN_FORMAT_VERSION = (10, 10)
|
||||
|
||||
@@ -9,8 +9,14 @@ 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}
|
||||
ParaTrooper = {"clsid": "{PARA}", "name": "ParaTrooper", "weight": 80}
|
||||
OV10_Paratrooper = {
|
||||
"clsid": "OV10_Paratrooper",
|
||||
"name": "OV10_Paratrooper",
|
||||
"weight": 400,
|
||||
}
|
||||
Fuel_Tank_150_gallons_ = {
|
||||
"clsid": "{150gal}",
|
||||
"name": "Fuel Tank 150 gallons",
|
||||
@@ -47,6 +53,11 @@ 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}
|
||||
|
||||
@@ -61,6 +72,7 @@ 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,
|
||||
@@ -69,6 +81,62 @@ 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}
|
||||
|
||||
@@ -83,6 +151,7 @@ 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,
|
||||
@@ -91,6 +160,62 @@ 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_)
|
||||
@@ -99,6 +224,7 @@ 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}
|
||||
|
||||
@@ -113,6 +239,7 @@ 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,
|
||||
@@ -121,6 +248,62 @@ 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}
|
||||
|
||||
@@ -135,6 +318,7 @@ 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,
|
||||
@@ -143,15 +327,76 @@ 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:
|
||||
ParaTrooper = (8, Weapons.ParaTrooper)
|
||||
OV10_Paratrooper = (8, Weapons.OV10_Paratrooper)
|
||||
|
||||
class Pylon9:
|
||||
OV10_SMOKE = (9, Weapons.OV10_SMOKE)
|
||||
|
||||
28
qt_ui/cheatcontext.py
Normal file
@@ -0,0 +1,28 @@
|
||||
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)
|
||||
@@ -111,8 +111,6 @@ 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() == "":
|
||||
|
||||
@@ -8,15 +8,12 @@ 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")
|
||||
@@ -205,6 +202,7 @@ 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():
|
||||
@@ -213,25 +211,3 @@ 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,9 +25,7 @@ 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
|
||||
|
||||
|
||||
@@ -69,24 +67,8 @@ 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:
|
||||
@@ -102,7 +84,6 @@ 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)
|
||||
|
||||
@@ -121,9 +102,6 @@ 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:
|
||||
@@ -147,14 +125,6 @@ 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,7 +24,8 @@ 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:
|
||||
|
||||
@@ -664,6 +664,8 @@ class OverfullAirbasesDisplay(QGroupBox):
|
||||
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)
|
||||
|
||||
@@ -671,7 +673,12 @@ class OverfullAirbasesDisplay(QGroupBox):
|
||||
self.setLayout(layout)
|
||||
|
||||
self.label = QLabel()
|
||||
layout.addWidget(self.label)
|
||||
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setWidget(self.label)
|
||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
layout.addWidget(scroll)
|
||||
|
||||
self.on_allocation_changed()
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ class GameUpdateSignal(QObject):
|
||||
debriefingReceived = Signal(Debriefing)
|
||||
|
||||
game_loaded = Signal(Game)
|
||||
game_generated = Signal(Game)
|
||||
|
||||
def __init__(self):
|
||||
super(GameUpdateSignal, self).__init__()
|
||||
|
||||
@@ -24,6 +24,7 @@ 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
|
||||
@@ -33,10 +34,13 @@ 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
|
||||
@@ -150,6 +154,7 @@ 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)
|
||||
@@ -221,6 +226,12 @@ 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)
|
||||
|
||||
@@ -253,6 +264,8 @@ 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()
|
||||
@@ -322,7 +335,6 @@ class QLiberationWindow(QMainWindow):
|
||||
def newGame(self):
|
||||
wizard = NewGameWizard(self)
|
||||
wizard.show()
|
||||
wizard.accepted.connect(lambda: self.onGameGenerated(wizard.generatedGame))
|
||||
|
||||
def openFile(self):
|
||||
if (
|
||||
@@ -526,6 +538,14 @@ 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()
|
||||
|
||||
@@ -540,7 +560,14 @@ class QLiberationWindow(QMainWindow):
|
||||
def onDebriefing(self, debrief: Debriefing):
|
||||
logging.info("On Debriefing")
|
||||
self.debriefing = QDebriefingWindow(debrief)
|
||||
self.debriefing.show()
|
||||
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)
|
||||
|
||||
def open_tgo_info_dialog(self, tgo: TheaterGroundObject) -> None:
|
||||
QGroundObjectMenu(self, tgo, tgo.control_point, self.game).show()
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QIcon
|
||||
from PySide6.QtGui import QIcon, QPixmap
|
||||
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
|
||||
from qt_ui.uiconstants import AIRCRAFT_BANNERS, VEHICLE_BANNERS
|
||||
|
||||
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__}")
|
||||
|
||||
|
||||
class QUnitInfoWindow(QDialog):
|
||||
@@ -29,14 +63,10 @@ class QUnitInfoWindow(QDialog):
|
||||
header = QLabel(self)
|
||||
header.setGeometry(0, 0, 720, 360)
|
||||
|
||||
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")
|
||||
banner_path = banner_path_for(unit_type)
|
||||
if not banner_path.exists():
|
||||
banner_path = MISSING_BANNER_PATH
|
||||
pixmap = QPixmap(banner_path)
|
||||
header.setPixmap(pixmap.scaled(header.width(), header.height()))
|
||||
self.layout.addWidget(header, 0, 0)
|
||||
|
||||
|
||||
@@ -220,10 +220,7 @@ 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,19 +9,17 @@ 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
|
||||
@@ -119,13 +117,11 @@ class QBaseMenu2(QDialog):
|
||||
return self.game_model.game.settings.enable_base_capture_cheat
|
||||
|
||||
def cheat_capture(self) -> None:
|
||||
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)
|
||||
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()
|
||||
|
||||
@property
|
||||
def has_transfer_destinations(self) -> bool:
|
||||
|
||||
@@ -3,10 +3,8 @@ 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.windows.GameUpdateSignal import GameUpdateSignal
|
||||
from qt_ui.cheatcontext import game_state_modifying_cheat_context
|
||||
from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import (
|
||||
QGroundForcesStrategySelector,
|
||||
)
|
||||
@@ -52,15 +50,12 @@ class QGroundForcesStrategy(QGroupBox):
|
||||
self.setLayout(layout)
|
||||
|
||||
def cheat_alter_front_line(self, enemy_point: ControlPoint, advance: bool) -> None:
|
||||
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)
|
||||
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)
|
||||
|
||||
43
qt_ui/windows/gameoverdialog.py
Normal file
@@ -0,0 +1,43 @@
|
||||
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)
|
||||
@@ -4,6 +4,8 @@ from PySide6.QtWidgets import (
|
||||
QFrame,
|
||||
QLabel,
|
||||
QVBoxLayout,
|
||||
QScrollArea,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from game import Game
|
||||
@@ -35,6 +37,16 @@ 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>'
|
||||
@@ -42,12 +54,12 @@ class QFlightPayloadTab(QFrame):
|
||||
docsText.setAlignment(Qt.AlignCenter)
|
||||
docsText.setOpenExternalLinks(True)
|
||||
|
||||
layout.addLayout(PropertyEditor(self.flight))
|
||||
scrolling_layout.addLayout(PropertyEditor(self.flight))
|
||||
self.loadout_selector = DcsLoadoutSelector(flight)
|
||||
self.loadout_selector.currentIndexChanged.connect(self.on_new_loadout)
|
||||
layout.addWidget(self.loadout_selector)
|
||||
layout.addWidget(self.payload_editor)
|
||||
layout.addWidget(docsText)
|
||||
scrolling_layout.addWidget(self.loadout_selector)
|
||||
scrolling_layout.addWidget(self.payload_editor)
|
||||
scrolling_layout.addWidget(docsText)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
from PySide6.QtCore import QItemSelectionModel, QPoint
|
||||
from PySide6.QtCore import QItemSelectionModel, QPoint, QModelIndex
|
||||
from PySide6.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide6.QtWidgets import QHeaderView, QTableView
|
||||
from PySide6.QtWidgets import (
|
||||
QHeaderView,
|
||||
QTableView,
|
||||
QStyledItemDelegate,
|
||||
QDoubleSpinBox,
|
||||
QWidget,
|
||||
QStyleOptionViewItem,
|
||||
)
|
||||
|
||||
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__()
|
||||
@@ -16,8 +37,9 @@ class QFlightWaypointList(QTableView):
|
||||
self.flight = flight
|
||||
|
||||
self.model = QStandardItemModel(self)
|
||||
self.model.itemChanged.connect(self.on_changed)
|
||||
self.setModel(self.model)
|
||||
self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])
|
||||
self.model.setHorizontalHeaderLabels(HEADER_LABELS)
|
||||
|
||||
header = self.horizontalHeader()
|
||||
header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
|
||||
@@ -27,27 +49,52 @@ class QFlightWaypointList(QTableView):
|
||||
self.indexAt(QPoint(1, 1)), QItemSelectionModel.Select
|
||||
)
|
||||
|
||||
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()
|
||||
self.altitude_editor_delegate = AltitudeEditorDelegate(self)
|
||||
self.setItemDelegateForColumn(1, self.altitude_editor_delegate)
|
||||
|
||||
self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"])
|
||||
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()
|
||||
|
||||
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)
|
||||
self.model.setHorizontalHeaderLabels(HEADER_LABELS)
|
||||
|
||||
def add_waypoint_row(
|
||||
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(
|
||||
self, row: int, flight: Flight, waypoint: FlightWaypoint
|
||||
) -> None:
|
||||
self.model.insertRow(self.model.rowCount())
|
||||
@@ -55,15 +102,25 @@ class QFlightWaypointList(QTableView):
|
||||
self.model.setItem(row, 0, QWaypointItem(waypoint, row))
|
||||
|
||||
altitude = int(waypoint.alt.feet)
|
||||
altitude_type = "AGL" if waypoint.alt_type == "RADIO" else "MSL"
|
||||
altitude_item = QStandardItem(f"{altitude} ft {altitude_type}")
|
||||
altitude_item.setEditable(False)
|
||||
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)
|
||||
|
||||
tot = self.tot_text(flight, waypoint)
|
||||
tot_item = QStandardItem(tot)
|
||||
tot_item.setEditable(False)
|
||||
self.model.setItem(row, 2, tot_item)
|
||||
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)
|
||||
|
||||
def tot_text(self, flight: Flight, waypoint: FlightWaypoint) -> str:
|
||||
if waypoint.waypoint_type == FlightWaypointType.TAKEOFF:
|
||||
|
||||
@@ -28,6 +28,7 @@ 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(
|
||||
@@ -94,6 +95,7 @@ def wrap_label_text(text: str, width: int = 100) -> str:
|
||||
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
|
||||
@@ -139,7 +141,6 @@ 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")
|
||||
@@ -228,16 +229,14 @@ class NewGameWizard(QtWidgets.QWizard):
|
||||
mod_settings,
|
||||
self.lua_plugin_manager,
|
||||
)
|
||||
self.generatedGame = generator.generate()
|
||||
game = generator.generate()
|
||||
|
||||
if (
|
||||
AirWingConfigurationDialog(self.generatedGame, self).exec()
|
||||
== QDialog.DialogCode.Rejected
|
||||
):
|
||||
if AirWingConfigurationDialog(game, self).exec() == QDialog.DialogCode.Rejected:
|
||||
logging.info("Aborted air wing configuration")
|
||||
return
|
||||
|
||||
self.generatedGame.begin_turn_0(squadrons_start_full=use_new_squadron_rules)
|
||||
game.begin_turn_0(squadrons_start_full=use_new_squadron_rules)
|
||||
GameUpdateSignal.get_instance().game_generated.emit(game)
|
||||
|
||||
super(NewGameWizard, self).accept()
|
||||
|
||||
|
||||
@@ -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@e74c3885a55affda09b36907aa7afe5588b0702f#egg=pydcs
|
||||
pyinstaller==5.7.0
|
||||
git+https://github.com/pydcs/dcs@e006f0df6db933fa34b2d5cb04db41653537503e#egg=pydcs
|
||||
pyinstaller==5.12.0
|
||||
pyinstaller-hooks-contrib==2022.14
|
||||
pyproj==3.4.1
|
||||
PySide6==6.4.1
|
||||
|
||||
BIN
resources/campaigns/exercise_bright_star.miz
Normal file
133
resources/campaigns/exercise_bright_star.yaml
Normal file
@@ -0,0 +1,133 @@
|
||||
---
|
||||
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
|
||||
@@ -12,10 +12,10 @@ version: "10.9"
|
||||
squadrons:
|
||||
# Tonopah Airport
|
||||
17:
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
- primary: TARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
size: 12
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
|
||||
@@ -42,9 +42,9 @@ squadrons:
|
||||
#San Julian
|
||||
11:
|
||||
- primary: BAI
|
||||
secondary: air-to-ground
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
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.4"
|
||||
version: "10.9"
|
||||
recommended_player_faction: US Navy 2005
|
||||
recommended_enemy_faction: China 2010
|
||||
miz: gran_polvorin.miz
|
||||
@@ -111,10 +111,6 @@ 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.6"
|
||||
version: "10.9"
|
||||
miz: operation_allied_sword.miz
|
||||
performance: 2
|
||||
recommended_start_date: 2004-07-17
|
||||
@@ -325,10 +325,6 @@ squadrons:
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Su-24M Fencer-D
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 2
|
||||
# Bassel Al-Assad
|
||||
21:
|
||||
- primary: TARCAP
|
||||
@@ -342,15 +338,15 @@ squadrons:
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 2
|
||||
size: 1
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- IL-76MD
|
||||
size: 2
|
||||
size: 1
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- A-50
|
||||
size: 2
|
||||
size: 1
|
||||
- primary: Strike
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
@@ -399,4 +395,4 @@ squadrons:
|
||||
secondary: air-to-ground
|
||||
aircraft:
|
||||
- Mi-8MTV2 Hip
|
||||
size: 4
|
||||
size: 2
|
||||
|
||||
@@ -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.1"
|
||||
version: "10.9"
|
||||
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: 2
|
||||
size: 1
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 2
|
||||
size: 1
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- IL-78MD
|
||||
size: 2
|
||||
size: 1
|
||||
# OPFOR First FOB
|
||||
FOB Gecitkale:
|
||||
- primary: CAS
|
||||
|
||||
@@ -43,10 +43,10 @@ squadrons:
|
||||
aircraft:
|
||||
- AV-8B Harrier II Night Attack
|
||||
size: 8
|
||||
- primary: BARCAP
|
||||
secondary: air-to-air
|
||||
- primary: TARCAP
|
||||
secondary: any
|
||||
aircraft:
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
size: 12
|
||||
- primary: CAS
|
||||
secondary: air-to-ground
|
||||
|
||||
@@ -66,6 +66,11 @@ 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.4"
|
||||
version: "10.9"
|
||||
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.1"
|
||||
version: "10.9"
|
||||
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.4"
|
||||
version: "10.9"
|
||||
advanced_iads: true
|
||||
recommended_player_faction: US Navy 2005
|
||||
recommended_enemy_faction: Iran 2015
|
||||
@@ -77,15 +77,7 @@ squadrons:
|
||||
- primary: AEW&C
|
||||
aircraft:
|
||||
- A-50
|
||||
size: 2
|
||||
- primary: Refueling
|
||||
aircraft:
|
||||
- IL-78M
|
||||
size: 2
|
||||
- primary: Transport
|
||||
aircraft:
|
||||
- IL-78MD
|
||||
size: 2
|
||||
size: 1
|
||||
- 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.1"
|
||||
version: "10.9"
|
||||
recommended_start_date: 2006-08-03
|
||||
recommended_player_money: 900
|
||||
recommended_enemy_money: 1200
|
||||
|
||||
560
resources/customized_payloads/F-15ESE.lua
Normal file
@@ -0,0 +1,560 @@
|
||||
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"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["CLSID"] = "ALQ_184",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{8D399DDA-FF81-4F14-904D-099B34FE7918}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{8D399DDA-FF81-4F14-904D-099B34FE7918}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[4] = {
|
||||
@@ -25,14 +25,21 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{6CEB49FC-DED8-4DED-B053-E1F033FF72D3}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 10,
|
||||
},
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 4,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 7,
|
||||
}, },
|
||||
["tasks"] = {
|
||||
[1] = 11,
|
||||
},
|
||||
@@ -41,7 +48,7 @@ local unitPayloads = {
|
||||
["name"] = "CAS",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["CLSID"] = "ALQ_184",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
@@ -61,19 +68,19 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
@@ -85,15 +92,15 @@ local unitPayloads = {
|
||||
["name"] = "STRIKE",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["CLSID"] = "ALQ_184",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["CLSID"] = "{AB8B8299-F1CC-4359-89B5-2172E0CF4A5A}",
|
||||
["num"] = 8,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["CLSID"] = "{AB8B8299-F1CC-4359-89B5-2172E0CF4A5A}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[4] = {
|
||||
@@ -105,19 +112,19 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{BCE4E030-38E9-423E-98ED-24BE3DA87C32}",
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
@@ -129,7 +136,7 @@ local unitPayloads = {
|
||||
["name"] = "ANTISHIP",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["CLSID"] = "ALQ_184",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
@@ -149,19 +156,19 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
@@ -173,7 +180,7 @@ local unitPayloads = {
|
||||
["name"] = "SEAD",
|
||||
["pylons"] = {
|
||||
[1] = {
|
||||
["CLSID"] = "{6D21ECEA-F85B-4E8D-9D51-31DC9B8AA4EF}",
|
||||
["CLSID"] = "ALQ_184",
|
||||
["num"] = 6,
|
||||
},
|
||||
[2] = {
|
||||
@@ -181,7 +188,7 @@ local unitPayloads = {
|
||||
["num"] = 8,
|
||||
},
|
||||
[3] = {
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["CLSID"] = "{E6A6262A-CA08-4B3D-B030-E1A993B98452}",
|
||||
["num"] = 3,
|
||||
},
|
||||
[4] = {
|
||||
@@ -193,19 +200,19 @@ local unitPayloads = {
|
||||
["num"] = 9,
|
||||
},
|
||||
[6] = {
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 1,
|
||||
},
|
||||
[7] = {
|
||||
["CLSID"] = "{AIS_ASQ_T50}",
|
||||
["CLSID"] = "{C8E06185-7CD6-4C90-959F-044679E90751}",
|
||||
["num"] = 10,
|
||||
},
|
||||
[8] = {
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 7,
|
||||
},
|
||||
[9] = {
|
||||
["CLSID"] = "{444BA8AE-82A7-4345-842E-76154EFCCA46}",
|
||||
["CLSID"] = "{F376DBEE-4CAE-41BA-ADD9-B2910AC95DEC}",
|
||||
["num"] = 4,
|
||||
},
|
||||
},
|
||||
|
||||
1045
resources/dcs/beacons/sinai.json
Normal file
@@ -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,6 +18,7 @@ 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)
|
||||
|
||||
@@ -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,6 +19,7 @@ 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)
|
||||
|
||||
@@ -17,9 +17,14 @@ 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
|
||||
@@ -44,7 +49,11 @@ 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
|
||||
@@ -75,6 +84,7 @@ infantry_units:
|
||||
- Infantry M249
|
||||
- Infantry M4
|
||||
- MANPADS Stinger
|
||||
- Mortar 2B11 120mm
|
||||
preset_groups:
|
||||
- Hawk
|
||||
- Patriot
|
||||
|
||||
79
resources/factions/egypt_2000.yaml
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
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
|
||||
@@ -13,6 +13,7 @@ aircrafts:
|
||||
- C-130J-30 Super Hercules
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F-4E Phantom II
|
||||
- UH-1H Iroquois
|
||||
|
||||
@@ -13,6 +13,7 @@ aircrafts:
|
||||
- C-130J-30 Super Hercules
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Mirage 2000C
|
||||
|
||||
@@ -20,6 +20,7 @@ aircrafts:
|
||||
- F-14B Tomcat
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- Ka-50 Hokum
|
||||
|
||||
@@ -20,6 +20,7 @@ aircrafts:
|
||||
- F-14B Tomcat
|
||||
- F-15C Eagle
|
||||
- F-15E Strike Eagle
|
||||
- F-15E Strike Eagle (Suite 4+)
|
||||
- F-16CM Fighting Falcon (Block 50)
|
||||
- F/A-18C Hornet (Lot 20)
|
||||
- S-3B Viking
|
||||
|
||||
@@ -22,6 +22,7 @@ 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)
|
||||
|
||||
@@ -10,6 +10,7 @@ unit_lost_events = {} -- killed units will be added via S_EVENT_UNIT_LOST
|
||||
kill_events = {} -- killed units will be added via S_EVENT_KILL
|
||||
base_capture_events = {}
|
||||
destroyed_objects_positions = {} -- will be added via S_EVENT_DEAD event
|
||||
killed_ground_units = {} -- keep track of static ground object deaths
|
||||
mission_ended = false
|
||||
|
||||
local function ends_with(str, ending)
|
||||
@@ -39,6 +40,7 @@ function write_state()
|
||||
["kill_events"] = kill_events,
|
||||
["mission_ended"] = mission_ended,
|
||||
["destroyed_objects_positions"] = destroyed_objects_positions,
|
||||
["killed_ground_units"] = killed_ground_units,
|
||||
}
|
||||
if not json then
|
||||
local message = string.format("Unable to save DCS Liberation state to %s, JSON library is not loaded !", _debriefing_file_location)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 17th Weapons Squadron
|
||||
nickname: Hooters
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 17th WS AF90 Low Vis Clean
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 333rd Fighter Squadron
|
||||
nickname: Lancers
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 333rd Rocketeers FS AF87-199 333 FGS
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 334th Fighter Squadron
|
||||
nickname: Eagles
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 334th Eagles FS AF89 Aim High
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 335th Fighter Squadron
|
||||
nickname: Chiefs
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 335th Chiefs FS AF89 Low Vis Combat
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 336th Fighter Squadron
|
||||
nickname: Rocketeers
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 336th Rocketeers FS AF88 Low Vis Combat
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 389th Fighter Squadron
|
||||
nickname: Thunderbolts
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 389th Thunderbolts FS AF90 Low Vis Combat
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 391st Fighter Squadron
|
||||
nickname: Bold Tigers
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 391st Bold Tigers FS AF90-241 High Vis Combat
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 492th Fighter Squadron
|
||||
nickname: Madhatters
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 492nd Madhatters FS AF91-315 Vader
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 494th Fighter Squadron
|
||||
nickname: Panthers
|
||||
female_pilot_percentage: 7
|
||||
country: USA
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: USAF 494th Panthers FS 91-603 75th D-Day Anniversary
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
name: 69th Squadron
|
||||
nickname: Hammers
|
||||
female_pilot_percentage: 7
|
||||
country: Israel
|
||||
role: Strike Fighter
|
||||
aircraft: F-15E Strike Eagle (Suite 4+)
|
||||
livery: IDF RA'AM, 69 Hammer Sqn
|
||||
@@ -63,6 +63,10 @@ QToolBar::separator {
|
||||
height:1px;
|
||||
}
|
||||
|
||||
QToolBar QToolButton {
|
||||
font: bold 18px;
|
||||
}
|
||||
|
||||
QMenu::item:selected {
|
||||
background: #435466;
|
||||
}
|
||||
|
||||
44
resources/theaters/sinai/info.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Sinai
|
||||
timezone: +2
|
||||
daytime:
|
||||
dawn: [6, 8]
|
||||
day: [8, 16]
|
||||
dusk: [16, 18]
|
||||
night: [0, 5]
|
||||
climate:
|
||||
day_night_temperature_difference: 8.0
|
||||
seasons:
|
||||
winter:
|
||||
average_pressure: 29.86 # TODO: Find real-world data
|
||||
average_temperature: 10.0
|
||||
weather:
|
||||
thunderstorm: 1
|
||||
raining: 25
|
||||
cloudy: 35
|
||||
clear: 40
|
||||
spring:
|
||||
weather:
|
||||
thunderstorm: 1
|
||||
raining: 10
|
||||
cloudy: 30
|
||||
clear: 60
|
||||
summer:
|
||||
average_pressure: 29.98 # TODO: Find real-world data
|
||||
average_temperature: 28.5
|
||||
weather:
|
||||
thunderstorm: 1
|
||||
raining: 5
|
||||
cloudy: 30
|
||||
clear: 65
|
||||
fall:
|
||||
weather:
|
||||
thunderstorm: 1
|
||||
raining: 15
|
||||
cloudy: 35
|
||||
clear: 50
|
||||
turbulence:
|
||||
high_avg_yearly_turbulence_per_10cm: 9
|
||||
low_avg_yearly_turbulence_per_10cm: 3.5
|
||||
solar_noon_turbulence_per_10cm: 3.5
|
||||
midnight_turbulence_per_10cm: -3
|
||||
BIN
resources/theaters/sinai/landmap.p
Normal file
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 333 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 236 KiB After Width: | Height: | Size: 236 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |