150 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
427 changed files with 28242 additions and 15175 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,7 +2,7 @@
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: ''
---

View File

@@ -2,7 +2,7 @@
name: Code Architecture
description: >
Suggest a refactor, or other issues related to the codebase.
labels: ["refactor", "python"]
labels: ["Refactor", "Python", "Priority Low"]
body:
- type: markdown
attributes:

View File

@@ -2,7 +2,7 @@
name: Feature Request
description: >
Suggest an idea for this project.
labels: enhancement
labels: ["Enhancement", "Priority Medium"]
body:
- type: markdown
attributes:

View File

@@ -2,7 +2,7 @@
name: Mod support request
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

@@ -49,4 +49,15 @@ jobs:
include_image: true
avatar_url: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
username: GitHub Build Notifier
if: startsWith(github.ref_name, 'test')
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

@@ -1,3 +1,63 @@
# 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
@@ -47,8 +107,25 @@ 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

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.

19589
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -135,14 +135,16 @@ from pydcs_extensions.f16i_idf.f16i_idf import (
F_16D_52,
F_16D_50_NS,
F_16D_52_NS,
F_16D_Barak,
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
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
@@ -175,17 +177,22 @@ ESCORT_CAPABLE = [
MiG_29S,
F_16C_50,
F_16I,
F_16D_Barak,
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,
@@ -249,11 +256,14 @@ CAS_CAPABLE = [
F_15E,
F_16C_50,
F_16I,
F_16D_Barak,
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,
@@ -332,13 +342,14 @@ CAS_CAPABLE = [
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_F4B,
VSN_F84G,
]
@@ -348,11 +359,15 @@ SEAD_ESCORT_CAPABLE = [
JF_17,
F_16C_50,
F_16I,
F_16D_Barak,
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,
@@ -432,11 +447,14 @@ STRIKE_CAPABLE = [
Tornado_GR4,
F_16C_50,
F_16I,
F_16D_Barak,
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,
@@ -465,6 +483,8 @@ STRIKE_CAPABLE = [
F_4E,
A_10C_2,
A_10C,
VSN_F4C,
VSN_F4B,
S_3B,
A_4E_C,
Bronco_OV_10A,
@@ -506,7 +526,6 @@ STRIKE_CAPABLE = [
VSN_F104C,
VSN_F105G,
VSN_F105D,
VSN_F4B,
VSN_F84G,
]
@@ -516,6 +535,8 @@ ANTISHIP_CAPABLE = [
Tu_142,
Tu_22M3,
H_6J,
FA_18E,
FA_18F,
FA_18C_hornet,
JAS39Gripen_AG,
F_16A_MLU,
@@ -565,11 +586,14 @@ RUNWAY_ATTACK_CAPABLE = [
AJS37,
F_16C_50,
F_16I,
F_16D_Barak,
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,
@@ -598,6 +622,8 @@ RUNWAY_ATTACK_CAPABLE = [
F_4E,
A_10C_2,
A_10C,
VSN_F4C,
VSN_F4B,
S_3B,
A_4E_C,
Bronco_OV_10A,
@@ -631,7 +657,6 @@ RUNWAY_ATTACK_CAPABLE = [
VSN_F104G,
VSN_F104C,
VSN_F100,
VSN_F4B,
]
# For any aircraft that isn't necessarily directly involved in strike
@@ -667,7 +692,6 @@ AIR_ASSAULT_CAPABLE = [
Mi_24P,
Mi_24V,
Hercules,
C_47,
]
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]

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
@@ -65,6 +75,11 @@ class Flight(SidcDescribable):
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
@@ -87,6 +102,15 @@ 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()
@@ -150,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
@@ -186,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
@@ -254,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,6 +4,7 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import Iterator, TYPE_CHECKING, Type
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
@@ -13,8 +14,10 @@ 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
@@ -25,7 +28,7 @@ class AirAssaultLayout(StandardLayout):
pickup: FlightWaypoint | None
nav_to_ingress: list[FlightWaypoint]
ingress: FlightWaypoint
drop_off: 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
@@ -37,7 +40,8 @@ class AirAssaultLayout(StandardLayout):
yield self.pickup
yield from self.nav_to_ingress
yield self.ingress
yield self.drop_off
if self.drop_off is not None:
yield self.drop_off
yield self.target
yield from self.nav_to_home
yield self.arrival
@@ -53,7 +57,9 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay):
@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:
@@ -80,8 +86,10 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay):
class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
def layout(self) -> AirAssaultLayout:
if not self.flight.is_helo:
raise PlanningError("Air assault is only usable by helicopters")
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
@@ -89,7 +97,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
builder = WaypointBuilder(self.flight, self.coalition)
if 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,
@@ -99,27 +107,25 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
pickup = None
pickup_position = self.flight.departure.position
else:
# TODO The calculation of the Pickup LZ is currently randomized. This
# leads to the problem that we can not gurantee that the LZ is clear of
# obstacles. This has to be improved in the future so that the Mission can
# be autoplanned. In the current state the User has to check the created
# Waypoints for the Pickup and Dropoff LZs are free of obstacles.
# Create a special pickup zone for Helos from Airbase / FOB
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 we can not gurantee a safe LZ for DropOff. See comment above.
# 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),
@@ -135,7 +141,7 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
self.package.waypoints.ingress,
self.package.target,
),
drop_off=builder.dropoff_zone(drop_off_zone),
drop_off=dz,
target=assault_area,
nav_to_home=builder.nav_path(
drop_off_zone.position,
@@ -150,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,15 +5,18 @@ 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)
@@ -106,15 +109,10 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
if self.flight.is_helo:
# Create CTLD Zones for Helo flights
pickup_zone = builder.pickup_zone(
MissionTarget(
"Pickup Zone", cargo.origin.position.random_point_within(1000, 200)
)
MissionTarget("Pickup Zone", self._generate_ctld_pickup())
)
drop_off_zone = builder.dropoff_zone(
MissionTarget(
"Dropoff zone",
cargo.next_stop.position.random_point_within(1000, 200),
)
MissionTarget("Dropoff zone", self._generate_ctld_dropoff())
)
# Show the zone waypoints only to the player
pickup_zone.only_for_player = True
@@ -153,3 +151,15 @@ class Builder(IBuilder[AirliftFlightPlan, AirliftLayout]):
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

@@ -8,8 +8,6 @@ from .formationattack import (
FormationAttackLayout,
)
from .waypointbuilder import WaypointBuilder
from .. import FlightType
from ...utils import Distance
class EscortFlightPlan(FormationAttackFlightPlan):
@@ -32,11 +30,9 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
join = builder.join(self.package.waypoints.join)
split = builder.split(self.package.waypoints.split)
refuel = builder.refuel(self.package.waypoints.refuel)
initial = None
if self.package.primary_task == FlightType.STRIKE:
initial = builder.escort_hold(
self.package.waypoints.initial, Distance.from_feet(20000)
)
initial = builder.escort_hold(
self.package.waypoints.initial, self.doctrine.ingress_altitude
)
return FormationAttackLayout(
departure=builder.takeoff(self.flight.departure),

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

@@ -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

@@ -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}",
@@ -555,10 +558,14 @@ class WaypointBuilder:
"""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,
control_point.position,
pos,
meters(0),
"RADIO",
description=f"Stop for cargo at {control_point.name}",

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
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

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
@@ -214,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

@@ -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

@@ -29,7 +29,6 @@ class DefaultSquadronAssigner:
self.coalition.player
):
for squadron_config in self.config.by_location[control_point]:
squadron_def = self.override_squadron_defaults(
self.find_squadron_for(squadron_config, control_point),
squadron_config,
@@ -53,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
@@ -94,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,
@@ -153,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

@@ -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

@@ -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

@@ -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

@@ -1,5 +1,6 @@
from __future__ import annotations
import random
from dataclasses import dataclass
from random import randint
@@ -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

@@ -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

@@ -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

@@ -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

@@ -60,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)
@@ -178,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"))
@@ -206,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", [])
]
@@ -335,8 +335,9 @@ class Faction:
if not mod_settings.uh_60l:
self.remove_aircraft("UH-60L")
self.remove_aircraft("KC130J")
if not mod_settings.f4b_phantom:
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:
@@ -345,10 +346,10 @@ class Faction:
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()
# Remove the stock Viper because that DCS ID is now used by the Sufa
self.remove_aircraft_by_name("F-16CM Fighting Falcon (Block 50)")
if not mod_settings.f22_raptor:
self.remove_aircraft("F-22A")
if not mod_settings.f84g_thunderjet:
@@ -367,6 +368,7 @@ class Faction:
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")
@@ -423,13 +425,56 @@ 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:
@@ -451,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

@@ -31,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

@@ -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

@@ -137,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
@@ -238,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

@@ -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

@@ -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}")

View File

@@ -1,15 +1,19 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from game.ato.packagewaypoints import PackageWaypoints
from game.data.doctrine import MODERN_DOCTRINE, COLDWAR_DOCTRINE, WWII_DOCTRINE
from game.utils import knots, feet, nautical_miles
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
@@ -18,6 +22,9 @@ class Migrator:
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 = [
@@ -40,5 +47,32 @@ class Migrator:
def _update_packagewaypoints(self) -> None:
for c in self.game.coalitions:
for p in c.ato.packages:
if not hasattr(p.waypoints, "initial"):
p.waypoints = PackageWaypoints.create(p, c)
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

@@ -29,6 +29,7 @@ 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:
@@ -178,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,
)
@@ -272,8 +273,14 @@ class AircraftBehavior:
# Search Then Engage task, which we have to use instead of the Escort
# task for the reasons explained in JoinPointBuilder.
group.task = Escort.name
if flight.package.primary_task == FlightType.STRIKE:
group.add_trigger_action(SwitchWaypoint(None, 5))
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
)
@@ -283,26 +290,33 @@ class AircraftBehavior:
# 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 = SEAD.name
if flight.package.primary_task == FlightType.STRIKE:
group.add_trigger_action(SwitchWaypoint(None, 5))
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,
)

View File

@@ -29,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
@@ -104,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:
@@ -115,8 +118,10 @@ class AircraftGenerator:
)
self.unit_map.add_aircraft(group, flight)
if (
package.primary_task == FlightType.STRIKE
and package.primary_flight is not None
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)}"))
@@ -155,9 +160,6 @@ class AircraftGenerator:
def _spawn_unused_for(
self, squadron: Squadron, country: Country, faction: Faction
) -> None:
if self.game.settings.perf_disable_idle_aircraft:
return
assert isinstance(squadron.location, Airfield)
for _ in range(squadron.untasked_aircraft):
# Creating a flight even those this isn't a fragged mission lets us
@@ -202,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

@@ -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

@@ -135,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]:
@@ -208,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))
@@ -244,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:

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,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

@@ -71,10 +71,9 @@ class JoinPointBuilder(PydcsWaypointBuilder):
max_dist: float = 30.0,
vertical_spacing: float = 2000.0,
) -> None:
rx = (random.random() + 0.1) * 333
ry = feet(vertical_spacing).meters
rz = (random.random() + 0.1) * 50 * random.choice([-1, 1])
rz = (random.random() + 0.1) * 166 * random.choice([-1, 1])
pos = {"x": rx, "y": ry, "z": rz}
group_id = None

View File

@@ -4,7 +4,7 @@ from datetime import timedelta
from typing import Any, Iterable, Union
from dcs import Mission
from dcs.planes import AJS37, F_14B, JF_17
from dcs.planes import AJS37, F_14A_135_GR, F_14B, JF_17
from dcs.point import MovingPoint, PointAction
from dcs.unitgroup import FlyingGroup
@@ -91,5 +91,5 @@ class PydcsWaypointBuilder:
for i, t in enumerate(targets):
if self.group.units[0].unit_type == JF_17 and i < 4:
self.group.add_nav_target_point(t.position, "PP" + str(i + 1))
if self.group.units[0].unit_type == F_14B and i == 0:
if self.group.units[0].unit_type in [F_14B, F_14A_135_GR] and i == 0:
self.group.add_nav_target_point(t.position, "ST")

View File

@@ -71,11 +71,20 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
if self.flight.unit_type.dcs_unit_type.tacan:
tanker_info = self.mission_data.tankers[-1]
tacan = tanker_info.tacan
tacan_callsign = {
"Texaco": "TEX",
"Arco": "ARC",
"Shell": "SHL",
}.get(tanker_info.callsign)
if self.flight.tcn_name is None:
cs = tanker_info.callsign[:-2]
csn = tanker_info.callsign[-1]
tacan_callsign = {
"Texaco": "TX",
"Arco": "AC",
"Shell": "SH",
}.get(cs)
if tacan_callsign:
tacan_callsign = tacan_callsign + csn
else:
tacan_callsign = cs[0:2] + csn
else:
tacan_callsign = self.flight.tcn_name
waypoint.add_task(
ActivateBeaconCommand(
@@ -85,5 +94,6 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
bearing=True,
unit_id=self.group.units[0].id,
aa=True,
tanker=True,
)
)

View File

@@ -49,9 +49,10 @@ class SeadIngressBuilder(PydcsWaypointBuilder):
# All non ARM types like Decoys will use the normal AttackGroup Task
attack_task = AttackGroup(
miz_group.id,
weapon_type=DcsWeaponType.Guided,
weapon_type=DcsWeaponType.Decoy,
group_attack=True,
expend=Expend.All,
altitude=waypoint.alt,
)
waypoint.tasks.append(attack_task)

View File

@@ -6,7 +6,6 @@ from .pydcswaypointbuilder import PydcsWaypointBuilder
class SplitPointBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
if not self.flight.flight_type.is_air_to_air:
# Capture any non A/A type to avoid issues with SPJs that use the primary radar such as the F/A-18C.
# You can bully them with STT to not be able to fire radar guided missiles at you,

View File

@@ -3,9 +3,9 @@ import copy
from dcs import Point
from dcs.planes import B_17G, B_52H, Tu_22M3, B_1B
from dcs.point import MovingPoint
from dcs.task import Bombing, Expend, OptFormation, WeaponType
from dcs.task import Bombing, Expend, OptFormation, WeaponType, CarpetBombing
from game.utils import mach, Distance
from game.utils import mach
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -13,15 +13,12 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
bomber = self.group.units[0].unit_type in [B_17G, Tu_22M3]
bomber_guided = self.group.units[0].unit_type in [B_1B, B_52H]
if bomber_guided:
self.add_strike_tasks(waypoint, WeaponType.Guided)
self.add_bombing_tasks(waypoint)
elif bomber:
self.add_bombing_tasks(waypoint)
else:
self.add_strike_tasks(waypoint)
waypoint.tasks.append(OptFormation.trail_open())
if bomber_guided or not bomber:
self.add_strike_tasks(waypoint, WeaponType.Guided)
waypoint.tasks.append(OptFormation.ww2_bomber_element_close())
self.add_bombing_tasks(waypoint)
def add_bombing_tasks(self, waypoint: MovingPoint) -> None:
targets = self.waypoint.targets
@@ -32,9 +29,16 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
for target in targets[1:]:
center += target.position
center /= len(targets)
ga = True if self.flight.count > 1 else False
bombing = Bombing(
center, weapon_type=WeaponType.Bombs, expend=Expend.All, group_attack=ga
avg_spacing = 0.0
for t in targets:
avg_spacing += center.distance_to_point(t.position)
avg_spacing /= len(targets)
bombing = CarpetBombing(
center,
weapon_type=WeaponType.Bombs,
expend=Expend.All,
carpet_length=avg_spacing,
altitude=round(self.flight.coalition.doctrine.ingress_altitude.meters),
)
waypoint.tasks.append(bombing)
@@ -49,7 +53,8 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
bombing.params["expend"] = Expend.All.value
waypoint.tasks.append(bombing)
waypoint.speed = mach(0.85, Distance.from_feet(20000)).meters_per_second
doctrine = self.flight.coalition.doctrine
waypoint.speed = mach(0.85, doctrine.ingress_altitude).meters_per_second
# Register special waypoints
self.register_special_waypoints(self.waypoint.targets)

View File

@@ -20,6 +20,7 @@ from game.missiongenerator.aircraft.waypoints.cargostop import CargoStopBuilder
from game.missiongenerator.missiondata import MissionData
from game.settings import Settings
from game.utils import pairwise
from .airassaultingress import AirAssaultIngressBuilder
from .baiingress import BaiIngressBuilder
from .landingzone import LandingZoneBuilder
from .casingress import CasIngressBuilder
@@ -136,6 +137,7 @@ class WaypointGenerator:
FlightWaypointType.DROPOFF_ZONE: LandingZoneBuilder,
FlightWaypointType.REFUEL: RefuelPointBuilder,
FlightWaypointType.CARGO_STOP: CargoStopBuilder,
FlightWaypointType.INGRESS_AIR_ASSAULT: AirAssaultIngressBuilder,
}
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
return builder(

View File

@@ -1,210 +0,0 @@
from __future__ import annotations
import logging
from typing import List, TYPE_CHECKING, Tuple, Type
from dcs.mission import Mission, StartType
from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135, PlaneType
from dcs.task import (
AWACS,
ActivateBeaconCommand,
MainTask,
Refueling,
SetImmortalCommand,
SetInvisibleCommand,
)
from dcs.unittype import UnitType
from game.ato.ai_flight_planner_db import AEWC_CAPABLE
from game.callsigns import callsign_for_support_unit
from game.naming import namegen
from game.radio.radios import RadioRegistry
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
from game.utils import Heading
from .airconflictdescription import AirConflictDescription
from .missiondata import AwacsInfo, MissionData, TankerInfo
if TYPE_CHECKING:
from game import Game
TANKER_DISTANCE = 15000
TANKER_ALT = 4572
TANKER_HEADING_OFFSET = 45
AWACS_DISTANCE = 150000
AWACS_ALT = 13000
class AirSupportGenerator:
def __init__(
self,
mission: Mission,
conflict: AirConflictDescription,
game: Game,
radio_registry: RadioRegistry,
tacan_registry: TacanRegistry,
mission_data: MissionData,
) -> None:
self.mission = mission
self.conflict = conflict
self.game = game
self.radio_registry = radio_registry
self.tacan_registry = tacan_registry
self.mission_data = mission_data
@classmethod
def support_tasks(cls) -> List[Type[MainTask]]:
return [Refueling, AWACS]
@staticmethod
def _get_tanker_params(unit_type: Type[UnitType]) -> Tuple[int, int]:
if unit_type is KC130:
return TANKER_ALT - 500, 596
elif unit_type is KC_135:
return TANKER_ALT, 770
elif unit_type is KC135MPRS:
return TANKER_ALT + 500, 596
return TANKER_ALT, 574
def generate(self) -> None:
player_cp = (
self.conflict.blue_cp
if self.conflict.blue_cp.captured
else self.conflict.red_cp
)
country = self.mission.country(self.game.blue.country_name)
if not self.game.settings.disable_legacy_tanker:
fallback_tanker_number = 0
for i, tanker_unit_type in enumerate(
self.game.faction_for(player=True).tankers
):
unit_type = tanker_unit_type.dcs_unit_type
if not issubclass(unit_type, PlaneType):
logging.warning(f"Refueling aircraft {unit_type} must be a plane")
continue
# TODO: Make loiter altitude a property of the unit type.
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type)
freq = self.radio_registry.alloc_uhf()
tacan = self.tacan_registry.alloc_for_band(
TacanBand.Y, TacanUsage.AirToAir
)
tanker_heading = Heading.from_degrees(
self.conflict.red_cp.position.heading_between_point(
self.conflict.blue_cp.position
)
+ TANKER_HEADING_OFFSET * i
)
tanker_position = player_cp.position.point_from_heading(
tanker_heading.degrees, TANKER_DISTANCE
)
tanker_group = self.mission.refuel_flight(
country=country,
name=namegen.next_tanker_name(country, tanker_unit_type),
airport=None,
plane_type=unit_type,
position=tanker_position,
altitude=alt,
race_distance=58000,
frequency=freq.mhz,
start_type=StartType.Warm,
speed=airspeed,
tacanchannel=str(tacan),
)
tanker_group.set_frequency(freq.mhz)
callsign = callsign_for_support_unit(tanker_group)
tacan_callsign = {
"Texaco": "TEX",
"Arco": "ARC",
"Shell": "SHL",
}.get(callsign)
if tacan_callsign is None:
# The dict above is all the callsigns currently in the game, but
# non-Western countries don't use the callsigns and instead just
# use numbers. It's possible that none of those nations have
# TACAN compatible refueling aircraft, but fallback just in
# case.
tacan_callsign = f"TK{fallback_tanker_number}"
fallback_tanker_number += 1
if tanker_unit_type != IL_78M:
# Override PyDCS tacan channel.
tanker_group.points[0].tasks.pop()
tanker_group.points[0].tasks.append(
ActivateBeaconCommand(
tacan.number,
tacan.band.value,
tacan_callsign,
True,
tanker_group.units[0].id,
True,
)
)
tanker_group.points[0].tasks.append(SetInvisibleCommand(True))
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
self.mission_data.tankers.append(
TankerInfo(
group_name=str(tanker_group.name),
callsign=callsign,
variant=tanker_unit_type.name,
freq=freq,
tacan=tacan,
start_time=None,
end_time=None,
blue=True,
)
)
if not self.game.settings.disable_legacy_aewc:
possible_awacs = [
a
for a in self.game.faction_for(player=True).aircrafts
if a in AEWC_CAPABLE
]
if not possible_awacs:
logging.warning("No AWACS for faction")
return
awacs_unit = possible_awacs[0]
freq = self.radio_registry.alloc_uhf()
unit_type = awacs_unit.dcs_unit_type
if not issubclass(unit_type, PlaneType):
logging.warning(f"AWACS aircraft {unit_type} must be a plane")
return
awacs_flight = self.mission.awacs_flight(
country=country,
name=namegen.next_awacs_name(country),
plane_type=unit_type,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.center.random_point_within(
AWACS_DISTANCE, AWACS_DISTANCE
),
frequency=freq.mhz,
start_type=StartType.Warm,
)
awacs_flight.set_frequency(freq.mhz)
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
self.mission_data.awacs.append(
AwacsInfo(
group_name=str(awacs_flight.name),
callsign=callsign_for_support_unit(awacs_flight),
freq=freq,
depature_location=None,
start_time=None,
end_time=None,
blue=True,
)
)

View File

@@ -17,7 +17,7 @@ from game.radio.radios import RadioFrequency
from game.runways import RunwayData
from game.theater import ControlPoint, FrontLine
from .aircraft.flightdata import FlightData
from .airsupportgenerator import AwacsInfo, TankerInfo
from .missiondata import AwacsInfo, TankerInfo
from .flotgenerator import JtacInfo
if TYPE_CHECKING:

View File

@@ -59,7 +59,6 @@ class DrawingsGenerator:
if destination in seen:
continue
else:
# Determine path color
if cp.captured and destination.captured:
color = BLUE_PATH_COLOR

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import logging
import math
import random
from datetime import timedelta
from typing import List, Optional, TYPE_CHECKING, Tuple
from dcs import Mission
@@ -15,7 +16,6 @@ from dcs.task import (
AFAC,
AttackGroup,
ControlledTask,
EPLRS,
FAC,
FireAtPoint,
GoToWaypoint,
@@ -47,6 +47,7 @@ from .frontlineconflictdescription import FrontLineConflictDescription
from .groundforcepainter import GroundForcePainter
from .lasercoderegistry import LaserCodeRegistry
from .missiondata import JtacInfo, MissionData
from ..ato import FlightType
if TYPE_CHECKING:
from game import Game
@@ -203,7 +204,6 @@ class FlotGenerator:
side: Country,
forward_heading: Heading,
) -> None:
infantry_position = self.conflict.find_ground_position(
group.points[0].position.random_point_within(250, 50),
500,
@@ -276,8 +276,29 @@ class FlotGenerator:
vehicle = vg.units[0]
GroundForcePainter(faction, vehicle).apply_livery()
def _earliest_tot_on_flot(self, player: bool) -> timedelta:
tots = [
x.time_over_target
for x in self.game.ato_for(player).packages
if x.primary_task == FlightType.CAS
]
return (
timedelta(seconds=random.randint(300, 1800))
if len(tots) == 0
else min(
[
x.time_over_target
for x in self.game.ato_for(player).packages
if x.primary_task == FlightType.CAS
]
)
)
def _set_reform_waypoint(
self, dcs_group: VehicleGroup, forward_heading: Heading
self,
dcs_group: VehicleGroup,
forward_heading: Heading,
hold_duration: timedelta = timedelta(),
) -> None:
"""Setting a waypoint close to the spawn position allows the group to reform gracefully
rather than spin
@@ -285,7 +306,10 @@ class FlotGenerator:
reform_point = dcs_group.position.point_from_heading(
forward_heading.degrees, 50
)
dcs_group.add_waypoint(reform_point)
rp = dcs_group.add_waypoint(reform_point)
hold = ControlledTask(Hold())
hold.stop_after_duration(hold_duration.seconds)
rp.add_task(hold)
def _plan_artillery_action(
self,
@@ -319,11 +343,10 @@ class FlotGenerator:
# Artillery will fall back when under attack
if stance != CombatStance.RETREAT:
# Hold position
dcs_group.points[1].tasks.append(Hold())
retreat = self.find_retreat_point(
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 3)
dcs_group, forward_heading, int(RETREAT_DISTANCE / 3)
)
dcs_group.add_waypoint(
dcs_group.position.point_from_heading(forward_heading.degrees, 1),
@@ -370,7 +393,10 @@ class FlotGenerator:
Handles adding the DCS tasks for tank and IFV groups for all combat stances.
Returns True if tasking was added, returns False if the stance was not a combat stance.
"""
self._set_reform_waypoint(dcs_group, forward_heading)
duration = timedelta()
if stance != CombatStance.RETREAT:
duration = self._earliest_tot_on_flot(not to_cp.coalition.player)
self._set_reform_waypoint(dcs_group, forward_heading, duration)
if stance == CombatStance.AGGRESSIVE:
# Attack nearest enemy if any
# Then move forward OR Attack enemy base if it is not too far away
@@ -457,7 +483,10 @@ class FlotGenerator:
Handles adding the DCS tasks for APC and ATGM groups for all combat stances.
Returns True if tasking was added, returns False if the stance was not a combat stance.
"""
self._set_reform_waypoint(dcs_group, forward_heading)
duration = timedelta()
if stance != CombatStance.RETREAT:
duration = self._earliest_tot_on_flot(not to_cp.coalition.player)
self._set_reform_waypoint(dcs_group, forward_heading, duration)
if stance in [
CombatStance.AGGRESSIVE,
CombatStance.BREAKTHROUGH,
@@ -491,14 +520,10 @@ class FlotGenerator:
from_cp: ControlPoint,
to_cp: ControlPoint,
) -> None:
if not self.game.settings.perf_moving_units:
return
for dcs_group, group in ally_groups:
if group.unit_type.eplrs_capable:
dcs_group.points[0].tasks.append(EPLRS(dcs_group.id))
if group.role == CombatGroupRole.ARTILLERY:
if self.game.settings.perf_artillery:
target = self.get_artillery_target_in_range(
@@ -519,7 +544,7 @@ class FlotGenerator:
if stance == CombatStance.RETREAT:
# In retreat mode, the units will fall back
# If the ally base is close enough, the units will even regroup there
# If the allied base is close enough, the units will even regroup there
if (
from_cp.position.distance_to_point(dcs_group.points[0].position)
<= RETREAT_DISTANCE
@@ -537,7 +562,7 @@ class FlotGenerator:
self, dcs_group: VehicleGroup, forward_heading: Heading
) -> None:
"""
This add a trigger to manage units fleeing whenever their group is hit hard, or being engaged by CAS
This adds a trigger to manage units fleeing whenever their group is hit hard, or being engaged by CAS
"""
if len(dcs_group.units) == 1:
@@ -554,7 +579,7 @@ class FlotGenerator:
# We add a new retreat waypoint
dcs_group.add_waypoint(
self.find_retreat_point(
dcs_group, forward_heading, (int)(RETREAT_DISTANCE / 8)
dcs_group, forward_heading, int(RETREAT_DISTANCE / 8)
),
self.wpt_pointaction,
)
@@ -684,7 +709,7 @@ class FlotGenerator:
@staticmethod
def get_artilery_group_distance_from_frontline(group: CombatGroup) -> int:
"""
For artilery group, decide the distance from frontline with the range of the unit
For artillery group, decide the distance from frontline with the range of the unit
"""
rg = group.unit_type.dcs_unit_type.threat_range - 7500
if rg > DISTANCE_FROM_FRONTLINE[CombatGroupRole.ARTILLERY][1]:

View File

@@ -43,10 +43,10 @@ from game.radio.radios import RadioFrequency
from game.runways import RunwayData
from game.theater import TheaterGroundObject, TheaterUnit
from game.theater.bullseye import Bullseye
from game.utils import Distance, UnitSystem, meters, mps, pounds
from game.utils import Distance, UnitSystem, meters, mps, pounds, knots, feet
from game.weather import Weather
from .aircraft.flightdata import FlightData
from .airsupportgenerator import AwacsInfo, TankerInfo
from .missiondata import AwacsInfo, TankerInfo
from .briefinggenerator import CommInfo, JtacInfo, MissionInfoGenerator
if TYPE_CHECKING:
@@ -191,7 +191,6 @@ class NumberedWaypoint:
class FlightPlanBuilder:
WAYPOINT_DESC_MAX_LEN = 25
def __init__(self, start_time: datetime.datetime, units: UnitSystem) -> None:
@@ -336,7 +335,7 @@ class BriefingPage(KneeboardPage):
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name is not None:
if self.flight.custom_name:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
@@ -403,6 +402,19 @@ class BriefingPage(KneeboardPage):
writer.text(
f"Turbulence: {round(self.weather.atmospheric.turbulence_per_10cm)} per 10cm at ground level."
)
writer.text(
f"Wind: {self.weather.wind.at_0m.direction}°"
f" / {round(mps(self.weather.wind.at_0m.speed).knots)}kts (0ft)"
f" ; {self.weather.wind.at_2000m.direction}°"
f" / {round(mps(self.weather.wind.at_2000m.speed).knots)}kts (~6500ft)"
f" ; {self.weather.wind.at_8000m.direction}°"
f" / {round(mps(self.weather.wind.at_8000m.speed).knots)}kts (~26000ft)"
)
c = self.weather.clouds
writer.text(
f'Cloud base: {f"{int(round(meters(c.base).feet, -2))}ft" if c else "CAVOK"}'
f'{f", {c.preset.ui_name[:-2]}" if c and c.preset else ""}'
)
fl = self.flight
@@ -456,7 +468,7 @@ class BriefingPage(KneeboardPage):
ils = ""
return [
row_title,
"\n".join(textwrap.wrap(runway.airfield_name, width=24)),
"\n".join(textwrap.wrap(runway.airfield_name, width=17)),
atc,
tacan,
ils,
@@ -482,6 +494,7 @@ class SupportPage(KneeboardPage):
def __init__(
self,
flight: FlightData,
package_flights: List[FlightData],
comms: List[CommInfo],
awacs: List[AwacsInfo],
tankers: List[TankerInfo],
@@ -490,6 +503,7 @@ class SupportPage(KneeboardPage):
dark_kneeboard: bool,
) -> None:
self.flight = flight
self.package_flights = package_flights
self.comms = list(comms)
self.awacs = awacs
self.tankers = tankers
@@ -500,60 +514,92 @@ class SupportPage(KneeboardPage):
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name is not None:
if self.flight.custom_name:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
writer.title(f"{self.flight.callsign} Support Info{custom_name_title}")
# Package Section
package = self.flight.package
custom = f' "{package.custom_name}"' if package.custom_name else ""
writer.heading(f"{package.package_description} Package{custom}")
freq = self.format_frequency(package.frequency).replace("\n", " - ")
writer.text(f" FREQ: {freq}", font=writer.table_font)
tot = self._format_time(package.time_over_target)
writer.text(f" TOT: {tot}", font=writer.table_font)
comm_ladder = []
for comm in self.comms:
comm_ladder.append(
[
comm.name,
"",
"",
str(len(self.flight.units)),
self.format_frequency(comm.freq),
]
)
for f in self.package_flights:
comm_ladder.append(
[
f.callsign,
str(f.flight_type),
KneeboardPageWriter.wrap_line(str(f.aircraft_type), 23),
str(len(f.units)),
self.format_frequency(f.intra_flight_channel),
]
)
writer.table(comm_ladder, headers=["Callsign", "Task", "Type", "#A/C", "FREQ"])
# AEW&C
writer.heading("AEW&C")
aewc_ladder = []
for single_aewc in self.awacs:
if single_aewc.depature_location is None:
dep = "-"
arr = "-"
tot = "-"
tos = "-"
else:
dep = self._format_time(single_aewc.start_time)
arr = self._format_time(single_aewc.end_time)
tot = self._format_time(single_aewc.start_time)
tos = self._format_duration(
single_aewc.end_time - single_aewc.start_time
)
aewc_ladder.append(
[
str(single_aewc.callsign),
self.format_frequency(single_aewc.freq),
str(single_aewc.depature_location),
str(dep),
str(arr),
"TOT: " + tot + "\n" + "TOS: " + tos,
]
)
writer.table(
aewc_ladder,
headers=["Callsign", "FREQ", "Depature", "ETD", "ETA"],
headers=["Callsign", "FREQ", "Departure", "TOT / TOS"],
)
# Package Section
writer.heading("Comm ladder")
comm_ladder = []
for comm in self.comms:
comm_ladder.append(
[comm.name, "", "", "", self.format_frequency(comm.freq)]
)
writer.heading("Tankers:")
for tanker in self.tankers:
tot = self._format_time(tanker.start_time)
tos = self._format_duration(tanker.end_time - tanker.start_time)
comm_ladder.append(
[
tanker.callsign,
"Tanker",
tanker.variant,
KneeboardPageWriter.wrap_line(tanker.variant, 21),
str(tanker.tacan),
self.format_frequency(tanker.freq),
"TOT: " + tot + "\n" + "TOS: " + tos,
]
)
writer.table(comm_ladder, headers=["Callsign", "Task", "Type", "TACAN", "FREQ"])
writer.table(
comm_ladder,
headers=["Callsign", "Task", "Type", "TACAN", "FREQ", "TOT / TOS"],
)
writer.heading("JTAC")
jtacs = []
@@ -573,7 +619,9 @@ class SupportPage(KneeboardPage):
writer.write(path)
def format_frequency(self, frequency: RadioFrequency) -> str:
def format_frequency(self, frequency: Optional[RadioFrequency]) -> str:
if frequency is None:
return ""
channel = self.flight.channel_for(frequency)
if channel is None:
return str(frequency)
@@ -589,6 +637,13 @@ class SupportPage(KneeboardPage):
local_time = self.start_time + time
return f"{local_time.strftime('%H:%M:%S')}{'Z' if local_time.tzinfo is not None else ''}"
@staticmethod
def _format_duration(time: Optional[datetime.timedelta]) -> str:
if time is None:
return ""
time -= datetime.timedelta(microseconds=time.microseconds)
return f"{time}"
class SeadTaskPage(KneeboardPage):
"""A kneeboard page containing SEAD/DEAD target information."""
@@ -611,7 +666,7 @@ class SeadTaskPage(KneeboardPage):
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name is not None:
if self.flight.custom_name:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
@@ -653,7 +708,7 @@ class StrikeTaskPage(KneeboardPage):
def write(self, path: Path) -> None:
writer = KneeboardPageWriter(dark_theme=self.dark_kneeboard)
if self.flight.custom_name is not None:
if self.flight.custom_name:
custom_name_title = ' ("{}")'.format(self.flight.custom_name)
else:
custom_name_title = ""
@@ -733,8 +788,13 @@ class KneeboardGenerator(MissionInfoGenerator):
for flight in self.flights:
if not flight.client_units:
continue
package_flights = [
f
for f in self.flights
if f.package is flight.package and f is not flight
]
all_flights[flight.aircraft_type].extend(
self.generate_flight_kneeboard(flight)
self.generate_flight_kneeboard(flight, package_flights)
)
return all_flights
@@ -745,7 +805,9 @@ class KneeboardGenerator(MissionInfoGenerator):
return StrikeTaskPage(flight, self.dark_kneeboard)
return None
def generate_flight_kneeboard(self, flight: FlightData) -> List[KneeboardPage]:
def generate_flight_kneeboard(
self, flight: FlightData, package_flights: List[FlightData]
) -> List[KneeboardPage]:
"""Returns a list of kneeboard pages for the given flight."""
if flight.aircraft_type.utc_kneeboard:
@@ -765,6 +827,7 @@ class KneeboardGenerator(MissionInfoGenerator):
),
SupportPage(
flight,
package_flights,
self.comms,
self.awacs,
self.tankers,

View File

@@ -33,8 +33,8 @@ class AwacsInfo(GroupInfo):
"""AWACS information for the kneeboard."""
depature_location: Optional[str]
start_time: Optional[timedelta]
end_time: Optional[timedelta]
start_time: timedelta
end_time: timedelta
@dataclass
@@ -43,8 +43,8 @@ class TankerInfo(GroupInfo):
variant: str
tacan: TacanChannel
start_time: Optional[timedelta]
end_time: Optional[timedelta]
start_time: timedelta
end_time: timedelta
@dataclass

View File

@@ -17,13 +17,11 @@ from game.missiongenerator.aircraft.aircraftgenerator import (
AircraftGenerator,
)
from game.naming import namegen
from game.radio.radios import RadioFrequency, RadioRegistry
from game.radio.radios import RadioFrequency, RadioRegistry, MHz
from game.radio.tacan import TacanRegistry
from game.theater import Airfield
from game.theater.bullseye import Bullseye
from game.unitmap import UnitMap
from .airconflictdescription import AirConflictDescription
from .airsupportgenerator import AirSupportGenerator
from .briefinggenerator import BriefingGenerator, MissionInfoGenerator
from .cargoshipgenerator import CargoShipGenerator
from .convoygenerator import ConvoyGenerator
@@ -39,14 +37,12 @@ from .missiondata import MissionData
from .tgogenerator import TgoGenerator
from .triggergenerator import TriggerGenerator
from .visualsgenerator import VisualsGenerator
from ..radio.TacanContainer import TacanContainer
if TYPE_CHECKING:
from game import Game
COMBINED_ARMS_SLOTS = 3
def country_id_from_name(name: str) -> int:
for k, v in country_dict.items():
if v.name == name:
@@ -70,7 +66,11 @@ class MissionGenerator:
self.generation_started = False
with open("resources/default_options.lua", "r", encoding="utf-8") as f:
self.mission.options.load_from_dict(dcs.lua.loads(f.read())["options"])
options = dcs.lua.loads(f.read())["options"]
ext_view = game.settings.external_views_allowed
options["miscellaneous"]["f11_free_camera"] = ext_view
options["difficulty"]["spectatorExternalViews"] = ext_view
self.mission.options.load_from_dict(options)
def generate_miz(self, output: Path) -> UnitMap:
if self.generation_started:
@@ -159,6 +159,9 @@ class MissionGenerator:
unique_map_frequencies: set[RadioFrequency] = set()
self.initialize_tacan_registry(unique_map_frequencies)
self.initialize_radio_registry(unique_map_frequencies)
# Allocate UHF/VHF Guard Freq first!
unique_map_frequencies.add(MHz(243))
unique_map_frequencies.add(MHz(121, 500))
for frequency in unique_map_frequencies:
self.radio_registry.reserve(frequency)
@@ -176,6 +179,9 @@ class MissionGenerator:
logging.warning(f"TACAN beacon has no channel: {beacon.callsign}")
else:
self.tacan_registry.mark_unavailable(beacon.tacan_channel)
for cp in self.game.theater.controlpoints:
if isinstance(cp, TacanContainer) and cp.tacan is not None:
self.tacan_registry.mark_unavailable(cp.tacan)
def initialize_radio_registry(
self, unique_map_frequencies: set[RadioFrequency]
@@ -220,17 +226,6 @@ class MissionGenerator:
def generate_air_units(self, tgo_generator: TgoGenerator) -> None:
"""Generate the air units for the Operation"""
# Air Support (Tanker & Awacs)
air_support_generator = AirSupportGenerator(
self.mission,
AirConflictDescription.for_theater(self.game.theater),
self.game,
self.radio_registry,
self.tacan_registry,
self.mission_data,
)
air_support_generator.generate()
# Generate Aircraft Activity on the map
aircraft_generator = AircraftGenerator(
self.mission,
@@ -241,7 +236,7 @@ class MissionGenerator:
self.tacan_registry,
self.laser_code_registry,
self.unit_map,
mission_data=air_support_generator.mission_data,
mission_data=self.mission_data,
helipads=tgo_generator.helipads,
)
@@ -257,19 +252,18 @@ class MissionGenerator:
self.game.red.ato,
tgo_generator.runways,
)
aircraft_generator.spawn_unused_aircraft(
self.mission.country(self.game.blue.country_name),
self.mission.country(self.game.red.country_name),
)
if not self.game.settings.perf_disable_idle_aircraft:
aircraft_generator.spawn_unused_aircraft(
self.mission.country(self.game.blue.country_name),
self.mission.country(self.game.red.country_name),
)
self.mission_data.flights = aircraft_generator.flights
for flight in aircraft_generator.flights:
if not flight.client_units:
continue
flight.aircraft_type.assign_channels_for_flight(
flight, air_support_generator.mission_data
)
self.mission_data.flights = aircraft_generator.flights
flight.aircraft_type.assign_channels_for_flight(flight, self.mission_data)
def generate_destroyed_units(self) -> None:
"""Add destroyed units to the Mission"""
@@ -330,12 +324,11 @@ class MissionGenerator:
gen.generate()
def setup_combined_arms(self) -> None:
observers = 3
self.mission.groundControl.pilot_can_control_vehicles = COMBINED_ARMS_SLOTS > 0
self.mission.groundControl.blue_game_masters = 1
self.mission.groundControl.blue_tactical_commander = COMBINED_ARMS_SLOTS
self.mission.groundControl.blue_observer = observers
self.mission.groundControl.red_game_masters = 1
self.mission.groundControl.red_tactical_commander = COMBINED_ARMS_SLOTS
self.mission.groundControl.red_observer = observers
self.mission.groundControl.neutrals_observer = observers
settings = self.game.settings
commanders = settings.tactical_commander_count
self.mission.groundControl.pilot_can_control_vehicles = commanders > 0
self.mission.groundControl.blue_game_masters = settings.game_masters_count
self.mission.groundControl.blue_tactical_commander = commanders
self.mission.groundControl.blue_jtac = settings.jtac_count
self.mission.groundControl.blue_observer = settings.observer_count

View File

@@ -22,6 +22,8 @@ from dcs.ships import (
CVN_73,
CVN_75,
Stennis,
Forrestal,
LHA_Tarawa,
)
from dcs.statics import Fortification
from dcs.task import (
@@ -35,7 +37,7 @@ from dcs.task import (
)
from dcs.translation import String
from dcs.triggers import Event, TriggerOnce, TriggerStart, TriggerZone
from dcs.unit import Unit, InvisibleFARP
from dcs.unit import Unit, InvisibleFARP, BaseFARP
from dcs.unitgroup import MovingGroup, ShipGroup, StaticGroup, VehicleGroup
from dcs.unittype import ShipType, VehicleType
from dcs.vehicles import vehicle_map
@@ -45,10 +47,16 @@ from game.missiongenerator.groundforcepainter import (
GroundForcePainter,
)
from game.missiongenerator.missiondata import CarrierInfo, MissionData
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
from game.radio.radios import RadioFrequency, RadioRegistry
from game.radio.tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
from game.runways import RunwayData
from game.theater import ControlPoint, TheaterGroundObject, TheaterUnit
from game.theater import (
ControlPoint,
TheaterGroundObject,
TheaterUnit,
NavalControlPoint,
)
from game.theater.theatergroundobject import (
CarrierGroundObject,
GenericCarrierGroundObject,
@@ -351,7 +359,7 @@ class GenericCarrierGenerator(GroundObjectGenerator):
def __init__(
self,
ground_object: GenericCarrierGroundObject,
control_point: ControlPoint,
control_point: NavalControlPoint,
country: Country,
game: Game,
mission: Mission,
@@ -372,9 +380,12 @@ class GenericCarrierGenerator(GroundObjectGenerator):
self.mission_data = mission_data
def generate(self) -> None:
# This can also be refactored as the general generation was updated
atc = self.radio_registry.alloc_uhf()
if self.control_point.frequency is not None:
atc = self.control_point.frequency
if atc not in self.radio_registry.allocated_channels:
self.radio_registry.reserve(atc)
else:
atc = self.radio_registry.alloc_uhf()
for g_id, group in enumerate(self.ground_object.groups):
if not group.units:
@@ -409,15 +420,33 @@ class GenericCarrierGenerator(GroundObjectGenerator):
f"Error generating carrier group for {self.control_point.name}"
)
ship_group.units[0].type = carrier_type.id
tacan = self.tacan_registry.alloc_for_band(
TacanBand.X, TacanUsage.TransmitReceive
)
tacan_callsign = self.tacan_callsign()
icls = next(self.icls_alloc)
if self.control_point.tacan is None:
tacan = self.tacan_registry.alloc_for_band(
TacanBand.X, TacanUsage.TransmitReceive
)
else:
tacan = self.control_point.tacan
if self.control_point.tcn_name is None:
tacan_callsign = self.tacan_callsign()
else:
tacan_callsign = self.control_point.tcn_name
link4 = None
if carrier_type in [Stennis, CVN_71, CVN_72, CVN_73, CVN_75]:
link4 = self.radio_registry.alloc_uhf()
self.activate_beacons(ship_group, tacan, tacan_callsign, icls, link4)
link4carriers = [Stennis, CVN_71, CVN_72, CVN_73, CVN_75, Forrestal]
if carrier_type in link4carriers:
if self.control_point.link4 is None:
link4 = self.radio_registry.alloc_uhf()
else:
link4 = self.control_point.link4
icls = None
icls_name = self.control_point.icls_name
if carrier_type in link4carriers or carrier_type == LHA_Tarawa:
if self.control_point.icls_channel is None:
icls = next(self.icls_alloc)
else:
icls = self.control_point.icls_channel
self.activate_beacons(
ship_group, tacan, tacan_callsign, icls, icls_name, link4
)
self.add_runway_data(
brc or Heading.from_degrees(0), atc, tacan, tacan_callsign, icls
)
@@ -461,7 +490,8 @@ class GenericCarrierGenerator(GroundObjectGenerator):
group: ShipGroup,
tacan: TacanChannel,
callsign: str,
icls: int,
icls: Optional[int] = None,
icls_name: Optional[str] = None,
link4: Optional[RadioFrequency] = None,
) -> None:
group.points[0].tasks.append(
@@ -473,12 +503,14 @@ class GenericCarrierGenerator(GroundObjectGenerator):
aa=False,
)
)
group.points[0].tasks.append(
ActivateICLSCommand(icls, unit_id=group.units[0].id)
)
if icls is not None:
icls_name = "" if icls_name is None else icls_name
group.points[0].tasks.append(
ActivateICLSCommand(icls, group.units[0].id, icls_name)
)
if link4 is not None:
group.points[0].tasks.append(
ActivateLink4Command(int(link4.mhz), group.units[0].id)
ActivateLink4Command(link4.hertz, group.units[0].id)
)
group.points[0].tasks.append(ActivateACLSCommand(unit_id=group.units[0].id))
@@ -488,9 +520,8 @@ class GenericCarrierGenerator(GroundObjectGenerator):
atc: RadioFrequency,
tacan: TacanChannel,
callsign: str,
icls: int,
icls: Optional[int],
) -> None:
# TODO: Make unit name usable.
# This relies on one control point mapping exactly
# to one LHA, carrier, or other usable "runway".
# This isn't wholly true, since the DD escorts of
@@ -498,10 +529,10 @@ class GenericCarrierGenerator(GroundObjectGenerator):
# they aren't exposed as such to the game. Should
# clean this up so that's possible. We can't use the
# unit name since it's an arbitrary ID.
self.runways[self.control_point.name] = RunwayData(
self.runways[self.control_point.full_name] = RunwayData(
self.control_point.name,
brc,
"N/A",
f"{brc.degrees:03}",
atc=atc,
tacan=tacan,
tacan_callsign=callsign,
@@ -590,6 +621,13 @@ class HelipadGenerator:
self.helipads.add_unit(
InvisibleFARP(self.m.terrain, self.m.next_unit_id(), name_i)
)
# Set FREQ
if isinstance(self.cp, RadioFrequencyContainer) and self.cp.frequency:
for hp in self.helipads.units:
if isinstance(hp, BaseFARP):
hp.heliport_frequency = self.cp.frequency.mhz
pad = self.helipads.units[-1]
pad.position = helipad
pad.heading = heading
@@ -661,7 +699,9 @@ class TgoGenerator:
for ground_object in cp.ground_objects:
generator: GroundObjectGenerator
if isinstance(ground_object, CarrierGroundObject):
if isinstance(ground_object, CarrierGroundObject) and isinstance(
cp, NavalControlPoint
):
generator = CarrierGenerator(
ground_object,
cp,
@@ -675,7 +715,9 @@ class TgoGenerator:
self.unit_map,
self.mission_data,
)
elif isinstance(ground_object, LhaGroundObject):
elif isinstance(ground_object, LhaGroundObject) and isinstance(
cp, NavalControlPoint
):
generator = LhaGenerator(
ground_object,
cp,

View File

@@ -2,8 +2,9 @@ from typing import Type
from dcs.helicopters import HelicopterType, helicopter_map
from dcs.planes import PlaneType, plane_map
from dcs.unittype import VehicleType
from dcs.unittype import VehicleType, ShipType
from dcs.vehicles import vehicle_map
from dcs.ships import ship_map
def helicoptermod(helicopter: Type[HelicopterType]) -> Type[HelicopterType]:
@@ -19,3 +20,8 @@ def planemod(plane: Type[PlaneType]) -> Type[PlaneType]:
def vehiclemod(vehicle: Type[VehicleType]) -> Type[VehicleType]:
vehicle_map[vehicle.id] = vehicle
return vehicle
def shipmod(ship: Type[ShipType]) -> Type[ShipType]:
ship_map[ship.id] = ship
return ship

View File

@@ -5,7 +5,7 @@ import logging
import textwrap
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional, TYPE_CHECKING
from typing import List, Optional, TYPE_CHECKING, Any
from game.settings import Settings
@@ -32,9 +32,9 @@ class LuaPluginWorkOrder:
class PluginSettings:
def __init__(self, identifier: str, enabled_by_default: bool) -> None:
def __init__(self, identifier: str, value: Any) -> None:
self.identifier = identifier
self.enabled_by_default = enabled_by_default
self.value = value
self.settings = Settings()
self.initialize_settings()
@@ -46,20 +46,26 @@ class PluginSettings:
# Plugin options are saved in the game's Settings, but it's possible for
# plugins to change across loads. If new plugins are added or new
# options added to those plugins, initialize the new settings.
self.settings.initialize_plugin_option(self.identifier, self.enabled_by_default)
self.settings.initialize_plugin_option(self.identifier, self.value)
@property
def enabled(self) -> bool:
def get_value(self) -> Any:
return self.settings.plugin_option(self.identifier)
def set_enabled(self, enabled: bool) -> None:
self.settings.set_plugin_option(self.identifier, enabled)
def set_value(self, value: Any) -> None:
self.settings.set_plugin_option(self.identifier, value)
class LuaPluginOption(PluginSettings):
def __init__(self, identifier: str, name: str, enabled_by_default: bool) -> None:
super().__init__(identifier, enabled_by_default)
def __init__(
self, identifier: str, name: str, min: Any, max: Any, value: Any
) -> None:
super().__init__(identifier, value)
self.name = name
if type(value) == int or type(value) == float:
self.min, self.max = min, max
else:
self.min, self.max = None, None
@dataclass(frozen=True)
@@ -83,7 +89,9 @@ class LuaPluginDefinition:
LuaPluginOption(
identifier=f"{name}.{option_id}",
name=option.get("nameInUI", name),
enabled_by_default=option.get("defaultValue"),
min=option.get("minimumValue", 0),
max=option.get("maximumValue", 10000),
value=option.get("defaultValue"),
)
)
@@ -136,6 +144,10 @@ class LuaPlugin(PluginSettings):
def options(self) -> List[LuaPluginOption]:
return self.definition.options
@property
def enabled(self) -> bool:
return type(self.get_value) == bool and self.get_value
@classmethod
def from_json(cls, name: str, path: Path) -> Optional[LuaPlugin]:
try:
@@ -160,9 +172,9 @@ class LuaPlugin(PluginSettings):
if self.options:
option_decls = []
for option in self.options:
enabled = str(option.enabled).lower()
value = str(option.get_value).lower()
name = option.identifier
option_decls.append(f" dcsRetribution.plugins.{name} = {enabled}")
option_decls.append(f" dcsRetribution.plugins.{name} = {value}")
joined_options = "\n".join(option_decls)

View File

@@ -16,8 +16,6 @@ if TYPE_CHECKING:
from game.factions.faction import Faction
from game.squadrons import Squadron
FRONTLINE_RESERVES_FACTOR = 1.3
@dataclass(frozen=True)
class AircraftProcurementRequest:
@@ -41,7 +39,6 @@ class ProcurementAi:
manage_front_line: bool,
manage_aircraft: bool,
) -> None:
self.game = game
self.is_player = for_player
self.air_wing = game.air_wing_for(for_player)
@@ -62,8 +59,8 @@ class ProcurementAi:
):
return 0
# faction has no planes
if len(self.faction.aircrafts) == 0:
# faction has no planes or no squadrons
if len(self.faction.aircrafts) == 0 or len(self.air_wing.squadrons) == 0:
return 1
for cp in self.owned_points:
@@ -74,13 +71,15 @@ class ProcurementAi:
cp_aircraft = cp.allocated_aircraft()
aircraft_investment += cp_aircraft.total_value
total_investment = aircraft_investment + armor_investment
if total_investment == 0:
air = self.game.settings.auto_procurement_balance / 100.0
ground = 1 - air
weighted_investment = aircraft_investment * air + armor_investment * ground
if weighted_investment == 0:
# Turn 0 or all units were destroyed. Either way, split 30/70.
return 0.3
return min(0.3, ground)
# the more planes we have, the more ground units we want and vice versa
ground_unit_share = aircraft_investment / total_investment
ground_unit_share = aircraft_investment * air / weighted_investment
if ground_unit_share > 1.0:
raise ValueError
@@ -257,7 +256,8 @@ class ProcurementAi:
# No source of ground units, so can't buy anything.
continue
purchase_target = cp.frontline_unit_count_limit * FRONTLINE_RESERVES_FACTOR
fr_factor = self.game.settings.frontline_reserves_factor / 100.0
purchase_target = cp.frontline_unit_count_limit * fr_factor
allocated = cp.allocated_ground_units(
self.game.coalition_for(self.is_player).transfers
)

View File

@@ -0,0 +1,6 @@
from typing import Optional
class ICLSContainer:
icls_channel: Optional[int] = None
icls_name: Optional[str] = None

View File

@@ -0,0 +1,8 @@
from typing import Optional
from game.radio.RadioFrequencyContainer import RadioFrequencyContainer
from game.radio.radios import RadioFrequency
class Link4Container(RadioFrequencyContainer):
link4: Optional[RadioFrequency] = None

View File

@@ -0,0 +1,7 @@
from typing import Optional
from game.radio.radios import RadioFrequency
class RadioFrequencyContainer:
frequency: Optional[RadioFrequency] = None

View File

@@ -0,0 +1,8 @@
from typing import Optional
from game.radio.tacan import TacanChannel
class TacanContainer:
tacan: Optional[TacanChannel] = None
tcn_name: Optional[str] = None

View File

@@ -76,6 +76,9 @@ class CommonRadioChannelAllocator(RadioChannelAllocator):
for jtac in mission_data.jtacs:
flight.assign_channel(radio_id, next(channel_alloc), jtac.freq)
if (freq := flight.package.frequency) is not None:
flight.assign_channel(radio_id, next(channel_alloc), freq)
if flight.arrival != flight.departure and flight.arrival.atc is not None:
flight.assign_channel(radio_id, next(channel_alloc), flight.arrival.atc)

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import itertools
import logging
import random
import re
from dataclasses import dataclass
from typing import Dict, FrozenSet, Iterator, List, Set, Tuple
@@ -156,57 +157,61 @@ class ChannelInUseError(RuntimeError):
# TODO: Figure out appropriate steps for each radio. These are just guesses.
#: List of all known radios used by aircraft in the game.
RADIOS: List[Radio] = [
Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
Radio("AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), MHz(1), Modulation.AM),)),
Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), MHz(1), Modulation.FM),)),
Radio("AN/ARC-164", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
Radio(
"AN/ARC-186(V) AM", (RadioRange(MHz(116), MHz(152), kHz(25), Modulation.AM),)
),
Radio("AN/ARC-186(V) FM", (RadioRange(MHz(30), MHz(76), kHz(25), Modulation.FM),)),
Radio(
"AN/ARC-210",
(
RadioRange(
MHz(225),
MHz(400),
MHz(1),
kHz(25),
Modulation.AM,
frozenset((MHz(243),)),
),
RadioRange(MHz(136), MHz(155), MHz(1), Modulation.AM),
RadioRange(MHz(156), MHz(174), MHz(1), Modulation.FM),
RadioRange(MHz(118), MHz(136), MHz(1), Modulation.AM),
RadioRange(MHz(30), MHz(88), MHz(1), Modulation.FM),
RadioRange(MHz(136), MHz(155), kHz(25), Modulation.AM),
RadioRange(MHz(156), MHz(174), kHz(25), Modulation.FM),
RadioRange(MHz(118), MHz(136), kHz(25), Modulation.AM),
RadioRange(MHz(30), MHz(88), kHz(25), Modulation.FM),
# The AN/ARC-210 can also use 225-400 and 136-155 with FM Modulation
RadioRange(
MHz(225),
MHz(400),
MHz(1),
kHz(25),
Modulation.FM,
frozenset((MHz(243),)),
),
RadioRange(MHz(136), MHz(155), MHz(1), Modulation.FM),
RadioRange(MHz(136), MHz(155), kHz(25), Modulation.FM),
),
),
Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(152), MHz(1), Modulation.AM),)),
Radio("SCR-522", (RadioRange(MHz(100), MHz(156), MHz(1), Modulation.AM),)),
Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), MHz(1), Modulation.AM),)),
Radio("AN/ARC-222", (RadioRange(MHz(116), MHz(152), kHz(25), Modulation.AM),)),
Radio("SCR-522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
Radio("A.R.I. 1063", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
Radio("BC-1206", (RadioRange(kHz(200), kHz(400), kHz(10), Modulation.AM),)),
Radio(
"TRT ERA 7000 V/UHF",
(
RadioRange(MHz(118), MHz(150), MHz(1), Modulation.AM),
RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),
RadioRange(MHz(118), MHz(150), kHz(25), Modulation.AM),
RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),
),
),
Radio("TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
Radio(
"TRT ERA 7200 UHF", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)
),
# Tomcat radios
# # https://www.heatblur.se/F-14Manual/general.html#an-arc-159-uhf-1-radio
Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
Radio("AN/ARC-159", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
# https://www.heatblur.se/F-14Manual/general.html#an-arc-182-v-uhf-2-radio
Radio(
"AN/ARC-182",
(
RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),
RadioRange(MHz(108), MHz(174), MHz(1), Modulation.AM),
RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),
RadioRange(MHz(108), MHz(174), kHz(25), Modulation.AM),
# The Range from 30-88MHz should be FM but its modeled as AM in dcs
RadioRange(MHz(30), MHz(88), MHz(1), Modulation.AM),
RadioRange(MHz(30), MHz(88), kHz(25), Modulation.AM),
),
),
Radio(
@@ -220,18 +225,28 @@ RADIOS: List[Radio] = [
# 4 preset channels (A/B/C/D)
Radio("SCR522", (RadioRange(MHz(100), MHz(156), kHz(25), Modulation.AM),)),
# JF-17 Radios should use AM
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), MHz(1), Modulation.AM),)),
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), MHz(1), Modulation.AM),)),
Radio("R&S M3AR VHF", (RadioRange(MHz(120), MHz(174), kHz(25), Modulation.AM),)),
Radio("R&S M3AR UHF", (RadioRange(MHz(225), MHz(400), kHz(25), Modulation.AM),)),
# MiG-15bis
Radio("RSI-6K HF", (RadioRange(MHz(3, 750), MHz(5), kHz(25), Modulation.AM),)),
# MiG-19P
Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), MHz(1), Modulation.AM),)),
Radio("RSIU-4V", (RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM),)),
# MiG-21bis
Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), MHz(1), Modulation.AM),)),
Radio("RSIU-5V", (RadioRange(MHz(118), MHz(140), kHz(25), Modulation.AM),)),
# Ka-50
# Note: Also capable of 100MHz-150MHz, but we can't model gaps.
Radio("R-800L1", (RadioRange(MHz(220), MHz(400), kHz(25), Modulation.AM),)),
Radio("R-828", (RadioRange(MHz(20), MHz(60), kHz(25), Modulation.FM),)),
# Mi-8 / Mi-24P
Radio(
"R-863",
(
RadioRange(MHz(100), MHz(150), kHz(25), Modulation.AM),
RadioRange(MHz(220), MHz(400), kHz(25), Modulation.AM),
RadioRange(MHz(100), MHz(150), kHz(25), Modulation.FM),
RadioRange(MHz(220), MHz(400), kHz(25), Modulation.FM),
),
),
# UH-1H
Radio("AN/ARC-51BX", (RadioRange(MHz(225), MHz(400), kHz(50), Modulation.AM),)),
Radio("AN/ARC-131", (RadioRange(MHz(30), MHz(76), kHz(50), Modulation.FM),)),
@@ -263,7 +278,7 @@ RADIOS: List[Radio] = [
RadioRange(
MHz(225),
MHz(400),
MHz(1),
kHz(25),
Modulation.AM,
frozenset((MHz(243),)),
),
@@ -275,28 +290,28 @@ RADIOS: List[Radio] = [
RadioRange(
MHz(30),
MHz(88),
MHz(1),
kHz(25),
Modulation.FM,
frozenset((MHz(40, 500),)),
),
RadioRange(
MHz(108),
MHz(156),
MHz(1),
kHz(25),
Modulation.AM,
frozenset((MHz(121, 500),)),
),
RadioRange(
MHz(156),
MHz(174),
MHz(1),
kHz(25),
Modulation.FM,
frozenset((MHz(156, 800),)),
),
RadioRange(
MHz(225),
MHz(400),
MHz(1),
kHz(25),
Modulation.AM, # Actually AM/FM, but we can't represent that.
frozenset((MHz(243),)),
),
@@ -323,6 +338,13 @@ def get_radio(name: str) -> Radio:
raise KeyError(f"Unknown radio: {name}")
def random_frequency(radio: Radio) -> RadioFrequency:
range: RadioRange = radio.ranges[0]
delta = round(random.random() * (range.maximum.hertz - range.minimum.hertz))
delta -= delta % range.step.hertz
return RadioFrequency(range.minimum.hertz + delta)
class RadioRegistry:
"""Manages allocation of radio channels.
@@ -368,9 +390,8 @@ class RadioRegistry:
OutOfChannelsError: All channels compatible with the given radio are
already allocated.
"""
allocator = self.radio_allocators[radio]
try:
while (channel := next(allocator)) in self.allocated_channels:
while (channel := random_frequency(radio)) in self.allocated_channels:
pass
self.reserve(channel)
return channel

View File

@@ -54,8 +54,8 @@ class TacanChannel:
if match is None:
raise ValueError(f"Could not parse TACAN from {text}")
number = int(match.group(1))
if not number:
raise ValueError("TACAN channel cannot be 0")
if not (0 < number <= 126):
raise ValueError("TACAN channel cannot be 0 or larger than 126")
return TacanChannel(number, TacanBand(match.group(2)))

View File

@@ -31,7 +31,6 @@ class SceneryGroup:
def __init__(
self, zone_def: TriggerZone, zones: Iterable[TriggerZone], category: str
) -> None:
self.zone_def = zone_def
self.zones = zones
self.position = zone_def.position

View File

@@ -50,7 +50,6 @@ class GameUpdateEventsJs(BaseModel):
def from_events(
cls, events: GameUpdateEvents, game: Game | None
) -> GameUpdateEventsJs:
# We still need to be able to send update events when there is no game loaded
# because we need to send the unload event.
new_combats = []

View File

@@ -36,6 +36,7 @@ CAMPAIGN_MANAGEMENT_PAGE = "Campaign Management"
GENERAL_SECTION = "General"
PILOTS_AND_SQUADRONS_SECTION = "Pilots and Squadrons"
HQ_AUTOMATION_SECTION = "HQ Automation"
FLIGHT_PLANNER_AUTOMATION = "Flight Planner Automation"
MISSION_GENERATOR_PAGE = "Mission Generator"
@@ -173,30 +174,6 @@ class Settings:
"extremely incomplete so does not affect all weapons."
),
)
disable_legacy_aewc: bool = boolean_option(
"Spawn invulnerable, always-available AEW&C aircraft (deprecated)",
page=CAMPAIGN_MANAGEMENT_PAGE,
section=GENERAL_SECTION,
default=True,
invert=True,
detail=(
"If checked, an invulnerable friendly AEW&C aircraft that begins the "
"mission on station will be be spawned. This behavior will be removed in a "
"future release."
),
)
disable_legacy_tanker: bool = boolean_option(
"Spawn invulnerable, always-available tanker aircraft (deprecated)",
page=CAMPAIGN_MANAGEMENT_PAGE,
section=GENERAL_SECTION,
default=True,
invert=True,
detail=(
"If checked, an invulnerable friendly tanker aircraft that begins the "
"mission on station will be be spawned. This behavior will be removed in a "
"future release."
),
)
# Pilots and Squadrons
ai_pilot_levelling: bool = boolean_option(
"Allow AI pilot leveling",
@@ -297,7 +274,77 @@ class Settings:
HQ_AUTOMATION_SECTION,
default=True,
)
reserves_procurement_target: int = 10
auto_procurement_balance: int = bounded_int_option(
"AI ground unit procurement budget ratio (%)",
CAMPAIGN_MANAGEMENT_PAGE,
HQ_AUTOMATION_SECTION,
min=0,
max=100,
default=50,
detail=(
"Ratio (larger number -> more budget for ground units) "
"that indicates how the AI procurement planner should "
"spend its budget."
),
)
frontline_reserves_factor: int = bounded_int_option(
"AI ground unit front-line reserves factor (%)",
CAMPAIGN_MANAGEMENT_PAGE,
HQ_AUTOMATION_SECTION,
min=0,
max=1000,
default=130,
detail=(
"Factor to be multiplied with the control point's unit count limit "
"to calculate the procurement target for reserve troops at front-lines."
),
)
reserves_procurement_target: int = bounded_int_option(
"AI ground unit reserves procurement target",
CAMPAIGN_MANAGEMENT_PAGE,
HQ_AUTOMATION_SECTION,
min=0,
max=1000,
default=10,
detail=(
"The number of units that will be bought as reserves for applicable control points"
),
)
# Flight Planner Automation
#: The weight used for 2-ships.
fpa_2ship_weight: int = bounded_int_option(
"2-ship weight factor (WF2)",
CAMPAIGN_MANAGEMENT_PAGE,
FLIGHT_PLANNER_AUTOMATION,
default=50,
min=0,
max=100,
detail=(
"Used as a distribution to randomize 2/3/4-ships for BARCAP, CAS, OCA & ANTI-SHIP flights. "
"The weight W_i is calculated according to the following formula: &#10;&#13;"
"W_i = WF_i / (WF2 + WF3 + WF4)"
),
)
#: The weight used for 3-ships.
fpa_3ship_weight: int = bounded_int_option(
"3-ship weight factor (WF3)",
CAMPAIGN_MANAGEMENT_PAGE,
FLIGHT_PLANNER_AUTOMATION,
default=35,
min=0,
max=100,
detail="See 2-ship weight factor (WF2)",
)
fpa_4ship_weight: int = bounded_int_option(
"4-ship weight factor (WF4)",
CAMPAIGN_MANAGEMENT_PAGE,
FLIGHT_PLANNER_AUTOMATION,
default=15,
min=0,
max=100,
detail="See 2-ship weight factor (WF2)",
)
# Mission Generator
# Gameplay
@@ -367,7 +414,7 @@ class Settings:
"Player flights ignore TOT and spawn immediately",
MISSION_GENERATOR_PAGE,
GAMEPLAY_SECTION,
default=False,
default=True,
detail=(
"Does not adjust package waypoint times. Should not be used if players "
"have runway or in-air starts."
@@ -379,6 +426,16 @@ class Settings:
"option only allows the player to wait on the ground.</strong>"
),
)
atflir_autoswap: bool = boolean_option(
"Auto-swap ATFLIR to LITENING",
MISSION_GENERATOR_PAGE,
GAMEPLAY_SECTION,
default=True,
detail=(
"Automatically swaps ATFLIR to LITENING pod for newly generated land-based F-18 flights "
"without having to change the payload. <u>Takes effect after current turn!</u>"
),
)
default_start_type: StartType = choices_option(
"Default start type for AI aircraft",
page=MISSION_GENERATOR_PAGE,
@@ -400,6 +457,14 @@ class Settings:
min=30,
max=150,
)
desired_tanker_on_station_time: timedelta = minutes_option(
"Desired tanker on-station time",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=timedelta(minutes=60),
min=30,
max=150,
)
# Mission specific
max_frontline_length: int = bounded_int_option(
"Maximum frontline length (km)",
@@ -409,6 +474,67 @@ class Settings:
min=1,
max=100,
)
opfor_autoplanner_aggressiveness: int = bounded_int_option(
"OPFOR autoplanner aggressiveness (%)",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=20,
min=0,
max=100,
detail=(
"Chance (larger number -> higher chance) that the OPFOR AI "
"autoplanner will take risks and plan flights against targets "
"within threatened airspace."
),
)
game_masters_count: int = bounded_int_option(
"Number of game masters",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=1,
min=0,
max=10,
detail=(
"The number of game master slots to generate for each side. "
"Game masters can see, control & direct all units in the mission."
),
)
tactical_commander_count: int = bounded_int_option(
"Number of tactical commands",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=3,
min=0,
max=10,
detail=(
"The number of tactical commander slots to generate for each side. "
"Tactical commanders can control & direct friendly units."
),
)
jtac_count: int = bounded_int_option(
"Number of JTAC controllers",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=3,
min=0,
max=10,
detail=(
"The number of JTAC controller slots to generate for each side. "
"JTAC operators can only control friendly units."
),
)
observer_count: int = bounded_int_option(
"Number of observers",
page=MISSION_GENERATOR_PAGE,
section=GAMEPLAY_SECTION,
default=0,
min=0,
max=10,
detail=(
"The number of observers slots to generate for each side. "
'Use this to allow spectators when disabling "Allow external views".'
),
)
# Performance
perf_smoke_gen: bool = boolean_option(
@@ -523,6 +649,7 @@ class Settings:
show_red_ato: bool = False
enable_frontline_cheats: bool = False
enable_base_capture_cheat: bool = False
enable_transfer_cheat: bool = False
# LUA Plugins system
plugins: Dict[str, bool] = field(default_factory=dict)
@@ -533,17 +660,17 @@ class Settings:
def plugin_settings_key(identifier: str) -> str:
return f"plugins.{identifier}"
def initialize_plugin_option(self, identifier: str, default_value: bool) -> None:
def initialize_plugin_option(self, identifier: str, default_value: Any) -> None:
try:
self.plugin_option(identifier)
except KeyError:
self.set_plugin_option(identifier, default_value)
def plugin_option(self, identifier: str) -> bool:
def plugin_option(self, identifier: str) -> Any:
return self.plugins[self.plugin_settings_key(identifier)]
def set_plugin_option(self, identifier: str, enabled: bool) -> None:
self.plugins[self.plugin_settings_key(identifier)] = enabled
def set_plugin_option(self, identifier: str, value: Any) -> None:
self.plugins[self.plugin_settings_key(identifier)] = value
def __setstate__(self, state: dict[str, Any]) -> None:
# __setstate__ is called with the dict of the object being unpickled. We

View File

@@ -235,7 +235,6 @@ class MissionResultsProcessor:
delta = DEFEAT_INFLUENCE
status_msg = f"Enemy casualties outnumber allied casualties along the {cp.name}-{enemy_cp.name} frontline. Allied forces claim a victory."
elif ally_casualties > enemy_casualties:
if (
ally_units_alive > 2 * enemy_units_alive
and player_aggresive

View File

@@ -22,7 +22,7 @@ class SquadronDefLoader:
def squadron_directories() -> Iterator[Path]:
from game import persistency
yield Path(persistency.base_path()) / "Liberation/Squadrons"
yield Path(persistency.base_path()) / "Retribution/Squadrons"
yield Path("resources/squadrons")
def load(self) -> dict[AircraftType, list[SquadronDef]]:

View File

@@ -22,6 +22,7 @@ from typing import (
TYPE_CHECKING,
Tuple,
Type,
Union,
)
from uuid import UUID
@@ -40,6 +41,7 @@ from dcs.ships import (
Hms_invincible,
)
from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.triggers import TriggerZone
from dcs.unitgroup import ShipGroup, StaticGroup
from dcs.unittype import ShipType
@@ -61,6 +63,7 @@ from game.theater.presetlocation import PresetLocation
from game.utils import Distance, Heading, meters
from .base import Base
from .frontline import FrontLine
from .interfaces.CTLD import CTLD
from .missiontarget import MissionTarget
from .theatergroundobject import (
GenericCarrierGroundObject,
@@ -73,6 +76,10 @@ from ..data.units import UnitClass
from ..db import Database
from ..dcs.aircrafttype import AircraftType
from ..dcs.groundunittype import GroundUnitType
from ..radio.ICLSContainer import ICLSContainer
from ..radio.Link4Container import Link4Container
from ..radio.RadioFrequencyContainer import RadioFrequencyContainer
from ..radio.TacanContainer import TacanContainer
from ..utils import nautical_miles
from ..weather import Conditions
@@ -305,7 +312,7 @@ class ControlPointStatus(IntEnum):
Destroyed = auto()
StartingPosition = ShipGroup | StaticGroup | Airport | Point
StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point]
class ControlPoint(MissionTarget, SidcDescribable, ABC):
@@ -870,6 +877,11 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
) -> RunwayData:
...
def stub_runway_data(self) -> RunwayData:
return RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
@property
def airdrome_id_for_landing(self) -> Optional[int]:
return None
@@ -1042,9 +1054,13 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
...
class Airfield(ControlPoint):
class Airfield(ControlPoint, CTLD):
def __init__(
self, airport: Airport, theater: ConflictTheater, starts_blue: bool
self,
airport: Airport,
theater: ConflictTheater,
starts_blue: bool,
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
) -> None:
super().__init__(
airport.name,
@@ -1056,6 +1072,7 @@ class Airfield(ControlPoint):
)
self.airport = airport
self._runway_status = RunwayStatus()
self.ctld_zones = ctld_zones
@property
def dcs_airport(self) -> Airport:
@@ -1128,6 +1145,14 @@ class Airfield(ControlPoint):
conditions: Conditions,
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
if not self.airport.runways:
# Some airfields are heliports and don't have any runways. This isn't really
# the best fix, since we should still try to generate partial data for TACAN
# beacons, but it'll do for a bug fix, and the proper fix probably involves
# making heliports their own CP type.
# https://github.com/dcs-liberation/dcs_liberation/issues/2710
return self.stub_runway_data()
assigner = RunwayAssigner(conditions)
return assigner.get_preferred_runway(theater, self.airport)
@@ -1161,7 +1186,9 @@ class Airfield(ControlPoint):
return ControlPointStatus.Functional
class NavalControlPoint(ControlPoint, ABC):
class NavalControlPoint(
ControlPoint, ABC, Link4Container, TacanContainer, ICLSContainer
):
@property
def is_fleet(self) -> bool:
return True
@@ -1219,7 +1246,7 @@ class NavalControlPoint(ControlPoint, ABC):
fallback = RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
return dynamic_runways.get(self.name, fallback)
return dynamic_runways.get(self.full_name, fallback)
@property
def runway_status(self) -> RunwayStatus:
@@ -1264,7 +1291,7 @@ class Carrier(NavalControlPoint):
return SymbolSet.SEA_SURFACE, SeaSurfaceEntity.CARRIER
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from game.ato import FlightType
from game.ato.flighttype import FlightType
yield from super().mission_types(for_player)
if self.is_friendly(for_player):
@@ -1371,9 +1398,7 @@ class OffMapSpawn(ControlPoint):
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
logging.warning("TODO: Off map spawns have no runways.")
return RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
return self.stub_runway_data()
@property
def runway_status(self) -> RunwayStatus:
@@ -1392,14 +1417,20 @@ class OffMapSpawn(ControlPoint):
return ControlPointStatus.Functional
class Fob(ControlPoint):
class Fob(ControlPoint, RadioFrequencyContainer, CTLD):
def __init__(
self, name: str, at: Point, theater: ConflictTheater, starts_blue: bool
):
self,
name: str,
at: Point,
theater: ConflictTheater,
starts_blue: bool,
ctld_zones: Optional[List[Tuple[Point, float]]] = None,
) -> None:
super().__init__(
name, at, at, theater, starts_blue, cptype=ControlPointType.FOB
)
self.name = name
self.ctld_zones = ctld_zones
@property
def symbol_set_and_entity(self) -> tuple[SymbolSet, Entity]:
@@ -1415,9 +1446,7 @@ class Fob(ControlPoint):
dynamic_runways: Dict[str, RunwayData],
) -> RunwayData:
logging.warning("TODO: FOBs have no runways.")
return RunwayData(
self.full_name, runway_heading=Heading.from_degrees(0), runway_name=""
)
return self.stub_runway_data()
@property
def runway_status(self) -> RunwayStatus:

