347 Commits

Author SHA1 Message Date
Raffson
f962032dc8 Fix mypy complaining about something that was already covered -_- 2023-04-02 22:39:51 +02:00
Raffson
f498c08dc9 Inject ground units to TgoConfig preset group if fill and has no unit 2023-04-02 22:39:51 +02:00
Raffson
f461a46cdd Update JAS-39 2023-04-02 22:39:50 +02:00
Raffson
f9d27f810b Update Sufa to v3.6 2023-04-01 23:28:55 +02:00
Raffson
b49c99d5cf Fix edge-case bug in layout's group size 2023-04-01 17:45:35 +02:00
Raffson
f05a6888d3 Changelog 1.1.1 2023-04-01 17:45:35 +02:00
Raffson
0413474576 version bump 2023-04-01 17:45:34 +02:00
Raffson
b4277c25c5 Fix incorrect plugin (option) values 2023-04-01 17:45:34 +02:00
Raffson
d4efd72f6d Fix procurement bug when coalition has no squadrons
If a user removes all squadrons manually, the faction may still contain aircraft but no budget for aircraft should be allocated at all. In such a case, all money should go to ground units...
2023-04-01 15:54:32 +02:00
Raffson
3060645a26 Extra measure to avoid 0 units in transfer map 2023-03-18 22:10:22 +01:00
Raffson
0c6c578e35 Filter units in deepcopy of faction instead 2023-03-18 21:29:35 +01:00
Raffson
e08f134c32 Cleanup unused imports 2023-03-18 21:20:46 +01:00
Raffson
23038671b3 Disable altitude restriction for DEAD
This is automatically being set by pydcs as soon as you set the altitude
2023-03-18 21:19:45 +01:00
dependabot[bot]
ae24b5e1ea Bump webpack from 5.75.0 to 5.76.2 in /client
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.2.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.2)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-18 21:16:49 +01:00
Raffson
918903f831 Fix plugins not loading properly after restart of app 2023-03-18 00:52:11 +01:00
Raffson
f3241c286c Fix convoy bug mentioned by Adecarcer 2023-03-17 23:55:36 +01:00
Raffson
e682176040 Polish DEAD strike ingress tasking 2023-03-17 23:41:17 +01:00
Raffson
8bea0c5cf8 Polishing CarpetBombing task 2023-03-17 23:41:17 +01:00
Raffson
497fd56efc Add ST wpt to F-14A 2023-03-12 22:15:34 +01:00
Raffson
100f4b66fd Update changelog.md 2023-03-12 20:22:53 +01:00
MetalStormGhost
e2797fd796 F/A-18E/F/G Super Hornet mod support v2.1 (#84)
* Don't generate runway data for heliports.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2710.

* Remove dead code.

* Implemented support for Chiller Juice Studios F/A-18E/F/G Super Hornet mod

The Chiller Juice Studios Super Hornet mod works like this: it changes the stock F/A-18C Hornet into F/A-18E/F/G Super Hornet / Growler. The exact variant is selected with argument 999 in the livery description.lua, which is why I chose to add the three variants in the FA-18C_hornet.yaml. This way, we can use the squadrons mechanism in Liberation to select the appropriate variant by specifying the correct livery for the squadron.

Current properties injected are wingspan / width and the custom ordnance added by the mod.

Added F/A-18E/F/G banner by Schmokedpancake and F/A-18F icon.

Resolves https://github.com/dcs-liberation/dcs_liberation/issues/2681

* Added a separate loadout file for the Chiller Juice Studios F/A-18E/F/G Super Hornet mod. Currently only replaces the FPU-8A fuel tanks with FPU-12s.

* Added the possibility to use the AI variant of the F/A-18C in campaigns, allowing different loadouts and in the future, the Super Hornet mod alongside legacy Hornets in the same campaign.

* Updated Chiller Juice Studios F/A-18E/F/G Super Hornet mod support to version 2.0. Removed the 1.x version property and pylon injection since they are no longer necessary, since 2.0 adds the Super Hornet variants as separate aircraft. For the same reason, removed the AI-only F/A-18C from the faction files (still retained the aircraft yaml, loadout files and icon/banner in case someone still wants to use it).

Includes F/A-18E/F/G banner by Schmokedpancake, loadouts by @sgtfuzzle17 and F/A-18E/F icons.

* Added Super Hornet, Growler squadrons and Growler banner by @sgtfuzzle17
The squadrons include the model of the airframe in their name, so they can be referenced directly from campaign yaml files without the risk of conflicting with the same squadron of a different era, flying a different airframe.

Also updated the E and G model icons.

Resolves #77

* Fixed a bug with the EA-18G banner not being visible in Retribution. Also added the Super Hornet variants to factions bluefor_modern and Israel-USN_2005_Allied_Sword.

* Corrected the descriptions for tandem-seat Super Hornet variants.

* Updated Chiller Juice Studios F/A-18E/F/G Super Hornet mod support to version 2.1

* Anti-ship loadouts are now named properly.

* Update changelog.md

* Update QNewGameWizard.py

---------

Co-authored-by: Dan Albert <dan@gingerhq.net>
Co-authored-by: Raffson <Raffson@users.noreply.github.com>
2023-03-12 19:17:29 +00:00
MetalStormGhost
e827d6d1d0 Swedish Military Assets Pack v1.10 support (#90)
* Added a pydcs extension for Swedish Military Assets for DCS by Currenthill v1.10 (2022-11-01). Also added shipmod decorator for the ships added in the mod and remove_ship function in faction.py

* Added unit yamls for Swedish Military Assets for DCS by Currenthill v1.10 (2022-11-01)

* Added missing RBS-15 group yaml and Ag 90 Sniper Team unit yaml.

* Fixed swedishmilitaryassetspack imports.

* Renamed some unit yamls for Swedish Military Assets pack.

* Encoded LvKv9040.yaml in UTF-8.

* Encoded Grkpbv90.yaml in UTF-8.

* Encoded BV410*.yaml in UTF-8.

* Encoded CV9040.yaml in UTF-8.

* Fixed Strv103 yaml syntax.

* Encoded Strv2000.yaml in UTF-8.

* Renamed some unit yamls for Swedish Military Assets pack.

* Renamed BV410 air-defence units.

* Added Swedish short-range air defence groups.

* Added Swedish medium- and long-range air defence groups (LvS-103 batteries).

* Added icons for some Swedish Military Assets units.

* Added faction files for:
sweden_1997
sweden_2020
2023-03-12 18:31:14 +01:00
Dan Albert
b9609a00f6 Update radio configuration for the A-10s.
The latest DCS update both added support for preset radios (two both
A-10C modules!), and re-ordered the legacy A-10C's radios so we can use
the VHF radio for intra-flight.

After this patch, the legacy A-10C will use VHF for intra-flight, the
new module will use one AN/ARC-210 for intra-flight and the other for
inter-flight comms, and both modules will have preset channels assigned.
2023-03-12 14:49:40 +01:00
Raffson
aaa06a5e88 Fix possible conficts in runway data
In case 2 carriers are present and have the same name, the kneeboard information gets screwed up...
2023-03-12 14:49:40 +01:00
Raffson
26851520c8 Account for change in name for AIM-7E 2023-03-12 14:49:39 +01:00
Raffson
cf0b5dc931 New campaign: 1968 Yankee Station by Adecarcer
Co-Authored-By: Antonio <54774215+Adecarcer@users.noreply.github.com>
2023-03-12 14:47:37 +01:00
Raffson
b2592b506f Bump pydcs (data export) 2023-03-12 14:47:36 +01:00
Raffson
e84551b301 Tweak CAP & CAS payloads for F-14A/B 2023-03-12 14:47:36 +01:00
Raffson
cebcee8d32 Tweak STRIKE ingress tasks
Use carpet-bombing instead, combine all tasks to better support guided/unguided attacks
2023-03-12 14:47:35 +01:00
Raffson
d75c6d7e9a Tweak F-15E STRIKE payload 2023-03-12 01:01:11 +01:00
Raffson
eed514a432 Fix mistake during integration of Splash Damage 2.0
Lua plugin settings would always load the default values instead
2023-03-11 21:25:51 +01:00
Raffson
218e783bb1 Force DEAD strike attack run direction 2023-03-10 23:50:45 +01:00
Raffson
e1e1689d23 Update F-14A/B payload again
Use rockets for DEAD strikes, as they seem to be very effective
2023-03-10 23:48:57 +01:00
Raffson
5c7a87dbfd Tweak DEAD Ingress WPT-tasks 2023-03-05 23:46:07 +01:00
Raffson
5e7f5a68c2 Update F-14A/B payloads 2023-03-05 23:44:47 +01:00
Raffson
5b19e0fdcd formatting 2023-03-05 23:44:47 +01:00
Raffson
648fad33d8 Force minimum altitude for launching decoys 2023-03-05 23:44:30 +01:00
Raffson
843bb30b99 Improve (SEAD) Escort tasking
- Always use holding point when it's a formation attack FP
- More accurate index for SPLIT wpt in triggered action

cleanup
2023-03-05 23:44:30 +01:00
Raffson
0f093d5f54 Fix winchester RTB when AI uses devoys 2023-03-05 14:34:47 +01:00
Raffson
3a09a15970 Add clsid's to AIM-7E weapon data 2023-03-05 14:33:00 +01:00
Raffson
83acf8b93d Remove KS-19 HDS preset instead of regular one 2023-03-05 13:43:31 +01:00
Raffson
1793703e47 Fix F-14s not using AGM-141 during SEAD 2023-03-05 00:53:54 +01:00
Raffson
6713ee155c Migrate to Splash Damage v2.0 by RotorOps
Partial fix #64
2023-03-04 23:01:39 +01:00
Raffson
17f8830fbf Bump pydcs (miz serialization fix) 2023-03-04 15:45:18 +01:00
Raffson
24d995828f Handle corrupted preferences file
Partial fix dcs-liberation/dcs_liberation#2747
Includes pydcs bump since it contains the other half of the fix.
2023-03-04 14:10:51 +01:00
Raffson
ae379bd8f5 Introduce weighted distribution for random 2/3/4-ships 2023-02-26 21:45:56 +01:00
Raffson
f2cebc82cc Bump pydcs 2023-02-26 12:35:09 +01:00
Raffson
2fa2e16b21 Update changelog 2023-02-26 00:14:51 +01:00
Raffson
f670ccd8a6 Initialize campaign values with first campaign in the list 2023-02-26 00:04:36 +01:00
Raffson
9ffaec6d16 Fix invalid weapons fallback names
Also add logging to detect invalid fallback names more easily
2023-02-25 19:13:12 +01:00
Raffson
60de35ddf3 Re-create game-loop in case of abort
Fixes #96
2023-02-25 17:50:53 +01:00
Raffson
41faf794cc Remove 'group_size_max' for A-6A 2023-02-25 16:41:40 +01:00
Raffson
be37b4b3e7 Lower max line length for airfield in kneeboard 2023-02-19 18:58:01 +01:00
Raffson
3598b07185 Bump pydcs
Fix #95
2023-02-19 18:42:50 +01:00
Raffson
602f88dab9 More options for better control over procurement 2023-02-19 14:14:33 +01:00
Raffson
a4f5c290b5 Remove unused method 2023-02-18 21:07:27 +01:00
Raffson
8e42df11f3 Fix security issue 2023-02-18 18:59:54 +01:00
Raffson
8c6daa0976 Update weapons data by erogar 2023-02-18 18:59:53 +01:00
Raffson
bb629a7be2 Weapons data migration (changing names) 2023-02-18 18:59:53 +01:00
Raffson
80a7d7da2d Add wind, cloud base and BRC info to kneeboard 2023-02-18 18:59:52 +01:00
holyorangejuice
c45795434a WRL Campaign / Updated Faction
New WRL Campaign plus changed to WRL redfor faction.
2023-02-16 19:23:50 +01:00
MetalStormGhost
04971f3f65 F-4 mod support update to v2.8.1.01 Standalone (#87)
Updated the VSN F-4 mod support to v2.8.1.01 Standalone + 29Jan23 Patch, which adds the land based F-4C variant. Includes banner by Datoneslav (used with permission) and the official VSN logo (also used with permission).
2023-02-15 20:24:24 +01:00
Raffson
6bf5356d00 Add A-4E to USA 1965/1975 + WRL-TFB 2023-02-15 20:12:46 +01:00
Raffson
0cd7db633b Add small example of #22 in AOD 2023-02-12 21:36:43 +01:00
Raffson
d9967bda8f Add logic for specification of ground_units in campaign yaml file
Resolves #22
2023-02-12 21:36:43 +01:00
Raffson
34b9d8c334 Add 'SA-15 SHORAD' preset group 2023-02-12 21:21:55 +01:00
Raffson
12ff6679e4 Read squadrons from SavedGames/DCS/Retribution 2023-02-12 17:44:25 +01:00
Raffson
4f9a250a9e Cleanup print statement 2023-02-12 16:18:38 +01:00
Raffson
c622cacadc Read campaigns from SavedGames/DCS/Retribution 2023-02-12 13:56:29 +01:00
Raffson
786fb4e1c0 Tanker's type section slightly too wide on kneeboard 2023-02-11 21:52:51 +01:00
Raffson
75bc521fa5 Fixing Radios for Mirage-F1EE, Mi-8MT & Mi-24P 2023-02-11 21:52:51 +01:00
Dan Albert
71296f9180 Remove dead code. 2023-02-06 20:39:24 +01:00
Dan Albert
86672d882e Don't generate runway data for heliports.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2710.
2023-02-06 20:39:23 +01:00
Raffson
3fc82af3ac Add slider option for weighted AI procurement
Resolves #81
2023-02-05 23:32:37 +01:00
Raffson
22835e7535 Pydcs bump to avoid heli's spawning in slot 37 at Ramat David
Slot 37 in particular is a hangar, which was obviously causing issues...
2023-02-05 23:32:36 +01:00
Raffson
f979a0c23d Fix heli spawn/landing at FOB/FARP
Resolves #45
2023-02-05 23:32:36 +01:00
Raffson
5f1f2b34b7 Remove obsolete installer 2023-02-05 12:05:28 +01:00
Raffson
9ab2fc1714 Add options for number of Combined Arms slots
Resolves #74

- Game masters
- Tactical commanders
- JTAC
- Observers
2023-02-05 12:05:28 +01:00
Raffson
b25f484ef3 More renaming of Liberation into Retribution 2023-02-05 12:05:27 +01:00
Raffson
dd0b565de9 Update changelog 2023-02-04 23:57:52 +01:00
Raffson
29d88fd61d Add TOT/TOS to kneeboard for AWACS & Tankers 2023-02-04 23:57:51 +01:00
Raffson
7625725fd8 Allow F11 free camera & spectating if "external views" is allowed 2023-02-04 23:57:51 +01:00
Raffson
cef25bd2f3 Ignore TOT & spawn immediately for player flights by default 2023-02-04 21:56:52 +01:00
Raffson
1e9cee1003 Bump pydcs
Fixing a mistake that could cause an exception + improvement to callsign selection
2023-02-04 21:56:52 +01:00
Raffson
15bcc2d947 Add TOTs in briefing 2023-02-04 18:45:38 +01:00
Raffson
763dbebf13 Increase random horizontal separation for escort flights 2023-02-04 17:06:40 +01:00
Raffson
667addf25b Wrap aircraft_type in kneeboard support page 2023-02-04 17:03:31 +01:00
Raffson
1033923f68 Update React dependencies 2023-02-04 17:02:44 +01:00
Raffson
d410f9d3c4 Formatting... 2023-02-02 21:41:01 +01:00
Raffson
a1cc3412e8 Update 'black' 2023-02-02 21:37:07 +01:00
Raffson
a98c9ffe8d When using 'isinstance', TYPE_CHECKING is NOT ENOUGH!!! 2023-02-02 21:33:54 +01:00
Raffson
5d506ca97e Fixes for Tanker TACAN
- Y band was not having range info
- Callsign number was missing
2023-02-02 21:28:17 +01:00
Raffson
51c0bfa970 Update changelog 2023-01-29 22:26:51 +01:00
Raffson
5c8e783980 Fix AAR TCN on Yankee band 2023-01-29 22:26:51 +01:00
Raffson
b7d95819c1 Don't strip all digits from air support's callsign 2023-01-29 22:26:50 +01:00
MetalStormGhost
bb0bf66eac Improved the F-16I Sufa & F-16D mod v3.2 loadouts. 2023-01-29 12:36:16 +01:00
Raffson
be7008722b Bump pydcs 2023-01-28 00:31:42 +01:00
MetalStormGhost
5535282894 F-16D Barak Block 30 and Block 40 support
Added support for F-16D Barak Block 30 and Block 40 variants. Still using F-16C Barak banners as placeholders until I snap some good ones from the two-seaters.

Resolves #78
2023-01-26 15:37:48 +01:00
MetalStormGhost
2fb5f2b738 Updated the IDF Mods Project F-16I mod support to version 3.2 2023-01-26 15:37:48 +01:00
Raffson
e87621576d Add CTLD zones to AOD campaign + version bump 2023-01-23 19:31:28 +01:00
Raffson
531625ff08 Designated CTLD zones for ControlPoints (AB/FOB/FARP)
Resolves #46
2023-01-21 00:24:42 +01:00
Raffson
0d57f710aa Fix exception when airfields use GUARD freqs 2023-01-20 22:10:02 +01:00
Raffson
0da431fe33 Cleanup print statement 2023-01-20 20:41:47 +01:00
Raffson
88f984b0a8 Configurable RF/TCN/ICLS/LINK4 with UI feedback
Resolves #70

Freq/Channel will turn orange when double booked.
Freq will turn red if GUARD freq was assigned.
2023-01-17 18:32:20 +01:00
Raffson
ddb9d6968b Reserve VHF Guard FREQ 121.5 2023-01-15 02:35:01 +01:00
MetalStormGhost
3313535b27 Add fallback terrain icons in campaign selector (#80) 2023-01-14 14:33:05 +00:00
Raffson
cccc6e5a44 Reserve GUARD freq. 243MHz
Fix #79
2023-01-13 23:03:28 +01:00
Raffson
8b802a9cbc Fix issue in migrator when package has no flights
Would cause an exception because `PackageWaypoints.create(p, c)` would call `package.departure_closest_to_target()` on its turn which raises a runtime exception if no flights are present. Makes total sense when you think about it...

Fixes #66
2023-01-13 22:26:59 +01:00
Raffson
0a3a56d4bc Propagate update_flight event in '_instant_relocate' instead
Dropped the ball here, and pretty hard too...
2023-01-13 22:26:59 +01:00
Raffson
7bae6cd562 Add package frequency
Part of #70
2023-01-13 22:26:58 +01:00
Raffson
e8151678dd Fix UI bug in CreateFlight window
Pilot's player checkbox would be enabled for non-flyable aircraft if the initially selected aircraft that was flyable.
2023-01-13 22:26:58 +01:00
Raffson
9dd6022907 Fix Apache radios
Co-Authored-By: Grizzly35 <116302517+Grizzly35@users.noreply.github.com>
2023-01-13 22:26:58 +01:00
Raffson
48e0d579cf Add package frequency & show package info on kneeboard
Resolves #71
2023-01-13 22:26:57 +01:00
Raffson
cc9f61005b Update pydcs
Solves callsign problem mentioned in discord, where they would always look like "Callsign 1X"
2023-01-07 18:05:29 +01:00
MetalStormGhost
48ba8ff255 Fixed a FrontLine import bug in flightplanbuilder.py (#72)
Restored the default patrol duration handling in PackageRefuelingFlightPlan. Desired tanker on-station time will now only apply to TheaterRefuelingFlightPlan.
2023-01-07 13:54:30 +01:00
Raffson
3f570cce5e Update build.yml 2023-01-06 21:19:31 +01:00
MetalStormGhost
01ee24c8cf Configurable tanker on-station time (#69) 2023-01-06 20:11:24 +01:00
dependabot[bot]
1deff6fa49 Bump json5 from 1.0.1 to 1.0.2 in /client (#68)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-05 19:17:30 +01:00
Raffson
4404d31c7b Remove future from requirements.txt
Dependabot warns us about this package having security issues, and it seems this package isn't used throughout the project.
2023-01-05 19:06:31 +01:00
Raffson
989562b90f Avoid appending triggers without actions
Resolves #67

The problem turned out to be split-triggers with no actions, which occurs when a STRIKE flight has no escorts in its package. Added a guard for this so the trigger isn't pushed to the mission in such a case.
2023-01-05 19:06:31 +01:00
Raffson
bd63e4484d Random 5-30min delay for ground force attack without CAS 2023-01-03 20:52:17 +01:00
Raffson
df642fe7a1 Set altitude for assault waypoint if Hercules 2023-01-02 18:58:45 +01:00
Raffson
cb8f2619f2 Update changelog 2023-01-02 02:35:14 +01:00
Raffson
2aed461b3a Add custom name for packages in ATO overview
Resolves #55
2023-01-02 02:35:14 +01:00
Raffson
f4d75a2c6f Allow edit of flight's custom name 2023-01-02 02:26:12 +01:00
Raffson
e36d51a7c2 Adjust string representation of Flight class 2023-01-02 01:22:22 +01:00
Raffson
7267b70066 Update issue templates according to new labels 2023-01-02 01:07:09 +01:00
Raffson
62d1a89269 Add clone buttons for flights & packages
Resolves #56
2023-01-02 01:04:30 +01:00
Raffson
6d0ac603f2 Generate packages according to TOT order (latest first, earliest last)
This makes sure that aircraft spawning in the air due to a shortage of parking spots, will have the earliest TOT possible. Flights with later TOTs should be generated first so that they remain at the airfield as OCA target.
2023-01-01 16:56:05 +01:00
Raffson
37f2e5bf3a Use stacking algorithm in "_generate_over_departure"
Resolves #57

"_generate_over_departure" is the method used to generate flights in the air when no more parking spots are available. Pydcs changes were required as it turned out aircraft were trying to spawn on helicopter parking spots, which forces them to start in the air by DCS itself.
2023-01-01 16:56:04 +01:00
Raffson
9ddaf50ad4 Refactor logic wrt "disable idle aircraft" 2023-01-01 16:06:36 +01:00
MetalStormGhost
42f488df0b Renamed Liberation to Retribution in the following files: (#62) 2023-01-01 15:29:08 +01:00
MetalStormGhost
d2a7de2775 Add new option in settings: OPFOR autoplanner aggressiveness (#61)
"Threatened" threshold based on a suggestion from FinCenturion. (#61)
2023-01-01 15:21:08 +01:00
Raffson
ee1fc53dd3 Add button to rename pilots in air wing 2023-01-01 00:46:31 +01:00
Raffson
192741af36 Make units selectable in faction overview during campaign gen
Resolves #35
Resolves #40
2022-12-31 23:27:11 +01:00
Raffson
cd4ace4ad5 Rename custom factions folder 2022-12-30 13:59:23 +01:00
Raffson
39426dde45 Option to define culling exclusion radius in campaign yaml file 2022-12-27 22:29:08 +01:00
Raffson
bd4eb3e105 Fix wrong payload names for ANTISHIP in F16/18 2022-12-27 21:59:41 +01:00
Raffson
c37c56c879 Air Assault for C-130 mod
Resolves #49
2022-12-27 21:51:14 +01:00
Raffson
d26fc84316 Automatically set 'herculescargo' plugin if present in air wing 2022-12-27 21:51:13 +01:00
Raffson
33586aaf4f Remove C47 from AI DB flight planner
Until this aircraft actually drop troops, it's not air assault capable
2022-12-27 21:51:13 +01:00
Raffson
650b06d7c8 Avoid random squadrons in config using livery overrides
Avoids choosing random squadrons during air wing configuration so that livery overrides takes precedence by default for a given aircraft type, unless the campaign file specifies a specific squadron. Users can still add a squadron manually which will automatically set the correct livery if they use a squadron preset.

Resolves #52
2022-12-27 21:51:13 +01:00
Raffson
09807b25a8 Bumb version 2022-12-27 21:51:12 +01:00
Raffson
13d42c3f83 Add icon & banner for Hercules mod
Resolves #51
2022-12-27 21:51:12 +01:00
Raffson
6966b14c6b Minor bugfix in squadron transfer destination combobox 2022-12-27 21:51:12 +01:00
Raffson
00ab0c4be2 Add "Instant Squadron Transfer" cheat option
Resolves #14
2022-12-27 21:51:11 +01:00
Raffson
5c06e74659 Auto-swap ATFLIR to LITENING for land-based Hornets
Resolves #50
2022-12-27 21:51:11 +01:00
Raffson
b0a0050725 Delay ground force attack until first friendly CAS TOT
If no friendly CAS flight was planned, no delay is planned...
2022-12-27 21:51:10 +01:00
Raffson
c5a7fbd15a Remove deprecated options 2022-12-27 21:51:10 +01:00
holyorangejuice
fa093ea237 Updated F18 and F16 payloads (#48)
Following loadouts have been configured F-16C:
- ANTISHIP: 2xAIM120C, 2xAIM9X, 2xAGM65K, 2x Fuel Tank, ALQ-184, TGP
- BAI: 2xAIM120C, 2xAIM9X, 6xAGM65D, 2x Fuel Tank, ALQ-184, TGP
- BARCAP: 4xAIM120C, 2xAIM9X, 2x Fuel Tank -> Inherited by TARCAP, Fighter Sweep & Escort
- CAS: 2xAIM120C, 2xAIM9X, 6xAGM65H, 2x Fuel Tank, ALQ-184, TGP
- DEAD: 2xAIM120C, 2xAIM9X, 2xAGM154A, 2x Fuel Tank, ALQ-184, TGP + HTS
- Ferry: 2xAIM120C, 2xAIM9X, 2x Traveling Pod, 2x Fuel Tank
- OCA Runway: 2xAIM120C, 2xAIM9X, 2xGBU31v3, 2x Fuel Tank, ALQ-184, TGP
- SEAD: 2xAIM120C, 2xAIM9X, 2xAGM88C, 2x Fuel Tank, ALQ-184, TGP + HTS -> Inherited by SEAD Escort
- STRIKE: 2xAIM120C, 2xAIM9X, 2xGBU31v1, 2x Fuel Tank, ALQ-184, TGP

Following loadouts have been configured F-18C:
- ANTISHIP: 1xAIM120C, 2xAIM9X, 4xAGM84D, 1x Fuel Tank, ATFLIR
- BAI: 1xAIM120C, 2xAIM9X, 4xAGM65F, 1x Fuel Tank, ATFLIR
- BARCAP: 5xAIM120C, 2xAIM9X, 2x Fuel Tank -> Inherited by TARCAP, Fighter Sweep & Escort
- CAS: 1xAIM120C, 2xAIM9X, 4xAGM65F, 1x Fuel Tank, ATFLIR
- DEAD: 1xAIM120C, 2xAIM9X, 4xAGM154C, 2x Fuel Tank, ATFLIR
- Ferry: 1xAIM120C, 2xAIM9X, 3x Fuel Tank, ATFLIR
- OCA Runway: 1xAIM120C, 2xAIM9X, 2xGBU31v4, 2x Fuel Tank, ATLFIR
- SEAD: 1xAIM120C, 2xAIM9X, 2xAGM88C, 2x Fuel Tank, TGP + HTS -> Inherited by SEAD Escort
- STRIKE: 1xAIM120C, 2xAIM9X, 2xGBU31v2, 2x Fuel Tank, ATFLIR
2022-12-25 15:42:05 +01:00
holyorangejuice
782759bd48 WRL mission yaml updates (#47)
- Removed/changed some units from WRL missions.
- Minor changes across all missions.
- Added frontline length recommendations to each mission yaml.
2022-12-23 12:54:56 +01:00
Raffson
653e507e89 Set "RTB on bingo" false for ferry flights 2022-12-20 20:56:30 +01:00
Raffson
4f9c7a7370 Adjust issue templates 2022-12-20 20:46:33 +01:00
Raffson
c114256b82 Renaming Liberation
Resolves #42
2022-12-20 20:46:32 +01:00
MetalStormGhost
cdd72820b4 A-4E-C mod support v2.1.0 (#43)
* Updated a4ec.py to Community A-4E-C mod version 2.1.0

* Updated changelog.

Updated A-4E mod version in QNewGameWizard.py (#44)

Co-Authored-By: Raffson <Raffson@users.noreply.github.com>
2022-12-19 23:07:35 +01:00
Raffson
4b8e7e972e Relocate layouts.p 2022-12-19 19:08:25 +01:00
Raffson
68b04b0e46 Fix broken factions
Due to the F-16A MLU having a wrong filename, all factions that included it got broken. Also fixed an excessive comma in USN 1985
2022-12-19 19:06:58 +01:00
holyorangejuice
d00b7a176e WRL Factions Update (#41)
Changed WRL blue,red and added red insurgent faction.
2022-12-19 18:48:04 +01:00
Dan Albert
3c3abd484f Fix livery for VF-33 F-14A squadron.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2610.
2022-12-18 22:12:23 +01:00
Dan Albert
9d5eece5a5 Fix encoding issues with the Peru faction.
Not really sure what's going on here, but presumably it's UTF-16 and
UTF-8 fighting.
2022-12-18 22:12:23 +01:00
Dan Albert
f08df08c7e Add changelog notes that were missed in PRs. 2022-12-18 22:12:23 +01:00
DillieKoe
44e5bc646d Add Peru faction.
A new Peru faction with a mirage squadon
2022-12-18 22:08:20 +01:00
ColonelAkirNakesh
74eb8f38f9 Override liveries for Russian aircraft in bluefor faction. 2022-12-18 22:08:20 +01:00
Raffson
5ff8fb170d Only check for ground units in capture triggers 2022-12-18 16:42:40 +01:00
Raffson
39a5b486d3 Start using own pydcs fork 2022-12-18 16:42:39 +01:00
Raffson
6664a139d8 Support for Ka-50 Black Shark 3
Resolves #38
2022-12-18 16:42:39 +01:00
Raffson
d5de5b3a78 Stack aircraft when spawning right above CP
Should prevent mid-air collisions in most cases, though I'm still worried about "off map" spawns that can possibly collide, though an easy fix would be to manually use time-spacing.
Alternatively we need to treat it as a special case, assigning different altitudes to avoid collisions during the first leg of the flight if that turns out to be the case...
2022-12-18 16:42:38 +01:00
Raffson
c3024e38a6 Update WRL factions 2022-12-17 16:03:32 +01:00
Raffson
cfd05400d2 Update changelog 2022-12-17 16:03:31 +01:00
Raffson
64992a2949 Wrap target description for strike waypoints on kneeboard
Resolve #39
2022-12-17 16:02:29 +01:00
Raffson
8ea7656362 Fix exception when trying to add extra waypoints 2022-12-17 16:02:28 +01:00
Raffson
cd760c45a8 Updating WRL campaigns 2022-12-17 16:02:28 +01:00
MetalStormGhost
15f6c8dd85 Add new performance option: Disable idle aircraft at airfields (#37) 2022-12-16 18:59:21 +01:00
MetalStormGhost
7d2dd0bcbe Add option to only generate night missions (#36)
Replaces the existing checkbox to disable night missions with a selector that allows you to choose between Day/Night/Day&Night
2022-12-13 19:08:56 +01:00
Raffson
608525d5b4 Add game master slot & more observer/commander slots 2022-12-11 21:36:41 +01:00
Raffson
49d4d508d4 Add F-15E payload for DEAD 2022-12-11 21:35:43 +01:00
Raffson
0e286ab7b5 Include B-1B payload for DEAD 2022-12-11 19:36:01 +01:00
dependabot[bot]
4e27591afa Bump certifi from 2022.9.24 to 2022.12.7 (#34)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.9.24 to 2022.12.7.
- [Release notes](https://github.com/certifi/python-certifi/releases)
- [Commits](https://github.com/certifi/python-certifi/compare/2022.09.24...2022.12.07)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-11 12:21:48 +01:00
Raffson
01c5f3e2a9 Rename F-16A icon
Fixes crash trying to access AIRCRAFT_ICONS["F-16A"]
2022-12-10 19:06:17 +01:00
Raffson
436d2f87da Add F-16A MLU
Resolves #10
2022-12-10 17:28:02 +01:00
Raffson
2e2a397312 Increase Mirage F1's priority for RUNWAY_ATTACK 2022-12-10 17:28:02 +01:00
Raffson
54a5461ecb Remove duplicate campaign 2022-12-10 17:28:01 +01:00
Raffson
49ddd5ce96 Add links in descriptions of WRL's factions and campaigns 2022-12-10 17:28:01 +01:00
Raffson
e9c1e7b770 Allow external links in Campaign/Faction description 2022-12-10 17:28:01 +01:00
Raffson
c298e5bc24 Add campaign: Kutaisi to Vaziani by WRL
Resolves #31

Co-Authored-By: holyorangejuice <4298744+holyorangejuice@users.noreply.github.com>
2022-12-10 17:28:00 +01:00
MetalStormGhost
48e5e7ca95 F-100 Super Sabre mod version 2.7.18.30765 patch 20.10.22 support (#32)
Also makes F-105G "SEAD Escort" capabale
2022-12-10 17:24:22 +01:00
MetalStormGhost
55fb5befb9 F-104C is now correctly removed from the faction when the mod is not enabled. (#33) 2022-12-10 17:17:35 +01:00
Raffson
22c7e21a66 Add battle4area51 by Oscar Juliet
Still WIP as the IADS network is not fully connected

Co-Authored-By: holyorangejuice <4298744+holyorangejuice@users.noreply.github.com>
2022-12-04 21:32:23 +01:00
Raffson
a88f48f3ce Bump campaign version 2022-12-04 21:27:43 +01:00
Raffson
6c673697f0 Fix livery overrides 2022-12-04 13:40:11 +01:00
MetalStormGhost
4af7255917 Cull FlightType.TRANSPORT, FlightType.AEWC & FlightType.REFUELING surroundings
Don't create culling exclusion zones around FlightType.TRANSPORT, FlightType.AEWC & FlightType.REFUELING mission targets. (#30)
2022-12-03 22:12:35 +01:00
MetalStormGhost
dfa33724e9 B1-B bomber strike ingress now has expend=Expend.All and WeaponType.Guided strike mission. 2022-12-03 22:43:20 +02:00
Raffson
568fd35ddc Fix CAS' landing WPT 2022-12-03 16:31:03 +01:00
MetalStormGhost
bc674edb3a Updated the Caucasus landmap (#28)
It is now also suitable for campaigns set in the forested regions of Caucasus. There are corridors for ground forces from Apsheronsk to Tuapse and from Maykop to Sochi. Also, the hilly area around Ambrolauri can now be used in campaigns as well.
2022-12-03 16:25:29 +01:00
MetalStormGhost
f5c7935993 GroundForcePainter for applying custom liveries to ground units and ships (#26)
See #26 for more info
2022-12-03 16:11:22 +01:00
MetalStormGhost
0c30ce3bb3 Revert "Revert "Add support for the AI-only F-14A."" (#27)
This reverts commit 920c9b15ff.
2022-12-03 14:31:40 +01:00
Raffson
e38a6cd907 Support defining max frontline length in campaign
Resolve #25
2022-12-02 23:08:47 +01:00
Raffson
dd98a1d078 Update operation_vectrons_claw.yaml 2022-12-02 23:08:46 +01:00
Raffson
c9611cca99 Update F-14B payloads 2022-12-02 23:08:46 +01:00
RndName
8a17184ccc Fix manual debrief submit not ending mission 2022-12-02 23:08:46 +01:00
Dan Albert
e5b193f21a Changelog section for 6.1. 2022-12-02 23:08:45 +01:00
Raffson
b9c62bc44a primary flight dead => escort RTB
Make sure trigger isn't activated when split flag was already set to true
2022-12-02 23:08:45 +01:00
Raffson
243ded448d Improve bomber tasking 2022-12-02 23:08:44 +01:00
Raffson
ae561e5564 Remove 'initial' wpt from STRIKE 2022-12-02 21:37:08 +01:00
ColonelAkirNakesh
5ffb8cbd96 Update uk_1990.json (#24)
Replaced USS Tarawa with HMS Invincible.
2022-11-29 21:58:17 +01:00
Raffson
07d0e555e5 Limit spacing 2022-11-27 21:09:51 +01:00
Raffson
e5c80ef3af Move Bronco down in CAS list, lowering priority 2022-11-27 21:09:13 +01:00
Raffson
11cc62d9a9 Remove py
Attempt to remove this as it seems to be unused and according to dependabot contains a ReDoS vulnerability
2022-11-26 21:45:25 +01:00
MetalStormGhost
8c9f07643f Add some South Atlantic naval assets (#23)
- Unit yamls for several ships and added some new ones to uk_1990.json
- 2 new layouts: Aircraft Carrier / LHA with Frigate escorts
- Add "HMS Invincible" as Control Point
2022-11-26 21:01:16 +01:00
Raffson
85607b97d5 Update ai_flight_planner_db.py 2022-11-26 17:50:01 +01:00
Raffson
0bc67adea8 Remove references to liberation in issue templates 2022-11-26 17:38:00 +01:00
Starfire13
1cfab07b8b Updates Khopa's Normandy and Channel campaign to 10.0
Note that Operation Dynamo only requires a yaml file update. The .miz file is fine and is not included here.
2022-11-26 17:38:00 +01:00
Dan Albert
499e63e2c3 Unfilter the custom waypoint targets.
There doesn't appear to be any reason for us to be poking at
implementation details here aside from changing the name from "unit" to
"building" for that case. Just iterate over the known strike targets.

Making this change uncovered some latent type errors.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2564.
2022-11-26 17:38:00 +01:00
Dan Albert
bc8624d605 Remove dead code. 2022-11-26 17:37:59 +01:00
Raffson
11a3c49464 Correct radios for the MB-339A.
Cherry picked from e208df16b2ed9ea50e93b16d2eb8b8f4ffb22e82

Co-Authored-By: Dan Albert <danalbert@google.com>
2022-11-26 17:37:59 +01:00
Dan Albert
23becb49b2 Update RoleplayingPleb's campaigns.
https://github.com/dcs-liberation/dcs_liberation/issues/2558
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2561.
2022-11-26 17:37:59 +01:00
Dan Albert
72b594934e Make the casualty report scrollable.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2567.
2022-11-26 17:37:58 +01:00
Raffson
d535d312a5 Update requirements.txt 2022-11-26 17:37:58 +01:00
RndName
4b0215c16f Remove dcs capture event from state json
With the latest change we added capture zones and corresponding trigger rules for all Airfields as well so we do not need to rely on the dcs capture event S_EVENT_BASE_CAPTURED anymore.
2022-11-26 16:26:12 +01:00
RndName
8292da414d Add Airfield to list of capture zone types
This will create capture zones and the trigger rules to check for a base capture. Will fix an issue where the dcs capture event is not fired and therefore the capture not recognized by liberation
2022-11-26 16:26:12 +01:00
SnappyComebacks
b2cce52b34 Update pydcs.
Added ice halo generation.
2022-11-26 15:21:52 +01:00
Dan Albert
6ec248dfbf Move develop forward to 7.0. 2022-11-26 15:21:24 +01:00
Dan Albert
634d48e3e7 Attribution compliance for OSM data.
https://github.com/dcs-liberation/dcs_liberation/issues/2434
2022-11-26 15:20:23 +01:00
Dan Albert
391efb1b2b Fuzzle's campaign updates.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2425.
2022-11-26 15:18:51 +01:00
Dan Albert
07e64cfa02 Add Gran Polvorin campaign.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2424.
2022-11-26 15:18:51 +01:00
Dan Albert
594a8dd3cc Add Up the Coast campaign.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2223.
2022-11-26 15:18:50 +01:00
Dan Albert
f5ef509af6 Fix drop zone display for air assault.
Troops must be dropped inside this zone or they won't attack the target.
The zone needs to be drawn in the map so players don't break the flight
plan by accidentally moving the drop waypoint outside the DZ.

I've move the API for doing this out of `PatrollingFlightPlan` in favor
of a mixin so this is no longer presented as `engagement_distance` by
the flight plan. I don't love that it's still the `commit-boundary`
endpoint, but it's fine for now.

I don't know why mypy wasn't able to catch this. pycharm is also
struggling to understand this class.
2022-11-26 15:18:41 +01:00
Raffson
e541111a27 Remove obsolete files 2022-11-20 16:29:19 +01:00
Raffson
c0c310862a Increase default AI skill to Veteran 2022-11-20 16:20:09 +01:00
Dan Albert
65f09c46ca Fix typo. 2022-11-20 16:07:03 +01:00
Dan Albert
f199dd5b9f Note the start time feature in the changelog.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2400.
2022-11-20 16:07:03 +01:00
Dan Albert
56e53219c2 Changelog 6 cleanup.
Typo fixes, sort, recategorize.
2022-11-20 16:07:02 +01:00
Dan Albert
cee00d8315 Update pydcs.
Includes all the new airfields in the South Atlantic.
2022-11-20 16:07:02 +01:00
Dan Albert
cbc124eff6 Hide sim speed controls behind a flag.
Flag is only controlled from the command-line because redoing Qt layout
usually breaks things. Off by default in 6 since this feature is nowhere
near done enough to even be used experimentally (most changes to the ATO
made after the sim begins will break the game).

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2535.
2022-11-20 16:07:02 +01:00
Dan Albert
219b52fc12 Fix adding and removing waypoints in the UI.
This moves more of the logic out of the UI so mypy can actually spot
these mistakes more often.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2495.
2022-11-20 16:07:01 +01:00
Dan Albert
98e8fc88bd Add bug link for refuelzonegeometry. 2022-11-20 16:07:01 +01:00
Dan Albert
9a73a10a09 Remove refueling from sweep flights.
The timing for these doesn't work. Sweep RTBs at the same time the
package reaches its TOT. The tanker won't be on station until 1m30s
before the package reaches the refueling point.
2022-11-20 16:07:01 +01:00
Dan Albert
6ff3a34c14 Fix layout for TARCAP, Escort, and strike-like.
If there's a refuel point, nav from that, not from the patrol end/split.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1749.
2022-11-20 16:07:01 +01:00
Dan Albert
0499ded1b3 Clarify AirAssault flight plan method name.
`engagement_distance` is used elsewhere to mean commit distance, so this
looked like a bug when I stumbled across it. Rename it to be more
explicit.
2022-11-20 16:07:00 +01:00
SnappyComebacks
9fa04702af Wind & Turbulence updates from Liberation
Tune turbulence values.

Modify the range of values used to choose a wind speed.

Wind speed at high elevation IRL can range from 20 to 160 knots around the globe.
You may see wind speed generated here up to 100+ knots, but generally around 40 or so.
IRL wind speed appears to depend on the latitude of the sun, not in this implementation.

Note increased wind speeds in the changelog.

Limit wind speed to 97 knots.

Made minor adjustments to wind speed calculation.

Calculate turbulance.

Turbulance is based off time of day, and day of year.
Each theatre may adjust their turbulance parameters.

Spell turbulence correctly.
2022-11-20 16:07:00 +01:00
Raffson
89c4cc9d79 Advanced IADS: Reconnect from config
Co-Authored-By: RndName <mail@rndname.de>
2022-11-20 16:07:00 +01:00
RndName
b49562f4bc Airlift & AirAssault updates from Liberation
Cleanup and reword refuel waypoints

- rename Stopover back to CargoStop
- precise some waypoint naming

Cleanup and refine airlift and airassault waypoints

- Drop Off and Pickup now correctly worded
- Helo waypoints now represent LandingZones for pickup and dropoff

Forbid planes from air assault.

Make air assault drop-off non-optional.

There is always a drop-off location for troops.

Add docs explaining what the assault area is.

Add error handling for cargo drop waypoints.

Document some airlift waypoint behavior.

Remove unnecessary refuel waypoint in airlifts.

Remove CTLD logic from Airlift flightplan for AI

Add Ingress Point to AirAssault FlightPlan

Add simulation halt at AirAssault ingress

Remove AirAssault completly from AutoPlanner

Remove unneeded LandRefuel from Airlift

Clarify cargo waypoints for AirLift and AirAssault
2022-11-20 16:06:59 +01:00
Nosajthedevil
5f15ddc52c Add OV-10A Bronco Mod 2022-11-20 16:06:59 +01:00
Starfire13
5bde9804d6 Starfire's campaign updates for 10.5. 2022-11-20 16:06:59 +01:00
Raffson
547f1aed75 Use flag to indicate end of escort task 2022-11-19 22:47:33 +01:00
Raffson
5aed3fbb87 Add migrator
Attempts to fix incompatibilities in save files across builds. The idea is to try and handle the incompatibility as graceful as possible, and adding more "incompatibility fixes" in the future.
2022-11-19 22:47:33 +01:00
dependabot[bot]
13cb4d321a Bump loader-utils from 1.4.1 to 1.4.2 in /client (#20)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.2/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-19 16:39:49 +01:00
Raffson
004a99e080 Cleanup 2022-11-13 22:38:24 +01:00
Raffson
2aee878a40 Adjust ai flight planner for (sead) escort 2022-11-13 22:38:24 +01:00
Raffson
ca042631b7 Always propose SEAD Escort 2022-11-13 22:38:24 +01:00
Raffson
7e51061582 Formatting 2022-11-13 22:38:23 +01:00
Raffson
e71ff17a23 Fix mypy errors 2022-11-13 22:38:23 +01:00
Raffson
2270300177 Adjust flightplans for escorts 2022-11-13 22:38:23 +01:00
Raffson
3f79fa5b9d Lock speed during strike ingress 2022-11-13 22:38:22 +01:00
Raffson
322a6a700a Reconfigure escort task 2022-11-13 22:38:22 +01:00
Raffson
ba66c18f28 Allow AI to jettison empty tanks 2022-11-13 22:38:22 +01:00
Raffson
5f10154000 Add triggered waypoint switch to strike escorts 2022-11-13 22:38:21 +01:00
Raffson
66290fe1ce Lock speed towards split & set flag upon arrival 2022-11-13 22:38:21 +01:00
Raffson
0c3340b543 Set refueling probability to 10% 2022-11-13 22:38:21 +01:00
Raffson
c7ba4e8db6 Update requirements.txt 2022-11-13 22:38:20 +01:00
Raffson
65ec41c6ce Bump pydcs
Introduces option for jettisoning empty tanks
2022-11-13 22:38:20 +01:00
Raffson
395969a371 Initial attempt at fixing escorts 2022-11-13 22:38:20 +01:00
Raffson
f2f818acb3 Update changelog.md 2022-11-13 22:38:01 +01:00
Raffson
5668c37127 Fine-tune briefing formatting 2022-11-10 17:43:05 +01:00
Raffson
5f67f02cde Add tanker info to briefing 2022-11-10 17:42:47 +01:00
dependabot[bot]
f0fcd7155c Bump loader-utils from 1.4.0 to 1.4.1 in /client (#18)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v1.4.1/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v1.4.0...v1.4.1)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-10 16:07:57 +01:00
MetalStormGhost
f96348cf59 Add banner for the F-100 & add the VSN Century Series logo for F-4B (#16)
Added a banner for the F-100 Super Sabre (by datoneslav, used with permission) and added the VSN Century Series logo for the F-4B Phantom.
2022-11-10 13:14:48 +01:00
Raffson
f7f4803fdc Update Skynet to 3.0.1 2022-11-07 21:08:27 +01:00
Raffson
810c202f1b Add "save before quit" in closing dialog 2022-11-07 18:46:46 +01:00
Raffson
2d770ae05e Support for MB-339A 2022-11-07 18:46:16 +01:00
Raffson
5dfeb60a13 Tweak weather
Hopefully this prevents the generation of thunderstorms in clear skies
2022-11-05 16:03:19 +01:00
Raffson
5896bdccc1 Fix IADS exception
Exception was caused by `skynet_nodes` trying to generate node for dead units, which would eventually be thrown by `dcs_name_for_group`
2022-11-04 19:30:11 +01:00
Raffson
206c046382 Update build.yml 2022-11-01 21:56:49 +01:00
Raffson
3ce66894f0 Stop loiter 60 seconds earlier 2022-11-01 21:56:49 +01:00
Raffson
cfcdcd0838 Update README.md 2022-10-31 14:58:33 +01:00
Raffson
474980060e Change STRIKE payloads of F-15E to 4xGBU-31 2022-10-31 14:39:12 +01:00
Raffson
e816b4e58b Update requirements.txt 2022-10-30 20:48:35 +01:00
MetalStormGhost
3f4460ad6f Squadron pilot limits enabled by default (#13) 2022-10-30 19:49:33 +01:00
Raffson
92e2d45f0d Refactor mod labels/checkboxes in NewGameWizard (#12) 2022-10-30 17:09:22 +01:00
Raffson
7e7c920816 Fix for orbit's broken stop condition 2022-10-29 23:54:01 +02:00
Raffson
6606f0f58b Also look for Liberation payloads 2022-10-29 21:08:22 +02:00
MetalStormGhost
b64a0f17eb F-16I mod v2.2 support (by IDF Mods Project) (#9) 2022-10-29 17:12:50 +02:00
Raffson
7a5e878733 Corrected Polish faction Leopard 2 variants
Co-Authored-By: MetalStormGhost <89945461+MetalStormGhost@users.noreply.github.com>
2022-10-29 12:59:08 +02:00
Raffson
5edec0c0cb Update changelog.md 2022-10-29 12:49:06 +02:00
Raffson
8912b1de62 Reduce price of A-6A to 12M 2022-10-28 19:21:35 +02:00
Raffson
93328d4443 (Mod)Support for VSN A-6A Intruder v2.7.5.01
Co-Authored-By: HellAngelic <108198242+HellAngelic@users.noreply.github.com>
2022-10-27 20:36:38 +02:00
Raffson
dbb9152b9a Forgotten imports in __init__.py for mods 2022-10-27 18:15:17 +02:00
Raffson
5b8caea9ff Remove unused imports from pydcs extension f4b.py 2022-10-27 17:55:17 +02:00
Raffson
24e9936f28 Add F-16A to CAS/ANTI-SHIP/DEAD in ai_flight_planner_db.py 2022-10-25 19:59:21 +02:00
Raffson
3e9776dc89 Update requirements.txt 2022-10-25 19:17:45 +02:00
MetalStormGhost
07de598aec Enabled SEAD Escort missions to be tasked on Naval targets and OCA/Aircraft missions on FARPs. When SEAD Escorts are tasked on Naval groups, they will also target ships. 2022-10-25 19:16:21 +02:00
Raffson
2ea74d7435 Update README.md 2022-10-25 19:08:57 +02:00
Raffson
3c3b81cb1f Update requirements.txt 2022-10-24 19:53:52 +02:00
Raffson
a736bf9b09 "Optimize Imports" in game & qt-ui
Adjustments made by PyCharm
2022-10-24 18:54:41 +02:00
Raffson
ada297d331 Rename custom payload names
F-105 & F-4B still had payloads with "Liberation" in their name
2022-10-24 17:37:50 +02:00
MetalStormGhost
515d28313d Support F-84G Thunderjet mod v2.5.7.01 (#8)
* Implemented support for the VSN F-84G Thunderjet version 2.5.7.01
2022-10-23 19:00:11 +02:00
Paul Dockar
303ae3ac14 Fix barely visible text in the Intel box 2022-10-23 17:48:01 +02:00
Paul Dockar
426cbdec89 update changelog 2022-10-23 17:48:00 +02:00
Paul Dockar
dcc2256195 Adding conditions vanilla theme icons
Update uiconstants.py to include theme icon path for all weather conditions.
Moving DCS theme icons.
Created Vanilla theme icons
Fixes #1062
2022-10-23 17:48:00 +02:00
Raffson
bdbfc19c7c Attempt at fixing Dependabot issues in front-end 2022-10-23 17:42:42 +02:00
Raffson
041202d242 Update changelog.md 2022-10-23 17:33:04 +02:00
Raffson
92199b7b95 Remove SON-9 from Cold-War-Flak group
Interferes with factions that shouldn't have EWR units available, like Russia/USA 1955
2022-10-23 17:22:03 +02:00
Raffson
8bc530ee81 Fix mistakes in factions after adding KS-19/SON-9 2022-10-23 15:00:43 +02:00
Raffson
ffb9bee5af Increase minimum number of armored units in layouts 2022-10-23 14:53:18 +02:00
Raffson
7413df4ccc Add WRL campaigns
- Syria - Assault on Damascus
- Caucasus - Battle for Georgia (v8)
- Caucasus - Up The Coast

Adds recommended factions for the given campaigns

Co-Authored-By: holyorangejuice <4298744+holyorangejuice@users.noreply.github.com>
2022-10-23 14:52:14 +02:00
Raffson
12d67c71c8 Make option pages/sections scrollable 2022-10-22 16:02:32 +02:00
Raffson
8f40a507b9 Update changelog.md 2022-10-22 15:14:32 +02:00
Raffson
4adf1ee898 Adjust Cold-War-Flak group/layout 2022-10-22 00:45:16 +02:00
Raffson
4d155f0a26 Update factions for KS-19/SON-9 2022-10-22 00:45:16 +02:00
Raffson
040addc1f5 Adjust AAA_Site layout
Making this compatible with KS-19/SON-9
2022-10-22 00:45:16 +02:00
Raffson
42d1ef648e Adjust KS-19 preset
Renamed present KS-19 group to KS-19_HDS, as this used to be a preset for the KS-19 of the High Digit SAMs mod.
2022-10-22 00:45:15 +02:00
Raffson
f681598104 Add data files for KS-19 & SON-9 2022-10-22 00:45:15 +02:00
Raffson
5810bd6201 Update mission_start_EN.j2 2022-10-22 00:45:15 +02:00
Raffson
547136adc7 Update F-104 banners
Update banners of VSN_F104S_AG by GLOBAL HAWK as well as VSN_F104G & VSN_F104S banners by Datoneslav. Banners include the official VSN logos by J.P. Ferré. All used with permission.

Co-Authored-By: MetalStormGhost <89945461+MetalStormGhost@users.noreply.github.com>
2022-10-18 21:24:05 +02:00
Raffson
ce12047fa3 Add F-104G to Netherlands faction
Co-Authored-By: MetalStormGhost <89945461+MetalStormGhost@users.noreply.github.com>
2022-10-18 21:22:56 +02:00
Raffson
5ab4c70013 Update changelog.md
Indicate current supported version of F-104, which was forgotten during the update introduced after the addition of the livery scanner

Co-Authored-By: MetalStormGhost <89945461+MetalStormGhost@users.noreply.github.com>
2022-10-18 21:13:19 +02:00
Raffson
7bbb07c927 Finish F-104C mod support v2.7.11.222.01 (#6)
Finished the F-104C mod support of version 2.7.11.222.01, which was included during a new export after the livery-scanner was introduced. Includes VSN_F104C banner by GLOBAL HAWK. Banner includes the official VSN logo by J.P. Ferré. Used with permission.

Co-Authored-By: MetalStormGhost <89945461+MetalStormGhost@users.noreply.github.com>
2022-10-18 21:12:31 +02:00
Dan Albert
db20ad3232 Stop using shapely's almost_equals.
The docs say it was deprecated in favor of equals_exact with an explicit
tolerance.
2022-10-16 15:00:06 +02:00
Dan Albert
eccbfa7a64 Update pydcs.
Includes a new data export with a fix for the Apache property bug.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2470.
2022-10-16 15:00:06 +02:00
Dan Albert
6e126bd562 Send the new turn event from turn initialization.
We'll have to get smarter about this some day (since turn initialization
won't always exist), but for now we can avoid stale UI data by doing
what we've always done and refreshing the world on turn init.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2397.
2022-10-16 15:00:05 +02:00
Dan Albert
2ac9d6bd98 Add the git SHA to the build ID.
The build number is actually a pain to use. The git SHA is much more
useful.
2022-10-16 15:00:01 +02:00
Raffson
9ad92d26d4 Forgot check if advanced_iads is active 2022-10-16 14:49:13 +02:00
Raffson
ace3af8f50 Avoid targeting convoys if disabled 2022-10-16 12:51:21 +02:00
Raffson
81f459c571 Update changelog.md 2022-10-16 12:51:21 +02:00
Raffson
a215af13f5 Update README.md 2022-10-16 12:51:21 +02:00
Dan Albert
1bbe023e20 Faction update for insurgents (hard).
https://github.com/dcs-liberation/dcs_liberation/issues/2482
2022-10-16 12:51:20 +02:00
Dan Albert
34d208c19e Updated insurgent faction.
https://github.com/dcs-liberation/dcs_liberation/issues/2482
2022-10-16 12:51:20 +02:00
Dan Albert
b0c53152ef Campaign update: The Valley of Rotary.
https://github.com/dcs-liberation/dcs_liberation/issues/2481
2022-10-16 12:51:20 +02:00
Dan Albert
35f9d6ed99 Campaign update: Tblisi Gap.
https://github.com/dcs-liberation/dcs_liberation/issues/2481
2022-10-16 12:51:19 +02:00
Dan Albert
d508c1855c New campaign: The Falcon Went Over the Mountain.
https://github.com/dcs-liberation/dcs_liberation/issues/2481
2022-10-16 12:51:19 +02:00
Dan Albert
9e5f1d08d2 Add a comment explaining a wtf in alt estimation. 2022-10-16 12:51:19 +02:00
Dan Albert
0135e40f1a Fix offshore targets for WW2 factions.
This makes the oil platform a required building so that all factions can
use it. Alternatively, we could pick a different offshore target for WW2
factions, or gracefully degrade to not generating these targets for WW2
factions. This approach seems to best match the designer's intent.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2322.
2022-10-16 12:51:18 +02:00
Dan Albert
33ae428506 Make Mozdok to Maykop editable without WW2 assets.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2391.
2022-10-16 12:51:18 +02:00
Dan Albert
d89ad18825 Changelog updates. 2022-10-16 12:51:18 +02:00
Dan Albert
9f3f9ad808 Don't allow helicopters at non-FARP FOBs.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2378.
2022-10-16 12:51:18 +02:00
Dan Albert
d53bfd4542 Add additional weapon data.
Partial submission of the data in
https://github.com/dcs-liberation/dcs_liberation/issues/2348. I haven't
changed anything that conflicted with
https://github.com/dcs-liberation/dcs_liberation/pull/1858.
2022-10-16 12:51:17 +02:00
Dan Albert
febcbce413 Key the PrimaryMarker with destination presence.
This is how React recommend dealing with derived state that needs to be
reset:
https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

The problem is that the new turn will give the component new props, but
new props will not cause the state to be reset. We can either do that
manually (which React recommends only for the cases where it is
absolutely necessary:
https://reactjs.org/docs/hooks-faq.html#how-do-i-implement-getderivedstatefromprops),
or by forcing the component to be replaced by using a key.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2388.
2022-10-16 12:51:17 +02:00
Dan Albert
b478e9c3af Split up the ControlPoint.tsx monolith.
This is in need of some serious refactoring so that
https://github.com/dcs-liberation/dcs_liberation/issues/2388 can be
fixed.
2022-10-16 12:51:17 +02:00
Dan Albert
6c388bd557 Factor out control point location event behavior. 2022-10-16 12:51:16 +02:00
Dan Albert
f0a30b5cb7 Document primary/secondary CP marker behavior. 2022-10-16 12:51:16 +02:00
Dan Albert
daa5227f07 Fix the react app initialization.
ReactDOM.render is deprecated and using it forces react 17 behavior.
2022-10-16 12:51:16 +02:00
MetalStormGhost
8920d5890a F-105 Thunderchief banners (#5)
* Added F-105D and F-105G banners by Jim (used with permission).
* Added the official VNS logos to the F-105 Thunderchief banners.
2022-10-16 12:42:02 +02:00
MetalStormGhost
04a5497f3b F-4B Phantom II mod support (#4) 2022-10-15 23:07:06 +02:00
MetalStormGhost
139e42dbed Support for the F-105 Thunderchief mod
v2.7.12.23x
2022-10-15 12:57:43 +02:00
MetalStormGhost
8c905f95e3 Implemented support for the F-100 Super Sabre mod versions v2.7.18.01 & 2.7.18.30765 and patches 30.09.22 & 09.10.22. 2022-10-15 10:55:26 +02:00
Raffson
33305cfb46 Limit beta's webhook to test-branches only 2022-10-15 00:06:50 +02:00
Raffson
9d65c450b6 Rename payloads 2022-10-14 21:45:49 +02:00
785 changed files with 42514 additions and 14031 deletions

View File

@@ -3,7 +3,7 @@ name: Bug report
description: >
Use for any bug that happens after campaign generation. If the New Game wizard
failed, use the "New Game wizard failed" template instead.
labels: bug
labels: ["Bug", "Priority Medium"]
body:
- type: markdown
attributes:

View File

@@ -2,19 +2,19 @@
name: Campaign update submission
about: Submit an update to a campaign you maintain.
title: 'Update for <campaign name>'
labels: campaign-update-submission
labels: Campaign Update Submission
assignees: ''
---
This form should only be used for submitted updated miz/json files for campaigns
distributed with Liberation. If you are _requesting_ an update to a campaign, see
https://github.com/dcs-liberation/dcs_liberation/wiki/Campaign-maintenance. If the
distributed with Retribution. If you are _requesting_ an update to a campaign, see
https://github.com/dcs-retribution/dcs-retribution/wiki/Campaign-maintenance. If the
campaign has an owner, it will be updated before release. If it does not, you can
volunteer to own it.
If you are not the owner of the campaign listed on
https://github.com/dcs-liberation/dcs_liberation/wiki/Campaign-maintenance, please start
https://github.com/dcs-retribution/dcs-retribution/wiki/Campaign-maintenance, please start
there.
Otherwise, delete everything above the line below and fill out the following form. Note:

View File

@@ -0,0 +1,17 @@
---
name: Code Architecture
description: >
Suggest a refactor, or other issues related to the codebase.
labels: ["Refactor", "Python", "Priority Low"]
body:
- type: markdown
attributes:
value: >
Before filing, please search the issues to see if the problem has already been reported.
- type: textarea
attributes:
label: Describe your proposal.
description: >
Indicate where the issue lies and briefly explain how you plan to tackle it.
validations:
required: true

View File

@@ -1,12 +1,12 @@
blank_issues_enabled: false
contact_links:
- name: FAQ
url: https://discord.gg/PXHA6AXw
url: https://discord.gg/b4x34Bg4We
about: Check to see if your issue is in the FAQ.
- name: Manual
url: https://github.com/dcs-liberation/dcs_liberation/wiki/
url: https://github.com/dcs-retribution/dcs-retribution/wiki/
- name: Feature blocking DCS AI bugs
url: https://github.com/dcs-liberation/dcs_liberation#dcs-bugs
url: https://github.com/dcs-retribution/dcs-retribution#dcs-bugs
about: >
A list of known DCS bugs that prevent us from improving AI behavior. Check
the list before filing AI bugs here to see if it's something we know about

View File

@@ -1,21 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
Before filing, please search the issue tracker to see if this feature has already been requested.
If requesting a DCS AI feature, check If reporting a DCS AI bug, check https://github.com/dcs-liberation/dcs_liberation#dcs-bugs.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,31 @@
---
name: Feature Request
description: >
Suggest an idea for this project.
labels: ["Enhancement", "Priority Medium"]
body:
- type: markdown
attributes:
value: >
Before filing, please search the issue tracker to see if this feature has already been requested.
If requesting a DCS AI feature, check If reporting a DCS AI bug,
check https://github.com/dcs-retribution/dcs-retribution#dcs-bugs.
- type: textarea
attributes:
label: Is your feature request related to a problem? Please describe.
description: >
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: true
- type: textarea
attributes:
label: Describe the solution you'd like
description: >
A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: >
Add any other context or screenshots about the feature request here.

View File

@@ -1,8 +1,8 @@
---
name: Mod support request
about: Request Liberation support for new mods, or updates to existing mods
about: Request Retribution support for new mods, or updates to existing mods
title: Add/update <mod name>
labels: mod support
labels: Mod Request
assignees: ''
---

View File

@@ -3,7 +3,7 @@ name: New Game wizard failed
description: >
Use for bugs that prevent the "New Game" wizard from completing successfully.
If the wizard completes without issue, use the normal bug report template.
labels: bug
labels: ["Bug", "Priority High"]
body:
- type: markdown
attributes:

View File

@@ -25,6 +25,7 @@ jobs:
- name: Set build number
run: |
[IO.File]::WriteAllLines($pwd.path + "\resources\buildnumber", $env:GITHUB_RUN_NUMBER)
[IO.File]::WriteAllLines($pwd.path + "\resources\gitsha", $env:GITHUB_SHA)
- name: Build app
uses: ./.github/actions/build-app
@@ -42,11 +43,21 @@ jobs:
- name: Send status to Discord
uses: stegzilla/discord-notify@v2
with:
webhook_url: ${{ secrets.discord_webhook_betas }} # required
webhook_url: ${{ secrets.discord_webhook_betas }}
title: "Build #${{ github.run_number }} available!"
message: "**Download link:\n**
https://github.com/dcs-retribution/dcs-retribution/actions/runs/${{ github.run_id }}"
message: "**Branch:** ${{ github.ref_name }}\n**Download link:\n** https://github.com/dcs-retribution/dcs-retribution/actions/runs/${{ github.run_id }}"
include_image: true
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
username: GitHub Build Notifier
if: success() # or failure() or success()
if: startsWith(github.ref_name, 'test')
- name: Send status to Discord
uses: stegzilla/discord-notify@v2
with:
webhook_url: ${{ secrets.discord_webhook_alphas }}
title: "Build #${{ github.run_number }} available!"
message: "**Branch:** ${{ github.ref_name }}\n**Download link:\n** https://github.com/dcs-retribution/dcs-retribution/actions/runs/${{ github.run_id }}"
include_image: true
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
username: GitHub Build Notifier
if: github.ref_name == 'dev'

View File

@@ -26,7 +26,6 @@ the patreon link will keep pointing to [Khopa's](https://github.com/Khopa) accou
Currently, Retribution supports the following new features:
* A livery selector in the air wing configuration dialog
* Fixed mod support (still unfixed in Liberation)
* Support for campaigns with quad-point triggers
* Support for the F-15D 'Baz' mod
* Support for the Su-30 mod
@@ -34,12 +33,13 @@ Currently, Retribution supports the following new features:
* Variation in flight-size for BAI/ANTISHIP/DEAD/STRIKE main flight (randomize 2/3/4-ships)
* More performance options
* Various bug-fixes (including a fix for the zombie carrier)
* And more... (See [changelog](https://github.com/dcs-retribution/dcs-retribution/blob/main/changelog.md))
## Downloads
Latest release is available here : https://github.com/dcs-retribution/dcs-retribution/releases
To download preview builds of the next version of DCS Retribution, see https://github.com/dcs-retribution/dcs-retribution/wiki/Preview-builds.
To download preview builds of the next version of DCS Retribution, see https://github.com/dcs-retribution/dcs-retribution/wiki/Betas.
## DCS bugs
@@ -62,7 +62,7 @@ Our plans for future releases can be found on our
[Projects page](https://github.com/dcs-retribution/dcs-retribution/projects).
Each planned release has a Project, and the page for that project has columns for to do,
in progress, and done. Items in the Done column are in the
[preview build](https://github.com/dcs-retribution/dcs-retribution/wiki/Preview-builds)
[preview build](https://github.com/dcs-retribution/dcs-retribution/wiki/Betas)
for that release. Items in the To do column are planned to be added to that release.
## Resources

View File

@@ -1,57 +1,199 @@
# Retribution 1.1.1 (hotfix)
## Features/Improvements
* **[Modding]** Support for IDF Mod Project F-16I Sufa & F-16D v3.6 mod
* **[Modding]** Support for JAS-39 Gripen v1.8.5-beta mod
## Fixes
* **[Plugins]** Fix bug where changes to plugin options doesn't do anything.
* **[Campaign Management]** Fix bug in procurement when no squadrons are present.
* **[Layouts]** Fix edge-case bug layout's group size.
* **[Campaign Design]** Preset groups assigned to specific TGOs not working as intended.
# Retribution 1.1.0
## Features/Improvements
* **[Mission Generation]** Given a CAS flight was planned, delay ground force attack until first CAS flight is on station
* **[Mission Generation]** Add option to switch ATFLIR to LITENING automatically for ground based F/A-18C flights
* **[Mission Generation]** Add option to configure OPFOR autoplanner aggressiveness and have the AI take risks and plan missions against defended targets
* **[Mission Generation]** Add option to configure the desired tanker on-station time in settings
* **[Mission Generation]** Reserve GUARD frequency on VHF/UHF
* **[Mission Generation]** Randomization in radio frequency allocation
* **[Mission Generation]** Configurable number of Combined Arms slots
* **[Mission Generation]** Enable spectating & F11 free camera when the "Allow external views" option is selected
* **[Cheat Menu]** Option to instantly transfer squadrons across bases.
* **[Modding]** Support for IDF Mod Project F-16I Sufa & F-16D v3.2 mod
* **[Modding]** Support for F/A-18E/F/G mod version 2.1
* **[Modding]** Support for Swedish Military Assets for DCS by Currenthill Version 1.10
* **[UI]** Add selectable units in faction overview during campaign generation.
* **[UI]** Add button to rename pilots in Air Wing's Squadron dialog.
* **[UI]** Add clone buttons for flights & packages.
* **[UI]** Editing of flight's custom name.
* **[UI]** Introduce custom names for packages (purely for organizational purposes).
* **[UI]** Configurable UHF frequency (225-400MHz) for Packages, Carriers, LHAs, FOBs & FARPs.
* **[UI]** Configurable Intra-Flight frequency for Flights.
* **[UI]** Configurable TACAN for Carriers, LHAs & Tankers.
* **[UI]** Configurable ICLS for capable Carriers & LHAs.
* **[UI]** Configurable LINK4 for Carriers.
* **[Kneeboard]** Show package information in Support page
* **[Kneeboard]** Show extra weather information on 'Mission info' page
* **[Kneeboard]** Show BRC in 'RWY' column for aircraft carriers on 'Mission info' page
* **[Campaign Design]** Ability to define designated CTLD zones for Control Points (Airbases & FOBs/FARPs)
* **[Campaign Design]** Ability to define preset groups for specific TGOs, given the preset group is accessible for the faction and the task matches.
* **[Campaign Management]** Additional options for automated budget management.
* **[Campaign Management]** New options to allow more control of randomized flight sizes (applicable for BARCAP/CAS/OCA/ANTI-SHIP).
* **[Plugins]** Updated Splash Damage script to v2.0 by RotorOps.
* **[Mission Generation]** Improvements to DEAD & STRIKE flights, allowing AI to handle a larger variety of weapons.
## Fixes
* **[UI]** Removed deprecated options
* **[UI]** Add missing icon & banner for C130 Hercules mod
* **[Mission Generation]** Avoid aircraft from being assigned to helicopter parking spots, resulting into air starts that usually crash.
* **[Mission Generation]** Use stacking algorithm to create vertical separation between flights spawning mid-mission over their departure, usually resulting into mid-air collisions.
* **[Mission Generation]** Fixed all callsigns being "Enfield 1-1" on dedicated servers.
* **[Mission Generation]** Fixed AI ferry flights for helicopters when transferring to a FOB/FARP.
* **[Mission Generation]** Fixed 'Uninitialized flight' exception when adding flights after aborted take-off.
* **[Modding]** Fixed conflicts caused by HDS units
* **[UX]** Gracefully handle corrupted preferences file.
* **[Mission Generation]** Aircraft not using decoys during SEAD.
* **[Campaign]** New campaign (1968 Yankee Station) by Adecarcer
# Retribution 1.0.1 (hotfix)
* **[Mission Generation]** Fix serialization issue when STRIKE flight has no escorts
# Retribution 1.0.0
## Features/Improvements
* **[Engine]** Support for DCS v2.8.1.34437.
* **[Briefing]** Add tanker info to mission briefing
* **[Campaign]** Add 5 new campaigns by Oscar Juliet from WRL
* **[Campaign]** Add ability to define livery overrides also for ground/naval units
* **[Data]** Added data to support C-47 Skytrain.
* **[Data]** Added data to support F-16A MLU.
* **[Data]** Added data to support KS-19 & SON-9, including support for "AAA Site" layout.
* **[Mission Generation]** Add option to configure the maximum front-line length in settings
* **[Mission Generation]** Variable flight-size (2/3/4-ship) for BAI/ANTISHIP/DEAD/STRIKE (main) missions
* **[Mission Generation]** Use Escort & SEAD tasks for Escort & SEAD Escort flights
* **[Mission Generation]** Variable flight-size (2/3/4-ship) for
BAI/ANTISHIP/DEAD/STRIKE/BARCAP/CAS/OCA/AIR-ASSAULT (main) missions
* **[Mission Generation]** Add option to only generate night missions
* **[Modding]** Support for F-15D 'Baz' mod version 1.0
* **[Modding]** Support for Su-30 mod version 2.01B
* **[Modding]** Support for A-6A Intruder version 2.7.5.01
* **[Modding]** Support for F-4B Phantom II mod version v2.7.10.02, patch 2022.10.02
* **[Modding]** Support for F-100 Super Sabre mod versions v2.7.18.01 & 2.7.18.30765 and patch 20.10.22
* **[Modding]** Support for F-105 mod version 2.7.12.23x
* **[Modding]** Support IDF Mod Project F-16I Sufa & F-16D v2.2 mod
* **[Modding]** Support for F-84G mod version 2.5.7.01
* **[Modding]** Updated F-104 mod version support to 2.7.11.222.01
* **[Modding]** Updated Community A-4E-C mod version support to 2.1.0 release.
* **[UI]** Add livery selector to Air Wing Configurator's squadrons.
* **[Performance]** Added performance option: Maximum front-line unit supply per control point.
* **[Performance]** Added performance option: Disable convoys.
* **[Performance]** Added performance option: Front-line troops prefer roads.
* **[Data]** Added data to support C-47 Skytrain.
* **[Performance]** Added performance option: Disable idle aircraft at airfields.
* **[Squadrons]** Squadron pilot limits enabled by default.
* **[UI]** Add livery selector to Air Wing Configurator's squadrons.
## Fixes
* **[Mission Generation]** Fixed issue where aircraft carriers would return after being killed.
* **[Mission Generation]** Kneeboard STRIKE coordinates would sometimes get clipped when not fitting.
* **[UI]** Fix exception when trying to add a waypoints to a flightplan.
# Liberation:
# 7.0.0
Saves from 6.x are not compatible with 7.0.
## Features/Improvements
* **[Engine]** Support for DCS 2.8.3.37556.
* **[Engine]** Saved games are now a zip file of save assets for easier bug reporting. The new extension is .liberation.zip. Drag and drop that file into bug reports.
* **[Flight Planning]** Package TOT and composition can be modified after advancing time in Liberation.
* **[Mission Generation]** Units on the front line are now hidden on MFDs.
* **[Mission Generation]** Preset radio channels will now be configured for both A-10C modules.
* **[Mission Generation]** Both A-10C modules now use separate radios for inter- and intra-flight comms (similar to other modern aircraft).
* **[Modding]** Updated Community A-4E-C mod version support to 2.1.0 release.
* **[Modding]** Add support for VSN F-4B and F-4C mod.
## Fixes
# 6.1.1
## Fixes
* **[Data]** Fixed unit ID for the KS-19 AAA. KS-19 would not previously generate correctly in missions. A new game is required for this fix to take effect.
* **[Flight Planning]** Automatic flight planning will no longer accidentally plan a recovery tanker instead of a theater refueling package. This fixes a potential crash during mission generation when opfor plans a refueling task at a sunk carrier. You'll need to skip the current turn to force opfor to replan their flights to get the fix.
* **[Mission Generation]** Using heliports (airports without any runways) will no longer cause mission generation to fail.
# 6.1.0
Saves from 6.0.0 are compatible with 6.1.0
## Features/Improvements
* **[Factions]** Defaulted bluefor modern to use Georgian and Ukrainian liveries for Russian aircraft.
* **[Factions]** Added Peru.
* **[Modding]** Added support for the HMS Ariadne, Achilles, and Castle class.
## Fixes
* **[Flight Planning]** Fixes CAS flights not having landing waypoints.
* **[Squadrons]** Fixed the livery for the VF-33 F-14A squadron.
* **[UI]** Fixed an issue where manual submit of mission results did not end the mission correctly.
# 6.0.0
Saves from 5.x are not compatible with 6.0.
## Features/Improvements
* **[Engine]** Support for DCS 2.7.18.30348.
* **[Mission Generation]** Added an option to fast-forward mission generation until the point of first contact (WIP).
* **[Mission Generation]** Added performance option to not cull IADS when culling would effect how mission is played at target area.
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
* **[Engine]** Support for DCS 2.8.0.33006.
* **[Factions]** Updated the Faction file structure. Older custom faction files will not work correctly and have to be updated to the new structure.
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. AI does not plan.
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. This mission type will not be planned automatically.
* **[Flight Planning]** Air to Ground flights now have ECM enabled on lock at the join point, and SEAD/DEAD also have ECM enabled on detection and lock at ingress.
* **[Flight Planning]** AWACS flightplan changed from orbit to a racetrack to reduce data link disconnects which were caused by blind spots as a result of the bank angle.
* **[Flight Planning]** Added a new helo mission type: AirAssault which can be used to load and transport infantry troops from a pickup zone or a carrier to an enemy CP to capture it.
* **[Flight Planning]** Improved the Airlift mission type so that it now can be enforced within the unit transfer dialog and implemented CTLD support. This allows user to spawn sling loadable crates at the pickup location and fly transport flights.
* **[Flight Planning]** Added a new helo mission type: AirAssault which can be used to load and transport infantry troops from a pickup zone or a carrier to an enemy CP to capture it.
* **[Flight Planning]** Improved the Airlift mission type so that it now can be enforced within the unit transfer dialog and implemented CTLD support. This allows user to spawn sling loadable crates at the pickup location and fly transport flights.
* **[Mission Generation]** Added an option to fast-forward mission generation until the point of first contact (WIP).
* **[Mission Generation]** Added performance option to not cull IADS when culling would affect how mission is played at target area.
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
* **[Mission Generation]** Added ice halos.
* **[Mission Generation]** Adjusted wind speeds. Wind speeds at high altitude are generally higher now.
* **[Mission Generation]** Added turbulence. Higher in Summer and Winter, also higher at day time than at nighttime.
* **[Modding]** Updated UH-60L mod version support to 1.3.1
* **[Modding]** Updated the High Digit SAMs implementation and added the HQ-2 as well as the upgraded SA-2 and SA-3 Launchers from the mod. Threat range circles will now also be displayed correctly.
* **[Modding]** Theater information such as climate properties is now moddable.
* **[Modding]** Allow campaign designers to define default values for the economy settings (starting budget and multiplier).
* **[Modding]** Campaigns can now optionally define their start time by including a time in the `recommended_start_date` field. There is not currently a way to override the start time in the UI.
* **[Plugins]** Allow full support of the SkynetIADS plugin with all advanced features (connection nodes, power sources, command centers) if campaign supports it.
* **[Plugins]** Added support for the CTLD script by ciribob with many possible customization options and updated the JTAC Autolase to the CTLD included script.
* **[UI]** Added options to the loadout editor for setting properties such as HMD choice.
* **[UI]** Added separate images for the different carrier types.
* **[UI]** Add Accept/Reset buttons to Air Wing Configurator screen.
* **[Campaign]** Allow campaign designers to define default values for the economy settings (starting budget and multiplier).
* **[Plugins]** Allow full support of the SkynetIADS plugin with all advanced features (connection nodes, power sources, command centers) if campaign supports it.
* **[Plugins]** Added support for the CTLD script by ciribob with many possible customization options and updated the JTAC Autolase to the CTLD included script.
## Fixes
* **[Engine]** Fixed issue that prevented some weapon types like torpedoes from being recognized.
* **[Flight Planning]** Fixed a miscalculation of waypoint TOTs that would require time travel.
* **[Loadouts]** Improved the range of the F-16 CAS loadout by adding bags.
* **[Mission Generation]** AAA ground units now spawn correctly at the frontline
* **[Mission Generation]** Fixed SA-13 incorrectly created as SA-8 Loading Unit which will not be spawned in the generated mission.
* **[Mission Generation]** Fixed adding additional mission types for a squadron causing error messages when the mission type is not supported by the aircraft type by default
* **[Mission Generation]** Fixed an issue where SEAD/DEAD/BAI flights fired all missiles / bombs against a single unit in a group instead of targeting the whole group.
* **[Mission Generation]** Fixed an issue which generated the helipads at FARPs incorrectly and placed the helicopters within each other.
* **[Mission Generation]** Fixed an issue with SEAD missions flown by the AI when using the Skynet Plugin and anti-radiation missiles (ARM). The AI now correctly engages the SAM when it comes alive instead of diving into it.
* **[Mission Generation]** Fixed an issue where SEAD/DEAD/BAI flights fired all missiles / bombs against a single unit in a group instead of targeting the whole group.
* **[Mission Generation]** Fixed adding additional mission types for a squadron causing error messages when the mission type is not supported by the aircraft type by default
* **[Mission Generation]** AAA ground units now spawn correctly at the frontline
* **[UI]** Fixed and issue where the liberation main exe was still running after application close.
* **[Mission Generation]** Fixed generation issue that would cause AI helicopters to get stuck after taking off from a FARP.
* **[Mission Generation]** Fixed mission scripting error caused by control points with apostrophes in their names, such as Tha'lah.
* **[Modding]** Campaigns that used quad zones for scenery targets will no longer load. Only circular zones were ever supported, but an implementation quirk allowed them to load in a way that would misbehave. A "No white triggerzones found" message during campaign generation is the sign of a broken campaign.
* **[Modding]** Loadouts with invalid weapons (typically new DCS weapons not yet available in Liberation) will be ignored rather than causing an error.
* **[Squadrons]** Fixed issue in air wing configuration that would allow squadrons to be created with no home base if no base was available.
* **[Squadrons]** Helicopter squadrons can no longer be assigned to FOBs that are not FARPs.
* **[UI]** Add vanilla theme weather and time of day icons
* **[UI]** Disable player slots for non-flyable aircraft.
* **[UI]** Fixed and issue where the liberation main exe was still running after application close.
# 5.2.0

View File

@@ -1,6 +1,6 @@
# DCS Liberation Client
# DCS Retribution Client
This is a React app for the front-end of DCS Liberation. It is a work in
This is a React app for the front-end of DCS Retribution. It is a work in
progress that just barely implements the map. This is not useful for players
yet.

20154
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -69,7 +69,9 @@
"electron": "^21.1.0",
"electron-is-dev": "^2.0.0",
"generate-license-file": "^2.0.0",
"got": "^11.8.5",
"license-checker": "^25.0.1",
"nth-check": "^2.0.1",
"react-scripts": "5.0.1",
"ts-node": "^10.9.1",
"wait-on": "^6.0.1"

View File

@@ -1,270 +1,15 @@
import backend from "../../api/backend";
import { ControlPoint as ControlPointModel } from "../../api/liberationApi";
import {
useClearControlPointDestinationMutation,
useSetControlPointDestinationMutation,
} from "../../api/liberationApi";
import {
Icon,
LatLng,
Point,
Marker as LMarker,
Polyline as LPolyline,
LatLngLiteral,
} from "leaflet";
import { Symbol as MilSymbol } from "milsymbol";
import {
MutableRefObject,
useCallback,
useEffect,
useRef,
useState,
} from "react";
import ReactDOMServer from "react-dom/server";
import { Marker, Polyline, Tooltip } from "react-leaflet";
function iconForControlPoint(cp: ControlPointModel) {
const symbol = new MilSymbol(cp.sidc, {
size: 24,
colorMode: "Dark",
});
return new Icon({
iconUrl: symbol.toDataURL(),
iconAnchor: new Point(symbol.getAnchor().x, symbol.getAnchor().y),
});
}
function openInfoDialog(controlPoint: ControlPointModel) {
backend.post(`/qt/info/control-point/${controlPoint.id}`);
}
function openNewPackageDialog(controlPoint: ControlPointModel) {
backend.post(`/qt/create-package/control-point/${controlPoint.id}`);
}
import { MobileControlPoint } from "./MobileControlPoint";
import { StaticControlPoint } from "./StaticControlPoint";
interface ControlPointProps {
controlPoint: ControlPointModel;
}
function LocationTooltipText(props: ControlPointProps) {
return <h3 style={{ margin: 0 }}>{props.controlPoint.name}</h3>;
}
function metersToNauticalMiles(meters: number) {
return meters * 0.000539957;
}
function formatLatLng(latLng: LatLng) {
const lat = latLng.lat.toFixed(2);
const lng = latLng.lng.toFixed(2);
const ns = latLng.lat >= 0 ? "N" : "S";
const ew = latLng.lng >= 0 ? "E" : "W";
return `${lat}&deg;${ns} ${lng}&deg;${ew}`;
}
function destinationTooltipText(
cp: ControlPointModel,
destinationish: LatLngLiteral,
inRange: boolean
) {
const destination = new LatLng(destinationish.lat, destinationish.lng);
const distance = metersToNauticalMiles(
destination.distanceTo(cp.position)
).toFixed(1);
if (!inRange) {
return `Out of range (${distance}nm away)`;
}
const dest = formatLatLng(destination);
return `${cp.name} moving ${distance}nm to ${dest} next turn`;
}
function PrimaryMarker(props: ControlPointProps) {
// We can't use normal state to update the marker tooltip or the line points
// because if we set any state in the drag event it will re-render the
// component and interrupt dragging. Instead, keep refs to the objects and
// mutate them directly.
//
// For the same reason, the path is owned by this component, because updating
// sibling state would be messy. Lifting the state into the parent would still
// cause this component to redraw.
const marker: MutableRefObject<LMarker | undefined> = useRef();
const pathLine: MutableRefObject<LPolyline | undefined> = useRef();
const [hasDestination, setHasDestination] = useState<boolean>(
props.controlPoint.destination != null
);
const [pathDestination, setPathDestination] = useState<LatLngLiteral>(
props.controlPoint.destination
? props.controlPoint.destination
: props.controlPoint.position
);
const [position, setPosition] = useState<LatLngLiteral>(
props.controlPoint.destination
? props.controlPoint.destination
: props.controlPoint.position
);
const setDestination = useCallback((destination: LatLng) => {
setPathDestination(destination);
setPosition(destination);
setHasDestination(true);
}, []);
const resetDestination = useCallback(() => {
setPathDestination(props.controlPoint.position);
setPosition(props.controlPoint.position);
setHasDestination(false);
}, [props.controlPoint.position]);
const [putDestination, { isLoading }] =
useSetControlPointDestinationMutation();
const [cancelTravel] = useClearControlPointDestinationMutation();
useEffect(() => {
marker.current?.setTooltipContent(
props.controlPoint.destination
? destinationTooltipText(
props.controlPoint,
props.controlPoint.destination,
true
)
: ReactDOMServer.renderToString(
<LocationTooltipText controlPoint={props.controlPoint} />
)
);
});
return (
<>
<Marker
position={position}
icon={iconForControlPoint(props.controlPoint)}
draggable={props.controlPoint.mobile && !isLoading}
autoPan
// We might draw other markers on top of the CP. The tooltips from the
// other markers are helpful so we want to keep them, but make sure the CP
// is always the clickable thing.
zIndexOffset={1000}
opacity={props.controlPoint.destination ? 0.5 : 1}
ref={(ref) => {
if (ref != null) {
marker.current = ref;
}
}}
eventHandlers={{
click: () => {
if (!hasDestination) {
openInfoDialog(props.controlPoint);
}
},
contextmenu: () => {
if (props.controlPoint.destination) {
cancelTravel({ cpId: props.controlPoint.id }).then(() => {
resetDestination();
});
} else {
openNewPackageDialog(props.controlPoint);
}
},
drag: (event) => {
const destination = event.target.getLatLng();
backend
.get(
`/control-points/${props.controlPoint.id}/destination-in-range?lat=${destination.lat}&lng=${destination.lng}`
)
.then((inRange) => {
marker.current?.setTooltipContent(
destinationTooltipText(
props.controlPoint,
destination,
inRange.data
)
);
});
pathLine.current?.setLatLngs([
props.controlPoint.position,
destination,
]);
},
dragend: async (event) => {
const currentPosition = new LatLng(
pathDestination.lat,
pathDestination.lng
);
const destination = event.target.getLatLng();
setDestination(destination);
try {
await putDestination({
cpId: props.controlPoint.id,
body: { lat: destination.lat, lng: destination.lng },
}).unwrap();
} catch (error) {
console.error("setDestination failed", error);
setDestination(currentPosition);
}
},
}}
>
<Tooltip />
</Marker>
<Polyline
positions={[props.controlPoint.position, pathDestination]}
weight={1}
color="#80BA80"
interactive
ref={(ref) => {
if (ref != null) {
pathLine.current = ref;
}
}}
/>
</>
);
}
interface SecondaryMarkerProps {
controlPoint: ControlPointModel;
destination: LatLngLiteral | undefined;
}
function SecondaryMarker(props: SecondaryMarkerProps) {
if (!props.destination) {
return <></>;
}
return (
<Marker
position={props.controlPoint.position}
icon={iconForControlPoint(props.controlPoint)}
// We might draw other markers on top of the CP. The tooltips from the
// other markers are helpful so we want to keep them, but make sure the CP
// is always the clickable thing.
zIndexOffset={1000}
eventHandlers={{
click: () => {
openInfoDialog(props.controlPoint);
},
contextmenu: () => {
openNewPackageDialog(props.controlPoint);
},
}}
>
<Tooltip>
<LocationTooltipText {...props} />
</Tooltip>
</Marker>
);
}
export default function ControlPoint(props: ControlPointProps) {
return (
<>
<PrimaryMarker {...props} />
<SecondaryMarker
controlPoint={props.controlPoint}
destination={props.controlPoint.destination}
/>
</>
);
if (props.controlPoint.mobile) {
return <MobileControlPoint {...props} />;
} else {
return <StaticControlPoint {...props} />;
}
}

View File

@@ -0,0 +1,22 @@
import { ControlPoint } from "../../api/_liberationApi";
import backend from "../../api/backend";
function openInfoDialog(controlPoint: ControlPoint) {
backend.post(`/qt/info/control-point/${controlPoint.id}`);
}
function openNewPackageDialog(controlPoint: ControlPoint) {
backend.post(`/qt/create-package/control-point/${controlPoint.id}`);
}
export const makeLocationMarkerEventHandlers = (controlPoint: ControlPoint) => {
return {
click: () => {
openInfoDialog(controlPoint);
},
contextmenu: () => {
openNewPackageDialog(controlPoint);
},
};
};

View File

@@ -0,0 +1,15 @@
import { ControlPoint } from "../../api/_liberationApi";
import { Icon, Point } from "leaflet";
import { Symbol } from "milsymbol";
export const iconForControlPoint = (cp: ControlPoint) => {
const symbol = new Symbol(cp.sidc, {
size: 24,
colorMode: "Dark",
});
return new Icon({
iconUrl: symbol.toDataURL(),
iconAnchor: new Point(symbol.getAnchor().x, symbol.getAnchor().y),
});
};

View File

@@ -0,0 +1,9 @@
interface LocationTooltipTextProps {
name: string;
}
export const LocationTooltipText = (props: LocationTooltipTextProps) => {
return <h3 style={{ margin: 0 }}>{props.name}</h3>;
};
export default LocationTooltipText;

View File

@@ -0,0 +1,228 @@
import { ControlPoint } from "../../api/_liberationApi";
import backend from "../../api/backend";
import {
useClearControlPointDestinationMutation,
useSetControlPointDestinationMutation,
} from "../../api/liberationApi";
import { makeLocationMarkerEventHandlers } from "./EventHandlers";
import { iconForControlPoint } from "./Icons";
import LocationTooltipText from "./LocationTooltipText";
import { MovementPath, MovementPathHandle } from "./MovementPath";
import { StaticControlPoint } from "./StaticControlPoint";
import { LatLng, Marker as LMarker, LatLngLiteral } from "leaflet";
import { useCallback, useEffect, useRef, useState } from "react";
import ReactDOMServer from "react-dom/server";
import { Marker, Tooltip } from "react-leaflet";
function metersToNauticalMiles(meters: number) {
return meters * 0.000539957;
}
function formatLatLng(latLng: LatLng) {
const lat = latLng.lat.toFixed(2);
const lng = latLng.lng.toFixed(2);
const ns = latLng.lat >= 0 ? "N" : "S";
const ew = latLng.lng >= 0 ? "E" : "W";
return `${lat}&deg;${ns} ${lng}&deg;${ew}`;
}
function destinationTooltipText(
cp: ControlPoint,
destinationish: LatLngLiteral,
inRange: boolean
) {
const destination = new LatLng(destinationish.lat, destinationish.lng);
const distance = metersToNauticalMiles(
destination.distanceTo(cp.position)
).toFixed(1);
if (!inRange) {
return `Out of range (${distance}nm away)`;
}
const dest = formatLatLng(destination);
return `${cp.name} moving ${distance}nm to ${dest} next turn`;
}
interface PrimaryMarkerProps {
controlPoint: ControlPoint;
}
/**
* The primary control point marker. For non-mobile control points, this has
* fairly simple behavior: it's a marker in a fixed location that can manage
* units and can have missions planned against it.
*
* For mobile control points, this is a draggable marker. If the control point
* has a destination (either because it was dragged after render, or because it
* had a destination in the game that was loaded), the unit management and
* mission planning behaviors are delegated to SecondaryMarker, and the primary
* marker becomes only a destination marker. It can be dragged to change the
* destination, and can be right clicked to cancel movement.
*/
function PrimaryMarker(props: PrimaryMarkerProps) {
// We can't use normal state to update the marker tooltip or the line points
// because if we set any state in the drag event it will re-render this
// component and all children, interrupting dragging. Instead, keep refs to
// the objects and mutate them directly.
//
// For the same reason, the path is owned by this component, because updating
// sibling state would be messy. Lifting the state into the parent would still
// cause this component to redraw.
const markerRef = useRef<LMarker | null>(null);
const pathRef = useRef<MovementPathHandle | null>(null);
const [hasDestination, setHasDestination] = useState<boolean>(
props.controlPoint.destination != null
);
const [position, setPosition] = useState<LatLngLiteral>(
props.controlPoint.destination
? props.controlPoint.destination
: props.controlPoint.position
);
const setDestination = useCallback((destination: LatLng) => {
setPosition(destination);
setHasDestination(true);
}, []);
const resetDestination = useCallback(() => {
setPosition(props.controlPoint.position);
setHasDestination(false);
}, [props]);
const [putDestination, { isLoading }] =
useSetControlPointDestinationMutation();
const [cancelTravel] = useClearControlPointDestinationMutation();
useEffect(() => {
markerRef.current?.setTooltipContent(
props.controlPoint.destination
? destinationTooltipText(
props.controlPoint,
props.controlPoint.destination,
true
)
: ReactDOMServer.renderToString(
<LocationTooltipText name={props.controlPoint.name} />
)
);
});
const locationClickHandlers = makeLocationMarkerEventHandlers(
props.controlPoint
);
return (
<>
<Marker
position={position}
icon={iconForControlPoint(props.controlPoint)}
draggable={!isLoading}
autoPan
// We might draw other markers on top of the CP. The tooltips from the
// other markers are helpful so we want to keep them, but make sure the CP
// is always the clickable thing.
zIndexOffset={1000}
opacity={props.controlPoint.destination ? 0.5 : 1}
ref={(ref) => {
if (ref != null) {
markerRef.current = ref;
}
}}
eventHandlers={{
click: () => {
if (!hasDestination) {
locationClickHandlers.click();
}
},
contextmenu: () => {
if (props.controlPoint.destination) {
cancelTravel({ cpId: props.controlPoint.id }).then(() => {
resetDestination();
});
} else {
locationClickHandlers.contextmenu();
}
},
drag: (event) => {
const destination = event.target.getLatLng();
backend
.get(
`/control-points/${props.controlPoint.id}/destination-in-range?lat=${destination.lat}&lng=${destination.lng}`
)
.then((inRange) => {
markerRef.current?.setTooltipContent(
destinationTooltipText(
props.controlPoint,
destination,
inRange.data
)
);
});
pathRef.current?.setDestination(destination);
},
dragend: async (event) => {
const currentPosition = new LatLng(position.lat, position.lng);
const destination = event.target.getLatLng();
setDestination(destination);
try {
await putDestination({
cpId: props.controlPoint.id,
body: { lat: destination.lat, lng: destination.lng },
}).unwrap();
} catch (error) {
console.error("setDestination failed", error);
setDestination(currentPosition);
}
},
}}
>
<Tooltip />
</Marker>
<MovementPath
source={props.controlPoint.position}
destination={position}
ref={pathRef}
/>
</>
);
}
interface SecondaryMarkerProps {
controlPoint: ControlPoint;
destination: LatLngLiteral | undefined;
}
/**
* The secondary marker for a control point. The secondary marker will only be
* shown when the control point has a destination set. For mobile control
* points, the primary marker is draggable, and the secondary marker will be
* shown at the current location iff the control point has been dragged. The
* secondary marker is also the marker that has the normal control point
* interaction options (mission planning and unit management).
*/
function SecondaryMarker(props: SecondaryMarkerProps) {
if (!props.destination) {
return <></>;
}
return <StaticControlPoint controlPoint={props.controlPoint} />;
}
interface MobileControlPointProps {
controlPoint: ControlPoint;
}
export const MobileControlPoint = (props: MobileControlPointProps) => {
return (
<>
<PrimaryMarker
controlPoint={props.controlPoint}
key={props.controlPoint.destination ? 0 : 1}
/>
<SecondaryMarker
controlPoint={props.controlPoint}
destination={props.controlPoint.destination}
/>
</>
);
};

View File

@@ -0,0 +1,35 @@
import { LatLngLiteral, Polyline as LPolyline } from "leaflet";
import { forwardRef, useImperativeHandle, useRef } from "react";
import { Polyline } from "react-leaflet";
interface MovementPathProps {
source: LatLngLiteral;
destination: LatLngLiteral;
}
export interface MovementPathHandle {
setDestination: (destination: LatLngLiteral) => void;
}
export const MovementPath = forwardRef<MovementPathHandle, MovementPathProps>(
(props: MovementPathProps, ref) => {
const lineRef = useRef<LPolyline | null>(null);
useImperativeHandle(
ref,
() => ({
setDestination: (destination: LatLngLiteral) => {
lineRef.current?.setLatLngs([props.source, destination]);
},
}),
[props]
);
return (
<Polyline
positions={[props.source, props.destination]}
weight={1}
color="#80BA80"
ref={lineRef}
/>
);
}
);

View File

@@ -0,0 +1,27 @@
import { ControlPoint } from "../../api/_liberationApi";
import { makeLocationMarkerEventHandlers } from "./EventHandlers";
import { iconForControlPoint } from "./Icons";
import LocationTooltipText from "./LocationTooltipText";
import { Marker, Tooltip } from "react-leaflet";
interface StaticControlPointProps {
controlPoint: ControlPoint;
}
export const StaticControlPoint = (props: StaticControlPointProps) => {
return (
<Marker
position={props.controlPoint.position}
icon={iconForControlPoint(props.controlPoint)}
// We might draw other markers on top of the CP. The tooltips from the
// other markers are helpful so we want to keep them, but make sure the CP
// is always the clickable thing.
zIndexOffset={1000}
eventHandlers={makeLocationMarkerEventHandlers(props.controlPoint)}
>
<Tooltip>
<LocationTooltipText name={props.controlPoint.name} />
</Tooltip>
</Marker>
);
};

View File

@@ -4,18 +4,20 @@ import { SocketProvider } from "./components/socketprovider/socketprovider";
import "./index.css";
import * as serviceWorker from "./serviceWorker";
import React from "react";
import ReactDOM from "react-dom";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
ReactDOM.render(
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<Provider store={store}>
<SocketProvider>
<App />
</SocketProvider>
</Provider>
</React.StrictMode>,
document.getElementById("root")
</React.StrictMode>
);
// If you want your app to work offline and load faster, you can change

View File

@@ -1,2 +1,3 @@
from .game import Game
from .version import VERSION
from .migrator import Migrator

View File

@@ -2,8 +2,9 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING, Iterator, Optional
from game.data.groups import GroupTask
from game.armedforces.forcegroup import ForceGroup
from game.data.groups import GroupTask
from game.layout import LAYOUTS
from game.profiling import logged_duration

View File

@@ -10,19 +10,18 @@ import yaml
from dcs.unittype import ShipType, StaticType, UnitType as DcsUnitType, VehicleType
from game.data.groups import GroupTask
from game.data.radar_db import UNITS_WITH_RADAR
from game.dcs.groundunittype import GroundUnitType
from game.dcs.helpers import static_type_from_name
from game.dcs.shipunittype import ShipUnitType
from game.dcs.unittype import UnitType
from game.layout import LAYOUTS
from game.layout.layout import TgoLayout, TgoLayoutUnitGroup
from game.point_with_heading import PointWithHeading
from game.theater.theatergroundobject import (
IadsGroundObject,
IadsBuildingGroundObject,
NavalGroundObject,
)
from game.layout import LAYOUTS
from game.layout.layout import TgoLayout, TgoLayoutUnitGroup
from game.point_with_heading import PointWithHeading
from game.theater.theatergroup import IadsGroundGroup, IadsRole, TheaterGroup
from game.utils import escape_string_for_lua

View File

@@ -10,6 +10,7 @@ from dcs.helicopters import (
CH_47D,
CH_53E,
Ka_50,
Ka_50_3,
Mi_24P,
Mi_24V,
Mi_26,
@@ -45,11 +46,13 @@ from dcs.planes import (
FW_190A8,
FW_190D9,
F_117A,
F_14A,
F_14A_135_GR,
F_14B,
F_15C,
F_15E,
F_16A,
F_16A_MLU,
F_16C_50,
F_4E,
F_5E_3,
@@ -116,18 +119,35 @@ from dcs.planes import (
Tu_95MS,
WingLoong_I,
Yak_40,
MB_339A,
)
from dcs.unittype import FlyingType
from game.dcs.aircrafttype import AircraftType
from pydcs_extensions.a4ec.a4ec import A_4E_C
from pydcs_extensions.f104.f104 import VSN_F104G, VSN_F104S, VSN_F104S_AG
from pydcs_extensions.a6a.a6a import VSN_A6A
from pydcs_extensions.f100.f100 import VSN_F100
from pydcs_extensions.f104.f104 import VSN_F104C, VSN_F104G, VSN_F104S, VSN_F104S_AG
from pydcs_extensions.f105.f105 import VSN_F105D, VSN_F105G
from pydcs_extensions.f15d.f15d import F_15D
from pydcs_extensions.f16i_idf.f16i_idf import (
F_16D_50,
F_16D_52,
F_16D_50_NS,
F_16D_52_NS,
F_16D_Barak_30,
F_16D_Barak_40,
F_16I,
)
from pydcs_extensions.f22a.f22a import F_22A
from pydcs_extensions.f4b.f4b import VSN_F4B, VSN_F4C
from pydcs_extensions.f84g.f84g import VSN_F84G
from pydcs_extensions.fa18efg.fa18efg import FA_18E, FA_18F, EA_18G
from pydcs_extensions.hercules.hercules import Hercules
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_AG
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_BVR, JAS39Gripen_AG
from pydcs_extensions.su30.su30 import Su_30MKA, Su_30MKI, Su_30MKM, Su_30SM
from pydcs_extensions.su57.su57 import Su_57
from pydcs_extensions.ov10a.ov10a import Bronco_OV_10A
from pydcs_extensions.uh60l.uh60l import KC130J, UH_60L
from .flighttype import FlightType
@@ -138,13 +158,14 @@ from .flighttype import FlightType
# factions that also have F-4s should not.
# Used for CAP, Escort, and intercept if there is not a specialised aircraft available
CAP_CAPABLE = [
ESCORT_CAPABLE = [
Su_57,
F_22A,
F_15C,
F_15D,
F_14B,
F_14A_135_GR,
F_14A,
Su_33,
J_11A,
Su_30,
@@ -155,11 +176,24 @@ CAP_CAPABLE = [
Su_27,
MiG_29S,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
JF_17,
JAS39Gripen_BVR,
JAS39Gripen,
F_16A_MLU,
F_16A,
F_4E,
VSN_F4C,
VSN_F4B,
MiG_31,
MiG_25PD,
MiG_29G,
@@ -181,12 +215,14 @@ CAP_CAPABLE = [
F_5E_3,
VSN_F104S,
VSN_F104G,
VSN_F104C,
MiG_19P,
VSN_F100,
A_4E_C,
F_86F_Sabre,
MiG_15bis,
C_101CC,
L_39ZA,
VSN_F84G,
P_51D_30_NA,
P_51D,
SpitfireLFMkIXCW,
@@ -201,6 +237,11 @@ CAP_CAPABLE = [
I_16,
]
# Types to be skipped for escorts
CAP_CAPABLE = ESCORT_CAPABLE + [
L_39ZA,
]
# Used for CAS (Close air support) and BAI (Battlefield Interdiction)
CAS_CAPABLE = [
@@ -214,6 +255,15 @@ CAS_CAPABLE = [
F_15D,
F_15E,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
Tornado_GR4,
Tornado_IDS,
@@ -221,6 +271,8 @@ CAS_CAPABLE = [
JF_17,
AV8BNA,
A_10A,
F_16A_MLU,
F_16A,
B_1B,
A_4E_C,
F_14B,
@@ -248,6 +300,7 @@ CAS_CAPABLE = [
SA342M,
SA342L,
Ka_50,
Ka_50_3,
Mi_28N,
Mi_24P,
Mi_24V,
@@ -268,9 +321,9 @@ CAS_CAPABLE = [
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
Bronco_OV_10A,
UH_1H,
VSN_F104S_AG,
VSN_F104G,
A_20G,
Ju_88A4,
P_47D_40,
@@ -288,21 +341,39 @@ CAS_CAPABLE = [
WingLoong_I,
MQ_9_Reaper,
RQ_1A_Predator,
VSN_A6A,
VSN_F4C,
VSN_F4B,
VSN_F100,
VSN_F105G,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F84G,
]
# Aircraft used for SEAD and SEAD Escort tasks. Must be capable of the CAS DCS task.
SEAD_CAPABLE = [
SEAD_ESCORT_CAPABLE = [
JF_17,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
EA_18G,
FA_18E,
FA_18F,
FA_18C_hornet,
Tornado_IDS,
Su_25T,
Su_25TM,
F_4E,
A_4E_C,
F_14B,
F_14A_135_GR,
JAS39Gripen_AG,
AV8BNA,
Su_24M,
@@ -315,14 +386,22 @@ SEAD_CAPABLE = [
Su_30SM,
MiG_27K,
Tornado_GR4,
VSN_F105G,
VSN_F100,
]
SEAD_CAPABLE = SEAD_ESCORT_CAPABLE + [
F_16A_MLU,
F_14B,
F_14A_135_GR,
]
# Aircraft used for DEAD tasks. Must be capable of the CAS DCS task.
DEAD_CAPABLE = SEAD_CAPABLE + [
AJS37,
F_14B,
F_14A_135_GR,
F_16A,
F_15E,
JAS39Gripen_AG,
B_1B,
B_52H,
@@ -331,19 +410,25 @@ DEAD_CAPABLE = SEAD_CAPABLE + [
H_6J,
A_20G,
Ju_88A4,
VSN_F104S_AG,
VSN_F104G,
P_47D_40,
P_47D_30bl1,
P_47D_30,
P_51D_30_NA,
P_51D,
Bronco_OV_10A,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
MosquitoFBMkVI,
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F100,
VSN_F84G,
]
@@ -361,9 +446,19 @@ STRIKE_CAPABLE = [
AJS37,
Tornado_GR4,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
AV8BNA,
JF_17,
F_16A_MLU,
F_16A,
F_14B,
F_14A_135_GR,
@@ -388,8 +483,11 @@ STRIKE_CAPABLE = [
F_4E,
A_10C_2,
A_10C,
VSN_F4C,
VSN_F4B,
S_3B,
A_4E_C,
Bronco_OV_10A,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
@@ -406,11 +504,10 @@ STRIKE_CAPABLE = [
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
B_17G,
A_20G,
Ju_88A4,
VSN_F104S_AG,
VSN_F104G,
P_47D_40,
P_47D_30bl1,
P_47D_30,
@@ -422,6 +519,14 @@ STRIKE_CAPABLE = [
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F100,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F105G,
VSN_F105D,
VSN_F84G,
]
@@ -430,8 +535,12 @@ ANTISHIP_CAPABLE = [
Tu_142,
Tu_22M3,
H_6J,
FA_18E,
FA_18F,
FA_18C_hornet,
JAS39Gripen_AG,
F_16A_MLU,
F_16A,
Su_24M,
Su_17M4,
JF_17,
@@ -460,6 +569,14 @@ RUNWAY_ATTACK_CAPABLE = [
JF_17,
Tornado_IDS,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1CT,
H_6J,
B_1B,
B_52H,
@@ -468,9 +585,19 @@ RUNWAY_ATTACK_CAPABLE = [
F_15E,
AJS37,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
AV8BNA,
JF_17,
F_16A_MLU,
F_16A,
F_14B,
F_14A_135_GR,
@@ -495,17 +622,12 @@ RUNWAY_ATTACK_CAPABLE = [
F_4E,
A_10C_2,
A_10C,
VSN_F4C,
VSN_F4B,
S_3B,
A_4E_C,
Bronco_OV_10A,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1CT,
MiG_27K,
MiG_21Bis,
MiG_15bis,
@@ -513,11 +635,10 @@ RUNWAY_ATTACK_CAPABLE = [
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
B_17G,
A_20G,
Ju_88A4,
VSN_F104S_AG,
VSN_F104G,
P_47D_40,
P_47D_30bl1,
P_47D_30,
@@ -529,6 +650,13 @@ RUNWAY_ATTACK_CAPABLE = [
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F105G,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F100,
]
# For any aircraft that isn't necessarily directly involved in strike
@@ -564,7 +692,6 @@ AIR_ASSAULT_CAPABLE = [
Mi_24P,
Mi_24V,
Hercules,
C_47,
]
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]
@@ -605,7 +732,7 @@ def dcs_types_for_task(task: FlightType) -> Sequence[Type[FlyingType]]:
elif task == FlightType.SEAD:
return SEAD_CAPABLE
elif task == FlightType.SEAD_ESCORT:
return SEAD_CAPABLE
return SEAD_ESCORT_CAPABLE
elif task == FlightType.DEAD:
return DEAD_CAPABLE
elif task == FlightType.OCA_AIRCRAFT:
@@ -615,7 +742,7 @@ def dcs_types_for_task(task: FlightType) -> Sequence[Type[FlyingType]]:
elif task == FlightType.STRIKE:
return STRIKE_CAPABLE
elif task == FlightType.ESCORT:
return CAP_CAPABLE
return ESCORT_CAPABLE
elif task == FlightType.AEWC:
return AEWC_CAPABLE
elif task == FlightType.REFUELING:

View File

@@ -5,12 +5,17 @@ from datetime import datetime, timedelta
from typing import Any, List, Optional, TYPE_CHECKING
from dcs import Point
from dcs.planes import C_101CC, C_101EB, Su_33
from dcs.planes import C_101CC, C_101EB, Su_33, FA_18C_hornet
from pydcs_extensions.hercules.hercules import Hercules
from .flightroster import FlightRoster
from .flightstate import FlightState, Navigating, Uninitialized
from .flightstate.killed import Killed
from .loadouts import Loadout
from .loadouts import Loadout, Weapon
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
from ..radio.TacanContainer import TacanContainer
from ..radio.radios import RadioFrequency
from ..radio.tacan import TacanChannel
from ..sidc import (
Entity,
SidcDescribable,
@@ -18,9 +23,9 @@ from ..sidc import (
Status,
SymbolSet,
)
from game.dcs.aircrafttype import AircraftType
if TYPE_CHECKING:
from game.dcs.aircrafttype import AircraftType
from game.sim.gameupdateevents import GameUpdateEvents
from game.sim.simulationresults import SimulationResults
from game.squadrons import Squadron, Pilot
@@ -32,8 +37,10 @@ if TYPE_CHECKING:
from .package import Package
from .starttype import StartType
F18_TGP_PYLON: int = 4
class Flight(SidcDescribable):
class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
def __init__(
self,
package: Package,
@@ -46,6 +53,9 @@ class Flight(SidcDescribable):
custom_name: Optional[str] = None,
cargo: Optional[TransferOrder] = None,
roster: Optional[FlightRoster] = None,
frequency: Optional[RadioFrequency] = None,
channel: Optional[TacanChannel] = None,
callsign: Optional[str] = None,
) -> None:
self.id = uuid.uuid4()
self.package = package
@@ -63,6 +73,12 @@ class Flight(SidcDescribable):
self.start_type = start_type
self.use_custom_loadout = False
self.custom_name = custom_name
self.group_id: int = 0
self.frequency = frequency
if self.unit_type.dcs_unit_type.tacan:
self.tacan = channel
self.tcn_name = callsign
# Only used by transport missions.
self.cargo = cargo
@@ -86,10 +102,25 @@ class Flight(SidcDescribable):
self._flight_plan_builder = FlightPlanBuilderTypes.for_flight(self)(self)
is_f18 = self.squadron.aircraft.dcs_unit_type.id == FA_18C_hornet.id
on_land = not self.squadron.location.is_fleet
if on_land and is_f18 and self.coalition.game.settings.atflir_autoswap:
self.loadout.pylons[F18_TGP_PYLON] = Weapon.with_clsid(
str(
FA_18C_hornet.Pylon4.AN_AAQ_28_LITENING___Targeting_Pod_[1]["clsid"]
)
)
@property
def flight_plan(self) -> FlightPlan[Any]:
return self._flight_plan_builder.get_or_build()
def degrade_to_custom_flight_plan(self) -> None:
from .flightplans.custom import Builder as CustomBuilder
self._flight_plan_builder = CustomBuilder(self, self.flight_plan.waypoints[1:])
self.recreate_flight_plan()
def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy()
# Avoid persisting the flight state since that's not (currently) used outside
@@ -143,6 +174,10 @@ class Flight(SidcDescribable):
def is_helo(self) -> bool:
return self.unit_type.dcs_unit_type.helicopter
@property
def is_hercules(self) -> bool:
return self.unit_type == AircraftType.named("C-130J-30 Super Hercules")
@property
def from_cp(self) -> ControlPoint:
return self.departure
@@ -179,17 +214,18 @@ class Flight(SidcDescribable):
return Su_33.fuel_max * 0.8
elif unit_type in {C_101EB, C_101CC}:
return unit_type.fuel_max * 0.5
elif unit_type == Hercules:
return unit_type.fuel_max * 0.75
return None
def __repr__(self) -> str:
if self.custom_name:
return f"{self.custom_name} {self.count} x {self.unit_type}"
return f"[{self.flight_type}] {self.count} x {self.unit_type}"
return self.__str__()
def __str__(self) -> str:
string = f"[{self.flight_type}] {self.count} x {self.unit_type}"
if self.custom_name:
return f"{self.custom_name} {self.count} x {self.unit_type}"
return f"[{self.flight_type}] {self.count} x {self.unit_type}"
return f"{self.custom_name} - {string}"
return string
def abort(self) -> None:
from .flightplans.rtb import RtbFlightPlan
@@ -247,3 +283,15 @@ class Flight(SidcDescribable):
def recreate_flight_plan(self) -> None:
self._flight_plan_builder.regenerate()
@staticmethod
def clone_flight(flight: Flight) -> Flight:
return Flight(
flight.package,
flight.country,
flight.squadron,
flight.count,
flight.flight_type,
flight.start_type,
flight.divert,
)

View File

@@ -0,0 +1,20 @@
from __future__ import annotations
import random
from typing import Union, TYPE_CHECKING, Tuple
from game.theater.interfaces.CTLD import CTLD
from game.theater import ControlPoint
if TYPE_CHECKING:
from dcs import Point
def generate_random_ctld_point(cp: Union[ControlPoint, CTLD]) -> Point:
if isinstance(cp, CTLD) and cp.ctld_zones:
zone: Tuple[Point, float] = random.choice(cp.ctld_zones)
pos, radius = zone
return pos.random_point_within(radius)
elif isinstance(cp, CTLD) and isinstance(cp, ControlPoint):
return cp.position.random_point_within(2000, 200)
raise RuntimeError("Could not generate CTLD point")

View File

@@ -4,29 +4,44 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import Iterator, TYPE_CHECKING, Type
from game.ato.flightplans.airlift import AirliftLayout
from game.ato.flightplans.standard import StandardFlightPlan
from ._common_ctld import generate_random_ctld_point
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
from game.theater.controlpoint import ControlPointType
from game.theater.missiontarget import MissionTarget
from game.utils import Distance, feet, meters
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .uizonedisplay import UiZone, UiZoneDisplay
from .waypointbuilder import WaypointBuilder
from ..flightwaypoint import FlightWaypointType
from ...theater.interfaces.CTLD import CTLD
if TYPE_CHECKING:
from dcs import Point
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
class AirAssaultLayout(AirliftLayout):
class AirAssaultLayout(StandardLayout):
# The pickup point is optional because we don't always need to load the cargo. When
# departing from a carrier, LHA, or off-map spawn, the cargo is pre-loaded.
pickup: FlightWaypoint | None
nav_to_ingress: list[FlightWaypoint]
ingress: FlightWaypoint
drop_off: FlightWaypoint | None
# This is an implementation detail used by CTLD. The aircraft will not go to this
# waypoint. It is used by CTLD as the destination for unloaded troops.
target: FlightWaypoint
nav_to_home: list[FlightWaypoint]
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to_pickup
if self.pickup:
if self.pickup is not None:
yield self.pickup
yield from self.nav_to_drop_off
yield self.drop_off
yield from self.nav_to_ingress
yield self.ingress
if self.drop_off is not None:
yield self.drop_off
yield self.target
yield from self.nav_to_home
yield self.arrival
@@ -35,14 +50,16 @@ class AirAssaultLayout(AirliftLayout):
yield self.bullseye
class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]):
class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay):
@staticmethod
def builder_type() -> Type[Builder]:
return Builder
@property
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.drop_off
if self.flight.is_helo and self.layout.drop_off is not None:
return self.layout.drop_off
return self.layout.target
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.tot_waypoint:
@@ -53,65 +70,78 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]):
return None
@property
def engagement_distance(self) -> Distance:
# The radius of the WaypointZone created at the target location
def ctld_target_zone_radius(self) -> Distance:
return meters(2500)
@property
def mission_departure_time(self) -> timedelta:
return self.package.time_over_target
def ui_zone(self) -> UiZone:
return UiZone(
[self.layout.target.position],
self.ctld_target_zone_radius,
)
class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
def layout(self) -> AirAssaultLayout:
if not self.flight.is_helo and not self.flight.is_hercules:
raise PlanningError(
"Air assault is only usable by helicopters and Anubis' C-130 mod"
)
assert self.package.waypoints is not None
altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude
altitude_is_agl = self.flight.is_helo
builder = WaypointBuilder(self.flight, self.coalition)
if not self.flight.is_helo or self.flight.departure.cptype in [
if self.flight.is_hercules or self.flight.departure.cptype in [
ControlPointType.AIRCRAFT_CARRIER_GROUP,
ControlPointType.LHA_GROUP,
ControlPointType.OFF_MAP,
]:
# Non-Helo flights or Off_Map will be preloaded
# Off_Map spawns will be preloaded
# Carrier operations load the logistics directly from the carrier
pickup = None
pickup_position = self.flight.departure.position
else:
# Create a special pickup zone for Helos from Airbase / FOB
pickup = builder.pickup(
pickup = builder.pickup_zone(
MissionTarget(
"Pickup Zone",
self.flight.departure.position.random_point_within(1200, 600),
self._generate_ctld_pickup(),
)
)
pickup_position = pickup.position
assault_area = builder.assault_area(self.package.target)
heading = self.package.target.position.heading_between_point(pickup_position)
if self.flight.is_hercules:
assault_area.only_for_player = False
assault_area.alt = feet(1000)
# TODO: define CTLD dropoff zones in campaign miz?
drop_off_zone = MissionTarget(
"Dropoff zone",
self.package.target.position.point_from_heading(heading, 1200),
)
dz = builder.dropoff_zone(drop_off_zone) if self.flight.is_helo else None
return AirAssaultLayout(
departure=builder.takeoff(self.flight.departure),
nav_to_pickup=builder.nav_path(
self.flight.departure.position,
pickup_position,
altitude,
altitude_is_agl,
),
pickup=pickup,
nav_to_drop_off=builder.nav_path(
nav_to_ingress=builder.nav_path(
pickup_position,
drop_off_zone.position,
self.package.waypoints.ingress,
altitude,
altitude_is_agl,
),
drop_off=builder.drop_off(drop_off_zone),
stopover=None,
ingress=builder.ingress(
FlightWaypointType.INGRESS_AIR_ASSAULT,
self.package.waypoints.ingress,
self.package.target,
),
drop_off=dz,
target=assault_area,
nav_to_home=builder.nav_path(
drop_off_zone.position,
@@ -126,3 +156,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
def build(self) -> AirAssaultFlightPlan:
return AirAssaultFlightPlan(self.flight, self.layout())
def _generate_ctld_pickup(self) -> Point:
assert isinstance(self.flight.departure, CTLD)
return generate_random_ctld_point(self.flight.departure)

View File

@@ -5,24 +5,37 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, Type
from ._common_ctld import generate_random_ctld_point
from game.theater.missiontarget import MissionTarget
from game.utils import feet
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .standard import StandardFlightPlan, StandardLayout
from .waypointbuilder import WaypointBuilder
from ...theater.interfaces.CTLD import CTLD
if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
from dcs import Point
@dataclass(frozen=True)
class AirliftLayout(StandardLayout):
nav_to_pickup: list[FlightWaypoint]
# There will not be a pickup waypoint when the pickup airfield is the departure
# airfield for cargo planes, as the cargo is pre-loaded. Helicopters will still pick
# up the cargo near the airfield.
pickup: FlightWaypoint | None
# pickup_zone will be used for player flights to create the CTLD stuff
ctld_pickup_zone: FlightWaypoint | None
nav_to_drop_off: list[FlightWaypoint]
drop_off: FlightWaypoint
stopover: FlightWaypoint | None
# There will not be a drop-off waypoint when the drop-off airfield and the arrival
# airfield is the same for a cargo plane, as planes will land to unload and we don't
# want a double landing. Helicopters will still drop their cargo near the airfield
# before landing.
drop_off: FlightWaypoint | None
# drop_off_zone will be used for player flights to create the CTLD stuff
ctld_drop_off_zone: FlightWaypoint | None
nav_to_home: list[FlightWaypoint]
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
@@ -30,10 +43,13 @@ class AirliftLayout(StandardLayout):
yield from self.nav_to_pickup
if self.pickup is not None:
yield self.pickup
if self.ctld_pickup_zone is not None:
yield self.ctld_pickup_zone
yield from self.nav_to_drop_off
yield self.drop_off
if self.stopover is not None:
yield self.stopover
if self.drop_off is not None:
yield self.drop_off
if self.ctld_drop_off_zone is not None:
yield self.ctld_drop_off_zone
yield from self.nav_to_home
yield self.arrival
if self.divert is not None:
@@ -48,7 +64,11 @@ class AirliftFlightPlan(StandardFlightPlan[AirliftLayout]):
@property
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.drop_off
# The TOT is the time that the cargo will be dropped off. If the drop-off
# location is the arrival airfield and this is not a helicopter flight, there
# will not be a separate drop-off waypoint; the arrival landing waypoint is the
# drop-off waypoint.
return self.layout.drop_off or self.layout.arrival
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
# TOT planning isn't really useful for transports. They're behind the front
@@ -77,31 +97,26 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
builder = WaypointBuilder(self.flight, self.coalition)
pickup = None
stopover = None
drop_off = None
pickup_zone = None
drop_off_zone = None
if cargo.origin != self.flight.departure:
pickup = builder.cargo_stop(cargo.origin)
if cargo.next_stop != self.flight.arrival:
drop_off = builder.cargo_stop(cargo.next_stop)
if self.flight.is_helo:
# Create a pickupzone where the cargo will be spawned
pickup_zone = MissionTarget(
"Pickup Zone", cargo.origin.position.random_point_within(1000, 200)
# Create CTLD Zones for Helo flights
pickup_zone = builder.pickup_zone(
MissionTarget("Pickup Zone", self._generate_ctld_pickup())
)
pickup = builder.pickup(pickup_zone)
# If The cargo is at the departure controlpoint, the pickup waypoint should
# only be created for client flights
pickup.only_for_player = cargo.origin == self.flight.departure
# Create a dropoff zone where the cargo should be dropped
drop_off_zone = MissionTarget(
"Dropoff zone",
cargo.next_stop.position.random_point_within(1000, 200),
drop_off_zone = builder.dropoff_zone(
MissionTarget("Dropoff zone", self._generate_ctld_dropoff())
)
drop_off = builder.drop_off(drop_off_zone)
# Add an additional stopover point so that the flight can refuel
stopover = builder.stopover(cargo.next_stop)
else:
# Fixed Wing will get stopover points for pickup and dropoff
if cargo.origin != self.flight.departure:
pickup = builder.stopover(cargo.origin, "PICKUP")
drop_off = builder.stopover(cargo.next_stop, "DROP OFF")
# Show the zone waypoints only to the player
pickup_zone.only_for_player = True
drop_off_zone.only_for_player = True
nav_to_pickup = builder.nav_path(
self.flight.departure.position,
@@ -110,20 +125,11 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
altitude_is_agl,
)
if self.flight.client_count > 0:
# Normal Landing Waypoint
arrival = builder.land(self.flight.arrival)
else:
# The AI Needs another Stopover point to actually fly back to the original
# base. Otherwise the Cargo drop will be the new Landing Waypoint and the
# AI will end its mission there instead of flying back.
# https://forum.dcs.world/topic/211775-landing-to-refuel-and-rearm-the-landingrefuar-waypoint/
arrival = builder.stopover(self.flight.arrival, "LANDING")
return AirliftLayout(
departure=builder.takeoff(self.flight.departure),
nav_to_pickup=nav_to_pickup,
pickup=pickup,
ctld_pickup_zone=pickup_zone,
nav_to_drop_off=builder.nav_path(
cargo.origin.position,
cargo.next_stop.position,
@@ -131,17 +137,29 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
altitude_is_agl,
),
drop_off=drop_off,
stopover=stopover,
ctld_drop_off_zone=drop_off_zone,
nav_to_home=builder.nav_path(
cargo.origin.position,
self.flight.arrival.position,
altitude,
altitude_is_agl,
),
arrival=arrival,
arrival=builder.land(self.flight.arrival),
divert=builder.divert(self.flight.divert),
bullseye=builder.bullseye(),
)
def build(self) -> AirliftFlightPlan:
return AirliftFlightPlan(self.flight, self.layout())
def _generate_ctld_pickup(self) -> Point:
cargo = self.flight.cargo
if cargo and cargo.origin and isinstance(cargo.origin, CTLD):
return generate_random_ctld_point(cargo.origin)
raise RuntimeError("Could not generate CTLD pickup")
def _generate_ctld_dropoff(self) -> Point:
cargo = self.flight.cargo
if cargo and cargo.transport and isinstance(cargo.transport.destination, CTLD):
return generate_random_ctld_point(cargo.transport.destination)
raise RuntimeError("Could not generate CTLD dropoff")

View File

@@ -10,6 +10,7 @@ from game.utils import Distance, Speed, kph, meters
from .ibuilder import IBuilder
from .invalidobjectivelocation import InvalidObjectiveLocation
from .patrolling import PatrollingFlightPlan, PatrollingLayout
from .uizonedisplay import UiZone, UiZoneDisplay
from .waypointbuilder import WaypointBuilder
from ..flightwaypointtype import FlightWaypointType
@@ -28,13 +29,13 @@ class CasLayout(PatrollingLayout):
yield self.target
yield self.patrol_end
yield from self.nav_from
yield self.departure
yield self.arrival
if self.divert is not None:
yield self.divert
yield self.bullseye
class CasFlightPlan(PatrollingFlightPlan[CasLayout]):
class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
@staticmethod
def builder_type() -> Type[Builder]:
return Builder
@@ -65,6 +66,12 @@ class CasFlightPlan(PatrollingFlightPlan[CasLayout]):
def dismiss_escort_at(self) -> FlightWaypoint | None:
return self.layout.patrol_end
def ui_zone(self) -> UiZone:
return UiZone(
[self.layout.target.position],
self.engagement_distance,
)
class Builder(IBuilder[CasFlightPlan, CasLayout]):
def layout(self) -> CasLayout:

View File

@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Type
from .flightplan import FlightPlan, Layout
from .ibuilder import IBuilder
from .waypointbuilder import WaypointBuilder
from .. import Flight
from ..flightwaypointtype import FlightWaypointType
if TYPE_CHECKING:
@@ -55,9 +56,17 @@ class CustomFlightPlan(FlightPlan[CustomLayout]):
class Builder(IBuilder[CustomFlightPlan, CustomLayout]):
def __init__(
self, flight: Flight, waypoints: list[FlightWaypoint] | None = None
) -> None:
super().__init__(flight)
if waypoints is None:
waypoints = []
self.waypoints = waypoints
def layout(self) -> CustomLayout:
builder = WaypointBuilder(self.flight, self.coalition)
return CustomLayout(builder.takeoff(self.flight.departure), [])
return CustomLayout(builder.takeoff(self.flight.departure), self.waypoints)
def build(self) -> CustomFlightPlan:
return CustomFlightPlan(self.flight, self.layout())

View File

@@ -24,12 +24,15 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
ingress, target = builder.escort(
self.package.waypoints.ingress, self.package.target
)
ingress.only_for_player = True
target.only_for_player = True
hold = builder.hold(self._hold_point())
join = builder.join(self.package.waypoints.join)
split = builder.split(self.package.waypoints.split)
refuel = None
if self.package.waypoints.refuel is not None:
refuel = builder.refuel(self.package.waypoints.refuel)
refuel = builder.refuel(self.package.waypoints.refuel)
initial = builder.escort_hold(
self.package.waypoints.initial, self.doctrine.ingress_altitude
)
return FormationAttackLayout(
departure=builder.takeoff(self.flight.departure),
@@ -39,11 +42,12 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
),
join=join,
ingress=ingress,
initial=initial,
targets=[target],
split=split,
refuel=refuel,
nav_from=builder.nav_path(
split.position,
refuel.position,
self.flight.arrival.position,
self.doctrine.ingress_altitude,
),

View File

@@ -72,6 +72,15 @@ class FlightPlan(ABC, Generic[LayoutT]):
"""A list of all waypoints in the flight plan, in order."""
return list(self.iter_waypoints())
def get_index_of_wpt_by_type(self, wpt_type: FlightWaypointType) -> int:
index = 0
for wpt in self.waypoints:
if wpt and not wpt.only_for_player:
index += 1
if wpt.waypoint_type == wpt_type:
return index
return -1
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
"""Iterates over all waypoints in the flight plan, in order."""
yield from self.layout.iter_waypoints()

View File

@@ -26,12 +26,13 @@ from .theaterrefueling import TheaterRefuelingFlightPlan
if TYPE_CHECKING:
from game.ato import Flight
from game.theater import FrontLine
class FlightPlanBuilderTypes:
@staticmethod
def for_flight(flight: Flight) -> Type[IBuilder[Any, Any]]:
from game.theater import FrontLine
if flight.flight_type is FlightType.REFUELING:
if flight.package.target.is_friendly(flight.squadron.player) or isinstance(
flight.package.target, FrontLine

View File

@@ -4,13 +4,13 @@ from abc import ABC
from collections.abc import Iterator
from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, TypeVar
from typing import TYPE_CHECKING, TypeVar, Optional
from dcs import Point
from game.flightplan import HoldZoneGeometry
from game.theater import MissionTarget
from game.utils import Speed, meters
from game.utils import Speed, meters, Distance
from .flightplan import FlightPlan
from .formation import FormationFlightPlan, FormationLayout
from .ibuilder import IBuilder
@@ -134,6 +134,7 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
class FormationAttackLayout(FormationLayout):
ingress: FlightWaypoint
targets: list[FlightWaypoint]
initial: Optional[FlightWaypoint] = None
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
@@ -141,6 +142,8 @@ class FormationAttackLayout(FormationLayout):
yield from self.nav_to
yield self.join
yield self.ingress
if self.initial is not None:
yield self.initial
yield from self.targets
yield self.split
if self.refuel is not None:
@@ -181,9 +184,11 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
hold = builder.hold(self._hold_point())
join = builder.join(self.package.waypoints.join)
split = builder.split(self.package.waypoints.split)
refuel = None
if self.package.waypoints.refuel is not None:
refuel = builder.refuel(self.package.waypoints.refuel)
refuel = builder.refuel(self.package.waypoints.refuel)
ingress = builder.ingress(
ingress_type, self.package.waypoints.ingress, self.package.target
)
return FormationAttackLayout(
departure=builder.takeoff(self.flight.departure),
@@ -192,14 +197,12 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
hold.position, join.position, self.doctrine.ingress_altitude
),
join=join,
ingress=builder.ingress(
ingress_type, self.package.waypoints.ingress, self.package.target
),
ingress=ingress,
targets=target_waypoints,
split=split,
refuel=refuel,
nav_from=builder.nav_path(
split.position,
refuel.position,
self.flight.arrival.position,
self.doctrine.ingress_altitude,
),

View File

@@ -9,6 +9,7 @@ from typing import Any, TYPE_CHECKING, TypeGuard, TypeVar
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
from game.typeguard import self_type_guard
from game.utils import Distance, Speed
from .uizonedisplay import UiZone, UiZoneDisplay
if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@@ -37,7 +38,7 @@ class PatrollingLayout(StandardLayout):
LayoutT = TypeVar("LayoutT", bound=PatrollingLayout)
class PatrollingFlightPlan(StandardFlightPlan[LayoutT], ABC):
class PatrollingFlightPlan(StandardFlightPlan[LayoutT], UiZoneDisplay, ABC):
@property
@abstractmethod
def patrol_duration(self) -> timedelta:
@@ -97,3 +98,9 @@ class PatrollingFlightPlan(StandardFlightPlan[LayoutT], ABC):
self, flight_plan: FlightPlan[Any]
) -> TypeGuard[PatrollingFlightPlan[Any]]:
return True
def ui_zone(self) -> UiZone:
return UiZone(
[self.layout.patrol_start.position, self.layout.patrol_end.position],
self.engagement_distance,
)

View File

@@ -22,7 +22,6 @@ class SweepLayout(LoiterLayout):
nav_to: list[FlightWaypoint]
sweep_start: FlightWaypoint
sweep_end: FlightWaypoint
refuel: FlightWaypoint | None
nav_from: list[FlightWaypoint]
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
@@ -31,8 +30,6 @@ class SweepLayout(LoiterLayout):
yield from self.nav_to
yield self.sweep_start
yield self.sweep_end
if self.refuel is not None:
yield self.refuel
yield from self.nav_from
yield self.arrival
if self.divert is not None:
@@ -92,6 +89,7 @@ class SweepFlightPlan(LoiterFlightPlan):
GroundSpeed.for_flight(self.flight, self.layout.hold.alt),
)
@property
def mission_departure_time(self) -> timedelta:
return self.sweep_end_time
@@ -112,11 +110,6 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
hold = builder.hold(self._hold_point())
refuel = None
if self.package.waypoints is not None:
refuel = builder.refuel(self.package.waypoints.refuel)
return SweepLayout(
departure=builder.takeoff(self.flight.departure),
hold=hold,
@@ -130,7 +123,6 @@ class Builder(IBuilder[SweepFlightPlan, SweepLayout]):
),
sweep_start=start,
sweep_end=end,
refuel=refuel,
arrival=builder.land(self.flight.arrival),
divert=builder.divert(self.flight.divert),
bullseye=builder.bullseye(),

View File

@@ -105,9 +105,11 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
refuel = None
nav_from_origin = orbit1p
if self.package.waypoints is not None:
refuel = builder.refuel(self.package.waypoints.refuel)
nav_from_origin = refuel.position
return TarCapLayout(
departure=builder.takeoff(self.flight.departure),
@@ -115,7 +117,7 @@ class Builder(CapBuilder[TarCapFlightPlan, TarCapLayout]):
self.flight.departure.position, orbit0p, patrol_alt
),
nav_from=builder.nav_path(
orbit1p, self.flight.arrival.position, patrol_alt
nav_from_origin, self.flight.arrival.position, patrol_alt
),
patrol_start=start,
patrol_end=end,

View File

@@ -17,7 +17,7 @@ class TheaterRefuelingFlightPlan(RefuelingFlightPlan):
@property
def patrol_duration(self) -> timedelta:
return timedelta(hours=1)
return self.flight.coalition.game.settings.desired_tanker_on_station_time
class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):

View File

@@ -0,0 +1,18 @@
import abc
from dataclasses import dataclass
from dcs import Point
from game.utils import Distance
@dataclass(frozen=True)
class UiZone:
points: list[Point]
radius: Distance
class UiZoneDisplay(abc.ABC):
@abc.abstractmethod
def ui_zone(self) -> UiZone:
...

View File

@@ -14,6 +14,7 @@ from typing import (
from dcs.mapping import Point, Vector2
from game.ato.flightplans._common_ctld import generate_random_ctld_point
from game.ato.flightwaypoint import AltitudeReference, FlightWaypoint
from game.ato.flightwaypointtype import FlightWaypointType
from game.theater import (
@@ -23,7 +24,8 @@ from game.theater import (
TheaterGroundObject,
TheaterUnit,
)
from game.utils import Distance, meters, nautical_miles
from game.theater.interfaces.CTLD import CTLD
from game.utils import Distance, meters, nautical_miles, feet
if TYPE_CHECKING:
from game.coalition import Coalition
@@ -127,10 +129,9 @@ class WaypointBuilder:
position = divert.position
altitude_type: AltitudeReference
if isinstance(divert, OffMapSpawn):
if self.is_helo:
altitude = meters(500)
else:
altitude = self.doctrine.rendezvous_altitude
altitude = (
meters(500) if self.is_helo else self.doctrine.rendezvous_altitude
)
altitude_type = "BARO"
else:
altitude = meters(0)
@@ -225,15 +226,17 @@ class WaypointBuilder:
position: Point,
objective: MissionTarget,
) -> FlightWaypoint:
alt = self.doctrine.ingress_altitude
alt_type: AltitudeReference = "BARO"
if self.is_helo:
if self.is_helo or self.flight.is_hercules:
alt_type = "RADIO"
alt = meters(60) if self.is_helo else feet(1000)
return FlightWaypoint(
"INGRESS",
ingress_type,
position,
meters(60) if self.is_helo else self.doctrine.ingress_altitude,
alt,
alt_type,
description=f"INGRESS on {objective.name}",
pretty_name=f"INGRESS on {objective.name}",
@@ -304,6 +307,13 @@ class WaypointBuilder:
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
def assault_area(self, target: MissionTarget) -> FlightWaypoint:
"""A destination waypoint used by air-assault ground troops.
This waypoint is an implementation detail for CTLD and should not be followed by
aircraft.
"""
# TODO: Add a property that can hide this waypoint from the player's flight
# plan.
return self._target_area(f"ASSAULT {target.name}", target)
@staticmethod
@@ -415,6 +425,24 @@ class WaypointBuilder:
pretty_name="Orbit",
)
@staticmethod
def escort_hold(start: Point, altitude: Distance) -> FlightWaypoint:
"""Creates custom waypoint for escort flights that need to hold.
Args:
start: Position of the waypoint.
altitude: Altitude of the holding pattern.
"""
return FlightWaypoint(
"ESCORT HOLD",
FlightWaypointType.CUSTOM,
start,
altitude,
description="Anchor and hold at this point",
pretty_name="Escort Hold",
)
@staticmethod
def sweep_start(position: Point, altitude: Distance) -> FlightWaypoint:
"""Creates a sweep start waypoint.
@@ -494,58 +522,54 @@ class WaypointBuilder:
)
@staticmethod
def stopover(stopover: ControlPoint, name: str = "STOPOVER") -> FlightWaypoint:
"""Creates a stopover waypoint.
Args:
control_point: Pick up location.
def pickup_zone(pick_up: MissionTarget) -> FlightWaypoint:
"""Creates a pickup landing zone waypoint
This waypoint is used to generate the Trigger Zone used for AirAssault and
AirLift using the CTLD plugin (see LogisticsGenerator)
"""
return FlightWaypoint(
name,
FlightWaypointType.STOPOVER,
stopover.position,
meters(0),
"RADIO",
description=f"Stopover at {stopover}",
pretty_name="Stopover location",
control_point=stopover,
)
@staticmethod
def pickup(pick_up: MissionTarget) -> FlightWaypoint:
"""Creates a cargo pickup waypoint.
Args:
control_point: Pick up location.
"""
control_point = pick_up if isinstance(pick_up, ControlPoint) else None
return FlightWaypoint(
"PICKUP",
FlightWaypointType.PICKUP,
"PICKUPZONE",
FlightWaypointType.PICKUP_ZONE,
pick_up.position,
meters(0),
"RADIO",
description=f"Pick up cargo from {pick_up.name}",
pretty_name="Pick up location",
control_point=control_point,
pretty_name="Pick-up zone",
)
@staticmethod
def drop_off(drop_off: MissionTarget) -> FlightWaypoint:
"""Creates a cargo drop-off waypoint.
Args:
control_point: Drop-off location.
def dropoff_zone(drop_off: MissionTarget) -> FlightWaypoint:
"""Creates a dropoff landing zone waypoint
This waypoint is used to generate the Trigger Zone used for AirAssault and
AirLift using the CTLD plugin (see LogisticsGenerator)
"""
control_point = drop_off if isinstance(drop_off, ControlPoint) else None
return FlightWaypoint(
"DROP OFF",
FlightWaypointType.DROP_OFF,
"DROPOFFZONE",
FlightWaypointType.DROPOFF_ZONE,
drop_off.position,
meters(0),
"RADIO",
description=f"Drop off cargo at {drop_off.name}",
pretty_name="Drop off location",
pretty_name="Drop-off zone",
)
@staticmethod
def cargo_stop(control_point: ControlPoint) -> FlightWaypoint:
"""Creates a cargo stop waypoint.
This waypoint is used by AirLift as a landing and stopover waypoint
"""
if isinstance(control_point, CTLD) and control_point.ctld_zones:
pos = generate_random_ctld_point(control_point)
else:
pos = control_point.position
return FlightWaypoint(
"CARGOSTOP",
FlightWaypointType.CARGO_STOP,
pos,
meters(0),
"RADIO",
description=f"Stop for cargo at {control_point.name}",
pretty_name="Cargo stop",
control_point=control_point,
)

View File

@@ -137,6 +137,7 @@ class InFlight(FlightState, ABC):
FlightWaypointType.INGRESS_OCA_RUNWAY,
FlightWaypointType.INGRESS_SEAD,
FlightWaypointType.INGRESS_STRIKE,
FlightWaypointType.INGRESS_AIR_ASSAULT,
}
return self.current_waypoint.waypoint_type in contact_types

View File

@@ -40,6 +40,11 @@ class Navigating(InFlight):
)
def estimate_altitude(self) -> tuple[Distance, str]:
# This does not behave well when one of the waypoints is AGL and the other is
# MSL. We can't really avoid that problem though. We don't know where the ground
# is, so conversions between them are impossible, and we do need to use AGL
# altitudes for takeoff and landing waypoints (even if we had the runway
# elevation, we don't have elevation for FARPs).
return (
meters(
lerp(

View File

@@ -1,6 +1,5 @@
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass, field
from datetime import timedelta
from typing import Literal, TYPE_CHECKING
@@ -12,8 +11,7 @@ from game.theater.theatergroup import TheaterUnit
from game.utils import Distance, meters
if TYPE_CHECKING:
from game.theater import ControlPoint, MissionTarget
from game.theater import ControlPoint
AltitudeReference = Literal["BARO", "RADIO"]
@@ -32,7 +30,7 @@ class FlightWaypoint:
# having three names. A short and long form is enough.
description: str = ""
targets: Sequence[MissionTarget | TheaterUnit] = field(default_factory=list)
targets: list[TheaterUnit] = field(default_factory=list)
obj_name: str = ""
pretty_name: str = ""
only_for_player: bool = False

View File

@@ -43,8 +43,9 @@ class FlightWaypointType(IntEnum):
DIVERT = 23
INGRESS_OCA_RUNWAY = 24
INGRESS_OCA_AIRCRAFT = 25
PICKUP = 26
DROP_OFF = 27
PICKUP_ZONE = 26 # Pickup Zone for cargo or troops
DROPOFF_ZONE = 27 # Dropoff Zone for cargo or troops
BULLSEYE = 28
REFUEL = 29 # Should look for nearby tanker to refuel from.
STOPOVER = 30 # Stopover landing point using the LandingReFuAr waypoint type
CARGO_STOP = 30 # Stopover landing point using the LandingReFuAr waypoint type
INGRESS_AIR_ASSAULT = 31

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import datetime
import logging
from collections.abc import Iterable
from typing import Iterator, Mapping, Optional, TYPE_CHECKING, Type, Dict, Any
from typing import Iterator, Optional, TYPE_CHECKING, Type, Dict
from dcs.unittype import FlyingType
@@ -19,14 +19,14 @@ class Loadout:
def __init__(
self,
name: str,
pylons: Mapping[int, Optional[Weapon]],
pylons: Dict[int, Optional[Weapon]],
date: Optional[datetime.date],
is_custom: bool = False,
) -> None:
self.name = name
# We clear unused pylon entries on initialization, but UI actions can still
# cause a pylon to be emptied, so make the optional type explicit.
self.pylons: Mapping[int, Optional[Weapon]] = {
self.pylons: Dict[int, Optional[Weapon]] = {
k: v for k, v in pylons.items() if v is not None
}
self.date = date
@@ -151,10 +151,22 @@ class Loadout:
# names, so those have been included here too. The priority goes from first to
# last - the first element in the tuple will be tried first, then the second,
# etc.
loadout_names = {t: [f"Liberation {t.value}"] for t in FlightType}
loadout_names = {
t: [f"Retribution {t.value}", f"Liberation {t.value}"] for t in FlightType
}
legacy_names = {
FlightType.TARCAP: ("CAP HEAVY", "CAP", "Liberation BARCAP"),
FlightType.BARCAP: ("CAP HEAVY", "CAP", "Liberation TARCAP"),
FlightType.TARCAP: (
"CAP HEAVY",
"CAP",
"Retribution BARCAP",
"Liberation BARCAP",
),
FlightType.BARCAP: (
"CAP HEAVY",
"CAP",
"Retribution TARCAP",
"Liberation TARCAP",
),
FlightType.CAS: ("CAS MAVERICK F", "CAS"),
FlightType.STRIKE: ("STRIKE",),
FlightType.ANTISHIP: ("ANTISHIP",),

View File

@@ -13,19 +13,28 @@ from .flightplans.formation import FormationFlightPlan
from .flighttype import FlightType
from .packagewaypoints import PackageWaypoints
from .traveltime import TotEstimator
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
from ..radio.radios import RadioFrequency
if TYPE_CHECKING:
from game.theater import ControlPoint, MissionTarget
class Package:
class Package(RadioFrequencyContainer):
"""A mission package."""
def __init__(
self, target: MissionTarget, db: Database[Flight], auto_asap: bool = False
self,
target: MissionTarget,
db: Database[Flight],
auto_asap: bool = False,
custom_name: str | None = None,
frequency: RadioFrequency | None = None,
) -> None:
self.target = target
self._db = db
self.frequency = frequency
self.custom_name = custom_name
# True if the package ToT should be reset to ASAP whenever the player makes a
# change. This is really a UI property rather than a game property, but we want
@@ -129,6 +138,16 @@ class Package:
if not self.flights:
self.waypoints = None
@property
def primary_flight(self) -> Optional[Flight]:
task = self.primary_task
if not task:
return None
primaries = [x for x in self.flights if x.flight_type == task]
if len(primaries) > 0:
return primaries[0]
return None
@property
def primary_task(self) -> Optional[FlightType]:
if not self.flights:
@@ -204,3 +223,12 @@ class Package:
if flight.departure == airfield:
return airfield
raise RuntimeError("Could not find any airfield assigned to this package")
@staticmethod
def clone_package(package: Package) -> Package:
clone = Package(package.target, package._db, package.auto_asap)
clone.time_over_target = package.time_over_target
for f in package.flights:
cf = Flight.clone_flight(f)
clone.add_flight(cf)
return clone

View File

@@ -1,13 +1,15 @@
from __future__ import annotations
import random
from dataclasses import dataclass
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Optional
from dcs import Point
from game.ato.flightplans.waypointbuilder import WaypointBuilder
from game.flightplan import IpZoneGeometry, JoinZoneGeometry
from game.flightplan.refuelzonegeometry import RefuelZoneGeometry
from game.utils import nautical_miles
if TYPE_CHECKING:
from game.ato import Package
@@ -18,6 +20,7 @@ if TYPE_CHECKING:
class PackageWaypoints:
join: Point
ingress: Point
initial: Point
split: Point
refuel: Point
@@ -32,6 +35,11 @@ class PackageWaypoints:
coalition,
).find_best_ip()
hdg = package.target.position.heading_between_point(ingress_point)
# Generate a waypoint randomly between 7 & 9 NM
dist = nautical_miles(random.random() * 2 + 7).meters
initial_point = package.target.position.point_from_heading(hdg, dist)
join_point = JoinZoneGeometry(
package.target.position,
origin.position,
@@ -51,6 +59,7 @@ class PackageWaypoints:
return PackageWaypoints(
WaypointBuilder.perturb(join_point),
ingress_point,
initial_point,
WaypointBuilder.perturb(join_point),
refuel_point,
)

View File

@@ -3,8 +3,8 @@ import logging
import re
from typing import Any
from dcs.unitgroup import FlyingGroup
from dcs.flyingunit import FlyingUnit
from dcs.unitgroup import FlyingGroup
def callsign_for_support_unit(group: FlyingGroup[Any]) -> str:
@@ -15,7 +15,7 @@ def callsign_for_support_unit(group: FlyingGroup[Any]) -> str:
try:
return f"Flight {int(raw_callsign)}"
except ValueError:
return raw_callsign.rstrip("1234567890")
return f"{raw_callsign[:-2]} {raw_callsign[-2]}"
def create_group_callsign_from_unit(lead: FlyingUnit) -> str:

View File

@@ -19,6 +19,7 @@ from game.theater.iadsnetwork.iadsnetwork import IadsNetwork
from game.theater.theaterloader import TheaterLoader
from game.version import CAMPAIGN_FORMAT_VERSION
from .campaignairwingconfig import CampaignAirWingConfig
from .campaigngroundconfig import TgoConfig
from .mizcampaignloader import MizCampaignLoader
PERF_FRIENDLY = 0
@@ -32,6 +33,7 @@ DEFAULT_BUDGET = 2000
class Campaign:
name: str
menu_thumbnail_dcs_relative_path: Path
fallback_icon_path: Path
authors: str
description: str
@@ -90,6 +92,7 @@ class Campaign:
return cls(
data["name"],
TheaterLoader(data["theater"].lower()).menu_thumbnail_dcs_relative_path,
TheaterLoader(data["theater"].lower()).icon_path,
data.get("authors", "???"),
data.get("description", ""),
(version.major, version.minor),
@@ -135,6 +138,13 @@ class Campaign:
return CampaignAirWingConfig({})
return CampaignAirWingConfig.from_campaign_data(squadron_data, theater)
def load_ground_forces_config(self) -> TgoConfig:
ground_forces = self.data.get("ground_forces", {})
if not ground_forces:
logging.warning(f"Campaign {self.name} does not define any ground_forces")
return TgoConfig({})
return TgoConfig.from_campaign_data(ground_forces)
@property
def is_out_of_date(self) -> bool:
"""Returns True if this campaign is not up to date with the latest format.
@@ -171,7 +181,7 @@ class Campaign:
@classmethod
def iter_campaign_defs(cls) -> Iterator[Path]:
yield from cls.iter_campaigns_in_dir(
Path(persistency.base_path()) / "Liberation/Campaigns"
Path(persistency.base_path()) / "Retribution/Campaigns"
)
yield from cls.iter_campaigns_in_dir(Path("resources/campaigns"))

View File

@@ -0,0 +1,25 @@
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from typing import Iterator, Optional
from game.armedforces.forcegroup import ForceGroup
@dataclass(frozen=True)
class TgoConfig:
by_original_name: dict[str, ForceGroup]
def __iter__(self) -> Iterator[str]:
return self.by_original_name.__iter__()
def __getitem__(self, name: str) -> Optional[ForceGroup]:
return self.by_original_name.get(name)
@classmethod
def from_campaign_data(cls, data: dict[str, str]) -> TgoConfig:
by_original_name: dict[str, ForceGroup] = defaultdict()
for tgo_name, force_group in data.items():
by_original_name[tgo_name] = ForceGroup.from_preset_group(force_group)
return TgoConfig(by_original_name)

View File

@@ -1,13 +1,12 @@
from __future__ import annotations
import dataclasses
import logging
from typing import Optional, TYPE_CHECKING, Dict, Union
from typing import Optional, TYPE_CHECKING
from game.squadrons import Squadron
from game.squadrons.squadrondef import SquadronDef
from ..ato.flighttype import FlightType
from .campaignairwingconfig import CampaignAirWingConfig, SquadronConfig
from ..ato.flighttype import FlightType
from ..dcs.aircrafttype import AircraftType
from ..theater import ControlPoint
@@ -30,7 +29,6 @@ class DefaultSquadronAssigner:
self.coalition.player
):
for squadron_config in self.config.by_location[control_point]:
squadron_def = self.override_squadron_defaults(
self.find_squadron_for(squadron_config, control_point),
squadron_config,
@@ -54,7 +52,6 @@ class DefaultSquadronAssigner:
def find_squadron_for(
self, config: SquadronConfig, control_point: ControlPoint
) -> Optional[SquadronDef]:
for preferred_aircraft in config.aircraft:
squadron_def = self.find_preferred_squadron(
preferred_aircraft, config.primary, control_point
@@ -95,8 +92,9 @@ class DefaultSquadronAssigner:
if aircraft not in self.coalition.faction.aircrafts:
return None
lo = self.coalition.faction.liveries_overrides
squadron_def = self.find_squadron_for_airframe(aircraft, task, control_point)
if squadron_def is not None:
if squadron_def is not None and lo.get(aircraft) is None:
return squadron_def
# No premade squadron available for this aircraft that meets the requirements,
@@ -154,7 +152,6 @@ class DefaultSquadronAssigner:
def override_squadron_defaults(
squadron_def: Optional[SquadronDef], config: SquadronConfig
) -> Optional[SquadronDef]:
if squadron_def is None:
return None

View File

@@ -32,6 +32,7 @@ from game.utils import Distance, meters
if TYPE_CHECKING:
from game.theater.conflicttheater import ConflictTheater
from dcs import Point
class MizCampaignLoader:
@@ -107,8 +108,12 @@ class MizCampaignLoader:
if self.mission.country(self.RED_COUNTRY.name) is None:
self.mission.coalition["red"].add_country(self.RED_COUNTRY)
def control_point_from_airport(self, airport: Airport) -> ControlPoint:
cp = Airfield(airport, self.theater, starts_blue=airport.is_blue())
def control_point_from_airport(
self, airport: Airport, ctld_zones: List[Tuple[Point, float]]
) -> ControlPoint:
cp = Airfield(
airport, self.theater, starts_blue=airport.is_blue(), ctld_zones=ctld_zones
)
# Use the unlimited aircraft option to determine if an airfield should
# be owned by the player when the campaign is "inverted".
@@ -245,7 +250,8 @@ class MizCampaignLoader:
control_points = {}
for airport in self.mission.terrain.airport_list():
if airport.is_blue() or airport.is_red():
control_point = self.control_point_from_airport(airport)
ctld_zones = self.get_ctld_zones(airport.name)
control_point = self.control_point_from_airport(airport, ctld_zones)
control_points[control_point.id] = control_point
for blue in (False, True):
@@ -268,8 +274,13 @@ class MizCampaignLoader:
control_point.captured_invert = ship.late_activation
control_points[control_point.id] = control_point
for fob in self.fobs(blue):
ctld_zones = self.get_ctld_zones(fob.name)
control_point = Fob(
str(fob.name), fob.position, self.theater, starts_blue=blue
str(fob.name),
fob.position,
self.theater,
starts_blue=blue,
ctld_zones=ctld_zones,
)
control_point.captured_invert = fob.late_activation
control_points[control_point.id] = control_point
@@ -460,3 +471,9 @@ class MizCampaignLoader:
self.add_preset_locations()
self.add_supply_routes()
self.add_shipping_lanes()
def get_ctld_zones(self, prefix: str) -> List[Tuple[Point, float]]:
zones = [t for t in self.mission.triggers.zones() if prefix + " CTLD" in t.name]
for z in zones:
self.mission.triggers.zones().remove(z)
return [(z.position, z.radius) for z in zones]

View File

@@ -4,12 +4,12 @@ import itertools
import random
from typing import Optional, TYPE_CHECKING
from game.ato.ai_flight_planner_db import aircraft_for_task, tasks_for_aircraft
from game.ato.flighttype import FlightType
from game.dcs.aircrafttype import AircraftType
from game.squadrons.operatingbases import OperatingBases
from game.squadrons.squadrondef import SquadronDef
from game.theater import ControlPoint
from game.ato.ai_flight_planner_db import aircraft_for_task, tasks_for_aircraft
if TYPE_CHECKING:
from game.factions.faction import Faction

View File

@@ -2,8 +2,8 @@ from dataclasses import field, dataclass
from enum import Enum, auto
from typing import Optional
from game.theater import MissionTarget
from game.ato.flighttype import FlightType
from game.theater import MissionTarget
class EscortType(Enum):

View File

@@ -6,9 +6,9 @@ from collections import defaultdict
from datetime import timedelta
from typing import Iterator, Dict, TYPE_CHECKING
from game.theater import MissionTarget
from game.ato.flighttype import FlightType
from game.ato.traveltime import TotEstimator
from game.theater import MissionTarget
if TYPE_CHECKING:
from game.coalition import Coalition

View File

@@ -5,6 +5,7 @@ import operator
from collections.abc import Iterable, Iterator
from typing import TYPE_CHECKING, TypeVar
from game.ato.closestairfields import ClosestAirfields, ObjectiveDistanceCache
from game.theater import (
Airfield,
ControlPoint,
@@ -20,7 +21,6 @@ from game.theater.theatergroundobject import (
IadsBuildingGroundObject,
)
from game.utils import meters, nautical_miles
from game.ato.closestairfields import ClosestAirfields, ObjectiveDistanceCache
if TYPE_CHECKING:
from game import Game
@@ -139,14 +139,6 @@ class ObjectiveFinder:
"""Iterates over all active front lines in the theater."""
yield from self.game.theater.conflicts()
def air_assault_targets(self) -> Iterator[ControlPoint]:
"""Iterates over all capturable controlpoints for all active front lines"""
if not self.game.settings.plugin_option("ctld"):
# Air Assault should only be tasked with CTLD enabled
return
for front_line in self.front_lines():
yield front_line.control_point_hostile_to(self.is_player)
def vulnerable_control_points(self) -> Iterator[ControlPoint]:
"""Iterates over friendly CPs that are vulnerable to enemy CPs.
@@ -178,6 +170,8 @@ class ObjectiveFinder:
return self._targets_by_range(airfields)
def convoys(self) -> Iterator[Convoy]:
if self.game.settings.perf_disable_convoys:
return
for front_line in self.front_lines():
yield from self.game.coalition_for(
self.is_player

View File

@@ -7,7 +7,6 @@ from game.commander.tasks.compound.destroyenemygroundunits import (
from game.commander.tasks.compound.reduceenemyfrontlinecapacity import (
ReduceEnemyFrontLineCapacity,
)
from game.commander.tasks.primitive.airassault import PlanAirAssault
from game.commander.tasks.primitive.breakthroughattack import BreakthroughAttack
from game.commander.theaterstate import TheaterState
from game.htn import CompoundTask, Method
@@ -19,7 +18,6 @@ class CaptureBase(CompoundTask[TheaterState]):
front_line: FrontLine
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
yield [PlanAirAssault(self.enemy_cp(state))]
yield [BreakthroughAttack(self.front_line, state.context.coalition.player)]
yield [DestroyEnemyGroundUnits(self.front_line)]
if self.worth_destroying_ammo_depots(state):

View File

@@ -4,8 +4,8 @@ from dataclasses import dataclass
from game.commander.tasks.compound.attackairinfrastructure import (
AttackAirInfrastructure,
)
from game.commander.tasks.compound.attackbuildings import AttackBuildings
from game.commander.tasks.compound.attackbattlepositions import AttackBattlePositions
from game.commander.tasks.compound.attackbuildings import AttackBuildings
from game.commander.tasks.compound.capturebases import CaptureBases
from game.commander.tasks.compound.defendbases import DefendBases
from game.commander.tasks.compound.degradeiads import DegradeIads

View File

@@ -5,6 +5,7 @@ import operator
from abc import abstractmethod
from dataclasses import dataclass, field
from enum import IntEnum, auto, unique
import random
from typing import Generic, Iterator, Optional, TYPE_CHECKING, TypeVar, Union
from game.ato.flighttype import FlightType
@@ -170,7 +171,24 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
if not ignore_iads:
for iads_threat in self.iter_iads_threats(state):
threatened = True
# Only consider blue faction flights threatened.
# Red might still plan missions into hostile territory,
# depending on the aggressiveness setting.
if (
state.context.coalition.player
or random.randint(1, 100)
> state.context.coalition.game.settings.opfor_autoplanner_aggressiveness
):
threatened = True
if iads_threat not in state.threatening_air_defenses:
state.threatening_air_defenses.append(iads_threat)
return not threatened
def get_flight_size(self) -> int:
settings = self.target.coalition.game.settings
weights = [
settings.fpa_2ship_weight,
settings.fpa_3ship_weight,
settings.fpa_4ship_weight,
]
return random.choices([2, 3, 4], weights, k=1)[0]

View File

@@ -2,10 +2,10 @@ from __future__ import annotations
from dataclasses import dataclass
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import MissionTarget
from game.ato.flighttype import FlightType
@dataclass

View File

@@ -1,35 +0,0 @@
from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import ControlPoint
from game.ato.flighttype import FlightType
@dataclass
class PlanAirAssault(PackagePlanningTask[ControlPoint]):
def preconditions_met(self, state: TheaterState) -> bool:
if self.target not in state.air_assault_targets:
return False
if self.capture_blocked(state):
# Do not task if there are enemy battle_positions blocking the capture
return False
if not self.target_area_preconditions_met(state):
# Do not task if air defense is present in the target area
return False
return super().preconditions_met(state)
def capture_blocked(self, state: TheaterState) -> bool:
battle_positions = state.enemy_battle_positions[self.target]
return len(battle_positions.blocking_capture) > 0
def apply_effects(self, state: TheaterState) -> None:
state.air_assault_targets.remove(self.target)
def propose_flights(self) -> None:
self.propose_flight(FlightType.AIR_ASSAULT, randint(2, 4))
# TODO Validate this.. / is Heli escort possible?
self.propose_flight(FlightType.TARCAP, 2)

View File

@@ -3,11 +3,11 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.missionproposals import EscortType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater.theatergroundobject import NavalGroundObject
from game.ato.flighttype import FlightType
@dataclass
@@ -23,5 +23,6 @@ class PlanAntiShip(PackagePlanningTask[NavalGroundObject]):
state.eliminate_ship(self.target)
def propose_flights(self) -> None:
self.propose_flight(FlightType.ANTISHIP, randint(2, 4))
size = self.get_flight_size()
self.propose_flight(FlightType.ANTISHIP, size)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)

View File

@@ -3,10 +3,10 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.transfers import CargoShip
from game.ato.flighttype import FlightType
@dataclass
@@ -22,5 +22,6 @@ class PlanAntiShipping(PackagePlanningTask[CargoShip]):
state.enemy_shipping.remove(self.target)
def propose_flights(self) -> None:
self.propose_flight(FlightType.ANTISHIP, randint(2, 4))
size = self.get_flight_size()
self.propose_flight(FlightType.ANTISHIP, size)
self.propose_common_escorts()

View File

@@ -2,10 +2,10 @@ from __future__ import annotations
from dataclasses import dataclass
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater.theatergroundobject import VehicleGroupGroundObject
from game.ato.flighttype import FlightType
@dataclass

View File

@@ -1,12 +1,13 @@
from __future__ import annotations
import random
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import ControlPoint
from game.ato.flighttype import FlightType
@dataclass
@@ -22,7 +23,8 @@ class PlanBarcap(PackagePlanningTask[ControlPoint]):
state.barcaps_needed[self.target] -= 1
def propose_flights(self) -> None:
self.propose_flight(FlightType.BARCAP, randint(2, 4))
size = self.get_flight_size()
self.propose_flight(FlightType.BARCAP, size)
@property
def purchase_multiplier(self) -> int:

View File

@@ -3,10 +3,10 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import FrontLine
from game.ato.flighttype import FlightType
@dataclass
@@ -30,5 +30,6 @@ class PlanCas(PackagePlanningTask[FrontLine]):
state.vulnerable_front_lines.remove(self.target)
def propose_flights(self) -> None:
self.propose_flight(FlightType.CAS, randint(2, 4))
size = self.get_flight_size()
self.propose_flight(FlightType.CAS, size)
self.propose_flight(FlightType.TARCAP, 2)

View File

@@ -2,11 +2,10 @@ from __future__ import annotations
from dataclasses import dataclass
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.data.doctrine import Doctrine
from game.transfers import Convoy
from game.ato.flighttype import FlightType
@dataclass

View File

@@ -2,11 +2,11 @@ from __future__ import annotations
from dataclasses import dataclass
from game.ato.flighttype import FlightType
from game.commander.missionproposals import EscortType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater.theatergroundobject import IadsGroundObject
from game.ato.flighttype import FlightType
@dataclass
@@ -42,6 +42,5 @@ class PlanDead(PackagePlanningTask[IadsGroundObject]):
# needs a decent refactor to the escort planning to do so.
if self.target.has_live_radar_sam:
self.propose_flight(FlightType.SEAD, 2)
else:
self.propose_flight(FlightType.SEAD_ESCORT, 2, EscortType.Sead)
self.propose_flight(FlightType.SEAD_ESCORT, 2, EscortType.Sead)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)

View File

@@ -3,10 +3,10 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import ControlPoint
from game.ato.flighttype import FlightType
@dataclass
@@ -24,7 +24,8 @@ class PlanOcaStrike(PackagePlanningTask[ControlPoint]):
state.oca_targets.remove(self.target)
def propose_flights(self) -> None:
self.propose_flight(FlightType.OCA_RUNWAY, randint(2, 4))
size = self.get_flight_size()
self.propose_flight(FlightType.OCA_RUNWAY, size)
if self.aircraft_cold_start:
self.propose_flight(FlightType.OCA_AIRCRAFT, 2)
self.propose_common_escorts()

View File

@@ -2,10 +2,10 @@ from __future__ import annotations
from dataclasses import dataclass
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import MissionTarget
from game.ato.flighttype import FlightType
@dataclass

View File

@@ -1,12 +1,11 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater.theatergroundobject import TheaterGroundObject
from game.ato.flighttype import FlightType
@dataclass

View File

@@ -45,7 +45,6 @@ class TheaterState(WorldState["TheaterState"]):
context: PersistentContext
barcaps_needed: dict[ControlPoint, int]
active_front_lines: list[FrontLine]
air_assault_targets: list[ControlPoint]
front_line_stances: dict[FrontLine, Optional[CombatStance]]
vulnerable_front_lines: list[FrontLine]
aewc_targets: list[MissionTarget]
@@ -110,7 +109,6 @@ class TheaterState(WorldState["TheaterState"]):
context=self.context,
barcaps_needed=dict(self.barcaps_needed),
active_front_lines=list(self.active_front_lines),
air_assault_targets=list(self.air_assault_targets),
front_line_stances=dict(self.front_line_stances),
vulnerable_front_lines=list(self.vulnerable_front_lines),
aewc_targets=list(self.aewc_targets),
@@ -161,7 +159,6 @@ class TheaterState(WorldState["TheaterState"]):
cp: barcap_rounds for cp in finder.vulnerable_control_points()
},
active_front_lines=list(finder.front_lines()),
air_assault_targets=list(finder.air_assault_targets()),
front_line_stances={f: None for f in finder.front_lines()},
vulnerable_front_lines=list(finder.front_lines()),
aewc_targets=[finder.farthest_friendly_control_point()],

View File

@@ -1,10 +1,12 @@
import inspect
import dcs
REQUIRED_BUILDINGS = [
"ammo",
"factory",
"fob",
"oil",
]
IADS_BUILDINGS = [
@@ -15,7 +17,6 @@ IADS_BUILDINGS = [
DEFAULT_AVAILABLE_BUILDINGS = [
"fuel",
"oil",
"ware",
"farp",
"derrick",

View File

@@ -2,7 +2,7 @@ from dataclasses import dataclass
from datetime import timedelta
from game.data.units import UnitClass
from game.utils import Distance, feet, nautical_miles
from game.utils import Distance, feet, nautical_miles, Speed, knots
@dataclass
@@ -77,6 +77,16 @@ class Doctrine:
ground_unit_procurement_ratios: GroundUnitProcurementRatios
rtb_speed: Speed
sead_escort_spacing: Distance
escort_spacing: Distance
sead_escort_engagement_range: Distance
escort_engagement_range: Distance
MODERN_DOCTRINE = Doctrine(
cap=True,
@@ -113,6 +123,11 @@ MODERN_DOCTRINE = Doctrine(
UnitClass.RECON: 1,
}
),
rtb_speed=knots(500),
sead_escort_spacing=feet(1000),
escort_spacing=feet(2000),
sead_escort_engagement_range=nautical_miles(40),
escort_engagement_range=nautical_miles(30),
)
COLDWAR_DOCTRINE = Doctrine(
@@ -150,6 +165,11 @@ COLDWAR_DOCTRINE = Doctrine(
UnitClass.RECON: 1,
}
),
rtb_speed=knots(450),
sead_escort_spacing=feet(500),
escort_spacing=feet(1000),
sead_escort_engagement_range=nautical_miles(25),
escort_engagement_range=nautical_miles(20),
)
WWII_DOCTRINE = Doctrine(
@@ -186,4 +206,9 @@ WWII_DOCTRINE = Doctrine(
UnitClass.RECON: 1,
}
),
rtb_speed=knots(300),
sead_escort_spacing=feet(100),
escort_spacing=feet(200),
sead_escort_engagement_range=nautical_miles(10),
escort_engagement_range=nautical_miles(5),
)

View File

@@ -19,6 +19,18 @@ PydcsWeapon = Any
PydcsWeaponAssignment = tuple[int, PydcsWeapon]
def weapons_migrator(name: str) -> str:
migration_map = {
"AGM-88C HARM - High Speed Anti-Radiation Missile": "AGM-88C HARM",
"Kh-25MPU (Updated AS-12 Kegler) - 320kg, ARM, IN & Pas Rdr": "Kh-25MPU",
"Kh-31P (AS-17 Krypton) - 600kg, ARM, IN & Pas Rdr": "Kh-31P",
"Kh-58U (AS-11 Kilter) - 640kg, ARM, IN & Pas Rdr": "Kh-58U",
}
while name in migration_map:
name = migration_map[name]
return name
@dataclass(frozen=True)
class Weapon:
"""Wrapper for DCS weapons."""
@@ -139,7 +151,7 @@ class WeaponGroup:
def __setstate__(self, state: dict[str, Any]) -> None:
# Update any existing models with new data on load.
updated = WeaponGroup.named(state["name"])
updated = WeaponGroup.named(weapons_migrator(state["name"]))
state.update(updated.__dict__)
self.__dict__.update(state)
@@ -161,16 +173,21 @@ class WeaponGroup:
@classmethod
def _each_weapon_group(cls) -> Iterator[WeaponGroup]:
names = []
links = []
for group_file_path in Path("resources/weapons").glob("**/*.yaml"):
with group_file_path.open(encoding="utf8") as group_file:
data = yaml.safe_load(group_file)
name = data["name"]
names.append(name)
try:
weapon_type = WeaponType(data["type"])
except KeyError:
weapon_type = WeaponType.UNKNOWN
year = data.get("year")
fallback_name = data.get("fallback")
if fallback_name:
links.append((name, fallback_name))
group = WeaponGroup(name, weapon_type, year, fallback_name)
for clsid in data["clsids"]:
weapon = Weapon(clsid, group)
@@ -178,6 +195,17 @@ class WeaponGroup:
group.weapons.append(weapon)
yield group
for name, fb in links:
if fb not in names:
sn = "Unknown"
for n in names:
if fb in n:
sn = n
break
logging.error(
f"Weapon '{name}' has invalid fallback '{fb}': suggested = {sn}"
)
@classmethod
def register_clean_pylon(cls) -> None:
group = WeaponGroup(

View File

@@ -348,9 +348,11 @@ class AircraftType(UnitType[Type[FlyingType]]):
@classmethod
def _each_variant_of(cls, aircraft: Type[FlyingType]) -> Iterator[AircraftType]:
data_path = Path("resources/units/aircraft") / f"{aircraft.id}.yaml"
# Replace slashes with underscores because slashes are not allowed in filenames
aircraft_id = aircraft.id.replace("/", "_")
data_path = Path("resources/units/aircraft") / f"{aircraft_id}.yaml"
if not data_path.exists():
logging.warning(f"No data for {aircraft.id}; it will not be available")
logging.warning(f"No data for {aircraft_id}; it will not be available")
return
with data_path.open(encoding="utf-8") as data_file:
@@ -371,7 +373,7 @@ class AircraftType(UnitType[Type[FlyingType]]):
nautical_miles(50) if aircraft.helicopter else nautical_miles(150)
)
logging.warning(
f"{aircraft.id} does not specify a max_range. Defaulting to "
f"{aircraft_id} does not specify a max_range. Defaulting to "
f"{mission_range.nautical_miles}NM"
)

View File

@@ -3,9 +3,9 @@ from typing import Optional, Type
from dcs.helicopters import helicopter_map
from dcs.planes import plane_map
from dcs.ships import ship_map
from dcs.statics import fortification_map, groundobject_map, warehouse_map, cargo_map
from dcs.unittype import UnitType
from dcs.vehicles import vehicle_map
from dcs.statics import fortification_map, groundobject_map, warehouse_map, cargo_map
def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:

View File

@@ -338,9 +338,7 @@ class Debriefing:
seen = set()
captures = []
for capture in reversed(self.state_data.base_capture_events):
# The ID string in the JSON file will be an airport ID for airport captures
# but will be a UUID for all other types, since DCS doesn't know the UUIDs
# for the captured FOBs.
# The ID string in the JSON file will be the UUID generated from liberation
cp_id, new_owner_id_str, _name = capture.split("||")
# Only the most recent capture event matters.
@@ -349,13 +347,8 @@ class Debriefing:
seen.add(cp_id)
try:
control_point = self.game.theater.find_control_point_by_airport_id(
int(cp_id)
)
except ValueError:
# The CP ID could not be converted to an int, so it's a UUID.
control_point = self.game.theater.find_control_point_by_id(UUID(cp_id))
except KeyError:
except (KeyError, ValueError):
# Captured base is not a part of the campaign. This happens when neutral
# bases are near the conflict. Nothing to do.
continue

View File

@@ -11,6 +11,7 @@ from dcs.countries import country_dict
from dcs.unittype import ShipType, StaticType
from dcs.unittype import UnitType as DcsUnitType
from game.armedforces.forcegroup import ForceGroup
from game.data.building_data import (
WW2_ALLIES_BUILDINGS,
DEFAULT_AVAILABLE_BUILDINGS,
@@ -25,13 +26,13 @@ from game.data.doctrine import (
COLDWAR_DOCTRINE,
WWII_DOCTRINE,
)
from game.data.units import UnitClass
from game.data.groups import GroupRole
from game.data.units import UnitClass
from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.dcs.shipunittype import ShipUnitType
from game.armedforces.forcegroup import ForceGroup
from game.dcs.unittype import UnitType
from pydcs_extensions.f16i_idf.f16i_idf import inject_F16I
if TYPE_CHECKING:
from game.theater.start_generator import ModSettings
@@ -59,7 +60,7 @@ class Faction:
description: str = field(default="")
# Available aircraft
aircrafts: List[AircraftType] = field(default_factory=list)
aircraft: List[AircraftType] = field(default_factory=list)
# Available awacs aircraft
awacs: List[AircraftType] = field(default_factory=list)
@@ -115,6 +116,9 @@ class Faction:
# List of default livery overrides
liveries_overrides: Dict[AircraftType, List[str]] = field(default_factory=dict)
# List of default livery overrides for ground vehicles
liveries_overrides_ground_forces: Dict[str, List[str]] = field(default_factory=dict)
#: Set to True if the faction should force the "Unrestricted satnav" option
#: for the mission. This option enables GPS for capable aircraft regardless
#: of the time period or operator. For example, the CJTF "countries" don't
@@ -124,6 +128,10 @@ class Faction:
#: both will use it.
unrestricted_satnav: bool = False
# Store mod settings so mod properties can be injected again on game load,
# in case mods like CJS F/A-18E/F/G or IDF F-16I are selected by the player
mod_settings: Optional[ModSettings] = field(default=None)
def has_access_to_dcs_type(self, unit_type: Type[DcsUnitType]) -> bool:
# Vehicle and Ship Units
if any(unit_type == u.dcs_unit_type for u in self.accessible_units):
@@ -170,6 +178,10 @@ class Faction:
)
return sorted(air_defenses)
@cached_property
def aircrafts(self) -> list[UnitType[Any]]:
return list(self.aircraft + self.awacs + self.tankers)
@classmethod
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
faction = Faction(locales=json.get("locales"))
@@ -198,14 +210,10 @@ class Faction:
faction.authors = json.get("authors", "")
faction.description = json.get("description", "")
faction.aircrafts = [AircraftType.named(n) for n in json.get("aircrafts", [])]
faction.aircraft = [AircraftType.named(n) for n in json.get("aircrafts", [])]
faction.awacs = [AircraftType.named(n) for n in json.get("awacs", [])]
faction.tankers = [AircraftType.named(n) for n in json.get("tankers", [])]
faction.aircrafts = list(
set(faction.aircrafts + faction.awacs + faction.tankers)
)
faction.frontline_units = [
GroundUnitType.named(n) for n in json.get("frontline_units", [])
]
@@ -279,6 +287,16 @@ class Faction:
aircraft = AircraftType.named(name)
faction.liveries_overrides[aircraft] = [s.lower() for s in livery]
# Load liveries override for ground forces
faction.liveries_overrides_ground_forces = {}
liveries_overrides_ground_forces = json.get(
"liveries_overrides_ground_forces", {}
)
for vehicle_type, livery in liveries_overrides_ground_forces.items():
faction.liveries_overrides_ground_forces[vehicle_type] = [
s.lower() for s in livery
]
faction.unrestricted_satnav = json.get("unrestricted_satnav", False)
return faction
@@ -294,7 +312,21 @@ class Faction:
if unit.unit_class is unit_class:
yield unit
def apply_mod_settings(self, mod_settings: ModSettings) -> None:
def apply_mod_settings(self, mod_settings: Optional[ModSettings] = None) -> None:
if mod_settings is None:
if self.mod_settings is None:
# No mod settings were provided and none were saved for this faction
# so stop here
return
elif self.mod_settings is not None:
# Saved mod settings were found for this faction,
# so load them for use
mod_settings = self.mod_settings
else:
# Update the mod settings of this faction
# so the settings can be applied again on load, if needed
self.mod_settings = mod_settings
# aircraft
if not mod_settings.a4_skyhawk:
self.remove_aircraft("A-4E-C")
@@ -303,16 +335,40 @@ class Faction:
if not mod_settings.uh_60l:
self.remove_aircraft("UH-60L")
self.remove_aircraft("KC130J")
if not mod_settings.f4bc_phantom:
self.remove_aircraft("VSN_F4B")
self.remove_aircraft("VSN_F4C")
if not mod_settings.f15d_baz:
self.remove_aircraft("F-15D")
if not mod_settings.f_16_idf:
self.remove_aircraft("F-16I")
self.remove_aircraft("F_16D_52")
self.remove_aircraft("F_16D_50")
self.remove_aircraft("F_16D_50_NS")
self.remove_aircraft("F_16D_52_NS")
self.remove_aircraft("F-16D_Barak_30")
self.remove_aircraft("F-16D_Barak_40")
else:
inject_F16I()
if not mod_settings.f22_raptor:
self.remove_aircraft("F-22A")
if not mod_settings.f84g_thunderjet:
self.remove_aircraft("VSN_F84G")
if not mod_settings.f100_supersabre:
self.remove_aircraft("VSN_F100")
if not mod_settings.f104_starfighter:
self.remove_aircraft("VSN_F104C")
self.remove_aircraft("VSN_F104G")
self.remove_aircraft("VSN_F104S")
self.remove_aircraft("VSN_F104S_AG")
if not mod_settings.f105_thunderchief:
self.remove_aircraft("VSN_F105D")
self.remove_aircraft("VSN_F105G")
if not mod_settings.a6a_intruder:
self.remove_aircraft("VSN_A6A")
if not mod_settings.jas39_gripen:
self.remove_aircraft("JAS39Gripen")
self.remove_aircraft("JAS39Gripen_BVR")
self.remove_aircraft("JAS39Gripen_AG")
if not mod_settings.su30_flanker_h:
self.remove_aircraft("Su-30MKA")
@@ -321,6 +377,8 @@ class Faction:
self.remove_aircraft("Su-30SM")
if not mod_settings.su57_felon:
self.remove_aircraft("Su-57")
if not mod_settings.ov10a_bronco:
self.remove_aircraft("Bronco-OV-10A")
# frenchpack
if not mod_settings.frenchpack:
self.remove_vehicle("AMX10RCR")
@@ -367,19 +425,67 @@ class Faction:
self.remove_preset("SA-20B/S-300PMU-2")
self.remove_preset("SA-23/S-300VM")
self.remove_preset("SA-17")
self.remove_preset("KS-19")
self.remove_preset("KS-19_HDS")
self.remove_preset("HQ-2")
self.remove_preset("SA-2/S-75 V-759/5V23")
self.remove_preset("SA-3/S-125 V-601P/5V27")
self.remove_vehicle("SAM SA-14 Strela-3 manpad")
self.remove_vehicle("SAM SA-24 Igla-S manpad")
self.remove_vehicle("Polyana-D4M1 C2 node")
if not mod_settings.fa_18efg:
self.remove_aircraft("FA-18E")
self.remove_aircraft("FA-18F")
self.remove_aircraft("EA-18G")
# swedish military assets pack
if not mod_settings.swedishmilitaryassetspack:
self.remove_vehicle("BV410_RBS70")
self.remove_vehicle("BV410_RBS90")
self.remove_vehicle("LvS_103_Lavett103_Rb103A")
self.remove_vehicle("LvS_103_Lavett103_Rb103B")
self.remove_vehicle("LvS_103_Lavett103_HX_Rb103A")
self.remove_vehicle("LvS_103_Lavett103_HX_Rb103B")
self.remove_vehicle("LvS_103_StriE103")
self.remove_vehicle("LvS_103_PM103")
self.remove_vehicle("LvS_103_PM103_HX")
self.remove_vehicle("LvS_103_Elverk103")
self.remove_vehicle("LvKv9040")
self.remove_vehicle("RBS_70")
self.remove_vehicle("RBS_90")
self.remove_vehicle("RBS_98")
self.remove_vehicle("UndE23")
self.remove_vehicle("BV410")
self.remove_vehicle("CV9040")
self.remove_vehicle("Strv103")
self.remove_vehicle("Strv121")
self.remove_vehicle("Strv122")
self.remove_vehicle("Strv2000")
self.remove_vehicle("Volvo740")
self.remove_vehicle("RBS_15KA")
self.remove_vehicle("AG_90")
self.remove_vehicle("SwedishinfantryAK4")
self.remove_vehicle("SwedishinfantryAK5")
self.remove_vehicle("SwedishinfantryAK5GT")
self.remove_vehicle("SwedishinfantryKSP90")
self.remove_vehicle("SwedishinfantryKSP58")
self.remove_vehicle("SwedishinfantryPskott86")
self.remove_vehicle("RBS_57")
self.remove_vehicle("RBS_58")
self.remove_vehicle("Artillerisystem08")
self.remove_vehicle("Grkpbv90")
self.remove_ship("HSwMS_Visby")
self.remove_ship("Strb90")
self.remove_aircraft("HKP15B")
def remove_aircraft(self, name: str) -> None:
for i in self.aircrafts:
if i.dcs_unit_type.id == name:
self.aircrafts.remove(i)
def remove_aircraft_by_name(self, name: str) -> None:
for i in self.aircrafts:
if i.name == name:
self.aircrafts.remove(i)
def remove_preset(self, name: str) -> None:
for pg in self.preset_groups:
if pg.name == name:
@@ -390,6 +496,11 @@ class Faction:
if i.dcs_unit_type.id == name:
self.frontline_units.remove(i)
def remove_ship(self, name: str) -> None:
for i in self.naval_units:
if i.dcs_unit_type.id == name:
self.naval_units.remove(i)
def load_ship(name: str) -> Optional[Type[ShipType]]:
if (ship := getattr(dcs.ships, name, None)) is not None:

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
import json
import logging
from pathlib import Path
@@ -30,7 +31,7 @@ class FactionLoader:
@classmethod
def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]:
user_faction_path = Path(persistency.base_path()) / "Liberation/Factions"
user_faction_path = Path(persistency.base_path()) / "Retribution/Factions"
files = cls.find_faction_files_in(
FACTION_DIRECTORY
) + cls.find_faction_files_in(user_faction_path)

View File

@@ -21,7 +21,8 @@ class RefuelZoneGeometry:
def find_best_refuel_point(self) -> Point:
# Do simple at first.
# TODO: Consider threats.
# TODO: Avoid threatened areas, minimize backtracking.
# https://github.com/dcs-liberation/dcs_liberation/issues/2542
distance = 0.75 * self.package_home.distance_to_point(self.join)
heading = self.package_home.heading_between_point(self.join)
return self.package_home.point_from_heading(heading, distance)

View File

@@ -232,6 +232,10 @@ class Game:
LuaPluginManager.load_settings(self.settings)
ObjectiveDistanceCache.set_theater(self.theater)
self.compute_unculled_zones(GameUpdateEvents())
# Apply mod settings again so mod properties get injected again,
# in case mods like CJS F/A-18E/F/G or IDF F-16I are selected by the player
self.blue.faction.apply_mod_settings()
self.red.faction.apply_mod_settings()
if not game_still_initializing:
# We don't need to push events that happen during load. The UI will fully
# reset when we're done.
@@ -341,7 +345,6 @@ class Game:
with logged_duration("Turn initialization"):
self.initialize_turn(events)
events.begin_new_turn()
EventStream.put_nowait(events)
# Autosave progress
@@ -437,6 +440,8 @@ class Game:
with logged_duration("Computing culling positions"):
self.compute_unculled_zones(events)
events.begin_new_turn()
def message(self, title: str, text: str = "") -> None:
self.informations.append(Information(title, text, turn=self.turn))
@@ -523,12 +528,20 @@ class Game:
packages = itertools.chain(self.blue.ato.packages, self.red.ato.packages)
for package in packages:
if package.primary_task is FlightType.BARCAP:
if package.primary_task in [
FlightType.BARCAP,
FlightType.TRANSPORT,
FlightType.AEWC,
FlightType.REFUELING,
]:
# BARCAPs will be planned at most locations on smaller theaters,
# rendering culling fairly useless. BARCAP packages don't really
# need the ground detail since they're defensive. SAMs nearby
# are only interesting if there are enemies in the area, and if
# there are they won't be culled because of the enemy's mission.
# Don't create culling exclusion zones around FlightType.TRANSPORT,
# FlightType.AEWC & FlightType.REFUELING mission targets.
continue
zones.append(package.target.position)

View File

@@ -89,7 +89,6 @@ class GroundPlanner:
self.reserve: List[CombatGroup] = []
def plan_groundwar(self) -> None:
ground_unit_limit = self.cp.frontline_unit_count_limit
remaining_available_frontline_units = ground_unit_limit
@@ -139,7 +138,6 @@ class GroundPlanner:
remaining_available_frontline_units -= available
while available > 0:
if role == CombatGroupRole.SHORAD:
count = 1
else:

View File

@@ -1,7 +1,5 @@
from __future__ import annotations
from collections import defaultdict
import logging
import random
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Iterator, Type, Optional
@@ -28,7 +26,6 @@ from game.theater.theatergroundobject import (
IadsGroundObject,
)
from game.theater.theatergroup import TheaterUnit
from game.utils import Heading
if TYPE_CHECKING:
from game.factions.faction import Faction
@@ -140,11 +137,14 @@ class TgoLayoutUnitGroup:
@property
def group_size(self) -> int:
"""The amount of units to be generated. If unit_count is defined in the layout this will be randomized accordingly. Otherwise this will be the maximum size."""
"""
The amount of units to be generated. If unit_count is defined in the layout this will be
randomized accordingly. Otherwise this will be the maximum size.
"""
if self.unit_count:
if len(self.unit_count) == 1:
return self.unit_count[0]
return random.choice(range(min(self.unit_count), max(self.unit_count)))
return random.choice(range(min(self.unit_count), max(self.unit_count) + 1))
return self.max_size
@property
@@ -241,7 +241,6 @@ class AntiAirLayout(TgoLayout):
location: PresetLocation,
control_point: ControlPoint,
) -> IadsGroundObject:
if GroupTask.EARLY_WARNING_RADAR in self.tasks:
return EwrGroundObject(name, location, control_point)
elif any(tasking in self.tasks for tasking in GroupRole.AIR_DEFENSE.tasks):

View File

@@ -1,9 +1,9 @@
from __future__ import annotations
from collections import defaultdict
import itertools
import logging
import pickle
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import Iterator
@@ -31,7 +31,7 @@ from game.profiling import logged_duration
from game.version import VERSION
LAYOUT_DIR = "resources/layouts/"
LAYOUT_DUMP = "Liberation/layouts.p"
LAYOUT_DUMP = "Retribution/layouts.p"
LAYOUT_TYPES = {
GroupRole.AIR_DEFENSE: AntiAirLayout,
@@ -132,7 +132,6 @@ class LayoutLoader:
temp_mis.country(country.name).ship_group,
temp_mis.country(country.name).static_group,
):
try:
g_id, u_id, group_name, group_mapping = mapping.group_for_name(
dcs_group.name

View File

@@ -1,6 +1,6 @@
from __future__ import annotations
from collections import defaultdict
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Any, Optional, Type

View File

@@ -19,4 +19,4 @@ def init_logging(version: str) -> None:
with log_config.open() as log_file:
logging.config.dictConfig(yaml.safe_load(log_file))
logging.info(f"DCS Liberation {version}")
logging.info(f"DCS Retribution {version}")

78
game/migrator.py Normal file
View File

@@ -0,0 +1,78 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from game.ato.packagewaypoints import PackageWaypoints
from game.data.doctrine import MODERN_DOCTRINE, COLDWAR_DOCTRINE, WWII_DOCTRINE
if TYPE_CHECKING:
from game import Game
def try_set_attr(obj: Any, attr_name: str, val: Any = None) -> None:
if not hasattr(obj, attr_name):
setattr(obj, attr_name, val)
class Migrator:
def __init__(self, game: Game):
self.game = game
self._migrate_game()
def _migrate_game(self) -> None:
self._update_doctrine()
self._update_packagewaypoints()
self._update_package_attributes()
self._update_control_points()
self._update_flights()
def _update_doctrine(self) -> None:
doctrines = [
MODERN_DOCTRINE,
COLDWAR_DOCTRINE,
WWII_DOCTRINE,
]
for c in self.game.coalitions:
if c.faction.doctrine.__dict__ in [d.__dict__ for d in doctrines]:
continue
found = False
for d in doctrines:
if c.faction.doctrine.rendezvous_altitude == d.rendezvous_altitude:
c.faction.doctrine = d
found = True
break
if not found:
c.faction.doctrine = MODERN_DOCTRINE
def _update_packagewaypoints(self) -> None:
for c in self.game.coalitions:
for p in c.ato.packages:
if p.flights:
try_set_attr(p.waypoints, "initial", PackageWaypoints.create(p, c))
def _update_package_attributes(self) -> None:
for c in self.game.coalitions:
for p in c.ato.packages:
try_set_attr(p, "custom_name")
try_set_attr(p, "frequency")
def _update_control_points(self) -> None:
for cp in self.game.theater.controlpoints:
is_carrier = cp.is_carrier
is_lha = cp.is_lha
is_fob = cp.category == "fob"
radio_configurable = is_carrier or is_lha or is_fob
if radio_configurable:
try_set_attr(cp, "frequency")
if is_carrier or is_lha:
try_set_attr(cp, "tacan")
try_set_attr(cp, "tcn_name")
try_set_attr(cp, "icls_channel")
try_set_attr(cp, "icls_name")
try_set_attr(cp, "link4")
def _update_flights(self) -> None:
for f in self.game.db.flights.objects.values():
try_set_attr(f, "frequency")
try_set_attr(f, "tacan")
try_set_attr(f, "tcn_name")

View File

@@ -8,6 +8,7 @@ from dcs.task import (
CAP,
CAS,
EPLRS,
Escort,
FighterSweep,
GroundAttack,
Nothing,
@@ -19,12 +20,16 @@ from dcs.task import (
Refueling,
RunwayAttack,
Transport,
SEAD,
SwitchWaypoint,
OptJettisonEmptyTanks,
)
from dcs.unitgroup import FlyingGroup
from game.ato import Flight, FlightType
from game.ato.flightplans.aewc import AewcFlightPlan
from game.ato.flightplans.theaterrefueling import TheaterRefuelingFlightPlan
from game.ato.flightwaypointtype import FlightWaypointType
class AircraftBehavior:
@@ -83,6 +88,7 @@ class AircraftBehavior:
rtb_winchester: Optional[OptRTBOnOutOfAmmo.Values] = None,
restrict_jettison: Optional[bool] = None,
mission_uses_gun: bool = True,
rtb_on_bingo: bool = True,
) -> None:
group.points[0].tasks.clear()
group.points[0].tasks.append(OptReactOnThreat(react_on_threat))
@@ -104,7 +110,8 @@ class AircraftBehavior:
for unit in group.units:
unit.gun = 0
group.points[0].tasks.append(OptRTBOnBingoFuel(True))
group.points[0].tasks.append(OptRTBOnBingoFuel(rtb_on_bingo))
group.points[0].tasks.append(OptJettisonEmptyTanks())
# Do not restrict afterburner.
# https://forums.eagle.ru/forum/english/digital-combat-simulator/dcs-world-2-5/bugs-and-problems-ai/ai-ad/7121294-ai-stuck-at-high-aoa-after-making-sharp-turn-if-afterburner-is-restricted
@@ -172,9 +179,9 @@ class AircraftBehavior:
group,
react_on_threat=OptReactOnThreat.Values.EvadeFire,
roe=OptROE.Values.OpenFire,
# ASM includes ARMs and TALDs (among other things, but those are the useful
# Guided includes ARMs and TALDs (among other things, but those are the useful
# weapons for SEAD).
rtb_winchester=OptRTBOnOutOfAmmo.Values.ASM,
rtb_winchester=OptRTBOnOutOfAmmo.Values.Guided,
restrict_jettison=True,
mission_uses_gun=False,
)
@@ -265,7 +272,15 @@ class AircraftBehavior:
# Escort groups are actually given the CAP task so they can perform the
# Search Then Engage task, which we have to use instead of the Escort
# task for the reasons explained in JoinPointBuilder.
group.task = CAP.name
group.task = Escort.name
if flight.flight_plan.is_formation(flight.flight_plan):
index = flight.flight_plan.get_index_of_wpt_by_type(
FlightWaypointType.SPLIT
)
if index > 0:
group.add_trigger_action(SwitchWaypoint(None, index))
else:
logging.warning(f"Couldn't determine SPLIT for {group.name}")
self.configure_behavior(
flight, group, roe=OptROE.Values.OpenFire, restrict_jettison=True
)
@@ -274,25 +289,34 @@ class AircraftBehavior:
# CAS is able to perform all the same tasks as SEAD using a superset of the
# available aircraft, and F-14s are not able to be SEAD despite having TALDs.
# https://forums.eagle.ru/topic/272112-cannot-assign-f-14-to-sead/
group.task = CAS.name
group.task = SEAD.name
index = flight.flight_plan.get_index_of_wpt_by_type(FlightWaypointType.SPLIT)
if index > 0 and flight.flight_plan.is_formation(flight.flight_plan):
group.add_trigger_action(SwitchWaypoint(None, index))
if index < 1:
logging.warning(f"Couldn't determine SPLIT for {group.name}")
self.configure_behavior(
flight,
group,
roe=OptROE.Values.OpenFire,
# ASM includes ARMs and TALDs (among other things, but those are the useful
# Guided includes ARMs and TALDs (among other things, but those are the useful
# weapons for SEAD).
rtb_winchester=OptRTBOnOutOfAmmo.Values.ASM,
rtb_winchester=OptRTBOnOutOfAmmo.Values.Guided,
restrict_jettison=True,
mission_uses_gun=False,
)
def configure_transport(self, group: FlyingGroup[Any], flight: Flight) -> None:
group.task = Transport.name
roe = OptROE.Values.WeaponHold
if flight.is_hercules:
group.task = GroundAttack.name
roe = OptROE.Values.OpenFire
self.configure_behavior(
flight,
group,
react_on_threat=OptReactOnThreat.Values.EvadeFire,
roe=OptROE.Values.WeaponHold,
roe=roe,
restrict_jettison=True,
)
@@ -304,6 +328,7 @@ class AircraftBehavior:
react_on_threat=OptReactOnThreat.Values.EvadeFire,
roe=OptROE.Values.WeaponHold,
restrict_jettison=True,
rtb_on_bingo=False,
)
def configure_unknown_task(self, group: FlyingGroup[Any], flight: Flight) -> None:

View File

@@ -5,9 +5,12 @@ from datetime import datetime
from functools import cached_property
from typing import Any, Dict, List, TYPE_CHECKING
from dcs.action import AITaskPush
from dcs.condition import FlagIsTrue, GroupDead, Or, FlagIsFalse
from dcs.country import Country
from dcs.mission import Mission
from dcs.terrain.terrain import NoParkingSlotError
from dcs.triggers import TriggerOnce, Event
from dcs.unitgroup import FlyingGroup, StaticGroup
from game.ato.airtaaskingorder import AirTaskingOrder
@@ -17,8 +20,8 @@ from game.ato.flighttype import FlightType
from game.ato.package import Package
from game.ato.starttype import StartType
from game.factions.faction import Faction
from game.missiongenerator.missiondata import MissionData
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
from game.missiongenerator.missiondata import MissionData
from game.radio.radios import RadioRegistry
from game.radio.tacan import TacanRegistry
from game.runways import RunwayData
@@ -26,6 +29,7 @@ from game.settings import Settings
from game.theater.controlpoint import (
Airfield,
ControlPoint,
Fob,
)
from game.unitmap import UnitMap
from .aircraftpainter import AircraftPainter
@@ -101,7 +105,9 @@ class AircraftGenerator:
ato: The ATO to spawn aircraft for.
dynamic_runways: Runway data for carriers and FARPs.
"""
for package in ato.packages:
self._reserve_frequencies_and_tacan(ato)
for package in reversed(sorted(ato.packages, key=lambda x: x.time_over_target)):
if not package.flights:
continue
for flight in package.flights:
@@ -111,6 +117,25 @@ class AircraftGenerator:
flight, country, dynamic_runways
)
self.unit_map.add_aircraft(group, flight)
if (
package.primary_flight is not None
and package.primary_flight.flight_plan.is_formation(
package.primary_flight.flight_plan
)
):
splittrigger = TriggerOnce(Event.NoEvent, f"Split-{id(package)}")
splittrigger.add_condition(FlagIsTrue(flag=f"split-{id(package)}"))
splittrigger.add_condition(Or())
splittrigger.add_condition(FlagIsFalse(flag=f"split-{id(package)}"))
splittrigger.add_condition(GroupDead(package.primary_flight.group_id))
for flight in package.flights:
if flight.flight_type in [
FlightType.ESCORT,
FlightType.SEAD_ESCORT,
]:
splittrigger.add_action(AITaskPush(flight.group_id, 1))
if len(splittrigger.actions) > 0:
self.mission.triggerrules.triggers.append(splittrigger)
def spawn_unused_aircraft(
self, player_country: Country, enemy_country: Country
@@ -152,7 +177,7 @@ class AircraftGenerator:
flight.state = Completed(flight, self.game.settings)
group = FlightGroupSpawner(
flight, country, self.mission, self.helipads
flight, country, self.mission, self.helipads, self.mission_data
).create_idle_aircraft()
AircraftPainter(flight, group).apply_livery()
self.unit_map.add_aircraft(group, flight)
@@ -162,7 +187,7 @@ class AircraftGenerator:
) -> FlyingGroup[Any]:
"""Creates and configures the flight group in the mission."""
group = FlightGroupSpawner(
flight, country, self.mission, self.helipads
flight, country, self.mission, self.helipads, self.mission_data
).create_flight_group()
self.flights.append(
FlightGroupConfigurator(
@@ -179,4 +204,27 @@ class AircraftGenerator:
self.use_client,
).configure()
)
wpt = group.waypoint("LANDING")
if flight.is_helo and isinstance(flight.arrival, Fob) and wpt:
hpad = self.helipads[flight.arrival].units.pop(0)
wpt.helipad_id = hpad.id
wpt.link_unit = hpad.id
self.helipads[flight.arrival].units.append(hpad)
return group
def _reserve_frequencies_and_tacan(self, ato: AirTaskingOrder) -> None:
for package in ato.packages:
if package.frequency is None:
continue
if package.frequency not in self.radio_registry.allocated_channels:
self.radio_registry.reserve(package.frequency)
for f in package.flights:
if (
f.frequency
and f.frequency not in self.radio_registry.allocated_channels
):
self.radio_registry.reserve(f.frequency)
if f.tacan and f.tacan not in self.tacan_registy.allocated_channels:
self.tacan_registy.mark_unavailable(f.tacan)

View File

@@ -12,9 +12,9 @@ from dcs.unitgroup import FlyingGroup
from game.ato import Flight, FlightType
from game.callsigns import callsign_for_support_unit
from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
from game.missiongenerator.logisticsgenerator import LogisticsGenerator
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
from game.radio.radios import RadioFrequency, RadioRegistry
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
from game.runways import RunwayData
@@ -133,14 +133,20 @@ class FlightGroupConfigurator:
laser_codes.append(None)
def setup_radios(self) -> RadioFrequency:
if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}:
channel = self.radio_registry.alloc_uhf()
self.register_air_support(channel)
else:
channel = self.flight.unit_type.alloc_flight_radio(self.radio_registry)
freq = self.flight.frequency
if freq is None and (freq := self.flight.package.frequency) is None:
freq = self.radio_registry.alloc_uhf()
self.flight.package.frequency = freq
if freq not in self.radio_registry.allocated_channels:
self.radio_registry.reserve(freq)
self.group.set_frequency(channel.mhz)
return channel
if self.flight.flight_type in {FlightType.AEWC, FlightType.REFUELING}:
self.register_air_support(freq)
elif self.flight.frequency is None and self.flight.client_count:
freq = self.flight.unit_type.alloc_flight_radio(self.radio_registry)
self.group.set_frequency(freq.mhz)
return freq
def register_air_support(self, channel: RadioFrequency) -> None:
callsign = callsign_for_support_unit(self.group)
@@ -151,13 +157,18 @@ class FlightGroupConfigurator:
callsign=callsign,
freq=channel,
depature_location=self.flight.departure.name,
end_time=self.flight.flight_plan.mission_departure_time,
start_time=self.flight.flight_plan.takeoff_time(),
start_time=self.flight.flight_plan.patrol_start_time,
end_time=self.flight.flight_plan.patrol_end_time,
blue=self.flight.departure.captured,
)
)
elif isinstance(self.flight.flight_plan, TheaterRefuelingFlightPlan):
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y, TacanUsage.AirToAir)
if self.flight.tacan is None:
tacan = self.tacan_registry.alloc_for_band(
TacanBand.Y, TacanUsage.AirToAir
)
else:
tacan = self.flight.tacan
self.mission_data.tankers.append(
TankerInfo(
group_name=str(self.group.name),

View File

@@ -6,7 +6,7 @@ from dcs import Mission
from dcs.country import Country
from dcs.mapping import Vector2
from dcs.mission import StartType as DcsStartType
from dcs.planes import Su_33
from dcs.planes import F_14A, Su_33
from dcs.ships import KUZNECOW
from dcs.terrain import Airport, NoParkingSlotError
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
@@ -15,6 +15,7 @@ from game.ato import Flight
from game.ato.flightstate import InFlight
from game.ato.starttype import StartType
from game.ato.traveltime import GroundSpeed
from game.missiongenerator.missiondata import MissionData
from game.naming import namegen
from game.theater import Airfield, ControlPoint, Fob, NavalControlPoint, OffMapSpawn
from game.utils import feet, meters
@@ -32,6 +33,8 @@ WARM_START_ALTITUDE = meters(3000)
MINIMUM_MID_MISSION_SPAWN_ALTITUDE_MSL = feet(6000)
MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL = feet(500)
STACK_SEPARATION = feet(200)
RTB_ALTITUDE = meters(800)
RTB_DISTANCE = 5000
HELI_ALT = 500
@@ -44,11 +47,13 @@ class FlightGroupSpawner:
country: Country,
mission: Mission,
helipads: dict[ControlPoint, StaticGroup],
mission_data: MissionData,
) -> None:
self.flight = flight
self.country = country
self.mission = mission
self.helipads = helipads
self.mission_data = mission_data
def create_flight_group(self) -> FlyingGroup[Any]:
"""Creates the group for the flight and adds it to the mission.
@@ -74,8 +79,12 @@ class FlightGroupSpawner:
self.flight.state.is_waiting_for_start
or self.flight.state.spawn_type is not StartType.IN_FLIGHT
):
return self.generate_flight_at_departure()
return self.generate_mid_mission()
grp = self.generate_flight_at_departure()
self.flight.group_id = grp.id
return grp
grp = self.generate_mid_mission()
self.flight.group_id = grp.id
return grp
def create_idle_aircraft(self) -> FlyingGroup[Any]:
airport = self.flight.squadron.location.dcs_airport
@@ -126,7 +135,6 @@ class FlightGroupSpawner:
"No room on runway or parking slots. Starting from the air."
)
group = self._generate_over_departure(name, cp)
group.points[0].alt = 1500
return group
def generate_mid_mission(self) -> FlyingGroup[Any]:
@@ -136,6 +144,10 @@ class FlightGroupSpawner:
pos = self.flight.state.estimate_position()
pos += Vector2(random.randint(100, 1000), random.randint(100, 1000))
alt, alt_type = self.flight.state.estimate_altitude()
cp = self.flight.squadron.location.id
if cp not in self.mission_data.cp_stack:
self.mission_data.cp_stack[cp] = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL
# We don't know where the ground is, so just make sure that any aircraft
# spawning at an MSL altitude is spawned at some minimum altitude.
@@ -145,8 +157,9 @@ class FlightGroupSpawner:
# Set a minimum AGL value for 'alt' if needed,
# otherwise planes might crash in trees and stuff.
if alt_type == "RADIO" and alt < MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL:
alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL
if alt_type == "RADIO" and alt < self.mission_data.cp_stack[cp]:
alt = self.mission_data.cp_stack[cp]
self.mission_data.cp_stack[cp] += STACK_SEPARATION
group = self.mission.flight_group(
country=self.country,
@@ -194,7 +207,11 @@ class FlightGroupSpawner:
elif self.flight.unit_type.helicopter:
alt = WARM_START_HELI_ALT
else:
alt = WARM_START_ALTITUDE
if origin.id not in self.mission_data.cp_stack:
min_alt = MINIMUM_MID_MISSION_SPAWN_ALTITUDE_AGL
self.mission_data.cp_stack[origin.id] = min_alt
alt = self.mission_data.cp_stack[origin.id]
self.mission_data.cp_stack[origin.id] += STACK_SEPARATION
speed = GroundSpeed.for_flight(self.flight, alt)
pos = at + Vector2(random.randint(100, 1000), random.randint(100, 1000))
@@ -230,16 +247,18 @@ class FlightGroupSpawner:
def _generate_at_cp_helipad(self, name: str, cp: ControlPoint) -> FlyingGroup[Any]:
try:
helipad = self.helipads[cp]
except IndexError as ex:
except IndexError:
raise NoParkingSlotError()
group = self._generate_at_group(name, helipad)
if self.start_type is not StartType.COLD:
if self.start_type is StartType.WARM:
group.points[0].type = "TakeOffParkingHot"
hpad = helipad.units[0]
for i in range(self.flight.count):
group.units[i].position = helipad.units[i].position
group.units[i].heading = helipad.units[i].heading
group.units[i].position = hpad.position
group.units[i].heading = hpad.heading
group.units[i].parking_id = str(i + 1)
return group
def dcs_start_type(self) -> DcsStartType:
@@ -259,7 +278,9 @@ class FlightGroupSpawner:
# Setting Su-33s starting from the non-supercarrier Kuznetsov to take off from
# runway to work around a DCS AI issue preventing Su-33s from taking off when
# set to "Takeoff from ramp" (#1352)
if (
# Also setting the F-14A AI variant to start from cats since they are reported
# to have severe pathfinding problems when doing ramp starts (#1927)
if self.flight.unit_type.dcs_unit_type == F_14A or (
self.flight.unit_type.dcs_unit_type == Su_33
and group_units[0] is not None
and group_units[0].type == KUZNECOW.id

View File

@@ -0,0 +1,25 @@
from dcs import Mission
from dcs.action import SetFlag
from dcs.condition import TimeAfter
from dcs.task import ControlledTask
from dcs.triggers import TriggerOnce, Event
from game.ato import Package
def create_stop_orbit_trigger(
orbit: ControlledTask, package: Package, mission: Mission, elapsed: int
) -> None:
orbit.stop_if_user_flag(id(package), True)
orbits = [
x
for x in mission.triggerrules.triggers
if x.comment == f"StopOrbit{id(package)}"
]
if not any(orbits):
stop_trigger = TriggerOnce(Event.NoEvent, f"StopOrbit{id(package)}")
stop_condition = TimeAfter(elapsed)
stop_action = SetFlag(id(package))
stop_trigger.add_condition(stop_condition)
stop_trigger.add_action(stop_action)
mission.triggerrules.triggers.append(stop_trigger)

View File

@@ -0,0 +1,28 @@
from dcs.point import MovingPoint
from dcs.task import Expend, WeaponType, CarpetBombing, OptROE
from game.ato.flightwaypointtype import FlightWaypointType
from game.utils import feet, knots
from pydcs_extensions.hercules.hercules import Hercules
from .pydcswaypointbuilder import PydcsWaypointBuilder
class AirAssaultIngressBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
air_drop = self.group.units[0].unit_type in [Hercules]
if air_drop:
waypoint.speed = knots(230).meters_per_second
waypoint.speed_locked = True
waypoint.ETA_locked = False
tgt = self.flight.flight_plan.package.target.position
for wpt in self.flight.flight_plan.waypoints:
if wpt.waypoint_type == FlightWaypointType.TARGET_GROUP_LOC:
tgt = wpt.position
break
bombing = CarpetBombing(
tgt,
weapon_type=WeaponType.Bombs,
expend=Expend.All,
carpet_length=feet(9000).meters,
)
waypoint.add_task(bombing)

View File

@@ -1,9 +1,4 @@
from dcs.point import MovingPoint
from dcs.task import Land
from game.utils import feet
from dcs.point import PointAction
from dcs.point import MovingPoint, PointAction
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -11,16 +6,9 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
class CargoStopBuilder(PydcsWaypointBuilder):
def build(self) -> MovingPoint:
waypoint = super().build()
# Create a landing task, currently only for Helos!
if self.flight.is_helo:
# Calculate a landing point with a small buffer to prevent AI from landing
# directly at the static ammo depot and exploding
landing_point = waypoint.position.random_point_within(15, 5)
# Use Land Task with 30s duration for helos
waypoint.add_task(Land(landing_point, duration=30))
else:
# Fixed wing will drop the cargo at the waypoint so we set a lower altitude
waypoint.alt = int(feet(10000).meters)
waypoint.alt_type = "BARO"
waypoint.action = PointAction.FlyOverPoint
waypoint.type = "LandingReFuAr"
waypoint.action = PointAction.LandingReFuAr
waypoint.landing_refuel_rearm_time = 2 # Minutes.
if (control_point := self.waypoint.control_point) is not None:
waypoint.airdrome_id = control_point.airdrome_id_for_landing
return waypoint

View File

@@ -1,7 +1,8 @@
import logging
import math
from dcs.point import MovingPoint
from dcs.task import AttackGroup, OptECMUsing, WeaponType
from dcs.task import AttackGroup, OptECMUsing, WeaponType, Expend
from game.theater import TheaterGroundObject
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -28,10 +29,24 @@ class DeadIngressBuilder(PydcsWaypointBuilder):
continue
task = AttackGroup(
miz_group.id, weapon_type=WeaponType.Auto, group_attack=True
miz_group.id,
weapon_type=WeaponType.Guided,
altitude=round(self.flight.coalition.doctrine.ingress_altitude.meters),
)
waypoint.tasks.append(task)
dir = target.position.heading_between_point(waypoint.position)
task = AttackGroup(
miz_group.id,
weapon_type=WeaponType.Unguided,
expend=Expend.All,
direction=math.radians(dir),
altitude=round(self.flight.coalition.doctrine.ingress_altitude.meters),
)
task.params["altitudeEnabled"] = False
waypoint.tasks.append(task)
# Preemptively use ECM to better avoid getting swatted.
ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfDetectedLockByRadar)
waypoint.tasks.append(ecm_option)

View File

@@ -4,6 +4,7 @@ from dcs.point import MovingPoint
from dcs.task import ControlledTask, OptFormation, OrbitAction
from game.ato.flightplans.loiter import LoiterFlightPlan
from ._helper import create_stop_orbit_trigger
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -22,8 +23,10 @@ class HoldPointBuilder(PydcsWaypointBuilder):
return
push_time = self.flight.flight_plan.push_time
self.waypoint.departure_time = push_time
loiter.stop_after_time(
int((push_time - self.elapsed_mission_time).total_seconds())
)
elapsed = int((push_time - self.elapsed_mission_time).total_seconds()) - 60
loiter.stop_after_time(elapsed)
# What follows is some code to cope with the broken 'stop after time' condition
create_stop_orbit_trigger(loiter, self.package, self.mission, elapsed)
# end of hotfix
waypoint.add_task(loiter)
waypoint.add_task(OptFormation.finger_four_close())

View File

@@ -1,50 +1,60 @@
from typing import List, Type
import random
from typing import List
from dcs.point import MovingPoint
from dcs.task import (
ControlledTask,
EngageTargets,
EscortTaskAction,
OptECMUsing,
OptFormation,
TargetType,
Targets,
)
from game.ato import FlightType
from game.utils import nautical_miles
from game.theater import NavalControlPoint
from game.utils import nautical_miles, feet
from .pydcswaypointbuilder import PydcsWaypointBuilder
class JoinPointBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
waypoint.tasks.append(OptFormation.finger_four_open())
doctrine = self.flight.coalition.doctrine
if self.flight.flight_type == FlightType.ESCORT:
self.configure_escort_tasks(
waypoint,
[
Targets.All.Air.Planes.Fighters,
Targets.All.Air.Planes.MultiroleFighters,
Targets.All.Air.Planes.Fighters.id,
Targets.All.Air.Planes.MultiroleFighters.id,
],
max_dist=doctrine.escort_engagement_range.nautical_miles,
vertical_spacing=doctrine.escort_spacing.feet,
)
if self.flight.count < 4:
waypoint.tasks.append(OptFormation.line_abreast_open())
else:
waypoint.tasks.append(OptFormation.spread_four_open())
elif self.flight.flight_type == FlightType.SEAD_ESCORT:
self.configure_escort_tasks(
waypoint, [Targets.All.GroundUnits.AirDefence.AAA.SAMRelated]
)
if isinstance(self.flight.package.target, NavalControlPoint):
self.configure_escort_tasks(
waypoint,
[
Targets.All.Naval.id,
Targets.All.GroundUnits.AirDefence.AAA.SAMRelated.id,
],
max_dist=doctrine.sead_escort_engagement_range.nautical_miles,
vertical_spacing=doctrine.sead_escort_spacing.feet,
)
else:
self.configure_escort_tasks(
waypoint,
[Targets.All.GroundUnits.AirDefence.AAA.SAMRelated.id],
max_dist=doctrine.sead_escort_engagement_range.nautical_miles,
vertical_spacing=doctrine.sead_escort_spacing.feet,
)
# Let the AI use ECM to preemptively defend themselves.
ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfDetectedLockByRadar)
waypoint.tasks.append(ecm_option)
if self.flight.count < 4:
waypoint.tasks.append(OptFormation.line_abreast_open())
else:
waypoint.tasks.append(OptFormation.spread_four_open())
elif not self.flight.flight_type.is_air_to_air:
# Capture any non A/A type to avoid issues with SPJs that use the primary radar such as the F/A-18C.
# You can bully them with STT to not be able to fire radar guided missiles at you,
@@ -54,48 +64,31 @@ class JoinPointBuilder(PydcsWaypointBuilder):
ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfOnlyLockByRadar)
waypoint.tasks.append(ecm_option)
waypoint.tasks.append(OptFormation.finger_four_open())
@staticmethod
def configure_escort_tasks(
waypoint: MovingPoint, target_types: List[Type[TargetType]]
self,
waypoint: MovingPoint,
target_types: List[str],
max_dist: float = 30.0,
vertical_spacing: float = 2000.0,
) -> None:
# Ideally we would use the escort mission type and escort task to have
# the AI automatically but the AI only escorts AI flights while they are
# traveling between waypoints. When an AI flight performs an attack
# (such as attacking the mission target), AI escorts wander aimlessly
# until the escorted group resumes its flight plan.
#
# As such, we instead use the Search Then Engage task, which is an
# enroute task that causes the AI to follow their flight plan and engage
# enemies of the set type within a certain distance. The downside to
# this approach is that AI escorts are no longer related to the group
# they are escorting, aside from the fact that they fly a similar flight
# plan at the same time. With Escort, the escorts will follow the
# escorted group out of the area. The strike element may or may not fly
# directly over the target, and they may or may not require multiple
# attack runs. For the escort flight we must just assume a flight plan
# for the escort to fly. If the strike flight doesn't need to overfly
# the target, the escorts are needlessly going in harms way. If the
# strike flight needs multiple passes, the escorts may leave before the
# escorted aircraft do.
#
# Another possible option would be to use Search Then Engage for join ->
# ingress and egress -> split, but use a Search Then Engage in Zone task
# for the target area that is set to end on a flag flip that occurs when
# the strike aircraft finish their attack task.
#
# https://forums.eagle.ru/topic/251798-options-for-alternate-ai-escort-behavior
waypoint.add_task(
ControlledTask(
EngageTargets(
# TODO: From doctrine.
max_distance=int(nautical_miles(30).meters),
targets=target_types,
)
rx = (random.random() + 0.1) * 333
ry = feet(vertical_spacing).meters
rz = (random.random() + 0.1) * 166 * random.choice([-1, 1])
pos = {"x": rx, "y": ry, "z": rz}
group_id = None
if self.package.primary_flight is not None:
group_id = self.package.primary_flight.group_id
escort = ControlledTask(
EscortTaskAction(
group_id=group_id,
engagement_max_dist=int(nautical_miles(max_dist).meters),
targets=target_types,
position=pos,
)
)
# We could set this task to end at the split point. pydcs doesn't
# currently support that task end condition though, and we don't really
# need it.
escort.stop_if_user_flag(f"split-{id(self.package)}", True)
waypoint.tasks.append(escort)

Some files were not shown because too many files have changed in this diff Show More