View File

@@ -12,7 +12,7 @@ from ..utils import Heading, pairwise
if TYPE_CHECKING:
from game.ato import FlightType
from .controlpoint import ControlPoint
from .controlpoint import ControlPoint, Coalition
FRONTLINE_MIN_CP_DISTANCE = 5000
@@ -121,6 +121,10 @@ class FrontLine(MissionTarget):
"""Returns a tuple of the two control points."""
return self.blue_cp, self.red_cp
@property
def coalition(self) -> Coalition:
return self.blue_cp.coalition
@property
def route_length(self) -> float:
"""The total distance of all segments"""

View File

@@ -0,0 +1,7 @@
from typing import Optional, List, Tuple
from dcs import Point
class CTLD:
ctld_zones: Optional[List[Tuple[Point, float]]] = None

View File

@@ -6,7 +6,7 @@ from dcs.mapping import Point
if TYPE_CHECKING:
from game.ato.flighttype import FlightType
from game.theater import TheaterUnit
from game.theater import TheaterUnit, Coalition
class MissionTarget:
@@ -47,3 +47,7 @@ class MissionTarget:
@property
def strike_targets(self) -> list[TheaterUnit]:
return []
@property
def coalition(self) -> Coalition:
raise NotImplementedError

View File

@@ -4,7 +4,7 @@ import logging
import random
from dataclasses import dataclass
from datetime import datetime, time
from typing import List
from typing import List, Optional
import dcs.statics
@@ -30,6 +30,7 @@ from .theatergroup import IadsGroundGroup, IadsRole, SceneryUnit, TheaterGroup
from ..armedforces.armedforces import ArmedForces
from ..armedforces.forcegroup import ForceGroup
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
from ..campaignloader.campaigngroundconfig import TgoConfig
from ..data.groups import GroupTask
from ..profiling import logged_duration
from ..settings import Settings
@@ -47,15 +48,17 @@ class GeneratorSettings:
no_lha: bool
no_player_navy: bool
no_enemy_navy: bool
tgo_config: TgoConfig
@dataclass
class ModSettings:
a4_skyhawk: bool = False
a6a_intruder: bool = False
f4b_phantom: bool = False
f4bc_phantom: bool = False
f15d_baz: bool = False
f_16_idf: bool = False
fa_18efg: bool = False
f22_raptor: bool = False
f84g_thunderjet: bool = False
f100_supersabre: bool = False
@@ -69,6 +72,7 @@ class ModSettings:
frenchpack: bool = False
high_digit_sams: bool = False
ov10a_bronco: bool = False
swedishmilitaryassetspack: bool = False
class GameGenerator:
@@ -295,9 +299,38 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
self.generate_missile_sites()
self.generate_coastal_sites()
def get_unit_group_for_task(
self, position: PresetLocation, task: GroupTask
) -> Optional[ForceGroup]:
tgo_config = self.generator_settings.tgo_config
fg = tgo_config[position.original_name]
valid_fg = (
fg
and task in fg.tasks
and all([u in self.faction.accessible_units for u in fg.units])
)
if valid_fg:
assert fg
for layout in fg.layouts:
for lg in layout.groups:
for ug in lg.unit_groups:
if not fg.has_unit_for_layout_group(ug) and ug.fill:
for g in self.faction.ground_units:
if g.unit_class in ug.unit_classes:
fg.units.append(g)
unit_group: Optional[ForceGroup] = fg
self.armed_forces.add_or_update_force_group(fg)
else:
if fg and not valid_fg:
logging.warning(
f"Override in ground_forces failed for {fg} at {position.original_name}"
)
unit_group = self.armed_forces.random_group_for_task(task)
return unit_group
def generate_armor_groups(self) -> None:
for position in self.control_point.preset_locations.armor_groups:
unit_group = self.armed_forces.random_group_for_task(GroupTask.BASE_DEFENSE)
unit_group = self.get_unit_group_for_task(position, GroupTask.BASE_DEFENSE)
if not unit_group:
logging.error(f"{self.faction_name} has no ForceGroup for Armor")
return
@@ -351,7 +384,7 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
def generate_aa_at(self, location: PresetLocation, tasks: list[GroupTask]) -> None:
for task in tasks:
unit_group = self.armed_forces.random_group_for_task(task)
unit_group = self.get_unit_group_for_task(location, task)
if unit_group:
# Only take next (smaller) aa_range when no template available for the
# most requested range. Otherwise break the loop and continue

View File

@@ -27,7 +27,7 @@ if TYPE_CHECKING:
from game.ato.flighttype import FlightType
from game.threatzones import ThreatPoly
from .theatergroup import TheaterUnit, TheaterGroup
from .controlpoint import ControlPoint
from .controlpoint import ControlPoint, Coalition
NAME_BY_CATEGORY = {
@@ -275,6 +275,10 @@ class TheaterGroundObject(MissionTarget, SidcDescribable, ABC):
def is_iads(self) -> bool:
return False
@property
def coalition(self) -> Coalition:
return self.control_point.coalition
class BuildingGroundObject(TheaterGroundObject):
def __init__(

View File

@@ -195,7 +195,8 @@ class TheaterGroup:
def max_threat_range(self, radar_only: bool = False) -> Distance:
"""Calculate the maximum threat range of the TheaterGroup.
This also checks for Launcher and Tracker Pairs and if they are functioning or not. Allows to also use only radar emitting units for the calculation with the parameter."""
This also checks for Launcher and Tracker Pairs and if they are functioning or not. Allows to also use only radar emitting units for the calculation with the parameter.
"""
max_non_radar = meters(0)
max_telar_range = meters(0)
max_tel_range = meters(0)

View File

@@ -90,6 +90,10 @@ class TheaterLoader:
def landmap_path(self) -> Path:
return self.descriptor_path.with_name("landmap.p")
@property
def icon_path(self) -> Path:
return self.descriptor_path.with_name("icon.gif")
@property
def menu_thumbnail_dcs_relative_path(self) -> Path:
with self.descriptor_path.open() as descriptor_file:

View File

@@ -59,6 +59,7 @@ from game.utils import meters, nautical_miles
if TYPE_CHECKING:
from game import Game
from game.squadrons import Squadron
from game.theater import Coalition
class Transport:
@@ -414,7 +415,8 @@ class MultiGroupTransport(MissionTarget, Transport):
units: dict[GroundUnitType, int] = defaultdict(int)
for transfer in self.transfers:
for unit_type, count in transfer.units.items():
units[unit_type] += count
if count > 0:
units[unit_type] += count
return units
def iter_units(self) -> Iterator[GroundUnitType]:
@@ -432,6 +434,10 @@ class MultiGroupTransport(MissionTarget, Transport):
def description(self) -> str:
raise NotImplementedError
@property
def coalition(self) -> Coalition:
return self.origin.coalition
class Convoy(MultiGroupTransport):
def __init__(self, origin: ControlPoint, destination: ControlPoint) -> None:
@@ -615,13 +621,20 @@ class PendingTransfers:
raise ValueError
units = {}
to_delete = []
for unit_type, remaining in transfer.units.items():
take = min(remaining, size)
if not take:
if not remaining:
to_delete.append(unit_type)
continue
size -= take
transfer.units[unit_type] -= take
if not transfer.units[unit_type]:
to_delete.append(unit_type)
units[unit_type] = take
if not size:
break
for td in to_delete:
del transfer.units[td]
new_transfer = TransferOrder(transfer.origin, transfer.destination, units)
self.pending_transfers.append(new_transfer)
return new_transfer
@@ -717,7 +730,6 @@ class PendingTransfers:
self.order_airlift_assets_at(control_point)
def desired_airlift_capacity(self, control_point: ControlPoint) -> int:
if control_point.has_factory:
is_major_hub = control_point.total_aircraft_parking > 0
# Check if there is a CP which is only reachable via Airlift

View File

@@ -2,8 +2,8 @@ from pathlib import Path
MAJOR_VERSION = 1
MINOR_VERSION = 0
MICRO_VERSION = 0
MINOR_VERSION = 1
MICRO_VERSION = 1
def _optional_build_id_component(path: Path) -> str | None:
@@ -170,6 +170,13 @@ VERSION = _build_version_string()
#:
#: Version 10.5
#: * Support for scenery objectives defined by quad zones.
#: * Campaign designers can now define `max_frontline_length` (in km) setting:
#: `max_frontline_length: 25`
CAMPAIGN_FORMAT_VERSION = (10, 5)
#: * Campaign designers can now define more settings:
#: `max_frontline_length: 25` (in km)
#: `culling_exclusion_radius: 35` (in km)
#:
#: Version 10.6
#: * Designated CTLD zones for ControlPoints (Airbases & FOBs/FARPs)
#: * 'ground_forces' in yaml file to specify preset groups for TGOs,
#: given the group is available for the faction and the task matches
CAMPAIGN_FORMAT_VERSION = (10, 6)

View File

@@ -299,7 +299,6 @@ class Weather:
def interpolate_solar_activity(
time_of_day: TimeOfDay, high: float, low: float
) -> float:
scale: float = 0
match time_of_day:

Binary file not shown.

View File

@@ -1,51 +0,0 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "DCS Liberation"
#define MyAppVersion "{{version}}"
#define MyAppPublisher "Khopa"
#define MyAppURL "https://github.com/dcs-liberation/dcs_liberation/wiki"
#define MyAppExeName "liberation_main.exe"
[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{6753B352-D281-42CB-9AFA-5E93EB90AA5A}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
DefaultGroupName={#MyAppName}
AllowNoIcons=yes
; Remove the following line to run in administrative install mode (install for all users.)
PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
OutputDir=..\dist
OutputBaseFilename=dcs_liberation
SetupIconFile=..\resources\icon.ico
UninstallDisplayIcon={app}\liberation_main.exe
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "..\dist\dcs_liberation\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

View File

@@ -14,6 +14,7 @@ from .su30 import *
from .su57 import *
from .uh60l import *
from .ov10a import *
from .swedishmilitaryassetspack import *
def load_mods() -> None:

View File

@@ -12,7 +12,6 @@ from game.modsupport import planemod
class VSN_A6A(PlaneType):
id = "VSN_A6A"
flyable = True
group_size_max = 1
height = 4.57
width = 10.15
length = 17.98

View File

@@ -613,7 +613,10 @@ class VSN_F104S(PlaneType):
AIM_9M_Sidewinder_IR_AAM = (3, Weapons.AIM_9M_Sidewinder_IR_AAM)
AIM_9P_Sidewinder_IR_AAM = (3, Weapons.AIM_9P_Sidewinder_IR_AAM)
AIM_9P5_Sidewinder_IR_AAM = (3, Weapons.AIM_9P5_Sidewinder_IR_AAM)
AIM_7E_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
3,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7F_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
class Pylon4:
@@ -659,7 +662,10 @@ class VSN_F104S(PlaneType):
AIM_9M_Sidewinder_IR_AAM = (9, Weapons.AIM_9M_Sidewinder_IR_AAM)
AIM_9P_Sidewinder_IR_AAM = (9, Weapons.AIM_9P_Sidewinder_IR_AAM)
AIM_9P5_Sidewinder_IR_AAM = (9, Weapons.AIM_9P5_Sidewinder_IR_AAM)
AIM_7E_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
9,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
AIM_7F_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
class Pylon10:

View File

@@ -106,7 +106,10 @@ class F_15D(PlaneType):
4,
Weapons.AIM_7MH_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
4,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
I_Derby_ER = (4, WeaponsF15D.I_Derby_ER)
class Pylon5:
@@ -121,7 +124,10 @@ class F_15D(PlaneType):
5,
Weapons.AIM_7MH_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
5,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
I_Derby_ER = (5, WeaponsF15D.I_Derby_ER)
ELL___8222SB = (5, WeaponsF15D.ELL___8222SB)
@@ -141,7 +147,10 @@ class F_15D(PlaneType):
7,
Weapons.AIM_7MH_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
7,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
I_Derby_ER = (7, WeaponsF15D.I_Derby_ER)
class Pylon8:
@@ -156,7 +165,10 @@ class F_15D(PlaneType):
8,
Weapons.AIM_7MH_Sparrow_Semi_Active_Radar,
)
AIM_7E_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
8,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
I_Derby_ER = (8, WeaponsF15D.I_Derby_ER)
class Pylon9:

File diff suppressed because it is too large Load Diff

View File

@@ -7,12 +7,17 @@ from game.modsupport import planemod
from pydcs_extensions.weapon_injector import inject_weapons
class WeaponsF4B:
class WeaponsF4BC:
F4B_SUU_23_Gun_Pod = {
"clsid": "{VSN_F4B_Gunpod}",
"name": "F4B SUU-23 Gun Pod",
"weight": 112.35,
}
F4B_Gunpod_w_SAPHEI_T = {
"clsid": "{VSN_F4B_Equalizer}",
"name": "F4B Gunpod w/SAPHEI-T",
"weight": 244.46,
}
VSN_F4EC_PTB = {
"clsid": "VSN_F4EC_PTB",
"name": "Fuel tank 600 Gal",
@@ -28,6 +33,11 @@ class WeaponsF4B:
"name": "Fuel tank 370 Gal",
"weight": 1240,
}
VSN_F4B_C2_PTB = {
"clsid": "VSN_F4B_C2_PTB",
"name": "Fuel tank Center 370 Gal",
"weight": 1240,
}
LAU_105_2_AIM_9J = {
"clsid": "{VSN_F4B_LAU105_AIM9J}",
"name": "LAU-105 2*AIM-9J",
@@ -40,7 +50,7 @@ class WeaponsF4B:
}
inject_weapons(WeaponsF4B)
inject_weapons(WeaponsF4BC)
@planemod
@@ -98,10 +108,6 @@ class VSN_F4B(PlaneType):
2,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
)
LAU_118a_with_AGM_45B_Shrike_ARM__Imp_ = (
2,
Weapons.LAU_118a_with_AGM_45B_Shrike_ARM__Imp_,
)
Smokewinder___red = (2, Weapons.Smokewinder___red)
Smokewinder___green = (2, Weapons.Smokewinder___green)
Smokewinder___blue = (2, Weapons.Smokewinder___blue)
@@ -123,7 +129,8 @@ class VSN_F4B(PlaneType):
2,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb,
)
VSN_F4EL_PTB = (2, WeaponsF4B.VSN_F4EL_PTB)
BIN_200 = (2, Weapons.BIN_200)
VSN_F4EL_PTB = (2, WeaponsF4BC.VSN_F4EL_PTB)
class Pylon3:
GBU_10___2000lb_Laser_Guided_Bomb = (
@@ -140,18 +147,6 @@ class VSN_F4B(PlaneType):
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
Mk_84___2000lb_GP_Bomb_LD = (3, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_88_with_2_x_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
3,
Weapons.LAU_88_with_2_x_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_ = (
3,
Weapons.LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_118a_with_AGM_45B_Shrike_ARM__Imp_ = (
3,
Weapons.LAU_118a_with_AGM_45B_Shrike_ARM__Imp_,
)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
3,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
@@ -168,18 +163,6 @@ class VSN_F4B(PlaneType):
3,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_ = (
3,
Weapons.LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_88_with_3_x_AGM_65D___Maverick_D__IIR_ASM_ = (
3,
Weapons.LAU_88_with_3_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_105_with_2_x_AIM_9M_Sidewinder_IR_AAM = (
3,
Weapons.LAU_105_with_2_x_AIM_9M_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9L = (3, Weapons.LAU_105_2_AIM_9L)
LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM = (
3,
@@ -190,11 +173,13 @@ class VSN_F4B(PlaneType):
3,
Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9J = (3, WeaponsF4B.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (3, WeaponsF4B.LAU_105_2_AIM_9JULI)
AIM_120B_AMRAAM___Active_Rdr_AAM = (3, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
LAU_105_2_AIM_9J = (3, WeaponsF4BC.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (3, WeaponsF4BC.LAU_105_2_AIM_9JULI)
AIM_7F_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
3,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets = (
3,
Weapons.Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets,
@@ -208,18 +193,21 @@ class VSN_F4B(PlaneType):
3,
Weapons.BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD,
)
BIN_200 = (3, Weapons.BIN_200)
class Pylon4:
AIM_7M_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7M_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_120B_AMRAAM___Active_Rdr_AAM = (4, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
4,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon5:
AIM_7M_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7M_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_120B_AMRAAM___Active_Rdr_AAM = (5, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
5,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon6:
Smokewinder___red = (6, Weapons.Smokewinder___red)
@@ -240,20 +228,23 @@ class VSN_F4B(PlaneType):
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
ALQ_131___ECM_Pod = (6, Weapons.ALQ_131___ECM_Pod)
F4B_SUU_23_Gun_Pod = (6, WeaponsF4B.F4B_SUU_23_Gun_Pod)
VSN_F4EC_PTB = (6, WeaponsF4B.VSN_F4EC_PTB)
F4B_Gunpod_w_SAPHEI_T = (6, WeaponsF4BC.F4B_Gunpod_w_SAPHEI_T)
VSN_F4EC_PTB = (6, WeaponsF4BC.VSN_F4EC_PTB)
VSN_F4B_C2_PTB = (6, WeaponsF4BC.VSN_F4B_C2_PTB)
class Pylon7:
AIM_7M_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7M_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_120B_AMRAAM___Active_Rdr_AAM = (7, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
7,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon8:
AIM_7M_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7M_Sparrow_Semi_Active_Radar)
AIM_7F_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_120B_AMRAAM___Active_Rdr_AAM = (8, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
8,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon9:
GBU_10___2000lb_Laser_Guided_Bomb = (
@@ -270,18 +261,6 @@ class VSN_F4B(PlaneType):
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
Mk_84___2000lb_GP_Bomb_LD = (9, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_88_with_2_x_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
9,
Weapons.LAU_88_with_2_x_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_ = (
9,
Weapons.LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_118a_with_AGM_45B_Shrike_ARM__Imp_ = (
9,
Weapons.LAU_118a_with_AGM_45B_Shrike_ARM__Imp_,
)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
9,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
@@ -298,18 +277,6 @@ class VSN_F4B(PlaneType):
9,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_ = (
9,
Weapons.LAU_88_with_2_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_88_with_3_x_AGM_65D___Maverick_D__IIR_ASM_ = (
9,
Weapons.LAU_88_with_3_x_AGM_65D___Maverick_D__IIR_ASM_,
)
LAU_105_with_2_x_AIM_9M_Sidewinder_IR_AAM = (
9,
Weapons.LAU_105_with_2_x_AIM_9M_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9L = (9, Weapons.LAU_105_2_AIM_9L)
LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM = (
9,
@@ -320,11 +287,13 @@ class VSN_F4B(PlaneType):
9,
Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9J = (9, WeaponsF4B.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (9, WeaponsF4B.LAU_105_2_AIM_9JULI)
AIM_120B_AMRAAM___Active_Rdr_AAM = (9, Weapons.AIM_120B_AMRAAM___Active_Rdr_AAM)
LAU_105_2_AIM_9J = (9, WeaponsF4BC.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (9, WeaponsF4BC.LAU_105_2_AIM_9JULI)
AIM_7F_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7E_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
9,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets = (
9,
Weapons.Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets,
@@ -338,6 +307,7 @@ class VSN_F4B(PlaneType):
9,
Weapons.BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD,
)
BIN_200 = (9, Weapons.BIN_200)
class Pylon10:
GBU_10___2000lb_Laser_Guided_Bomb = (
@@ -369,9 +339,331 @@ class VSN_F4B(PlaneType):
10,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
)
LAU_118a_with_AGM_45B_Shrike_ARM__Imp_ = (
Smokewinder___red = (10, Weapons.Smokewinder___red)
Smokewinder___green = (10, Weapons.Smokewinder___green)
Smokewinder___blue = (10, Weapons.Smokewinder___blue)
Smokewinder___white = (10, Weapons.Smokewinder___white)
Smokewinder___yellow = (10, Weapons.Smokewinder___yellow)
BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
10,
Weapons.LAU_118a_with_AGM_45B_Shrike_ARM__Imp_,
Weapons.BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
10,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD = (
10,
Weapons.BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD,
)
BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb = (
10,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb,
)
BIN_200 = (10, Weapons.BIN_200)
VSN_F4ER_PTB = (10, WeaponsF4BC.VSN_F4ER_PTB)
pylons = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
tasks = [
task.CAP,
task.Escort,
task.FighterSweep,
task.Intercept,
task.Reconnaissance,
task.GroundAttack,
task.CAS,
task.AFAC,
task.RunwayAttack,
]
task_default = task.CAP
@planemod
class VSN_F4C(PlaneType):
id = "VSN_F4C"
flyable = True
height = 5.02
width = 11.71
length = 19.2
fuel_max = 6416
max_speed = 2545.2
chaff = 48
flare = 48
charge_total = 96
chaff_charge_size = 1
flare_charge_size = 1
category = "Interceptor" # {78EFB7A2-FD52-4b57-A6A6-3BF0E1D6555F}
radio_frequency = 127.5
livery_name = "VSN_F4C" # from type
Liveries = Liveries()[livery_name]
class Pylon1:
Smoke_Generator___red_ = (1, Weapons.Smoke_Generator___red_)
Smoke_Generator___green_ = (1, Weapons.Smoke_Generator___green_)
Smoke_Generator___blue_ = (1, Weapons.Smoke_Generator___blue_)
Smoke_Generator___white_ = (1, Weapons.Smoke_Generator___white_)
Smoke_Generator___yellow_ = (1, Weapons.Smoke_Generator___yellow_)
Smoke_Generator___orange_ = (1, Weapons.Smoke_Generator___orange_)
class Pylon2:
GBU_10___2000lb_Laser_Guided_Bomb = (
2,
Weapons.GBU_10___2000lb_Laser_Guided_Bomb,
)
GBU_12___500lb_Laser_Guided_Bomb = (2, Weapons.GBU_12___500lb_Laser_Guided_Bomb)
BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets = (
2,
Weapons.BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets,
)
Mk_84___2000lb_GP_Bomb_LD = (2, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
2,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
2,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
2,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
2,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
)
Smokewinder___red = (2, Weapons.Smokewinder___red)
Smokewinder___green = (2, Weapons.Smokewinder___green)
Smokewinder___blue = (2, Weapons.Smokewinder___blue)
Smokewinder___white = (2, Weapons.Smokewinder___white)
Smokewinder___yellow = (2, Weapons.Smokewinder___yellow)
BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
2,
Weapons.BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
2,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD = (
2,
Weapons.BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD,
)
BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb = (
2,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb,
)
BIN_200 = (2, Weapons.BIN_200)
VSN_F4EL_PTB = (2, WeaponsF4BC.VSN_F4EL_PTB)
class Pylon3:
GBU_10___2000lb_Laser_Guided_Bomb = (
3,
Weapons.GBU_10___2000lb_Laser_Guided_Bomb,
)
GBU_12___500lb_Laser_Guided_Bomb = (3, Weapons.GBU_12___500lb_Laser_Guided_Bomb)
BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets = (
3,
Weapons.BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets,
)
BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD = (
3,
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
Mk_84___2000lb_GP_Bomb_LD = (3, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
3,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
3,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
3,
Weapons.BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
3,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_105_2_AIM_9L = (3, Weapons.LAU_105_2_AIM_9L)
LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM = (
3,
Weapons.LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9P5 = (3, Weapons.LAU_105_2_AIM_9P5)
LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM = (
3,
Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9J = (3, WeaponsF4BC.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (3, WeaponsF4BC.LAU_105_2_AIM_9JULI)
AIM_7F_Sparrow_Semi_Active_Radar = (3, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
3,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets = (
3,
Weapons.Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets,
)
Mk_82___500lb_GP_Bomb_LD = (3, Weapons.Mk_82___500lb_GP_Bomb_LD)
TER_9A_with_3_x_Mk_82_Snakeye___500lb_GP_Bomb_HD = (
3,
Weapons.TER_9A_with_3_x_Mk_82_Snakeye___500lb_GP_Bomb_HD,
)
BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD = (
3,
Weapons.BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD,
)
BIN_200 = (3, Weapons.BIN_200)
class Pylon4:
AIM_7F_Sparrow_Semi_Active_Radar = (4, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
4,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon5:
AIM_7F_Sparrow_Semi_Active_Radar = (5, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
5,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon6:
Smokewinder___red = (6, Weapons.Smokewinder___red)
Smokewinder___green = (6, Weapons.Smokewinder___green)
Smokewinder___blue = (6, Weapons.Smokewinder___blue)
Smokewinder___white = (6, Weapons.Smokewinder___white)
Smokewinder___yellow = (6, Weapons.Smokewinder___yellow)
BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD = (
6,
Weapons.BRU_41A_with_6_x_Mk_82___500lb_GP_Bomb_LD,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
6,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD = (
6,
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
ALQ_131___ECM_Pod = (6, Weapons.ALQ_131___ECM_Pod)
F4B_Gunpod_w_SAPHEI_T = (6, WeaponsF4BC.F4B_Gunpod_w_SAPHEI_T)
VSN_F4EC_PTB = (6, WeaponsF4BC.VSN_F4EC_PTB)
VSN_F4ER_PTB = (6, WeaponsF4BC.VSN_F4ER_PTB)
class Pylon7:
AIM_7F_Sparrow_Semi_Active_Radar = (7, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
7,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon8:
AIM_7F_Sparrow_Semi_Active_Radar = (8, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
8,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
class Pylon9:
GBU_10___2000lb_Laser_Guided_Bomb = (
9,
Weapons.GBU_10___2000lb_Laser_Guided_Bomb,
)
GBU_12___500lb_Laser_Guided_Bomb = (9, Weapons.GBU_12___500lb_Laser_Guided_Bomb)
BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets = (
9,
Weapons.BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets,
)
BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD = (
9,
Weapons.BRU_42_with_3_x_Mk_82___500lb_GP_Bombs_LD,
)
Mk_84___2000lb_GP_Bomb_LD = (9, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
9,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
9,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
9,
Weapons.BRU_33_with_2_x_LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
9,
Weapons.BRU_33_with_2_x_LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_105_2_AIM_9L = (9, Weapons.LAU_105_2_AIM_9L)
LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM = (
9,
Weapons.LAU_105_with_2_x_AIM_9P_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9P5 = (9, Weapons.LAU_105_2_AIM_9P5)
LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM = (
9,
Weapons.LAU_7_with_2_x_AIM_9B_Sidewinder_IR_AAM,
)
LAU_105_2_AIM_9J = (9, WeaponsF4BC.LAU_105_2_AIM_9J)
LAU_105_2_AIM_9JULI = (9, WeaponsF4BC.LAU_105_2_AIM_9JULI)
AIM_7F_Sparrow_Semi_Active_Radar = (9, Weapons.AIM_7F_Sparrow_Semi_Active_Radar)
AIM_7E_2_Sparrow_Semi_Active_Radar = (
9,
Weapons.AIM_7E_2_Sparrow_Semi_Active_Radar,
)
Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets = (
9,
Weapons.Mk_20_Rockeye___490lbs_CBU__247_x_HEAT_Bomblets,
)
Mk_82___500lb_GP_Bomb_LD = (9, Weapons.Mk_82___500lb_GP_Bomb_LD)
TER_9A_with_3_x_Mk_82_Snakeye___500lb_GP_Bomb_HD = (
9,
Weapons.TER_9A_with_3_x_Mk_82_Snakeye___500lb_GP_Bomb_HD,
)
BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD = (
9,
Weapons.BRU_33_with_2_x_Mk_83___1000lb_GP_Bomb_LD,
)
BIN_200 = (9, Weapons.BIN_200)
class Pylon10:
GBU_10___2000lb_Laser_Guided_Bomb = (
10,
Weapons.GBU_10___2000lb_Laser_Guided_Bomb,
)
GBU_12___500lb_Laser_Guided_Bomb = (
10,
Weapons.GBU_12___500lb_Laser_Guided_Bomb,
)
BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets = (
10,
Weapons.BRU_42_with_3_x_Mk_20_Rockeye___490lbs_CBUs__247_x_HEAT_Bomblets,
)
Mk_84___2000lb_GP_Bomb_LD = (10, Weapons.Mk_84___2000lb_GP_Bomb_LD)
LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG = (
10,
Weapons.LAU_10_pod___4_x_127mm_ZUNI__UnGd_Rkts_Mk71__HE_FRAG,
)
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
10,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
_3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE = (
10,
Weapons._3_x_LAU_61_pods___57_x_2_75_Hydra__UnGd_Rkts_M151__HE,
)
LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos = (
10,
Weapons.LAU_61_pod___19_x_2_75_Hydra__UnGd_Rkts_M156__Wht_Phos,
)
Smokewinder___red = (10, Weapons.Smokewinder___red)
Smokewinder___green = (10, Weapons.Smokewinder___green)
@@ -394,13 +686,10 @@ class VSN_F4B(PlaneType):
10,
Weapons.BRU_33_with_2_x_GBU_12___500lb_Laser_Guided_Bomb,
)
VSN_F4ER_PTB = (10, WeaponsF4B.VSN_F4ER_PTB)
BIN_200 = (10, Weapons.BIN_200)
VSN_F4ER_PTB = (10, WeaponsF4BC.VSN_F4ER_PTB)
class Pylon11:
L005_Sorbtsiya_ECM_pod__left_ = (11, Weapons.L005_Sorbtsiya_ECM_pod__left_)
L_081_Fantasmagoria_ELINT_pod = (11, Weapons.L_081_Fantasmagoria_ELINT_pod)
pylons = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
pylons = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
tasks = [
task.CAP,

File diff suppressed because it is too large Load Diff

View File

@@ -136,6 +136,11 @@ class JAS39GripenWeapons:
"name": "M70B HE Unguided rocket",
"weight": 372.2,
}
_4_x_M_71_120_kg_GP_Bomb_High_drag = {
"clsid": "{JAS39_M71HD}",
"name": "4 x M/71 120 kg GP Bomb High-drag",
"weight": 605,
}
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = {
"clsid": "{JAS39_M71LD}",
"name": "4 x M/71 120 kg GP Bomb Low-drag ",
@@ -251,6 +256,26 @@ class JAS39GripenWeapons:
"name": "KEPD 350 Long Range Anti-Radiation Cruise-missile",
"weight": 1400,
}
RBS_15_Mk4_AShM = {
"clsid": "{JAS39_RBS15}",
"name": "RBS-15 Mk4 AShM",
"weight": 650,
}
RBS_15_Mk4_AShM_for_AI = {
"clsid": "{JAS39_RBS15AI}",
"name": "RBS-15 Mk4 AShM for AI",
"weight": 650,
}
AGM_65H_TV_Guided_Missile = {
"clsid": "{JAS39_AGM_65H}",
"name": "AGM-65H TV Guided Missile",
"weight": 260,
}
AGM_65K_TV_Guided_Missile = {
"clsid": "{JAS39_AGM_65K}",
"name": "AGM-65K TV Guided Missile",
"weight": 349,
}
inject_weapons(JAS39GripenWeapons)
@@ -328,6 +353,10 @@ class JAS39Gripen(PlaneType):
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (2, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (2, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
@@ -368,6 +397,10 @@ class JAS39Gripen(PlaneType):
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (3, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (3, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
Drop_tank_1100_litre = (3, JAS39GripenWeapons.Drop_tank_1100_litre)
@@ -415,6 +448,10 @@ class JAS39Gripen(PlaneType):
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (6, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (6, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
Drop_tank_1100_litre = (6, JAS39GripenWeapons.Drop_tank_1100_litre)
@@ -454,6 +491,267 @@ class JAS39Gripen(PlaneType):
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (7, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (7, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
class Pylon8:
IRIS_T_IR_AAM = (8, JAS39GripenWeapons.IRIS_T_IR_AAM)
AIM_9L_Sidewinder_IR_AAM_ = (8, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
A_Darter_IR_AAM = (8, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (8, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (8, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (8, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (8, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
AN_ASQ_T50_TCTS_Pod___ACMI_Pod = (8, Weapons.AN_ASQ_T50_TCTS_Pod___ACMI_Pod)
Smokewinder___red = (8, Weapons.Smokewinder___red)
Smokewinder___green = (8, Weapons.Smokewinder___green)
Smokewinder___blue = (8, Weapons.Smokewinder___blue)
Smokewinder___white = (8, Weapons.Smokewinder___white)
Smokewinder___yellow = (8, Weapons.Smokewinder___yellow)
Smokewinder___orange = (8, Weapons.Smokewinder___orange)
class Pylon9:
Litening_III_Targeting_Pod_FLIR = (
9,
JAS39GripenWeapons.Litening_III_Targeting_Pod_FLIR,
)
class Pylon10:
Integrated_ELINT = (10, JAS39GripenWeapons.Integrated_ELINT)
class Pylon11:
EWS_39_Integrated_ECM = (11, JAS39GripenWeapons.EWS_39_Integrated_ECM)
pylons: Set[int] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
tasks = [
task.Intercept,
task.CAP,
task.Reconnaissance,
task.Escort,
task.FighterSweep,
]
task_default = task.FighterSweep
@planemod
class JAS39Gripen_BVR(PlaneType):
id = "JAS39Gripen_BVR"
flyable = True
height = 4.5
width = 8.4
length = 14.1
fuel_max = 2550
max_speed = 2649.996
chaff = 80
flare = 40
charge_total = 120
chaff_charge_size = 1
flare_charge_size = 1
eplrs = True
category = "Interceptor" # {78EFB7A2-FD52-4b57-A6A6-3BF0E1D6555F}
radio_frequency = 127.5
livery_name = "JAS39GRIPEN_BVR" # from type
Liveries = Liveries()[livery_name]
class Pylon1:
IRIS_T_IR_AAM = (1, JAS39GripenWeapons.IRIS_T_IR_AAM)
AIM_9L_Sidewinder_IR_AAM_ = (1, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
A_Darter_IR_AAM = (1, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (1, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (1, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (1, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (1, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
AN_ASQ_T50_TCTS_Pod___ACMI_Pod = (1, Weapons.AN_ASQ_T50_TCTS_Pod___ACMI_Pod)
Smokewinder___red = (1, Weapons.Smokewinder___red)
Smokewinder___green = (1, Weapons.Smokewinder___green)
Smokewinder___blue = (1, Weapons.Smokewinder___blue)
Smokewinder___white = (1, Weapons.Smokewinder___white)
Smokewinder___yellow = (1, Weapons.Smokewinder___yellow)
Smokewinder___orange = (1, Weapons.Smokewinder___orange)
class Pylon2:
IRIS_T_IR_AAM = (2, JAS39GripenWeapons.IRIS_T_IR_AAM)
AIM_9L_Sidewinder_IR_AAM_ = (2, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
A_Darter_IR_AAM = (2, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (2, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (2, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (2, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (2, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
Meteor_BVRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.Meteor_BVRAAM_Active_Rdr_AAM,
)
AIM_120B_AMRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.AIM_120B_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_5_AMRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.AIM_120C_5_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_7_AMRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.AIM_120C_7_AMRAAM_Active_Rdr_AAM,
)
I_Derby_ER_BVRAAM_Active_Rdr_AAM = (
2,
JAS39GripenWeapons.I_Derby_ER_BVRAAM_Active_Rdr_AAM,
)
Mk_82_500_lb_GP_Bomb = (2, JAS39GripenWeapons.Mk_82_500_lb_GP_Bomb)
Mk_83_1000_lb_GP_Bomb = (2, JAS39GripenWeapons.Mk_83_1000_lb_GP_Bomb)
_2_x_Mk_82_500_lb_GP_Bomb = (2, JAS39GripenWeapons._2_x_Mk_82_500_lb_GP_Bomb)
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = (
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (2, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (2, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
class Pylon3:
AIM_9L_Sidewinder_IR_AAM_ = (3, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
IRIS_T_IR_AAM = (3, JAS39GripenWeapons.IRIS_T_IR_AAM)
A_Darter_IR_AAM = (3, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (3, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (3, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (3, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (3, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
Meteor_BVRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.Meteor_BVRAAM_Active_Rdr_AAM,
)
AIM_120B_AMRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.AIM_120B_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_5_AMRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.AIM_120C_5_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_7_AMRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.AIM_120C_7_AMRAAM_Active_Rdr_AAM,
)
I_Derby_ER_BVRAAM_Active_Rdr_AAM = (
3,
JAS39GripenWeapons.I_Derby_ER_BVRAAM_Active_Rdr_AAM,
)
Mk_82_500_lb_GP_Bomb = (3, JAS39GripenWeapons.Mk_82_500_lb_GP_Bomb)
Mk_83_1000_lb_GP_Bomb = (3, JAS39GripenWeapons.Mk_83_1000_lb_GP_Bomb)
Mk_84_2000_lb_GP_Bomb = (3, JAS39GripenWeapons.Mk_84_2000_lb_GP_Bomb)
_2_x_Mk_82_500_lb_GP_Bomb = (3, JAS39GripenWeapons._2_x_Mk_82_500_lb_GP_Bomb)
_2_x_Mk_83_1000_lb_GP_Bomb = (3, JAS39GripenWeapons._2_x_Mk_83_1000_lb_GP_Bomb)
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = (
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (3, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (3, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
Drop_tank_1100_litre = (3, JAS39GripenWeapons.Drop_tank_1100_litre)
class Pylon4:
Drop_tank_1100_litre = (4, JAS39GripenWeapons.Drop_tank_1100_litre)
class Pylon5:
Litening_III_Targeting_Pod = (5, JAS39GripenWeapons.Litening_III_Targeting_Pod)
class Pylon6:
AIM_9L_Sidewinder_IR_AAM_ = (6, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
IRIS_T_IR_AAM = (6, JAS39GripenWeapons.IRIS_T_IR_AAM)
A_Darter_IR_AAM = (6, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (6, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (6, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (6, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (6, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
Meteor_BVRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.Meteor_BVRAAM_Active_Rdr_AAM,
)
AIM_120B_AMRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.AIM_120B_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_5_AMRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.AIM_120C_5_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_7_AMRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.AIM_120C_7_AMRAAM_Active_Rdr_AAM,
)
I_Derby_ER_BVRAAM_Active_Rdr_AAM = (
6,
JAS39GripenWeapons.I_Derby_ER_BVRAAM_Active_Rdr_AAM,
)
Mk_82_500_lb_GP_Bomb = (6, JAS39GripenWeapons.Mk_82_500_lb_GP_Bomb)
Mk_83_1000_lb_GP_Bomb = (6, JAS39GripenWeapons.Mk_83_1000_lb_GP_Bomb)
Mk_84_2000_lb_GP_Bomb = (6, JAS39GripenWeapons.Mk_84_2000_lb_GP_Bomb)
_2_x_Mk_82_500_lb_GP_Bomb = (6, JAS39GripenWeapons._2_x_Mk_82_500_lb_GP_Bomb)
_2_x_Mk_83_1000_lb_GP_Bomb = (6, JAS39GripenWeapons._2_x_Mk_83_1000_lb_GP_Bomb)
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = (
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (6, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (6, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
Drop_tank_1100_litre = (6, JAS39GripenWeapons.Drop_tank_1100_litre)
class Pylon7:
IRIS_T_IR_AAM = (7, JAS39GripenWeapons.IRIS_T_IR_AAM)
AIM_9L_Sidewinder_IR_AAM_ = (7, JAS39GripenWeapons.AIM_9L_Sidewinder_IR_AAM_)
A_Darter_IR_AAM = (7, JAS39GripenWeapons.A_Darter_IR_AAM)
AIM_9M_Sidewinder_IR_AAM_ = (7, JAS39GripenWeapons.AIM_9M_Sidewinder_IR_AAM_)
AIM_9X_Sidewinder_IR_AAM_ = (7, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (7, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (7, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
Meteor_BVRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.Meteor_BVRAAM_Active_Rdr_AAM,
)
AIM_120B_AMRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.AIM_120B_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_5_AMRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.AIM_120C_5_AMRAAM_Active_Rdr_AAM,
)
AIM_120C_7_AMRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.AIM_120C_7_AMRAAM_Active_Rdr_AAM,
)
I_Derby_ER_BVRAAM_Active_Rdr_AAM = (
7,
JAS39GripenWeapons.I_Derby_ER_BVRAAM_Active_Rdr_AAM,
)
Mk_82_500_lb_GP_Bomb = (7, JAS39GripenWeapons.Mk_82_500_lb_GP_Bomb)
Mk_83_1000_lb_GP_Bomb = (7, JAS39GripenWeapons.Mk_83_1000_lb_GP_Bomb)
_2_x_Mk_82_500_lb_GP_Bomb = (7, JAS39GripenWeapons._2_x_Mk_82_500_lb_GP_Bomb)
_4_x_M_71_120_kg_GP_Bomb_Low_drag_ = (
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (7, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (7, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
@@ -542,14 +840,8 @@ class JAS39Gripen_AG(PlaneType):
AIM_9X_Sidewinder_IR_AAM_ = (2, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (2, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (2, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
RBS_15_Mk4_Gungnir_Anti_ship_Missile = (
2,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile,
)
RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_ = (
2,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_,
)
RBS_15_Mk4_AShM = (2, JAS39GripenWeapons.RBS_15_Mk4_AShM)
RBS_15_Mk4_AShM_for_AI = (2, JAS39GripenWeapons.RBS_15_Mk4_AShM_for_AI)
MAR_1_High_Speed_Anti_Radiation_Missile = (
2,
JAS39GripenWeapons.MAR_1_High_Speed_Anti_Radiation_Missile,
@@ -605,17 +897,18 @@ class JAS39Gripen_AG(PlaneType):
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
2,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (2, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (2, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
_3_x_Brimstone_Laser_Guided_Missile = (
2,
JAS39GripenWeapons._3_x_Brimstone_Laser_Guided_Missile,
)
LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
2,
Weapons.LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_117_AGM_65H = (2, Weapons.LAU_117_AGM_65H)
AGM_65K_TV_Guided_Missile = (2, JAS39GripenWeapons.AGM_65K_TV_Guided_Missile)
AGM_65H_TV_Guided_Missile = (2, JAS39GripenWeapons.AGM_65H_TV_Guided_Missile)
_3_x_SPEAR_3_Anti_Radiation_Missile = (
2,
JAS39GripenWeapons._3_x_SPEAR_3_Anti_Radiation_Missile,
@@ -630,11 +923,8 @@ class JAS39Gripen_AG(PlaneType):
AIM_9X_Sidewinder_IR_AAM_ = (3, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (3, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (3, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
3,
Weapons.LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_117_AGM_65H = (3, Weapons.LAU_117_AGM_65H)
AGM_65K_TV_Guided_Missile = (3, JAS39GripenWeapons.AGM_65K_TV_Guided_Missile)
AGM_65H_TV_Guided_Missile = (3, JAS39GripenWeapons.AGM_65H_TV_Guided_Missile)
_3_x_Brimstone_Laser_Guided_Missile = (
3,
JAS39GripenWeapons._3_x_Brimstone_Laser_Guided_Missile,
@@ -644,14 +934,8 @@ class JAS39Gripen_AG(PlaneType):
JAS39GripenWeapons._3_x_SPEAR_3_Anti_Radiation_Missile,
)
_3_x_SPEAR_EW_Decoy = (3, JAS39GripenWeapons._3_x_SPEAR_EW_Decoy)
RBS_15_Mk4_Gungnir_Anti_ship_Missile = (
3,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile,
)
RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_ = (
3,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_,
)
RBS_15_Mk4_AShM = (3, JAS39GripenWeapons.RBS_15_Mk4_AShM)
RBS_15_Mk4_AShM_for_AI = (3, JAS39GripenWeapons.RBS_15_Mk4_AShM_for_AI)
MAR_1_High_Speed_Anti_Radiation_Missile = (
3,
JAS39GripenWeapons.MAR_1_High_Speed_Anti_Radiation_Missile,
@@ -729,6 +1013,10 @@ class JAS39Gripen_AG(PlaneType):
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
3,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
Drop_tank_1100_litre = (3, JAS39GripenWeapons.Drop_tank_1100_litre)
M70B_HE_Unguided_rocket = (3, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (3, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
@@ -742,6 +1030,7 @@ class JAS39Gripen_AG(PlaneType):
)
class Pylon4:
DIS_LS_6_100_DUAL_L = (4, Weapons.DIS_LS_6_100_DUAL_L)
Drop_tank_1100_litre = (4, JAS39GripenWeapons.Drop_tank_1100_litre)
# ERRR {INV-SMOKE-RED}
@@ -762,11 +1051,8 @@ class JAS39Gripen_AG(PlaneType):
AIM_9X_Sidewinder_IR_AAM_ = (6, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (6, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (6, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
6,
Weapons.LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_117_AGM_65H = (6, Weapons.LAU_117_AGM_65H)
AGM_65K_TV_Guided_Missile = (6, JAS39GripenWeapons.AGM_65K_TV_Guided_Missile)
AGM_65H_TV_Guided_Missile = (6, JAS39GripenWeapons.AGM_65H_TV_Guided_Missile)
_3_x_Brimstone_Laser_Guided_Missile = (
6,
JAS39GripenWeapons._3_x_Brimstone_Laser_Guided_Missile,
@@ -776,14 +1062,8 @@ class JAS39Gripen_AG(PlaneType):
JAS39GripenWeapons._3_x_SPEAR_3_Anti_Radiation_Missile,
)
_3_x_SPEAR_EW_Decoy = (6, JAS39GripenWeapons._3_x_SPEAR_EW_Decoy)
RBS_15_Mk4_Gungnir_Anti_ship_Missile = (
6,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile,
)
RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_ = (
6,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_,
)
RBS_15_Mk4_AShM = (6, JAS39GripenWeapons.RBS_15_Mk4_AShM)
RBS_15_Mk4_AShM_for_AI = (6, JAS39GripenWeapons.RBS_15_Mk4_AShM_for_AI)
MAR_1_High_Speed_Anti_Radiation_Missile = (
6,
JAS39GripenWeapons.MAR_1_High_Speed_Anti_Radiation_Missile,
@@ -861,6 +1141,10 @@ class JAS39Gripen_AG(PlaneType):
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
6,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
Drop_tank_1100_litre = (6, JAS39GripenWeapons.Drop_tank_1100_litre)
M70B_HE_Unguided_rocket = (6, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (6, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
@@ -881,14 +1165,8 @@ class JAS39Gripen_AG(PlaneType):
AIM_9X_Sidewinder_IR_AAM_ = (7, JAS39GripenWeapons.AIM_9X_Sidewinder_IR_AAM_)
Python_5_IR_AAM = (7, JAS39GripenWeapons.Python_5_IR_AAM)
AIM_132_ASRAAM_IR_AAM = (7, JAS39GripenWeapons.AIM_132_ASRAAM_IR_AAM)
RBS_15_Mk4_Gungnir_Anti_ship_Missile = (
7,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile,
)
RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_ = (
7,
JAS39GripenWeapons.RBS_15_Mk4_Gungnir_Anti_ship_Missile__AI_,
)
RBS_15_Mk4_AShM = (7, JAS39GripenWeapons.RBS_15_Mk4_AShM)
RBS_15_Mk4_AShM_for_AI = (7, JAS39GripenWeapons.RBS_15_Mk4_AShM_for_AI)
MAR_1_High_Speed_Anti_Radiation_Missile = (
7,
JAS39GripenWeapons.MAR_1_High_Speed_Anti_Radiation_Missile,
@@ -944,17 +1222,18 @@ class JAS39Gripen_AG(PlaneType):
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_Low_drag_,
)
_4_x_M_71_120_kg_GP_Bomb_High_drag = (
7,
JAS39GripenWeapons._4_x_M_71_120_kg_GP_Bomb_High_drag,
)
M70B_HE_Unguided_rocket = (7, JAS39GripenWeapons.M70B_HE_Unguided_rocket)
M70B_AP_Unguided_rocket = (7, JAS39GripenWeapons.M70B_AP_Unguided_rocket)
_3_x_Brimstone_Laser_Guided_Missile = (
7,
JAS39GripenWeapons._3_x_Brimstone_Laser_Guided_Missile,
)
LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_ = (
7,
Weapons.LAU_117_with_AGM_65K___Maverick_K__CCD_Imp_ASM_,
)
LAU_117_AGM_65H = (7, Weapons.LAU_117_AGM_65H)
AGM_65K_TV_Guided_Missile = (7, JAS39GripenWeapons.AGM_65K_TV_Guided_Missile)
AGM_65H_TV_Guided_Missile = (7, JAS39GripenWeapons.AGM_65H_TV_Guided_Missile)
_3_x_SPEAR_3_Anti_Radiation_Missile = (
7,
JAS39GripenWeapons._3_x_SPEAR_3_Anti_Radiation_Missile,

View File

@@ -0,0 +1 @@
from .swedishmilitaryassetspack import *

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