Compare commits

...

1210 Commits
2.1.5 ... 2.4.3

Author SHA1 Message Date
C. Perreau
bf1087df3c Merge pull request #912 from Khopa/develop_2_4_x
2.4.3 Release
2021-02-22 21:04:23 +01:00
Khopa
523ef08697 Ran black reformater on 2.4x branch 2021-02-22 20:55:51 +01:00
Khopa
c2310453d8 Updated version String to 2.4.3 2021-02-22 20:51:53 +01:00
Khopa
b4a6a5dc26 Changelog update for 4.2.3 2021-02-22 20:51:24 +01:00
Khopa
20ceb440fa black 2021-02-22 20:28:54 +01:00
Khopa
4ee9c66524 Updated Hercules cargo script to latest version. 2021-02-22 20:28:52 +01:00
Khopa
9d04abd3af Updated C130J Hercules pydcs data 2021-02-22 20:28:51 +01:00
Khopa
a562345876 Updated credits in about dialog. 2021-02-22 20:28:34 +01:00
Dan Albert
2e7daceb3c Increase file log level to debug.
Fixes https://github.com/Khopa/dcs_liberation/issues/907
2021-02-22 20:27:57 +01:00
Khopa
a9aba3484a F-22 default loadout replaced weapons in internal bay by their "no-drag" versions 2021-02-22 20:24:27 +01:00
Khopa
1b9bbb8eb6 Updated F22 mod 2021-02-22 20:24:19 +01:00
BenBenBeartrax
25c44723a9 customized_payloads: M-2000C add Eclair counter meassures pod to all loadouts 2021-02-22 20:18:24 +01:00
Dan Albert
24f98aede5 Undo unintentional change.
Not sure how the extra whitespace got there, but this gets overwritten
on every launch.

(cherry picked from commit 2ffe3bf722)
2021-02-13 13:09:47 -08:00
Dan Albert
e40b916b07 Merge pull request #884 from Khopa/develop_2_4_x
Release 2.4.2.
2021-02-12 19:05:44 -08:00
Dan Albert
1bc994c102 Fix version number of the release. 2021-02-12 16:18:01 -08:00
Dan Albert
21be4d38e1 Mention new start dates in the changelog.
(cherry picked from commit f7889b785d)
2021-02-12 16:16:36 -08:00
Mustang-25
3b58c571b3 Add a mid-90s campaign date option
1995 is a good date to pick if you want to date restrict all GPS weapons but still have all the laser guided options.

(cherry picked from commit 27829a024a)
2021-02-12 16:16:35 -08:00
Mustang-25
e0430cf607 Adjust HARM Weapon Restriction Date
Official Navy docs have the 88A's IOC date in 1983. Also left a note on the B and C IOC dates if DCS ever adds the older models.

(cherry picked from commit a0fda2552f)
2021-02-12 16:16:31 -08:00
Dan Albert
fb425d3524 Fix rounding of budget in recruitment menu.
Fixes https://github.com/Khopa/dcs_liberation/issues/861.

(cherry picked from commit 5792eb354c)
2021-02-12 14:01:34 -08:00
Dan Albert
45300b64c5 Mention weapon data in changelog.
(cherry picked from commit dce7d91511)
2021-02-12 13:54:51 -08:00
Brandon Danyluk
4b7ef46f82 Add weapon era restrictions for USA/Russia/UK/France (#860)
(cherry picked from commit 61f1e11a48)
2021-02-12 13:49:40 -08:00
Dan Albert
b67e6d20f1 Move 2.4.2 fix to the correct section.
(cherry picked from commit 3d1afa74d4)
2021-02-12 13:48:02 -08:00
Dan Albert
0ae6575087 Transfer pending purchases forward along capture.
Fixes https://github.com/Khopa/dcs_liberation/issues/828.

(cherry picked from commit d8c94f5ece)
2021-02-12 13:46:08 -08:00
Malakhit
08f67860be Merge pull request #854 from Khopa/develop_2_4_x
2.4.1 Release
2021-02-07 11:22:38 +00:00
Simon Clark
3fab1d92b7 Fixed some areas where the non-pretty name for a unit was displayed. 2021-02-07 11:15:25 +00:00
Simon Clark
6573157112 Changelog. 2021-02-07 10:49:34 +00:00
Simon Clark
e83841eb0b Fix syntax error with SH-60B payload. 2021-02-07 10:47:36 +00:00
Simon Clark
6a0e18c0e9 Change the logic for culling missile sites.
Missile sites now generate a 2.5km culling circle around themselves, rather than using the standard full culling zone size.

Fixes #850.
2021-02-06 22:20:02 +00:00
Simon Clark
b71b6473e3 Start 2.4.1, fix #852. 2021-02-06 19:56:17 +00:00
Malakhit
c69e5e05a3 Merge pull request #841 from Khopa/develop_2_4_x
2.4.0 Release
2021-02-05 18:46:23 +00:00
Dan Albert
845e7fb956 Highlight major areas of change in the changelog.
The list is getting too long to be easily scannable for big changes :)
2021-01-31 14:52:16 -08:00
Khopa
f5cc2c3a37 Removed Western georgia campaign 2021-01-31 23:44:49 +01:00
Dan Albert
d56c2f7a50 Allow editing the arrival/diver airfield.
Fixes https://github.com/Khopa/dcs_liberation/issues/810
2021-01-31 14:26:14 -08:00
Dan Albert
5c47a8f7e1 Fix flyover waypoint generation.
Fixes https://github.com/Khopa/dcs_liberation/issues/820
2021-01-31 13:56:48 -08:00
Simon Clark
fb724e0150 Missed the bofors.
Also removes cargo carrier from factions.
2021-01-31 21:46:26 +00:00
Dan Albert
94ef47c89d Remove incomplete feature from changelog.
https://github.com/Khopa/dcs_liberation/issues/511 isn't done yet, and
as-is the system does barely anything, so not worth mentioning.
2021-01-31 13:26:29 -08:00
Dan Albert
7e415b3fd7 Fix carrier packages at beginning of campaign.
Fixes https://github.com/Khopa/dcs_liberation/issues/819
2021-01-31 13:21:34 -08:00
Simon Clark
fee497219e FInal set of vehicles, I think? 2021-01-31 20:23:58 +00:00
Simon Clark
7624f09f98 A-20G is good at anti-ship. 2021-01-31 17:09:19 +00:00
Simon Clark
946fe0a94c Added a lot of tanks to the unit info list. A lot of tanks. 2021-01-31 16:55:17 +00:00
Khopa
c7e1699546 SEAD/DEAD loadout for Harrier is using 4 AGM65-F + 2 Sidearm 2021-01-31 17:14:40 +01:00
Khopa
de4a617743 Fixed AV8B Harrier SEAD loadout contains invalid ordnance (AGM-65G) Replace by AGM-65F 2021-01-31 17:11:24 +01:00
Simon Clark
228d62dd32 Duplicates. 2021-01-31 12:15:51 +00:00
Simon Clark
51f8a60096 Add strategic bombers to DEAD/Strike lists. 2021-01-31 12:15:17 +00:00
Simon Clark
83f1a95966 Tweak the offset heading to 2 degrees, one degree doesn't always seem to be enough. 2021-01-31 10:54:29 +00:00
Dan Albert
646ba94d10 Make UI update faster on TOT change.
Fixes https://github.com/Khopa/dcs_liberation/issues/815
2021-01-30 20:25:34 -08:00
Dan Albert
8c5c08d678 Don't show income for dead buildings on the map. 2021-01-30 18:27:07 -08:00
Dan Albert
3d0b47a181 Fix exception in building debrief.
There are always a bunch of integer dead ground units. Not sure what
they are.
2021-01-30 18:24:25 -08:00
Dan Albert
ea7bece3b8 Display the TGO's income, not the CPs. 2021-01-30 17:23:18 -08:00
Dan Albert
944a8e9cd6 Allow selecting start type when creating flights. 2021-01-30 16:16:04 -08:00
Simon Clark
88f7d1d572 Changelog. 2021-01-31 00:14:53 +00:00
Simon Clark
6d78c1e302 Round budget display income figure to 2DP.
Fixes #809
2021-01-31 00:13:10 +00:00
Simon Clark
791aa8b6d1 Fix the powerplant object template.
For some reason, the large building with the tank on is rotated 180 degrees when put into an actual mission. Therefore, I've just spun it around to counter this.
2021-01-30 23:46:15 +00:00
Dan Albert
a078b67b36 Show arrival airfield in the flight list.
Fixes https://github.com/Khopa/dcs_liberation/issues/798.
2021-01-30 15:14:05 -08:00
Dan Albert
34d4ecd4e6 Add an option for default start type.
Changing this completely breaks OCA/Aircraft missions, but if the player
doesn't care about those this can reduce airfield congestion. The UI
warns about this.

This also makes the AI start type selectable in the flight UI.

Fixes https://github.com/Khopa/dcs_liberation/issues/387
Fixes https://github.com/Khopa/dcs_liberation/issues/729
2021-01-30 15:04:23 -08:00
Dan Albert
5047b535c4 Make ASAP a checkbox and maintain ASAP on changes.
Fixes https://github.com/Khopa/dcs_liberation/issues/642
2021-01-30 13:33:39 -08:00
Simon Clark
768d239840 Adds missile sites to the do-not-cull list.
Contributes-to: #700
2021-01-30 18:34:57 +00:00
Simon Clark
89392553bd Adds a new icon for AA sites with no threat.
Also adds the logic to check for this state.

Contributes-to: #239
2021-01-30 18:08:17 +00:00
Simon Clark
9a41217a59 Removes control point name dash->space replace.
This was breaking runway destruction. This seems to have been a legacy change from 1c67a2e4cf (diff-4c34a7844594e282145481e501ff81d198e869aad558b3589fbc1e140625ea03R20)

Fixes #741
2021-01-30 15:50:13 +00:00
Simon Clark
f6557e4980 Changelog. 2021-01-30 15:03:17 +00:00
Simon Clark
cc3cd95e2d Test for unit_name + " object" in the debrief.
This is because all building objects have " object" appended to the end in the unit map, but not in the state.json. Therefore, we should now find destroyed building objects in the unit map.

Should fix #793.
2021-01-30 15:00:40 +00:00
Simon Clark
8fb02136bb Changelog. 2021-01-30 13:38:55 +00:00
Simon Clark
1c86585f03 Make certain stances use slightly offset headings.
There's a DCS bug where vehicles sometimes don't move if they don't have to change heading. This is a bit of a hacky workaround for that.

Should fix #797.
2021-01-30 13:35:22 +00:00
Simon Clark
242085966b Changelog. 2021-01-30 01:14:11 +00:00
Simon Clark
6217075adc Add mission planning docs link to TOT field.
Completes: #696
2021-01-30 01:12:59 +00:00
Simon Clark
883f233c09 Adds WIP handling for default payload display with restricted date feature enabled. 2021-01-30 01:00:40 +00:00
Khopa
e4cc749180 Changelog update 2021-01-30 01:33:36 +01:00
Khopa
9780798b75 Merge remote-tracking branch 'khopa/develop' into develop 2021-01-30 01:30:00 +01:00
Khopa
225325cc29 High Digit SAM mod support added with an example factions (Russia 2010 High Digit SAM) 2021-01-30 01:28:47 +01:00
Simon Clark
2e3307065c Changelog. 2021-01-30 00:22:37 +00:00
Simon Clark
f6a4316093 Merge branch 'develop' of https://github.com/Khopa/dcs_liberation into develop 2021-01-30 00:19:38 +00:00
Simon Clark
b80aad7449 Link custom theater/faction/loadout docs in UI.
Contributes-to: #614
2021-01-30 00:19:29 +00:00
Khopa
5dbd1d093b High digit sam pydcs export update 2021-01-30 00:33:26 +01:00
Simon Clark
64e2e1109e Add the two Redfor factions from @RobertPeary.
Contributes-to: #488
2021-01-29 21:42:34 +00:00
Simon Clark
9c60140cec Changelog and Hercules radio fix.
Contributes-to: #782
2021-01-29 20:43:19 +00:00
Simon Clark
6099b664ac Updates Hercules Cargo file.
Fixes #782.
2021-01-29 20:36:05 +00:00
Simon Clark
33d084eff0 Add required mod link for USA C-130 faction.
Fixes #803.
2021-01-29 20:32:59 +00:00
Simon Clark
07eb14eaa6 Final aircraft banner until I download some mods. 2021-01-29 20:25:24 +00:00
Simon Clark
b2710fafd4 More banners.
Fixed a few typos.
2021-01-29 20:16:10 +00:00
Simon Clark
8f44b4571a Another batch of banners.
Also removes unarmed trainers.
2021-01-29 19:17:27 +00:00
Simon Clark
169f010fae Bunch more banner images. 2021-01-29 17:33:19 +00:00
Khopa
3e1547e0da Harrier default CAS loadout has APKWS 2021-01-27 22:29:43 +01:00
Khopa
825b9935ee Merge remote-tracking branch 'khopa/develop' into develop 2021-01-27 22:26:14 +01:00
Khopa
2f2e086fbb Using new pydcs export 2021-01-27 22:25:26 +01:00
Simon Clark
2f6e0c15fe Unit detail updates 2021-01-26 16:14:58 +00:00
Simon Clark
09e0c3dd63 Made the description text message a bit clearer. 2021-01-24 17:18:45 +00:00
Simon Clark
4529ac9b92 More banner images. 2021-01-24 16:43:08 +00:00
Dan Albert
8dac4eca55 Don't plan strikes against non-strike targets.
FOB strucutres can't be repaired and the player can't target them.

SAMs are targeted by DEAD already, so considering them for strike double
plans the mission.

Fixes https://github.com/Khopa/dcs_liberation/issues/686
2021-01-23 14:29:10 -08:00
Dan Albert
bf56091dc7 Fix inverted condition for building waypoints.
Fixes https://github.com/Khopa/dcs_liberation/issues/548
2021-01-23 14:10:25 -08:00
Khopa
f10f580f1c Fixed factions descriptions not being updated after auto-selection of factions. 2021-01-23 00:33:07 +01:00
Khopa
c09861d1ca Changelog update 2021-01-22 23:51:17 +01:00
Khopa
8e8df2b846 Changelog update 2021-01-22 23:51:03 +01:00
Khopa
d4a1d5bb9e Added campaign "Exercise Vegas Nerve" by Starfire 2021-01-22 23:49:03 +01:00
Khopa
84145aa7a7 Added Black Sea Lite campaign by Starfire. 2021-01-22 23:42:48 +01:00
Khopa
25f32a4776 New game wizard : Added default factions in description 2021-01-22 23:41:12 +01:00
Khopa
097c42d1dd New game wizard : Added performance information about the selected campaigns. 2021-01-22 23:07:17 +01:00
Khopa
91ac368a19 Changelog update 2021-01-22 22:11:49 +01:00
Khopa
f959dd0519 Campaign have a recommended defaults faction setup to ease campaign setup 2021-01-22 22:09:55 +01:00
Simon Clark
444605920f Adds F-14 to the DEAD list (TALD pods are cool) 2021-01-22 15:41:42 +00:00
Simon Clark
a102d8b39f Change case and add CR2. 2021-01-21 18:47:26 +00:00
Simon Clark
22eb861d28 More banner images. 2021-01-21 18:41:19 +00:00
Simon Clark
779b36bf7b The M163 Vulcan is now American again. 2021-01-20 18:07:16 +00:00
Simon Clark
f36336403b A few more ground units. The list goes on forever.
Next one to do is the TPz Fuchs.
2021-01-19 21:32:11 +00:00
Simon Clark
b7fbade968 More work on the unit info screen. 2021-01-19 17:53:55 +00:00
Simon Clark
0535b20db7 Another batch of screenshots. 2021-01-19 12:05:08 +00:00
Simon Clark
ddd91e3078 _24ed again. 2021-01-18 22:47:06 +00:00
Simon Clark
97f734b8fc Rename the B-1 image. 2021-01-18 22:46:12 +00:00
Simon Clark
38941f02a8 Add some initial images for the unit info pages.
This was pretty fun! DCS is very photogenic given the right conditions...
2021-01-18 22:44:34 +00:00
Simon Clark
84e09be199 Add aircraft manufacturer and role to info window. 2021-01-18 20:13:51 +00:00
Simon Clark
995a89d370 Add initial version of the unit info window.
DCS features a massive range of aircraft and land vehicles, and not all of them make their role(s) clear just from the name alone. What this commit does is add an "information" button (and resultant window) to the recruitment section. This should allow new players to understand what each unit is/does.

Current state - every aircraft has a country of origin and an introduction date for that variant. Some also have a small placeholder description, taken from ED's store page for that aircraft. There is also a placeholder picture (taken from a rejected image from my own personal photography) that will, in time, show a banner image of each unit.

Todo - add appropriate screenshots for each aircraft's banner, replace the placeholder text for each aircraft (this will take a while...) and add more data points for each unit type, such as a unit role (i.e. "air-superiority fighter", "multirole fighter", etc) or perhaps a list of weapons carried. I also haven't made a start on the huge number of ground units yet.
2021-01-18 19:27:54 +00:00
Simon Clark
6f11a269bc Add a few more pretty names. 2021-01-18 12:27:55 +00:00
Simon Clark
24a212a987 Make the C-130 work. For real this time.
Also separate out SEAD and DEAD taskings. Some aircraft can DEAD but not SEAD.

Also make the recruitment menu use the pretty names in the alphabetical sort func.
2021-01-17 21:41:02 +00:00
Simon Clark
3282ba0302 Hercules mod has AC-130s, so CAS makes sense too. 2021-01-17 21:10:14 +00:00
Simon Clark
a4db443f93 Add the Hercules to strike tasks.
It's not strike capable per se, but can transport objects for a strike.
2021-01-17 14:56:36 +00:00
Dan Albert
f8276f7e59 Don't defend against air threats from FOBs.
FOBs (and broken runways) do not need to be considered as sources of air
threats.

Fixes https://github.com/Khopa/dcs_liberation/issues/778
2021-01-17 01:17:33 -08:00
Dan Albert
b545634d87 Revert accidental display options default change. 2021-01-16 15:30:54 -08:00
Dan Albert
5da4cace94 Don't plan BARCAPs so aggressively.
Limit the commit range of a BARCAP to halfway to the closest enemy
airbase so that they don't become offensive missions.

This has the side effect of largely reducing long retreats to hold
points from front line airfields, since the package can get much closer
without being at risk of engagement by an enemy BARCAP.

Fixes https://github.com/Khopa/dcs_liberation/issues/742.
2021-01-16 15:26:25 -08:00
Dan Albert
1a2475dc25 Add display option for BARCAP commit range. 2021-01-16 15:15:40 -08:00
Dan Albert
2374239238 Fix doctrine inversion in threat zones.
The threat zone is the zone project by the given coaltition, not against
it.
2021-01-16 14:01:53 -08:00
Simon Clark
f84d77d334 Fix startup faction error. 2021-01-14 15:30:08 +00:00
Simon Clark
5a275e6153 Missed some subs. 2021-01-13 20:45:59 +00:00
Simon Clark
5f07069f1d Remove submarines for the time being.
They definitely feel too much like placeholders at this point.
They should be removed until they're in a better state.

https://forums.eagle.ru/topic/251238-submarines is a good read on the topic.
2021-01-13 19:52:35 +00:00
Simon Clark
1e1cebc3fc Display a "No aircraft available" message.
If in the create flight dialog, there are no suitable aircraft for a task, or no aircraft left at all, a suitable message is now shown that prevents the user from creating a flight.

Also adds in a quick "remember what plane the user had selected last" feature.
2021-01-13 12:11:53 +00:00
Simon Clark
c40ad75fa2 Add DEAD to the selectable aircraft list. 2021-01-13 10:37:05 +00:00
Simon Clark
c80db72bf7 Changelog. 2021-01-12 19:03:02 +00:00
Simon Clark
727ec6bc28 Filter mission types by aircraft.
First stab at implementing #392.
You can now only select aircraft types that can do the selected task.
2021-01-12 18:58:29 +00:00
Simon Clark
9ebad734a9 Changelog. 2021-01-12 17:30:15 +00:00
Simon Clark
0094628f6b Add the S-3B Viking into the game.
It's a carrier-based strike/anti-ship platform. What's not to love?

Completes #759.
2021-01-12 14:57:29 +00:00
Simon Clark
abbb046566 Add the SH-60B Seahawk into the game.
It was requested in #759, and also carries the Penguin AShM, which could be fun.
2021-01-12 12:27:31 +00:00
Dan Albert
a1136953d0 Fix custom waypoints. 2021-01-11 20:16:23 -08:00
Dan Albert
3298a5c6ad Improve front line flight plans.
Fixes https://github.com/Khopa/dcs_liberation/issues/462
2021-01-10 15:03:42 -08:00
Simon Clark
5e24fe9bb1 Poland is now Poland again.
The Mission Editor can be tricked into giving factions any unit.
Therefore, Poland can be Poland again, and not CJTFB.
2021-01-10 13:16:40 +00:00
Simon Clark
bc5b32ddef Changelog. 2021-01-10 13:09:28 +00:00
Simon Clark
7269dbb79d Edit Flight window is now dynamically sized.
This means that the waypoint names no longer get cut off.

Contributes-to: Khopa/dcs_liberation#754
2021-01-10 13:07:02 +00:00
Simon Clark
6f5bb6ffa2 Fix HQ7 generation error. 2021-01-10 11:46:58 +00:00
root0fall
7d0b738918 fix 588 - long waypoint descriptor in kneeboard generation 2021-01-09 23:35:47 -08:00
Dan Albert
1539d9c7ed Add a 1985 East German faction. 2021-01-09 16:02:51 -08:00
Dan Albert
4ae95e06ef Add a 1985 French faction. 2021-01-09 16:02:51 -08:00
Dan Albert
fa1166d014 Limit reserve ground units to 10.
Allowing these to grow infinitely leads to some really weird behaviors
when the enemy has been buying reserves long enough, where capturing a
base might result in 80 enemy vehicles suddenly at the gates.

This is just an interim fix. Ideally these units would be reinforcing
the front line as needed
(https://github.com/Khopa/dcs_liberation/issues/382), and a CP could be
lost without needing to completely destroy the defender.
2021-01-09 13:10:21 -08:00
Dan Albert
2d9e5fe984 Fix handling of destroyed buildings. 2021-01-08 19:21:06 -08:00
Simon Clark
7ae934e940 Make new flight comboboxes auto-adjust their size.
Content was previously being cut off when the first airport selected had a shorter name than the one the player wanted.
2021-01-08 22:52:21 +00:00
Simon Clark
64b2eeface Stop loadout resetting each time the editor opens. 2021-01-08 10:22:56 +00:00
Simon Clark
558dc591a3 Made the payload editor more user-friendly.
A user can now modify the existing default loadout, rather than starting from scratch.

Also adds WIP handling for removed pylons, which looks like it may need some PyDCS work.

Also fixes the F-14B default loadouts for everything OTHER than fighter sweep again.
2021-01-08 09:42:09 +00:00
Simon Clark
0bfd766a0b Make the base capture cheat also toggleable.
Also changelog.
2021-01-07 23:04:33 +00:00
Simon Clark
7741713a7c Makes the base intel window scrollable.
Contributes-to: Khopa/dcs_liberation#691
2021-01-07 22:56:04 +00:00
Khopa
454b540bce 2.3.4 changelog hadn't been added on develop branch. 2021-01-07 21:56:07 +01:00
Simon Clark
591c62b6d5 Make frontline advance/retreat cheats optional.
This can now be toggled on and off in the cheats menu.
2021-01-07 17:46:06 +00:00
Simon Clark
fdb4a7b055 Try to prevent objectives getting the same name.
Fixes Khopa/dcs_liberation#745
2021-01-07 17:22:43 +00:00
Simon Clark
f845ad9b31 Move the OCA payload type from runway->aircraft. 2021-01-07 15:56:00 +00:00
Simon Clark
f5f33ec865 Fixes the F-14B fighter sweep loadout.
The default one just picked two external tanks, so using the CAP one.

Fixes Khopa/dcs_liberation#663
2021-01-07 15:52:24 +00:00
Simon Clark
d35faf15d7 Tuples require a comma after a single item. 2021-01-07 13:42:21 +00:00
Simon Clark
7085bce6d4 Add support for some extra loadout types.
Also fixes the bug that @Starfire13 spotted in Khopa/dcs_liberation#744.
2021-01-07 13:37:16 +00:00
Simon Clark
a81890e844 Changelog. 2021-01-07 12:35:08 +00:00
Simon Clark
8dbec21b02 Adds Iran 1988 Faction.
Also adds the SA342 Gazelle to Iraq, as they used them.

Adds associated pretty names.
2021-01-07 12:34:35 +00:00
Simon Clark
a654c8229a Make the default loadout code more resilient.
It can now cope with the payload overrides defined in the db.
2021-01-07 11:31:46 +00:00
Simon Clark
f2e35c185b Displays the default loadout in the payload UI.
The default loadout is now displayed whenever the custom loadout checkbox is unchecked.

The custom loadout is reset when the custom loadout is checked, to force the user to be prescriptive about the loadout.

Contributes-to: Khopa/dcs_liberation#725
2021-01-07 01:45:45 -08:00
Simon Clark
062c2643ad Adds rockets/hellfires to Apaches where sensible. 2021-01-06 17:18:13 -08:00
Simon Clark
088c7b35ba Adds more strategic & maritime bombers for Russia.
Also adds the prettified name for the Tu-142.
2021-01-06 17:16:03 -08:00
walterroach
ef439a6c42 Merge pull request #736 from SimonC6R/fix-carrier-sam-rings
Carrier group threat rings move with the carrier.
2021-01-06 17:24:15 -06:00
Dan Albert
40956a4042 Merge pull request #738 from SimonC6R/remove-pyotr-velikiy
Remove Pyotr Velikiy from the generator for now.
2021-01-06 12:43:59 -08:00
Simon Clark
8680e90e3b Changelog. 2021-01-06 20:42:10 +00:00
Simon Clark
fac770424c Remove Pyotr Velikiy from the generator for now.
It's just too strong to be killed by a reasonable number of Harpoons. DCS needs a fixed ship damage model first.

Contributes-to: Khopa/dcs_liberation#567
2021-01-06 20:39:50 +00:00
Simon Clark
042be9da6d Carrier group threat rings move with the carrier.
Previously, the individual units in the group were moved, but the ground_unit object was not.

Fixes: Khopa/dcs_liberation#735
2021-01-06 18:58:20 +00:00
Khopa
81b0ea1eef Updated AIM-7 config to account for the Belly and Shoulder versions of the AIM-7 used on the F-14. Also Downgrade AIM-54C to AIM-54A before 86 on the Tomcat. 2021-01-06 01:27:53 +01:00
Khopa
52289d1283 More fallbacks weapons for popular precision air to ground weapons 2021-01-06 00:56:20 +01:00
Khopa
1843d23203 Merge remote-tracking branch 'khopa/develop' into develop 2021-01-06 00:54:42 +01:00
Khopa
1a32fef987 More fallbacks weapons for popular precision air to ground weapons 2021-01-06 00:53:55 +01:00
Simon Clark
c740c8304b Adds prettier user-facing aircraft names. (#726)
This makes the names of the aircraft displayed to the player in the UI more verbose and readable.

It allows allows specific countries to display an aircraft's name differently. An example of this would be the JF-17 Thunder, which is known in China as the FC-1 Fierce Dragon - this now displays correctly in the Liberation UI.
2021-01-05 13:21:38 -08:00
Khopa
c3401d478b Added R-73 -> R60 to weapons fallback data 2021-01-05 21:40:20 +01:00
Khopa
cf583bcd55 Merge branch 'master' into develop
# Conflicts:
#	changelog.md
#	game/db.py
#	game/game.py
#	game/income.py
#	game/theater/theatergroundobject.py
#	game/version.py
#	qt_ui/windows/finances/QFinancesMenu.py
2021-01-05 19:50:29 +01:00
Simon Clark
ef143a7ebb Changelog update. 2021-01-05 03:34:10 -08:00
SimonC6R
7aec483e73 Also add the E-2C to Israel and Japan.
Israel only got it in 1981 but it's that or the E-3A they never used.

Japan never used the E-3A, but did buy the E-2.
2021-01-05 02:36:08 -08:00
SimonC6R
db6a3b9849 Use E-2C Hawkeye for US faction between 1964-1977. 2021-01-05 02:36:08 -08:00
Dan Albert
657c5e1f52 Merge pull request #712 from SimonC6R/add-greece-2005
Adds a 2005-ish Greece faction.
2021-01-05 02:34:52 -08:00
Simon Clark
3b0466d7cb Adds a 2010-ish Poland faction. (#713)
The SU-17M4 represents the remaining Su-22s that Poland still flies.
I've used the Stryker as a replacement for the KTO Rosomak, which was in service as far back as 2007.
There were a few Molniyas still in service as of 2010, namely the Metalowiec and Rolnik, as well as a singular Kilo class.
2021-01-05 02:31:49 -08:00
Dan Albert
8a9177b459 Merge pull request #716 from SimonC6R/sold-aircraft-state
Adds a buffer for sold aircraft/vehicles.
2021-01-05 02:12:49 -08:00
Simon Clark
a0e63511d6 Update Greece faction with comments. 2021-01-05 10:12:46 +00:00
Simon Clark
7c3f7d4b8e Addresses review comments. 2021-01-05 09:42:05 +00:00
Dan Albert
be1062c373 Update era-specific loadout release notes. 2021-01-05 00:54:28 -08:00
Dan Albert
2bd5ab06a7 More weapon data.
https://github.com/Khopa/dcs_liberation/issues/490
2021-01-05 00:43:59 -08:00
Dan Albert
e174c1b147 Fix bug in weapon downgrading.
We want the *first* valid replacement, not the last one :)

https://github.com/Khopa/dcs_liberation/issues/490
2021-01-05 00:26:19 -08:00
Dan Albert
7ef191be2a Add more weapon data.
Finishes AIM-120 and AIM-9X.

https://github.com/Khopa/dcs_liberation/issues/490
2021-01-05 00:25:37 -08:00
SimonC6R
03a29aeedf Type annotations. 2021-01-05 00:31:54 +00:00
SimonC6R
d10b4c1e13 Re-add whitespace. 2021-01-05 00:27:26 +00:00
SimonC6R
ab2046a2c2 Refactor the sell unit changes as requested.
It works more simply now, and also doesn't immediately sell the unit.

Also adds a matching UI dialog popup for selling too many ground units.
2021-01-05 00:26:40 +00:00
Dan Albert
bc6b2e0f3e Add warning for missing weapon data.
These are disabled by default because it would otherwise warn about
nearly every piece of equipment in the game currently.
2021-01-04 15:57:37 -08:00
Dan Albert
746c99ebd6 Update Skynet to 2.0.1.
Fixes https://github.com/Khopa/dcs_liberation/issues/717
2021-01-04 15:36:18 -08:00
Dan Albert
34945e7eba Add date-based loadout restriction.
Follow up work:

* Data entry. I plan to do the air-to-air missiles in the near term. I
  covered some variants of the AIM-120, AIM-7, and AIM-9 here, but there
  are variants of those weapons for each mounting rack that need to be
  done still, as well as all the non-US weapons.
* Arbitrary start dates.

https://github.com/Khopa/dcs_liberation/issues/490
2021-01-04 15:13:40 -08:00
Dan Albert
507b217065 Clean up custom loadout interface.
Wraps the pydcs data in a real type so we don't need to spread the
reflection all over.
2021-01-04 15:13:40 -08:00
Simon Clark
e222f17199 Adds payload and EWRS info for J-11A. (#715)
No loadout had been specified for the J-11A, so I've added one. The J-11A carries a similar loadout to the Su-27, but with R-77s instead of R-27ERs.

Also looks like it was missing from the EWRS details, so I've added it there too.

Addresses Khopa/dcs_liberation#610
Addresses Khopa/dcs_liberation#671
2021-01-04 13:07:07 -08:00
Simon Clark
144cfecc0f Adds the E-2C Hawkeye to the game. (#714)
* Adds the E-2C Hawkeye to the game.

It wasn't being imported from pydcs, and thus wasn't in the list of AWACS aircraft or prices.

Also adds it to the 1985 US Navy list, as that makes sense to do.

Updates .gitignore to ignore my VS Code settings file.

Addresses Khopa/dcs_liberation#709
2021-01-04 13:04:11 -08:00
Simon Clark
64066bfc90 Merge branch 'develop' of https://github.com/Khopa/dcs_liberation into develop 2021-01-04 19:27:36 +00:00
Simon Clark
366ac4ee14 Adds a buffer for sold aircraft/vehicles.
This feature allows you to cancel the sales of aircraft or ground vehicles if needed.

Upon clicking the minus button, a count of sold units will be appended to the unit count. This count responds to both further presses of the minus button, and also to presses of the plus button. No further units will be requested for the next turn until all sold units have been re-bought.

I've tested a bunch of different scenarios with this:

- Selling and rebuying a unit - the budget increases and decreases as expected.

- Selling one unit, buying a unit worth the new player budget, and then trying to rebuy the old unit - the old unit cannot be rebought until the budget has been freed up for it.

- Closing the base window and re-opening it - the sold unit count is retained.

- Ending the turn - the sold unit count is reset back to 0 as expected.

Contributes to Khopa/dcs_liberation#365
2021-01-04 19:27:29 +00:00
walterroach
851984ee66 Revert TGO ID reset - fixes #708 2021-01-04 11:36:40 -06:00
Dan Albert
34bdc0e80b Fix new game creation. 2021-01-01 16:15:43 -08:00
Dan Albert
61d7d5e041 Update minimum version of pyside2.
Fixes https://github.com/Khopa/dcs_liberation/issues/416
2021-01-01 15:50:01 -08:00
Dan Albert
b5278550e7 Retreat air units from captured bases.
If there are not airbases withing ferry range with available parking for
the aircraft then the aircraft will be captured and sold. Otherwise the
aircraft will retreat to the closest available airbase.

Fixes https://github.com/Khopa/dcs_liberation/issues/693
2021-01-01 15:19:16 -08:00
Dan Albert
461635c001 Note pending unit refund in changelog. 2021-01-01 14:46:51 -08:00
Dan Albert
fcb1d8e104 Retreat ground forces from captured bases.
If the captured base has no connection to other friendly objectives (the
base was encircled) then the enemy equipment will be captured and sold.
Otherwise the units will retreat toward connected bases.

Partial fix for https://github.com/Khopa/dcs_liberation/issues/693.
Still need to fix this for air units.
2021-01-01 14:45:43 -08:00
Dan Albert
6cbc2b707a Fix type of Game.budget.
This *is* a float, since the income multiplier is a float. The type
annotations were wrong.
2021-01-01 13:56:33 -08:00
Dan Albert
9671542bdf Decouple unit deliveries and conflict events.
Fixes https://github.com/Khopa/dcs_liberation/issues/692 and lets us
clean up the interface quite a bit.
2021-01-01 13:48:23 -08:00
Dan Albert
de325c1208 Refunding unfulfilled orders on capture.
Fixes https://github.com/Khopa/dcs_liberation/issues/682
2021-01-01 13:23:58 -08:00
Dan Albert
802eff1faa Fix some airfield data in Persian Gulf.
Not sure if this changed or has just always been wrong.
2021-01-01 13:22:33 -08:00
C. Perreau
068f9e42d7 Merge pull request #698 from Khopa/develop_2_3_x
Release 2.3.4
2020-12-31 14:07:30 +01:00
Khopa
4a483c3b27 Changelog 2020-12-31 13:52:02 +01:00
Khopa
e3bd958069 Fixed possible AttributeError when generating missile site fire tasks 2020-12-31 13:28:21 +01:00
Khopa
900cf0a9d0 Update preview version number 2020-12-31 13:16:09 +01:00
C. Perreau
64c424b9a6 Merge pull request #649 from Khopa/develop_2_3_x
Release 2.3.3
2020-12-31 01:26:19 +01:00
Dan Albert
5734c29312 Remove wrong precondition in aircraft procurement.
Those task types aren't correct here (that whole dict probably serves
little purpose now), and the actual unit pool is handled in
_affordable_aircraft_of_types.
2020-12-28 16:06:54 -08:00
walterroach
362caa6ac1 Merge pull request #689 from walterroach/naming
Naming
2020-12-28 17:22:42 -06:00
Dan Albert
b620976b70 Flesh out Desert War IADS, add carriers. 2020-12-28 13:16:39 -08:00
walterroach
daba4ef09e Fix aircraft group IDs not being reproducible. 2020-12-28 14:04:19 -06:00
walterroach
c697a34239 Correctly reset ANIMALS 2020-12-28 11:31:04 -06:00
walterroach
09b7cb3d85 Change naming to static class
This ensures all generators are using the same ID set.
2020-12-28 11:22:28 -06:00
walterroach
d7e48662e0 Add custom flight names
Mildly breaks save compat with 2.3; All existing flight dialogs will be
broken, passing the turn or recreating all the flights in the UI will
allow you to continue
2020-12-28 10:52:25 -06:00
walterroach
9fd5c6f230 Add package info to aircraft names
Makes it easier to identify aircraft for client flights
2020-12-28 09:51:37 -06:00
walterroach
aa7825d4aa Reproducible unit naming
Splits infantry and other unit IDs.

Resets IDs to start from zero at each press of "Takeoff"

Direct access to the the `Game` class IDs is done on the `Operation` class to
preserve save compat
2020-12-28 09:27:33 -06:00
Dan Albert
436725b38e Don't award base capture bonuses.
If anything we should be granting the loser extra income so they can
recover. For now just remove.
2020-12-27 19:52:42 -08:00
Dan Albert
922d935bc1 Tweak default budget and turn 0 allocation.
AI wasn't buying many ground vehicles. Keep the turn 0 air budget about
where it was but bump up the ground budget from ~120M to ~600M.
2020-12-27 19:48:03 -08:00
Dan Albert
3716395453 Don't show FOB structure as a target.
This isn't perfect because the auto planner might still target it. We
need a larger refactoring for target iteration so we don't need to
remember all the special rules at each call site. For now, this restores
the 2.3.2 behavior.

Fixes https://github.com/Khopa/dcs_liberation/issues/681

(cherry picked from commit 69833f66e3)
2020-12-27 19:35:45 -08:00
Dan Albert
69833f66e3 Don't show FOB structure as a target.
This isn't perfect because the auto planner might still target it. We
need a larger refactoring for target iteration so we don't need to
remember all the special rules at each call site. For now, this restores
the 2.3.2 behavior.

Fixes https://github.com/Khopa/dcs_liberation/issues/681
2020-12-27 19:34:10 -08:00
Dan Albert
ec787b913c Use exact name matching when picking targets.
Inexact name matching targets the first group that partially matches the
given group name. aa|71 will match aa|7.

The inexact match that was here was only needed for an early attempt to
use skynet where group names were not used consistently. That's no
longer a problem so we don't need this workaround.

Fixes https://github.com/Khopa/dcs_liberation/issues/676

(cherry picked from commit 89f313295e)
2020-12-27 13:50:55 -08:00
Dan Albert
89f313295e Use exact name matching when picking targets.
Inexact name matching targets the first group that partially matches the
given group name. aa|71 will match aa|7.

The inexact match that was here was only needed for an early attempt to
use skynet where group names were not used consistently. That's no
longer a problem so we don't need this workaround.

Fixes https://github.com/Khopa/dcs_liberation/issues/676
2020-12-27 13:45:53 -08:00
Dan Albert
7bc7a44c72 Allow managing disbanded sites in CPs.
Fixes https://github.com/Khopa/dcs_liberation/issues/679

(cherry picked from commit 317a882386)
2020-12-27 13:09:15 -08:00
Dan Albert
317a882386 Allow managing disbanded sites in CPs.
Fixes https://github.com/Khopa/dcs_liberation/issues/679
2020-12-27 13:08:59 -08:00
Dan Albert
3a9f585b6b Plan multiple CAP rounds per turn.
On station time for CAP is only 30 minutes, so plan three cycles to give
~90 minutes of CAP coverage.

Default starting budget has increased significantly to account for the
greatly increased aircraft needs on turn 1.

Fixes https://github.com/Khopa/dcs_liberation/issues/673
2020-12-26 17:26:07 -08:00
Dan Albert
7bbb1c0822 Don't show repaired TGOs as dead.
We were never resetting the dead state for repaired SAMs. Rather than
tracking that manually, determine liveness from the number of units left
alive.

For building objectives where the group is not assigned to the TGO until
*mission* generation time we still need manual tracking.

(cherry picked from commit d3b1f6110f)
2020-12-26 16:21:31 -08:00
Dan Albert
d3b1f6110f Don't show repaired TGOs as dead.
We were never resetting the dead state for repaired SAMs. Rather than
tracking that manually, determine liveness from the number of units left
alive.

For building objectives where the group is not assigned to the TGO until
*mission* generation time we still need manual tracking.
2020-12-26 16:19:41 -08:00
Dan Albert
a6dc3d2aff Handle threat/detection per group.
Some SAMs have multiple groups (such as an SA-10 group with its
accompanying SA-15 and SA-19 groups). This shows each group's threat and
detection separately on the map, and also makes it so that an SA-10 with
dead radars will no longer contribute to the threat zone just because
the shilka next to it still has a functioning radar.

https://github.com/Khopa/dcs_liberation/issues/647
Fixes https://github.com/Khopa/dcs_liberation/issues/672
2020-12-26 15:52:36 -08:00
Dan Albert
d946a9e526 Don't count blind SAMs as threats.
https://github.com/Khopa/dcs_liberation/issues/647
2020-12-26 15:40:38 -08:00
Dan Albert
17dd1b193e Remove aa_ranges in favor of using the TGO data. 2020-12-26 15:25:23 -08:00
Dan Albert
d634fd3236 Adjust income based on control point type.
* Navies and off map spawns generate no income
* FOBs generate 10 instead of 20

Fixes https://github.com/Khopa/dcs_liberation/issues/662
2020-12-26 15:08:57 -08:00
Dan Albert
e861e5b3d6 Even out player and opfor income rules.
Should help fix the anemic enemy forces after the first few turns.
2020-12-26 14:35:57 -08:00
Dan Albert
6045f4dd91 Fix New Package for naval control points.
Also reordered the tasks so ship-specific tasks appear first.

Fixes https://github.com/Khopa/dcs_liberation/issues/628

(cherry picked from commit 8be2841bdf)
2020-12-26 14:30:34 -08:00
Dan Albert
8be2841bdf Fix New Package for naval control points.
Also reordered the tasks so ship-specific tasks appear first.

Fixes https://github.com/Khopa/dcs_liberation/issues/628
2020-12-26 14:30:26 -08:00
Dan Albert
b6e37b9e67 Exclude non-AA groups from SAM threat zones.
Fixes https://github.com/Khopa/dcs_liberation/issues/666
2020-12-26 14:00:31 -08:00
walterroach
0d0d582bd8 Add F-14A-135-GR Icon 2020-12-26 13:24:45 -06:00
walterroach
0c42227e5e Add F-14A-135-GR Icon 2020-12-26 13:23:45 -06:00
Dan Albert
98ac4bd5c8 Fix generator -> group conversion when buying SAM.
Fixes https://github.com/Khopa/dcs_liberation/issues/664
Fixes https://github.com/Khopa/dcs_liberation/issues/665
2020-12-26 02:23:15 -08:00
Dan Albert
a43b100781 Purchase reserves for CAP/CAS.
Next turn's defenses should be planned in preference to expanding
offensive capabilities.

Fixes https://github.com/Khopa/dcs_liberation/issues/511
2020-12-25 19:15:31 -08:00
Dan Albert
c7f9bfbb43 Sell off incomplete opfor squadrons.
Short term fix for https://github.com/Khopa/dcs_liberation/issues/41.
2020-12-25 18:45:58 -08:00
Dan Albert
b5f8e6925b Rank aircraft purchase preferences.
Rather than randomly selecting compatible aircraft for missions, perfer
the *best* aircraft for the job. This removes the "preferred" lists in
favor of sorting the capable lists in priority order. To maintain some
amount of variety the procurer has a 50/50 chance of buying when it
finds a match.

Fixes https://github.com/Khopa/dcs_liberation/issues/510
2020-12-25 18:22:50 -08:00
Dan Albert
993e59413a Add pretty-print for AircraftProcurementRequest. 2020-12-25 17:31:26 -08:00
Dan Albert
9f2fab78a1 Flesh out intel displays.
* Add enemy air and ground unit reports.
* Changes the summary to be a comparison of relative strengths rather
  than raw enemy numbers.

Fixes https://github.com/Khopa/dcs_liberation/issues/658

(cherry picked from commit 3bdf1377c0)
2020-12-25 16:08:10 -08:00
Dan Albert
3bdf1377c0 Flesh out intel displays.
* Add enemy air and ground unit reports.
* Changes the summary to be a comparison of relative strengths rather
  than raw enemy numbers.

Fixes https://github.com/Khopa/dcs_liberation/issues/658
2020-12-25 16:08:02 -08:00
Dan Albert
8f24cf07be Improve layout of intel window.
(cherry picked from commit 1f4516b954)
2020-12-25 16:05:04 -08:00
Dan Albert
17c40234e9 Add basic intel window.
Currently only shows the enemy's economic information.

https://github.com/Khopa/dcs_liberation/issues/658
(cherry picked from commit 1d76ee4871)
2020-12-25 16:04:54 -08:00
Dan Albert
4cecddcdd0 Add basic enemy intel widget.
https://github.com/Khopa/dcs_liberation/issues/658
(cherry picked from commit b53cac4c7a)
2020-12-25 16:04:17 -08:00
Dan Albert
1f4516b954 Improve layout of intel window. 2020-12-25 14:42:04 -08:00
Dan Albert
1d76ee4871 Add basic intel window.
Currently only shows the enemy's economic information.

https://github.com/Khopa/dcs_liberation/issues/658
2020-12-25 03:24:33 -08:00
Dan Albert
b53cac4c7a Add basic enemy intel widget.
https://github.com/Khopa/dcs_liberation/issues/658
2020-12-25 01:38:14 -08:00
Dan Albert
29a0644719 Never generate empty ship groups.
Fixes https://github.com/Khopa/dcs_liberation/issues/391

(cherry picked from commit c833078e71)
2020-12-25 01:35:24 -08:00
Dan Albert
c833078e71 Never generate empty ship groups.
Fixes https://github.com/Khopa/dcs_liberation/issues/391
2020-12-25 01:32:54 -08:00
Dan Albert
e4cba8d19f Mention Skynet PD in changelog. 2020-12-24 19:32:31 -08:00
Dan Albert
cd6620712f Configure skynet point defenses.
Fixes https://github.com/Khopa/dcs_liberation/issues/470
2020-12-24 17:10:32 -08:00
Dan Albert
85619b156d Support groups for SAM templates.
It's only possible to control emissions for the group as a whole, so
Skynet needs PDs to be in separate groups from the main part of the SAM
for PD to operate correctly.

https://github.com/Khopa/dcs_liberation/issues/429
https://github.com/Khopa/dcs_liberation/issues/470
2020-12-24 16:09:42 -08:00
Dan Albert
10debbc286 Constrain front lines better.
Holes in the inclusion zone are defined by exclusion zones, not by holes
in the inclusion zone. Add a cached property for the inclusion zone that
is not also sea or exclusion zone and use that boundary instead.
2020-12-24 13:50:29 -08:00
Khopa
dcac5b488a Changelog update 2020-12-24 16:11:12 +01:00
Khopa
e1009bdafa Fixed ships group that could be replaced by sam site. Removed the possibility to disband ship groups for now. 2020-12-24 16:09:13 +01:00
Khopa
38ce842ca8 Merge remote-tracking branch 'khopa/master' into develop_2_3_x
# Conflicts:
#	changelog.md
#	game/version.py
#	gen/ground_forces/ai_ground_planner.py
#	pydcs
#	resources/factions/iraq_1991.json
#	resources/factions/russia_2010.json
2020-12-24 13:32:14 +01:00
Dan Albert
aafd09569c Fix mypy error. 2020-12-24 02:20:49 -08:00
Dan Albert
67a9df686e Add fast path for NavPoint equality.
Hot method and the FFI costs for comparing the points are not cheap.
2020-12-24 02:06:08 -08:00
Dan Albert
9a374711fd Don't access point coordinates when hashing.
For some reason this is crazy expensive. Turn time goes from 1.7 seconds
to 1 second with this change.
2020-12-24 01:49:07 -08:00
Dan Albert
b9138acbc8 Use shapely projection instead of brute force.
Converts the landmap to use MultiPolygon instead of a collection of
polygons, since Shapely has explicit support for this.

Because we've done that, we can use a single projection from a line
instead of brute forcing the extent of the front line.

This makes turn processing ~66% faster (3 seconds to 1.8).

There are probably other places this should be used.
2020-12-24 01:20:15 -08:00
Dan Albert
2a65916f7c Log time taken in turn processing. 2020-12-24 00:53:36 -08:00
Dan Albert
6aa1f1cca0 Prefer buying aircraft at safe airbases.
Fixes https://github.com/Khopa/dcs_liberation/issues/652
2020-12-23 22:25:47 -08:00
Dan Albert
8c1ebfda02 Show ASAP TOT for debug view flights. 2020-12-23 21:42:13 -08:00
Dan Albert
81af5d7497 Use navmesh to plan strike-like flight plans.
The cases where the target is extremely close to the origin point still
use the old flight plan pattern. This is probably fine.

https://github.com/Khopa/dcs_liberation/issues/292
2020-12-23 21:30:36 -08:00
Khopa
368bf08ade Fixed mypy error 2020-12-24 03:18:53 +01:00
Dan Albert
d95f623ca9 Use navmesh to plan sweep missions.
https://github.com/Khopa/dcs_liberation/issues/292
2020-12-23 18:08:58 -08:00
Dan Albert
2856fbc42b Improve display of nav points in kneeboard. 2020-12-23 17:52:14 -08:00
Dan Albert
ac59e15bd9 Use navmesh to plan CAS and BARCAP.
https://github.com/Khopa/dcs_liberation/issues/292
2020-12-23 17:40:59 -08:00
Dan Albert
91d9bbdc97 Add visual debugging for other fligth plans. 2020-12-23 17:31:04 -08:00
Dan Albert
575f4e1786 Clean up debug display options. 2020-12-23 17:19:57 -08:00
Dan Albert
bff905fae5 Use navmeshes to improve TARCAP flight plans.
Started with TARCAP because they're simple, but will follow and extend
this to the other flight plans next.

This works by building navigation meshes (navmeshes) of the theater
based on the threat regions. A navmesh is created for each faction to
allow the unique pathing around each side's threats. Navmeshes are built
such that there are nav edges around threat zones to allow the planner
to pick waypoints that (slightly) route around threats before
approaching the target.

Using the navmesh, routes are found using A*. Performance appears
adequate, and could probably be improved with a cache if needed since
the small number of origin points means many flights will share portions
of their flight paths.

This adds a few visual debugging tools to the map. They're disabled by
default, but changing the local `debug` variable in `DisplayOptions` to
`True` will make them appear in the display options menu. These are:

* Display navmeshes (red and blue). Displaying either navmesh will draw
  each navmesh polygon on the map view and highlight the mesh that
  contains the cursor. Neighbors are indicated by a small yellow line
  pointing from the center of the polygon's edge/vertext that is shared
  with its neighbor toward the centroid of the zone.
* Shortest path from control point to mouse location. The first control
  point for the selected faction is arbitrarily selected, and the
  shortest path from that control point to the mouse cursor will be
  drawn on the map.
* TARCAP plan near mouse location. A TARCAP will be planned from the
  faction's first control point to the target nearest the mouse cursor.

https://github.com/Khopa/dcs_liberation/issues/292
2020-12-23 17:09:34 -08:00
Khopa
c0fa135bf6 Artillery groups would retreat in the wrong direction - fixed (parameters of the find_retreat_point function are a bit confusing 😕 ) 2020-12-24 02:03:12 +01:00
Khopa
86394d8f19 Artillery groups would retreat in the wrong direction - fixed (parameters of the retreat point function are a bit confusing 😕 ) 2020-12-24 02:02:17 +01:00
Khopa
72c233cb0d Fixed possible assertion error when redeploying units which would lead to ground units not being redeployed. 2020-12-24 01:47:44 +01:00
Khopa
04e2c02eff SCUD missile sites will fire on nearest enemy airport by default 2020-12-24 01:26:00 +01:00
Khopa
7362744df2 Changelog update 2020-12-23 22:15:56 +01:00
Khopa
01951b5c32 Reworked emirates campaign 2020-12-23 21:58:39 +01:00
Khopa
f2f52771bd Removed "broken" midgame setting 2020-12-23 21:37:59 +01:00
Khopa
b59167d3ca Changelog update, WW2 factions can recruit AA/AT guns for frontlines. 2020-12-23 18:21:13 +01:00
Khopa
88e466562c Infantry squads can contain a mortar. 2020-12-23 17:53:52 +01:00
Khopa
1f85e5d7f8 Changelog update 2020-12-23 17:25:15 +01:00
Khopa
50471d510e Fixed and added many ground units icons 2020-12-23 17:24:20 +01:00
Khopa
8b7cf2f725 Changelog update 2020-12-23 01:35:19 +01:00
Khopa
282a5109ba Infantry group are always made of 5 units instead of a random amount. 2020-12-23 01:33:49 +01:00
Khopa
3d3b4738d9 Insurgent hard faction name fixed 2020-12-23 01:31:07 +01:00
Khopa
66149bb591 Fixed error in merge 2020-12-22 23:34:08 +01:00
Khopa
b0ad664ece Merge branch 'develop_2_3_x' into develop
# Conflicts:
#	changelog.md
#	game/procurement.py
#	resources/factions/iraq_1991.json
2020-12-22 23:32:06 +01:00
Khopa
7c29ea836c Infantry is only generated for IFV and APC groups 2020-12-22 23:24:27 +01:00
Khopa
92e9e8c56a Merge remote-tracking branch 'khopa/develop_2_3_x' into develop_2_3_x 2020-12-22 23:23:52 +01:00
Khopa
12bf26223d Added shorad units on frontline 2020-12-22 23:23:32 +01:00
Dan Albert
56d7993c8f Improve threat zone display options. 2020-12-22 13:57:05 -08:00
Dan Albert
52b63927b4 Prune escorts from packages that don't need them.
If the package is not flying into the threat zones of significant air
defenses there's no need for SEAD, and packages not near enemy airbases
do not need escorts. Prune these flights from the package to save
aircraft.
2020-12-22 13:12:04 -08:00
Dan Albert
86558bdef6 Add threat zone modeling.
Creates threat zones around airfields and non-trivial air defenses (it's
not worth dodging anything with a threat range under 3nm). These threat
zones can be used to aid mission planning and waypoint placement.

https://github.com/Khopa/dcs_liberation/issues/292
2020-12-22 13:12:04 -08:00
Dan Albert
e46262b021 Move has_radar into the TGO. 2020-12-22 12:59:29 -08:00
Dan Albert
c53feb5ccb Specify CAP engagement range in the doctrine. 2020-12-22 12:42:36 -08:00
Dan Albert
fc6d4f0990 Add EWRS plugin.
Fixes https://github.com/Khopa/dcs_liberation/issues/323
2020-12-21 21:28:27 +01:00
C. Perreau
df948bde9d Update CONTRIBUTING.md 2020-12-21 14:14:42 +01:00
C. Perreau
203a720ae1 Create CONTRIBUTING.md 2020-12-21 14:14:39 +01:00
C. Perreau
3410f08cfb Create CODE_OF_CONDUCT.md 2020-12-21 14:14:37 +01:00
C. Perreau
a553914ef4 Merge pull request #625 from Khopa/contributing
Create CONTRIBUTING.md
2020-12-21 14:13:29 +01:00
Khopa
21220141f2 pydcs submodule version update 2020-12-21 14:08:21 +01:00
Khopa
caf2d8436b Changelog update for 2.3.3 2020-12-21 13:56:00 +01:00
Khopa
4cc305fa81 Syrian civil war description updated 2020-12-21 13:55:33 +01:00
Khopa
60f837d0b9 Fixed : AI wouldn't buy artillery units 2020-12-21 13:34:56 +01:00
C. Perreau
05bd7f8e6b Update CONTRIBUTING.md 2020-12-21 13:04:16 +01:00
Emanuele Garofalo
e58ab34a15 Update NATO_Desert_Storm.json 2020-12-21 13:02:01 +01:00
Emanuele Garofalo
d960758ef3 Update iraq_1991.json 2020-12-21 13:01:58 +01:00
Emanuele Garofalo
a36ccdcc39 Update NATO_Desert_Storm.json 2020-12-21 13:01:57 +01:00
Emanuele Garofalo
d582948377 Update NATO_Desert_Storm.json 2020-12-21 03:31:19 -08:00
Emanuele Garofalo
d806e0b1c3 Update iraq_1991.json 2020-12-21 03:31:19 -08:00
Emanuele Garofalo
b9e110a7e3 Update NATO_Desert_Storm.json 2020-12-21 03:31:19 -08:00
Dan Albert
a2f218d56d Add EWRS plugin.
Fixes https://github.com/Khopa/dcs_liberation/issues/323
2020-12-21 01:19:54 -08:00
Khopa
2c475011a1 Syria terrain update + syrian civil war reworked in miz format 2020-12-21 03:19:17 +01:00
walterroach
2d7fc33726 Fix Distance being passed to pydcs methods 2020-12-20 17:19:49 -06:00
Dan Albert
0c8d1e1dc4 Remove checkbox from feature request form. 2020-12-20 14:48:54 -08:00
Khopa
bb04ce2abb Golan heights battle scenario fully migrated to miz format 2020-12-20 22:39:15 +01:00
Khopa
9850b22c0a Improved "Golan Heights Campaign Lite" for the Syria map. 2020-12-20 14:56:55 +01:00
C. Perreau
a2f65666a5 Create CONTRIBUTING.md 2020-12-20 14:20:18 +01:00
C. Perreau
7730809dbb Merge pull request #619 from Khopa/add-code-of-conduct-1
Add code of conduct
2020-12-20 13:45:25 +01:00
Dan Albert
2ac818dcdd Convert to new unit APIs, remove old APIs.
There are probably plenty of raw ints around that never used the old
conversion APIs, but we'll just need to fix those when we see them.

Fixes https://github.com/Khopa/dcs_liberation/issues/558
2020-12-19 22:08:57 -08:00
Dan Albert
113947b9f0 Add types for distance and speed.
Not converting all at once so I can prove the concept. After that we'll
want to cover all the cases where an int distance or speed is a part of
the save game (I've done one of them here with `Flight.alt`) so further
cleanups don't break save compat.

https://github.com/Khopa/dcs_liberation/issues/558
2020-12-19 21:07:55 -08:00
walterroach
44bc2d769b Merge branch 'develop_2_3_x' into develop 2020-12-19 20:27:31 -06:00
Khopa
02ecfebb85 Merge remote-tracking branch 'khopa/develop_2_3_x' into develop_2_3_x 2020-12-19 23:54:28 +01:00
Khopa
a1fed62591 Added "Golan Heights Campaign Lite" for the Syria map. 2020-12-19 23:54:02 +01:00
Dan Albert
778ed6ad91 Update 2.3 branch to 2.3.3 preview. 2020-12-19 13:48:44 -08:00
Dan Albert
7d539f5810 Remove pyproj from requirements.txt.
Not actually used.
2020-12-19 12:44:12 -08:00
Dan Albert
b407acbc07 Add section for 2.4 changelog. 2020-12-19 12:36:32 -08:00
Dan Albert
3260260dce Move more SAMs off runways in Syria Full.
(cherry picked from commit e5bca224e9)
2020-12-19 12:30:38 -08:00
Dan Albert
70c1290993 Note fixed SAM placement in changelog. 2020-12-19 12:30:38 -08:00
Dan Albert
6bae60c51e Note Iraq 1991 faction in changelog.
(cherry picked from commit 8447c563ea)
2020-12-19 12:30:38 -08:00
Emanuele Garofalo
a45adb6b3a New Faction Iraq 1991
(cherry picked from commit fd61a4b23a)
2020-12-19 12:30:38 -08:00
Dan Albert
476aaf5d3e Update changelog for #616.
(cherry picked from commit 3e4bb88089)
2020-12-19 12:30:38 -08:00
Dan Albert
58187b6969 Put back code to reserve beacon frequencies.
Fixes https://github.com/Khopa/dcs_liberation/issues/616

(cherry picked from commit 2f3f53a978)
2020-12-19 12:30:38 -08:00
Dan Albert
f3a3d81d96 Update beacon data.
(cherry picked from commit 89755b1005)
2020-12-19 12:30:38 -08:00
Dan Albert
7a40b54153 Update beacon importer to handle TheChannel.
TheChannel doesn't have message catalogs for English.

(cherry picked from commit a7203ea90a)
2020-12-19 12:30:38 -08:00
Dan Albert
9dd62d3538 Improve TOT planning.
Moves all TOT planning into the FlightPlan to clean up specialized
behavior and improve planning characteristics.

The important part of this change is that flights are now planning to
the mission time for their flight rather than the package as a whole.
For example, a TARCAP is planned based on the time from startup to the
patrol point, a sweep is planned based on the time from startup to the
sween end point, and a strike flight is planned based on the time from
startup to the target location. TOT offsets can be handled within the
flight plan.

As another benefit of theis cleanup, flights without hold points no
longer account for the hold time in their planning, so those flights are
planned to reach their targets sooner.

As a follow up TotEstimator can be removed, but I want to keep this low
impact for 2.3.2.

Fixes https://github.com/Khopa/dcs_liberation/issues/593

(cherry picked from commit 745dfc71bc)
2020-12-19 12:30:38 -08:00
walterroach
76e4a6ed83 Fix bad air defense location #617 2020-12-19 12:30:38 -08:00
Dan Albert
7a9eb06677 Add missing 2.3.2 change to changelog.
(cherry picked from commit 4eac743812)
2020-12-19 12:30:38 -08:00
Dan Albert
26f54e7619 Fix adding custom waypoints.
Fixes https://github.com/Khopa/dcs_liberation/issues/604

(cherry picked from commit 296e6e8e8f)
2020-12-19 12:30:38 -08:00
Khopa
117b7ae414 Changelog update 2020-12-19 12:30:38 -08:00
Khopa
baeac324d6 Updated version string 2020-12-19 12:30:38 -08:00
Khopa
0db0f003dc Added ZSU-57 sites 2020-12-19 12:30:38 -08:00
Khopa
2d4f341710 T72B3 and BTR-82A support 2020-12-19 12:30:38 -08:00
Khopa
b8a41dc937 pydcs version update to include new data export 2020-12-19 12:30:38 -08:00
walterroach
2f2bb0de4f Fix Ground units ... don't move forward #601 2020-12-19 12:30:38 -08:00
Dan Albert
3b76d7f47e Fail gracefully when out of radio channels.
Fixes https://github.com/Khopa/dcs_liberation/issues/598

(cherry picked from commit 498af28efb)
2020-12-19 12:30:38 -08:00
Dan Albert
10b74e507f Don't exclude BARCAP targets from culling.
Fixes https://github.com/Khopa/dcs_liberation/issues/597

(cherry picked from commit b9ade2295e)
2020-12-19 12:30:38 -08:00
Dan Albert
8a03a9462b Move more SAMs off runways in Syria Full.
(cherry picked from commit e5bca224e9)
2020-12-19 12:22:27 -08:00
Dan Albert
e5bca224e9 Move more SAMs off runways in Syria Full. 2020-12-19 12:22:08 -08:00
Dan Albert
197bf5d0cf Mark develop branch as 2.4 preview. 2020-12-19 12:07:35 -08:00
Dan Albert
d8b15ebcdb Merge branch 'develop_2_3_x' into develop 2020-12-19 12:03:21 -08:00
Dan Albert
078466241f Note fixed SAM placement in changelog. 2020-12-19 12:02:34 -08:00
Dan Albert
57c3eb5d2c Remove checkboxes from bug template.
Prevents all our bugs from looking complete or in progress because they have partially completed "tasks".
2020-12-19 11:59:28 -08:00
C. Perreau
a4876167c4 Create CODE_OF_CONDUCT.md 2020-12-19 20:57:55 +01:00
Dan Albert
a38a5654a9 Document current license.
Copied from the original shdwp repository.

https://github.com/Khopa/dcs_liberation/issues/243
2020-12-19 11:56:13 -08:00
Dan Albert
69a41879bb Note Iraq 1991 faction in changelog.
(cherry picked from commit 8447c563ea)
2020-12-19 11:53:49 -08:00
Emanuele Garofalo
e3524a506b New Faction Iraq 1991
(cherry picked from commit fd61a4b23a)
2020-12-19 11:53:46 -08:00
Dan Albert
8447c563ea Note Iraq 1991 faction in changelog. 2020-12-19 11:53:25 -08:00
Emanuele Garofalo
fd61a4b23a New Faction Iraq 1991 2020-12-19 11:52:54 -08:00
Dan Albert
9257311896 Update changelog for #616.
(cherry picked from commit 3e4bb88089)
2020-12-19 11:47:02 -08:00
Dan Albert
23e870e416 Put back code to reserve beacon frequencies.
Fixes https://github.com/Khopa/dcs_liberation/issues/616

(cherry picked from commit 2f3f53a978)
2020-12-19 11:47:01 -08:00
Dan Albert
8270b28d85 Update beacon data.
(cherry picked from commit 89755b1005)
2020-12-19 11:47:00 -08:00
Dan Albert
1a0889d3d9 Update beacon importer to handle TheChannel.
TheChannel doesn't have message catalogs for English.

(cherry picked from commit a7203ea90a)
2020-12-19 11:47:00 -08:00
Dan Albert
5382d99a94 Improve TOT planning.
Moves all TOT planning into the FlightPlan to clean up specialized
behavior and improve planning characteristics.

The important part of this change is that flights are now planning to
the mission time for their flight rather than the package as a whole.
For example, a TARCAP is planned based on the time from startup to the
patrol point, a sweep is planned based on the time from startup to the
sween end point, and a strike flight is planned based on the time from
startup to the target location. TOT offsets can be handled within the
flight plan.

As another benefit of theis cleanup, flights without hold points no
longer account for the hold time in their planning, so those flights are
planned to reach their targets sooner.

As a follow up TotEstimator can be removed, but I want to keep this low
impact for 2.3.2.

Fixes https://github.com/Khopa/dcs_liberation/issues/593

(cherry picked from commit 745dfc71bc)
2020-12-19 11:46:58 -08:00
Dan Albert
3e4bb88089 Update changelog for #616. 2020-12-19 11:46:19 -08:00
Dan Albert
2f3f53a978 Put back code to reserve beacon frequencies.
Fixes https://github.com/Khopa/dcs_liberation/issues/616
2020-12-19 11:45:33 -08:00
Dan Albert
89755b1005 Update beacon data. 2020-12-19 11:44:12 -08:00
Dan Albert
a7203ea90a Update beacon importer to handle TheChannel.
TheChannel doesn't have message catalogs for English.
2020-12-19 11:44:02 -08:00
walterroach
afb0ac14c4 Fix bad air defense location #617 2020-12-19 13:31:31 -06:00
Dan Albert
745dfc71bc Improve TOT planning.
Moves all TOT planning into the FlightPlan to clean up specialized
behavior and improve planning characteristics.

The important part of this change is that flights are now planning to
the mission time for their flight rather than the package as a whole.
For example, a TARCAP is planned based on the time from startup to the
patrol point, a sweep is planned based on the time from startup to the
sween end point, and a strike flight is planned based on the time from
startup to the target location. TOT offsets can be handled within the
flight plan.

As another benefit of theis cleanup, flights without hold points no
longer account for the hold time in their planning, so those flights are
planned to reach their targets sooner.

As a follow up TotEstimator can be removed, but I want to keep this low
impact for 2.3.2.

Fixes https://github.com/Khopa/dcs_liberation/issues/593
2020-12-19 11:20:16 -08:00
Dan Albert
82d9689d1b Remove links from bug report template.
Links don't render here.
2020-12-18 13:39:48 -08:00
Dan Albert
6afaef1654 Add issue templates. 2020-12-18 13:38:07 -08:00
Dan Albert
bb42d86012 Add more information to the readme.
Adds information about filing bugs/feature requests, finding the roadmap, and finding the preview builds.
2020-12-18 13:10:05 -08:00
Dan Albert
5b44580061 Add missing 2.3.2 change to changelog.
(cherry picked from commit 4eac743812)
2020-12-17 16:28:17 -08:00
Dan Albert
4eac743812 Add missing 2.3.2 change to changelog. 2020-12-17 16:27:54 -08:00
Dan Albert
ed8ab37bd5 Fix adding custom waypoints.
Fixes https://github.com/Khopa/dcs_liberation/issues/604

(cherry picked from commit 296e6e8e8f)
2020-12-17 16:26:19 -08:00
Dan Albert
563c3f0f1b Merge branch 'develop_2_3_x' into develop 2020-12-17 16:25:18 -08:00
Dan Albert
296e6e8e8f Fix adding custom waypoints.
Fixes https://github.com/Khopa/dcs_liberation/issues/604
2020-12-17 16:21:31 -08:00
Khopa
334aab2755 Changelog update 2020-12-18 01:09:14 +01:00
Khopa
419f4f3156 Updated version string 2020-12-18 01:08:34 +01:00
Khopa
ec5a26e8dd Added ZSU-57 sites 2020-12-18 01:05:06 +01:00
Khopa
2b7cd36eea T72B3 and BTR-82A support 2020-12-18 00:42:44 +01:00
Khopa
2f11731052 pydcs version update to include new data export 2020-12-18 00:25:50 +01:00
Khopa
23a0846533 Merge branch 'develop_2_3_x' into develop
# Conflicts:
#	changelog.md
#	qt_ui/windows/QLiberationWindow.py
#	resources/campaigns/syria_full_map_remastered.json
2020-12-18 00:23:53 +01:00
walterroach
666858f8e2 Fix Ground units ... don't move forward #601 2020-12-17 02:03:02 -06:00
Dan Albert
2288b7f7b2 Fail gracefully when out of radio channels.
Fixes https://github.com/Khopa/dcs_liberation/issues/598

(cherry picked from commit 498af28efb)
2020-12-16 22:24:07 -08:00
Dan Albert
498af28efb Fail gracefully when out of radio channels.
Fixes https://github.com/Khopa/dcs_liberation/issues/598
2020-12-16 19:12:18 -08:00
Khopa
3902ab3375 Fixed : BMD_1 IFV missing from db. Fixed error preventing mission generation when the role of an unit can not be determined.
(cherry picked from commit 4112a86fe9)
2020-12-16 19:08:42 -08:00
Dan Albert
6bb0bdf66e Don't exclude BARCAP targets from culling.
Fixes https://github.com/Khopa/dcs_liberation/issues/597

(cherry picked from commit b9ade2295e)
2020-12-16 18:53:17 -08:00
Dan Albert
b9ade2295e Don't exclude BARCAP targets from culling.
Fixes https://github.com/Khopa/dcs_liberation/issues/597
2020-12-16 18:52:12 -08:00
C. Perreau
44b5f5a919 Merge pull request #596 from Khopa/develop_2_3_x
Release 2.3.1
2020-12-16 21:45:30 +01:00
Khopa
17d37494c2 2.3.1 changelog 2020-12-16 21:14:25 +01:00
Khopa
f0d81e98a0 About dialog update 2020-12-16 21:12:01 +01:00
Khopa
e3b13f7b4a UX : Display a warning message when attempting to buy more aircraft at an already full airfield. 2020-12-16 21:08:48 +01:00
Khopa
ba2686630a Updated version number 2020-12-16 20:51:36 +01:00
Emanuele Garofalo
e195cfa6a0 Replaced previous Syria full map by the new version by Hawkmoon 2020-12-16 20:49:03 +01:00
Emanuele Garofalo
b9fbd1906f Add files via upload
Fixed NATO DESERT STORM FACTIO
2020-12-16 20:47:04 +01:00
Emanuele Garofalo
1f611bafef Add files via upload
New Syria Full map Remastered
2020-12-16 20:46:54 +01:00
C. Perreau
af7faa59dc Merge pull request #595 from ITAHawkmoon/develop
Syria Full map Remastered & NATO Desert Storm fixed
2020-12-16 20:43:34 +01:00
Emanuele Garofalo
0b21ee46ea Delete NATO Desert Storm.json
old faction
2020-12-16 16:23:40 +01:00
Emanuele Garofalo
f64996a350 Add files via upload
Fixed NATO DESERT STORM FACTIO
2020-12-16 16:22:37 +01:00
Emanuele Garofalo
d7cccd1980 Add files via upload
New Syria Full map Remastered
2020-12-16 16:20:45 +01:00
walterroach
b9467d9236 Fix broken Full Caucasus Map frontline 2020-12-16 09:08:13 -06:00
walterroach
69096b15ae Fix broken Full Caucasus Map frontline 2020-12-16 09:06:51 -06:00
Dan Albert
a075e62bad Fix easy going CAPs.
Fixes https://github.com/Khopa/dcs_liberation/issues/592

(cherry picked from commit 1ebe367e07)
2020-12-15 22:48:21 -08:00
Dan Albert
1ebe367e07 Fix easy going CAPs.
Fixes https://github.com/Khopa/dcs_liberation/issues/592
2020-12-15 22:48:05 -08:00
Khopa
db229f25bf Changelog update 2020-12-16 00:28:59 +01:00
Khopa
787c93b9d4 Another yaml release update 2020-12-15 23:55:13 +01:00
Khopa
63953992a9 Fix release yml syntax error 2020-12-15 23:46:01 +01:00
Khopa
567cb0c0b6 Removed exe installer from release process (To avoid having people installing it in C:/Program Files 2020-12-15 23:38:59 +01:00
C. Perreau
f37999a3ef Merge pull request #590 from Khopa/develop_2_3_x
Release 2.3.0
2020-12-15 23:36:08 +01:00
Khopa
545761e3d4 2.3.0 changelog update 2020-12-15 23:22:21 +01:00
C. Perreau
97ea67d01d Merge pull request #533 from walterroach/changelog_audit
WIP: 2.3.0 Changelog Audit
2020-12-15 23:19:13 +01:00
Khopa
561d679a62 Merge remote-tracking branch 'khopa/develop_2_3_x' into develop_2_3_x 2020-12-15 23:14:51 +01:00
Khopa
991cd91dd4 Possible to setup Runway attack payload, default to strike payload otherwise; 2020-12-15 23:14:36 +01:00
walterroach
9e51ff0253 Merge pull request #589 from walterroach/sead_flyover
SEAD Tasking
2020-12-15 15:25:31 -06:00
Khopa
1a1e55e16c About dialog update 2020-12-15 21:01:13 +01:00
Khopa
2fe4a39784 Changed default payload for C-130 mod 2020-12-15 21:00:56 +01:00
Khopa
7d81b9ef5c C-130 Plugin by Plob 2020-12-15 20:19:42 +01:00
Khopa
b355b6dc60 Updated Persian Gulf Full Map in new miz format by Plob. 2020-12-15 20:19:36 +01:00
Khopa
819148762b Hercules 130 pydcs extensions updated to version 6.0 (MOAB 💣 support) 2020-12-15 20:19:35 +01:00
Khopa
4112a86fe9 Fixed : BMD_1 IFV missing from db. Fixed error preventing mission generation when the role of an unit can not be determined. 2020-12-15 20:09:41 +01:00
walterroach
7838c9b49b Revert DEAD flyover 2020-12-15 12:58:55 -06:00
Khopa
ca5a70e3bc pydcs version update 2020-12-15 19:57:32 +01:00
Khopa
d6376c3a91 pydcs version update 2020-12-15 19:57:04 +01:00
Khopa
e134143f16 Fixed possible crash in mission generation when generating a mission with a faction that doesn't have mandpad and with the disabled infantry option checked.
(cherry picked from commit 31fdd24c3e)
2020-12-15 10:20:41 -08:00
Dan Albert
0f1577d314 Fix crash in new game generation.
Fixes https://github.com/Khopa/dcs_liberation/issues/583

(cherry picked from commit 793b356c01)
2020-12-15 10:20:38 -08:00
Dan Albert
793b356c01 Fix crash in new game generation.
Fixes https://github.com/Khopa/dcs_liberation/issues/583
2020-12-15 10:16:21 -08:00
walterroach
a36858f3ea Change SEAD target waypoint to flyover 2020-12-15 11:53:12 -06:00
Dan Albert
aaa6637435 Don't cull objects near package targets.
https://github.com/Khopa/dcs_liberation/issues/578
Fixes https://github.com/Khopa/dcs_liberation/issues/262

(cherry picked from commit 25efdd3d4f)
2020-12-14 23:55:59 -08:00
Dan Albert
f0f6739cf8 Don't cull flights.
This was made largely pointless in 2.2, since the AI won't plan a dozen
CAPs 300nm from the front line any more. Culling flights mostly just
confuses players and breaks the planning AI.

If we do want to limit flight counts, we need to do that by limiting
flight counts. The AI will try to put their aircraft as close to the
mission as possible, so culling will do very little to stop them from
spawning 50 flights at the front-line attached airfield.

https://github.com/Khopa/dcs_liberation/issues/578
(cherry picked from commit 0b2483ea15)
2020-12-14 23:55:58 -08:00
Dan Albert
25efdd3d4f Don't cull objects near package targets.
https://github.com/Khopa/dcs_liberation/issues/578
Fixes https://github.com/Khopa/dcs_liberation/issues/262
2020-12-14 23:47:31 -08:00
Dan Albert
0b2483ea15 Don't cull flights.
This was made largely pointless in 2.2, since the AI won't plan a dozen
CAPs 300nm from the front line any more. Culling flights mostly just
confuses players and breaks the planning AI.

If we do want to limit flight counts, we need to do that by limiting
flight counts. The AI will try to put their aircraft as close to the
mission as possible, so culling will do very little to stop them from
spawning 50 flights at the front-line attached airfield.

https://github.com/Khopa/dcs_liberation/issues/578
2020-12-14 23:35:30 -08:00
Dan Albert
2c38ce910c Add off-map spawns to the Black Sea campaign.
Fixes https://github.com/Khopa/dcs_liberation/issues/561

(cherry picked from commit 4d26ec0789)
2020-12-14 22:36:57 -08:00
Dan Albert
4d26ec0789 Add off-map spawns to the Black Sea campaign.
Fixes https://github.com/Khopa/dcs_liberation/issues/561
2020-12-14 22:36:39 -08:00
Dan Albert
edba923f2f Fix budget update for non-base SAMs.
Just emit the signal to update the budget rather than trying to figure
out the heirarchy of the UI.

Fixes https://github.com/Khopa/dcs_liberation/issues/581

(cherry picked from commit 7d907aac0f)
2020-12-14 17:41:14 -08:00
Dan Albert
7d907aac0f Fix budget update for non-base SAMs.
Just emit the signal to update the budget rather than trying to figure
out the heirarchy of the UI.

Fixes https://github.com/Khopa/dcs_liberation/issues/581
2020-12-14 17:00:56 -08:00
Khopa
d6981550a8 C-130 Plugin by Plob 2020-12-14 22:23:21 +01:00
Khopa
8b0636367b Updated Persian Gulf Full Map in new miz format by Plob. 2020-12-14 22:09:37 +01:00
Khopa
a6c9d0f9bc Hercules 130 pydcs extensions updated to version 6.0 (MOAB 💣 support) 2020-12-14 21:41:30 +01:00
walterroach
4b0d2f7abc Merge pull request #576 from walterroach/explict_resources
Explicit resources
2020-12-13 18:04:58 -06:00
Dan Albert
dd28781b69 Fix kneeboard ILS for many airfields.
Fixes https://github.com/Khopa/dcs_liberation/issues/564

(cherry picked from commit 61ebe9780e)
2020-12-13 15:12:39 -08:00
Dan Albert
ece56032f1 Fix missing import for new package on enemy CVs.
Fixes https://github.com/Khopa/dcs_liberation/issues/570

(cherry picked from commit e887082501)
2020-12-13 15:12:39 -08:00
Dan Albert
61ebe9780e Fix kneeboard ILS for many airfields.
Fixes https://github.com/Khopa/dcs_liberation/issues/564
2020-12-13 13:48:08 -08:00
Dan Albert
e887082501 Fix missing import for new package on enemy CVs.
Fixes https://github.com/Khopa/dcs_liberation/issues/570
2020-12-13 13:44:10 -08:00
walterroach
8e3039dd37 Fix Zero Fuel Start #559 2020-12-13 15:41:13 -06:00
walterroach
1d1c130d19 Add missing WW2 units to db
Fixes #571
2020-12-13 15:03:19 -06:00
Khopa
1fd3f70eec Fix usa C-130 faction name being the same as usa_2005 2020-12-13 12:41:49 -06:00
walterroach
d9ea33cbb9 Fix diversified frontline distance 2020-12-13 12:21:09 -06:00
Khopa
80778aa267 Fix usa C-130 faction name being the same as usa_2005 2020-12-13 17:21:13 +01:00
C. Perreau
445cb4f146 Merge pull request #569 from ITAHawkmoon/ITAHawkmoon-update-db
Update db and new faction.py
2020-12-13 16:43:09 +01:00
C. Perreau
95db2aa14f Merge branch 'develop' into ITAHawkmoon-update-db 2020-12-13 16:25:52 +01:00
Emanuele Garofalo
4b0fc637eb Update NATO Desert Storm.json
syntax error
2020-12-13 12:43:47 +01:00
Emanuele Garofalo
48d6b4cfa1 Add files via upload
New faction NATO desert Storm
2020-12-13 12:33:21 +01:00
Emanuele Garofalo
fc11182bbe Update db.py
added some plane missing from the db
2020-12-13 12:29:58 +01:00
Dan Albert
25b72e1af4 Fix UI exception for custom flight plans.
(cherry picked from commit 1848338ef7)
2020-12-12 18:27:55 -08:00
Dan Albert
1848338ef7 Fix UI exception for custom flight plans. 2020-12-12 18:27:30 -08:00
Dan Albert
ff0446cc12 Fix division by zero for very close waypoints.
Fixes https://github.com/Khopa/dcs_liberation/issues/557

(cherry picked from commit 08ceb57c31)
2020-12-12 14:51:32 -08:00
Dan Albert
08ceb57c31 Fix division by zero for very close waypoints.
Fixes https://github.com/Khopa/dcs_liberation/issues/557
2020-12-12 14:51:03 -08:00
Dan Albert
affb332eb9 Fix duplication of pydcs translation keys.
We shouldn't be constructing these by hand, and the first argument is
not the value of the string. I'm not really sure why the current code
works at all. We probably do this in other places and should clean that
up, but for now this should fix Tauntaun.

Fixes https://github.com/Khopa/dcs_liberation/issues/528
2020-12-12 14:24:30 -08:00
Dan Albert
6455c38ff4 Fix duplication of pydcs translation keys.
We shouldn't be constructing these by hand, and the first argument is
not the value of the string. I'm not really sure why the current code
works at all. We probably do this in other places and should clean that
up, but for now this should fix Tauntaun.

Fixes https://github.com/Khopa/dcs_liberation/issues/528

(cherry picked from commit affb332eb9)
2020-12-12 14:24:09 -08:00
Dan Albert
f62c2fbabb Show player waypoint timing in the briefing.
Fixes https://github.com/Khopa/dcs_liberation/issues/536

(cherry picked from commit d5276c9d4a)
2020-12-12 14:18:36 -08:00
Dan Albert
d5276c9d4a Show player waypoint timing in the briefing.
Fixes https://github.com/Khopa/dcs_liberation/issues/536
2020-12-12 14:17:48 -08:00
walterroach
4c0fc5a407 Adjust tanker speed and altitudes #444 2020-12-12 16:08:37 -06:00
walterroach
f608cd5aef fix mypy issues 2020-12-12 15:45:55 -06:00
walterroach
0371b62acb Merge branch 'ground_tasking' into develop_2_3_x 2020-12-12 15:41:00 -06:00
walterroach
eddd66b5c4 West coast Caucasus Landmap improvements 2020-12-12 15:35:12 -06:00
walterroach
c8e71d269b Improve Ground Unit Behavior
* Prevent spinning units
* Prevent units spawning in exclusion zone
* Prevent units from moving into exclusion zone
2020-12-12 15:33:26 -06:00
Dan Albert
ae034d5387 Show takeoff time in waypoint list.
https://github.com/Khopa/dcs_liberation/issues/536
(cherry picked from commit 07e5c568c4)
2020-12-12 13:22:57 -08:00
Dan Albert
07e5c568c4 Show takeoff time in waypoint list.
https://github.com/Khopa/dcs_liberation/issues/536
2020-12-12 13:22:23 -08:00
walterroach
817d6a0e15 Merge remote-tracking branch 'upstream/develop_2_3_x' into ground_tasking 2020-12-12 12:44:47 -06:00
Khopa
31fdd24c3e Fixed possible crash in mission generation when generating a mission with a faction that doesn't have mandpad and with the disabled infantry option checked. 2020-12-12 15:27:36 +01:00
Khopa
bfa0e4ba49 Made normandy campaign less cluttered 2020-12-12 15:22:51 +01:00
Khopa
dff98f0b53 Made normandy campaign less cluttered 2020-12-12 15:22:35 +01:00
Khopa
d0856ff279 pydcs update 2020-12-12 14:36:57 +01:00
Khopa
c89ff2c3d6 pydcs update 2020-12-12 14:36:40 +01:00
Khopa
4ec88d524a Fixed invasion_from_turkey inverted setup. 2020-12-12 14:25:30 +01:00
Khopa
d6b762efa7 Fixed invasion_from_turkey inverted setup. 2020-12-12 14:25:12 +01:00
Khopa
b476a26759 Fix russia_small campaign not being invertable 2020-12-12 13:23:32 +01:00
Khopa
48c218b430 Fix russia_small campaign not being invertable 2020-12-12 13:22:01 +01:00
Dan Albert
1a062e2170 Set carrier speed for recovery.
https://github.com/Khopa/dcs_liberation/issues/543
(cherry picked from commit d316836e90)
2020-12-11 19:19:48 -08:00
Dan Albert
fe658eb877 Always aim the carrier into the wind.
https://github.com/Khopa/dcs_liberation/issues/543
(cherry picked from commit 8443f61f0a)
2020-12-11 19:19:47 -08:00
Dan Albert
d316836e90 Set carrier speed for recovery.
https://github.com/Khopa/dcs_liberation/issues/543
2020-12-11 19:19:08 -08:00
Dan Albert
8443f61f0a Always aim the carrier into the wind.
https://github.com/Khopa/dcs_liberation/issues/543
2020-12-11 19:01:35 -08:00
Khopa
feed55186f Migrated "polygon" code to shapely 2020-12-12 02:31:43 +01:00
walterroach
56591b8655 broken waypoint finder 2020-12-11 18:17:44 -06:00
Khopa
7c52ca15f3 C-130 example faction 2020-12-12 00:54:19 +01:00
Khopa
06c751f214 Migrated invasion from turkey campaign to new miz format. 2020-12-12 00:51:46 +01:00
Khopa
9d774eaad8 Fixed culling display distance. Allowed smaller distance for culling (useful for WW2 maps) 2020-12-11 22:39:09 +01:00
Khopa
babfd4abda Merge remote-tracking branch 'khopa/develop' into develop 2020-12-11 22:04:50 +01:00
Khopa
a029c165a0 Normandy campaigns and terrain update 2020-12-11 22:04:33 +01:00
walterroach
9b51533d96 Caucasus Terrain and frontline tweaks 2020-12-11 14:35:06 -06:00
Khopa
e1b7e0eb00 Merge remote-tracking branch 'khopa/develop' into develop 2020-12-11 20:56:30 +01:00
Khopa
508a5693c9 Fixed duplicated tgo issue with chinese and russian navy generators. 2020-12-11 20:55:59 +01:00
walterroach
67806f3d76 Dec 10 review 2020-12-11 11:53:25 -06:00
walterroach
f687a30c7e Merge pull request #525 from root0fall/fix_221
refresh budget after purchase from sub-dialog
2020-12-11 11:43:16 -06:00
Dan Albert
15615a1077 Add the Black Sea campaign.
Similar to Western Georgia, but extended further up the coast, and
without the initial battle in Georgia itself.
2020-12-11 01:15:28 -08:00
Dan Albert
905175c210 Add --invert to the game generator. 2020-12-11 01:15:28 -08:00
Dan Albert
409e070887 Generate required IADS near FOBs. 2020-12-11 01:09:14 -08:00
Dan Albert
f659dc1f76 Reference point rework.
* Introduce a real type.
* Rewrite _transform_point to make use of Point.
* Add shift modifier for large (10 pixel) adjustements to reference
  points. Unmodified behavior is now single pixel.
* Use WASD for moving the second point (shift modified numpad keys don't
  seem to work).
* Add a debug option to draw transformed reference points to check for
  errors. If they don't overlap, something is wrong.
* Cleaned up all the existing reference points. Caucasus in particular
  is now *much* better.

As an added bonus, the cleanup for carrier movement projection now also
shows an invalid destination when the destination is on land.
2020-12-10 21:08:38 -08:00
root0fall
b8922b39fd add type hinting to update_dialog_budget() 2020-12-11 09:24:21 +08:00
Khopa
8137d57cdf Dev : Improved Reference point setup mode, made it easier to setup point with polygon map appearing as transparent overlay, and displaying reference points on the map [Press Shift+R to toggle the mode]) 2020-12-11 00:08:08 +01:00
Khopa
bf290ac1a9 Improved Normandy reference points 2020-12-10 23:34:06 +01:00
Khopa
77fda00233 Invasion of iran lite campaign update. 2020-12-10 23:19:51 +01:00
Khopa
0255088e30 Fixed buildings showing in airbase defense menu for FOB points. 2020-12-10 23:19:26 +01:00
Khopa
b74d8b12d0 Fixed duplicated unit names in german WW2 Flak site groups. (Which would cause duplicated unit error in unitmap.py when trying to start the mission) 2020-12-10 22:32:24 +01:00
Khopa
2834f2982c Fix error on first start because of new last_save_file parameter. 2020-12-10 21:11:24 +01:00
Khopa
7d07faa5fb Fix last commit error 2020-12-10 21:09:19 +01:00
Khopa
fd70f0fc4a Pydcs extensions and db info added for C-130 Hercules mod ✈️ 2020-12-10 21:07:04 +01:00
walterroach
7744f84e85 Changelog audit 2020-12-10 14:04:48 -06:00
walterroach
441ef79aa4 Merge pull request #529 from walterroach/you_won
You Won Message
2020-12-09 18:30:47 -06:00
walterroach
c54f6ba4d2 Use set instead of dict 2020-12-09 18:25:24 -06:00
walterroach
61ecdfc48c Merge branch 'develop' into you_won 2020-12-09 18:12:18 -06:00
Khopa
5e2b259af1 Updated changelog 2020-12-10 00:20:22 +01:00
Khopa
1258f3e17c Carrier and Tarawa are now fully moveable 2020-12-10 00:13:34 +01:00
Khopa
44ed895277 Merge remote-tracking branch 'khopa/develop' into develop 2020-12-09 22:55:24 +01:00
Khopa
c74e18e449 Target positions for ship movement are now properly projected on DCS game X,Y plane 2020-12-09 22:55:11 +01:00
walterroach
2ea3f914f0 You Won message #419 2020-12-09 14:37:17 -06:00
Khopa
d6e4a50064 QOL : On launch, auto load the latest saved game. 2020-12-09 21:01:32 +01:00
walterroach
6296896471 Add special fuel case for C101 #492 2020-12-08 18:26:37 -06:00
walterroach
473cda971a Fix typing mistake 2020-12-08 17:16:09 -06:00
walterroach
cf570adabe Refactor armor
Break `plan_action_for_groups` into smaller methods

Add type hinting and cleanup formatting
2020-12-08 16:55:04 -06:00
walterroach
02196f2883 Change ground unit waypoints to offroad Ground Unit Waypoint
"On-Road" #495
2020-12-08 15:11:47 -06:00
root0fall
8b49752401 refresh budget after purchase from subwindow
fixes https://github.com/Khopa/dcs_liberation/issues/221
2020-12-08 20:47:02 +08:00
Dan Albert
8c64867918 Fix duplicate names of AAA units.
Fixes https://github.com/Khopa/dcs_liberation/issues/517
2020-12-06 22:14:56 -08:00
Dan Albert
e544063c40 Update Skynet and Mist. 2020-12-06 20:20:08 -08:00
Dan Albert
edfaaacd04 Determine player vs enemy early in debriefing.
This way the results of committing the debriefing can't alter the view
of the debriefing. It looks like it was probably that case that
debriefing information displays (but not the committed results) would be
incorrect after a base capture because the results might be shown after
the results were committed.

Maybe fixes https://github.com/Khopa/dcs_liberation/issues/513
2020-12-06 20:20:08 -08:00
Dan Albert
84b8613cf5 Cleanup debriefing signals.
We don't need most of this information.
2020-12-06 20:20:08 -08:00
walterroach
aea82e2266 Add FOB capture 2020-12-06 21:45:21 -06:00
Dan Albert
b0b9c1c8e6 Fix deletion of waypoints.
`FlightPlan.waypoints` returns a list created from the iterator now, so
removing items from that list does not actually alter the flight plan.

Fixes https://github.com/Khopa/dcs_liberation/issues/489
2020-12-06 15:32:28 -08:00
Dan Albert
b5ff32c5b6 Show patrol end time on the kneeboard.
Fixed https://github.com/Khopa/dcs_liberation/issues/421
2020-12-06 15:01:21 -08:00
Dan Albert
e0223ded54 Fix display of AA ranges for most ship types.
Fixes https://github.com/Khopa/dcs_liberation/issues/390
2020-12-06 14:22:33 -08:00
Khopa
2012ad0aa3 Fixed cursor / single click behaviour on map. 2020-12-06 21:12:44 +01:00
Dan Albert
15d72a8dcb Fix turn number off by one.
Fixes https://github.com/Khopa/dcs_liberation/issues/482
2020-12-06 01:08:23 -08:00
Dan Albert
e525b11695 Remove scary label from automation options. 2020-12-06 01:06:12 -08:00
Dan Albert
8f30e60e1b Use unplanned missions to guide aircraft purchase.
AI aircraft purchase decisions are now driven by the missions that the
flight planner was unable to fulfill. This way we're buying the aircraft
we actually need instead of buying them at random, in the locations we
need them, in the order they're needed.

There's a bit more that could be improved here:

* Unused aircraft could be ferried to where they would be more useful.
* Partial squadrons could be completed rather than buying a whole flight
  at a time.
* Aircraft could be ranked by their usefulness so we're not buying so
  many Hueys when a Hornet would do better.
* Purchase a buffer of CAP capable aircraft in case too many are shot
  down and they are not available next turn.

https://github.com/Khopa/dcs_liberation/issues/361
2020-12-06 00:59:04 -08:00
Dan Albert
ce977ac937 Remove the enemy forces multiplier option.
This didn't do what it claimed to (it actually just determines the
threshold for whether a control point shoudl be a *preferred* canidate
for purchasing ground units), and the income multipliers offer the
intended behavior.
2020-12-05 23:55:33 -08:00
Dan Albert
aa9ffa0855 Obey parking limits when auto-buying aircraft.
Fixes https://github.com/Khopa/dcs_liberation/issues/137
2020-12-05 23:36:51 -08:00
Dan Albert
6a8ca810ff Add auto-procure option to the CLI generator. 2020-12-05 23:20:58 -08:00
Dan Albert
f2d2fd7014 Add automation options to the new game wizard.
To have an effect on turn zero these need to be enabled in the wizard.
Since the last page was getting quite full I've split it into two pages:
one for the objective generation options and a second for the difficulty
and player assist options.

I also added an option to set the inital budget for opfor.
2020-12-05 23:16:17 -08:00
Dan Albert
ddd06b3162 Move mid-game option to the map options page. 2020-12-05 22:47:26 -08:00
Dan Albert
d519aa1dad Move the AI to the normal procurement system.
The procurement AI now uses the same system as the players. Orders are
placed and take a turn to fulfill.

This has a few advantages:

* We no longer need special case purchase logic for the turn 0
  population of opfor airbases.
* Players using auto-purchase can cancel orders they don't like.
2020-12-05 22:41:05 -08:00
Dan Albert
7226359e64 Avoid negative AI produrement budgets.
This also fixes the double charging that the AI was suffering from, so
they have a much better chance of actually affording planes now.

Fixes https://github.com/Khopa/dcs_liberation/issues/504
2020-12-05 21:50:30 -08:00
Dan Albert
f396ff7f12 Add procurement automation for players.
The allows the players to use the same auto-purchase mechanics that the
AI uses. The behavior is very bad, but it's no worse than what OPFOR
deals with.

There's a lot that needs to be improved before this is really a good
choice for the player:

* Option to adjust budget balance between front lines and airbases.
* Disallow negative budgets (which incidentally will cause more aircraft
  to be purchased, since the armor purchases currently accidentally
  spend the aircraft budget).
* Buy less randomly: https://github.com/Khopa/dcs_liberation/issues/361.
* Obey parking limits.
* Use the delivery events rather than instant delivery (also allows the
  player to cancel orders they don't want).

Fixes https://github.com/Khopa/dcs_liberation/issues/362
2020-12-05 21:18:23 -08:00
Dan Albert
1adee0af17 Factor AI purchases out of Game.
For now this is mostly behavior preserving. I slightly improved the
ability to buy units when multiple front lines exist by removing full
bases as candidates, but it should be a minor change at best. A larger
improvement will come later.

This is also written such that it will work for the player as well. The
procurer currently runs for the player but with all the options off, so
it does nothing. The next patch allows adds options for the player to
use auto-procurement.
2020-12-05 20:42:02 -08:00
Dan Albert
bac47dad83 Add settings for scaling income.
This adds both player and enemy income multiplier options. Note that
previously the AI was only getting 75% of their income. I've changed
that to give them their full income by default since the player can now
influence it.
2020-12-05 18:06:57 -08:00
Dan Albert
f1a2602cfd Move generator only settings out of Settings. 2020-12-05 16:56:14 -08:00
Dan Albert
b8e64d4369 Revert "Revert "Migrate buildings and TGOs to unit map.""
With fixed tracking for TGO groups and fortification "buildings".

Fixes https://github.com/Khopa/dcs_liberation/issues/485

This reverts commit 72ac8ca872.
2020-12-05 14:26:16 -08:00
Dan Albert
72ac8ca872 Revert "Migrate buildings and TGOs to unit map."
Not registering kills correctly. It was in my limited testing, so need
to dig deeper.

https://github.com/Khopa/dcs_liberation/issues/494

This reverts commit 90697194a1.
2020-12-04 23:57:58 -08:00
Dan Albert
ccb41829c9 Mark an optional property as optional. 2020-12-04 01:10:23 -08:00
Dan Albert
90697194a1 Migrate buildings and TGOs to unit map.
Fixes https://github.com/Khopa/dcs_liberation/issues/485.
2020-12-04 01:10:18 -08:00
Dan Albert
13f4baa34e Fix duplicate name of Patriot unit.
Caught by the `UnitMap`. This does break saves that have live Patriot
STRs.
2020-12-04 01:03:57 -08:00
Khopa
76840ff5c2 Added display settings to the toolbar. 2020-12-03 22:48:28 +01:00
Khopa
72ac806cb8 Possible to plan ships movements on the map (UI only) 2020-12-03 01:01:15 +01:00
Khopa
bf275fe564 Added FOB on Iran Invasion lite campaign. 2020-12-02 00:37:14 +01:00
Khopa
68818ae50d Syria 2011 country changed to combined joint task force red, as some units are not available to Syria in DCS. 2020-12-01 23:45:28 +01:00
Khopa
7315d097c2 Regorganized difficulty page of settings window 2020-12-01 23:42:34 +01:00
Khopa
cdf28700cf Possible to spawn manpads on frontline even if infantry squads are disabled. 2020-12-01 23:14:07 +01:00
Khopa
948c1d0bb0 Fixed unused aircraft not using skins setup in factions files. 2020-12-01 22:50:11 +01:00
Khopa
de0a3f929c Made WW2 Flak sites more compact, so it's easier to fit them in fields on the Normandy map. 2020-12-01 13:49:09 +01:00
Khopa
c3023a9f99 Normandy terrain data update 2020-12-01 13:39:54 +01:00
Dan Albert
4f37610dfb Convert front line units to UnitMap.
https://github.com/Khopa/dcs_liberation/issues/485
2020-12-01 01:25:51 -08:00
walterroach
aef4316f72 Merge pull request #487 from walterroach/fob
FOB Control Points
2020-11-30 23:29:15 -06:00
walterroach
378dbf254a Remove commented code 2020-11-30 23:28:39 -06:00
Khopa
f0b6a37ce2 Migrated normandy small to new miz format. 2020-12-01 00:36:49 +01:00
Khopa
ff12a120e6 Migrated invasion of iran campaign to miz format. 2020-11-30 23:12:01 +01:00
walterroach
d04be4d71b Merge branch 'develop' into fob 2020-11-30 12:18:38 -06:00
Dan Albert
581aaaad28 Merge develop 2020-11-30 12:18:31 -06:00
walterroach
453eb9feb4 revert unrelated changes 2020-11-30 12:10:03 -06:00
walterroach
4059ee44b8 pg campaign 2020-11-29 22:47:18 -06:00
walterroach
7a222ecfa0 gulf landmap update 2020-11-29 22:47:03 -06:00
Dan Albert
be15e9adf2 Adjust IADS for Inherent Resolve.
Had too many long range SAMs and not enough medium range SAMs. Balanced
that out a bit and added many of the fixed SAM positions on the map as
possible target locations.
2020-11-29 17:52:57 -08:00
Dan Albert
2fd097c613 Remove SA-15 from Syria 2011.
It looks like the SA-15s were something delivered a few years later than
this. The SA-10s are also more recent, but they have been using SA-5s
for quite some time and until those are available in DCS the SA-10 makes
a reasonable approximation.
2020-11-29 16:33:30 -08:00
Ignacio Muñoz Fernandez
66ee5f5392 Bingo & Joker Fuel for Flight Plans (#480)
Add bingo and joker fuel information to the kneeboard.
2020-11-29 14:53:15 -08:00
walterroach
3bb08f8d30 FOB names
Remove icon
2020-11-29 16:26:08 -06:00
walterroach
d7787adddc Generators for FOBs and their defenses 2020-11-29 14:21:02 -06:00
walterroach
1f37b879b1 Add FOB ControlPoint type 2020-11-29 12:16:39 -06:00
Khopa
45ce28f9bf Another big Persian gulf terrain update (Iran) 2020-11-29 15:19:28 +01:00
Khopa
4e87bed4e5 Migrated invasion of iran lite campaign to new miz format. 2020-11-29 15:00:56 +01:00
Khopa
a7421fc670 Updated Persian Gulf terrain data to include more exclusions zones. 2020-11-29 15:00:03 +01:00
Khopa
208a7550ef Added C101CC to list of antiship capable aircrafts. 2020-11-29 13:16:35 +01:00
Khopa
37e23f70d6 Put manpads in modern faction's infantry squads. And added a setting to disable it. 2020-11-29 13:11:49 +01:00
Dan Albert
7daefa8ae5 Fix off by one in briefing waypoint numbers.
Fixes https://github.com/Khopa/dcs_liberation/issues/441
2020-11-28 20:28:35 -08:00
walterroach
292ac42003 Fix campaigns without frontline.
* Missions will now generate without a frontline conflict
* Bulls is now defined as the nearest opposing airfield for each side
2020-11-28 19:53:01 -06:00
walterroach
c501c45c52 Update channel landmap
Ensures a valid frontline location can be found on the Dunkirk campaign
between Saint Omer and Dunkirk
2020-11-28 19:42:10 -06:00
walterroach
29b894f8b0 Fix odd frontline unit spawns
* Modfied frontline vector to ensure start point stays outside of
  exclusion zone.  (Previously it could be up to 100m inside)

* Change randomization in offset distance from frontline to be based
  on a percentage of the unit type's fixed offset instead of the
  width of frontline.
  (Prevents units from being far from their expected distance from
  frontline)

* Change visualgen to use the same frontline vector calculation as the
  unit spawns
2020-11-28 18:43:32 -06:00
Dan Albert
55573bf40a Clear loaded scripts before generation.
Every mission generated after the first each time Liberation was
skipping all of the plugins (including the base plugin) because they'd
already been loaded on a previous generation and the list wasn't
cleared.
2020-11-28 15:39:33 -08:00
Khopa
f2c2ef82c5 Fixed unused aircraft using the same slots as used aircrafts. 2020-11-28 22:29:09 +01:00
Khopa
d6b33d353c Now possible to get CVN-75 Harry S Truman supercarrier generated for US faction 2020-11-28 21:57:58 +01:00
Khopa
2ed1c36c54 Fixed infantry for red player being the same units as blue faction. Fixed skill for enemy vehicles being the same as player's one. 2020-11-28 21:34:21 +01:00
Khopa
7cbcbc1171 Migrated desert war to new miz format 2020-11-28 19:19:33 +01:00
Khopa
b74dcfa053 Battle of britain campaign migrated to new miz format. 2020-11-28 18:03:55 +01:00
Khopa
62bf7eb227 Dunkirk campaign migrated to the new miz format. 2020-11-28 17:41:20 +01:00
Dan Albert
07bfe8e29a Move a SAM that's occluding Palmyra. 2020-11-27 20:11:21 -08:00
Dan Albert
0b258997dd Update faction air defenses. 2020-11-27 19:56:49 -08:00
Dan Albert
69421ad7a1 Fix faction info for air defense changes. 2020-11-27 18:12:41 -08:00
Dan Albert
fdf571c016 Clean up faction air defenses vs shorads vs SAMs. 2020-11-27 18:10:13 -08:00
Dan Albert
bd60760f9d Merge faction sams and shorads into air_defenses.
Fixes https://github.com/Khopa/dcs_liberation/issues/473. Air defenses
for bases, strike locations, and fixed IADS will now all downgrade to
lower tier systems as needed. Strike locations will still be spawned as
an equally weighted random generator from either the medium or long
range groups, but will use a short range system if none are available to
the faction.

I've made the change in a way that leaves factions compatible, but will
follow up to clean up our built-in factions.
2020-11-27 17:45:40 -08:00
Dan Albert
e8aa9839b0 Fall back to lower range SAMs when needed.
Mostly fixes https://github.com/Khopa/dcs_liberation/issues/473. The
last part of the fix is to migrate the `shorads` property of the faction
to just be in `sams` and just use the property to decide its use.
Currently factions like USA 2005 that have long range SAMs and SHORADs
only will still not spawn anything at medium sites because they have no
other SAMs declared.
2020-11-27 17:45:40 -08:00
Dan Albert
fcdb22db5b Add range property to all air defense generators. 2020-11-27 17:45:40 -08:00
walterroach
e73cf68def Merge pull request #474 from walterroach/frontline_vector
Frontline vector
2020-11-27 18:49:50 -06:00
Dan Albert
fa5b842cc7 Strengthen SA-10, add point defense.
Fixes https://github.com/Khopa/dcs_liberation/issues/417, though the
notes on that bug about this being non-optimal for skynet are still
true. This doesn't make skynet behavior any worse though, and does
improve it some compared to not having PD.

Adds two new SA-10 generator variants:

* Tier 2, with SA-15 for point defense
* Tier 3, with SA-15 for point defense and the Shilka upgraded to a
  Tunguska.

Updated factions that are capable of those systems, added missing SAMs
to those factions, and removed use of SA-19 as an independent SAM from
those factions. Will do a larger audit of faction SAMs later.
2020-11-27 16:25:10 -08:00
walterroach
43a21cb341 Fix bug #353 2020-11-27 17:53:56 -06:00
walterroach
45361b57a7 fixed bad import 2020-11-27 16:42:11 -06:00
walterroach
046c7a662a DisplayOption string change 2020-11-27 16:33:31 -06:00
walterroach
a9f1de13b1 Fix armor groups spawning bugs
* Prevent common cases where ground units do not spawn due to
  frontline position being in exclusion zone

* Fix case where ground units will spawn inside exclusion zone due to
  random offset from frontline center being fixed

* Remove dead code from `conflictgen.py`

* Start cleanup of `GroundConflictGenerator`
2020-11-27 16:31:52 -06:00
walterroach
edbe2d86f2 Merge branch 'develop' into frontline_vector 2020-11-27 13:47:10 -06:00
walterroach
4e12a1cdad Rework frontline vector
Ensures frontline stays outside of exclusion zones by adjusting its
position and width

Adds a DisplayOption for viewing the frontline vector on the map
2020-11-27 13:46:53 -06:00
Dan Albert
d24c7ea93e Remove the Hawk from USA 2005.
According to Wikipedia the last user of these (USMC) stopped using them
in 2002.
2020-11-26 22:55:44 -08:00
Dan Albert
484f1e8d51 Generate required IADS even at unconnected bases.
If the campaign designer doesn't want SAMs at unconnected bases they can
just not put them there. If they *do* put them there, generate them.
2020-11-26 22:54:50 -08:00
Dan Albert
8d5abb877c Improve IADS on Inherent Resolve. 2020-11-26 22:54:30 -08:00
Dan Albert
fd454dce74 Load campaign data lazily.
Error checking comes later, but the new game wizard opens much faster by
not spending time creating theaters it doesn't need.

Fixes https://github.com/Khopa/dcs_liberation/issues/469
2020-11-26 22:14:23 -08:00
Dan Albert
5d4fccd438 Fix mypy regressions. 2020-11-26 20:08:24 -08:00
Dan Albert
9f078e1483 Don't generate empty groups. 2020-11-26 20:03:16 -08:00
Dan Albert
0e807d84c2 Differentiate required long and medium range SAMs.
To improve IADS design in campaigns, this differentiates required long
and medium range SAMs. SAMs that must be long range SAMs are defined by
SA-10 or Patriot launchers, while medium range SAMs are defined by SA-2,
SA-3, or Hawk launchers.

Long range SAMs positions will only be populated by long range SAMs
(Patriots and SA-10s), and not all factions have those available. Medium
range SAMs currently comprise all air defenses that are not long range
SAMs, so if the faction includes flak guns in their `sams` property then
flak guns may be spawned at medium range SAM locations.

Base defenses and random SAM locations continue to use either type of
SAM.
2020-11-26 19:48:14 -08:00
Ignacio Muñoz Fernandez
3ad57d995b feat: added timestamp to information widget log items 2020-11-26 15:48:16 -08:00
Ignacio Muñoz Fernandez
28cf42aeb8 fix: Unable to find MLRS_BM21_Grad in pydcs 2020-11-26 15:43:54 -08:00
Ignacio Muñoz Fernandez
7fcf74a8ed added none check on budget setGame 2020-11-26 15:43:36 -08:00
Ignacio Muñoz Fernandez
a0d38f7465 fix: disable topbar buttons when game is None 2020-11-26 15:43:36 -08:00
Dan Albert
cd97526d2b Mark the fishbed as capable of strike missions. 2020-11-26 13:46:33 -08:00
Khopa
87fdc16f9b Update to support the newest version of the Rafale mod. 2020-11-26 21:38:46 +01:00
Khopa
b69eb02766 Added base defenses for russia small campaign 2020-11-26 13:38:59 +01:00
Dan Albert
7636234649 Fix mission generation when labels are changed.
Fixes https://github.com/Khopa/dcs_liberation/issues/457
2020-11-25 22:17:15 -08:00
Dan Albert
2bd673a531 Don't allow operating on broken runways.
Doesn't allow helos or harriers to do it either even though they should
be able to because we don't currently support ground spawns, which would
be needed to prevent those aircraft from using the runway. Even then, I
don't know if they can be forced to *land* vertically.

Fixes https://github.com/Khopa/dcs_liberation/issues/432
2020-11-25 18:50:00 -08:00
Ignacio Muñoz Fernandez
a1b64bc72d Cleanup QWeatherInfoWindow and rjust wind bearings. (#456) 2020-11-25 18:10:46 -08:00
Dan Albert
80bc9d6b23 Support reparing damaged runways.
Repairing a damaged runway costs $100M and takes 4 turns (one day). The
AI will always repair runways if they can afford it. if a runway is
damaged again during the repair the process must begin again.

Runways are still operational despite what the UI says. Preventing the
player and AI from using damaged runways (except for with helicopters
and harriers) is next.
2020-11-25 18:07:51 -08:00
Dan Albert
ee768b9147 Display damaged runways. 2020-11-25 18:07:51 -08:00
Khopa
7dfb0c67e5 Migrated campaign "Russia Small" to the new campaign format. 2020-11-26 00:10:11 +01:00
Dan Albert
75ea5cc462 Fix None dereferences in weather UI.
Also normalizes line endings.
2020-11-25 14:39:33 -08:00
Khopa
afabf6fd00 Fixed Control Point being set as neutral in some case. 2020-11-25 23:35:01 +01:00
Khopa
0eb4519797 Fixed IndexError preventing mission generation when a faction does not have any awacs aircraft available. 2020-11-25 23:11:55 +01:00
Dan Albert
611f04ab5a Resurrect force multiplier option.
Fixes https://github.com/Khopa/dcs_liberation/issues/440
2020-11-25 14:10:34 -08:00
Dan Albert
a9ba2deafa Update pydcs to get access to satnav options.
Fixes https://github.com/Khopa/dcs_liberation/issues/426
2020-11-25 13:16:51 -08:00
Dan Albert
0c4e920af3 Handle runway damage in the debrief.
Apparently we were already getting this info because it's a unit like
any other according to the event system, so if runways were actually
sufficiently damaged we'd emit a ton of exceptions.

This doesn't do anything yet, but tracks the damage state. Will add the
ability to repair them next, and then finally make the damage render the
runway inoperable.
2020-11-25 13:12:46 -08:00
Ignacio Muñoz Fernandez
ef0e565337 chore: standarized parameters in game/utils.py and added docblocks 2020-11-25 13:04:03 -08:00
Ignacio Muñoz Fernandez
02e7ab41b4 feat: MVP done for Weather Information Display 2020-11-25 13:04:03 -08:00
Ignacio Muñoz Fernandez
59bd4541c4 Removed old daytime icons 2020-11-25 13:04:03 -08:00
Ignacio Muñoz Fernandez
ca30af4238 wip: finished work on the TopPanel Widget, added weather icons, changed timeofday icons 2020-11-25 13:04:03 -08:00
Ignacio Muñoz Fernandez
718b3f2623 wip: fixed qt cosmetic issue, added forecast text generation, initial for weather window 2020-11-25 13:04:03 -08:00
Ignacio Muñoz Fernandez
6e153c6451 wip: initial work on issue #406 2020-11-25 13:04:03 -08:00
Khopa
4a1809d56e Added name of F-22 mod author in USA 2005 faction. 2020-11-25 12:13:19 +01:00
Khopa
a2bf0c1bea Added the wrong F-22A.lua file last night 😵 2020-11-25 12:12:07 +01:00
Dan Albert
f0d9dae33b Remove M-2000 from runway strike preferred list. 2020-11-24 17:32:09 -08:00
Dan Albert
b99462b628 Rename OCA strike tasks. 2020-11-24 17:28:48 -08:00
Dan Albert
65ac30acda Merge ground strike and SAM location presets.
Locations that should always be SAMs should be done with fixed IADs
locations, so we don't need the separate type.

The generic "ground strike location" needs to be eventually split up
into more specific types, so eventually all of the non-base defense SAMs
will go away.
2020-11-24 17:15:24 -08:00
walterroach
2072b6fa63 Merge pull request #447 from walterroach/caucasus_miz
Caucasus full single miz campaign
2020-11-24 18:37:04 -06:00
walterroach
4f604ba687 replace mizdata 2020-11-24 18:30:56 -06:00
Khopa
b9fe559b42 Forgot to commit F-22A mod payload 2020-11-25 01:17:27 +01:00
Dan Albert
efcdbebda5 Handle additional preset location types.
Missile sites were accidentally excluded, and coastal defenses aren't
being generated yet.
2020-11-24 15:50:51 -08:00
Khopa
8886850c60 Added fictional factions from Discord user Starfire 2020-11-25 00:49:53 +01:00
Khopa
20276e5230 Removed an unused property in controlpoint.py 2020-11-25 00:46:23 +01:00
Khopa
ed96bc83b4 Added support for the F-22A mod. 2020-11-25 00:45:40 +01:00
walterroach
c0147f5eb7 Caucasus full single miz campaign 2020-11-24 17:06:59 -06:00
Khopa
5bf5f024cb More adjustments to ai_flight_planner_db 2020-11-24 23:34:22 +01:00
Khopa
4628e8320a Integrated and reviewed changes to flight planner db from @foxwxl 2020-11-24 23:03:23 +01:00
Khopa
9c1d36d18a Removed radials from control point data. (Not used anymore) 2020-11-24 22:48:40 +01:00
Khopa
789b618e37 Changelog update 2020-11-24 13:10:28 +01:00
Khopa
d0804a6f9e Added missile icons for missiles sites 2020-11-24 13:08:59 +01:00
walterroach
17fe977b06 Merge pull request #437 from walterroach/operation_refactor
Operation refactor
2020-11-23 22:36:46 -06:00
walterroach
60783ca390 remove mypy ignore 2020-11-23 22:28:53 -06:00
walterroach
34a7a37409 Operation refactor cleanup
Fix bug in closest cp algorithm
2020-11-23 22:27:12 -06:00
Dan Albert
e68d2b5deb Plan OCA strikes at heavily populated airfields.
https://github.com/Khopa/dcs_liberation/issues/349
2020-11-23 18:09:05 -08:00
Dan Albert
b0317055e7 Implement OCA strike missions.
https://github.com/Khopa/dcs_liberation/issues/349
2020-11-23 17:58:53 -08:00
Dan Albert
6e0af7c144 Fix names of tasks to not use the enum name. 2020-11-23 17:15:42 -08:00
Dan Albert
9394ed663a Add runway bombing missions.
This allows planning the missions and the missions are functional, but
they will have no effect on future turns yet.
2020-11-23 16:47:58 -08:00
walterroach
967574820f Remove unused conflictgen globals
Remove unused  `Conflict` properties

mypy fixes

Cleanup
2020-11-23 18:14:25 -06:00
walterroach
da17d1e5d1 Change Operation to a static class
Removed always True "event successful"

Add `AirWarEvent` as the primary game `Event` applied to every miz

Cleanup of `FrontLineAttackEvent`

Change `Operation.is_awacs_enabled` to two bools for each side red/blue
Currently controlled by whether an AWACs is available for the faction
(and only ever true for Blue)
2020-11-23 18:14:25 -06:00
Dan Albert
63bdbebcaa Refactor control points into individual classes. 2020-11-23 14:34:58 -08:00
Dan Albert
c67263662d Fix arrival box changing when changing aircraft. 2020-11-23 00:23:57 -08:00
Dan Albert
2484457183 Spawn unused aircraft at airports.
The aircraft that have not been fragged will now be spawned in parking
to provide more targets for OCA strikes. We do this only at airports,
not carriers. The AI has enough trouble taxiing around uncrowded carrier
decks that we probably shouldn't make it harder for them, plus most of
the aircraft will be stored below the flight deck (we allow 90 aircraft
to be stored at the carrier, which certainly will not fit on the flight
deck).

The aircraft are spawned in an uncontrolled state and nothing will
activate them, so aside from the cost of rendering them they shouldn't
affect performance.

Fixes https://github.com/Khopa/dcs_liberation/issues/148
2020-11-23 00:12:42 -08:00
Dan Albert
d7b328b887 Consider trasnfers when counting parking. 2020-11-22 18:44:16 -08:00
Dan Albert
493e53c28f Perform aircraft transfers at the end of the turn. 2020-11-22 18:44:16 -08:00
walterroach
7438c30885 Merge pull request #430 from walterroach/develop_test
Update pydcs submodule
2020-11-22 18:59:35 -06:00
walterroach
fac43ba20b update submodule 2020-11-22 18:46:18 -06:00
walterroach
c2eb243026 Fix bug #400
arg position
2020-11-22 17:43:19 -06:00
Khopa
2adaee8671 Added Soviet Union 1943 2020-11-23 00:15:44 +01:00
walterroach
57edc5678c Merge branch 'develop' of https://github.com/Khopa/dcs_liberation into develop 2020-11-22 16:41:59 -06:00
walterroach
730130b19e Set AGL altitude on target waypoints 2020-11-22 16:41:53 -06:00
Dan Albert
17b0cee507 Add unrestricted SATNAV support to factions.
Enabled for bluefor modern, since they ought to have GPS but seemingly
don't.

This change does nothing until https://github.com/pydcs/dcs/pull/102
lands and we update to it.
2020-11-22 13:28:05 -08:00
Dan Albert
2557383946 Fix type of default map visibility setting.
The UI sets these to the proper enum types; only the default is wrong.
Fix the default and clean up the associated code.

Note that this does minorly break save compatibility and alters default
behavior, since previously we were ignoring the default option. Ignoring
the default looks unintentional since there is no explicit "don't force
this option" setting in the UI.

Existing saves can be fixed simply by changing this option to something
else and then back.
2020-11-22 13:28:05 -08:00
walterroach
29d3b5dfc6 Add missing P-47 icons 2020-11-22 02:12:41 -06:00
Dan Albert
f6fad30852 Add unit name -> Liberation object map.
Generated units are added to this during mission generation so we can
map destroyed units back to the data that generated them. Currently only
implemented for aircraft as a proof of concept.
2020-11-21 21:01:46 -08:00
walterroach
d5a081a15f Merge pull request #422 from walterroach/groundwar
Operation refactor and conflictgen cleanup
2020-11-21 21:00:51 -06:00
walterroach
6147e9ac96 mypy cleanup 2020-11-21 19:31:30 -06:00
walterroach
b32ca4f92f merge 2020-11-21 18:57:15 -06:00
walterroach
b57dd51f86 Merge branch 'develop' into frontline 2020-11-21 18:55:56 -06:00
walterroach
fc6ca162af Merge branch 'develop_2_2_x' into develop 2020-11-21 18:24:36 -06:00
walterroach
58ba9e9d1d bad arg 2020-11-21 18:17:35 -06:00
walterroach
939b6c468d refactoring 2020-11-21 17:19:54 -06:00
Dan Albert
bf7df6721a Fix turn 0.
`Game.initialize_turn` doesn't run on turn zero, so we have to set up
the unit order events when we create the `Game`.
2020-11-21 15:00:49 -08:00
walterroach
f6e0dbbb6a operation refactoring 2020-11-21 17:00:22 -06:00
Dan Albert
851c2d88a9 Factor out Lua generation. 2020-11-21 14:55:00 -08:00
Dan Albert
200c13dc31 Remove dead code. 2020-11-21 13:27:19 -08:00
walterroach
8889e35f9e Merge branch 'develop' into frontline 2020-11-21 12:50:44 -06:00
Khopa
398fa1e73d Fixed warehouse building not generating any rewards 2020-11-21 18:31:12 +01:00
walterroach
316f73138c Merge branch 'develop' into frontline 2020-11-21 11:19:31 -06:00
walterroach
708b615ad7 Merge conflict 2020-11-21 11:18:30 -06:00
walterroach
866ff78518 Merge branch 'develop_2_2_x' into frontline 2020-11-21 11:15:36 -06:00
Khopa
f0480b033f Removed debug print 2020-11-21 17:30:31 +01:00
Khopa
4d19548736 Changelog update / consistency with 2.X branch. 2020-11-21 17:05:33 +01:00
Khopa
fcf45554ef Fixed culling display and added setting to include/exclude carriers from culling area. 2020-11-21 17:01:50 +01:00
Khopa
799b0fae94 Merge branch 'develop' of https://github.com/khopa/dcs_liberation into develop 2020-11-21 14:46:01 +01:00
Dan Albert
0d95716545 Correct type annotations. 2020-11-20 21:40:31 -08:00
walterroach
8c5b808eba inherent resolve frontlines 2020-11-20 20:28:59 -08:00
walterroach
007dcf548e remove dead code 2020-11-20 21:02:11 -06:00
walterroach
9a640bf7eb inherent resolve frontlines 2020-11-20 20:38:32 -06:00
walterroach
edd02d9dd6 Merge branch 'develop' into frontline 2020-11-20 20:32:50 -06:00
Dan Albert
75edbb62f1 Clean up available parking code a bit.
Moved more into the `ControlPoint`.
2020-11-20 18:31:19 -08:00
walterroach
2b6227f3b1 remove dead code 2020-11-20 20:29:30 -06:00
Dan Albert
c4b8a41742 Move calculation of aircraft amounts into game.
The planner needs to know how much space is still expected to be
available next turn.
2020-11-20 18:25:03 -08:00
Dan Albert
f8b2dbe283 Move unit delivery ownership out of the UI.
Breaks save compat, but we need to have this knowledge outside the UI so
we can know whether or not we can ferry aircraft to the airfield without
overflowing parking.
2020-11-20 18:04:27 -08:00
Dan Albert
20091292f4 Remove dead code. 2020-11-20 17:37:20 -08:00
Dan Albert
a594f45aae Pick divert airfields when planning.
https://github.com/Khopa/dcs_liberation/issues/342
2020-11-20 17:29:58 -08:00
Ignacio Muñoz Fernandez
d394d01ea8 fix: conditional value at passTurn on QTopPanel 2020-11-20 17:12:13 -08:00
Ignacio Muñoz Fernandez
70d982b0ed fix: Pass Turn and Take Off Buttons when no game is loaded 2020-11-20 17:12:13 -08:00
Dan Albert
ae68a35a1a Remove save compat since it's breaking anyway.
Removal of old paths/names for things that no longer exist.
2020-11-20 17:06:01 -08:00
Dan Albert
a9fcfe60f4 Add arrival/divert airfield selection.
Breaks save compat because it adds new fields to `Flight` that have no
constant default. Removing all of our other save compat at the same
time.

Note that player flights with a divert point will have a nav point for
their actual landing point. This is because we place the divert point
last, and DCS won't let us have a land point anywhere but the final
waypoint. It would allow a LandingReFuAr point, but they're only
generated for player flights anyway so it doesn't really matter.

Fixes https://github.com/Khopa/dcs_liberation/issues/342
2020-11-20 16:16:00 -08:00
Ignacio Muñoz Fernandez
c3b028ef4b fix: fixes #325 for version 2.2.x 2020-11-20 03:50:58 -08:00
C. Perreau
833399f068 Merge pull request #409 from kavinsky/fix/issue-325
fix: fixes #325 Budget calculation for both factions
2020-11-20 12:44:16 +01:00
Ignacio Muñoz Fernandez
5695cf4ac5 fix: fixes #325 Budget calculation and enemy reinforcement calculation now takes into account the destroyed buildings 2020-11-20 12:38:06 +01:00
Ignacio Muñoz Fernandez
1553e5efd5 chore: syntax cleaning and redundant variable cleanup 2020-11-20 02:57:23 -08:00
Ignacio Muñoz Fernandez
0f1b396dd2 chore: variable name changes 2020-11-20 02:57:23 -08:00
Ignacio Muñoz Fernandez
976ee51bf5 feat: Added Financial Income to GroundObjectMenu building window 2020-11-20 02:57:23 -08:00
Dan Albert
7c22f6e83b Change mach function to take altitude in meters.
All of the callers are passing altitude in meters because that's what
pydcs uses. This still returns knots which makes it extra weird, but
that's what almost all of the callers expect.

It's probably a good idea to introduce some explicit types for the
various distance and speed units to avoid these sorts of mistakes.
2020-11-20 02:19:38 -08:00
Dan Albert
18b6f7b84c Add off-map spawn locations.
The AI isn't making use of these yet, but it's not smart enough to do so
anyway.

Would benefit from an icon to differentiate it on the map.

I'm stretching the definition of "control point" quite a bit. We might
want to put a class above `ControlPoint` for `AirSpawnLocation` to
represent types of spawn locations that can't be captured and don't have
ground objectives.

Fixes https://github.com/Khopa/dcs_liberation/issues/274
2020-11-20 02:19:03 -08:00
Dan Albert
206d09f7f8 Maybe correct fishbed radios.
Maybe fixes https://github.com/Khopa/dcs_liberation/issues/377
2020-11-20 00:20:01 -08:00
Dan Albert
4e910c4b09 Maybe correct fishbed radios.
Maybe fixes https://github.com/Khopa/dcs_liberation/issues/377
2020-11-20 00:18:00 -08:00
Dan Albert
bc3cd50a6c Add support for required SAMs in campaigns.
"Required" SAMs (designative by redfor long range SAM launchers in the
ME) will always be spawned during campaign generation. This makes it
possible to build a semi-guaranteed IADS (the exact type of SAM is
dependent) on the choice of faction.

Requierd SAMs will consume the slots of random SAMs during generation.
Later we should differentiate between strategic SAMs like SA-10s and
tactical SAMs like SA-11s so we can fill in the medium range SAMs at
random locations among the fixed long range SAMs.
2020-11-19 23:47:07 -08:00
Dan Albert
cecf611f91 Add docs for PresetLocations. 2020-11-19 21:59:36 -08:00
Dan Albert
5e4802f05e Return base defense locations to pool on capture.
When a base is captured we clear its defenses. Those locations need to
be returned to the preset location pool so they can be used for the new
base defenses.
2020-11-19 21:50:29 -08:00
Dan Albert
1e70e654ed Don't attempt loading miz files as JSON. 2020-11-19 21:32:44 -08:00
Dan Albert
20054b9825 Update Inherent Resolve campaign presets.
None of these are placed precisely yet, but they're useful for testing.
2020-11-19 21:29:21 -08:00
Dan Albert
5fb6a53cbd Add preset configuration for offshore types. 2020-11-19 21:28:57 -08:00
Dan Albert
ff751c30f9 Add preset configuration for strike targets. 2020-11-19 21:20:40 -08:00
Dan Albert
c1614ad5a7 Add TODOs for carrier CP names. 2020-11-19 21:19:53 -08:00
Dan Albert
13e372159a Change default settings to match UI defaults.
Doesn't affect the thing players see, but corrects the defaults when
using the command line mission generator.
2020-11-19 21:19:12 -08:00
Dan Albert
1ee0aafd9a Unify TGO location selection.
We currently have three methods of choosing locations for TGOs:

1. From the campaign miz
2. From the per-CP mizdata files
3. Randomly

Move the selection among these sources into a single place and use it
everywhere that we search for a TGO location.

Longer term methods 2 and 3 will be removed.
2020-11-19 21:09:33 -08:00
Dan Albert
df80ec635f Add a new miz file based campaign generator.
Defining a campaign using a miz file instead of as JSON has a number of
advantages:

* Much easier for players to mod their campaigns.
* Easier to see the big picture of how objective locations will be laid
  out, since every control point can be seen at once.
* No need to associate objective locations to control points explicitly;
  the campaign generator can claim objectives for control points based
  on distance.
* Easier to create an IADS that performs well.
* Non-random campaigns are easier to make.

The downside is duplication across campaigns, and a less structured data
format for complex objects. The former is annoying if we have to fix a
bug that appears in a dozen campaigns. It's less an annoyance for
needing to start from scratch since the easiest way to create a campaign
will be to copy the "full" campaign for the given theater and prune it.

So far I've implemented control points, base defenses, and front lines.
Still need to add support for non-base defense TGOs.

This currently doesn't do anything for the `radials` property of the
`ControlPoint` because I'm not sure what those are.
2020-11-19 16:55:21 -08:00
C. Perreau
6524286f04 Merge pull request #403 from Khopa/develop_2_2_x
Release 2.2.1
2020-11-20 00:29:15 +01:00
Khopa
87248fec53 Changelog update 2020-11-19 22:00:56 +01:00
Khopa
6470d25d18 About dialog update 2020-11-19 21:59:01 +01:00
Khopa
b7634a8ac3 About dialog update 2020-11-19 21:54:33 +01:00
walterroach
434755a620 Update README.md 2020-11-19 14:23:43 -06:00
walterroach
3eb2529b0b Fix #402 2020-11-19 13:20:05 -06:00
Khopa
e6e4cca076 Made it possible to setup custom liveries in faction files. 2020-11-19 20:18:26 +01:00
Khopa
8d57bbc777 Changelog update 2.2.1 2020-11-19 20:17:11 +01:00
Khopa
483db564f9 Added F-14A support 2020-11-19 20:14:21 +01:00
Khopa
63d5862319 Pydcs module update 2020-11-19 20:13:15 +01:00
Dan Albert
365b379798 Update changelog for 2.2.1. 2020-11-19 00:45:03 -08:00
Dan Albert
20f97e48a9 Update changelog for 2.2.1. 2020-11-19 00:42:54 -08:00
Dan Albert
fd473f0a46 Fix custom waypoints.
Like with deleting waypoints, these will degrade the flight plan to the
2.1 behavior.

Ascend/descend points aren't in use any more, so I removed those.
2020-11-19 00:36:43 -08:00
Dan Albert
94c5ed8bdc Fix custom waypoints.
Like with deleting waypoints, these will degrade the flight plan to the
2.1 behavior.

Ascend/descend points aren't in use any more, so I removed those.
2020-11-19 00:29:05 -08:00
Dan Albert
4b7b4bf110 Allow deleting waypoints.
In almost every case this leaves us with a flight plan we can't reason
about, so it gets degraded to `CustomFlightPlan`. The exception is when
deleting a target point when there are other target points remaining.
This probably gets people using this feature back to what they want
though, which is essentially the 2.1 behavior.

Fixes https://github.com/Khopa/dcs_liberation/issues/393
2020-11-18 23:44:16 -08:00
Dan Albert
a816877d08 Allow deleting waypoints.
In almost every case this leaves us with a flight plan we can't reason
about, so it gets degraded to `CustomFlightPlan`. The exception is when
deleting a target point when there are other target points remaining.
This probably gets people using this feature back to what they want
though, which is essentially the 2.1 behavior.

Fixes https://github.com/Khopa/dcs_liberation/issues/393
2020-11-18 23:43:01 -08:00
Dan Albert
2d56ae1cb6 Avoid cases where empty flights could be created.
Fixes https://github.com/Khopa/dcs_liberation/issues/373
2020-11-18 22:04:24 -08:00
Dan Albert
a223da8f99 Avoid cases where empty flights could be created.
Fixes https://github.com/Khopa/dcs_liberation/issues/373
2020-11-18 22:03:33 -08:00
Dan Albert
216adcc35a Add infor about delayed flights to the start page.
Fixes https://github.com/Khopa/dcs_liberation/issues/398
2020-11-18 21:27:19 -08:00
Dan Albert
8d485d5fa2 Delay player CV flight when their settings say so.
Fixes https://github.com/Khopa/dcs_liberation/issues/375

This also fixes a problem where we're spawning non-cold start planes in
an uncontrolled state. The ME won't let us do this, so we probably
shouldn't be doing that.
2020-11-18 21:27:18 -08:00
Dan Albert
fa549fcf94 Don't delay player flights with short delays.
Not much point in delaying humans 8 seconds.

Fixes https://github.com/Khopa/dcs_liberation/issues/397
2020-11-18 21:27:18 -08:00
Dan Albert
98fd707aea Add infor about delayed flights to the start page.
Fixes https://github.com/Khopa/dcs_liberation/issues/398
2020-11-18 21:26:14 -08:00
Dan Albert
5928f29f11 Delay player CV flight when their settings say so.
Fixes https://github.com/Khopa/dcs_liberation/issues/375

This also fixes a problem where we're spawning non-cold start planes in
an uncontrolled state. The ME won't let us do this, so we probably
shouldn't be doing that.
2020-11-18 19:34:38 -08:00
Dan Albert
c3ebabbe44 Don't delay player flights with short delays.
Not much point in delaying humans 8 seconds.

Fixes https://github.com/Khopa/dcs_liberation/issues/397
2020-11-18 19:27:03 -08:00
Khopa
29fd094dd0 Added F-14A support 2020-11-19 00:34:24 +01:00
walterroach
f7966b8d8c fix bug #394 2020-11-18 16:37:30 -06:00
Khopa
ee2f4ecbc8 Pydcs submodule update 2020-11-18 21:51:47 +01:00
Khopa
b08f6cad1d French translations for jinja templates, might be useful later. 2020-11-18 21:50:58 +01:00
Dan Albert
e851223733 Fix mypy. 2020-11-18 01:58:35 -08:00
Dan Albert
1f12546ff4 Add command line option to generate a new game.
Saves us a ton of clicks while developing the campaign generator.
2020-11-17 18:56:35 -08:00
Dan Albert
8345063e84 Move theater into game. 2020-11-17 18:11:33 -08:00
walterroach
482bedd739 Merge pull request #381 from foxwxl/patch-1
Create briefingtemplate_CN.j2
2020-11-17 15:47:27 -06:00
Dan Albert
7d7a334418 Fix mypy. 2020-11-17 02:04:38 -08:00
Dan Albert
69dbe62b70 Plan anti-ship missions automatically. 2020-11-17 01:08:06 -08:00
foxwxl
f61167cedf Create briefingtemplate_CN.j2
Briefing template CN version
2020-11-17 16:52:52 +08:00
Dan Albert
2ac92a75a4 Plan BAI missions automatically. 2020-11-17 00:36:41 -08:00
Dan Albert
fe80a9fd08 Make garrisons not SAMs.
The AI planner was planning DEAD against tanks because technically they
were SAMs.
2020-11-17 00:25:45 -08:00
Dan Albert
505af7635f Include EWRs in DEAD targets. 2020-11-16 23:53:25 -08:00
Dan Albert
426dc69e1d Allow DEAD on EWRs. 2020-11-16 23:53:25 -08:00
Dan Albert
082e8c062c Add anti-ship missions.
The only practical difference between this and BAI is that the target is
floating, so this mostly shares its implementation with BAI.

Fixes https://github.com/Khopa/dcs_liberation/issues/350
2020-11-16 23:29:19 -08:00
Dan Albert
9f2409bb9e Make sweep flights actually sweep flights.
DCS group task doesn't alter behavior, but it does alter which tasks are
available to the group.
2020-11-16 22:47:32 -08:00
Dan Albert
14dd8e43a4 Improve waypoint names for BAI targets. 2020-11-16 22:37:19 -08:00
Dan Albert
9fb33526a7 Add BAI missions.
BAI is used for attacking ground vehicles as opposed to buildings like
strike does, and not air defenses like DEAD does. Unlike strike, BAI is
tolerant of moving targets.

Fixes https://github.com/Khopa/dcs_liberation/issues/216
2020-11-16 21:11:18 -08:00
Dan Albert
8bd00bf450 Move mission type compatibility to the target.
This was also needed in other parts of the UI and is easier to implement
in the target class anyway.

Note that DEAD is now properly restricted to air defense targets.

Also added error boxes to the UI for when planning fails on an invalid
target.
2020-11-16 21:11:14 -08:00
walterroach
f3553ced78 Merge pull request #366 from walterroach/new_frontline
New frontline paths.  First step in #287
2020-11-16 22:13:33 -06:00
Khopa
a52dc43c9e MAde it possible to setup liveries in faction files. 2020-11-16 22:02:30 -06:00
Khopa
0b6b40a358 Pydcs repository update : Fixed issue with clipped wing variant of the spitfire not being seen as a flyable module 2020-11-16 22:02:27 -06:00
Dan Albert
f6371d2ef1 Further improve split/join positioning. 2020-11-16 22:02:22 -06:00
walterroach
ecd073e31d typing and comment cleanup 2020-11-16 22:01:49 -06:00
Dan Albert
b7d160631a Further improve split/join positioning.
(cherry picked from commit dc235f36c8)
2020-11-16 19:15:41 -08:00
Dan Albert
dc235f36c8 Further improve split/join positioning. 2020-11-16 19:15:11 -08:00
Khopa
7503c1e1e9 Pydcs repository update : Fixed issue with clipped wing variant of the spitfire not being seen as a flyable module 2020-11-17 00:06:10 +01:00
walterroach
253e8a209c fix return statement 2020-11-16 17:02:04 -06:00
Khopa
e26b692631 MAde it possible to setup liveries in faction files. 2020-11-16 23:57:12 +01:00
walterroach
658d808524 Merge branch 'develop' into new_frontline 2020-11-16 16:25:21 -06:00
Dan Albert
28e00055ab Differentiate BARCAP and TARCAP.
Previously the only difference between these was the objective type:
TARCAP was for front lines and BARCAP was for everything else.

Now BARCAP is for friendly areas and TARCAP is for enemy areas. The
practical difference between the two is that a TARCAP package is like
the old front line CAP in that it will adjust its patrol time to match
the package if it can, and it will also arrive two minutes ahead of the
rest of the package to clear the area if needed.
2020-11-16 00:32:50 -08:00
Dan Albert
8eef1eaa7c Fix mypy issue. 2020-11-15 23:59:20 -08:00
Dan Albert
e361a857a4 Only engage fighters with sweep. 2020-11-15 23:53:12 -08:00
Dan Albert
d369ce8847 Add fighter sweep tasks.
Fighter sweeps arrive at the target ahead of the rest of the package
(currently a fixed 5 minute lead) to clear out enemy fighters and then
RTB.

Fixes https://github.com/Khopa/dcs_liberation/issues/348
2020-11-15 23:49:14 -08:00
walterroach
d05897edcb Change default CAS loadout for Viggen
Reported that AI can't hit the broad side of a barn with the rockets.
2020-11-15 22:59:46 -06:00
walterroach
1d98432c57 Briefing tweak
Fixes frontline info repeating when player has no vehicles.
2020-11-15 22:59:40 -06:00
walterroach
e60166dc89 Change default CAS loadout for Viggen
Reported that AI can't hit the broad side of a barn with the rockets.
2020-11-15 22:50:14 -06:00
walterroach
87afc2fcef bezier frontline display
Start on refactoring and cleanup of QLiberationMap.py
2020-11-15 22:08:30 -06:00
walterroach
bd1457c435 Complete Caucasus Full Map frontline data. 2020-11-15 22:08:30 -06:00
walterroach
c1f88b4a5f frontline refactoring
`FrontLine` is tightly coupled with `ConflictTheater`.
  Moved into the same module to prevent circular imports.

Moved `ConflictTheater.frontline_data` from class var
to instance var to allow save games to have different
versions of frontlines.
2020-11-15 22:08:18 -06:00
walterroach
a080d4b692 Briefing tweak
Fixes frontline info repeating when player has no vehicles.
2020-11-15 22:06:05 -06:00
walterroach
c20e9e19cb Add cheat for more easily debugging frontline 2020-11-15 20:56:09 -06:00
Dan Albert
d9056acc6d Improve hold/split/join point positioning.
This also removes ascend/descend waypoints. They don't seem to be
helping at all. The AI already gets an implicit ascend waypoint (they
won't go to waypoint one until they've climbed sufficiently), and
forcing unnecessary sharp turns toward the possibly mispredicted ascent
direction can mess with the AI. It's also yet another variable to
contend with when planning hold points, and hold points do essentially
the same thing.

Fixes https://github.com/Khopa/dcs_liberation/issues/352.

(cherry picked from commit 21cd764f66)
2020-11-15 18:53:42 -08:00
Dan Albert
21cd764f66 Improve hold/split/join point positioning.
This also removes ascend/descend waypoints. They don't seem to be
helping at all. The AI already gets an implicit ascend waypoint (they
won't go to waypoint one until they've climbed sufficiently), and
forcing unnecessary sharp turns toward the possibly mispredicted ascent
direction can mess with the AI. It's also yet another variable to
contend with when planning hold points, and hold points do essentially
the same thing.

Fixes https://github.com/Khopa/dcs_liberation/issues/352.
2020-11-15 18:53:17 -08:00
Khopa
dfc31dfd5c Changelog update
(cherry picked from commit 8ffbf32677)
2020-11-15 18:53:13 -08:00
Khopa
8ffbf32677 Changelog update 2020-11-15 16:14:18 +01:00
Khopa
6e2124252c Added full persian gulf map by Plob 2020-11-15 15:58:00 +01:00
Khopa
3dd07b8c23 Added factions made by Discord user HerrTom 2020-11-15 15:57:59 +01:00
Khopa
2e067aada6 Added full persian gulf map by Plob 2020-11-15 15:57:36 +01:00
Khopa
78cd60f3df Added factions made by Discord user HerrTom 2020-11-15 15:50:29 +01:00
Dan Albert
2891649531 Fix pyinstaller spec for release.
final and buildnumber are optional files. Move them into resources to
avoid naming them explicitly.

(cherry picked from commit fae9650f56)
2020-11-14 13:13:18 -08:00
Dan Albert
4b40739918 Fix versioning for release builds.
(cherry picked from commit 9019cbfd2b)
2020-11-14 13:13:18 -08:00
Dan Albert
96c401e1b9 Fix pyinstaller spec for release.
final and buildnumber are optional files. Move them into resources to
avoid naming them explicitly.

(cherry picked from commit fae9650f56)
2020-11-14 13:06:59 -08:00
Dan Albert
fad132dcca Fix versioning for release builds.
(cherry picked from commit 9019cbfd2b)
2020-11-14 13:06:01 -08:00
Dan Albert
fae9650f56 Fix pyinstaller spec for release.
final and buildnumber are optional files. Move them into resources to
avoid naming them explicitly.
2020-11-14 13:01:11 -08:00
C. Perreau
e26e7f53c5 Merge pull request #367 from Khopa/develop_2_2_x
Release 2.2.0
2020-11-14 21:46:59 +01:00
Khopa
696710bf41 Merge remote-tracking branch 'khopa/master' into develop_2_2_x
# Conflicts:
#	changelog.md
#	game/operation/operation.py
#	gen/aircraft.py
#	gen/groundobjectsgen.py
#	qt_ui/uiconstants.py
2020-11-14 21:40:07 +01:00
Dan Albert
9019cbfd2b Fix versioning for release builds. 2020-11-14 12:39:50 -08:00
Khopa
f8735927bf Version string 2.2.0 2020-11-14 21:32:27 +01:00
Khopa
16cfc4e945 Changelog update for 2.2.0 2020-11-14 21:32:05 +01:00
Khopa
85f931316a Changelog update for 2.2.0 2020-11-14 21:29:03 +01:00
Khopa
717ea05d38 Pulled latest pydcs version 2020-11-14 21:24:45 +01:00
Khopa
3987274764 Pulled latest pydcs version 2020-11-14 21:24:15 +01:00
Khopa
73a97f9c2a Added High Digit Sams mods pydcs export extensions, maybe for later use. 2020-11-14 20:27:38 +01:00
walterroach
80612ba97d Merge branch 'develop_2_2_x' into develop 2020-11-14 12:24:26 -06:00
walterroach
c4d2b92e34 Merge branch 'develop_2_2_x' into new_frontline 2020-11-14 12:18:40 -06:00
walterroach
169fba9ab8 fixes viggen client waypoints
Only one target waypoint created when flight is Viggen client flight.

    M Waypoints not explicitly set anymore
      (they don't need to be when waypoint has a TOT)
2020-11-14 12:17:15 -06:00
Khopa
0b902e19ee Adding Persian gulf started mizdata files before release 2020-11-14 14:48:48 +01:00
Khopa
ab26a76789 A-20G won't level bomb unit groups 2020-11-14 14:19:34 +01:00
Khopa
1f43fbe16e A-20G won't level bomb unit groups 2020-11-14 14:17:07 +01:00
Dan Albert
f29cb99530 Update the README with a more recent screenshot.
(cherry picked from commit ef84703da9)
2020-11-14 01:39:47 -08:00
Dan Albert
ef84703da9 Update the README with a more recent screenshot. 2020-11-14 01:39:20 -08:00
Dan Albert
9e32ea7413 Handle inventory when selling aircraft.
This still leaves a bit to be desired, namely that selling aircraft
happens immediately but buying aircraft takes a turn. However, that's
how this behaved before, so this restores the 2.1 behavior. Worth
investigating further in the future.

(cherry picked from commit 75769df8e2)
2020-11-14 00:12:02 -08:00
Dan Albert
75769df8e2 Handle inventory when selling aircraft.
This still leaves a bit to be desired, namely that selling aircraft
happens immediately but buying aircraft takes a turn. However, that's
how this behaved before, so this restores the 2.1 behavior. Worth
investigating further in the future.
2020-11-14 00:11:19 -08:00
Dan Albert
95fd4cab05 Fix error box in flight creation.
(cherry picked from commit a81254cd18)
2020-11-13 21:01:30 -08:00
Dan Albert
a81254cd18 Fix error box in flight creation. 2020-11-13 21:00:55 -08:00
Dan Albert
4cff838de0 Develop is now 2.3. 2020-11-13 18:46:59 -08:00
walterroach
3838b3ca4f More frontlines 2020-11-13 17:03:46 -06:00
walterroach
7dc3e041c8 Merge branch 'new_frontline' of https://github.com/walterroach/dcs_liberation into new_frontline 2020-11-13 09:12:40 -06:00
walterroach
33b92423d8 cleanup comments
remove unnecessary method call
2020-11-13 09:12:08 -06:00
walterroach
6237fffa5a cleanup comments 2020-11-13 09:03:02 -06:00
walterroach
0b4e2d3b6b Merge branch 'develop' into new_frontline 2020-11-13 08:58:43 -06:00
Dan Albert
c4d08fa7b7 Fix handling of non-AA units in AA groups.
Some units in pydcs have detection_range and threat_range
defined, but explicitly set to None.
2020-11-13 02:57:51 -08:00
Dan Albert
cec28351e7 Remove dead code. 2020-11-13 01:20:59 -08:00
walterroach
398630d51e initial tooling 2020-11-12 21:51:54 -06:00
walterroach
61400ba726 caucasus test data 2020-11-12 21:47:54 -06:00
walterroach
33885e2216 initial multi segment frontline implementation 2020-11-12 21:47:13 -06:00
walterroach
5719b136fe sanity check 2020-11-12 19:16:01 -06:00
walterroach
ede5ee60c3 frontline 2020-11-12 18:21:37 -06:00
Khopa
9620ac7e7e Updated mizdata on Caucasus map 2020-11-12 21:08:44 +01:00
Dan Albert
c0bfdbf4bb Update client counts when packages are changed.
Fixes https://github.com/Khopa/dcs_liberation/issues/345
2020-11-11 15:06:57 -08:00
Dan Albert
efb544a303 Fix bad argument type. 2020-11-11 15:05:58 -08:00
Dan Albert
adfc4b7244 Don't plan DEAD missions against scuds.
Fixes https://github.com/Khopa/dcs_liberation/issues/355
2020-11-11 14:57:39 -08:00
Khopa
7a5ce98569 Added mizdata preset locations for the channel map. 2020-11-11 14:27:13 +01:00
Khopa
1fcceb0901 Mizdata for the channel map 2020-11-11 02:53:12 +01:00
Khopa
22c552053f Made Patriot and Sa-10 sites more compact, so it's easier to find valid position to generate them on the map. 2020-11-11 01:03:57 +01:00
Khopa
818c679d4f Merge branch 'develop' of https://github.com/khopa/dcs_liberation into develop 2020-11-10 23:48:45 +01:00
Khopa
3ff36c45aa Ground Object and Sam sites locations are chosen from a set of preset location when possible. 2020-11-10 23:48:27 +01:00
Dan Albert
623d461b06 Fix delays of AI flights.
Fixes https://github.com/Khopa/dcs_liberation/issues/354
2020-11-10 13:18:06 -08:00
Dan Albert
7535013848 Fix #85: update the max aircraft available.
https://github.com/Khopa/dcs_liberation/issues/85
2020-11-10 00:52:19 -08:00
Dan Albert
a63bac8826 Don't display scud sites as SAM threat range. 2020-11-09 21:51:55 -08:00
Dan Albert
d2c831c4ee Handle inventory for changing flight size. 2020-11-09 21:29:47 -08:00
walterroach
840b5ce071 Change CAS ROE 2020-11-09 20:36:07 -06:00
walterroach
be6abc0025 Don't display attack button for dead base groups 2020-11-09 15:48:34 -06:00
walterroach
ef585c59dd Merge pull request #343 from walterroach/missed_events
Missed Debrief Events
2020-11-09 15:36:09 -06:00
walterroach
16d9c1ccad Add EWR to destruction events in debrief
Also impacted reinforcement generation on turn after EWR destroyed.
2020-11-09 14:49:44 -06:00
walterroach
9a9ef78583 Fix statics not being killed in debrief 2020-11-09 13:34:56 -06:00
Dan Albert
fe7ee5b610 Add more to the changelog. 2020-11-09 00:55:57 -08:00
walterroach
680804040a Update changelog. 2020-11-09 00:43:53 -08:00
Dan Albert
0d4fe73daa Add ships to skynet EWR. 2020-11-08 23:51:28 -08:00
Dan Albert
407190c6c5 Use EWRs for EWR. 2020-11-08 22:17:06 -08:00
Dan Albert
73998dbde0 Show EWRs as detection range, not threat range. 2020-11-08 19:03:47 -08:00
Dan Albert
8827f7df34 Cleanup Lua plugin implementation.
* Move the UI code out of the plugin logic.
* Add types where needed.
* Move into game package.
* Improve error handling.
* Simplify settings behavior.
* Don't load disabled plugins.
* Remove knowledge of non-base plugins from game generation.

Fixes https://github.com/Khopa/dcs_liberation/issues/311
2020-11-08 17:53:52 -08:00
Khopa
4c394a9e2d Merge branch 'develop' of https://github.com/khopa/dcs_liberation into develop 2020-11-08 23:24:24 +01:00
Khopa
5946fc7404 Improved campaign selection screen in new game wizard. 2020-11-08 23:24:10 +01:00
Dan Albert
5b8ecb2c14 Add radio data for the MiG-21.
Fixes https://github.com/Khopa/dcs_liberation/issues/49
2020-11-08 13:25:58 -08:00
Khopa
61253e4d4d Show name instead of Id of ground units in faction selection screen. 2020-11-08 22:02:16 +01:00
Khopa
2c0ca5803f Improved the faction selection screen in the new game wizard. 2020-11-08 21:57:26 +01:00
Khopa
690f3d0f13 B-52 default strike loadout use Mk-84 2020-11-08 13:52:43 +01:00
walterroach
42c259bc58 Fix briefing whitespace 2020-11-08 01:28:28 -08:00
walterroach
11426a0713 Revert "more explicity waypoint namespace"
This reverts commit 314be482f9.
2020-11-07 21:16:10 -08:00
walterroach
e6af1b8645 more explicity waypoint namespace 2020-11-07 21:16:10 -08:00
walterroach
103f18191d Move viggen tot check down to pydcs waypoint only. 2020-11-07 21:16:10 -08:00
walterroach
fb312236a2 rework special waypoints 2020-11-07 21:16:10 -08:00
Dan Albert
c850c0095d Add DEAD to the flight recreate types. 2020-11-07 17:07:45 -08:00
Dan Albert
58481268f7 Fix mypy issue. 2020-11-07 16:33:00 -08:00
Dan Albert
11604671f8 Fix bad stagger interval calculation.
Was using the interval from mission start to latest rather than from
earliest to latest, so this could sometimes be off by a bit and cause
us to not generate enough start times.
2020-11-07 16:25:56 -08:00
Dan Albert
e8feded4c3 Add EWR generation.
Fixes https://github.com/Khopa/dcs_liberation/issues/66
2020-11-07 16:20:58 -08:00
walterroach
18f9b38d25 Keep only last base capture per base in debrief 2020-11-07 15:14:02 -08:00
Dan Albert
676eea3ccc Don't plan missions against dead targets.
Fixes https://github.com/Khopa/dcs_liberation/issues/314
2020-11-07 13:59:52 -08:00
Dan Albert
ee113d080e Merge branch 'theater-refactor' into develop 2020-11-07 13:39:35 -08:00
Dan Albert
8bc69415a7 Clean up base defense generation. 2020-11-07 13:38:58 -08:00
Dan Albert
3979ee57ff Cleanup oddities of CP generation.
* Don't spawn missile sites or ground locations for CVs/LHAs.
* Don't spawn ground locations around CVs/LHAs.
* Spawn AA sites even if the faction has no buildings defined.
2020-11-07 13:38:58 -08:00
Dan Albert
c2ee169d16 Fix regeneration of base defenses on capture.
I messed up the counting here and was counting *every* object rather
than just the base defenses, so it was very unlikely that we'd hit
base defenses on the second object, which is the only index valid for
SAM generation. That logic maybe needs to be different, but this
restores the previous behavior.
2020-11-07 13:38:58 -08:00
Dan Albert
56b51c85bb Fix targeting SAMs with strike missions.
The changes for Skynet unfortunately broke this because the names used
by the TGO and the name of the group itself were no longer in sync.
This deserves a larger cleanup where we decouple that naming
requirement (TGOs don't need a lot of the data they currently have),
but this works until we have the time to do that.

Fixes https://github.com/Khopa/dcs_liberation/issues/298
2020-11-07 13:37:52 -08:00
Dan Albert
853ee5aac4 Cleanup Theater Ground Object.
A whole bunch of this data is redundant.
2020-11-07 13:10:06 -08:00
Dan Albert
4cf406aefa Defer game load to after UI initialization.
This removes both the double load that happens on startup and also
makes it possible to get the UI to create a new game if the existing
default.liberation is not compatible.
2020-11-07 13:03:25 -08:00
Khopa
1abb341cb6 Added SCUD missiles sites 2020-11-07 19:20:08 +01:00
Khopa
44dce9598c Added support for Tetrarch and Daimler Armoured car. (New WW2 units) 2020-11-07 17:43:30 +01:00
Khopa
aefc8685a1 Added new FLAK units to WW2 allies factions 2020-11-07 17:40:26 +01:00
Khopa
26761342f5 Changelog update 2020-11-07 16:25:23 +01:00
Khopa
ca777bcebb Tuned base defense location generator for big airbases. (Some would end up without any base defenses before) 2020-11-07 16:23:46 +01:00
Khopa
5742075ff2 Syria terrain update for Incirlik airbase exlusions zones. 2020-11-07 16:22:59 +01:00
Khopa
c7a6ec9691 Changelog update 2020-11-07 03:27:03 +01:00
Khopa
e0153cfa6a Added Freya EWR sites to WW2 german factions 2020-11-07 03:21:53 +01:00
Khopa
3fd5e1bae7 Added newest P-47 variants to WW2 factions 2020-11-07 02:31:29 +01:00
Khopa
d9511a7edd Improved Strike mission generation.
- B52, A-20, and Tu-22 will level bomb targets
- When there is an unit group as target, all the units are now engaged instead of only the first unit of the group
2020-11-07 02:13:17 +01:00
C. Perreau
040db055fd Merge pull request #309 from DanAlbert/theater-refactor
Refactor game and ground object generation.
2020-11-06 20:33:19 +01:00
C. Perreau
b9f8cfd10d Merge pull request #319 from walterroach/fix_flight_edit
Fixes #312
2020-11-06 20:18:33 +01:00
Khopa
ff46556927 Fixed russia 1990 faction. Added Tu_22M3. 2020-11-06 18:56:25 +01:00
walterroach
d1815a3d6e Fixes #312 2020-11-06 10:38:29 -06:00
Dan Albert
fdfa4827ab Remove unused and broken scripts. 2020-11-05 21:39:49 -08:00
Dan Albert
5d579ccef9 Add hints for creating packages. 2020-11-05 17:08:34 -08:00
Dan Albert
4145d5578e Refactor game and ground object generation.
No real functional improvements yet, just reorganizing to make
improvements easier.
2020-11-05 16:09:34 -08:00
Dan Albert
43eb041bb8 Fix escort planning for racetracks. 2020-11-05 15:17:45 -08:00
Dan Albert
0b8ac8fc47 Move to hold points more slowly.
This should give the AI a better chance to climb before reaching the
waypoint.
2020-11-05 15:17:45 -08:00
Dan Albert
de3ba5908f Round waypoint TOTs in the UI. 2020-11-05 15:17:45 -08:00
C. Perreau
bbb6251aa9 Merge pull request #308 from walterroach/briefing_jinja
Briefing jinja
2020-11-05 23:31:58 +01:00
walterroach
6f71d92a7b Merge branch 'develop' into briefing_jinja 2020-11-05 16:08:16 -06:00
walterroach
a8b59cc567 Move briefing string literals to template 2020-11-05 16:07:44 -06:00
walterroach
f4d3660eac split sead and dead
change SEAD to engage in zone
2020-11-05 13:42:44 -08:00
walterroach
1f165835c6 type hinting and comment cleanup 2020-11-05 13:20:54 -08:00
walterroach
9087f3487d Single ShipGroupGenerator added 2020-11-05 13:20:54 -08:00
Walter
4ca92ea22d Fix ships being wrong unit type 2020-11-05 13:20:54 -08:00
Khopa
e2682d633f Added payloads for bombers. 2020-11-05 20:43:49 +01:00
Khopa
e6cb1b5970 Updated pydcs submodule commit reference 2020-11-05 19:59:21 +01:00
Khopa
de2d548139 Added Flak guns to most coldwar factions 2020-11-05 00:34:57 +01:00
Khopa
4d1a0b85e4 Added free version of WW2 factions that will not require the WW2 Asset Pack. 2020-11-04 22:25:24 +01:00
Khopa
5cfbd8c3ad Added building set for WW2 units that does not require WW2 asset pack. 2020-11-04 22:01:28 +01:00
Khopa
95f72be8eb Fixed issue with duplicated WW2 germany faction IDs 2020-11-04 21:51:09 +01:00
Khopa
95c4dfa52f Added new Flak site configuration (now that flak 18 and bofors guns do not require WW2 asset pack) 2020-11-04 21:50:25 +01:00
Khopa
b72a2f4a5f Fixed error in new game wizard with units and mods required not being displayed correctly in some cases. 2020-11-04 21:09:48 +01:00
Khopa
844f8595d1 Fixed ID of some units to match new pydcs data-export. 2020-11-04 21:07:28 +01:00
Khopa
1c9d9be667 Using latest pydcs data-export 2020-11-04 21:02:01 +01:00
Khopa
2a02a743a4 Do not display manage button on base defense group for enemy cp. 2020-11-04 18:53:18 +01:00
Khopa
968d9365d6 Re-added F-15E to bluefor modern. 2020-11-03 20:38:04 +01:00
Walter
cdb16cc591 Fixes #268
Changes red base info to show defenses first, and adds an attack option
2020-11-02 18:06:10 -08:00
Walter
86bc41c15c Add base menu to new package dialog for red cp 2020-11-02 18:06:10 -08:00
Dan Albert
ac05c7cfaa Fix recreate as CAP for ground targets. 2020-11-02 02:33:09 -08:00
Dan Albert
9c07fe5963 Remove source of error in package waypoint timing.
We were calculating the TOT based on travel time to the *flight's*
target, but the ingress point based on the travel time to the target
area. If the difference in travel time between the center of the
target area and the first target were different then we'd calculate
the start time incorrectly even for single flight packages.

Seems to fix https://github.com/Khopa/dcs_liberation/issues/295
2020-11-02 02:26:07 -08:00
Dan Albert
ed05f995b5 Refactor strike formation timing calculations. 2020-11-02 02:25:33 -08:00
Dan Albert
85491dca20 Fix front line CAP patrol end time. 2020-11-02 01:34:35 -08:00
Dan Albert
465399f803 Add radio configuration for the UH-1H and Ka-50. 2020-11-01 17:12:42 -08:00
Dan Albert
3550c8a8f6 Remove afterburner restrictions.
The AI often needs afterburner to recover from high AoA.

Fixes https://github.com/Khopa/dcs_liberation/issues/205
2020-11-01 15:12:05 -08:00
Dan Albert
739c0f8f52 Add radio data for MiG-15bis and MiG-19P.
Fixes https://github.com/Khopa/dcs_liberation/issues/233.
2020-11-01 14:50:23 -08:00
Dan Albert
49aa79c612 Fix mypy issues. 2020-11-01 14:49:50 -08:00
Dan Albert
cdde75b517 Add option to avoid delaying player flights.
Fixes https://github.com/Khopa/dcs_liberation/issues/227
2020-11-01 14:13:06 -08:00
Dan Albert
dde74af6b5 Fix client/player detection.
Client needs to be used if there are other player slots in *any*
flight, not just the same group.

Fixes https://github.com/Khopa/dcs_liberation/issues/297
2020-11-01 14:02:32 -08:00
Dan Albert
eff9c77c9a Fix departure time in the kneeboard.
We don't have the departure time set until after we create the initial
FlightData object. Populate the value after it is determined.

Fixes https://github.com/Khopa/dcs_liberation/issues/290
2020-11-01 13:31:56 -08:00
Dan Albert
5ba633c8a1 Round TOT/start time as needed.
The increased precision that we had everywhere except the UI and the
interface with DCS was causing issues with ASAP creating barely
negative start times. The main cause of this was that we'd compute the
earliest possible TOT, it would result in, for example, 23:10.002.
When we then set the QTimeEdit for the TOT, we have to round because
it does not support (nor do we really want to display) sub-second
values, which then caused the previously 0 start time to be -0.002.

Instead, since the sub-second values aren't really interesting anyway,
we now just round TOTs up and start times down. This should prevent
negative start times from occurring (except when they've been manually
planned as such), and also prevents start times of 00:00:01.

Also rounds the package waypoint times to avoid the same issues, but
it's not really important which direction we round these.

Fixes https://github.com/Khopa/dcs_liberation/issues/295
2020-11-01 13:31:10 -08:00
Dan Albert
ab67a38ca5 Remove rounding from waypoint timing in the UI.
This is behaving strangely on some machines. Stop hiding the details
in the UI while we debug.
2020-11-01 01:42:58 -08:00
Dan Albert
08f0c9d30a Fix timedelta -> timedelta conversion.
Fixes https://github.com/Khopa/dcs_liberation/issues/293
2020-11-01 00:58:35 -07:00
Dan Albert
9d747a9f9b Fix kneeboard ground speeds. 2020-10-31 20:28:07 -07:00
walterroach
31ca121498 Add github build number to title bar for preview
builds
2020-10-31 20:10:58 -07:00
walterroach
44b5f5acf1 added notify call back 2020-10-31 20:09:20 -07:00
walterroach
eb4878dfc4 remove useless props 2020-10-31 20:09:20 -07:00
walterroach
3dc7dc3d1a Dan'ts notes
commit 35ab9103cebf823bab85fbb1c9ff4bc2b9c5701a
Author: walterroach <37820425+walterroach@users.noreply.github.com>
Date:   Fri Oct 30 22:25:42 2020 -0500

    more cleanup

commit d3d008bc6b29f328ad48063bd4b0520e1b819d68
Author: walterroach <37820425+walterroach@users.noreply.github.com>
Date:   Fri Oct 30 22:17:59 2020 -0500

    More briefinggen cleanup

commit b2033f091d7191aecefb86fecb5cb060c074706e
Author: walterroach <37820425+walterroach@users.noreply.github.com>
Date:   Fri Oct 30 22:08:48 2020 -0500

    briefinggen cleanup

commit 72ea0196c22d9493df078765800f3fafb9c054dc
Author: walterroach <37820425+walterroach@users.noreply.github.com>
Date:   Fri Oct 30 21:57:52 2020 -0500

    Add notifier method back to Operation

commit efd39a9e03d02b9d581637d0d1c289af68e749c3
Author: walterroach <37820425+walterroach@users.noreply.github.com>
Date:   Fri Oct 30 21:34:37 2020 -0500

    Revert "Move kneeboard and briefing gen to unified"
    Removes properties added to Operation
    This reverts commit 941f2af770.
2020-10-31 20:09:20 -07:00
Walter
6a6133e5cd template cleanup 2020-10-31 20:09:20 -07:00
Walter
65c85d7f0b init args change 2020-10-31 20:09:20 -07:00
Walter
d519dfa5da docstrings and cleanup 2020-10-31 20:09:20 -07:00
Walter
73ea83bbdd Move kneeboard and briefing gen to unified
interface
2020-10-31 20:09:20 -07:00
Walter
235a5ec538 cleanup 2020-10-31 20:09:20 -07:00
walterroach
f81a3d03c0 working briefinggen refactor 2020-10-31 20:09:20 -07:00
walterroach
6878b57fba refactor frontline brief generation 2020-10-31 20:09:20 -07:00
walterroach
0143e5641f var change 2020-10-31 20:09:20 -07:00
walterroach
5adc92c601 Properly get enum instead of value on strategy
selecter
2020-10-31 20:09:20 -07:00
walterroach
0b2fbddbc5 refactor frontline situation briefing 2020-10-31 20:09:20 -07:00
Walter
28035bf02b linting and formatting 2020-10-31 20:09:20 -07:00
Walter
6c9a9de3f3 parent f03121af5a
first pass briefing refactor

briefing fixes

briefing fixes

Stop briefing generate being called twice

Stop frontline advantage string being appended
when there are no units.

jinja template

always return enum instance in Strategy Selector

For some reason on DEFENSE, enum is appended to control point stance,
but on all other the enum.value is added instead.

I don't see any case where the value is used, but there are many
cases that the enum instance is evaluated against.

type issue

junja's not a thing

swap mapping with dict

jinja template

always return enum instance in Strategy Selector

For some reason on DEFENSE, enum is appended to control point stance,
but on all other the enum.value is added instead.

I don't see any case where the value is used, but there are many
cases that the enum instance is evaluated against.

type issue

Update build.yml

junja's not a thing

swap mapping with dict

Restore build job
2020-10-31 20:09:20 -07:00
Walter
62139fc4eb Fix Nevada landmap not loading
logging on open try except block
2020-10-31 19:55:26 -07:00
Dan Albert
88b9ed29ba Reorganize flight planning.
Previously we were trying to make every potential flight plan look
just like a strike mission's flight plan. This led to a lot of special
case behavior in several places that was causing us to misplan TOTs.

I've reorganized this such that there's now an explicit `FlightPlan`
class, and any specialized behavior is handled by the subclasses.

I've also taken the opportunity to alter the behavior of CAS and
front-line CAP missions. These no longer involve the usual formation
waypoints. Instead the CAP will aim to be on station at the time that
the CAS mission reaches its ingress point, and leave at its egress
time. Both flights fly directly to the point with a start time
configured for a rendezvous.

It might be worth adding hold points back to every flight plan just to
ensure that non-formation flights don't end up with a very low speed
enroute to the target if they perform ground ops quicker than
expected.
2020-10-31 19:29:24 -07:00
Dan Albert
d94c57afd6 Fix error displaying flight edit dialog.
If no airfield was selected (as sometimes happens, usually when there
are no aircraft available anywhere), report that no aircraft are
available.
2020-10-31 18:39:06 -07:00
Dan Albert
b6421646ff Name the set TOT ASAP button better. 2020-10-30 17:28:04 -07:00
Dan Albert
611b6fc272 Fix mypy. 2020-10-30 16:43:19 -07:00
Dan Albert
9cdbef9faf Revert "Run tests in GitHub actions."
Was pushed by accident.

This reverts commit f5047fc0cc.
2020-10-30 16:14:38 -07:00
Dan Albert
b34de70fc7 Fix logging.
Same problem as last time: we were logging during initialization
before the log handlers could be initialized.
2020-10-30 16:13:34 -07:00
Dan Albert
f5047fc0cc Run tests in GitHub actions. 2020-10-30 15:09:20 -07:00
Dan Albert
258c34e61d Make test runnable from command line.
`pytest tests` works now. I can't explain why `pytest` alone does not,
but it could have something to do with us not being a real Python
package.

With just `pytest` I get:

    E   ModuleNotFoundError: No module named 'tests.test_factions'

But `python -c "import tests.test_factions"` works fine.
2020-10-30 15:05:44 -07:00
Dan Albert
f365487fd6 Remove user-specific config file. 2020-10-30 13:51:18 -07:00
Khopa
8f65b7ee7c Fixed and added many ground unit icons 2020-10-30 16:59:13 +01:00
Khopa
9397f1f39c Fixed issue with Russian 1955/65/75 factions 2020-10-30 16:16:46 +01:00
Walter
f03121af5a fixed wrong conditionals 2020-10-28 16:27:18 -07:00
Dan Albert
239b9f8234 Use python, not py.
py is a shortcut that launches the *latest* version of Python on the
machine. https://stackoverflow.com/a/50896577/632035

The build machines were updated to include python 3.9, so we were
doing everything with 3.9 instead of 3.8. pyproj doesn't have a binary
wheel for 3.9 on pypi yet, so we were falling back to building it from
source, which we aren't able to do, breaking the build.
2020-10-28 00:06:12 -07:00
Dan Albert
1620c602cf Fix mypy issue. 2020-10-27 18:12:13 -07:00
Walter
fa01303460 #257 CasIngressBuilder changes
* Calculate radius from frontline length global
* Determine CAS waypoint by waypoint type rather than name
* Change logging from error to exception
2020-10-27 09:48:36 -05:00
walterroach
1e5bd916d9 CasIngressBuilder proper inheritence 2020-10-26 23:33:56 -05:00
walterroach
62d89239fc merge 2020-10-26 23:20:05 -05:00
walterroach
91bee4e6c2 Changed CAS engage task to zone 2020-10-26 23:10:53 -05:00
Walter
a465dde32f Changed task to add AAA and infantry properly 2020-10-26 16:42:40 -07:00
Walter
254dd5f70f Add Air Defenses to engage task 2020-10-26 16:42:40 -07:00
Walter
1a70ed5121 CAS Weapons Hold until Ingress 2020-10-26 16:42:40 -07:00
Walter
16d5a550ce Move Engage targets to ingress point for CAS 2020-10-26 16:42:40 -07:00
Khopa
794cc43a41 Fix pmc mb339 modded faction id 2020-10-26 23:56:52 +01:00
Walter
8583bbf893 Changed task to add AAA and infantry properly 2020-10-26 17:19:52 -05:00
Walter
63d510f2ea Add Air Defenses to engage task 2020-10-26 15:48:22 -05:00
David Pierron
2c6b26003b Merge pull request #237 from VEAF/skynet-iads-plugin
First version of the Skynet IADS plugin
2020-10-26 21:39:58 +01:00
Walter
fdaf3bc30f CAS Weapons Hold until Ingress 2020-10-26 15:26:51 -05:00
Walter
816d9696b5 Move Engage targets to ingress point for CAS 2020-10-26 15:26:47 -05:00
David Pierron
bdaa6a294a Merge branch 'skynet-iads-plugin' of https://github.com/VEAF/dcs_liberation into skynet-iads-plugin 2020-10-26 21:17:26 +01:00
David Pierron
a6b15b9529 Merge remote-tracking branch 'upstream/develop' into skynet-iads-plugin 2020-10-26 21:17:16 +01:00
David Pierron
878529e8f8 Merge branch 'develop' into skynet-iads-plugin 2020-10-26 21:14:24 +01:00
David Pierron
2cb37b5bd8 added typing 2020-10-26 21:06:25 +01:00
David Pierron
de95cfc981 base plugin needs to be loaded first,
before skynetiads
2020-10-26 21:06:08 +01:00
David Pierron
0477247cf2 correction :
skipping plugin work orders did not work
2020-10-26 21:05:34 +01:00
David Pierron
e0319a4047 Merge remote-tracking branch 'upstream/develop'
into skynet-iads-plugin
2020-10-26 18:05:07 +01:00
Khopa
839f163ac5 Fixed issue with base defense that could not be repaired 2020-10-26 00:32:05 +01:00
Khopa
ba9ad4c371 Merge branch 'develop' of https://github.com/khopa/dcs_liberation into develop 2020-10-26 00:24:30 +01:00
Khopa
bdcf7c1828 Fixed mig 21 wrong identifier in some files. 2020-10-26 00:24:03 +01:00
Dan Albert
821e9fc114 Merge branch 'fix-mypy-actions' into develop 2020-10-25 16:19:57 -07:00
Dan Albert
334d21897b Fix mypy actions.
GitHub actions fail based on the exit code of the *last* command in
the step, so we were failing mypy but passing the action.
2020-10-25 16:19:46 -07:00
Dan Albert
b405c3ab32 Fix mypy issues in faction code. 2020-10-25 16:19:46 -07:00
Khopa
3cabb1e02d Inverted usa 1990 and 2005 factions names 2020-10-26 00:16:37 +01:00
Khopa
ec7f8f5710 Completed basic faction parameters loading test. 2020-10-25 14:38:10 +01:00
Khopa
93f0627c5e Fixed errors in faction code 2020-10-25 14:21:25 +01:00
Khopa
3d8c2d689e Added unit tests for faction. (To be completed) 2020-10-25 14:20:43 +01:00
Khopa
e1572c09ff Improved faction & faction loader typing. Fixed error with netherlands faction. 2020-10-25 14:08:11 +01:00
Khopa
910af12fb9 Default faction for bluefor is USA 2005. 2020-10-25 12:19:51 +01:00
C. Perreau
acbd45341f Merge pull request #249 from Khopa/faction_refactor
Moddable factions
2020-10-25 02:19:22 +02:00
Khopa
901c89371c Fixed syntax errors in json files 2020-10-24 23:40:05 +02:00
Khopa
743534bdda Migrated latest factions to new faction json format. 2020-10-24 23:35:39 +02:00
Walter
98bb1a9f65 Explicit message for confirm_no_client_launch 2020-10-24 13:28:02 -07:00
Dan Albert
bfc602f22f Add cheat option to capture bases.
Capturing bases is sometimes really annoying because of the DCS unit
AI and our non-optimal ground victory heuristics. Add a cheat option
to allow the player to move on without the tedium.
2020-10-24 13:06:37 -07:00
Dan Albert
dd2b61edf3 Use FrontLine in ConflictTheater.conflicts. 2020-10-24 12:24:29 -07:00
Khopa
0fd58135fd Migrated more factions to new faction json format. 2020-10-24 18:02:41 +02:00
Khopa
4672252242 Added description field to both campaign and faction json file. 2020-10-24 17:40:04 +02:00
Khopa
a0c61bf73a Authors informations are loaded in Campaign object (to be displayed in UI later) 2020-10-24 17:23:09 +02:00
Khopa
d6c19a8aff Added author field to campaign json file 2020-10-24 17:18:55 +02:00
Khopa
b6a933e264 Readded Full Map - Caucasus, remade by Discord user "george" 2020-10-24 17:16:56 +02:00
Khopa
0191eca9dc Keeping new factions up to date with develop branch. 2020-10-24 17:03:39 +02:00
Khopa
f962fd55bc Merge branch 'develop' into faction_refactor
# Conflicts:
#	game/factions/bluefor_coldwar.py
#	game/factions/bluefor_coldwar_a4.py
#	game/factions/bluefor_coldwar_mods.py
#	game/factions/bluefor_modern.py
2020-10-24 16:57:28 +02:00
David Pierron
33fa719e8d Merge branch 'skynet-iads-plugin' of https://github.com/VEAF/dcs_liberation into skynet-iads-plugin 2020-10-24 11:03:03 +02:00
David Pierron
adb9352905 removed mist.lua from skynet plugin
It's already included in the base plugin, which is mandatory
2020-10-24 11:02:46 +02:00
David Pierron
58574c67df Merge branch 'develop' into skynet-iads-plugin 2020-10-24 11:01:18 +02:00
Dan Albert
04d3ba4c47 Add mypy to github actions so we stop regressing. 2020-10-24 01:25:47 -07:00
Dan Albert
0f1d2b8685 Remove a plugin we don't include. 2020-10-24 01:10:15 -07:00
Dan Albert
c06a855113 Fix mypy regressions.
Mostly in the plugin system, which needed a handful of asserts that
shouldn't be necessary, but fixing them requires a refactor.
2020-10-24 01:10:11 -07:00
Dan Albert
e9bfd58ee1 Shorten strike waypoint descriptions.
We don't really need this much detail in these, and it was overflowing
the kneeboard.
2020-10-23 22:52:32 -07:00
Dan Albert
5f02febb6c Fix kneeboard crash for small strike missions.
When we don't coalesce target points on the kneeboard we call
add_waypoint_row multiple times, so calls after the first were using
the travel time from one strike target to the next. Since they were so
close that rounded down to zero and caused a divide by zero error.

Update the last waypoint in add_waypoint instead.
2020-10-23 22:41:28 -07:00
Dan Albert
15db12fb21 Fix TOT/start for BARCAPs in other packages.
We were only getting BARCAP results right in BARCAP packages. This
fixes calculations of TOTs and start times for BARCAPs in strike
packages.

The probably needs some refactoring. BARCAP is just the symptomatic
example at the moment, but the real problem is that different mission
profiles exist and we currently only handle one. Making profiles
explicit in mission planning will clean this up, will be needed for
other future mission types, and makes it easier for us to alter
behavior for waypoint and timing decisions based on the aircraft or
mission type.
2020-10-23 22:28:51 -07:00
Dan Albert
c3fca6696d Fix waypoint altitudes for helicopters.
Fixes https://github.com/Khopa/dcs_liberation/issues/234
2020-10-23 14:52:14 -07:00
Dan Albert
dd4c37cde3 Pick runways and ascent/descent based on headwind. 2020-10-23 14:41:42 -07:00
Dan Albert
aa7ffdabb0 Fix planned flights view, stop hiding bugs. 2020-10-23 14:19:45 -07:00
Dan Albert
85f6616185 Add bombers to coalitions.
* B-1
* B-52
* F-117
* Tu-160
* Tu-22
* Tu-95

Also alters the default loadouts for the F-15E.
2020-10-23 13:50:03 -07:00
David Pierron
c8955bdca7 activate the SAM at mission start,
to haste their response
2020-10-23 07:49:26 +02:00
Khopa
669a8c9e64 Merge branch 'develop' into faction_refactor 2020-10-23 01:39:48 +02:00
Khopa
b4d0eb0b99 Started cleaning old factions, added back most factions as json. Fixed LHA not spawning. 2020-10-23 01:38:08 +02:00
C. Perreau
035bebaab8 Merge pull request #238 from walterroach/ter_fix
Fixes issue Khopa#203 TER weapons not selectable in custom payload
2020-10-23 01:07:37 +02:00
Walter
b20200318b ter fix 2020-10-22 16:29:46 -05:00
Walter
9caf83cda9 Fixes issue #203
Conditional in comprehension appears to be intended to exclude
dunders, but was also excluding many TER weapons from Weapon class args
beginning with '_'
2020-10-22 16:28:38 -05:00
David Pierron
493f9df4e2 Merge remote-tracking branch 'upstream/develop' into skynet-iads-plugin 2020-10-22 21:36:49 +02:00
Dan Albert
cc61893bca Fix more fallout from double/right click change. 2020-10-22 12:12:28 -07:00
David Pierron
3ba2fb76aa removed VEAF from the default plugins 2020-10-22 18:40:40 +02:00
David Pierron
e024da277b first working version of the skynetiads plugin 2020-10-22 18:39:54 +02:00
Khopa
bc1e793ce6 Fixed faction loader so it works with known modded units 2020-10-22 13:55:37 +02:00
Khopa
aa7eacc043 Added list of modded units in pydcs_extensions module 2020-10-22 13:51:24 +02:00
Khopa
dd7b9f1790 Fix faction shorad units loader 2020-10-22 13:50:24 +02:00
Khopa
d6b94345d9 Merge branch 'develop' into faction_refactor 2020-10-22 13:40:14 +02:00
Khopa
aa1ac56ec3 Faction rework, working :) 2020-10-22 13:33:18 +02:00
Dan Albert
fd969020af Add escort tasks for the AI. 2020-10-22 01:29:19 -07:00
Dan Albert
95f486870d Correct AI startup time.
It doesn't look like the AI is subject to much startup time. I see
B-1, F-15, F-16, and M-2000 all start up in about 2 minutes.
2020-10-22 00:46:53 -07:00
C. Perreau
f6d049da3c Merge pull request #232 from VEAF/develop
changes to the export of state.json :
2020-10-22 09:46:10 +02:00
Dan Albert
69f15824ca Fix breakage in package list UI. 2020-10-22 00:45:10 -07:00
Dan Albert
8c70d1ab79 Fix foot to meter conversion.
Somehow this constant was wrong so all of our foot-to-meter
conversions were coming out ~7% too large. We're still introducing
some error because we're rounding early rather than only when we need
an integer, but it's much more accurate now.
2020-10-21 23:58:23 -07:00
Dan Albert
58fd651a0b Confirm exit to avoid losing progress.
Fixes https://github.com/Khopa/dcs_liberation/issues/218
2020-10-21 18:35:14 -07:00
Dan Albert
177b505cb7 Update client slots UI on end turn.
Fixes https://github.com/Khopa/dcs_liberation/issues/222
2020-10-21 17:54:58 -07:00
Dan Albert
8ac5dbe22a Add double- and right-click actions for ATO lists.
Fixes https://github.com/Khopa/dcs_liberation/issues/230
2020-10-21 17:52:21 -07:00
Khopa
b744238fb8 Factions are properly loaded, now need to refactor whole code. 2020-10-22 01:32:33 +02:00
David Pierron
57b7402753 changes to the export of state.json :
- dcsLiberation.installPath does not include "state.json" anymore
- corrected behavior :
  - try LIBERATION_EXPORT_DIR
  - then try dcsLiberation.installPath
  - then try TEMP
  - then try working directory
- corrected multiple bugs in dcs_liberation.lua
- corrected bad string.format causing DCS crashes in
  jtacautolase-config.lua
2020-10-21 23:44:36 +02:00
Khopa
dcaa390d24 Added support for Su-57 mod by Cubanace 2020-10-21 22:21:36 +02:00
Khopa
59010f6949 Added support for Su-57 mod by Cubanace 2020-10-21 21:40:31 +02:00
Khopa
6a91fad10a Merge branch 'develop' into faction_refactor 2020-10-21 19:51:25 +02:00
C. Perreau
f03029417d Merge pull request #229 from VEAF/develop
Removed VEAF submodule
2020-10-21 11:39:18 +02:00
David Pierron
f5aa342602 Removed VEAF submodule 2020-10-21 11:32:39 +02:00
C. Perreau
53582ba539 Merge pull request #228 from VEAF/new-plugin-system
New plugin system
2020-10-20 23:16:19 +02:00
David Pierron
44c976948d bug correction in the JTACautolase LUA config 2020-10-20 23:02:46 +02:00
David Pierron
41d5020467 bug correction - when running a new campaing
without accessing the plugin setup screen
2020-10-20 22:25:39 +02:00
David Pierron
1bd26005f2 Merge remote-tracking branch 'upstream/develop' into new-plugin-system 2020-10-20 22:10:19 +02:00
David Pierron
b1840ce2ca Merge 'upstream/develop' into new-plugin-system 2020-10-20 22:10:08 +02:00
C. Perreau
769246c55d Merge pull request #224 from justin-lovell/feature/remote-hosting-flexibility
Flexible Dedicated Hosting Options for Mission Files
2020-10-20 22:04:37 +02:00
Khopa
24394d4d00 Faction rework wip. 2020-10-20 20:58:39 +02:00
Justin Lovell
cab5825b72 Flexible Dedicated Hosting Options
* Fixed minor errors on the original LUA scripting
* Refactored code to be self-contained to a function
* Changed the search logic to use an environment variable first, then
  fallback into other search options
2020-10-20 21:49:32 +11:00
Dan Albert
84beb2dfe5 Remove dead code. 2020-10-20 00:15:22 -07:00
Dan Albert
f8ac39fb82 Fix min/max inversion in wind setting. 2020-10-20 00:10:37 -07:00
Dan Albert
eb69d01067 Add distance and ground speed to the kneeboard. 2020-10-19 23:25:46 -07:00
Dan Albert
1c4f255c7f Add waypoint departure time to the kneeboard. 2020-10-19 22:44:54 -07:00
Dan Albert
4125f6ec06 Don't scale waypoint info text size when zooming. 2020-10-19 22:26:31 -07:00
Dan Albert
5023e0d30f Fix disabled delete button in package UI.
The selection changed handler isn't called for the initial selection.
2020-10-19 21:34:17 -07:00
Dan Albert
f65595c626 Automatically select newly created packages. 2020-10-19 21:17:24 -07:00
Dan Albert
916d1eec96 Limit flight size to available aircraft. 2020-10-19 20:46:18 -07:00
Dan Albert
c2d615315e Add client slot selection to new flight window. 2020-10-19 20:29:55 -07:00
Dan Albert
aa96ce7134 Fix cancel of new package. 2020-10-19 01:21:49 -07:00
Dan Albert
01f83e8451 Place CAP racetracks more defensively.
Ensure that we're never putting a CAP race track within 20 nmi of an
enemy airfield (aside from the target airfield, if the target is
hostile).
2020-10-19 00:12:36 -07:00
David Pierron
ed92e9afb9 changed the system to make use of JSON files 2020-10-18 18:23:31 +02:00
Dan Albert
064890c0a2 Mark the F-16 as SEAD capable.
Latest OB update has HARMs for the F-16.
2020-10-17 16:47:41 -07:00
Dan Albert
8617f48fc2 Sort air unit purchases properly, and by name.
Previously we were sorting by task first and price second. Much easier
to find things if they're sorted by name (although longer term we
should make the sort option selectable).
2020-10-17 16:46:54 -07:00
Dan Albert
2269cf0f08 Show objective value per turn in the tooltip. 2020-10-17 16:11:28 -07:00
Dan Albert
3d41eb1ab4 Clean up CAP types.
Stop using "CAP". Use BARCAP or TARCAP instead.

TARCAP no longer allowed anywhere but front lines, since that's all we
have mission planning for right now. Later will add TARCAP and BARCAP
for all objective types with different timing profiles.

Part two of the fix for
https://github.com/Khopa/dcs_liberation/issues/210.
2020-10-17 14:32:09 -07:00
Dan Albert
cace523aa8 Avoid crash for custom/empty flight plans.
Fixes https://github.com/Khopa/dcs_liberation/issues/210
2020-10-17 14:10:54 -07:00
Dan Albert
002f55dc04 Update the map from the main window.
This guarantees that we update the map *after* updating the model that
the map uses to draw flight plans. Without this, after creating a new
game we'd redraw the previous game's flight plans because the model
hadn't been updated by the time the map was.

Fixes https://github.com/Khopa/dcs_liberation/issues/212
2020-10-17 13:33:28 -07:00
Dan Albert
49b6951ac3 Generate weather conditions at turn start.
Weather and exact time of day information is helpful during mission
planning, so generate it at the start of the turn rather than at
takeoff time.

Another advantage aside from planning is that we can now use the wind
information to set carrier headings and takeoff runways appropriately.
2020-10-16 18:25:25 -07:00
Dan Albert
7aa17e5ad6 Fix package/flight selection signals.
Qt helpfully converts None to 0 for us, so use -1 instead of None.
2020-10-16 14:12:27 -07:00
Dan Albert
9db41270f3 Add red ATO cheat option, show red flight plans. 2020-10-16 13:43:01 -07:00
Dan Albert
e4852c74ab Remove completed TODOs/dead code. 2020-10-16 13:00:59 -07:00
Dan Albert
613f84aa3c Add aircraft-specific speed estimates. 2020-10-16 03:10:14 -07:00
Dan Albert
2814876976 Fix A-10C II radio data.
These are two different planes.
2020-10-16 03:09:21 -07:00
Dan Albert
69bf3999aa Fix new package double flight delete.
This button was connected twice.
2020-10-16 03:08:48 -07:00
Dan Albert
2fa3b26119 Improve speed estimations.
Reasonable ground speed depends a lot on altitude, so plumb that
information through to the speed estimator.

Also adds calculations for ground speed based on desired mach. I don't
know if DCS is using the same formulas, but we should at least be
pretty close.
2020-10-16 02:11:50 -07:00
Dan Albert
5a027c552e Point players to the kneeboard from the briefing. 2020-10-15 21:43:41 -07:00
Dan Albert
9efecf9514 Fix inventory handling for new packages. 2020-10-15 21:33:04 -07:00
Dan Albert
8b87c43869 Warn the player about misconfigured TOTs. 2020-10-15 21:04:22 -07:00
Dan Albert
f7fec834e6 Add a button to automatically set the package TOT. 2020-10-15 20:23:57 -07:00
Dan Albert
de43a1215c Update departure time when TOT is changed.
Fixes https://github.com/Khopa/dcs_liberation/issues/207
2020-10-15 18:03:03 -07:00
Dan Albert
8dc531bb7f Make the departure time non-editable.
Fixes part 2 of https://github.com/Khopa/dcs_liberation/issues/207.
2020-10-15 17:57:25 -07:00
Khopa
da2584d7ee Updated pydcs version 2020-10-15 20:58:06 +02:00
David Pierron
373924a959 small changes to veaf plugin 2020-10-15 16:21:39 +02:00
Khopa
f75032bd79 Put 2.2.0-preview as version string 2020-10-15 12:17:33 +02:00
Khopa
4203dc5d41 Prepared changelog first draft for version 2.2.0 2020-10-14 23:34:09 +02:00
Khopa
9fe1f8ff90 Now use the new sea map to place boats and offshore buildings. 2020-10-14 23:30:33 +02:00
Khopa
bc825f760d Added sea zones for each map, and display it in polygon map mode. 2020-10-14 23:21:16 +02:00
David Pierron
191199d9de Merge remote-tracking branch 'upstream/develop' into new-plugin-system 2020-10-14 09:01:37 +02:00
Khopa
883a66a792 Fix : Added missing channel.json file 2020-10-13 00:35:27 +02:00
Khopa
411e71b9a2 Added setting to display culling distance 2020-10-12 20:57:32 +02:00
David Pierron
5463505787 added documentation for plugins system 2020-10-12 20:01:27 +02:00
David Pierron
ec6fc076de multiple changes
- load plugins when loading a game
- moved plugins scripts to resources/plugins (for pyinstaller)
- removed vanilla JTAC and JTAC_smoke options and settings GUI
- call JtacAutolasePlugin in armor.py
- made a dictionary of INSTALLED_PLUGINS
- removed NIOD from the VEAF plugin
2020-10-12 19:49:39 +02:00
Khopa
5a245bf362 Fixed crash in polygon display mode for Nevada map. 2020-10-12 18:53:15 +02:00
Khopa
d95912322c Added Polygon drawing mode for map background 2020-10-12 18:48:50 +02:00
Khopa
9c58e73b39 Added more display options for SAMS 2020-10-12 18:12:45 +02:00
David Pierron
3c4ccd7d57 Merge remote-tracking branch 'upstream/develop' into new-plugin-system 2020-10-12 17:29:54 +02:00
David Pierron
d22943d755 added a customizable plugin system
- the base LUA functionality has been implemented as a mandatory plugin
- the jtacautolase functionality has been implemented as a plugin
- added a VEAF framework plugin

The plugins have GUI elements in the Settings window.
2020-10-12 17:27:13 +02:00
Dan Albert
974b6590d8 Estimate TOTs for packages.
We estimate the longest possible time from mission start to TOT for
all flights in a package and use that to set the TOT (plus any delay
used to stagger flights). This both cuts down on loiter time for
shorter flights and ensures that long flights will make it to the
target in time.

This is also used to compute the start time for the AI, so the
explicit delay option is no longer needed.
2020-10-12 01:00:47 -07:00
Dan Albert
d414c00b74 Make waypoint info more readable. 2020-10-10 16:52:59 -07:00
Khopa
7b79d183eb Changelog 2.1.5 did not make it on this branch somehow 2020-10-11 00:48:13 +02:00
Dan Albert
edd56cb407 Fix bad import of THEMES. 2020-10-10 14:11:44 -07:00
Dan Albert
c777204f50 Draw waypoint information on the map. 2020-10-10 14:09:10 -07:00
Dan Albert
55f12f20c1 Fix missing default parameter. 2020-10-10 14:05:25 -07:00
Dan Albert
1ac062653d Make the starting budget text editable. 2020-10-10 13:00:59 -07:00
Dan Albert
b0a176a22c Make "all" the default flight path display option. 2020-10-10 12:43:51 -07:00
Khopa
f4b07cb518 New game wizard : added to slider to choose starting money 2020-10-10 16:21:41 +02:00
Khopa
a0ff78a810 Merge branch 'develop' of https://github.com/khopa/dcs_liberation into develop
 Conflicts:
	qt_ui/widgets/map/QLiberationMap.py
2020-10-10 15:46:23 +02:00
Khopa
f22391855b Fixed SAM threat radius scale, added detection range, changed UI colors for SAMS range. 2020-10-10 15:43:58 +02:00
Dan Albert
1fa18447e1 Show player slots in the overview. 2020-10-09 20:07:54 -07:00
Dan Albert
2d8c8c63c9 Improve flight path display options.
Adds an option show only selected flight, and also changes the show
all option to highlight the selected flight.
2020-10-09 16:49:32 -07:00
Dan Albert
31d5e3151b Refactor display rules. 2020-10-09 14:54:58 -07:00
David Pierron
c77bfe9da2 plugin base : inject mission configuration data 2020-10-09 21:25:21 +02:00
David Pierron
9a7dfc55e3 Merge remote-tracking branch 'upstream/develop' into develop 2020-10-09 21:03:12 +02:00
C. Perreau
63bc3bd46e Merge pull request #193 from DanAlbert/tot
Plan waypoint TOTs.
2020-10-09 13:25:08 +02:00
Dan Albert
b5e5a3b2da Plan waypoint TOTs.
Also fixes the CAP racetracks so the AI actually stays on station.

Waypoint TOT assignment happens at mission generation time for the
sake of the UI. It's a bit messy since we have the late-initialized
field in FlightWaypoint, but on the other hand we don't have to reset
every extant waypoint whenever the player adjusts the mission's TOT.

If we want to clean this up a bit more, we could have two distinct
types for waypoints: one for the planning stage and one with the
resolved TOTs. We already do some thing like this with Flight vs
FlightData.

Future improvements:

* Estimate the group's ground speed so we don't need such wide margins
  of error.
* Delay takeoff to cut loiter fuel cost.
* Plan mission TOT based on the aircraft in the package and their
  travel times to the objective.
* Tune target area time prediction. Flights often don't need to travel
  all the way to the target point, and probably won't be doing it
  slowly, so the current planning causes a lot of extra time spent in
  enemy territory.
* Per-flight TOT offsets from the package to allow a sweep to arrive
  before the rest, etc.
2020-10-09 01:08:34 -07:00
Dan Albert
7abe32be5c Fix name of decent waypoint. 2020-10-09 00:26:57 -07:00
Dan Albert
f0279a6866 Ignore the entire logs directory.
We use a rotating log handler, so we generate more than just the one
file.
2020-10-08 22:49:04 -07:00
Dan Albert
5ce942c9a0 Confirm mission start when no client slots exist.
Especially considering the button in this position used to be how
players added client slots, confirm that they in fact want to launch
an AI-only mission before launching, and guide them toward the new UI.
2020-10-07 17:09:57 -07:00
David Pierron
59d6cc7625 Merge remote-tracking branch 'upstream/develop' into develop 2020-10-07 09:21:41 +02:00
Dan Albert
4abf806837 Fix initial frequencies for support aircraft.
Vaicom (a mod that adds voice control for the communications menus)
isn't able to follow the waypoint frequency change that normally sets
the radio channel for the AWACS/tanker flights. Set the group's
frequency correctly to start so it works.
2020-10-06 23:44:11 -07:00
Dan Albert
6bfb8cf2fd Fix double logging.
Calling logging.basicConfig creates a stream handler for the root
logger, and then we were adding our own with a different formatter.
Pass the format string to basicConfig so we don't need to add our own
duplicate stream handler.
2020-10-06 23:31:40 -07:00
Dan Albert
1d7f1082ea Fix logging by deferring campaign data loading.
Logging before we've made it to the logging setup was causing the root
logger to be permanently configured to the default (warning) log
level, so we weren't getting any info or debug logs any more.

Defer the campaign data load until it is needed rather than doing it
at import time. I've also cleaned up a bit so we only load each
campaign once, rather than re-loading the campaign to create the
theater again after the wizard is finished.
2020-10-06 23:26:18 -07:00
Dan Albert
1c4aec83cb Clean up start-up logging.
Most of this wasn't helpful. What was is now logging instead of print
so it can be configured.
2020-10-06 22:24:47 -07:00
Dan Albert
e537396fec Stagger package start times.
Avoids crowding the taxiways, and adds some life to the end of the
mission.

Later on, this will happen more naturally because we can delay
takeoffs to align with the package's DTOT.
2020-10-06 21:51:23 -07:00
Dan Albert
944748a0ac Don't throw away exception info on save/load.
Makes it much easier to determine what we did that broke save game
compatibility.
2020-10-06 21:50:44 -07:00
Dan Albert
023925d741 Set preferred mission types for aircraft. 2020-10-06 17:30:20 -07:00
Dan Albert
93db1254ec Improve insufficient aircraft message.
Specify only the tasks which were unfulfilled so the player knows what
to buy.
2020-10-06 17:29:42 -07:00
Dan Albert
e0725ff139 Add escort mission planning to the UI. 2020-10-06 17:29:19 -07:00
Dan Albert
9e96aee89f Add default escort loadouts. 2020-10-06 17:29:11 -07:00
Dan Albert
db6b660270 Fix mypy issues in all modules except qt_ui. 2020-10-06 17:24:08 -07:00
Dan Albert
1808e5bccf Add initial mypy.ini. 2020-10-06 17:01:37 -07:00
Dan Albert
5f1601a2da Remove the userdata package. 2020-10-06 17:01:37 -07:00
Khopa
60ce6658ad Campaigns are sorted by terrain in new game wizard 2020-10-07 01:12:16 +02:00
Khopa
e664652cc5 Now properly merged operation.py 2020-10-07 01:07:56 +02:00
Khopa
ca48a42701 Revert "Reverted automerge errors."
This reverts commit 00ea8ac4
2020-10-07 00:59:18 +02:00
Khopa
00ea8ac4e1 Reverted automerge errors. 2020-10-07 00:56:42 +02:00
Khopa
de5238e89a Fixed issue in Normandy small campaign 2020-10-07 00:27:43 +02:00
Khopa
3bb1327a65 Manuall reintroduced inverted campaign config in json campaign files 2020-10-07 00:25:02 +02:00
Khopa
71f77dd8fb Added moddable campaigns through json files. 2020-10-07 00:09:11 +02:00
C. Perreau
9101dae38a Merge pull request #184 from DanAlbert/waypoint-planning
Improve automated mission planning.
2020-10-06 22:04:58 +02:00
C. Perreau
1f18bf2bd8 Merge branch 'develop' into waypoint-planning 2020-10-06 22:01:44 +02:00
Dan Albert
f040804d02 Fix save issues after aborting mission.
When the mission is aborted the pending mission is still in the event
list, which is part of the game option. That event has a reference to
the operation, which in turn contains all the mission generator
objects. Two of these objects are the radio/TACAN allocators, which
use a generator to track the next free channel. Generators cannot be
picked, so because these are transitively part of the game object the
game cannot be saved.

Aside from the briefing generator, none of those objects are
actually needed outside the generation function itself, so just make
them locals instead.

This probably needs a larger refactor at some point. It doesn't look
like we need so many calls into the operation type (it has an
initialize, a prepare, and a generate, and it doesn't seem to need
anything but the last one). The only reason breifinggen needs to
remain a part of the class is because the briefing title and
description are filled in from the derived class, where title and
description should probably be overridden properties instead. I'm also
not sure if we need to make the event list a part of game at all, and
also don't think that the mission needs to be one of these events.
2020-10-05 02:17:14 -07:00
Khopa
e27625556c Exported existing campaigns to json objects. 2020-10-04 22:09:57 +02:00
Dan Albert
b13711ddef Update the waypoint builder UI.
Changing targets doesn't make sense now that flights belong to a
package. Change all the "generate" dialogs to simply confirm dialogs
to make sure the user is okay with us clobbering the flight plan.
2020-10-04 12:24:31 -07:00
Dan Albert
6ce82be46b Set up split/join points. 2020-10-04 12:24:31 -07:00
Dan Albert
56a5864600 Generate common ingress/egress points.
This still isn't very good because it doesn't work well for anything
but the automatically planned package.

Instead, should be a part of the Package itself, generated the first
time it is needed, and resettable by the user.
2020-10-04 12:24:31 -07:00
Dan Albert
07cbaa3e70 Plan escort flights.
TODO: UI
2020-10-04 12:24:31 -07:00
Dan Albert
2aecea88b0 Orient CAP tracks toward the enemy.
Pointing the race track 90 degrees away from where the enemy is
expected means the radar can't see much. CAP flights normally fly
*toward* the expected direction of contact and alternate approaching
and retreating legs with their wingman.
2020-10-04 12:24:31 -07:00
Dan Albert
582c43fb6c Generate CAP missions in useful locations.
CAP missions should be between the protected location and the nearest
threat. Find the closest enemy airfield and ensure that the CAP race
track is between it and the protected location.
2020-10-04 12:24:26 -07:00
Dan Albert
cc7c2cc707 Refactor flight plan generation. 2020-10-04 12:24:26 -07:00
Dan Albert
8b717c4f4c Replace doctrine dict with a real type. 2020-10-04 12:24:26 -07:00
Dan Albert
aa309af015 Redraw flight plans when they change. 2020-10-04 12:24:26 -07:00
Dan Albert
1e041b6249 Perform coalition-wide mission planning.
Mission planning on a per-control point basis lacked the context it
needed to make good decisions, and the ability to make larger missions
that pulled aircraft from multiple airfields.

The per-CP planners have been replaced in favor of a global planner
per coalition. The planner generates a list of potential missions in
order of priority and then allocates aircraft to the proposed flights
until no missions remain.

Mission planning behavior has changed:

* CAP flights will now only be generated for airfields within a
  predefined threat range of an enemy airfield.
* CAS, SEAD, and strike missions get escorts. Strike missions get a
  SEAD flight.
* CAS, SEAD, and strike missions will not be planned unless
  they have an escort available.
* Missions may originate from multiple airfields.

There's more to do:

* The range limitations imposed on the mission planner should take
  aircraft range limitations into account.
* Air superiority aircraft like the F-15 should be preferred for CAP
  over multi-role aircraft like the F/A-18 since otherwise we run the
  risk of running out of ground attack capable aircraft even though
  there are still unused aircraft.
* Mission priorities may need tuning.
* Target areas could be analyzed for potential threats, allowing
  escort flights to be optional or omitted if there is no threat to
  defend against. For example, late game a SEAD flight for a strike
  mission probably is not necessary.
* SAM threat should be judged by how close the extent of the SAM's
  range is to friendly locations, not the distance to the site itself.
  An SA-10 30 nm away is more threatening than an SA-6 25 nm away.
* Much of the planning behavior should be factored out into the
  coalition's doctrine.

But as-is this is an improvement over the existing behavior, so those
things can be follow ups.

The potential regression in behavior here is that we're no longer
planning multiple cycles of missions. Each objective will get one CAP.
I think this fits better with the turn cycle of the game, as a CAP
flight should be able to remain on station for the duration of the
turn (especially with refueling).

Note that this does break save compatibility as the old planner was a
part of the game object, and since that class is now gone it can't be
unpickled.
2020-10-04 12:24:26 -07:00
Khopa
1f240b02f4 Fix aircrafts landing point 2020-10-04 14:27:13 +02:00
Khopa
6317f376b7 EPLRS typo fixed 2020-10-04 14:11:28 +02:00
Khopa
5ecf9aeed8 EPLRS implemented for base defense unit. 2020-10-03 18:32:11 +02:00
Khopa
db36a76c2c Fixed eplrs for frontline ground units 2020-10-03 17:35:06 +02:00
Khopa
3df8fb5fe9 Merge branch 'develop' into develop_mission_planner 2020-10-03 17:06:07 +02:00
Khopa
5ba2e8a7a1 Fixed error after merge 2020-10-03 16:18:32 +02:00
Khopa
07d4b126f5 Enable EPLRS for ground units that can use it. 2020-10-03 16:18:12 +02:00
C. Perreau
6e14ec3227 Merge pull request #147 from DanAlbert/ato
Replace mission planning UI.
2020-10-02 13:31:45 +02:00
C. Perreau
028292a023 Merge branch 'develop_mission_planner' into ato 2020-10-02 13:26:21 +02:00
David Pierron
a38f2e36a2 Merge remote-tracking branch 'upstream/develop' into develop 2020-10-02 09:25:18 +02:00
David Pierron
5807fbf896 added a 'mkrelease' config for VS.Code 2020-09-30 13:35:09 +02:00
David Pierron
2c1dc6a18d removed useless link.cmd.sample file 2020-09-30 13:34:41 +02:00
Dan Albert
a3c06ce6e0 Limit task type combo box to valid mission types. 2020-09-27 13:44:58 -07:00
Dan Albert
0e1dfb8ccb Implement CAP and CAS for front lines. 2020-09-27 13:44:58 -07:00
Dan Albert
8a4a81a008 Remove unused frontline code. 2020-09-27 13:44:58 -07:00
Dan Albert
ff083942e8 Replace mission planning UI.
Mission planning has been completely redone. Missions are now planned
by right clicking the target area and choosing "New package".

A package can include multiple flights for the same objective. Right
now the automatic flight planner is only fragging single-flight
packages in the same manner that it used to, but that can be improved
now.

The air tasking order (ATO) is now the left bar of the main UI. This
shows every fragged package, and the flights in the selected package.
The info bar that was previously on the left is now a smaller bar at
the bottom of the screen. The old "Mission Planning" button is now
just the "Take Off" button.

The flight plan display no longer shows enemy flight plans. That could
be re-added if needed, probably with a difficulty/cheat option.

Aircraft inventories have been disassociated from the Planner class.
Aircraft inventories are now stored globally in the Game object.

Save games made prior to this update will not be compatible do to the
changes in how aircraft inventories and planned flights are stored.
2020-09-27 13:44:58 -07:00
Dan Albert
0eee5747af Clean up flight path drawing code. 2020-09-24 01:01:26 -07:00
Dan Albert
80f2b7a1db Cleanups in map object UI.
* Fix the context menu
* Remove unnecessary overrides
* Clean up formatting/naming
* Factor out base class for shared behavior
2020-09-24 01:01:26 -07:00
948 changed files with 54243 additions and 14805 deletions

37
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,37 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
Before filing, please search the issue tracker to see if the issue has already been reported.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional information**
We will usually need more information for debugging. Include as much of the following as you are able:
- DCS Liberation save file (the `.liberation` file you save from the DCS Liberation window). By default these are located in your DCS saved games directory (`%USERPROFILE%/Saved Games/DCS`).
- The generated mission file (the `.miz` file that you load in DCS to play the turn). By default these are located in your missions directory (`%USERPROFILE%/Saved Games/DCS/Missions`).
- A tacview track file, especially when demonstrating an issue with AI behavior. By default these are locaed in your Tacview tracks directory (`%USERPROFILE%/Documents/Tacview`).
**Version information (please complete the following information):**
- DCS Liberation [e.g. 2.3.1]:
**Additional context**
Add any other context about the problem here.

View File

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

View File

@@ -18,15 +18,29 @@ jobs:
- name: Install environment
run: |
py -m venv ./venv
python -m venv ./venv
- name: Install dependencies
run: |
./venv/scripts/activate
pip install -r requirements.txt
python -m pip install -r requirements.txt
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
- name: mypy game
run: |
./venv/scripts/activate
mypy game
- name: mypy gen
run: |
./venv/scripts/activate
mypy gen
- name: update build number
run: |
[IO.File]::WriteAllLines($pwd.path + "\resources\buildnumber", $env:GITHUB_RUN_NUMBER)
- name: Build binaries
run: |
./venv/scripts/activate

View File

@@ -20,15 +20,29 @@ jobs:
- name: Install environment
run: |
py -m venv ./venv
python -m venv ./venv
- name: Install dependencies
run: |
./venv/scripts/activate
pip install -r requirements.txt
python -m pip install -r requirements.txt
# For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead
Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force
- name: Finalize version
run: |
New-Item -ItemType file resources\final
- name: mypy game
run: |
./venv/scripts/activate
mypy game
- name: mypy gen
run: |
./venv/scripts/activate
mypy gen
- name: Build binaries
run: |
./venv/scripts/activate
@@ -39,11 +53,6 @@ jobs:
env:
TAG_NAME: ${{ github.ref }}
run: |
$version = ($env:TAG_NAME -split "/") | Select-Object -Last 1
(Get-Content .\installer\dcs_liberation.iss) -replace "{{version}}",$version | Out-File .\build\installer.iss
cd .\installer
iscc.exe ..\build\installer.iss
cd ..
Copy-Item .\changelog.md .\dist
- uses: actions/upload-artifact@v2
@@ -86,15 +95,7 @@ jobs:
body_path: releasenotes.md
draft: false
prerelease: ${{ steps.version.outputs.prerelease }}
- uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./dcs_liberation.exe
asset_name: dcs_liberation.${{ steps.version.outputs.number }}.exe
asset_content_type: application/exe
- uses: actions/upload-release-asset@v1
env:

5
.gitignore vendored
View File

@@ -5,10 +5,10 @@ resources/payloads/*.lua
venv
logs.txt
.DS_Store
.vscode/settings.json
dist/**
a.py
resources/tools/a.miz
tests/**
# User-specific stuff
.idea/
@@ -16,9 +16,8 @@ tests/**
/liberation_preferences.json
/state.json
logs/liberation.log
logs/
qt_ui/logs/liberation.log
*.psd
resources/scripts/plugins/*

View File

@@ -1,4 +0,0 @@
{
"python.pythonPath": "g:\\python\\dcs_liberation\\venv\\Scripts\\python.exe",
"vsintellicode.python.completionsEnabled": true
}

76
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at khopa.studio@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

26
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,26 @@
First, note that we have a code of conduct, please follow it in all your interactions with the project.
## Contributing as a non-developer
* Report bugs by opening issues here on Github.
* Help others users on Discord by answering their questions.
* Raise awareness about the project, by making a video and/or a tutorial.
Should you report a bug, please use the search bar at the top of the page to see if it has already been reported.
Note that you may need to remove the filter for open bugs if it's something we've recently fixed.
## Making content for Liberation
You can create new campaigns : See [campaign creation wiki](https://github.com/Khopa/dcs_liberation/wiki/Custom-Campaigns).
You can also improve existing campaigns.
You can then submit new campaigns on the "campaigns" channel on Discord, or by making a pull request if you are comfortable with git.
## Develop new features
If you want to develop a new feature, we recommend you first open an issue describing the new feature and discuss it with us on Discord before starting development.
However, feel free to work on any existing issue.
## Pull requests
Please submit your pull requests on the **develop** branch. We expect a description of its content, and when applicable, a reference to the issue(s) it is resolving.

165
LICENSE Normal file
View File

@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@@ -12,15 +12,25 @@
![GitHub stars](https://img.shields.io/github/stars/khopa/dcs_liberation?style=social)
## About DCS Liberation
DCS Liberation is a [DCS World](https://www.digitalcombatsimulator.com/en/products/world/) turn based single-player semi dynamic campaign.
DCS Liberation is a [DCS World](https://www.digitalcombatsimulator.com/en/products/world/) turn based single-player or co-op dynamic campaign.
It is an external program that generates full and complex DCS missions and manage a persistent combat environment.
![Logo](https://imgur.com/B6tvlBJ.png)
![Logo](https://i.imgur.com/4hq0rLq.png)
## Downloads
Latest release is available here : https://github.com/Khopa/dcs_liberation/releases
To download preview builds of the next version of DCS Liberation, see https://github.com/Khopa/dcs_liberation/wiki/Preview-builds.
## Bugs and feature requests
If you need to report a bug or want to suggest a new feature, you can do this on our [bug tracker](https://github.com/Khopa/dcs_liberation/issues). In either case, please use the search bar at the top of the page to see if it has already been reported. Note that you may need to remove the filter for open bugs if it's something we've recently fixed.
## Roadmap
Our plans for future releases can be found on our [Projects page](https://github.com/Khopa/dcs_liberation/projects). Each planned release has a Project, and the page for that project has columns for to do, in progress, and done. Items in the Done column are in the [preview build](https://github.com/Khopa/dcs_liberation/wiki/Preview-builds) for that release. Items in the To do column are planned to be added to that release.
## Resources
Tutorials, contributors and developer's guides are available in the project's [Wiki](https://github.com/Khopa/dcs_liberation/wiki/)
@@ -31,6 +41,10 @@ First, a big thanks to shdwp, for starting the original DCS Liberation project.
Then, DCS Liberation uses [pydcs](http://github.com/pydcs/dcs) for mission generation, and nothing would be possible without this.
It also uses the popular [Mist](https://github.com/mrSkortch/MissionScriptingTools) lua framework for mission scripting.
And for the JTAC feature, DCS Liberation embed Ciribob's JTAC Autolase [script](https://github.com/ciribob/DCS-JTACAutoLaze).
Excellent lua scripts DCS Liberation uses as plugins:
* For the JTAC feature, DCS Liberation embeds Ciribob's JTAC Autolase [script](https://github.com/ciribob/DCS-JTACAutoLaze).
* Walder's [Skynet-IADS](https://github.com/walder/Skynet-IADS) is used for Integrated Air Defense System.
Please also show some support to these projects !

View File

@@ -1,3 +1,278 @@
# 2.4.3
## Features/Improvements
* **[New Game Wizard]** Added the possibility to setup custom start date
## Fixes
* **[Mods]** Updated C-130J mod data to version 6.4
* **[Mods]** Updated F-22A mod to latest version
* **[Payload]** Mirage-2000C : Added Eclair counter measures pod to all default loadouts
# 2.4.2
## Features/Improvements
* **[Factions]** Introduction dates and fallback weapons added for US, Russian, UK, and French weapons. Huge thanks to @TheCandianVendingMachine for the massive amount of data entry!
* **[Campaigns]** Added 1995 start dates.
## Fixes
* **[Economy]** Pending ground unit purchases will also be transferred when a connected base is captured.
* **[UI]** Fixed rounding of budget in recruitment menu.
# 2.4.1
## Fixes
* **[Units]** Fixed syntax error with the SH-60B payload file.
* **[Culling]** Missile sites generate reasonably sized non-cull zones rather than 100km ones.
* **[UI]** Budget display is also now rounded to 2 decimal places.
* **[UI]** Fixed some areas where the old, non-pretty name was displayed to users.
# 2.4.0
Saves from 2.3 are not compatible with 2.4.
## Highlights
* Improved flight plan generation to avoid loitering in or traveling through threatened areas when practical.
* Improved AI aircraft purchasing behavior.
* Era-restricted weapons (work in progress).
* Tons of UI polish.
* Rebalanced economy to keep opfor competitive over the course of the game.
## Features/Improvements
* **[Flight Planner]** Air-to-air and SEAD escorts will no longer be automatically planned for packages that are not in range of threats.
* **[Flight Planner]** Non-custom flight plans will now navigate around threat areas en route to the target area when practical.
* **[Flight Planner]** Flight plans along front lines now ensure that the race track start is closer to the departure airfield than the race track end.
* **[Campaign AI]** Auto-purchase now prefers airfields that are not within range of the enemy.
* **[Campaign AI]** Auto-purchase now prefers the best aircraft for the task, but will attempt to maintain some variety.
* **[Campaign AI]** Opfor now sells off odd aircraft since they're unlikely to be used.
* **[Campaign AI]** Multiple rounds of CAP will be planned (roughly 90 minutes of coverage). Default starting budget has increased to account for the increased need for aircraft.
* **[Mission Generator]** Multiple groups are created for complex SAM sites (SAMs with additional point defense or SHORADS), improving Skynet behavior.
* **[Mission Generator]** Default start type can now be chosen in the settings. This replaces the non-functional "AI Parking Start" option. **Selecting any type other than cold will break OCA/Aircraft missions.**
* **[Cheat Menu]** Added ability to toggle base capture and frontline advance/retreat cheats.
* **[Skynet]** Updated to 2.0.1.
* **[Skynet]** Point defenses are now configured to remain on to protect the site they accompany.
* **[Hercules]** Updated the Hercules Cargo list file.
* **[Balance]** Opfor now gains income using the same rules as the player, significantly increasing their income relative to the player for most campaigns.
* **[Balance]** Units now retreat from captured bases when able. Units with no retreat path will be captured and sold.
* **[Economy]** FOBs generate only $10M per turn (previously $20M like airbases).
* **[Economy]** Carriers and off-map spawns generate no income (previously $20M like airbases).
* **[Economy]** Sales of aircraft and ground vehicles can now be cancelled before the next turn begins.
* **[UI]** Multi-SAM objectives now show threat and detection rings per group.
* **[UI]** New icon for AA sites with no active threat.
* **[UI]** Unit names are now prettier and more accurate, and can now be set per-country for added historical flavour.
* **[UI]** Default loadout is now shown for flights with no custom loadout selected.
* **[UI]** Aircraft for a new flight are now only selectable if they match the task type for that flight.
* **[UI]** WIP - There is now a unit info button for each unit in the recruitment list, that should help newer players learn what each unit does.
* **[UI]** Docs for time-on-target and creating new theaters/factions/loadouts are now linked in the UI at the appropriate places.
* **[UI]** ASAP is now a checkbox rather than a button. Enabling this will disable the TOT selector but changes to the package structure will automatically re-ASAP the package.
* **[UI]** Arrival airfield is now shown in the flight list if it differs from the departure airfield.
* **[UI]** Start type can now be selected when creating a flight.
* **[UI]** Arrival and divert airfields can be edited after the flight is created.
* **[Factions]** Added option for date-based loadout restriction. Active radar homing missiles are handled, patches welcome for the other thousand weapons.
* **[Factions]** Added Poland 2010 faction.
* **[Factions]** Added Greece 2005 faction.
* **[Factions]** Added Iran 1988 faction.
* **[Units]** Support for E-2 Hawkeye, SH-60B Seahawk, S-3B Viking (thanks to awinterquest) and SpGH Dana - these are now being used by appropriate factions.
* **[Culling]** Missile sites are no longer culled.
* **[Campaigns]** Added campaign "Black Sea Lite" by Starfire
* **[Campaigns]** Added campaign "Exercise Vegas Nerve" by Starfire
* **[New game Wizard]** The theater page is now the first page of the campaign wizard, recommended factions will be selected automatically on the faction selection page
* **[New game Wizard]** Added information text about the selected campaign performance.
* **[Mod Support]** Added support for High Digit SAMs mod 1.4.0
* **[Mod Support]** Added SAMs sites generator : KS19Generator, SA10BGenerator, SA12Generator, SA17Generator, SA20Generator, SA20BGenerator, SA23Generator
## Fixes
* **[Hercules]** Updated the default Hercules radio frequency.
* **[Economy]** Pending unit orders at captured bases will be refunded.
* **[UI]** Carrier group SAM threat rings now move with the carrier.
* **[UI]** Base intel menu no longer compresses text, and is now scrollable.
* **[UI]** Edit Flight window is now dynamically sized to adapt to the width of waypoint names, so they no longer get truncated.
* **[UI]** Budget income display is now rounded to 2 decimal places.
* **[UI]** Fixed incorrect income per turn displayed for strike target tooltip.
* **[Factions]** USA with C-130 faction now links to the required mod.
* **[Campaign]** Fixed issue where destroyed buildings would sometimes not count as destroyed and thus respawn.
* **[Campaign]** Fixed issue where destroyed runways were not registered.
* **[Units]** J-11A is no longer spawned with empty loadout.
* **[Units]** F-14B is no longer spawned with empty loadout for fighter sweep tasks.
* **[Units]** Pyotr Velikiy cruiser has been removed for now as it's nearly unkillable.
* **[Units]** Submarines have been removed for now as they aren't wholly functional.
* **[Units]** Fixed "FACTION ERROR : Unable to find OliverHazardPerryGroupGenerator in pydcs" error at startup.
* **[Mission Generator]** Fixed a bug where units set to Aggressive stance sometimes did not move.
* **[Mission Generator]** Flyover points for OCA/Aircraft missions are now generated correctly.
* **[Flight Planner]** Fixed not being able to create custom waypoints for buildings.
* **[Flight Planner]** Strike missions will no longer be automatically planned against SAMs.
* **[Flight Planner]** Strike missions will no longer be automatically planned against FOB structures.
# 2.3.4
## Fixes:
[Mission Generator] Mission generator would crash when generating fire missions for destroyed SCUD sites - fixed
# 2.3.3
## Features/Improvements
* **[Campaigns]** Reworked Golan Heights campaign on Syria, (Added FOB and preset locations for SAMS)
* **[Campaigns]** Added a lite version of the Golan Heights campaign
* **[Campaigns]** Reworked Syrian Civil War campaign (Added FOB and preset locations for SAMS)
* **[Campaigns]** Reworked Emirates campaign
* **[Campaigns]** AA units added to frontlines and updated all factions to include some frontline AA units.
* **[Mission Generator]** Infantry will only be generated for APC and IFV groups
* **[Mission Generator]** Infantry squads size is not randomized anymore
* **[Mission Generator]** Infantry squads can have a mortar.
* **[Mission Generator]** SCUD missiles sites will now fire on enemy controls points in range when possible
* **[Factions]** Updated Nato Desert Storm to include F-14A
* **[Factions]** Updated Iraq 1991 factions to include Zsu-57 and Mig-29A
* **[Factions]** Germany 1944, added Stug III and Stug IV
* **[Factions]** Added factions Insurgents (Hard) with better and more weapons
* **[Plugins]** [The EWRS plugin](https://github.com/Bob7heBuilder/EWRS) is now included.
* **[UI]** Added enemy intelligence summary and details window.
## Fixes:
* **[Factions]** AI would never buy artillery units for the frontline - fixed
* **[Factions]** Removed the F-111 unit from the NATO desert storm faction. (Recruiting it would cause crashes in DCS, since it is not a valid unit)
* **[Campaign]** Automatic redeployment of ground units would sometimes fail - fixed
* **[Mission Generator]** Artillery groups would retreat in the wrong direction - fixed
* **[Units]** Fixed SPG_Stryker_M1128_MGS not being in db
* **[UI]** Fixed and added many missing ground units icons
* **[UI]** Ship groups could be replaced by SAM sites in the UI, which would lead to broken mission being generated - fixed
* **[New Game Wizard]** Removed the "mid game" campaign generator option which is currently broken
* **[Mission Generator]** Empty navy groups will no longer be generated
* **[Mission Generator]** Fixed BAI, SEAD, and DEAD flights ocassionally being assigned the wrong targets.
* **[Flight Planner]** Fixed not being able to plan packages against opfor carriers
* **[UI]** Repaired SAMs no longer show as dead.
* **[UI]** Fixed not being able to manage a disbanded site after disbanding and closing the base menu.
# 2.3.2
## Features/Improvements
* **[Units]** Support for newly added BTR-82A, T-72B3
* **[Units]** Added ZSU-57 AAA sites
* **[Culling]** BARCAP missions no longer create culling exclusion zones.
* **[Flight Planner]** Improved TOT planning. Negative start times no longer occur with TARCAPs and hold times no longer affect planning for flight plans without hold points.
* **[Factions]** Added Iraq 1991 faction (thanks again to Hawkmoon!)
## Fixes:
* **[Mission Generator]** Fix mission generation error when there are too many radio frequency to setup for the Mig-21
* **[Mission Generator]** Fix ground units not moving forward
* **[Mission Generator]** Fixed assigned radio channels overlapping with beacons.
* **[Flight Planner]** Fix creation of custom waypoints.
* **[Campaigns]** Fixed many cases of SAMs spawning on the runways/taxiways in Syria Full.
# 2.3.1
## Features/Improvements
* **[UX]** Added a warning message when the player is attempting to buy more planes at an already full airbase.
* **[Campaigns]** Migrated Syria full map to new format. (Thanks to Hawkmoon)
* **[Faction]** Added NATO desert Storm faction (Thanks to Hawkmoon)
## Fixes:
* **[AI]** CAP flights will engage enemies again.
* **[Campaigns]** Fixed a missing path on the Caucasus Full Map campaign
# 2.3.0
## Features/Improvements
* **[Campaign Map]** Overhauled the campaign model
* **[Campaign Map]** Possible to add FOB as control points
* **[Campaign Map]** Added off-map spawn locations
* **[Campaign AI]** Overhauled AI recruiting behaviour
* **[Campaign AI]** Added AI procurement for Blue
* **[Campaign]** New Campaign: "Black Sea"
* **[Mission Planner]** Possible to move carrier and tarawa on the campaign map
* **[Mission Generator]** Infantry squads on frontline can have manpads
* **[Mission Generator]** Unused aircraft now spawned to allow for OCA strikes
* **[Mission Generator]** Opfor now obeys parking limits
* **[Mission Generator]** Support for Anubis C-130 Hercules mod
* **[Flight Planner]** Added fighter sweep missions.
* **[Flight Planner]** Added BAI missions.
* **[Flight Planner]** Added anti-ship missions.
* **[Flight Planner]** Differentiated BARCAP and TARCAP. TARCAP is now for hostile areas and will arrive before the package.
* **[Flight Planner]** Added OCA missions
* **[Flight Planner]** Added Alternate/divert airfields
* **[Culling]** Added possibility to include/exclude carriers from culling zones
* **[QOL]** On liberation startup, your latest save game is loaded automatically
* **[Units]** Reduced starting fuel load for C101
* **[UI]** Inform the user of the weather
* **[UI]** Added toolbar buttons to change map display settings
* **[Game]** Added new Economy options for adjusting income multipliers and starting budgets.
## Fixes :
* **[Map]** Missiles sites now have a proper icon and will not re-use the SAM sites icon
* **[Mission Generator]** Ground unit waypoints improperly set to "On Road" - fixed
* **[Mission Generator]** Target waypoints not at ground level - fixed
* **[Mission Generator]** Selected skill not applied to Helicopters - fixed
* **[Mission Generator]** Ground units do not always spawn - fixed
* **[Kneeboard]** Briefing waypoints off by one - fixed
* **[Game]** Destroyed buildings still granting budget - fixed
# 2.2.1
## Features/Improvements
* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom
* **[Factions]** Added map Persian Gulf full by Plob
* **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately.
* **[UI]** Mission start screen now informs players about delayed flights.
* **[Units]** Added support for F-14A-135-GR
* **[Modding]** Possible to setup liveries overrides in factions definition files
## Fixes :
* **[Flight Planner]** Hold, join, and split points are planned cautiously near enemy airfields. Ascend/descend points are no longer planned.
* **[Flight Planner]** Custom waypoints are usable again. Not that in most cases custom flight plans will revert to the 2.1 flight planning behavior.
* **[Flight Planner]** Fixed UI bug that made it possible to create empty flights which would throw an error.
* **[Flight Planner]** Player flights from carriers will now be delayed correctly according to the player's settings.
* **[Misc]** Spitfire variant with clipped wings was not seen as flyable by DCS Liberation (hence could not be setup as client/player slot)
* **[Misc]** Updated Syria terrain parking slots database, the out-of-date database could end up generating aircraft in wrong slots (We are still experiencing issues with somes airbases, such as Khalkhalah though)
# 2.2.0
## Features/Improvements :
* **[Campaign Generator]** Added early warning radar generation
* **[Campaign Generator]** Added scud launcher sites
* **[Cheat Menu]** Added ability to capture base from mission planner
* **[Cheat Menu]** Added ability to show red ATO
* **[Factions]** Added WW2 factions that do not depend on WW2 asset pack
* **[Factions]** Cold War / Middle eastern factions will use Flak sites
* **[Flight Planner]** Flight planner overhaul, with package and TOT system
* **[Flight Planner]** Pick runways and ascent/descent based on headwind
* **[Map]** Added polygon debug mode display
* **[Map]** Highlight the selected flight path on the map
* **[Map]** Improved SAM display settings
* **[Map]** Improved flight plan display settings
* **[Map]** Caucasus and The Channel map use a new system to generate SAM and strike target location to reduce probability of targets generated in the middle of a forests
* **[Misc]** Flexible Dedicated Hosting Options for Mission Files via environment variables
* **[Moddability]** Custom campaigns can be designed through json files
* **[Moddability]** LUA plugins can now be injected into Liberation missions.
* **[Moddability]** Optional Skynet IADS lua plugin now included
* **[New Game]** Starting budget can be freely selected
* **[New Game]** Exanded information for faction and campaign selection in the new game wizard
* **[UI]** Add double and right click actions to many UI elements.
* **[UI]** Add polygon drawing mode for map background
* **[UI]** Added a warning if you press takeoff with no player enabled flights
* **[UI]** Packages and flights now visible in the main window sidebar
* **[Units/Factions]** Added bombers to some coalitions
* **[Units/Factions]** Added support for SU-57 mod by Cubanace
* **[Units]** Added Freya EWR sites to german WW2 factions
* **[Units]** Added support for many bombers (B-52H, B-1B, Tu-22, Tu-142)
* **[Units]** Added support for new P-47 variants
## Fixes :
* **[Campaign Generator]** Big airbases could end up without any airbase defense.
* **[Campaign generator]** Ship group and offshore buildings should not be generated on land anymore
* **[Flight Planner]** Fix waypoint alitudes for helicopters
* **[Flight Planner]** Fixed CAS aircraft wandering away from frontline
* **[Maps]** Incirlik airbase was missing exclusions zones, so SAMS could end up being generated on the runway
* **[Mission Generator]** Fixed player/client confusion when a flight had only one player slot.
* **[Radios]** Fix A-10C radio
* **[UI]** Many missing unit icons were added
* **[UI]** Missing TER weapons in custom payload now selectable.
# 2.1.5
## Features/Improvements :
@@ -10,9 +285,7 @@
# 2.1.4
## Fixes :
* **[UI]** Fixed an issue that prevent generating the mission (take off button no working) on old savegames.
# 2.1.3
* **[UI]** Fixed an issue that prevented generating the mission (take off button no working) on old savegames.
## Features/Improvements :
* **[Units/Factions]** Added A-10C_2 to USA 2005 and Bluefor modern factions
@@ -276,4 +549,4 @@ Sorry :(
* **[Mission Generator]** Planned flights will spawn even if their home base has been captured or is being contested by enemy ground units.
* **[Campaign Generator]** Base defenses would not be generated on Normandy map and in some rare cases on others maps as well
* **[Mission Planning]** CAS waypoints created from the "Predefined waypoint selector" would not be at the exact location of the frontline
* **[Naming]** CAP mission flown from airbase are not named BARCAP anymore (CAP from carrier is still named BARCAP)
* **[Naming]** CAP mission flown from airbase are not named BARCAP anymore (CAP from carrier is still named BARCAP)

View File

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

View File

@@ -17,5 +17,5 @@ AAA_UNITS = [
AirDefence.AAA_Flak_Vierling_38,
AirDefence.AAA_Kdo_G_40,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_Bofors_40mm
]
AirDefence.AAA_Bofors_40mm,
]

View File

@@ -1,16 +1,70 @@
import inspect
import dcs
DEFAULT_AVAILABLE_BUILDINGS = ['fuel', 'ammo', 'comms', 'oil', 'ware', 'farp', 'fob', 'power', 'factory', 'derrick', 'aa']
DEFAULT_AVAILABLE_BUILDINGS = [
"fuel",
"ammo",
"comms",
"oil",
"ware",
"farp",
"fob",
"power",
"factory",
"derrick",
]
WW2_GERMANY_BUILDINGS = ['fuel', 'factory', 'ww2bunker', 'ww2bunker', 'ww2bunker', 'allycamp', 'allycamp', 'aa']
WW2_ALLIES_BUILDINGS = ['fuel', 'factory', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'allycamp', 'aa']
WW2_FREE = ["fuel", "factory", "ware", "fob"]
WW2_GERMANY_BUILDINGS = [
"fuel",
"factory",
"ww2bunker",
"ww2bunker",
"ww2bunker",
"allycamp",
"allycamp",
"fob",
]
WW2_ALLIES_BUILDINGS = [
"fuel",
"factory",
"allycamp",
"allycamp",
"allycamp",
"allycamp",
"allycamp",
"fob",
]
FORTIFICATION_BUILDINGS = ['Siegfried Line', 'Concertina wire', 'Concertina Wire', 'Czech hedgehogs 1', 'Czech hedgehogs 2',
'Dragonteeth 1', 'Dragonteeth 2', 'Dragonteeth 3', 'Dragonteeth 4', 'Dragonteeth 5',
'Haystack 1', 'Haystack 2', 'Haystack 3', 'Haystack 4', 'Hemmkurvenvenhindernis',
'Log posts 1', 'Log posts 2', 'Log posts 3', 'Log ramps 1', 'Log ramps 2', 'Log ramps 3',
'Belgian Gate', 'Container white']
FORTIFICATION_BUILDINGS = [
"Siegfried Line",
"Concertina wire",
"Concertina Wire",
"Czech hedgehogs 1",
"Czech hedgehogs 2",
"Dragonteeth 1",
"Dragonteeth 2",
"Dragonteeth 3",
"Dragonteeth 4",
"Dragonteeth 5",
"Haystack 1",
"Haystack 2",
"Haystack 3",
"Haystack 4",
"Hemmkurvenvenhindernis",
"Log posts 1",
"Log posts 2",
"Log posts 3",
"Log ramps 1",
"Log ramps 2",
"Log ramps 3",
"Belgian Gate",
"Container white",
]
FORTIFICATION_UNITS = [c for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)]
FORTIFICATION_UNITS_ID = [c.id for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)]
FORTIFICATION_UNITS = [
c for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)
]
FORTIFICATION_UNITS_ID = [
c.id for c in vars(dcs.vehicles.Fortification).values() if inspect.isclass(c)
]

View File

@@ -1,4 +1,24 @@
from dcs.planes import *
from dcs.planes import (
Bf_109K_4,
C_101CC,
FW_190A8,
FW_190D9,
F_5E_3,
F_86F_Sabre,
I_16,
L_39ZA,
MiG_15bis,
MiG_19P,
MiG_21Bis,
P_47D_30,
P_47D_30bl1,
P_47D_40,
P_51D,
P_51D_30_NA,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
)
from pydcs_extensions.a4ec.a4ec import A_4E_C
"""
@@ -6,7 +26,6 @@ This list contains the aircraft that do not use the guns as the last resort weap
They'll RTB when they don't have gun ammo left
"""
GUNFIGHTERS = [
# Cold War
MiG_15bis,
MiG_19P,
@@ -14,20 +33,19 @@ GUNFIGHTERS = [
F_86F_Sabre,
A_4E_C,
F_5E_3,
# Trainers
C_101CC,
L_39ZA,
# WW2
P_51D_30_NA,
P_51D,
P_47D_30,
P_47D_30bl1,
P_47D_40,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
Bf_109K_4,
FW_190D9,
FW_190A8,
I_16,
]
]

View File

@@ -1,95 +1,133 @@
from game.utils import nm_to_meter, feet_to_meter
from dataclasses import dataclass
from datetime import timedelta
MODERN_DOCTRINE = {
from game.utils import Distance, feet, nautical_miles
"GENERATORS": {
"CAS": True,
"CAP": True,
"SEAD": True,
"STRIKE": True,
"ANTISHIP": True,
},
"STRIKE_MAX_RANGE": 1500000,
"SEAD_MAX_RANGE": 1500000,
@dataclass(frozen=True)
class Doctrine:
cas: bool
cap: bool
sead: bool
strike: bool
antiship: bool
"CAP_EVERY_X_MINUTES": 20,
"CAS_EVERY_X_MINUTES": 30,
"SEAD_EVERY_X_MINUTES": 40,
"STRIKE_EVERY_X_MINUTES": 40,
rendezvous_altitude: Distance
hold_distance: Distance
push_distance: Distance
join_distance: Distance
split_distance: Distance
ingress_egress_distance: Distance
ingress_altitude: Distance
egress_altitude: Distance
"INGRESS_EGRESS_DISTANCE": nm_to_meter(45),
"INGRESS_ALT": feet_to_meter(20000),
"EGRESS_ALT": feet_to_meter(20000),
"PATROL_ALT_RANGE": (feet_to_meter(15000), feet_to_meter(33000)),
"PATTERN_ALTITUDE": feet_to_meter(5000),
min_patrol_altitude: Distance
max_patrol_altitude: Distance
pattern_altitude: Distance
"CAP_PATTERN_LENGTH": (nm_to_meter(15), nm_to_meter(40)),
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(6), nm_to_meter(15)),
"CAP_DISTANCE_FROM_CP": (nm_to_meter(10), nm_to_meter(40)),
#: The duration that CAP flights will remain on-station.
cap_duration: timedelta
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
}
#: The minimum length of the CAP race track.
cap_min_track_length: Distance
COLDWAR_DOCTRINE = {
#: The maximum length of the CAP race track.
cap_max_track_length: Distance
"GENERATORS": {
"CAS": True,
"CAP": True,
"SEAD": True,
"STRIKE": True,
"ANTISHIP": True,
},
#: The minimum distance between the defended position and the *end* of the
#: CAP race track.
cap_min_distance_from_cp: Distance
"STRIKE_MAX_RANGE": 1500000,
"SEAD_MAX_RANGE": 1500000,
#: The maximum distance between the defended position and the *end* of the
#: CAP race track.
cap_max_distance_from_cp: Distance
"CAP_EVERY_X_MINUTES": 20,
"CAS_EVERY_X_MINUTES": 30,
"SEAD_EVERY_X_MINUTES": 40,
"STRIKE_EVERY_X_MINUTES": 40,
#: The engagement range of CAP flights. Any enemy aircraft within this range
#: of the CAP's current position will be engaged by the CAP.
cap_engagement_range: Distance
"INGRESS_EGRESS_DISTANCE": nm_to_meter(30),
"INGRESS_ALT": feet_to_meter(18000),
"EGRESS_ALT": feet_to_meter(18000),
"PATROL_ALT_RANGE": (feet_to_meter(10000), feet_to_meter(24000)),
"PATTERN_ALTITUDE": feet_to_meter(5000),
cas_duration: timedelta
"CAP_PATTERN_LENGTH": (nm_to_meter(12), nm_to_meter(24)),
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(2), nm_to_meter(8)),
"CAP_DISTANCE_FROM_CP": (nm_to_meter(8), nm_to_meter(25)),
sweep_distance: Distance
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
}
WWII_DOCTRINE = {
MODERN_DOCTRINE = Doctrine(
cap=True,
cas=True,
sead=True,
strike=True,
antiship=True,
rendezvous_altitude=feet(25000),
hold_distance=nautical_miles(15),
push_distance=nautical_miles(20),
join_distance=nautical_miles(20),
split_distance=nautical_miles(20),
ingress_egress_distance=nautical_miles(45),
ingress_altitude=feet(20000),
egress_altitude=feet(20000),
min_patrol_altitude=feet(15000),
max_patrol_altitude=feet(33000),
pattern_altitude=feet(5000),
cap_duration=timedelta(minutes=30),
cap_min_track_length=nautical_miles(15),
cap_max_track_length=nautical_miles(40),
cap_min_distance_from_cp=nautical_miles(10),
cap_max_distance_from_cp=nautical_miles(40),
cap_engagement_range=nautical_miles(50),
cas_duration=timedelta(minutes=30),
sweep_distance=nautical_miles(60),
)
"GENERATORS": {
"CAS": True,
"CAP": True,
"SEAD": False,
"STRIKE": True,
"ANTISHIP": True,
},
COLDWAR_DOCTRINE = Doctrine(
cap=True,
cas=True,
sead=True,
strike=True,
antiship=True,
rendezvous_altitude=feet(22000),
hold_distance=nautical_miles(10),
push_distance=nautical_miles(10),
join_distance=nautical_miles(10),
split_distance=nautical_miles(10),
ingress_egress_distance=nautical_miles(30),
ingress_altitude=feet(18000),
egress_altitude=feet(18000),
min_patrol_altitude=feet(10000),
max_patrol_altitude=feet(24000),
pattern_altitude=feet(5000),
cap_duration=timedelta(minutes=30),
cap_min_track_length=nautical_miles(12),
cap_max_track_length=nautical_miles(24),
cap_min_distance_from_cp=nautical_miles(8),
cap_max_distance_from_cp=nautical_miles(25),
cap_engagement_range=nautical_miles(35),
cas_duration=timedelta(minutes=30),
sweep_distance=nautical_miles(40),
)
"STRIKE_MAX_RANGE": 1500000,
"SEAD_MAX_RANGE": 1500000,
"CAP_EVERY_X_MINUTES": 20,
"CAS_EVERY_X_MINUTES": 30,
"SEAD_EVERY_X_MINUTES": 40,
"STRIKE_EVERY_X_MINUTES": 40,
"INGRESS_EGRESS_DISTANCE": nm_to_meter(7),
"INGRESS_ALT": feet_to_meter(8000),
"EGRESS_ALT": feet_to_meter(8000),
"PATROL_ALT_RANGE": (feet_to_meter(4000), feet_to_meter(15000)),
"PATTERN_ALTITUDE": feet_to_meter(5000),
"CAP_PATTERN_LENGTH": (nm_to_meter(8), nm_to_meter(18)),
"FRONTLINE_CAP_DISTANCE_FROM_FRONTLINE": (nm_to_meter(1), nm_to_meter(6)),
"CAP_DISTANCE_FROM_CP": (nm_to_meter(0), nm_to_meter(5)),
"MAX_NUMBER_OF_INTERCEPTION_GROUP": 3,
}
WWII_DOCTRINE = Doctrine(
cap=True,
cas=True,
sead=False,
strike=True,
antiship=True,
hold_distance=nautical_miles(5),
push_distance=nautical_miles(5),
join_distance=nautical_miles(5),
split_distance=nautical_miles(5),
rendezvous_altitude=feet(10000),
ingress_egress_distance=nautical_miles(7),
ingress_altitude=feet(8000),
egress_altitude=feet(8000),
min_patrol_altitude=feet(4000),
max_patrol_altitude=feet(15000),
pattern_altitude=feet(5000),
cap_duration=timedelta(minutes=30),
cap_min_track_length=nautical_miles(8),
cap_max_track_length=nautical_miles(18),
cap_min_distance_from_cp=nautical_miles(0),
cap_max_distance_from_cp=nautical_miles(5),
cap_engagement_range=nautical_miles(20),
cas_duration=timedelta(minutes=30),
sweep_distance=nautical_miles(10),
)

View File

@@ -1,8 +1,28 @@
from dcs.ships import (
CGN_1144_2_Pyotr_Velikiy,
CG_1164_Moskva,
CVN_70_Carl_Vinson,
CVN_71_Theodore_Roosevelt,
CVN_72_Abraham_Lincoln,
CVN_73_George_Washington,
CVN_74_John_C__Stennis,
CV_1143_5_Admiral_Kuznetsov,
CV_1143_5_Admiral_Kuznetsov_2017,
FFG_11540_Neustrashimy,
FFL_1124_4_Grisha,
FF_1135M_Rezky,
FSG_1241_1MP_Molniya,
LHA_1_Tarawa,
Oliver_Hazzard_Perry_class,
Ticonderoga_class,
Type_052B_Destroyer,
Type_052C_Destroyer,
Type_054A_Frigate,
USS_Arleigh_Burke_IIa,
)
from dcs.vehicles import AirDefence
from dcs.ships import *
UNITS_WITH_RADAR = [
# Radars
AirDefence.SAM_SA_15_Tor_9A331,
AirDefence.SAM_SA_11_Buk_CC_9S470M1,
@@ -28,7 +48,6 @@ UNITS_WITH_RADAR = [
AirDefence.SAM_SA_3_S_125_TR_SNR,
AirDefence.SAM_SA_2_TR_SNR_75_Fan_Song,
AirDefence.HQ_7_Self_Propelled_STR,
# Ships
CVN_70_Carl_Vinson,
Oliver_Hazzard_Perry_class,
@@ -49,5 +68,5 @@ UNITS_WITH_RADAR = [
LHA_1_Tarawa,
Type_052B_Destroyer,
Type_054A_Frigate,
Type_052C_Destroyer
]
Type_052C_Destroyer,
]

886
game/data/weapons.py Normal file
View File

@@ -0,0 +1,886 @@
from __future__ import annotations
import datetime
import inspect
import logging
from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, Iterator, Optional, Set, Tuple, Type, Union, cast
from dcs.unitgroup import FlyingGroup
from dcs.unittype import FlyingType
from dcs.weapons_data import Weapons, weapon_ids
PydcsWeapon = Dict[str, Union[int, str]]
PydcsWeaponAssignment = Tuple[int, PydcsWeapon]
@dataclass(frozen=True)
class Weapon:
"""Wraps a pydcs weapon dict in a hashable type."""
cls_id: str
name: str
weight: int
def available_on(self, date: datetime.date) -> bool:
introduction_year = WEAPON_INTRODUCTION_YEARS.get(self)
if introduction_year is None:
logging.warning(
f"No introduction year for {self}, assuming always available"
)
return True
return date >= datetime.date(introduction_year, 1, 1)
@property
def as_pydcs(self) -> PydcsWeapon:
return {
"clsid": self.cls_id,
"name": self.name,
"weight": self.weight,
}
@property
def fallbacks(self) -> Iterator[Weapon]:
yield self
fallback = WEAPON_FALLBACK_MAP[self]
if fallback is not None:
yield from fallback.fallbacks
@classmethod
def from_pydcs(cls, weapon_data: PydcsWeapon) -> Weapon:
return cls(
cast(str, weapon_data["clsid"]),
cast(str, weapon_data["name"]),
cast(int, weapon_data["weight"]),
)
@classmethod
def from_clsid(cls, clsid: str) -> Optional[Weapon]:
data = weapon_ids.get(clsid)
if data is None:
return None
return cls.from_pydcs(data)
@dataclass(frozen=True)
class Pylon:
number: int
allowed: Set[Weapon]
def can_equip(self, weapon: Weapon) -> bool:
return weapon in self.allowed
def equip(self, group: FlyingGroup, weapon: Weapon) -> None:
if not self.can_equip(weapon):
raise ValueError(f"Pylon {self.number} cannot equip {weapon.name}")
group.load_pylon(self.make_pydcs_assignment(weapon), self.number)
def make_pydcs_assignment(self, weapon: Weapon) -> PydcsWeaponAssignment:
return self.number, weapon.as_pydcs
def available_on(self, date: datetime.date) -> Iterator[Weapon]:
for weapon in self.allowed:
if weapon.available_on(date):
yield weapon
@classmethod
def for_aircraft(cls, aircraft: Type[FlyingType], number: int) -> Pylon:
# In pydcs these are all arbitrary inner classes of the aircraft type.
# The only way to identify them is by their name.
pylons = [
v
for v in aircraft.__dict__.values()
if inspect.isclass(v) and v.__name__.startswith("Pylon")
]
# And that Pylon class has members with irrelevant names that have
# values of (pylon number, allowed weapon).
allowed = set()
for pylon in pylons:
for key, value in pylon.__dict__.items():
if key.startswith("__"):
continue
pylon_number, weapon = value
if pylon_number != number:
continue
allowed.add(Weapon.from_pydcs(weapon))
return cls(number, allowed)
@classmethod
def iter_pylons(cls, aircraft: Type[FlyingType]) -> Iterator[Pylon]:
for pylon in sorted(list(aircraft.pylons)):
yield cls.for_aircraft(aircraft, pylon)
_WEAPON_FALLBACKS = [
# ADM-141 TALD
(Weapons.ADM_141A, None),
(Weapons.ADM_141A_, None),
(Weapons.ADM_141A__, None),
(Weapons.ADM_141B, None),
# AGM-114K Hellfire
(Weapons.AGM114x2_OH_58, Weapons.M260_HYDRA), # assuming OH-58 and not MQ-9
(Weapons.AGM_114K, None), # Only for RQ-1
(Weapons.AGM_114K___4, Weapons.LAU_61___19_2_75__rockets_MK151_HE),
# AGM-119 Penguin
(Weapons.AGM_119B_Penguin, Weapons.Mk_82),
# AGM-122 Sidearm
(Weapons.AGM_122, None), # No known aircraft carries this
(Weapons.AGM_122_Sidearm, Weapons.GBU_12), # outer pylons harrier
(Weapons.AGM_122_Sidearm_, Weapons.LAU_117_AGM_65E), # internal pylons harrier
# AGM-154 JSOW
(Weapons.AGM_154A, Weapons.GBU_12),
(Weapons.BRU_55___2_x_AGM_154A, Weapons.BRU_33___2_x_GBU_12),
(Weapons.BRU_57___2_x_AGM_154A, None), # doesn't exist on any aircraft yet
(Weapons.AGM_154B, Weapons.CBU_105),
(Weapons.AGM_154C, Weapons.GBU_12),
(Weapons.AGM_154C_4, Weapons.GBU_31_8),
(Weapons.BRU_55___2_x_AGM_154C, Weapons.BRU_33___2_x_GBU_12),
# AGM-45 Shrike
(Weapons.AGM_45A, None),
(Weapons.AGM_45B, Weapons.AGM_45A),
(Weapons.AGM_45B_, Weapons.AGM_45A),
# AGM-62 Walleye
(Weapons.AGM_62, Weapons.Mk_84),
# AGM-65 Maverick
(Weapons.AGM_65D, None), # doesn't exist
(Weapons.AGM_65E, None), # doesn't exist
(Weapons.AGM_65K, None), # doesn't exist
(Weapons.LAU_117_AGM_65A, None), # doesn't exist
(Weapons.LAU_117_AGM_65B, None), # doesn't exist
(
Weapons.LAU_117_AGM_65D,
Weapons.AGM_62,
), # Walleye is the predecessor to the maverick
(Weapons.LAU_117_AGM_65E, None),
(Weapons.LAU_117_AGM_65F, Weapons.LAU_117_AGM_65D),
(Weapons.LAU_117_AGM_65G, Weapons.LAU_117_AGM_65D),
(Weapons.LAU_117_AGM_65H, Weapons.LAU_117_AGM_65D),
(Weapons.LAU_117_AGM_65K, Weapons.LAU_117_AGM_65D),
(Weapons.LAU_117_AGM_65L, None),
(Weapons.LAU_88_AGM_65D_2, None),
(Weapons.LAU_88_AGM_65D_2_, None),
(Weapons.LAU_88_AGM_65D_3, None),
(Weapons.LAU_88_AGM_65D_ONE, None),
(Weapons.LAU_88_AGM_65E_2, Weapons.LAU_88_AGM_65D_2),
(Weapons.LAU_88_AGM_65E_2_, Weapons.LAU_88_AGM_65D_2_),
(Weapons.LAU_88_AGM_65E_3, Weapons.LAU_88_AGM_65D_3),
(Weapons.LAU_88_AGM_65H, Weapons.LAU_88_AGM_65D_2),
(Weapons.LAU_88_AGM_65H_2_L, Weapons.LAU_88_AGM_65D_2_),
(Weapons.LAU_88_AGM_65H_2_R, Weapons.LAU_88_AGM_65D_2_),
(Weapons.LAU_88_AGM_65H_3, Weapons.LAU_88_AGM_65D_3),
(Weapons.LAU_88_AGM_65K_2, Weapons.LAU_88_AGM_65D_2),
(Weapons.LAU_88_AGM_65K_2_, Weapons.LAU_88_AGM_65D_2_),
(Weapons.LAU_88_AGM_65K_3, Weapons.LAU_88_AGM_65D_3),
# AGM-84 Harpoon
(Weapons.AGM_84, None), # doesn't exist
(Weapons.AGM_84A, Weapons.Mk_82),
(Weapons.AGM_84A_8, Weapons._27_Mk_82),
(Weapons.AGM_84D, Weapons.AGM_62),
(Weapons.AGM_84E, Weapons.LAU_117_AGM_65F),
(Weapons.AGM_84H, Weapons.AGM_84E),
# AGM-86 ALCM
(Weapons.AGM_86C, Weapons._27_Mk_82),
(Weapons.AGM_86C_20, Weapons._27_Mk_82),
(Weapons.AGM_86C_8, Weapons._27_Mk_82),
(Weapons.MER_6_AGM_86C, Weapons.MER_12_Mk_82),
# AGM-88 HARM
(Weapons.AGM_88C, Weapons.AGM_65D),
(Weapons.AGM_88C_, Weapons.AGM_65D),
# AIM-120 AMRAAM
(Weapons.AIM_120B, Weapons.AIM_7MH),
(Weapons.LAU_115___AIM_120B, Weapons.LAU_115C_AIM_7MH),
(Weapons.LAU_115_2_LAU_127_AIM_120B, Weapons.LAU_115C_AIM_7MH),
(Weapons.AIM_120C, Weapons.AIM_120B),
(Weapons.LAU_115___AIM_120C, Weapons.LAU_115___AIM_120B),
(Weapons.LAU_115_2_LAU_127_AIM_120C, Weapons.LAU_115_2_LAU_127_AIM_120B),
# AIM-54 Phoenix
(Weapons.AIM_54A_Mk47, None),
(Weapons.AIM_54A_Mk47_, None),
(Weapons.AIM_54A_Mk47__, None),
(Weapons.AIM_54A_Mk60, Weapons.AIM_54A_Mk47),
(Weapons.AIM_54A_Mk60_, Weapons.AIM_54A_Mk47_),
(Weapons.AIM_54A_Mk60__, Weapons.AIM_54A_Mk47__),
(Weapons.AIM_54C_Mk47, Weapons.AIM_54A_Mk60),
(Weapons.AIM_54C_Mk47_, Weapons.AIM_54A_Mk60_),
(Weapons.AIM_54C_Mk47__, Weapons.AIM_54A_Mk60__),
# AIM-7 Sparrow
(Weapons.AIM_7E, None),
(Weapons.AIM_7F, Weapons.AIM_7E),
(Weapons.AIM_7F_, None),
(Weapons.AIM_7F__, None),
(Weapons.AIM_7M, Weapons.AIM_7F),
(Weapons.AIM_7M_, Weapons.AIM_7F_),
(Weapons.AIM_7M__, Weapons.AIM_7F__),
(Weapons.AIM_7MH, Weapons.AIM_7M),
(Weapons.AIM_7MH_, Weapons.AIM_7M_),
(Weapons.AIM_7MH__, Weapons.AIM_7M__),
(Weapons.LAU_115C_AIM_7E, None),
(Weapons.LAU_115C_AIM_7F, Weapons.LAU_115C_AIM_7E),
(Weapons.LAU_115___AIM_7M, Weapons.LAU_115C_AIM_7F),
(Weapons.LAU_115C_AIM_7MH, Weapons.LAU_115___AIM_7M),
# AIM-9 Sidewinder
(Weapons.AIM_9L_Sidewinder_IR_AAM, None),
(Weapons.AIM_9M_Sidewinder_IR_AAM, Weapons.AIM_9P5_Sidewinder_IR_AAM),
(Weapons.AIM_9P5_Sidewinder_IR_AAM, Weapons.AIM_9P_Sidewinder_IR_AAM),
(Weapons.AIM_9P_Sidewinder_IR_AAM, Weapons.AIM_9L_Sidewinder_IR_AAM),
(Weapons.AIM_9X_Sidewinder_IR_AAM, Weapons.AIM_9P_Sidewinder_IR_AAM),
(Weapons.LAU_105_1_AIM_9L_L, None),
(Weapons.LAU_105_1_AIM_9L_R, None),
(Weapons.LAU_105_1_AIM_9M_L, Weapons.LAU_105_1_AIM_9L_L),
(Weapons.LAU_105_1_AIM_9M_R, Weapons.LAU_105_1_AIM_9L_R),
(Weapons.LAU_105_2_AIM_9L, None),
(Weapons.LAU_105_2_AIM_9P5, Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM),
(Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM, Weapons.LAU_105_2_AIM_9L),
(
Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM,
Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM,
),
(Weapons.LAU_115_2_LAU_127_AIM_9L, None),
(Weapons.LAU_115_2_LAU_127_AIM_9M, Weapons.LAU_115_2_LAU_127_AIM_9L),
(Weapons.LAU_115_2_LAU_127_AIM_9X, Weapons.LAU_115_2_LAU_127_AIM_9M),
(Weapons.LAU_115_LAU_127_AIM_9L, None),
(Weapons.LAU_115_LAU_127_AIM_9M, Weapons.LAU_115_LAU_127_AIM_9L),
(Weapons.LAU_115_LAU_127_AIM_9X, Weapons.LAU_115_LAU_127_AIM_9M),
(Weapons.LAU_127_AIM_9L, None),
(Weapons.LAU_127_AIM_9M, Weapons.LAU_127_AIM_9L),
(Weapons.LAU_127_AIM_9X, Weapons.LAU_127_AIM_9M),
(Weapons.LAU_138_AIM_9L, None),
(Weapons.LAU_138_AIM_9M, Weapons.LAU_138_AIM_9L),
(Weapons.LAU_7_AIM_9L, None),
(Weapons.LAU_7_AIM_9M, Weapons.LAU_7_AIM_9L),
(Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM),
(Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM),
(Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9L),
(Weapons.LAU_7_AIM_9X_Sidewinder_IR_AAM, Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM),
(Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM, None),
(
Weapons.LAU_7___2_AIM_9M_Sidewinder_IR_AAM,
Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM,
),
(
Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM,
Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM,
),
(
Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM,
Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM,
),
# ALQ ECM Pods
(Weapons.ALQ_131, None),
(Weapons.ALQ_184, Weapons.ALQ_131),
(Weapons.AN_ALQ_164_DECM_Pod, None),
# TGP Pods
(Weapons.AN_AAQ_28_LITENING_, None),
(Weapons.AN_AAQ_28_LITENING, Weapons.Lantirn_F_16),
(Weapons.AN_AAS_38_FLIR, None),
(Weapons.AN_ASQ_173_LST_SCAM, None),
(Weapons.AWW_13_DATALINK_POD, None),
(Weapons.LANTIRN_Targeting_Pod, None),
(Weapons.Lantirn_F_16, None),
(Weapons.Lantirn_Target_Pod, None),
(Weapons.Pavetack_F_111, None),
# BLU-107
(Weapons.BLU_107, None),
(Weapons.MER_6_BLU_107, Weapons.MER_6_Mk_82),
# GBU-10 LGB
(Weapons.DIS_GBU_10, Weapons.Mk_84),
(Weapons.GBU_10, Weapons.Mk_84),
(Weapons.GBU_10_, Weapons.Mk_84),
(Weapons.GBU_10_2, Weapons.Mk_84),
# GBU-12 LGB
(Weapons.AUF2_GBU_12_x_2, None),
(Weapons.BRU_33___2_x_GBU_12, Weapons.BRU_33___2_x_Mk_82_),
(Weapons.BRU_42_3_GBU_12, Weapons._3_Mk_82),
(Weapons.DIS_GBU_12, Weapons.Mk_82),
(Weapons.DIS_GBU_12_DUAL, Weapons.BRU_33___2_x_Mk_82_),
(Weapons.DIS_GBU_12_DUAL_L, Weapons.BRU_33___2_x_Mk_82_),
(Weapons.DIS_GBU_12_DUAL_R, Weapons.BRU_33___2_x_Mk_82_),
(Weapons.GBU_12, Weapons.Mk_82),
(Weapons.GBU_12_, Weapons.Mk_82_),
(Weapons.TER_9A___2_x_GBU_12, Weapons.TER_9A___2_x_Mk_82),
(Weapons.TER_9A___2_x_GBU_12_, Weapons.TER_9A___2_x_Mk_82_),
(Weapons._2xGBU_12, Weapons.Mk_82),
(Weapons._2xGBU_12_, Weapons.Mk_82_),
(Weapons._2_GBU_12, Weapons._2_Mk_82),
(Weapons._2_GBU_12_, Weapons._2_Mk_82_),
(Weapons._3_GBU_12, Weapons._3_Mk_82_),
# GBU-15 LGB
(Weapons.GBU_15, Weapons.Mk_84),
# GBU-16 LGB
(Weapons.BRU_33___2_x_GBU_16, None),
(Weapons.DIS_GBU_16, Weapons.Mk_83),
(Weapons.GBU_16, Weapons.Mk_83),
(Weapons.GBU_16_, Weapons.Mk_83_),
# GBU-24 LGB
(Weapons.GBU_24, Weapons.GBU_10),
(Weapons.GBU_24_, Weapons.GBU_10_),
(Weapons.GBU_24__, Weapons.GBU_10_),
# GBU-27 LGB
(Weapons.GBU_24, Weapons.GBU_10),
(Weapons.GBU_24_, Weapons.GBU_10_),
(Weapons.GBU_24__, Weapons.GBU_10_),
# GBU-28 LGB
(Weapons.GBU_28, Weapons.GBU_15),
# GBU-31 JDAM
(Weapons.GBU_31V3B_8, Weapons.B_1B_Mk_84_8),
(Weapons.GBU_31_8, Weapons.B_1B_Mk_84_8),
(Weapons.GBU_31_V_1_B, Weapons.GBU_24_),
(Weapons.GBU_31_V_2_B, Weapons.GBU_24_),
(Weapons.GBU_31_V_3_B, Weapons.GBU_24_),
(Weapons.GBU_31_V_4_B, Weapons.GBU_24_),
# GBU-32 JDAM
(Weapons.GBU_32_V_2_B, Weapons.GBU_16),
# GBU-32 JDAM
(Weapons.BRU_55___2_x_GBU_38, Weapons.BRU_33___2_x_Mk_82_),
(Weapons.BRU_57___2_x_GBU_38, None), # Doesn't exist
(Weapons.GBU_38, Weapons.Mk_82),
(Weapons.GBU_38_16, Weapons.MK_82_28),
(Weapons._2_GBU_38, Weapons._2_Mk_82),
(Weapons._2_GBU_38_, Weapons._2_Mk_82_),
(Weapons._3_GBU_38, Weapons._3_Mk_82_),
# GBU-54 LJDAM
(Weapons.GBU_54_V_1_B, Weapons.GBU_38),
(Weapons._2_GBU_54_V_1_B, Weapons._2_GBU_38),
(Weapons._2_GBU_54_V_1_B_, Weapons._2_GBU_38_),
(Weapons._3_GBU_54_V_1_B, Weapons._3_GBU_38),
# CBU-52
(Weapons.CBU_52B, None),
# CBU-87 CEM
(Weapons.CBU_87, Weapons.Mk_82),
(Weapons.TER_9A___2_x_CBU_87, Weapons.TER_9A___2_x_Mk_82),
(Weapons.TER_9A___2_x_CBU_87_, Weapons.TER_9A___2_x_Mk_82_),
(Weapons.TER_9A___3_x_CBU_87, Weapons.TER_9A___3_x_Mk_82),
# CBU-97
(Weapons.CBU_97, Weapons.Mk_82),
(Weapons.TER_9A___2_x_CBU_97, Weapons.TER_9A___2_x_Mk_82),
(Weapons.TER_9A___2_x_CBU_97_, Weapons.TER_9A___2_x_Mk_82_),
(Weapons.TER_9A___3_x_CBU_97, Weapons.TER_9A___3_x_Mk_82),
# CBU-99 (It's a bomb made in 1968, I'm not bothering right now with backups)
# CBU-103
(Weapons.BRU_57___2_x_CBU_103, None), # doesn't exist...
(Weapons.CBU_103, Weapons.CBU_87),
# CBU-105
(Weapons.BRU_57___2_x_CBU_105, None), # doesn't exist...
(Weapons.CBU_105, Weapons.CBU_97),
(
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M151___HE_APKWS,
Weapons.LAU_131___7_2_75__rockets_M151__HE_,
),
(
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M282___MPP_APKWS,
Weapons.LAU_131___7_2_75__rockets_M151__HE_,
),
(
Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M151___HE_APKWS,
Weapons.LAU_68_3___7_2_75__rockets_M151__HE_,
),
(
Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M282___MPP_APKWS,
Weapons.LAU_68_3___7_2_75__rockets_M151__HE_,
),
# Russia
# KAB-1500
(Weapons.KAB_1500Kr, None),
(Weapons.KAB_1500LG_Pr, Weapons.KAB_1500Kr),
(Weapons.KAB_1500L, Weapons.KAB_1500LG_Pr),
# KAB-500
(Weapons.KAB_500kr, Weapons.FAB_500_M62),
(Weapons.KAB_500L, Weapons.KAB_500kr),
(Weapons.KAB_500S, Weapons.KAB_500L),
# KH Series
(Weapons.Kh_22N, None),
(Weapons.Kh_23L, None),
(Weapons.Kh_25ML, None),
(Weapons.Kh_25ML_, None),
(Weapons.Kh_25ML__, None),
(Weapons.Kh_25MP, None),
(Weapons.Kh_25MPU, Weapons.Kh_25MP),
(Weapons.Kh_25MR, None),
(Weapons.Kh_25MR_, None),
(Weapons.Kh_28__AS_9_Kyle_, None),
(Weapons.Kh_29L, Weapons.Kh_25ML),
(Weapons.Kh_29L_, Weapons.Kh_25ML_),
(Weapons.Kh_29L__, Weapons.Kh_25ML__),
(Weapons.Kh_29T, Weapons.Kh_25MR),
(Weapons.Kh_29T_, Weapons.Kh_25MR_),
(Weapons.Kh_29T_, Weapons.Kh_25MR_),
(Weapons.Kh_31A, None),
(Weapons.Kh_31A_, None),
(Weapons.Kh_31A__, None),
(Weapons.Kh_31P, Weapons.Kh_25MP),
(Weapons.Kh_31P_, Weapons.Kh_25MP),
(Weapons.Kh_31P__, Weapons.Kh_25MP),
(Weapons.Kh_35, Weapons.Kh_31A),
(Weapons.Kh_35_, Weapons.Kh_31A_),
(Weapons.Kh_35_6, None),
(Weapons.Kh_41, None),
(Weapons.Kh_58U, Weapons.Kh_31P),
(Weapons.Kh_58U_, Weapons.Kh_31P_),
(Weapons.Kh_59M, Weapons.Kh_31A),
(Weapons.Kh_65, None),
(Weapons.Kh_65_6, None),
(Weapons.Kh_65_8, None),
(Weapons.Kh_66_Grom__21__APU_68, None),
# ECM
(Weapons.L175V_Khibiny_ECM_pod, None),
# R-13
(Weapons.R_13M, None),
(Weapons.R_13M1, Weapons.R_13M),
# R-24
(Weapons.R_24R, None),
(Weapons.R_24T, None),
# R-27
(Weapons.R_27T, Weapons.R_24T),
(Weapons.R_27R, Weapons.R_24R),
(Weapons.R_27ER, Weapons.R_27R),
(Weapons.R_27ET, Weapons.R_27T),
# R-33
(Weapons.R_33, None),
# R-3
(Weapons.R_3S, Weapons.R_13M),
(Weapons.R_3R, Weapons.R_3S),
# R-40
(Weapons.R_40R, None),
(Weapons.R_40T, None),
# R-55
(Weapons.R_55, None),
(Weapons.RS2US, None),
# R-60
(Weapons.R_60, Weapons.R_13M1),
(Weapons.R_60_x_2, Weapons.R_13M1),
(Weapons.R_60_x_2_, Weapons.R_13M1),
(Weapons.APU_60_1_R_60M, Weapons.R_3S),
(Weapons.R_60M, Weapons.R_60),
(Weapons.R_60M_, Weapons.R_60),
(Weapons.R_60M_2, Weapons.R_60M),
(Weapons.R_60M_2_, Weapons.R_60M),
(Weapons.R_60M_x_2, Weapons.R_60M),
(Weapons.R_60M_x_2_, Weapons.R_60M),
# R-73
(Weapons.R_73, Weapons.R_60M),
(Weapons.R_73_, None),
# R-77
(Weapons.R_77, Weapons.R_27ER),
(Weapons.R_77_, None),
# UK
# ALARM
(Weapons.ALARM, None),
(Weapons.ALARM_2, None),
# France
# BLG-66 Belouga
(Weapons.AUF2_BLG_66_AC_x_2, Weapons.AUF2_MK_82_x_2),
(Weapons.BLG_66_AC_Belouga, Weapons.Mk_82),
(Weapons.BLG_66_AC_Belouga_, Weapons.Mk_82),
# HOT-3
(Weapons.HOT3, None),
(Weapons.HOT3_, None),
# Magic 2
(Weapons.Matra_Magic_II, None),
(Weapons.R_550_Magic_2, None),
# Super 530D
(Weapons.Matra_Super_530D, Weapons.Matra_Magic_II),
(Weapons.Super_530D, None),
]
WEAPON_FALLBACK_MAP: Dict[Weapon, Optional[Weapon]] = defaultdict(
lambda: cast(Optional[Weapon], None),
(
(Weapon.from_pydcs(a), b if b is None else Weapon.from_pydcs(b))
for a, b in _WEAPON_FALLBACKS
),
)
WEAPON_INTRODUCTION_YEARS = {
# USA
# ADM-141 TALD
Weapon.from_pydcs(Weapons.ADM_141A): 1987,
Weapon.from_pydcs(Weapons.ADM_141A_): 1987,
Weapon.from_pydcs(Weapons.ADM_141A__): 1987,
Weapon.from_pydcs(Weapons.ADM_141B): 1987,
# AGM-114K Hellfire
Weapon.from_pydcs(Weapons.AGM114x2_OH_58): 1993,
Weapon.from_pydcs(Weapons.AGM_114K): 1993,
Weapon.from_pydcs(Weapons.AGM_114K___4): 1993,
# AGM-119 Penguin
Weapon.from_pydcs(Weapons.AGM_119B_Penguin): 1972,
# AGM-122 Sidearm
Weapon.from_pydcs(Weapons.AGM_122): 1986,
Weapon.from_pydcs(Weapons.AGM_122_Sidearm): 1986,
Weapon.from_pydcs(Weapons.AGM_122_Sidearm_): 1986,
# AGM-154 JSOW
Weapon.from_pydcs(Weapons.AGM_154A): 1998,
Weapon.from_pydcs(Weapons.BRU_55___2_x_AGM_154A): 1998,
Weapon.from_pydcs(Weapons.BRU_57___2_x_AGM_154A): 1998,
Weapon.from_pydcs(Weapons.AGM_154B): 2005,
Weapon.from_pydcs(Weapons.AGM_154C): 2005,
Weapon.from_pydcs(Weapons.AGM_154C_4): 2005,
Weapon.from_pydcs(Weapons.BRU_55___2_x_AGM_154C): 2005,
# AGM-45 Shrike
Weapon.from_pydcs(Weapons.AGM_45A): 1965,
Weapon.from_pydcs(Weapons.AGM_45B): 1970,
Weapon.from_pydcs(Weapons.AGM_45B_): 1970,
# AGM-62 Walleye
Weapon.from_pydcs(Weapons.AGM_62): 1972,
# AGM-65 Maverick
Weapon.from_pydcs(Weapons.AGM_65D): 1983,
Weapon.from_pydcs(Weapons.AGM_65E): 1985,
Weapon.from_pydcs(Weapons.AGM_65K): 2007,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65A): 1972,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65B): 1972,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65D): 1986,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65E): 1990,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65F): 1991,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65G): 1989,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65H): 2002,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65K): 2002,
Weapon.from_pydcs(Weapons.LAU_117_AGM_65L): 1985,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_2): 1983,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_2_): 1983,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_3): 1983,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65D_ONE): 1983,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_2): 1985,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_2_): 1985,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65E_3): 1985,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H): 2007,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_2_L): 2007,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_2_R): 2007,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65H_3): 2007,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_2): 2007,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_2_): 2007,
Weapon.from_pydcs(Weapons.LAU_88_AGM_65K_3): 2007,
# AGM-84 Harpoon
Weapon.from_pydcs(Weapons.AGM_84): 1979,
Weapon.from_pydcs(Weapons.AGM_84A): 1979,
Weapon.from_pydcs(Weapons.AGM_84A_8): 1979,
Weapon.from_pydcs(Weapons.AGM_84D): 1979,
Weapon.from_pydcs(Weapons.AGM_84E): 1990,
Weapon.from_pydcs(Weapons.AGM_84E_SLAM): 1990,
Weapon.from_pydcs(Weapons.AGM_84H): 1998,
# AGM-86 ALCM
Weapon.from_pydcs(Weapons.AGM_86C): 1986,
Weapon.from_pydcs(Weapons.AGM_86C_20): 1986,
Weapon.from_pydcs(Weapons.AGM_86C_8): 1986,
Weapon.from_pydcs(Weapons.MER_6_AGM_86C): 1986,
# AGM-88 HARM
Weapon.from_pydcs(Weapons.AGM_88C): 1983,
Weapon.from_pydcs(Weapons.AGM_88C_): 1983,
# for future reference: 1983 is the A model IOC. B model in 1986 and C model in 1994.
# AIM-120 AMRAAM
Weapon.from_pydcs(Weapons.AIM_120B): 1994,
Weapon.from_pydcs(Weapons.AIM_120C): 1996,
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_120B): 1994,
Weapon.from_pydcs(Weapons.LAU_115___AIM_120B): 1994,
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_120C): 1996,
Weapon.from_pydcs(Weapons.LAU_115___AIM_120C): 1996,
# AIM-54 Phoenix
Weapon.from_pydcs(Weapons.AIM_54A_Mk47): 1974,
Weapon.from_pydcs(Weapons.AIM_54A_Mk47_): 1974,
Weapon.from_pydcs(Weapons.AIM_54A_Mk47__): 1974,
Weapon.from_pydcs(Weapons.AIM_54A_Mk60): 1974,
Weapon.from_pydcs(Weapons.AIM_54A_Mk60_): 1974,
Weapon.from_pydcs(Weapons.AIM_54A_Mk60__): 1974,
Weapon.from_pydcs(Weapons.AIM_54C): 1974,
Weapon.from_pydcs(Weapons.AIM_54C_Mk47): 1974,
Weapon.from_pydcs(Weapons.AIM_54C_Mk47_): 1974,
Weapon.from_pydcs(Weapons.AIM_54C_Mk47__): 1974,
# AIM-7 Sparrow
Weapon.from_pydcs(Weapons.AIM_7E): 1963,
Weapon.from_pydcs(Weapons.AIM_7F): 1976,
Weapon.from_pydcs(Weapons.AIM_7F_): 1976,
Weapon.from_pydcs(Weapons.AIM_7F__): 1976,
Weapon.from_pydcs(Weapons.AIM_7M): 1982,
Weapon.from_pydcs(Weapons.AIM_7M_): 1982,
Weapon.from_pydcs(Weapons.AIM_7M__): 1982,
Weapon.from_pydcs(Weapons.AIM_7MH): 1987,
Weapon.from_pydcs(Weapons.AIM_7MH_): 1987,
Weapon.from_pydcs(Weapons.AIM_7MH__): 1987,
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7E): 1963,
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7F): 1976,
Weapon.from_pydcs(Weapons.LAU_115___AIM_7M): 1982,
Weapon.from_pydcs(Weapons.LAU_115C_AIM_7MH): 1987,
# AIM-9 Sidewinder
Weapon.from_pydcs(Weapons.AIM_9L_Sidewinder_IR_AAM): 1977,
Weapon.from_pydcs(Weapons.AIM_9M_Sidewinder_IR_AAM): 1982,
Weapon.from_pydcs(Weapons.AIM_9P5_Sidewinder_IR_AAM): 1980,
Weapon.from_pydcs(Weapons.AIM_9P_Sidewinder_IR_AAM): 1978,
Weapon.from_pydcs(Weapons.AIM_9X_Sidewinder_IR_AAM): 2003,
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9L_L): 1977,
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9L_R): 1977,
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9M_L): 1982,
Weapon.from_pydcs(Weapons.LAU_105_1_AIM_9M_R): 1982,
Weapon.from_pydcs(Weapons.LAU_105_2_AIM_9L): 1977,
Weapon.from_pydcs(Weapons.LAU_105_2_AIM_9P5): 1980,
Weapon.from_pydcs(Weapons.LAU_105___2_AIM_9M_Sidewinder_IR_AAM): 1982,
Weapon.from_pydcs(Weapons.LAU_105___2_AIM_9P_Sidewinder_IR_AAM): 1978,
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9L): 1977,
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9M): 1982,
Weapon.from_pydcs(Weapons.LAU_115_2_LAU_127_AIM_9X): 2003,
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9L): 1977,
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9M): 1982,
Weapon.from_pydcs(Weapons.LAU_115_LAU_127_AIM_9X): 2003,
Weapon.from_pydcs(Weapons.LAU_127_AIM_9L): 1977,
Weapon.from_pydcs(Weapons.LAU_127_AIM_9M): 1982,
Weapon.from_pydcs(Weapons.LAU_127_AIM_9X): 2003,
Weapon.from_pydcs(Weapons.LAU_138_AIM_9L): 1977,
Weapon.from_pydcs(Weapons.LAU_138_AIM_9M): 1982,
Weapon.from_pydcs(Weapons.LAU_7_AIM_9L): 1977,
Weapon.from_pydcs(Weapons.LAU_7_AIM_9M): 1982,
Weapon.from_pydcs(Weapons.LAU_7_AIM_9M_Sidewinder_IR_AAM): 1982,
Weapon.from_pydcs(Weapons.LAU_7_AIM_9P5_Sidewinder_IR_AAM): 1980,
Weapon.from_pydcs(Weapons.LAU_7_AIM_9P_Sidewinder_IR_AAM): 1978,
Weapon.from_pydcs(Weapons.LAU_7_AIM_9X_Sidewinder_IR_AAM): 2003,
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9L_Sidewinder_IR_AAM): 1977,
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9M_Sidewinder_IR_AAM): 1982,
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9P5_Sidewinder_IR_AAM): 1980,
Weapon.from_pydcs(Weapons.LAU_7___2_AIM_9P_Sidewinder_IR_AAM): 1978,
# ALQ ECM Pods
Weapon.from_pydcs(Weapons.ALQ_131): 1970,
Weapon.from_pydcs(Weapons.ALQ_184): 1989,
Weapon.from_pydcs(Weapons.AN_ALQ_164_DECM_Pod): 1984,
# TGP Pods
Weapon.from_pydcs(Weapons.AN_AAQ_28_LITENING): 1995,
Weapon.from_pydcs(Weapons.AN_AAQ_28_LITENING_): 1995,
Weapon.from_pydcs(Weapons.AN_AAS_38_FLIR): 1993,
Weapon.from_pydcs(Weapons.AN_ASQ_173_LST_SCAM): 1993,
Weapon.from_pydcs(Weapons.AWW_13_DATALINK_POD): 1967,
Weapon.from_pydcs(Weapons.LANTIRN_Targeting_Pod): 1985,
Weapon.from_pydcs(Weapons.Lantirn_F_16): 1985,
Weapon.from_pydcs(Weapons.Lantirn_Target_Pod): 1985,
Weapon.from_pydcs(Weapons.Pavetack_F_111): 1982,
# BLU-107
Weapon.from_pydcs(Weapons.BLU_107): 1983,
Weapon.from_pydcs(Weapons.MER_6_BLU_107): 1983,
# GBU-10 LGB
Weapon.from_pydcs(Weapons.DIS_GBU_10): 1976,
Weapon.from_pydcs(Weapons.GBU_10): 1976,
Weapon.from_pydcs(Weapons.GBU_10_): 1976,
Weapon.from_pydcs(Weapons.GBU_10_2): 1976,
# GBU-12 LGB
Weapon.from_pydcs(Weapons.AUF2_GBU_12_x_2): 1976,
Weapon.from_pydcs(Weapons.BRU_33___2_x_GBU_12): 1976,
Weapon.from_pydcs(Weapons.BRU_42_3_GBU_12): 1976,
Weapon.from_pydcs(Weapons.DIS_GBU_12): 1976,
Weapon.from_pydcs(Weapons.DIS_GBU_12_DUAL): 1976,
Weapon.from_pydcs(Weapons.DIS_GBU_12_DUAL_L): 1976,
Weapon.from_pydcs(Weapons.DIS_GBU_12_DUAL_R): 1976,
Weapon.from_pydcs(Weapons.GBU_12): 1976,
Weapon.from_pydcs(Weapons.GBU_12_): 1976,
Weapon.from_pydcs(Weapons.TER_9A___2_x_GBU_12): 1976,
Weapon.from_pydcs(Weapons.TER_9A___2_x_GBU_12_): 1976,
Weapon.from_pydcs(Weapons._2xGBU_12): 1976,
Weapon.from_pydcs(Weapons._2xGBU_12_): 1976,
Weapon.from_pydcs(Weapons._2_GBU_12): 1976,
Weapon.from_pydcs(Weapons._2_GBU_12_): 1976,
Weapon.from_pydcs(Weapons._3_GBU_12): 1976,
# GBU-15 LGB
Weapon.from_pydcs(Weapons.GBU_15): 1975,
# GBU-16 LGB
Weapon.from_pydcs(Weapons.BRU_33___2_x_GBU_16): 1976,
Weapon.from_pydcs(Weapons.DIS_GBU_16): 1976,
Weapon.from_pydcs(Weapons.GBU_16): 1976,
Weapon.from_pydcs(Weapons.GBU_16_): 1976,
Weapon.from_pydcs(Weapons._2_GBU_16): 1976,
Weapon.from_pydcs(Weapons._2_GBU_16_): 1976,
Weapon.from_pydcs(Weapons._3_GBU_16): 1976,
Weapon.from_pydcs(Weapons._3_GBU_16_): 1976,
# GBU-24 LGB
Weapon.from_pydcs(Weapons.GBU_24): 1986,
Weapon.from_pydcs(Weapons.GBU_24_): 1986,
Weapon.from_pydcs(Weapons.GBU_24__): 1986,
# GBU-27 LGB
Weapon.from_pydcs(Weapons.GBU_27): 1991,
Weapon.from_pydcs(Weapons.GBU_27_2): 1991,
Weapon.from_pydcs(Weapons.GBU_27_4): 1991,
# GBU-28
Weapon.from_pydcs(Weapons.GBU_28): 1991,
# GBU-31 JDAM
Weapon.from_pydcs(Weapons.GBU_31V3B_8): 2001,
Weapon.from_pydcs(Weapons.GBU_31_8): 2001,
Weapon.from_pydcs(Weapons.GBU_31_V_1_B): 2001,
Weapon.from_pydcs(Weapons.GBU_31_V_2_B): 2001,
Weapon.from_pydcs(Weapons.GBU_31_V_3_B): 2001,
Weapon.from_pydcs(Weapons.GBU_31_V_4_B): 2001,
# GBU-32 JDAM
Weapon.from_pydcs(Weapons.GBU_32_V_2_B): 2002,
# GBU-38 JDAM
Weapon.from_pydcs(Weapons.BRU_55___2_x_GBU_38): 2005,
Weapon.from_pydcs(Weapons.BRU_57___2_x_GBU_38): 2005,
Weapon.from_pydcs(Weapons.GBU_38): 2005,
Weapon.from_pydcs(Weapons.GBU_38_16): 2005,
Weapon.from_pydcs(Weapons._2_GBU_38): 2005,
Weapon.from_pydcs(Weapons._2_GBU_38_): 2005,
Weapon.from_pydcs(Weapons._3_GBU_38): 2005,
# GBU-54 LJDAM
Weapon.from_pydcs(Weapons.GBU_54_V_1_B): 2008,
Weapon.from_pydcs(Weapons._2_GBU_54_V_1_B): 2008,
Weapon.from_pydcs(Weapons._2_GBU_54_V_1_B_): 2008,
Weapon.from_pydcs(Weapons._3_GBU_54_V_1_B): 2008,
# CBU-52
Weapon.from_pydcs(Weapons.CBU_52B): 1970,
# CBU-87 CEM
Weapon.from_pydcs(Weapons.CBU_87): 1986,
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_87): 1986,
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_87_): 1986,
Weapon.from_pydcs(Weapons.TER_9A___3_x_CBU_87): 1986,
# CBU-97
Weapon.from_pydcs(Weapons.CBU_97): 1992,
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_97): 1992,
Weapon.from_pydcs(Weapons.TER_9A___2_x_CBU_97_): 1992,
Weapon.from_pydcs(Weapons.TER_9A___3_x_CBU_97): 1992,
# CBU-99
Weapon.from_pydcs(Weapons.BRU_33___2_x_CBU_99): 1968,
Weapon.from_pydcs(Weapons.CBU_99): 1968,
Weapon.from_pydcs(Weapons.BRU_33___2_x_Mk_20_Rockeye): 1968,
Weapon.from_pydcs(Weapons.DIS_MK_20): 1968,
Weapon.from_pydcs(Weapons.DIS_MK_20_DUAL_L): 1968,
Weapon.from_pydcs(Weapons.DIS_MK_20_DUAL_R): 1968,
Weapon.from_pydcs(Weapons.HSAB_9_Mk_20_Rockeye): 1968,
Weapon.from_pydcs(Weapons.MAK79_2_MK_20): 1968,
Weapon.from_pydcs(Weapons.MAK79_2_MK_20_): 1968,
Weapon.from_pydcs(Weapons.MAK79_MK_20): 1968,
Weapon.from_pydcs(Weapons.MAK79_MK_20_): 1968,
Weapon.from_pydcs(Weapons.MER_6_Mk_20_Rockeye): 1968,
Weapon.from_pydcs(Weapons.Mk_20): 1968,
Weapon.from_pydcs(Weapons.Mk_20_): 1968,
Weapon.from_pydcs(Weapons.Mk_20_18): 1968,
Weapon.from_pydcs(Weapons.Mk_20_Rockeye__6): 1968,
Weapon.from_pydcs(Weapons._2_MK_20): 1968,
Weapon.from_pydcs(Weapons._2_MK_20_): 1968,
Weapon.from_pydcs(Weapons._2_MK_20__): 1968,
Weapon.from_pydcs(Weapons._2_MK_20___): 1968,
Weapon.from_pydcs(Weapons._2_MK_20____): 1968,
Weapon.from_pydcs(Weapons._2_MK_20_____): 1968,
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye): 1968,
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye_): 1968,
Weapon.from_pydcs(Weapons._2_Mk_20_Rockeye__): 1968,
Weapon.from_pydcs(Weapons._3_Mk_20_Rockeye): 1968,
Weapon.from_pydcs(Weapons._3_Mk_20_Rockeye_): 1968,
# CBU-103
Weapon.from_pydcs(Weapons.BRU_57___2_x_CBU_103): 2000,
Weapon.from_pydcs(Weapons.CBU_103): 2000,
# CBU-105
Weapon.from_pydcs(Weapons.BRU_57___2_x_CBU_105): 2000,
Weapon.from_pydcs(Weapons.CBU_105): 2000,
# APKWS
Weapon.from_pydcs(
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M151___HE_APKWS
): 2016,
Weapon.from_pydcs(
Weapons.LAU_131_pod___7_x_2_75__Hydra___Laser_Guided_Rkts_M282___MPP_APKWS
): 2016,
Weapon.from_pydcs(
Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M151___HE_APKWS
): 2016,
Weapon.from_pydcs(
Weapons._3_x_LAU_131_pods___21_x_2_75__Hydra___Laser_Guided_M282___MPP_APKWS
): 2016,
# Russia
# KAB-1500
Weapon.from_pydcs(Weapons.KAB_1500Kr): 1985,
Weapon.from_pydcs(Weapons.KAB_1500L): 1995,
Weapon.from_pydcs(Weapons.KAB_1500LG_Pr): 1990,
# KAB-500
Weapon.from_pydcs(Weapons.KAB_500kr): 1980,
Weapon.from_pydcs(Weapons.KAB_500L): 1995,
Weapon.from_pydcs(Weapons.KAB_500S): 2000,
# Kh Series
Weapon.from_pydcs(Weapons.Kh_22N): 1962,
Weapon.from_pydcs(Weapons.Kh_23L): 1975,
Weapon.from_pydcs(Weapons.Kh_25ML): 1975,
Weapon.from_pydcs(Weapons.Kh_25ML_): 1975,
Weapon.from_pydcs(Weapons.Kh_25ML__): 1975,
Weapon.from_pydcs(Weapons.Kh_25MP): 1975,
Weapon.from_pydcs(Weapons.Kh_25MPU): 1980,
Weapon.from_pydcs(Weapons.Kh_25MPU_): 1980,
Weapon.from_pydcs(Weapons.Kh_25MPU__): 1980,
Weapon.from_pydcs(Weapons.Kh_25MR): 1975,
Weapon.from_pydcs(Weapons.Kh_25MR_): 1975,
Weapon.from_pydcs(Weapons.Kh_28__AS_9_Kyle_): 1973,
Weapon.from_pydcs(Weapons.Kh_29L): 1980,
Weapon.from_pydcs(Weapons.Kh_29L_): 1980,
Weapon.from_pydcs(Weapons.Kh_29L__): 1980,
Weapon.from_pydcs(Weapons.Kh_29T): 1980,
Weapon.from_pydcs(Weapons.Kh_29T_): 1980,
Weapon.from_pydcs(Weapons.Kh_29T__): 1980,
Weapon.from_pydcs(Weapons.Kh_31A): 1980,
Weapon.from_pydcs(Weapons.Kh_31A_): 1980,
Weapon.from_pydcs(Weapons.Kh_31A__): 1980,
Weapon.from_pydcs(Weapons.Kh_31P): 1980,
Weapon.from_pydcs(Weapons.Kh_31P_): 1980,
Weapon.from_pydcs(Weapons.Kh_31P__): 1980,
Weapon.from_pydcs(Weapons.Kh_35): 2003,
Weapon.from_pydcs(Weapons.Kh_35_): 2003,
Weapon.from_pydcs(Weapons.Kh_35_6): 2003,
Weapon.from_pydcs(Weapons.Kh_41): 1984,
Weapon.from_pydcs(Weapons.Kh_58U): 1985,
Weapon.from_pydcs(Weapons.Kh_58U_): 1985,
Weapon.from_pydcs(Weapons.Kh_59M): 1990,
Weapon.from_pydcs(Weapons.Kh_65): 1992,
Weapon.from_pydcs(Weapons.Kh_65_6): 1992,
Weapon.from_pydcs(Weapons.Kh_65_8): 1992,
Weapon.from_pydcs(Weapons.Kh_66_Grom__21__APU_68): 1968,
# ECM
Weapon.from_pydcs(Weapons.L175V_Khibiny_ECM_pod): 1982,
# R-13
Weapon.from_pydcs(Weapons.R_13M): 1961,
Weapon.from_pydcs(Weapons.R_13M1): 1965,
# R-24
Weapon.from_pydcs(Weapons.R_24R): 1981,
Weapon.from_pydcs(Weapons.R_24T): 1981,
# R-27
Weapon.from_pydcs(Weapons.R_27ER): 1983,
Weapon.from_pydcs(Weapons.R_27ET): 1986,
Weapon.from_pydcs(Weapons.R_27R): 1983,
Weapon.from_pydcs(Weapons.R_27T): 1983,
# R-33
Weapon.from_pydcs(Weapons.R_33): 1981,
# R-3
Weapon.from_pydcs(Weapons.R_3R): 1966,
Weapon.from_pydcs(Weapons.R_3S): 1962,
# R-40
Weapon.from_pydcs(Weapons.R_40R): 1976,
Weapon.from_pydcs(Weapons.R_40T): 1976,
# R-55
Weapon.from_pydcs(Weapons.R_55): 1957,
Weapon.from_pydcs(Weapons.RS2US): 1957,
# R-60
Weapon.from_pydcs(Weapons.R_60): 1973,
Weapon.from_pydcs(Weapons.R_60_x_2): 1973,
Weapon.from_pydcs(Weapons.R_60_x_2_): 1973,
Weapon.from_pydcs(Weapons.APU_60_1_R_60M): 1982,
Weapon.from_pydcs(Weapons.R_60M): 1982,
Weapon.from_pydcs(Weapons.R_60M_): 1982,
Weapon.from_pydcs(Weapons.R_60M_2): 1982,
Weapon.from_pydcs(Weapons.R_60M_2_): 1982,
Weapon.from_pydcs(Weapons.R_60M_x_2): 1982,
Weapon.from_pydcs(Weapons.R_60M_x_2_): 1982,
# R-73
Weapon.from_pydcs(Weapons.R_73): 1984,
Weapon.from_pydcs(Weapons.R_73_): 1984,
# R-77
Weapon.from_pydcs(Weapons.R_77): 2002,
Weapon.from_pydcs(Weapons.R_77_): 2002,
# UK
# ALARM
Weapon.from_pydcs(Weapons.ALARM): 1990,
Weapon.from_pydcs(Weapons.ALARM_2): 1990,
# France
# BLG-66 Belouga
Weapon.from_pydcs(Weapons.AUF2_BLG_66_AC_x_2): 1979,
Weapon.from_pydcs(Weapons.BLG_66_AC_Belouga): 1979,
Weapon.from_pydcs(Weapons.BLG_66_AC_Belouga_): 1979,
# HOT-3
Weapon.from_pydcs(Weapons.HOT3): 1998,
Weapon.from_pydcs(Weapons.HOT3_): 1998,
# Magic 2
Weapon.from_pydcs(Weapons.Matra_Magic_II): 1986,
Weapon.from_pydcs(Weapons.R_550_Magic_2): 1986,
# Super 530D
Weapon.from_pydcs(Weapons.Matra_Super_530D): 1988,
Weapon.from_pydcs(Weapons.Super_530D): 1988,
}

1107
game/db.py

File diff suppressed because it is too large Load Diff

282
game/debriefing.py Normal file
View File

@@ -0,0 +1,282 @@
from __future__ import annotations
import itertools
import json
import logging
import os
import threading
import time
from collections import defaultdict
from dataclasses import dataclass, field
from typing import (
Any,
Callable,
Dict,
Iterator,
List,
Type,
TYPE_CHECKING,
)
from dcs.unittype import FlyingType, UnitType
from game import db
from game.theater import Airfield, ControlPoint
from game.unitmap import Building, FrontLineUnit, GroundObjectUnit, UnitMap
from gen.flights.flight import Flight
if TYPE_CHECKING:
from game import Game
DEBRIEFING_LOG_EXTENSION = "log"
@dataclass(frozen=True)
class AirLosses:
player: List[Flight]
enemy: List[Flight]
@property
def losses(self) -> Iterator[Flight]:
return itertools.chain(self.player, self.enemy)
def by_type(self, player: bool) -> Dict[Type[FlyingType], int]:
losses_by_type: Dict[Type[FlyingType], int] = defaultdict(int)
losses = self.player if player else self.enemy
for loss in losses:
losses_by_type[loss.unit_type] += 1
return losses_by_type
def surviving_flight_members(self, flight: Flight) -> int:
losses = 0
for loss in self.losses:
if loss == flight:
losses += 1
return flight.count - losses
@dataclass
class GroundLosses:
player_front_line: List[FrontLineUnit] = field(default_factory=list)
enemy_front_line: List[FrontLineUnit] = field(default_factory=list)
player_ground_objects: List[GroundObjectUnit] = field(default_factory=list)
enemy_ground_objects: List[GroundObjectUnit] = field(default_factory=list)
player_buildings: List[Building] = field(default_factory=list)
enemy_buildings: List[Building] = field(default_factory=list)
player_airfields: List[Airfield] = field(default_factory=list)
enemy_airfields: List[Airfield] = field(default_factory=list)
@dataclass(frozen=True)
class StateData:
#: True if the mission ended. If False, the mission exited abnormally.
mission_ended: bool
#: Names of aircraft units that were killed during the mission.
killed_aircraft: List[str]
#: Names of vehicle (and ship) units that were killed during the mission.
killed_ground_units: List[str]
#: Names of static units that were destroyed during the mission.
destroyed_statics: List[str]
#: Mangled names of bases that were captured during the mission.
base_capture_events: List[str]
@classmethod
def from_json(cls, data: Dict[str, Any]) -> StateData:
return cls(
mission_ended=data["mission_ended"],
killed_aircraft=data["killed_aircrafts"],
# Airfields emit a new "dead" event every time a bomb is dropped on
# them when they've already dead. Dedup.
killed_ground_units=list(set(data["killed_ground_units"])),
destroyed_statics=data["destroyed_objects_positions"],
base_capture_events=data["base_capture_events"],
)
class Debriefing:
def __init__(
self, state_data: Dict[str, Any], game: Game, unit_map: UnitMap
) -> None:
self.state_data = StateData.from_json(state_data)
self.unit_map = unit_map
self.player_country = game.player_country
self.enemy_country = game.enemy_country
self.player_country_id = db.country_id_from_name(game.player_country)
self.enemy_country_id = db.country_id_from_name(game.enemy_country)
self.air_losses = self.dead_aircraft()
self.ground_losses = self.dead_ground_units()
@property
def front_line_losses(self) -> Iterator[FrontLineUnit]:
yield from self.ground_losses.player_front_line
yield from self.ground_losses.enemy_front_line
@property
def ground_object_losses(self) -> Iterator[GroundObjectUnit]:
yield from self.ground_losses.player_ground_objects
yield from self.ground_losses.enemy_ground_objects
@property
def building_losses(self) -> Iterator[Building]:
yield from self.ground_losses.player_buildings
yield from self.ground_losses.enemy_buildings
@property
def damaged_runways(self) -> Iterator[Airfield]:
yield from self.ground_losses.player_airfields
yield from self.ground_losses.enemy_airfields
def casualty_count(self, control_point: ControlPoint) -> int:
return len([x for x in self.front_line_losses if x.origin == control_point])
def front_line_losses_by_type(self, player: bool) -> Dict[Type[UnitType], int]:
losses_by_type: Dict[Type[UnitType], int] = defaultdict(int)
if player:
losses = self.ground_losses.player_front_line
else:
losses = self.ground_losses.enemy_front_line
for loss in losses:
losses_by_type[loss.unit_type] += 1
return losses_by_type
def building_losses_by_type(self, player: bool) -> Dict[str, int]:
losses_by_type: Dict[str, int] = defaultdict(int)
if player:
losses = self.ground_losses.player_buildings
else:
losses = self.ground_losses.enemy_buildings
for loss in losses:
if loss.ground_object.control_point.captured != player:
continue
losses_by_type[loss.ground_object.dcs_identifier] += 1
return losses_by_type
def dead_aircraft(self) -> AirLosses:
player_losses = []
enemy_losses = []
for unit_name in self.state_data.killed_aircraft:
flight = self.unit_map.flight(unit_name)
if flight is None:
logging.error(f"Could not find Flight matching {unit_name}")
continue
if flight.departure.captured:
player_losses.append(flight)
else:
enemy_losses.append(flight)
return AirLosses(player_losses, enemy_losses)
def dead_ground_units(self) -> GroundLosses:
losses = GroundLosses()
for unit_name in self.state_data.killed_ground_units:
front_line_unit = self.unit_map.front_line_unit(unit_name)
if front_line_unit is not None:
if front_line_unit.origin.captured:
losses.player_front_line.append(front_line_unit)
else:
losses.enemy_front_line.append(front_line_unit)
continue
ground_object_unit = self.unit_map.ground_object_unit(unit_name)
if ground_object_unit is not None:
if ground_object_unit.ground_object.control_point.captured:
losses.player_ground_objects.append(ground_object_unit)
else:
losses.enemy_ground_objects.append(ground_object_unit)
continue
building = self.unit_map.building_or_fortification(unit_name)
# Try appending object to the name, because we do this for building statics.
if building is None:
building = self.unit_map.building_or_fortification(
f"{unit_name} object"
)
if building is not None:
if building.ground_object.control_point.captured:
losses.player_buildings.append(building)
else:
losses.enemy_buildings.append(building)
continue
airfield = self.unit_map.airfield(unit_name)
if airfield is not None:
if airfield.captured:
losses.player_airfields.append(airfield)
else:
losses.enemy_airfields.append(airfield)
continue
# Only logging as debug because we don't currently track infantry
# deaths, so we expect to see quite a few unclaimed dead ground
# units. We should start tracking those and covert this to a
# warning.
logging.debug(
f"Death of untracked ground unit {unit_name} will "
"have no effect. This may be normal behavior."
)
return losses
@property
def base_capture_events(self):
"""Keeps only the last instance of a base capture event for each base ID."""
reversed_captures = list(reversed(self.state_data.base_capture_events))
last_base_cap_indexes = []
for idx, base in enumerate(i.split("||")[0] for i in reversed_captures):
if base not in [x[1] for x in last_base_cap_indexes]:
last_base_cap_indexes.append((idx, base))
return [reversed_captures[idx[0]] for idx in last_base_cap_indexes]
class PollDebriefingFileThread(threading.Thread):
"""Thread class with a stop() method. The thread itself has to check
regularly for the stopped() condition."""
def __init__(
self, callback: Callable[[Debriefing], None], game: Game, unit_map: UnitMap
) -> None:
super().__init__()
self._stop_event = threading.Event()
self.callback = callback
self.game = game
self.unit_map = unit_map
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def run(self):
if os.path.isfile("state.json"):
last_modified = os.path.getmtime("state.json")
else:
last_modified = 0
while not self.stopped():
if (
os.path.isfile("state.json")
and os.path.getmtime("state.json") > last_modified
):
with open("state.json", "r") as json_file:
json_data = json.load(json_file)
debriefing = Debriefing(json_data, self.game, self.unit_map)
self.callback(debriefing)
break
time.sleep(5)
def wait_for_debriefing(
callback: Callable[[Debriefing], None], game: Game, unit_map
) -> PollDebriefingFileThread:
thread = PollDebriefingFileThread(callback, game, unit_map)
thread.start()
return thread

14
game/event/airwar.py Normal file
View File

@@ -0,0 +1,14 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from .event import Event
if TYPE_CHECKING:
from game.theater import ConflictTheater
class AirWarEvent(Event):
"""Event handler for the air battle"""
def __str__(self):
return "AirWar"

View File

@@ -1,53 +1,52 @@
import typing
from __future__ import annotations
import logging
import math
from typing import Dict, Iterator, List, TYPE_CHECKING, Tuple, Type
from dcs.action import Coalition
from dcs.unittype import UnitType
from dcs.task import *
from dcs.vehicles import AirDefence
from dcs.unittype import UnitType
from dcs.mapping import Point
from dcs.task import Task
from dcs.unittype import UnitType, VehicleType
from game import *
from game import persistency
from game.debriefing import AirLosses, Debriefing
from game.infos.information import Information
from theater import *
from gen.environmentgen import EnvironmentSettings
from gen.conflictgen import Conflict
from game.db import assigned_units_from, unitdict_from
from theater.start_generator import generate_airbase_defense_group
from game.operation.operation import Operation
from game.theater import ControlPoint
from gen import AirTaskingOrder
from gen.ground_forces.combat_stance import CombatStance
from ..db import PRICES
from ..unitmap import UnitMap
from userdata.debriefing import Debriefing
from userdata import persistency
import game.db as db
DIFFICULTY_LOG_BASE = 1.1
EVENT_DEPARTURE_MAX_DISTANCE = 340000
if TYPE_CHECKING:
from ..game import Game
MINOR_DEFEAT_INFLUENCE = 0.1
DEFEAT_INFLUENCE = 0.3
STRONG_DEFEAT_INFLUENCE = 0.5
class Event:
silent = False
informational = False
is_awacs_enabled = False
ca_slots = 0
game = None # type: Game
location = None # type: Point
from_cp = None # type: ControlPoint
departure_cp = None # type: ControlPoint
to_cp = None # type: ControlPoint
operation = None # type: Operation
difficulty = 1 # type: int
environment_settings = None # type: EnvironmentSettings
BONUS_BASE = 5
def __init__(self, game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str):
def __init__(
self,
game,
from_cp: ControlPoint,
target_cp: ControlPoint,
location: Point,
attacker_name: str,
defender_name: str,
):
self.game = game
self.departure_cp = None
self.from_cp = from_cp
self.to_cp = target_cp
self.location = location
@@ -59,144 +58,143 @@ class Event:
return self.attacker_name == self.game.player_name
@property
def enemy_cp(self) -> ControlPoint:
if self.attacker_name == self.game.player_name:
return self.to_cp
else:
return self.departure_cp
@property
def tasks(self) -> typing.Collection[typing.Type[Task]]:
def tasks(self) -> List[Type[Task]]:
return []
@property
def global_cp_available(self) -> bool:
return False
def generate(self) -> UnitMap:
Operation.prepare(self.game)
unit_map = Operation.generate()
Operation.current_mission.save(
persistency.mission_path_for("liberation_nextturn.miz")
)
return unit_map
def is_departure_available_from(self, cp: ControlPoint) -> bool:
if not cp.captured:
return False
if self.location.distance_to_point(cp.position) > EVENT_DEPARTURE_MAX_DISTANCE:
return False
if cp.is_global and not self.global_cp_available:
return False
return True
def bonus(self) -> int:
return int(math.log(self.to_cp.importance + 1, DIFFICULTY_LOG_BASE) * self.BONUS_BASE)
def is_successfull(self, debriefing: Debriefing) -> bool:
return self.operation.is_successfull(debriefing)
def player_attacking(self, cp: ControlPoint, flights: db.TaskForceDict):
if self.is_player_attacking:
self.departure_cp = cp
else:
self.to_cp = cp
def player_defending(self, cp: ControlPoint, flights: db.TaskForceDict):
if self.is_player_attacking:
self.departure_cp = cp
else:
self.to_cp = cp
def generate(self):
self.operation.is_awacs_enabled = self.is_awacs_enabled
self.operation.ca_slots = self.ca_slots
self.operation.prepare(self.game.theater.terrain, is_quick=False)
self.operation.generate()
self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
self.environment_settings = self.operation.environment_settings
def commit(self, debriefing: Debriefing):
logging.info("Commiting mission results")
# ------------------------------
# Destroyed aircrafts
cp_map = {cp.id: cp for cp in self.game.theater.controlpoints}
for destroyed_aircraft in debriefing.killed_aircrafts:
try:
cpid = int(destroyed_aircraft.split("|")[3])
type = db.unit_type_from_name(destroyed_aircraft.split("|")[4])
if cpid in cp_map.keys():
cp = cp_map[cpid]
if type in cp.base.aircraft.keys():
logging.info("Aircraft destroyed : " + str(type))
cp.base.aircraft[type] = max(0, cp.base.aircraft[type]-1)
except Exception as e:
print(e)
# ------------------------------
# Destroyed ground units
killed_unit_count_by_cp = {cp.id: 0 for cp in self.game.theater.controlpoints}
cp_map = {cp.id: cp for cp in self.game.theater.controlpoints}
for killed_ground_unit in debriefing.killed_ground_units:
try:
cpid = int(killed_ground_unit.split("|")[3])
type = db.unit_type_from_name(killed_ground_unit.split("|")[4])
if cpid in cp_map.keys():
killed_unit_count_by_cp[cpid] = killed_unit_count_by_cp[cpid] + 1
cp = cp_map[cpid]
if type in cp.base.armor.keys():
logging.info("Ground unit destroyed : " + str(type))
cp.base.armor[type] = max(0, cp.base.armor[type] - 1)
except Exception as e:
print(e)
# ------------------------------
# Static ground objects
for destroyed_ground_unit_name in debriefing.killed_ground_units:
for cp in self.game.theater.controlpoints:
if not cp.ground_objects:
@staticmethod
def _transfer_aircraft(
ato: AirTaskingOrder, losses: AirLosses, for_player: bool
) -> None:
for package in ato.packages:
for flight in package.flights:
# No need to transfer to the same location.
if flight.departure == flight.arrival:
continue
# -- Static ground objects
for i, ground_object in enumerate(cp.ground_objects):
if ground_object.is_dead:
continue
# Don't transfer to bases that were captured. Note that if the
# airfield was back-filling transfers it may overflow. We could
# attempt to be smarter in the future by performing transfers in
# order up a graph to prevent transfers to full airports and
# send overflow off-map, but overflow is fine for now.
if flight.arrival.captured != for_player:
logging.info(
f"Not transferring {flight} because {flight.arrival} "
"was captured"
)
continue
if ground_object.matches_string_identifier(destroyed_ground_unit_name):
logging.info("cp {} killing ground object {}".format(cp, ground_object.string_identifier))
cp.ground_objects[i].is_dead = True
transfer_count = losses.surviving_flight_members(flight)
if transfer_count < 0:
logging.error(
f"{flight} had {flight.count} aircraft but "
f"{transfer_count} losses were recorded."
)
continue
info = Information("Building destroyed",
ground_object.dcs_identifier + " has been destroyed at location " + ground_object.obj_name,
self.game.turn)
self.game.informations.append(info)
aircraft = flight.unit_type
available = flight.departure.base.total_units_of_type(aircraft)
if available < transfer_count:
logging.error(
f"Found killed {aircraft} from {flight.departure} but "
f"that airbase has only {available} available."
)
continue
flight.departure.base.aircraft[aircraft] -= transfer_count
if aircraft not in flight.arrival.base.aircraft:
# TODO: Should use defaultdict.
flight.arrival.base.aircraft[aircraft] = 0
flight.arrival.base.aircraft[aircraft] += transfer_count
# -- AA Site groups
destroyed_units = 0
info = Information("Units destroyed at " + ground_object.obj_name,
"",
self.game.turn)
for i, ground_object in enumerate(cp.ground_objects):
if ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]:
for g in ground_object.groups:
if not hasattr(g, "units_losts"):
g.units_losts = []
for u in g.units:
if u.name == destroyed_ground_unit_name:
g.units.remove(u)
g.units_losts.append(u)
destroyed_units = destroyed_units + 1
info.text = u.type
ucount = sum([len(g.units) for g in ground_object.groups])
if ucount == 0:
ground_object.is_dead = True
if destroyed_units > 0:
self.game.informations.append(info)
def complete_aircraft_transfers(self, debriefing: Debriefing) -> None:
self._transfer_aircraft(
self.game.blue_ato, debriefing.air_losses, for_player=True
)
self._transfer_aircraft(
self.game.red_ato, debriefing.air_losses, for_player=False
)
@staticmethod
def commit_air_losses(debriefing: Debriefing) -> None:
for loss in debriefing.air_losses.losses:
aircraft = loss.unit_type
cp = loss.departure
available = cp.base.total_units_of_type(aircraft)
if available <= 0:
logging.error(
f"Found killed {aircraft} from {cp} but that airbase has "
"none available."
)
continue
logging.info(f"{aircraft} destroyed from {cp}")
cp.base.aircraft[aircraft] -= 1
@staticmethod
def commit_front_line_losses(debriefing: Debriefing) -> None:
for loss in debriefing.front_line_losses:
unit_type = loss.unit_type
control_point = loss.origin
available = control_point.base.total_units_of_type(unit_type)
if available <= 0:
logging.error(
f"Found killed {unit_type} from {control_point} but that "
"airbase has none available."
)
continue
logging.info(f"{unit_type} destroyed from {control_point}")
control_point.base.armor[unit_type] -= 1
@staticmethod
def commit_ground_object_losses(debriefing: Debriefing) -> None:
for loss in debriefing.ground_object_losses:
# TODO: This should be stored in the TGO, not in the pydcs Group.
if not hasattr(loss.group, "units_losts"):
loss.group.units_losts = []
loss.group.units.remove(loss.unit)
loss.group.units_losts.append(loss.unit)
def commit_building_losses(self, debriefing: Debriefing) -> None:
for loss in debriefing.building_losses:
loss.ground_object.kill()
self.game.informations.append(
Information(
"Building destroyed",
f"{loss.ground_object.dcs_identifier} has been destroyed at "
f"location {loss.ground_object.obj_name}",
self.game.turn,
)
)
@staticmethod
def commit_damaged_runways(debriefing: Debriefing) -> None:
for damaged_runway in debriefing.damaged_runways:
damaged_runway.damage_runway()
def commit(self, debriefing: Debriefing):
logging.info("Committing mission results")
self.commit_air_losses(debriefing)
self.commit_front_line_losses(debriefing)
self.commit_ground_object_losses(debriefing)
self.commit_building_losses(debriefing)
self.commit_damaged_runways(debriefing)
# ------------------------------
# Captured bases
#if self.game.player_country in db.BLUEFOR_FACTIONS:
coalition = 2 # Value in DCS mission event for BLUE
#else:
# if self.game.player_country in db.BLUEFOR_FACTIONS:
coalition = 2 # Value in DCS mission event for BLUE
# else:
# coalition = 1 # Value in DCS mission event for RED
for captured in debriefing.base_capture_events:
@@ -209,41 +207,41 @@ class Event:
if cp.id == id:
if cp.captured and new_owner_coalition != coalition:
cp.captured = False
info = Information(cp.name + " lost !", "The ennemy took control of " + cp.name + "\nShame on us !", self.game.turn)
for_player = False
info = Information(
cp.name + " lost !",
"The ennemy took control of "
+ cp.name
+ "\nShame on us !",
self.game.turn,
)
self.game.informations.append(info)
pname = self.game.enemy_name
captured_cps.append(cp)
elif not(cp.captured) and new_owner_coalition == coalition:
cp.captured = True
info = Information(cp.name + " captured !", "We took control of " + cp.name + "! Great job !", self.game.turn)
elif not (cp.captured) and new_owner_coalition == coalition:
for_player = True
info = Information(
cp.name + " captured !",
"We took control of " + cp.name + "! Great job !",
self.game.turn,
)
self.game.informations.append(info)
pname = self.game.player_name
captured_cps.append(cp)
else:
continue
cp.base.aircraft = {}
cp.base.armor = {}
airbase_def_id = 0
for g in cp.ground_objects:
g.groups = []
if g.airbase_group and pname != "":
generate_airbase_defense_group(airbase_def_id, g, pname, self.game, cp)
airbase_def_id = airbase_def_id + 1
cp.capture(self.game, for_player)
for cp in captured_cps:
logging.info("Will run redeploy for " + cp.name)
self.redeploy_units(cp)
except Exception:
logging.exception(f"Could not process base capture {captured}")
except Exception as e:
print(e)
self.complete_aircraft_transfers(debriefing)
# Destroyed units carcass
# -------------------------
for destroyed_unit in debriefing.destroyed_units:
for destroyed_unit in debriefing.state_data.destroyed_statics:
self.game.add_destroyed_units(destroyed_unit)
# -----------------------------------
@@ -251,12 +249,17 @@ class Event:
for cp in self.game.theater.player_points():
enemy_cps = [e for e in cp.connected_points if not e.captured]
for enemy_cp in enemy_cps:
print("Compute frontline progression for : " + cp.name + " to " + enemy_cp.name)
print(
"Compute frontline progression for : "
+ cp.name
+ " to "
+ enemy_cp.name
)
delta = 0
delta = 0.0
player_won = True
ally_casualties = killed_unit_count_by_cp[cp.id]
enemy_casualties = killed_unit_count_by_cp[enemy_cp.id]
ally_casualties = debriefing.casualty_count(cp)
enemy_casualties = debriefing.casualty_count(enemy_cp)
ally_units_alive = cp.base.total_armor
enemy_units_alive = enemy_cp.base.total_armor
@@ -267,7 +270,11 @@ class Event:
ratio = (1.0 + enemy_casualties) / (1.0 + ally_casualties)
player_aggresive = cp.stances[enemy_cp.id] in [CombatStance.AGGRESSIVE, CombatStance.ELIMINATION, CombatStance.BREAKTHROUGH]
player_aggresive = cp.stances[enemy_cp.id] in [
CombatStance.AGGRESSIVE,
CombatStance.ELIMINATION,
CombatStance.BREAKTHROUGH,
]
if ally_units_alive == 0:
player_won = False
@@ -292,11 +299,17 @@ class Event:
delta = DEFEAT_INFLUENCE
elif ally_casualties > enemy_casualties:
if ally_units_alive > 2*enemy_units_alive and player_aggresive:
if (
ally_units_alive > 2 * enemy_units_alive
and player_aggresive
):
# Even with casualties if the enemy is overwhelmed, they are going to lose ground
player_won = True
delta = MINOR_DEFEAT_INFLUENCE
elif ally_units_alive > 3*enemy_units_alive and player_aggresive:
elif (
ally_units_alive > 3 * enemy_units_alive
and player_aggresive
):
player_won = True
delta = STRONG_DEFEAT_INFLUENCE
else:
@@ -308,7 +321,10 @@ class Event:
delta = STRONG_DEFEAT_INFLUENCE
# No progress with defensive strategies
if player_won and cp.stances[enemy_cp.id] in [CombatStance.DEFENSIVE, CombatStance.AMBUSH]:
if player_won and cp.stances[enemy_cp.id] in [
CombatStance.DEFENSIVE,
CombatStance.AMBUSH,
]:
print("Defensive stance, progress is limited")
delta = MINOR_DEFEAT_INFLUENCE
@@ -316,89 +332,149 @@ class Event:
print(cp.name + " won ! factor > " + str(delta))
cp.base.affect_strength(delta)
enemy_cp.base.affect_strength(-delta)
info = Information("Frontline Report",
"Our ground forces from " + cp.name + " are making progress toward " + enemy_cp.name,
self.game.turn)
info = Information(
"Frontline Report",
"Our ground forces from "
+ cp.name
+ " are making progress toward "
+ enemy_cp.name,
self.game.turn,
)
self.game.informations.append(info)
else:
print(cp.name + " lost ! factor > " + str(delta))
enemy_cp.base.affect_strength(delta)
cp.base.affect_strength(-delta)
info = Information("Frontline Report",
"Our ground forces from " + cp.name + " are losing ground against the enemy forces from " + enemy_cp.name,
self.game.turn)
info = Information(
"Frontline Report",
"Our ground forces from "
+ cp.name
+ " are losing ground against the enemy forces from "
+ enemy_cp.name,
self.game.turn,
)
self.game.informations.append(info)
def skip(self):
pass
def redeploy_units(self, cp):
""""
def redeploy_units(self, cp: ControlPoint) -> None:
""" "
Auto redeploy units to newly captured base
"""
ally_connected_cps = [ocp for ocp in cp.connected_points if cp.captured == ocp.captured]
enemy_connected_cps = [ocp for ocp in cp.connected_points if cp.captured != ocp.captured]
ally_connected_cps = [
ocp for ocp in cp.connected_points if cp.captured == ocp.captured
]
enemy_connected_cps = [
ocp for ocp in cp.connected_points if cp.captured != ocp.captured
]
# If the newly captured cp does not have enemy connected cp,
# then it is not necessary to redeploy frontline units there.
if len(enemy_connected_cps) == 0:
return
# From each ally cp, send reinforcements
for ally_cp in ally_connected_cps:
self.redeploy_between(cp, ally_cp)
def redeploy_between(self, destination: ControlPoint, source: ControlPoint) -> None:
total_units_redeployed = 0
moved_units = {}
if source.has_active_frontline or not destination.captured:
# If there are still active front lines to defend at the
# transferring CP we should not transfer all units.
#
# Opfor also does not transfer all of their units.
# TODO: Balance the CPs rather than moving half from everywhere.
move_factor = 0.5
else:
# From each ally cp, send reinforcements
for ally_cp in ally_connected_cps:
total_units_redeployed = 0
own_enemy_cp = [ocp for ocp in ally_cp.connected_points if ally_cp.captured != ocp.captured]
# Otherwise we can move everything.
move_factor = 1
moved_units = {}
for frontline_unit, count in source.base.armor.items():
moved_units[frontline_unit] = int(count * move_factor)
total_units_redeployed = total_units_redeployed + int(count * move_factor)
# If the connected base, does not have any more enemy cp connected.
# Or if it is not the opponent redeploying forces there (enemy AI will never redeploy all their forces at once)
if len(own_enemy_cp) > 0 or not cp.captured:
for frontline_unit, count in ally_cp.base.armor.items():
moved_units[frontline_unit] = int(count/2)
total_units_redeployed = total_units_redeployed + int(count/2)
else: # So if the old base, does not have any more enemy cp connected, or if it is an enemy base
for frontline_unit, count in ally_cp.base.armor.items():
moved_units[frontline_unit] = count
total_units_redeployed = total_units_redeployed + count
destination.base.commision_units(moved_units)
source.base.commit_losses(moved_units)
cp.base.commision_units(moved_units)
ally_cp.base.commit_losses(moved_units)
# Also transfer pending deliveries.
for unit_type, count in source.pending_unit_deliveries.units.items():
if not issubclass(unit_type, VehicleType):
continue
if count <= 0:
# Don't transfer *sales*...
continue
move_count = int(count * move_factor)
source.pending_unit_deliveries.sell({unit_type: move_count})
destination.pending_unit_deliveries.order({unit_type: move_count})
total_units_redeployed += move_count
if total_units_redeployed > 0:
info = Information("Units redeployed", "", self.game.turn)
info.text = str(total_units_redeployed) + " units have been redeployed from " + ally_cp.name + " to " + cp.name
self.game.informations.append(info)
logging.info(info.text)
if total_units_redeployed > 0:
text = (
f"{total_units_redeployed} units have been redeployed from "
f"{source.name} to {destination.name}"
)
info = Information("Units redeployed", text, self.game.turn)
self.game.informations.append(info)
logging.info(text)
class UnitsDeliveryEvent:
def __init__(self, control_point: ControlPoint) -> None:
self.to_cp = control_point
self.units: Dict[Type[UnitType], int] = {}
class UnitsDeliveryEvent(Event):
informational = True
units = None # type: typing.Dict[UnitType, int]
def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game):
super(UnitsDeliveryEvent, self).__init__(game=game,
location=to_cp.position,
from_cp=from_cp,
target_cp=to_cp,
attacker_name=attacker_name,
defender_name=defender_name)
self.units = {}
def __str__(self):
def __str__(self) -> str:
return "Pending delivery to {}".format(self.to_cp)
def deliver(self, units: typing.Dict[UnitType, int]):
def order(self, units: Dict[Type[UnitType], int]) -> None:
for k, v in units.items():
self.units[k] = self.units.get(k, 0) + v
def skip(self):
def sell(self, units: Dict[Type[UnitType], int]) -> None:
for k, v in units.items():
self.units[k] = self.units.get(k, 0) - v
for k, v in self.units.items():
info = Information("Ally Reinforcement", str(k.id) + " x " + str(v) + " at " + self.to_cp.name, self.game.turn)
self.game.informations.append(info)
def consume_each_order(self) -> Iterator[Tuple[Type[UnitType], int]]:
while self.units:
yield self.units.popitem()
self.to_cp.base.commision_units(self.units)
def refund_all(self, game: Game) -> None:
for unit_type, count in self.consume_each_order():
try:
price = PRICES[unit_type]
except KeyError:
logging.error(f"Could not refund {unit_type.id}, price unknown")
continue
logging.info(f"Refunding {count} {unit_type.id} at {self.to_cp.name}")
game.adjust_budget(price * count, player=self.to_cp.captured)
def available_next_turn(self, unit_type: Type[UnitType]) -> int:
pending_units = self.units.get(unit_type)
if pending_units is None:
pending_units = 0
current_units = self.to_cp.base.total_units_of_type(unit_type)
return pending_units + current_units
def process(self, game: Game) -> None:
bought_units: Dict[Type[UnitType], int] = {}
sold_units: Dict[Type[UnitType], int] = {}
for unit_type, count in self.units.items():
coalition = "Ally" if self.to_cp.captured else "Enemy"
aircraft = unit_type.id
name = self.to_cp.name
if count >= 0:
bought_units[unit_type] = count
game.message(
f"{coalition} reinforcements: {aircraft} x {count} at {name}"
)
else:
sold_units[unit_type] = -count
game.message(f"{coalition} sold: {aircraft} x {-count} at {name}")
self.to_cp.base.commision_units(bought_units)
self.to_cp.base.commit_losses(sold_units)
self.units = {}
bought_units = {}
sold_units = {}

View File

@@ -1,53 +1,12 @@
from game.event import *
from game.operation.frontlineattack import FrontlineAttackOperation
from userdata.debriefing import Debriefing
from .event import Event
class FrontlineAttackEvent(Event):
@property
def tasks(self) -> typing.Collection[typing.Type[Task]]:
if self.is_player_attacking:
return [CAS, CAP]
else:
return [CAP]
@property
def global_cp_available(self) -> bool:
return True
"""
An event centered on a FrontLine Conflict.
Currently the same as its parent, but here for legacy compatibility as well as to allow for
future unique Event handling
"""
def __str__(self):
return "Frontline attack"
def is_successfull(self, debriefing: Debriefing):
attackers_success = True
if self.from_cp.captured:
return attackers_success
else:
return not attackers_success
def commit(self, debriefing: Debriefing):
super(FrontlineAttackEvent, self).commit(debriefing)
def skip(self):
if self.to_cp.captured:
self.to_cp.base.affect_strength(-0.1)
def player_attacking(self, flights: db.TaskForceDict):
op = FrontlineAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
self.operation = op
def player_defending(self, flights: db.TaskForceDict):
op = FrontlineAttackOperation(game=self.game,
attacker_name=self.attacker_name,
defender_name=self.defender_name,
from_cp=self.from_cp,
departure_cp=self.departure_cp,
to_cp=self.to_cp)
self.operation = op

View File

@@ -1,50 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Australia_2005 = {
"country": "Australia",
"side": "blue",
"units": [
FA_18C_hornet,
KC_135,
KC130,
C_130,
E_3A,
Armor.MBT_M1A2_Abrams,
Armor.MBT_Leopard_1A3,
Armor.APC_M113,
Armor.IFV_LAV_25,
Armor.IFV_MCV_80,
UH_1H,
AH_1W, # Standing as EC Tiger
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.Rapier_FSA_Launcher,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.Rapier_FSA_Launcher,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "destroyer": [
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "lhanames": [
"HMAS Canberra",
"HMAS Adelaide"
], "boat":[
"ArleighBurkeGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,58 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
BLUEFOR_COLDWAR = {
"country": "Combined Joint Task Forces Blue",
"side": "blue",
"units": [
F_14B,
F_4E,
F_5E_3,
A_10A,
AJS37,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
SA342M,
SA342L,
Armor.MBT_M60A3_Patton,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Chaparral_M48,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.AAA_Vulcan_M163,
], "aircraft_carrier": [
CVN_74_John_C__Stennis,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis",
], "lhanames": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
], "boat": [
], "has_jtac": True
}

View File

@@ -1,65 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
from pydcs_extensions.a4ec.a4ec import A_4E_C
BLUEFOR_COLDWAR_A4 = {
"country": "Combined Joint Task Forces Blue",
"side": "blue",
"units": [
F_14B,
F_4E,
F_5E_3,
A_10A,
AJS37,
A_4E_C,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
SA342M,
SA342L,
Armor.MBT_M60A3_Patton,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Chaparral_M48,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.AAA_Vulcan_M163,
], "aircraft_carrier": [
CVN_74_John_C__Stennis,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis",
], "lhanames": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
], "boat": [
], "requirements": {
"Community A-4E": "https://heclak.github.io/community-a4e-c/",
}, "has_jtac": True
}

View File

@@ -1,68 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
from pydcs_extensions.a4ec.a4ec import A_4E_C
from pydcs_extensions.mb339.mb339 import MB_339PAN
BLUEFOR_COLDWAR_MODS = {
"country": "USA",
"side": "blue",
"units": [
F_14B,
F_4E,
F_5E_3,
A_10A,
AJS37,
A_4E_C,
MB_339PAN,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
SA342M,
SA342L,
Armor.MBT_M60A3_Patton,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Chaparral_M48,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.AAA_Vulcan_M163,
], "aircraft_carrier": [
CVN_74_John_C__Stennis,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis",
], "lhanames": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
], "boat": [
], "requirements": {
"MB-339A": "http://www.freccetricolorivirtuali.net/",
"Community A-4E": "https://heclak.github.io/community-a4e-c/",
}, "has_jtac": True
}

View File

@@ -1,83 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
BLUEFOR_MODERN = {
"country": "Combined Joint Task Forces Blue",
"side": "blue",
"units": [
F_15C,
F_14B,
FA_18C_hornet,
F_16C_50,
JF_17,
M_2000C,
F_5E_3,
Su_27,
Su_25T,
A_10A,
A_10C,
A_10C_2,
AV8BNA,
AJS37,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
AH_64D,
Ka_50,
SA342M,
SA342L,
Armor.MBT_M1A2_Abrams,
Armor.MBT_Leopard_2,
Armor.ATGM_M1134_Stryker,
Armor.IFV_M2A2_Bradley,
Armor.IFV_Marder,
Armor.APC_M1043_HMMWV_Armament,
Artillery.MLRS_M270,
Artillery.SPH_M109_Paladin,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Patriot_EPP_III,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.SAM_Avenger_M1097,
], "aircraft_carrier": [
CVN_74_John_C__Stennis,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "destroyer": [
Oliver_Hazzard_Perry_class,
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis",
], "lhanames": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
], "boat":[
"ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,43 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Canada_2005 = {
"country": "Canada",
"side": "blue",
"units": [
FA_18C_hornet,
KC_135,
KC130,
C_130,
E_3A,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leopard_2,
Armor.IFV_LAV_25,
Armor.APC_M113,
Armor.IFV_MCV_80,
UH_1H,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Avenger_M1097,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.SAM_Avenger_M1097,
], "destroyer": [
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "boat":[
"ArleighBurkeGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,80 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
China_2010 = {
"country": "China",
"side": "red",
"units": [
MiG_21Bis, # Standing as J-7
Su_30,
Su_33,
J_11A,
JF_17,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
KJ_2000,
Mi_8MT,
Mi_28N,
AirDefence.SAM_SA_10_S_300PS_LN_5P85C, # Standing as HQ-9+
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.HQ_7_Self_Propelled_LN,
Armor.ZTZ_96B,
Armor.MBT_T_55,
Armor.ZBD_04A,
Armor.IFV_BMP_1,
Artillery.MLRS_9A52_Smerch,
Artillery.SPH_2S9_Nona,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Paratrooper_AKS,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_RPG_16,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160
],
"shorad":[
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.Rapier_FSA_Launcher, # Standing as PL-9C Shorad
AirDefence.HQ_7_Self_Propelled_LN
], "aircraft_carrier": [
CV_1143_5_Admiral_Kuznetsov,
], "destroyer": [
Type_052B_Destroyer,
Type_052C_Destroyer
], "cruiser": [
Type_054A_Frigate,
], "helicopter_carrier": [
Type_071_Amphibious_Transport_Dock,
], "lhanames": [
"Kunlun Shan",
"Jinggang Shan",
"Changbai Shan",
"Yimeng Shan",
"Longhu Shan",
"Wuzhi Shan",
"Wudang Shan"
], "carrier_names": [
"001 Liaoning",
"002 Shandong",
], "boat":[
"Type54GroupGenerator"
],
"has_jtac": True,
"jtac_unit": WingLoong_I
}

299
game/factions/faction.py Normal file
View File

@@ -0,0 +1,299 @@
from __future__ import annotations
import logging
from dataclasses import dataclass, field
from typing import Optional, Dict, Type, List, Any, cast
import dcs
from dcs.countries import country_dict
from dcs.planes import plane_map
from dcs.unittype import FlyingType, ShipType, VehicleType, UnitType
from dcs.vehicles import Armor, Unarmed, Infantry, Artillery, AirDefence
from game.data.building_data import (
WW2_ALLIES_BUILDINGS,
DEFAULT_AVAILABLE_BUILDINGS,
WW2_GERMANY_BUILDINGS,
WW2_FREE,
)
from game.data.doctrine import (
Doctrine,
MODERN_DOCTRINE,
COLDWAR_DOCTRINE,
WWII_DOCTRINE,
)
from pydcs_extensions.mod_units import MODDED_VEHICLES, MODDED_AIRPLANES
@dataclass
class Faction:
# Country used by this faction
country: str = field(default="")
# Nice name of the faction
name: str = field(default="")
# List of faction file authors
authors: str = field(default="")
# A description of the faction
description: str = field(default="")
# Available aircraft
aircrafts: List[Type[FlyingType]] = field(default_factory=list)
# Available awacs aircraft
awacs: List[Type[FlyingType]] = field(default_factory=list)
# Available tanker aircraft
tankers: List[Type[FlyingType]] = field(default_factory=list)
# Available frontline units
frontline_units: List[Type[VehicleType]] = field(default_factory=list)
# Available artillery units
artillery_units: List[Type[VehicleType]] = field(default_factory=list)
# Infantry units used
infantry_units: List[Type[VehicleType]] = field(default_factory=list)
# Logistics units used
logistics_units: List[Type[VehicleType]] = field(default_factory=list)
# Possible SAMS site generators for this faction
air_defenses: List[str] = field(default_factory=list)
# Possible EWR generators for this faction.
ewrs: List[str] = field(default_factory=list)
# Possible Missile site generators for this faction
missiles: List[str] = field(default_factory=list)
# Required mods or asset packs
requirements: Dict[str, str] = field(default_factory=dict)
# possible aircraft carrier units
aircraft_carrier: List[Type[UnitType]] = field(default_factory=list)
# possible helicopter carrier units
helicopter_carrier: List[Type[UnitType]] = field(default_factory=list)
# Possible carrier names
carrier_names: List[str] = field(default_factory=list)
# Possible helicopter carrier names
helicopter_carrier_names: List[str] = field(default_factory=list)
# Navy group generators
navy_generators: List[str] = field(default_factory=list)
# Available destroyers
destroyers: List[Type[ShipType]] = field(default_factory=list)
# Available cruisers
cruisers: List[Type[ShipType]] = field(default_factory=list)
# How many navy group should we try to generate per CP on startup for this faction
navy_group_count: int = field(default=1)
# How many missiles group should we try to generate per CP on startup for this faction
missiles_group_count: int = field(default=1)
# Whether this faction has JTAC access
has_jtac: bool = field(default=False)
# Unit to use as JTAC for this faction
jtac_unit: Optional[Type[FlyingType]] = field(default=None)
# doctrine
doctrine: Doctrine = field(default=MODERN_DOCTRINE)
# List of available buildings for this faction
building_set: List[str] = field(default_factory=list)
# List of default livery overrides
liveries_overrides: Dict[Type[UnitType], List[str]] = field(default_factory=dict)
#: Set to True if the faction should force the "Unrestricted satnav" option
#: for the mission. This option enables GPS for capable aircraft regardless
#: of the time period or operator. For example, the CJTF "countries" don't
#: appear to have GPS capability, so they need this.
#:
#: Note that this option cannot be set per-side. If either faction needs it,
#: both will use it.
unrestricted_satnav: bool = False
@classmethod
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
faction = Faction()
faction.country = json.get("country", "/")
if faction.country not in [c.name for c in country_dict.values()]:
raise AssertionError(
'Faction\'s country ("{}") is not a valid DCS country ID'.format(
faction.country
)
)
faction.name = json.get("name", "")
if not faction.name:
raise AssertionError("Faction has no valid name")
faction.authors = json.get("authors", "")
faction.description = json.get("description", "")
faction.aircrafts = load_all_aircraft(json.get("aircrafts", []))
faction.awacs = load_all_aircraft(json.get("awacs", []))
faction.tankers = load_all_aircraft(json.get("tankers", []))
faction.frontline_units = load_all_vehicles(json.get("frontline_units", []))
faction.artillery_units = load_all_vehicles(json.get("artillery_units", []))
faction.infantry_units = load_all_vehicles(json.get("infantry_units", []))
faction.logistics_units = load_all_vehicles(json.get("logistics_units", []))
faction.ewrs = json.get("ewrs", [])
faction.air_defenses = json.get("air_defenses", [])
# Compatibility for older factions. All air defenses now belong to a
# single group and the generator decides what belongs where.
faction.air_defenses.extend(json.get("sams", []))
faction.air_defenses.extend(json.get("shorads", []))
faction.missiles = json.get("missiles", [])
faction.requirements = json.get("requirements", {})
faction.carrier_names = json.get("carrier_names", [])
faction.helicopter_carrier_names = json.get("helicopter_carrier_names", [])
faction.navy_generators = json.get("navy_generators", [])
faction.aircraft_carrier = load_all_ships(json.get("aircraft_carrier", []))
faction.helicopter_carrier = load_all_ships(json.get("helicopter_carrier", []))
faction.destroyers = load_all_ships(json.get("destroyers", []))
faction.cruisers = load_all_ships(json.get("cruisers", []))
faction.has_jtac = json.get("has_jtac", False)
jtac_name = json.get("jtac_unit", None)
if jtac_name is not None:
faction.jtac_unit = load_aircraft(jtac_name)
else:
faction.jtac_unit = None
faction.navy_group_count = int(json.get("navy_group_count", 1))
faction.missiles_group_count = int(json.get("missiles_group_count", 0))
# Load doctrine
doctrine = json.get("doctrine", "modern")
if doctrine == "modern":
faction.doctrine = MODERN_DOCTRINE
elif doctrine == "coldwar":
faction.doctrine = COLDWAR_DOCTRINE
elif doctrine == "ww2":
faction.doctrine = WWII_DOCTRINE
else:
faction.doctrine = MODERN_DOCTRINE
# Load the building set
building_set = json.get("building_set", "default")
if building_set == "default":
faction.building_set = DEFAULT_AVAILABLE_BUILDINGS
elif building_set == "ww2free":
faction.building_set = WW2_FREE
elif building_set == "ww2ally":
faction.building_set = WW2_ALLIES_BUILDINGS
elif building_set == "ww2germany":
faction.building_set = WW2_GERMANY_BUILDINGS
else:
faction.building_set = DEFAULT_AVAILABLE_BUILDINGS
# Load liveries override
faction.liveries_overrides = {}
liveries_overrides = json.get("liveries_overrides", {})
for k, v in liveries_overrides.items():
k = load_aircraft(k)
if k is not None:
faction.liveries_overrides[k] = [s.lower() for s in v]
faction.unrestricted_satnav = json.get("unrestricted_satnav", False)
return faction
@property
def units(self) -> List[Type[UnitType]]:
return (
self.infantry_units
+ self.aircrafts
+ self.awacs
+ self.artillery_units
+ self.frontline_units
+ self.tankers
+ self.logistics_units
)
def unit_loader(unit: str, class_repository: List[Any]) -> Optional[Type[UnitType]]:
"""
Find unit by name
:param unit: Unit name as string
:param class_repository: Repository of classes (Either a module, a class, or a list of classes)
:return: The unit as a PyDCS type
"""
if unit is None:
return None
elif unit in plane_map.keys():
return plane_map[unit]
else:
for mother_class in class_repository:
if getattr(mother_class, unit, None) is not None:
return getattr(mother_class, unit)
if type(mother_class) is list:
for m in mother_class:
if m.__name__ == unit:
return m
logging.error(f"FACTION ERROR : Unable to find {unit} in pydcs")
return None
def load_aircraft(name: str) -> Optional[Type[FlyingType]]:
return cast(
Optional[FlyingType],
unit_loader(name, [dcs.planes, dcs.helicopters, MODDED_AIRPLANES]),
)
def load_all_aircraft(data) -> List[Type[FlyingType]]:
items = []
for name in data:
item = load_aircraft(name)
if item is not None:
items.append(item)
return items
def load_vehicle(name: str) -> Optional[Type[VehicleType]]:
return cast(
Optional[FlyingType],
unit_loader(
name, [Infantry, Unarmed, Armor, AirDefence, Artillery, MODDED_VEHICLES]
),
)
def load_all_vehicles(data) -> List[Type[VehicleType]]:
items = []
for name in data:
item = load_vehicle(name)
if item is not None:
items.append(item)
return items
def load_ship(name: str) -> Optional[Type[ShipType]]:
return cast(Optional[FlyingType], unit_loader(name, [dcs.ships]))
def load_all_ships(data) -> List[Type[ShipType]]:
items = []
for name in data:
item = load_ship(name)
if item is not None:
items.append(item)
return items

View File

@@ -0,0 +1,46 @@
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import Dict, Iterator, Optional, Type
from game.factions.faction import Faction
FACTION_DIRECTORY = Path("./resources/factions/")
class FactionLoader:
def __init__(self) -> None:
self._factions: Optional[Dict[str, Faction]] = None
@property
def factions(self) -> Dict[str, Faction]:
self.initialize()
assert self._factions is not None
return self._factions
def initialize(self) -> None:
if self._factions is None:
self._factions = self.load_factions()
@classmethod
def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]:
files = [f for f in FACTION_DIRECTORY.glob("*.json") if f.is_file()]
factions = {}
for f in files:
try:
with f.open("r", encoding="utf-8") as fdata:
data = json.load(fdata, encoding="utf-8")
factions[data["name"]] = Faction.from_json(data)
logging.info("Loaded faction : " + str(f))
except Exception:
logging.exception(f"Unable to load faction : {f}")
return factions
def __getitem__(self, name: str) -> Faction:
return self.factions[name]
def __iter__(self) -> Iterator[str]:
return iter(self.factions.keys())

View File

@@ -1,47 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
France_1995 = {
"country": "France",
"side": "blue",
"units": [
M_2000C,
Mirage_2000_5,
KC_135,
KC130,
C_130,
E_3A,
SA342M,
SA342L,
SA342Mistral,
Armor.MBT_Leclerc,
Armor.TPz_Fuchs, # Standing as VAB
Armor.APC_Cobra, # Standing as VBL
Armor.ATGM_M1134_Stryker, # Standing as VAB Mephisto
Artillery.SPH_M109_Paladin, # Standing as AMX30 AuF1
Artillery.MLRS_M270,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Roland_ADS,
AirDefence.SAM_Hawk_PCP,
AirDefence.HQ_7_Self_Propelled_LN, # Standing as Crotale
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.SAM_Roland_ADS
], "boat":[
"ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,62 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
France_2005 = {
"country": "France",
"side": "blue",
"units":[
M_2000C,
Mirage_2000_5,
FA_18C_hornet, # Standing as Rafale M
KC_135,
KC130,
C_130,
E_3A,
SA342M,
SA342L,
SA342Mistral,
Armor.MBT_Leclerc,
Armor.TPz_Fuchs, # Standing as VAB
Armor.APC_Cobra, # Standing as VBL
Armor.ATGM_M1134_Stryker, # Standing as VAB Mephisto
Artillery.SPH_M109_Paladin, # Standing as AMX30 AuF1
Artillery.MLRS_M270,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Roland_ADS,
AirDefence.SAM_Hawk_PCP,
AirDefence.HQ_7_Self_Propelled_LN, # Standing as Crotale
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad":[
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.SAM_Roland_ADS
], "aircraft_carrier": [
CVN_74_John_C__Stennis, # Standing as CDG Aircraft Carrier
], "helicopter_carrier": [
LHA_1_Tarawa, # Standing as Mistral Class
], "destroyer": [
Oliver_Hazzard_Perry_class,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"PA Charles de Gaulle",
], "lhanames": [
"L9013 Mistral",
"L9014 Tonerre",
"L9015 Dixmude"
], "boat":[
"ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,80 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
import pydcs_extensions.frenchpack.frenchpack as frenchpack
from pydcs_extensions.rafale.rafale import Rafale_M, Rafale_A_S
France_2005_Modded = {
"country": "France",
"side": "blue",
"units": [
M_2000C,
Mirage_2000_5,
Rafale_M,
Rafale_A_S,
KC_135,
KC130,
C_130,
E_3A,
SA342M,
SA342L,
SA342Mistral,
Armor.MBT_Leclerc,
Artillery.SPH_M109_Paladin, # Standing as AMX30 AuF1
Artillery.MLRS_M270,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
frenchpack.ERC_90,
frenchpack.TRM_2000_PAMELA,
frenchpack.VAB__50,
frenchpack.VAB_MEPHISTO,
frenchpack.VAB_T20_13,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
frenchpack.VBAE_CRAB,
frenchpack.VBAE_CRAB_MMP,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Roland_ADS,
AirDefence.SAM_Hawk_PCP,
AirDefence.HQ_7_Self_Propelled_LN, # Standing as Crotale
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.SAM_Roland_ADS
], "aircraft_carrier": [
CVN_74_John_C__Stennis, # Standing as CDG Aircraft Carrier
], "helicopter_carrier": [
LHA_1_Tarawa, # Standing as Mistral Class
], "destroyer": [
Oliver_Hazzard_Perry_class,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"PA Charles de Gaulle",
], "lhanames": [
"L9013 Mistral",
"L9014 Tonerre",
"L9015 Dixmude"
], "boat": [
"ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator"
], "requirements": {
"frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974",
"RAFALE 2.5.5": "https://www.digitalcombatsimulator.com/fr/files/3307478/",
}, "has_jtac": True
}

View File

@@ -1,47 +0,0 @@
from dcs.planes import *
from dcs.vehicles import *
from game.data.building_data import WW2_GERMANY_BUILDINGS
from game.data.doctrine import WWII_DOCTRINE
Germany_1944 = {
"country": "Third Reich",
"side": "red",
"units": [
FW_190A8,
FW_190D9,
Bf_109K_4,
Ju_88A4,
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B__Tiger_II,
Armor.APC_Sd_Kfz_251,
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.Sd_Kfz_184_Elefant,
Armor.TD_Jagdpanther_G1,
Armor.TD_Jagdpanzer_IV,
Artillery.Sturmpanzer_IV_Brummbär,
Unarmed.Sd_Kfz_2,
Unarmed.Sd_Kfz_7,
Unarmed.Kübelwagen_82,
Infantry.Infantry_Mauser_98,
AirDefence.AAA_8_8cm_Flak_36,
],"requirements":{
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
},
"shorad": [
AirDefence.AAA_8_8cm_Flak_36,
],
"objects": WW2_GERMANY_BUILDINGS,
"doctrine": WWII_DOCTRINE,
"boat": ["UBoatGroupGenerator", "SchnellbootGroupGenerator"],
"boat_count": 2,
"missiles": ["V1GroupGenerator"],
"missiles_count": 1
}

View File

@@ -1,40 +0,0 @@
from dcs.planes import *
from dcs.vehicles import *
from game.data.building_data import WW2_GERMANY_BUILDINGS
from game.data.doctrine import WWII_DOCTRINE
Germany_1944_Easy = {
"country": "Third Reich",
"side": "red",
"units": [
FW_190A8,
FW_190D9,
Bf_109K_4,
Ju_88A4,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
Armor.APC_Sd_Kfz_251,
Armor.IFV_Sd_Kfz_234_2_Puma,
Artillery.Sturmpanzer_IV_Brummbär,
Unarmed.Sd_Kfz_2,
Unarmed.Sd_Kfz_7,
Unarmed.Kübelwagen_82,
Infantry.Infantry_Mauser_98,
AirDefence.AAA_8_8cm_Flak_36,
],"requirements":{
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
},
"shorad":[
AirDefence.AAA_8_8cm_Flak_36,
],
"objects": WW2_GERMANY_BUILDINGS,
"doctrine": WWII_DOCTRINE,
"boat": ["UBoatGroupGenerator", "SchnellbootGroupGenerator"],
"boat_count": 1,
"missiles": ["V1GroupGenerator"],
"missiles_count": 1
}

View File

@@ -1,45 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Germany_1990 = {
"country": "Germany",
"side": "blue",
"units":[
MiG_29G,
Tornado_IDS,
F_4E,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
SA342M,
SA342L,
Armor.TPz_Fuchs,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leopard_2,
Armor.IFV_Marder,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Roland_ADS,
AirDefence.SAM_Hawk_PCP,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
],
"shorad":[
AirDefence.SPAAA_Gepard,
AirDefence.SAM_Roland_ADS,
], "boat":[
"OliverHazardPerryGroupGenerator"
]
}

View File

@@ -1,55 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
India_2010 = {
"country": "India",
"side": "blue",
"units": [
Mirage_2000_5,
M_2000C,
MiG_27K,
MiG_21Bis,
MiG_29S,
Su_30,
KC_135,
KC130,
C_130,
E_3A,
AH_64A,
Mi_8MT,
Armor.MBT_T_90,
Armor.MBT_T_72B,
Armor.IFV_BMP_2,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
],
"shorad":[
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.AAA_ZU_23_Emplacement,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_19_Tunguska_2S6
], "aircraft_carrier": [
CV_1143_5_Admiral_Kuznetsov,
], "destroyer": [
FSG_1241_1MP_Molniya,
], "carrier_names": [
"INS Vikramaditya"
], "boat":[
"ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator", "MolniyaGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,27 +0,0 @@
from dcs.vehicles import *
from dcs.ships import *
from dcs.planes import *
from dcs.helicopters import *
Insurgent = {
"country": "Insurgents",
"side": "red",
"units": [
AirDefence.AAA_ZU_23_Insurgent_Closed,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
Armor.APC_Cobra,
Armor.APC_MTLB,
Armor.ARV_BRDM_2,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Infantry_Soldier_Insurgents,
Infantry.Soldier_RPG,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160
]
}

View File

@@ -1,33 +0,0 @@
from dcs.ships import *
from dcs.vehicles import *
from pydcs_extensions.frenchpack.frenchpack import DIM__TOYOTA_BLUE, DIM__TOYOTA_DESERT, DIM__TOYOTA_GREEN, \
DIM__KAMIKAZE
Insurgent_modded = {
"country": "Insurgents",
"side": "red",
"units": [
AirDefence.AAA_ZU_23_Insurgent_Closed,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
DIM__TOYOTA_BLUE,
DIM__TOYOTA_DESERT,
DIM__TOYOTA_GREEN,
DIM__KAMIKAZE,
Armor.ARV_BRDM_2,
Armor.APC_Cobra,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Soldier_AK,
Infantry.Infantry_Soldier_Insurgents,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160
], "requirements": {
"frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974",
}
}

View File

@@ -1,58 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Iran_2015 = {
"country": "Iran",
"side": "red",
"units": [
MiG_29A,
F_4E,
F_14B,
F_5E_3,
MiG_21Bis,
Su_24M,
Su_25,
Su_17M4,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
Mi_28N,
Mi_24V,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_SA_2_LN_SM_90,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.SAM_SA_11_Buk_LN_9A310M1,
Armor.APC_M113,
Armor.APC_BTR_80,
Armor.MBT_M60A3_Patton,
Armor.MBT_T_72B,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Soldier_AK,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160
],
"shorad":[
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.AAA_ZU_23_Insurgent_Closed
], "boat":[
"GrishaGroupGenerator", "MolniyaGroupGenerator", "KiloSubGroupGenerator"
]
}

View File

@@ -1,33 +0,0 @@
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Israel_1948 = {
"country": "Israel",
"side": "blue",
"units":[
SpitfireLFMkIXCW,
SpitfireLFMkIX,
P_51D,
P_51D_30_NA,
Bf_109K_4, # Standing as Avia S-199
B_17G,
Armor.MT_M4A4_Sherman_Firefly,
Armor.APC_M2A1,
Armor.MT_M4_Sherman,
Armor.LAC_M8_Greyhound,
Unarmed.Transport_M818,
Infantry.Infantry_SMLE_No_4_Mk_1,
AirDefence.AAA_Bofors_40mm,
Armed_speedboat,
],"requirements":{
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
},
"shorad": [
AirDefence.AAA_Bofors_40mm
], "boat": [
], "has_jtac": False
}

View File

@@ -1,112 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
from pydcs_extensions.a4ec.a4ec import A_4E_C
Israel_1973 = {
"country": "Israel",
"side": "blue",
"units":[
F_4E,
A_4E_C,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
Armor.MT_M4A4_Sherman_Firefly,
Armor.APC_M2A1,
Armor.MBT_M60A3_Patton,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_Hawk_PCP,
AirDefence.AAA_Bofors_40mm,
AirDefence.SAM_Chaparral_M48,
Armed_speedboat,
], "requirements": {
"Community A-4E": "https://heclak.github.io/community-a4e-c/",
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
}, "shorad": [
AirDefence.SAM_Chaparral_M48,
AirDefence.AAA_Bofors_40mm
], "boat": [
], "has_jtac": True
}
Israel_1973_NO_WW2_UNITS = {
"country": "Israel",
"side": "blue",
"units":[
F_4E,
A_4E_C,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
Armor.MBT_M60A3_Patton,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Chaparral_M48,
Armed_speedboat,
], "requirements": {
"Community A-4E": "https://heclak.github.io/community-a4e-c/",
}, "shorad": [
AirDefence.SAM_Chaparral_M48,
], "boat": [
], "has_jtac": True
}
Israel_1982 = {
"country": "Israel",
"side": "blue",
"units":[
F_4E,
A_4E_C,
F_15C,
F_16A,
F_16C_50,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
AH_1W,
Armor.APC_M113,
Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_Mk__4,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Chaparral_M48,
Armed_speedboat,
], "requirements": {
"Community A-4E": "https://heclak.github.io/community-a4e-c/",
}, "shorad": [
AirDefence.SAM_Chaparral_M48,
], "boat": [
], "has_jtac": True
}

View File

@@ -1,44 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Israel_2000 = {
"country": "Israel",
"side": "blue",
"units":[
F_16C_50,
F_15C,
F_15E,
F_4E,
KC_135,
KC130,
C_130,
E_3A,
AH_1W,
AH_64D,
Armor.MBT_Merkava_Mk__4,
Armor.APC_M113,
Armor.APC_M1043_HMMWV_Armament,
Armor.ATGM_M1045_HMMWV_TOW,
Artillery.SPH_M109_Paladin,
Artillery.MLRS_M270,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_Patriot_EPP_III,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
],
"shorad": [
AirDefence.SAM_Avenger_M1097
], "boat": [
"ArleighBurkeGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,48 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Italy_1990 = {
"country": "Italy",
"side": "blue",
"units": [
Tornado_IDS,
AV8BNA,
# MB339,
KC_135,
S_3B_Tanker,
C_130,
E_3A,
AH_1W,
UH_1H,
Armor.MBT_Leopard_1A3, # OF-40 MBT
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Avenger_M1097,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad":[
AirDefence.SAM_Avenger_M1097,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "destroyer": [
Oliver_Hazzard_Perry_class,
], "cruiser": [
Ticonderoga_class,
], "lhanames": [
"Giuseppe Garibaldi",
"Cavour",
], "boat":[
"OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,52 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
from pydcs_extensions.mb339.mb339 import MB_339PAN
Italy_1990_MB339 = {
"country": "Italy",
"side": "blue",
"units": [
Tornado_IDS,
AV8BNA,
MB_339PAN,
KC_135,
S_3B_Tanker,
C_130,
E_3A,
AH_1W,
UH_1H,
Armor.MBT_Leopard_1A3, # OF-40 MBT
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Avenger_M1097,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad":[
AirDefence.SAM_Avenger_M1097,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "destroyer": [
Oliver_Hazzard_Perry_class,
], "cruiser": [
Ticonderoga_class,
], "lhanames": [
"Giuseppe Garibaldi",
"Cavour",
], "boat": [
"OliverHazardPerryGroupGenerator"
], "requirements": {
"MB-339A": "http://www.freccetricolorivirtuali.net/",
}, "has_jtac": True
}

View File

@@ -1,54 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Japan_2005 = {
"country": "Japan",
"side": "blue",
"units": [
F_15C, # F-15J/DJ
F_16C_50, # F-2A/B
F_4E, # F-4EJ
KC_135,
KC130,
C_130,
E_3A,
AH_1W,
AH_64D,
Armor.MBT_Merkava_Mk__4, # Standing as Type 10 MBT
Armor.MBT_M1A2_Abrams, # Standing as Type 90 MBT
Armor.IFV_Marder, # Standing as Type 89 IFV
Armor.TPz_Fuchs, # Standing as Type 96 APC
Armor.IFV_LAV_25, # Standing as Type 16 or Type 87
Armor.APC_M1043_HMMWV_Armament,
Artillery.MLRS_M270,
Artillery.SPH_M109_Paladin, # Standing as Type 99 SPH
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Patriot_EPP_III,
LHA_1_Tarawa,
], "shorad": [
AirDefence.SPAAA_Gepard, # Type 87 SPAG
], "helicopter_carrier": [
LHA_1_Tarawa, # Standing as Hyuga-class helicopter carrier
], "destroyer": [
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "lhanames": [
"Hyuga",
"Ise",
], "boat":[
"ArleighBurkeGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,49 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.vehicles import *
Libya_2011 = {
"country": "Libya",
"side": "red",
"units": [
MiG_21Bis,
MiG_23MLD,
Su_24M,
Su_17M4,
Mi_24V,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_2_LN_SM_90,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.HQ_7_Self_Propelled_LN,
Armor.IFV_BMP_1,
Armor.FDDM_Grad,
Armor.ARV_BRDM_2,
Armor.MBT_T_55,
Armor.MBT_T_72B,
Artillery.MLRS_BM_21_Grad,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Paratrooper_RPG_16,
Infantry.Infantry_Soldier_Insurgents
],
"shorad":[
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.SAM_SA_8_Osa_9A33,
], "boat": [
"GrishaGroupGenerator", "MolniyaGroupGenerator"
]
}

View File

@@ -1,38 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Netherlands_1990 = {
"country": "The Netherlands",
"side": "blue",
"units": [
F_16C_50,
F_5E_3,
KC_135,
KC130,
C_130,
E_3A,
AH_64A,
Armor.APC_M113,
Armor.MBT_Leopard_1A3,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Avenger_M1097,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
],
"shorad": [
AirDefence.SAM_Avenger_M1097
], "boat": [
"OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,53 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
NorthKorea_2000 = {
"country": "North Korea",
"side": "red",
"units":[
MiG_29A,
Su_25,
MiG_15bis,
MiG_21Bis,
MiG_23MLD,
MiG_19P,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
Mi_8MT,
Mi_24V,
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_80U,
Armor.IFV_BMP_1,
Armor.APC_BTR_80,
Armor.ARV_BRDM_2,
Unarmed.Transport_M818,
Infantry.Soldier_AK,
AirDefence.SAM_SA_2_LN_SM_90,
AirDefence.SAM_SA_3_S_125_LN_5P73,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160
],
"shorad":[
AirDefence.AAA_ZU_23_Emplacement,
AirDefence.SPAAA_ZSU_23_4_Shilka
],
"boat": [
"GrishaGroupGenerator", "MolniyaGroupGenerator"
]
}

View File

@@ -1,42 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Pakistan_2015 = {
"country": "Pakistan",
"side": "blue",
"units": [
JF_17,
F_16C_50,
MiG_21Bis, # Standing as J-7
MiG_19P, # Standing as J-6
IL_78M,
E_3A,
UH_1H,
AH_1W,
Armor.MBT_T_80U,
Armor.MBT_T_55, # Standing as Al-Zarrar / Type 59 MBT
Armor.ZBD_04A,
Armor.APC_BTR_80,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_SA_2_LN_SM_90, # Standing as HQ-2
AirDefence.SAM_SA_10_S_300PS_LN_5P85C, # Standing as HQ-9
Armed_speedboat,
], "shorad": [
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
AirDefence.AAA_ZU_23_Closed
], "boat": [
"Type54GroupGenerator", "OliverHazardPerryGroupGenerator"
],
"has_jtac": True,
"jtac_unit": WingLoong_I
}

View File

@@ -1,91 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
from pydcs_extensions.mb339.mb339 import MB_339PAN
PMC_WESTERN_A = {
"country": "USA",
"side": "blue",
"units": [
C_101CC,
UH_1H,
Mi_8MT,
OH_58D,
SA342M,
Armor.APC_M1043_HMMWV_Armament,
Armor.IFV_MCV_80,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Avenger_M1097,
Armed_speedboat,
], "shorad":[
AirDefence.SAM_Avenger_M1097,
], "has_jtac": True
}
PMC_WESTERN_B = {
"country": "USA",
"side": "blue",
"units": [
MB_339PAN,
C_101CC,
UH_1H,
Mi_8MT,
OH_58D,
SA342M,
Armor.APC_M1043_HMMWV_Armament,
Armor.IFV_MCV_80,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Avenger_M1097,
Armed_speedboat,
], "shorad":[
AirDefence.SAM_Avenger_M1097,
], "has_jtac": True,
"requirements": {
"MB-339A": "http://www.freccetricolorivirtuali.net/",
}
}
PMC_RUSSIAN = {
"country": "Russia",
"side": "blue",
"units": [
L_39C,
L_39ZA,
Mi_8MT,
Mi_24V,
Ka_50,
Armor.APC_Cobra,
Armor.APC_BTR_80,
Armor.ARV_BRDM_2,
Unarmed.Transport_Ural_375,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_RPG_16,
AirDefence.AAA_ZU_23_on_Ural_375,
Armed_speedboat,
], "shorad":[
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.AAA_ZU_23_Closed,
], "has_jtac": True
}

View File

@@ -1,38 +0,0 @@
from dcs.planes import MiG_15bis, IL_76MD, IL_78M, An_26B, An_30M, Yak_40
from dcs.ships import CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, Tanker_Elnya_160
from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery
Russia_1955 = {
"country": "Russia",
"side": "red",
"units": [
MiG_15bis,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
AirDefence.AAA_ZU_23_Closed,
AirDefence.AAA_ZU_23_on_Ural_375,
Armor.ARV_BRDM_2,
Armor.FDDM_Grad,
Armor.APC_MTLB,
Armor.MBT_T_55,
Artillery.MLRS_BM_21_Grad,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160,
# Infantry squad
Infantry.Paratrooper_AKS,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_RPG_16,
]
}

View File

@@ -1,55 +0,0 @@
from dcs.helicopters import Mi_8MT
from dcs.planes import MiG_15bis, MiG_19P, MiG_21Bis, IL_76MD, IL_78M, An_26B, An_30M, Yak_40, A_50
from dcs.ships import CV_1143_5_Admiral_Kuznetsov, Bulk_cargo_ship_Yakushev, Dry_cargo_ship_Ivanov, Tanker_Elnya_160
from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery
Russia_1965 = {
"country": "Russia",
"side": "red",
"units": [
MiG_15bis,
MiG_19P,
MiG_21Bis,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
Mi_8MT,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_2_LN_SM_90,
AirDefence.SAM_SA_3_S_125_LN_5P73,
Armor.ARV_BRDM_2,
Armor.APC_BTR_80,
Armor.ARV_BTR_RD,
Armor.IFV_BMD_1,
Armor.IFV_BMP_1,
Armor.MBT_T_55,
Artillery.MLRS_BM_21_Grad,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160,
# Infantry squad
Infantry.Paratrooper_AKS,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_RPG_16,
],
"shorad":[
AirDefence.AAA_ZU_23_Closed
], "boat": [
"GrishaGroupGenerator"
]
}

View File

@@ -1,71 +0,0 @@
from dcs.helicopters import Mi_8MT, Mi_24V
from dcs.planes import MiG_21Bis, MiG_23MLD, MiG_25PD, MiG_29A, Su_17M4, Su_24M, Su_25, IL_76MD, IL_78M, An_26B, An_30M, \
Yak_40, A_50
from dcs.ships import *
from dcs.vehicles import AirDefence, Armor, Unarmed, Infantry, Artillery
Russia_1975 = {
"country": "Russia",
"side": "red",
"units": [
MiG_21Bis,
MiG_23MLD,
MiG_25PD,
MiG_29A,
Su_17M4,
Su_24M,
Su_25,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
Mi_8MT,
Mi_24V,
AirDefence.AAA_ZU_23_Closed,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
Armor.ARV_BRDM_2,
Armor.APC_BTR_80,
Armor.IFV_BMD_1,
Armor.IFV_BMP_1,
Armor.MBT_T_55,
Artillery.SPH_2S9_Nona,
Artillery.SPH_2S1_Gvozdika,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160,
# Infantry squad
Infantry.Paratrooper_AKS,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_RPG_16,
],
"shorad": [
AirDefence.AAA_ZU_23_Emplacement,
AirDefence.SPAAA_ZSU_23_4_Shilka
], "aircraft_carrier": [
CV_1143_5_Admiral_Kuznetsov,
], "destroyer": [
FF_1135M_Rezky,
], "cruiser": [
CGN_1144_2_Pyotr_Velikiy,
], "boat": [
"RussianNavyGroupGenerator", "KiloSubGroupGenerator", "MolniyaGroupGenerator"
]
}

View File

@@ -1,74 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Russia_1990 = {
"country": "Russia",
"side": "red",
"units": [
MiG_23MLD,
MiG_25PD,
MiG_29A,
MiG_29S,
MiG_31,
Su_27,
Su_24M,
Su_25,
Ka_50,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
Mi_8MT,
Mi_24V,
AirDefence.AAA_ZU_23_Closed,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
Armor.ARV_BRDM_2,
Armor.APC_BTR_80,
Armor.IFV_BMD_1,
Armor.IFV_BMP_1,
Armor.MBT_T_55,
Artillery.MLRS_9K57_Uragan_BM_27,
Artillery.SPH_2S19_Msta,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160,
# Infantry squad
Infantry.Paratrooper_AKS,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_RPG_16,
],
"shorad":[
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.SPAAA_ZSU_23_4_Shilka
], "carrier_names": [
"Admiral Kuznetov",
"Admiral Gorshkov"
], "aircraft_carrier": [
CV_1143_5_Admiral_Kuznetsov,
], "destroyer": [
FF_1135M_Rezky,
], "cruiser": [
FSG_1241_1MP_Molniya,
], "boat":[
"RussianNavyGroupGenerator", "KiloSubGroupGenerator"
]
}

View File

@@ -1,77 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Russia_2010 = {
"country": "Russia",
"side": "red",
"units": [
Su_27,
Su_30,
Su_33,
MiG_29S,
MiG_31,
Su_25,
Su_25T,
Su_34,
Su_24M,
L_39ZA,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
Ka_50,
Mi_8MT,
Mi_24V,
Mi_28N,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SAM_SA_11_Buk_LN_9A310M1,
AirDefence.SAM_SA_10_S_300PS_LN_5P85C,
Armor.APC_BTR_80,
Armor.MBT_T_90,
Armor.MBT_T_80U,
Armor.MBT_T_72B,
Armor.IFV_BMP_1,
Armor.IFV_BMP_2,
Armor.IFV_BMP_3,
Artillery.MLRS_9K57_Uragan_BM_27,
Artillery.SPH_2S19_Msta,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160,
# Infantry squad
Infantry.Paratrooper_AKS,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_RPG_16,
],
"shorad":[
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3
], "aircraft_carrier": [
CV_1143_5_Admiral_Kuznetsov,
], "carrier_names": [
"Admiral Kuznetov"
], "destroyer": [
FF_1135M_Rezky,
], "cruiser": [
FSG_1241_1MP_Molniya,
], "boat": [
"RussianNavyGroupGenerator", "KiloSubGroupGenerator"
]
}

View File

@@ -1,50 +0,0 @@
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Spain_1990 = {
"country": "Spain",
"side": "blue",
"units": [
FA_18C_hornet,
AV8BNA,
F_5E_3,
C_101CC,
KC_135,
KC130,
C_130,
E_3A,
Armor.MBT_M60A3_Patton,
Armor.MBT_Leopard_2,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Avenger_M1097,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad":[
AirDefence.SAM_Avenger_M1097,
], "aircraft_carrier": [
CVN_74_John_C__Stennis, # Standing as Principe de Asturias
], "helicopter_carrier": [
LHA_1_Tarawa, # Standing as Juan Carlos
], "destroyer": [
Oliver_Hazzard_Perry_class,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"Principe de Asturias",
], "lhanames": [
"Juan Carlos I",
], "boat":[
"OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,31 +0,0 @@
from dcs.vehicles import *
from dcs.ships import *
from dcs.planes import *
from dcs.helicopters import *
Sweden_1990 = {
"country": "Sweden",
"side": "blue",
"units": [
AJS37,
UH_1H,
AirDefence.SAM_Hawk_PCP,
Armor.IFV_MCV_80, # Standing as Strf 90
Armor.MBT_Leopard_2,
Armor.APC_M1126_Stryker_ICV, # Closest thing available
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Soldier_AK,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160,
],
"shorad": [
AirDefence.SAM_Avenger_M1097
], "has_jtac": True
}

View File

@@ -1,267 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.vehicles import *
Syria_2011 = {
"country": "Syria",
"side": "red",
"units": [
MiG_21Bis,
MiG_23MLD,
MiG_25PD,
MiG_29S,
Su_17M4,
Su_24M,
L_39ZA,
Mi_24V,
Mi_8MT,
SA342M,
SA342L,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_SA_2_LN_SM_90,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_11_Buk_LN_9A310M1,
AirDefence.SAM_SA_10_S_300PS_LN_5P85C,
Armor.IFV_BMP_1,
Armor.IFV_BMP_2,
Armor.APC_BTR_80,
Armor.ARV_BRDM_2,
Armor.APC_MTLB,
Armor.APC_Cobra,
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_90,
Artillery.MLRS_BM_21_Grad,
Artillery.MLRS_9K57_Uragan_BM_27,
Artillery.SPH_2S1_Gvozdika,
Artillery.SPH_2S9_Nona,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Paratrooper_RPG_16,
Infantry.Soldier_AK
],
"shorad": [
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.AAA_ZU_23_on_Ural_375,
], "boat": [
"GrishaGroupGenerator", "MolniyaGroupGenerator"
]
}
Syria_1973 = {
"country": "Syria",
"side": "red",
"units": [
MiG_21Bis,
MiG_19P,
MiG_15bis, # Standing as Mig-17
Su_17M4, # Standing as Su-7
Mi_8MT,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_SA_2_LN_SM_90,
Armor.IFV_BMP_1,
Armor.APC_MTLB,
Armor.MBT_T_55,
Artillery.MLRS_BM_21_Grad,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Paratrooper_RPG_16,
Infantry.Soldier_AK
],
"shorad": [
AirDefence.AAA_ZU_23_on_Ural_375,
], "boat": [
"GrishaGroupGenerator"
]
}
Syria_1982 = {
"country": "Syria",
"side": "red",
"units": [
MiG_21Bis,
MiG_23MLD,
MiG_25PD,
MiG_19P,
Su_17M4, # Standing as Su-7
Mi_8MT,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_SA_2_LN_SM_90,
Armor.IFV_BMP_1,
Armor.APC_MTLB,
Armor.MBT_T_55,
Armor.MBT_T_72B,
Artillery.MLRS_BM_21_Grad,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Paratrooper_RPG_16,
Infantry.Soldier_AK
],
"shorad": [
AirDefence.AAA_ZU_23_on_Ural_375,
], "boat": [
"GrishaGroupGenerator"
]
}
Syria_1967 = {
"country": "Syria",
"side": "red",
"units": [
MiG_21Bis,
MiG_19P,
MiG_15bis, # Standing as Mig-17
Su_17M4, # Standing as Su-7
Mi_8MT,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
AirDefence.SAM_SA_2_LN_SM_90,
Armor.ARV_BRDM_2,
Armor.MBT_T_55,
Artillery.MLRS_BM_21_Grad,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Paratrooper_RPG_16,
Infantry.Soldier_AK
],
"shorad": [
AirDefence.AAA_ZU_23_on_Ural_375,
], "boat": [
"GrishaGroupGenerator"
]
}
Syria_1967_WW2_Weapons = {
"country": "Syria",
"side": "red",
"units": [
MiG_21Bis,
MiG_19P,
MiG_15bis, # Standing as Mig-17
Su_17M4, # Standing as Su-7
Mi_8MT,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
AirDefence.SAM_SA_2_LN_SM_90,
Armor.ARV_BRDM_2,
Armor.MBT_T_55,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
Armor.StuG_III_Ausf__G,
Armor.TD_Jagdpanzer_IV,
Artillery.MLRS_BM_21_Grad,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Soldier_RPG,
Infantry.Soldier_AK
], "requirements": {
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
},
"shorad": [
AirDefence.AAA_ZU_23_on_Ural_375,
], "boat": [
"GrishaGroupGenerator"
]
}
Arab_Armies_1948 = {
"country": "Syria",
"side": "red",
"units": [
SpitfireLFMkIX,
SpitfireLFMkIXCW,
AirDefence.SAM_SA_2_LN_SM_90,
Armor.MT_M4_Sherman,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
Armor.APC_Sd_Kfz_251,
Armor.IFV_Sd_Kfz_234_2_Puma,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Infantry_SMLE_No_4_Mk_1,
AirDefence.AAA_8_8cm_Flak_36,
], "requirements": {
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
},
"shorad": [
AirDefence.AAA_8_8cm_Flak_36,
], "boat": [
"GrishaGroupGenerator"
]
}

View File

@@ -1,41 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Turkey_2005 = {
"country": "Turkey",
"side": "blue",
"units":[
F_16C_50,
F_4E,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
AH_1W,
Armor.MBT_Leopard_2,
Armor.MBT_Leopard_1A3,
Armor.MBT_M60A3_Patton,
Armor.APC_Cobra,
Armor.APC_BTR_80,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.SAM_Avenger_M1097,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad":[
AirDefence.AAA_ZU_23_Emplacement,
AirDefence.SPAAA_ZSU_23_4_Shilka
], "boat":[
"OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,38 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
UAE_2005 = {
"country": "United Arab Emirates",
"side": "blue",
"units":[
M_2000C,
Mirage_2000_5,
F_16C_50,
KC_135,
KC130,
C_130,
E_3A,
AH_64D,
Armor.MBT_Leclerc,
Armor.IFV_BMP_3,
Armor.TPz_Fuchs,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.Rapier_FSA_Launcher,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "boat":[
"OliverHazardPerryGroupGenerator"
],
"has_jtac": True,
"jtac_unit": WingLoong_I
}

View File

@@ -1,44 +0,0 @@
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
from game.data.building_data import WW2_ALLIES_BUILDINGS
from game.data.doctrine import WWII_DOCTRINE
UK_1944 = {
"country": "UK",
"side": "blue",
"units": [
P_51D,
P_51D_30_NA,
P_47D_30,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
A_20G,
B_17G,
Armor.MT_M4A4_Sherman_Firefly,
Armor.MT_M4_Sherman,
Armor.APC_M2A1,
Armor.CT_Cromwell_IV,
Armor.ST_Centaur_IV,
Armor.HIT_Churchill_VII,
Infantry.Infantry_SMLE_No_4_Mk_1,
LS_Samuel_Chase,
LST_Mk_II,
LCVP__Higgins_boat,
Unarmed.CCKW_353,
AirDefence.AAA_Bofors_40mm,
], "shorad":[
AirDefence.AAA_Bofors_40mm,
],"requirements":{
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
},
"objects": WW2_ALLIES_BUILDINGS,
"doctrine": WWII_DOCTRINE,
"boat": ["WW2LSTGroupGenerator"],
"boat_count": 1
}

View File

@@ -1,52 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
UnitedKingdom_1990 = {
"country": "UK",
"side": "blue",
"units":[
AV8BNA, # Standing as BAE Harrier 2
Tornado_GR4,
F_4E,
KC_135,
KC130,
C_130,
E_3A,
SA342M,
AH_64A,
Armor.MBT_Challenger_II,
Armor.IFV_MCV_80,
Armor.APC_M1043_HMMWV_Armament,
Armor.ATGM_M1045_HMMWV_TOW,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.Rapier_FSA_Launcher,
AirDefence.SAM_Avenger_M1097, # Standing as Starstreak
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad":[
AirDefence.SAM_Avenger_M1097,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "destroyer": [
Oliver_Hazzard_Perry_class,
], "cruiser": [
Ticonderoga_class,
], "lhanames": [
"HMS Invincible",
"HMS Illustrious",
"HMS Ark Royal",
], "boat":[
"ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,53 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
Ukraine_2010 = {
"country": "Ukraine",
"side": "blue",
"units": [
Su_25,
Su_25T,
Su_24M,
Su_27,
MiG_29S,
L_39ZA,
IL_76MD,
IL_78M,
An_26B,
An_30M,
Yak_40,
A_50,
Mi_8MT,
Mi_24V,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_SA_11_Buk_LN_9A310M1,
AirDefence.SAM_SA_10_S_300PS_LN_5P85C,
Armor.APC_M1043_HMMWV_Armament,
Armor.IFV_BMP_3,
Armor.IFV_BMP_2,
Armor.APC_BTR_80,
Armor.MBT_T_80U,
Armor.MBT_T_72B,
Unarmed.Transport_Ural_375,
Unarmed.Transport_UAZ_469,
Infantry.Soldier_AK,
CV_1143_5_Admiral_Kuznetsov,
Bulk_cargo_ship_Yakushev,
Dry_cargo_ship_Ivanov,
Tanker_Elnya_160,
],
"shorad":[
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.AAA_ZU_23_on_Ural_375
], "boat":[
"GrishaGroupGenerator"
]
}

View File

@@ -1,72 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
US_Aggressors = {
"country": "USAF Aggressors",
"side": "red",
"units": [
F_15C,
F_5E_3,
FA_18C_hornet,
F_16C_50,
Su_27,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
AH_64D,
Ka_50,
SA342M,
SA342L,
Armor.MBT_M1A2_Abrams,
Armor.MBT_Leopard_2,
Armor.ATGM_M1134_Stryker,
Armor.IFV_M2A2_Bradley,
Armor.APC_M1043_HMMWV_Armament,
Artillery.MLRS_M270,
Artillery.SPH_M109_Paladin,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Patriot_EPP_III,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.SAM_Avenger_M1097,
], "aircraft_carrier": [
CVN_74_John_C__Stennis,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "destroyer": [
Oliver_Hazzard_Perry_class,
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis",
], "lhanames": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
], "boat":[
"ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator"
]
}

View File

@@ -1,86 +0,0 @@
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
from game.data.building_data import WW2_ALLIES_BUILDINGS
from game.data.doctrine import WWII_DOCTRINE
USA_1944 = {
"country": "USA",
"side": "blue",
"units": [
P_51D,
P_51D_30_NA,
P_47D_30,
A_20G,
B_17G,
Armor.MT_M4_Sherman,
Armor.M30_Cargo_Carrier,
Armor.APC_M2A1,
Armor.LAC_M8_Greyhound,
Armor.TD_M10_GMC,
Artillery.M12_GMC,
Infantry.Infantry_M1_Garand,
LS_Samuel_Chase,
LST_Mk_II,
LCVP__Higgins_boat,
Unarmed.CCKW_353,
AirDefence.AAA_Bofors_40mm,
], "shorad":[
AirDefence.AAA_Bofors_40mm,
],"requirements":{
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
},
"objects": WW2_ALLIES_BUILDINGS,
"doctrine": WWII_DOCTRINE,
"boat": ["WW2LSTGroupGenerator"],
"boat_count": 2
}
ALLIES_1944 = {
"country": "USA",
"side": "blue",
"units": [
P_51D,
P_51D_30_NA,
P_47D_30,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
A_20G,
B_17G,
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.CT_Cromwell_IV,
Armor.M30_Cargo_Carrier,
Armor.APC_M2A1,
Armor.CT_Cromwell_IV,
Armor.ST_Centaur_IV,
Armor.HIT_Churchill_VII,
Armor.LAC_M8_Greyhound,
Armor.TD_M10_GMC,
Artillery.M12_GMC,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_SMLE_No_4_Mk_1,
LS_Samuel_Chase,
LST_Mk_II,
LCVP__Higgins_boat,
Unarmed.CCKW_353,
AirDefence.AAA_Bofors_40mm,
], "shorad":[
AirDefence.AAA_Bofors_40mm,
],"requirements":{
"WW2 Asset Pack": "https://www.digitalcombatsimulator.com/en/products/other/wwii_assets_pack/",
},
"objects": WW2_ALLIES_BUILDINGS,
"doctrine": WWII_DOCTRINE,
"boat": ["WW2LSTGroupGenerator"],
"boat_count": 2
}

View File

@@ -1,33 +0,0 @@
from dcs.vehicles import *
from dcs.ships import *
from dcs.planes import *
from dcs.helicopters import *
USA_1955 = {
"country": "USA",
"side": "blue",
"units": [
F_86F_Sabre,
P_51D,
KC_135,
KC130,
C_130,
E_3A,
Armor.MT_M4A4_Sherman_Firefly,
Armor.MT_M4_Sherman,
Armor.MBT_M60A3_Patton,
Armor.APC_M2A1,
Armor.M30_Cargo_Carrier,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
AirDefence.AAA_Bofors_40mm,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
]
}

View File

@@ -1,36 +0,0 @@
from dcs.vehicles import *
from dcs.ships import *
from dcs.planes import *
from dcs.helicopters import *
USA_1960 = {
"country": "USA",
"side": "blue",
"units": [
F_86F_Sabre,
P_51D,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
Armor.MBT_M60A3_Patton,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.AAA_Vulcan_M163,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
],
"shorad":[
AirDefence.AAA_Vulcan_M163
]
}

View File

@@ -1,41 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
USA_1965 = {
"country": "USA",
"side": "blue",
"units": [
F_5E_3,
F_4E,
KC_135,
KC130,
C_130,
E_3A,
B_52H,
UH_1H,
Armor.MBT_M60A3_Patton,
Armor.APC_M113,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Chaparral_M48,
AirDefence.SAM_Hawk_PCP,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
],
"shorad":[
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Chaparral_M48
], "boat":[
]
}

View File

@@ -1,65 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
USA_1990 = {
"country": "USA",
"side": "blue",
"units": [
F_15C,
F_15E,
F_14B,
FA_18C_hornet,
F_16C_50,
A_10A,
AV8BNA,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
AH_64A,
Armor.MBT_M1A2_Abrams,
Armor.IFV_LAV_25,
Armor.APC_M1043_HMMWV_Armament,
Armor.ATGM_M1045_HMMWV_TOW,
Armor.ATGM_M1134_Stryker,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad":[
AirDefence.SAM_Avenger_M1097,
], "aircraft_carrier": [
CVN_74_John_C__Stennis,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "destroyer": [
Oliver_Hazzard_Perry_class,
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"CVN-72 Abraham Lincoln",
"CVN-73 Georges Washington",
"CVN-74 John C. Stennis",
], "lhanames": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
], "boat":[
"ArleighBurkeGroupGenerator", "OliverHazardPerryGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,73 +0,0 @@
from dcs.helicopters import *
from dcs.planes import *
from dcs.ships import *
from dcs.vehicles import *
USA_2005 = {
"country": "USA",
"side": "blue",
"units": [
F_15C,
F_15E,
F_14B,
FA_18C_hornet,
F_16C_50,
A_10C,
A_10C_2,
AV8BNA,
MQ_9_Reaper,
KC_135,
KC130,
C_130,
E_3A,
UH_1H,
AH_64D,
Armor.MBT_M1A2_Abrams,
Armor.ATGM_M1134_Stryker,
Armor.APC_M1126_Stryker_ICV,
Armor.IFV_M2A2_Bradley,
Armor.IFV_LAV_25,
Armor.APC_M1043_HMMWV_Armament,
Armor.ATGM_M1045_HMMWV_TOW,
Artillery.MLRS_M270,
Artillery.SPH_M109_Paladin,
Unarmed.Transport_M818,
Infantry.Infantry_M4,
Infantry.Soldier_M249,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_Patriot_EPP_III,
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
Armed_speedboat,
], "shorad": [
AirDefence.SAM_Avenger_M1097,
], "aircraft_carrier": [
CVN_74_John_C__Stennis,
], "helicopter_carrier": [
LHA_1_Tarawa,
], "destroyer": [
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 George Washington",
"CVN-74 John C. Stennis",
], "lhanames": [
"LHA-1 Tarawa",
"LHA-2 Saipan",
"LHA-3 Belleau Wood",
"LHA-4 Nassau",
"LHA-5 Peleliu"
], "boat":[
"ArleighBurkeGroupGenerator"
], "has_jtac": True
}

View File

@@ -1,11 +1,41 @@
from datetime import datetime, timedelta
import itertools
import logging
import random
import sys
from datetime import date, datetime, timedelta
from enum import Enum
from typing import Any, Dict, List
from game.db import REWARDS, PLAYER_BUDGET_BASE, sys
from dcs.action import Coalition
from dcs.mapping import Point
from dcs.task import CAP, CAS, PinpointStrike
from dcs.vehicles import AirDefence
from game import db
from game.inventory import GlobalAircraftInventory
from game.models.game_stats import GameStats
from gen.flights.ai_flight_planner import FlightPlanner
from game.plugins import LuaPluginManager
from gen.ato import AirTaskingOrder
from gen.conflictgen import Conflict
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import FlightType
from gen.ground_forces.ai_ground_planner import GroundPlanner
from .event import *
from . import persistency
from .debriefing import Debriefing
from .event.event import Event, UnitsDeliveryEvent
from .event.frontlineattack import FrontlineAttackEvent
from .factions.faction import Faction
from .income import Income
from .infos.information import Information
from .navmesh import NavMesh
from .procurement import ProcurementAi
from .settings import Settings
from .theater import ConflictTheater, ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import MissileSiteGroundObject
from .threatzones import ThreatZones
from .unitmap import UnitMap
from .weather import Conditions, TimeOfDay
COMMISION_UNIT_VARIETY = 4
COMMISION_LIMITS_SCALE = 1.5
@@ -37,49 +67,93 @@ ENEMY_BASE_STRENGTH_RECOVERY = 0.05
# cost of AWACS for single operation
AWACS_BUDGET_COST = 4
# Initial budget value
PLAYER_BUDGET_INITIAL = 650
# Bonus multiplier logarithm base
PLAYER_BUDGET_IMPORTANCE_LOG = 2
class TurnState(Enum):
WIN = 0
LOSS = 1
CONTINUE = 2
class Game:
settings = None # type: Settings
budget = PLAYER_BUDGET_INITIAL
events = None # type: typing.List[Event]
pending_transfers = None # type: typing.Dict[]
ignored_cps = None # type: typing.Collection[ControlPoint]
turn = 0
game_stats: GameStats = None
current_unit_id = 0
current_group_id = 0
def __init__(self, player_name: str, enemy_name: str, theater: ConflictTheater, start_date: datetime, settings):
def __init__(
self,
player_name: str,
enemy_name: str,
theater: ConflictTheater,
start_date: datetime,
settings: Settings,
player_budget: float,
enemy_budget: float,
) -> None:
self.settings = settings
self.events = []
self.events: List[Event] = []
self.theater = theater
self.player_name = player_name
self.player_country = db.FACTIONS[player_name]["country"]
self.player_country = db.FACTIONS[player_name].country
self.enemy_name = enemy_name
self.enemy_country = db.FACTIONS[enemy_name]["country"]
self.enemy_country = db.FACTIONS[enemy_name].country
self.turn = 0
self.date = datetime(start_date.year, start_date.month, start_date.day)
self.date = date(start_date.year, start_date.month, start_date.day)
self.game_stats = GameStats()
self.game_stats.update(self)
self.planners = {}
self.ground_planners = {}
self.ground_planners: Dict[int, GroundPlanner] = {}
self.informations = []
self.informations.append(Information("Game Start", "-" * 40, 0))
self.__culling_points = self.compute_conflicts_position()
self.__frontlineData = []
self.__destroyed_units = []
self.jtacs = []
# Culling Zones are for areas around points of interest that contain things we may not wish to cull.
self.__culling_zones: List[Point] = []
# Culling Points are for individual theater ground objects that we don't wish to cull.
self.__culling_points: List[Point] = []
self.__destroyed_units: List[str] = []
self.savepath = ""
self.budget = player_budget
self.enemy_budget = enemy_budget
self.current_unit_id = 0
self.current_group_id = 0
self.conditions = self.generate_conditions()
self.blue_ato = AirTaskingOrder()
self.red_ato = AirTaskingOrder()
self.aircraft_inventory = GlobalAircraftInventory(self.theater.controlpoints)
self.sanitize_sides()
self.on_load()
# Turn 0 procurement. We don't actually have any missions to plan, but
# the planner will tell us what it would like to plan so we can use that
# to drive purchase decisions.
blue_planner = CoalitionMissionPlanner(self, is_player=True)
blue_planner.plan_missions()
red_planner = CoalitionMissionPlanner(self, is_player=False)
red_planner.plan_missions()
self.plan_procurement(blue_planner, red_planner)
def __getstate__(self) -> Dict[str, Any]:
state = self.__dict__.copy()
# Avoid persisting any volatile types that can be deterministically
# recomputed on load for the sake of save compatibility.
del state["blue_threat_zone"]
del state["red_threat_zone"]
del state["blue_navmesh"]
del state["red_navmesh"]
return state
def __setstate__(self, state: Dict[str, Any]) -> None:
self.__dict__.update(state)
# Regenerate any state that was not persisted.
self.on_load()
def generate_conditions(self) -> Conditions:
return Conditions.generate(
self.theater, self.date, self.current_turn_time_of_day, self.settings
)
def sanitize_sides(self):
"""
@@ -95,13 +169,18 @@ class Game:
self.enemy_country = "Russia"
@property
def player_faction(self):
def player_faction(self) -> Faction:
return db.FACTIONS[self.player_name]
@property
def enemy_faction(self):
def enemy_faction(self) -> Faction:
return db.FACTIONS[self.enemy_name]
def faction_for(self, player: bool) -> Faction:
if player:
return self.player_faction
return self.enemy_faction
def _roll(self, prob, mult):
if self.settings.version == "dev":
# always generate all events for dev
@@ -110,78 +189,48 @@ class Game:
return random.randint(1, 100) <= prob * mult
def _generate_player_event(self, event_class, player_cp, enemy_cp):
self.events.append(event_class(self, player_cp, enemy_cp, enemy_cp.position, self.player_name, self.enemy_name))
self.events.append(
event_class(
self,
player_cp,
enemy_cp,
enemy_cp.position,
self.player_name,
self.enemy_name,
)
)
def _generate_events(self):
for player_cp, enemy_cp in self.theater.conflicts(True):
self._generate_player_event(FrontlineAttackEvent, player_cp, enemy_cp)
for front_line in self.theater.conflicts(True):
self._generate_player_event(
FrontlineAttackEvent,
front_line.control_point_a,
front_line.control_point_b,
)
def commision_unit_types(self, cp: ControlPoint, for_task: Task) -> typing.Collection[UnitType]:
importance_factor = (cp.importance - IMPORTANCE_LOW) / (IMPORTANCE_HIGH - IMPORTANCE_LOW)
if for_task == AirDefence and not self.settings.sams:
return [x for x in db.find_unittype(AirDefence, self.enemy_name) if x not in db.SAM_BAN]
def adjust_budget(self, amount: float, player: bool) -> None:
if player:
self.budget += amount
else:
return db.choose_units(for_task, importance_factor, COMMISION_UNIT_VARIETY, self.enemy_name)
self.enemy_budget += amount
def _commision_units(self, cp: ControlPoint):
for for_task in [CAS, CAP, AirDefence]:
limit = COMMISION_LIMITS_FACTORS[for_task] * math.pow(cp.importance,
COMMISION_LIMITS_SCALE) * self.settings.multiplier
missing_units = limit - cp.base.total_units(for_task)
if missing_units > 0:
awarded_points = COMMISION_AMOUNTS_FACTORS[for_task] * math.pow(cp.importance,
COMMISION_AMOUNTS_SCALE) * self.settings.multiplier
points_to_spend = cp.base.append_commision_points(for_task, awarded_points)
if points_to_spend > 0:
unittypes = self.commision_unit_types(cp, for_task)
if len(unittypes) > 0:
d = {random.choice(unittypes): points_to_spend}
logging.info("Commision {}: {}".format(cp, d))
cp.base.commision_units(d)
def process_player_income(self):
self.budget += Income(self, player=True).total
@property
def budget_reward_amount(self):
reward = 0
if len(self.theater.player_points()) > 0:
reward = PLAYER_BUDGET_BASE * len(self.theater.player_points())
for cp in self.theater.player_points():
for g in cp.ground_objects:
if g.category in REWARDS.keys():
reward = reward + REWARDS[g.category]
return reward
else:
return reward
def process_enemy_income(self):
# TODO: Clean up save compat.
if not hasattr(self, "enemy_budget"):
self.enemy_budget = 0
self.enemy_budget += Income(self, player=False).total
def _budget_player(self):
self.budget += self.budget_reward_amount
def awacs_expense_commit(self):
self.budget -= AWACS_BUDGET_COST
def units_delivery_event(self, to_cp: ControlPoint) -> UnitsDeliveryEvent:
event = UnitsDeliveryEvent(attacker_name=self.player_name,
defender_name=self.player_name,
from_cp=to_cp,
to_cp=to_cp,
game=self)
self.events.append(event)
return event
def units_delivery_remove(self, event: Event):
if event in self.events:
self.events.remove(event)
def initiate_event(self, event: Event):
#assert event in self.events
def initiate_event(self, event: Event) -> UnitMap:
# assert event in self.events
logging.info("Generating {} (regular)".format(event))
event.generate()
return event.generate()
def finish_event(self, event: Event, debriefing: Debriefing):
logging.info("Finishing event {}".format(event))
event.commit(debriefing)
if event.is_successfull(debriefing):
self.budget += event.bonus()
if event in self.events:
self.events.remove(event)
@@ -190,26 +239,33 @@ class Game:
def is_player_attack(self, event):
if isinstance(event, Event):
return event and event.attacker_name and event.attacker_name == self.player_name
return (
event
and event.attacker_name
and event.attacker_name == self.player_name
)
else:
return event and event.name and event.name == self.player_name
raise RuntimeError(f"{event} was passed when an Event type was expected")
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
def on_load(self) -> None:
LuaPluginManager.load_settings(self.settings)
ObjectiveDistanceCache.set_theater(self.theater)
self.compute_conflicts_position()
self.compute_threat_zones()
def pass_turn(self, no_action: bool = False) -> None:
logging.info("Pass turn")
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
self.turn = self.turn + 1
self.informations.append(
Information("End of turn #" + str(self.turn), "-" * 40, 0)
)
self.turn += 1
for event in self.events:
if self.settings.version == "dev":
# don't damage player CPs in by skipping in dev mode
if isinstance(event, UnitsDeliveryEvent):
event.skip()
else:
event.skip()
for control_point in self.theater.controlpoints:
control_point.process_turn(self)
self._enemy_reinforcement()
self._budget_player()
self.process_enemy_income()
self.process_player_income()
if not no_action and self.turn > 1:
for cp in self.theater.player_points():
@@ -219,111 +275,98 @@ class Game:
if not cp.is_carrier and not cp.is_lha:
cp.base.affect_strength(-PLAYER_BASE_STRENGTH_RECOVERY)
self.ignored_cps = []
if ignored_cps:
self.ignored_cps = ignored_cps
self.conditions = self.generate_conditions()
self.events = [] # type: typing.List[Event]
self.initialize_turn()
# Autosave progress
persistency.autosave(self)
def check_win_loss(self):
captured_states = {i.captured for i in self.theater.controlpoints}
if True not in captured_states:
return TurnState.LOSS
if False not in captured_states:
return TurnState.WIN
return TurnState.CONTINUE
def initialize_turn(self) -> None:
self.events = []
self._generate_events()
# Update statistics
self.game_stats.update(self)
# Plan flights & combat for next turn
self.__culling_points = self.compute_conflicts_position()
self.planners = {}
self.ground_planners = {}
self.aircraft_inventory.reset()
for cp in self.theater.controlpoints:
if cp.has_runway():
planner = FlightPlanner(cp, self)
planner.plan_flights()
self.planners[cp.id] = planner
self.aircraft_inventory.set_from_control_point(cp)
# Check for win or loss condition
turn_state = self.check_win_loss()
if turn_state in (TurnState.LOSS, TurnState.WIN):
return self.process_win_loss(turn_state)
# Plan flights & combat for next turn
self.compute_conflicts_position()
self.compute_threat_zones()
self.ground_planners = {}
self.blue_ato.clear()
self.red_ato.clear()
blue_planner = CoalitionMissionPlanner(self, is_player=True)
blue_planner.plan_missions()
red_planner = CoalitionMissionPlanner(self, is_player=False)
red_planner.plan_missions()
for cp in self.theater.controlpoints:
if cp.has_frontline:
gplanner = GroundPlanner(cp, self)
gplanner.plan_groundwar()
self.ground_planners[cp.id] = gplanner
# Autosave progress
persistency.autosave(self)
self.plan_procurement(blue_planner, red_planner)
def _enemy_reinforcement(self):
"""
Compute and commision reinforcement for enemy bases
"""
def plan_procurement(
self,
blue_planner: CoalitionMissionPlanner,
red_planner: CoalitionMissionPlanner,
) -> None:
# The first turn needs to buy a *lot* of aircraft to fill CAPs, so it
# gets much more of the budget that turn. Otherwise budget (after
# repairs) is split evenly between air and ground. For the default
# starting budget of 2000 this gives 600 to ground forces and 1400 to
# aircraft.
ground_portion = 0.3 if self.turn == 0 else 0.5
self.budget = ProcurementAi(
self,
for_player=True,
faction=self.player_faction,
manage_runways=self.settings.automate_runway_repair,
manage_front_line=self.settings.automate_front_line_reinforcements,
manage_aircraft=self.settings.automate_aircraft_reinforcements,
front_line_budget_share=ground_portion,
).spend_budget(self.budget, blue_planner.procurement_requests)
MAX_ARMOR = 30 * self.settings.multiplier
MAX_AIRCRAFT = 25 * self.settings.multiplier
self.enemy_budget = ProcurementAi(
self,
for_player=False,
faction=self.enemy_faction,
manage_runways=True,
manage_front_line=True,
manage_aircraft=True,
front_line_budget_share=ground_portion,
).spend_budget(self.enemy_budget, red_planner.procurement_requests)
production = 0.0
for enemy_point in self.theater.enemy_points():
for g in enemy_point.ground_objects:
if g.category in REWARDS.keys():
production = production + REWARDS[g.category]
production = production * 0.75
budget_for_armored_units = production / 2
budget_for_aircraft = production / 2
potential_cp_armor = []
for cp in self.theater.enemy_points():
for cpe in cp.connected_points:
if cpe.captured and cp.base.total_armor < MAX_ARMOR:
potential_cp_armor.append(cp)
if len(potential_cp_armor) == 0:
potential_cp_armor = self.theater.enemy_points()
i = 0
potential_units = [u for u in db.FACTIONS[self.enemy_name]["units"] if u in db.UNIT_BY_TASK[PinpointStrike]]
print("Enemy Recruiting")
print(potential_cp_armor)
print(budget_for_armored_units)
print(potential_units)
if len(potential_units) > 0 and len(potential_cp_armor) > 0:
while budget_for_armored_units > 0:
i = i + 1
if i > 50 or budget_for_armored_units <= 0:
break
target_cp = random.choice(potential_cp_armor)
if target_cp.base.total_armor >= MAX_ARMOR:
continue
unit = random.choice(potential_units)
price = db.PRICES[unit] * 2
budget_for_armored_units -= price * 2
target_cp.base.armor[unit] = target_cp.base.armor.get(unit, 0) + 2
info = Information("Enemy Reinforcement", unit.id + " x 2 at " + target_cp.name, self.turn)
print(str(info))
self.informations.append(info)
if budget_for_armored_units > 0:
budget_for_aircraft += budget_for_armored_units
potential_units = [u for u in db.FACTIONS[self.enemy_name]["units"] if
u in db.UNIT_BY_TASK[CAS] or u in db.UNIT_BY_TASK[CAP]]
if len(potential_units) > 0 and len(potential_cp_armor) > 0:
while budget_for_aircraft > 0:
i = i + 1
if i > 50 or budget_for_aircraft <= 0:
break
target_cp = random.choice(potential_cp_armor)
if target_cp.base.total_planes >= MAX_AIRCRAFT:
continue
unit = random.choice(potential_units)
price = db.PRICES[unit] * 2
budget_for_aircraft -= price * 2
target_cp.base.aircraft[unit] = target_cp.base.aircraft.get(unit, 0) + 2
info = Information("Enemy Reinforcement", unit.id + " x 2 at " + target_cp.name, self.turn)
print(str(info))
self.informations.append(info)
def message(self, text: str) -> None:
self.informations.append(Information(text, turn=self.turn))
@property
def current_turn_daytime(self):
return ["dawn", "day", "dusk", "night"][self.turn % 4]
def current_turn_time_of_day(self) -> TimeOfDay:
return list(TimeOfDay)[self.turn % 4]
@property
def current_day(self):
def current_day(self) -> date:
return self.date + timedelta(days=self.turn // 4)
def next_unit_id(self):
@@ -340,21 +383,56 @@ class Game:
self.current_group_id += 1
return self.current_group_id
def compute_threat_zones(self) -> None:
self.blue_threat_zone = ThreatZones.for_faction(self, player=True)
self.red_threat_zone = ThreatZones.for_faction(self, player=False)
self.blue_navmesh = NavMesh.from_threat_zones(
self.red_threat_zone, self.theater
)
self.red_navmesh = NavMesh.from_threat_zones(
self.blue_threat_zone, self.theater
)
def threat_zone_for(self, player: bool) -> ThreatZones:
if player:
return self.blue_threat_zone
return self.red_threat_zone
def navmesh_for(self, player: bool) -> NavMesh:
if player:
return self.blue_navmesh
return self.red_navmesh
def compute_conflicts_position(self):
"""
Compute the current conflict center position(s), mainly used for culling calculation
:return: List of points of interests
"""
zones = []
points = []
# By default, use the existing frontline conflict position
for conflict in self.theater.conflicts():
points.append(Conflict.frontline_position(self.theater, conflict[0], conflict[1])[0])
points.append(conflict[0].position)
points.append(conflict[1].position)
for front_line in self.theater.conflicts():
position = Conflict.frontline_position(
front_line.control_point_a, front_line.control_point_b, self.theater
)
zones.append(position[0])
zones.append(front_line.control_point_a.position)
zones.append(front_line.control_point_b.position)
for cp in self.theater.controlpoints:
# Don't cull missile sites - their range is long enough to make them
# easily culled despite being a threat.
for tgo in cp.ground_objects:
if isinstance(tgo, MissileSiteGroundObject):
points.append(tgo.position)
# If do_not_cull_carrier is enabled, add carriers as culling point
if self.settings.perf_do_not_cull_carrier:
if cp.is_carrier or cp.is_lha:
zones.append(cp.position)
# If there is no conflict take the center point between the two nearest opposing bases
if len(points) == 0:
if len(zones) == 0:
cpoint = None
min_distance = sys.maxsize
for cp in self.theater.player_points():
@@ -362,21 +440,36 @@ class Game:
d = cp.position.distance_to_point(cp2.position)
if d < min_distance:
min_distance = d
cpoint = Point((cp.position.x + cp2.position.x) / 2, (cp.position.y + cp2.position.y) / 2)
points.append(cp.position)
points.append(cp2.position)
cpoint = Point(
(cp.position.x + cp2.position.x) / 2,
(cp.position.y + cp2.position.y) / 2,
)
zones.append(cp.position)
zones.append(cp2.position)
break
if cpoint is not None:
break
if cpoint is not None:
points.append(cpoint)
zones.append(cpoint)
packages = itertools.chain(self.blue_ato.packages, self.red_ato.packages)
for package in packages:
if package.primary_task is FlightType.BARCAP:
# BARCAPs will be planned at most locations on smaller theaters,
# rendering culling fairly useless. BARCAP packages don't really
# need the ground detail since they're defensive. SAMs nearby
# are only interesting if there are enemies in the area, and if
# there are they won't be culled because of the enemy's mission.
continue
zones.append(package.target.position)
# Else 0,0, since we need a default value
# (in this case this means the whole map is owned by the same player, so it is not an issue)
if len(points) == 0:
points.append(Point(0, 0))
if len(zones) == 0:
zones.append(Point(0, 0))
return points
self.__culling_zones = zones
self.__culling_points = points
def add_destroyed_units(self, data):
pos = Point(data["x"], data["z"])
@@ -395,11 +488,31 @@ class Game:
if self.settings.perf_culling == False:
return False
else:
for c in self.__culling_points:
if c.distance_to_point(pos) < self.settings.perf_culling_distance * 1000:
for z in self.__culling_zones:
if (
z.distance_to_point(pos)
< self.settings.perf_culling_distance * 1000
):
return False
for p in self.__culling_points:
if p.distance_to_point(pos) < 2500:
return False
return True
def get_culling_zones(self):
"""
Check culling points
:return: List of culling zones
"""
return self.__culling_zones
def get_culling_points(self):
"""
Check culling points
:return: List of culling points
"""
return self.__culling_points
# 1 = red, 2 = blue
def get_player_coalition_id(self):
return 2
@@ -408,13 +521,23 @@ class Game:
return 1
def get_player_coalition(self):
return dcs.action.Coalition.Blue
return Coalition.Blue
def get_enemy_coalition(self):
return dcs.action.Coalition.Red
return Coalition.Red
def get_player_color(self):
return "blue"
def get_enemy_color(self):
return "red"
return "red"
def process_win_loss(self, turn_state: TurnState):
if turn_state is TurnState.WIN:
return self.message(
"Congratulations, you are victorious! Start a new campaign to continue."
)
elif turn_state is TurnState.LOSS:
return self.message(
"Game Over, you lose. Start a new campaign to continue."
)

55
game/income.py Normal file
View File

@@ -0,0 +1,55 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from game.db import REWARDS
if TYPE_CHECKING:
from game import Game
@dataclass(frozen=True)
class BuildingIncome:
name: str
category: str
number: int
income_per_building: int
@property
def income(self) -> int:
return self.number * self.income_per_building
class Income:
def __init__(self, game: Game, player: bool) -> None:
if player:
self.multiplier = game.settings.player_income_multiplier
else:
self.multiplier = game.settings.enemy_income_multiplier
self.control_points = []
self.buildings = []
names = set()
for cp in game.theater.control_points_for(player):
if cp.income_per_turn:
self.control_points.append(cp)
for tgo in cp.ground_objects:
names.add(tgo.obj_name)
for name in names:
count = 0
tgos = game.theater.find_ground_objects_by_obj_name(name)
category = tgos[0].category
if category not in REWARDS:
continue
for tgo in tgos:
if not tgo.is_dead:
count += 1
self.buildings.append(
BuildingIncome(name, category, count, REWARDS[category])
)
self.from_bases = sum(cp.income_per_turn for cp in self.control_points)
self.total_buildings = sum(b.income for b in self.buildings)
self.total = (self.total_buildings + self.from_bases) * self.multiplier

View File

@@ -1,11 +1,19 @@
import datetime
class Information():
class Information:
def __init__(self, title="", text="", turn=0):
self.title = title
self.text = text
self.turn = turn
self.timestamp = datetime.datetime.now()
def __str__(self):
s = "[" + str(self.turn) + "] " + self.title + "\n" + self.text
return s
return "[{}][{}] {} {}".format(
self.timestamp.strftime("%Y-%m-%d %H:%M:%S")
if self.timestamp is not None
else "",
self.turn,
self.title,
self.text,
)

130
game/inventory.py Normal file
View File

@@ -0,0 +1,130 @@
"""Inventory management APIs."""
from __future__ import annotations
from collections import defaultdict
from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING, Type
from dcs.unittype import FlyingType
from gen.flights.flight import Flight
if TYPE_CHECKING:
from game.theater import ControlPoint
class ControlPointAircraftInventory:
"""Aircraft inventory for a single control point."""
def __init__(self, control_point: ControlPoint) -> None:
self.control_point = control_point
self.inventory: Dict[Type[FlyingType], int] = defaultdict(int)
def add_aircraft(self, aircraft: Type[FlyingType], count: int) -> None:
"""Adds aircraft to the inventory.
Args:
aircraft: The type of aircraft to add.
count: The number of aircraft to add.
"""
self.inventory[aircraft] += count
def remove_aircraft(self, aircraft: Type[FlyingType], count: int) -> None:
"""Removes aircraft from the inventory.
Args:
aircraft: The type of aircraft to remove.
count: The number of aircraft to remove.
Raises:
ValueError: The control point cannot fulfill the requested number of
aircraft.
"""
available = self.inventory[aircraft]
if available < count:
raise ValueError(
f"Cannot remove {count} {aircraft.id} from "
f"{self.control_point.name}. Only have {available}."
)
self.inventory[aircraft] -= count
def available(self, aircraft: Type[FlyingType]) -> int:
"""Returns the number of available aircraft of the given type.
Args:
aircraft: The type of aircraft to query.
"""
try:
return self.inventory[aircraft]
except KeyError:
return 0
@property
def types_available(self) -> Iterator[Type[FlyingType]]:
"""Iterates over all available aircraft types."""
for aircraft, count in self.inventory.items():
if count > 0:
yield aircraft
@property
def all_aircraft(self) -> Iterator[Tuple[Type[FlyingType], int]]:
"""Iterates over all available aircraft types, including amounts."""
for aircraft, count in self.inventory.items():
if count > 0:
yield aircraft, count
def clear(self) -> None:
"""Clears all aircraft from the inventory."""
self.inventory.clear()
class GlobalAircraftInventory:
"""Game-wide aircraft inventory."""
def __init__(self, control_points: Iterable[ControlPoint]) -> None:
self.inventories: Dict[ControlPoint, ControlPointAircraftInventory] = {
cp: ControlPointAircraftInventory(cp) for cp in control_points
}
def reset(self) -> None:
"""Clears all control points and their inventories."""
for inventory in self.inventories.values():
inventory.clear()
def set_from_control_point(self, control_point: ControlPoint) -> None:
"""Set the control point's aircraft inventory.
If the inventory for the given control point has already been set for
the turn, it will be overwritten.
"""
inventory = self.inventories[control_point]
for aircraft, count in control_point.base.aircraft.items():
inventory.add_aircraft(aircraft, count)
def for_control_point(
self, control_point: ControlPoint
) -> ControlPointAircraftInventory:
"""Returns the inventory specific to the given control point."""
return self.inventories[control_point]
@property
def available_types_for_player(self) -> Iterator[Type[FlyingType]]:
"""Iterates over all aircraft types available to the player."""
seen: Set[Type[FlyingType]] = set()
for control_point, inventory in self.inventories.items():
if control_point.captured:
for aircraft in inventory.types_available:
if not control_point.can_operate(aircraft):
continue
if aircraft not in seen:
seen.add(aircraft)
yield aircraft
def claim_for_flight(self, flight: Flight) -> None:
"""Removes aircraft from the inventory for the given flight."""
inventory = self.for_control_point(flight.from_cp)
inventory.remove_aircraft(flight.unit_type, flight.count)
def return_from_flight(self, flight: Flight) -> None:
"""Returns a flight's aircraft to the inventory."""
inventory = self.for_control_point(flight.from_cp)
inventory.add_aircraft(flight.unit_type, flight.count)

View File

@@ -7,8 +7,7 @@ class DestroyedUnit:
y: int
name: str
def __init__(self, x , y, name):
def __init__(self, x, y, name):
self.x = x
self.y = y
self.name = name

View File

@@ -1,4 +1,4 @@
from theater import ControlPoint
from game.theater import ControlPoint
class FrontlineData:
@@ -6,7 +6,7 @@ class FrontlineData:
This Data structure will store information about an existing frontline
"""
def __init__(self, from_cp:ControlPoint, to_cp: ControlPoint):
def __init__(self, from_cp: ControlPoint, to_cp: ControlPoint):
self.to_cp = to_cp
self.from_cp = from_cp
self.enemy_units_position = []

View File

@@ -1,3 +1,6 @@
from typing import List
class FactionTurnMetadata:
"""
Store metadata about a faction
@@ -18,8 +21,8 @@ class GameTurnMetadata:
Store metadata about a game turn
"""
allied_units:FactionTurnMetadata
enemy_units:FactionTurnMetadata
allied_units: FactionTurnMetadata
enemy_units: FactionTurnMetadata
def __init__(self):
self.allied_units = FactionTurnMetadata()
@@ -31,10 +34,8 @@ class GameStats:
Store statistics for the current game
"""
data_per_turn: [GameTurnMetadata] = []
def __init__(self):
self.data_per_turn = []
self.data_per_turn: List[GameTurnMetadata] = []
def update(self, game):
"""
@@ -53,4 +54,3 @@ class GameStats:
turn_data.enemy_units.vehicles_count += sum(cp.base.armor.values())
self.data_per_turn.append(turn_data)

267
game/navmesh.py Normal file
View File

@@ -0,0 +1,267 @@
from __future__ import annotations
import heapq
import math
from collections import defaultdict
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Set, Tuple, Union
from dcs.mapping import Point
from shapely.geometry import (
LineString,
MultiPolygon,
Point as ShapelyPoint,
Polygon,
box,
)
from shapely.ops import nearest_points, triangulate
from game.theater import ConflictTheater
from game.threatzones import ThreatZones
from game.utils import nautical_miles
class NavMeshPoly:
def __init__(self, ident: int, poly: Polygon, threatened: bool) -> None:
self.ident = ident
self.poly = poly
self.threatened = threatened
self.neighbors: Dict[NavMeshPoly, Union[LineString, ShapelyPoint]] = {}
def __eq__(self, other: object) -> bool:
if not isinstance(other, NavMeshPoly):
return False
return self.ident == other.ident
def __hash__(self) -> int:
return self.ident
@dataclass(frozen=True)
class NavPoint:
point: ShapelyPoint
poly: NavMeshPoly
@property
def world_point(self) -> Point:
return Point(self.point.x, self.point.y)
def __hash__(self) -> int:
return hash(self.poly.ident)
def __eq__(self, other: object) -> bool:
if id(self) == id(other):
return True
if not isinstance(other, NavPoint):
return False
if not self.point.almost_equals(other.point):
return False
return self.poly == other.poly
def __str__(self) -> str:
return f"{self.point} in {self.poly.ident}"
@dataclass(frozen=True, order=True)
class FrontierNode:
cost: float
point: NavPoint = field(compare=False)
class NavFrontier:
def __init__(self) -> None:
self.nodes: List[FrontierNode] = []
def push(self, poly: NavPoint, cost: float) -> None:
heapq.heappush(self.nodes, FrontierNode(cost, poly))
def pop(self) -> Optional[NavPoint]:
try:
return heapq.heappop(self.nodes).point
except IndexError:
return None
class NavMesh:
def __init__(self, polys: List[NavMeshPoly]) -> None:
self.polys = polys
def localize(self, point: Point) -> Optional[NavMeshPoly]:
# This is a naive implementation but it's O(n). Runs at about 10k
# lookups a second on a 5950X. Flights usually have 5-10 waypoints, so
# that's 1k-2k flights before we lose a full second to localization as a
# part of flight plan creation.
#
# Can improve the algorithm later if needed, but that seems unnecessary
# currently.
p = ShapelyPoint(point.x, point.y)
for navpoly in self.polys:
if navpoly.poly.contains(p):
return navpoly
return None
@staticmethod
def travel_cost(a: NavPoint, b: NavPoint) -> float:
modifier = 1.0
if a.poly.threatened:
modifier = 3.0
return a.point.distance(b.point) * modifier
def travel_heuristic(self, a: NavPoint, b: NavPoint) -> float:
return self.travel_cost(a, b)
@staticmethod
def reconstruct_path(
came_from: Dict[NavPoint, Optional[NavPoint]],
origin: NavPoint,
destination: NavPoint,
) -> List[Point]:
current = destination
path: List[Point] = []
while current != origin:
path.append(current.world_point)
previous = came_from[current]
if previous is None:
raise RuntimeError(
f"Could not reconstruct path to {destination} from {origin}"
)
current = previous
path.append(origin.world_point)
path.reverse()
return path
@staticmethod
def dcs_to_shapely_point(point: Point) -> ShapelyPoint:
return ShapelyPoint(point.x, point.y)
def shortest_path(self, origin: Point, destination: Point) -> List[Point]:
origin_poly = self.localize(origin)
if origin_poly is None:
raise ValueError(f"Origin point {origin} is outside the navmesh")
destination_poly = self.localize(destination)
if destination_poly is None:
raise ValueError(f"Origin point {destination} is outside the navmesh")
return self._shortest_path(
NavPoint(self.dcs_to_shapely_point(origin), origin_poly),
NavPoint(self.dcs_to_shapely_point(destination), destination_poly),
)
def _shortest_path(self, origin: NavPoint, destination: NavPoint) -> List[Point]:
# Adapted from
# https://www.redblobgames.com/pathfinding/a-star/implementation.py.
frontier = NavFrontier()
frontier.push(origin, 0.0)
came_from: Dict[NavPoint, Optional[NavPoint]] = {origin: None}
best_known: Dict[NavPoint, float] = defaultdict(lambda: math.inf)
best_known[origin] = 0.0
while (current := frontier.pop()) is not None:
if current == destination:
break
if current.poly == destination.poly:
# Made it to the correct nav poly. Add the leg from the border
# to the target.
cost = best_known[current] + self.travel_cost(current, destination)
if cost < best_known[destination]:
best_known[destination] = cost
estimated = cost
frontier.push(destination, estimated)
came_from[destination] = current
for neighbor, boundary in current.poly.neighbors.items():
previous = came_from[current]
if previous is not None and previous.poly == neighbor:
# Don't backtrack.
continue
if previous is None and current != origin:
raise RuntimeError
_, neighbor_point = nearest_points(current.point, boundary)
neighbor_nav = NavPoint(neighbor_point, neighbor)
cost = best_known[current] + self.travel_cost(current, neighbor_nav)
if cost < best_known[neighbor_nav]:
best_known[neighbor_nav] = cost
estimated = cost + self.travel_heuristic(neighbor_nav, destination)
frontier.push(neighbor_nav, estimated)
came_from[neighbor_nav] = current
return self.reconstruct_path(came_from, origin, destination)
@staticmethod
def map_bounds(theater: ConflictTheater) -> Polygon:
points = []
for cp in theater.controlpoints:
points.append(ShapelyPoint(cp.position.x, cp.position.y))
for tgo in cp.ground_objects:
points.append(ShapelyPoint(tgo.position.x, tgo.position.y))
# Needs to be a large enough boundary beyond the known points so that
# threatened airbases at the map edges have room to retreat from the
# threat without running off the navmesh.
return box(*LineString(points).bounds).buffer(
nautical_miles(100).meters, resolution=1
)
@staticmethod
def create_navpolys(
polys: List[Polygon], threat_zones: ThreatZones
) -> List[NavMeshPoly]:
return [
NavMeshPoly(i, p, threat_zones.threatened(p)) for i, p in enumerate(polys)
]
@staticmethod
def associate_neighbors(polys: List[NavMeshPoly]) -> None:
# Maps (rounded) points to polygons that have a vertex at that point.
# The points are rounded to the nearest int so we can use them as dict
# keys. This allows us to perform approximate neighbor lookups more
# efficiently than comparing each poly to every other poly by finding
# approximate neighbors before checking if the polys actually touch.
points_map: Dict[Tuple[int, int], Set[NavMeshPoly]] = defaultdict(set)
for navpoly in polys:
# The coordinates of the polygon's boundary are a sequence of
# coordinates that define the polygon. The first point is repeated
# at the end, so skip the last vertex.
for x, y in navpoly.poly.boundary.coords[:-1]:
point = (int(x), int(y))
neighbors = {}
for potential_neighbor in points_map[point]:
intersection = navpoly.poly.intersection(potential_neighbor.poly)
if not intersection.is_empty:
potential_neighbor.neighbors[navpoly] = intersection
neighbors[potential_neighbor] = intersection
navpoly.neighbors.update(neighbors)
points_map[point].add(navpoly)
@classmethod
def from_threat_zones(
cls, threat_zones: ThreatZones, theater: ConflictTheater
) -> NavMesh:
# Simplify the threat poly to reduce the number of nav zones. Increase
# the size of the zone and then simplify it with the buffer size as the
# error margin. This will create a simpler poly around the threat zone.
buffer = nautical_miles(10).meters
threat_poly = threat_zones.all.buffer(buffer).simplify(buffer)
# Threat zones can be disconnected. Create a list of threat zones.
if isinstance(threat_poly, MultiPolygon):
polys = list(threat_poly.geoms)
else:
polys = [threat_poly]
# Subtract the threat zones from the whole-map poly to build a navmesh
# for the *safe* areas. Navigation within threatened regions is always
# a straight line to the target or out of the threatened region.
bounds = cls.map_bounds(theater)
for poly in polys:
bounds = bounds.difference(poly)
# Triangulate the safe-region to build the navmesh.
navpolys = cls.create_navpolys(triangulate(bounds), threat_zones)
cls.associate_neighbors(navpolys)
return cls(navpolys)

View File

@@ -1,39 +0,0 @@
from game.db import assigned_units_split
from .operation import *
MAX_DISTANCE_BETWEEN_GROUPS = 12000
class FrontlineAttackOperation(Operation):
interceptors = None # type: db.AssignedUnitsDict
escort = None # type: db.AssignedUnitsDict
strikegroup = None # type: db.AssignedUnitsDict
attackers = None # type: db.ArmorDict
defenders = None # type: db.ArmorDict
def prepare(self, terrain: Terrain, is_quick: bool):
super(FrontlineAttackOperation, self).prepare(terrain, is_quick)
if self.defender_name == self.game.player_name:
self.attackers_starting_position = None
self.defenders_starting_position = None
conflict = Conflict.frontline_cas_conflict(
attacker_name=self.attacker_name,
defender_name=self.defender_name,
attacker=self.current_mission.country(self.attacker_country),
defender=self.current_mission.country(self.defender_country),
from_cp=self.from_cp,
to_cp=self.to_cp,
theater=self.game.theater
)
self.initialize(mission=self.current_mission,
conflict=conflict)
def generate(self):
self.briefinggen.title = "Frontline CAS"
self.briefinggen.description = "Provide CAS for the ground forces attacking enemy lines. Operation will be considered successful if total number of enemy units will be lower than your own by a factor of 1.5 (i.e. with 12 units from both sides, enemy forces need to be reduced to at least 8), meaning that you (and, probably, your wingmans) should concentrate on destroying the enemy units. Target base strength will be lowered as a result. Be advised that your flight will not attack anything until you explicitly tell them so by comms menu."
super(FrontlineAttackOperation, self).generate()

View File

@@ -1,372 +1,222 @@
from typing import Set
from __future__ import annotations
from game.theater.theatergroundobject import TheaterGroundObject
from gen import *
from gen.airfields import AIRFIELD_DATA
from gen.beacons import load_beacons_for_terrain
from gen.radios import RadioRegistry
from gen.tacan import TacanRegistry
import logging
import os
from pathlib import Path
from typing import TYPE_CHECKING, Iterable, List, Optional, Set
from dcs import Mission
from dcs.action import DoScript, DoScriptFile
from dcs.coalition import Coalition
from dcs.countries import country_dict
from dcs.lua.parse import loads
from dcs.terrain.terrain import Terrain
from userdata.debriefing import *
from dcs.mapping import Point
from dcs.translation import String
from dcs.triggers import TriggerStart
from game.plugins import LuaPluginManager
from gen import Conflict, FlightType, VisualGenerator
from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData
from gen.airfields import AIRFIELD_DATA
from gen.airsupportgen import AirSupport, AirSupportConflictGenerator
from gen.armor import GroundConflictGenerator, JtacInfo
from gen.beacons import load_beacons_for_terrain
from gen.briefinggen import BriefingGenerator, MissionInfoGenerator
from gen.environmentgen import EnvironmentGenerator
from gen.forcedoptionsgen import ForcedOptionsGenerator
from gen.groundobjectsgen import GroundObjectsGenerator
from gen.kneeboard import KneeboardGenerator
from gen.naming import namegen
from gen.radios import RadioFrequency, RadioRegistry
from gen.tacan import TacanRegistry
from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator
from .. import db
from ..theater import Airfield
from ..unitmap import UnitMap
if TYPE_CHECKING:
from game import Game
class Operation:
attackers_starting_position = None # type: db.StartingPosition
defenders_starting_position = None # type: db.StartingPosition
"""Static class for managing the final Mission generation"""
current_mission = None # type: dcs.Mission
regular_mission = None # type: dcs.Mission
quick_mission = None # type: dcs.Mission
conflict = None # type: Conflict
armorgen = None # type: ArmorConflictGenerator
current_mission = None # type: Mission
airgen = None # type: AircraftConflictGenerator
triggersgen = None # type: TriggersGenerator
airsupportgen = None # type: AirSupportConflictGenerator
visualgen = None # type: VisualGenerator
envgen = None # type: EnvironmentGenerator
groundobjectgen = None # type: GroundObjectsGenerator
briefinggen = None # type: BriefingGenerator
forcedoptionsgen = None # type: ForcedOptionsGenerator
radio_registry: Optional[RadioRegistry] = None
tacan_registry: Optional[TacanRegistry] = None
game = None # type: Game
environment_settings = None
trigger_radius = TRIGGER_RADIUS_MEDIUM
is_quick = None
is_awacs_enabled = False
ca_slots = 0
player_awacs_enabled = True
# TODO: #436 Generate Air Support for red
enemy_awacs_enabled = True
ca_slots = 1
unit_map: UnitMap
jtacs: List[JtacInfo] = []
plugin_scripts: List[str] = []
def __init__(self,
game,
attacker_name: str,
defender_name: str,
from_cp: ControlPoint,
departure_cp: ControlPoint,
to_cp: ControlPoint = None):
self.game = game
self.attacker_name = attacker_name
self.attacker_country = db.FACTIONS[attacker_name]["country"]
self.defender_name = defender_name
self.defender_country = db.FACTIONS[defender_name]["country"]
print(self.defender_country, self.attacker_country)
self.from_cp = from_cp
self.departure_cp = departure_cp
self.to_cp = to_cp
self.is_quick = False
def units_of(self, country_name: str) -> typing.Collection[UnitType]:
return []
def is_successfull(self, debriefing: Debriefing) -> bool:
return True
@property
def is_player_attack(self) -> bool:
return self.from_cp.captured
def initialize(self, mission: Mission, conflict: Conflict):
self.current_mission = mission
self.conflict = conflict
self.briefinggen = BriefingGenerator(self.current_mission,
self.conflict, self.game)
def prepare(self, terrain: Terrain, is_quick: bool):
@classmethod
def prepare(cls, game: Game):
with open("resources/default_options.lua", "r") as f:
options_dict = loads(f.read())["options"]
cls._set_mission(Mission(game.theater.terrain))
cls.game = game
cls._setup_mission_coalitions()
cls.current_mission.options.load_from_dict(options_dict)
self.current_mission = dcs.Mission(terrain)
@classmethod
def conflicts(cls) -> Iterable[Conflict]:
assert cls.game
for frontline in cls.game.theater.conflicts():
yield Conflict(
cls.game.theater,
frontline.control_point_a,
frontline.control_point_b,
cls.game.player_name,
cls.game.enemy_name,
cls.game.player_country,
cls.game.enemy_country,
frontline.position,
)
print(self.game.player_country)
print(country_dict[db.country_id_from_name(self.game.player_country)])
print(country_dict[db.country_id_from_name(self.game.player_country)]())
# Setup coalition :
self.current_mission.coalition["blue"] = Coalition("blue")
self.current_mission.coalition["red"] = Coalition("red")
p_country = self.game.player_country
e_country = self.game.enemy_country
self.current_mission.coalition["blue"].add_country(country_dict[db.country_id_from_name(p_country)]())
self.current_mission.coalition["red"].add_country(country_dict[db.country_id_from_name(e_country)]())
print([c for c in self.current_mission.coalition["blue"].countries.keys()])
print([c for c in self.current_mission.coalition["red"].countries.keys()])
if is_quick:
self.quick_mission = self.current_mission
else:
self.regular_mission = self.current_mission
self.current_mission.options.load_from_dict(options_dict)
self.is_quick = is_quick
if is_quick:
self.attackers_starting_position = None
self.defenders_starting_position = None
else:
self.attackers_starting_position = self.departure_cp.at
self.defenders_starting_position = self.to_cp.at
def generate(self):
radio_registry = RadioRegistry()
tacan_registry = TacanRegistry()
# Dedup beacon/radio frequencies, since some maps have some frequencies
# used multiple times.
beacons = load_beacons_for_terrain(self.game.theater.terrain.name)
unique_map_frequencies: Set[RadioFrequency] = set()
for beacon in beacons:
unique_map_frequencies.add(beacon.frequency)
if beacon.is_tacan:
if beacon.channel is None:
logging.error(
f"TACAN beacon has no channel: {beacon.callsign}")
else:
tacan_registry.reserve(beacon.tacan_channel)
for airfield, data in AIRFIELD_DATA.items():
if data.theater == self.game.theater.terrain.name:
unique_map_frequencies.add(data.atc.hf)
unique_map_frequencies.add(data.atc.vhf_fm)
unique_map_frequencies.add(data.atc.vhf_am)
unique_map_frequencies.add(data.atc.uhf)
# No need to reserve ILS or TACAN because those are in the
# beacon list.
for frequency in unique_map_frequencies:
radio_registry.reserve(frequency)
# Generate meteo
envgen = EnviromentGenerator(self.current_mission, self.conflict,
self.game)
if self.environment_settings is None:
self.environment_settings = envgen.generate()
else:
envgen.load(self.environment_settings)
# Generate ground object first
groundobjectgen = GroundObjectsGenerator(
self.current_mission,
self.conflict,
self.game,
radio_registry,
tacan_registry
@classmethod
def air_conflict(cls) -> Conflict:
assert cls.game
player_cp, enemy_cp = cls.game.theater.closest_opposing_control_points()
mid_point = player_cp.position.point_from_heading(
player_cp.position.heading_between_point(enemy_cp.position),
player_cp.position.distance_to_point(enemy_cp.position) / 2,
)
return Conflict(
cls.game.theater,
player_cp,
enemy_cp,
cls.game.player_name,
cls.game.enemy_name,
cls.game.player_country,
cls.game.enemy_country,
mid_point,
)
groundobjectgen.generate()
# Generate destroyed units
for d in self.game.get_destroyed_units():
try:
utype = db.unit_type_from_name(d["type"])
except KeyError:
continue
@classmethod
def _set_mission(cls, mission: Mission) -> None:
cls.current_mission = mission
pos = Point(d["x"], d["z"])
if utype is not None and not self.game.position_culled(pos) and self.game.settings.perf_destroyed_units:
self.current_mission.static_group(
country=self.current_mission.country(self.game.player_country),
name="",
_type=utype,
hidden=True,
position=pos,
heading=d["orientation"],
dead=True,
)
@classmethod
def _setup_mission_coalitions(cls):
cls.current_mission.coalition["blue"] = Coalition("blue")
cls.current_mission.coalition["red"] = Coalition("red")
# Air Support (Tanker & Awacs)
airsupportgen = AirSupportConflictGenerator(
self.current_mission, self.conflict, self.game, radio_registry,
tacan_registry)
airsupportgen.generate(self.is_awacs_enabled)
p_country = cls.game.player_country
e_country = cls.game.enemy_country
cls.current_mission.coalition["blue"].add_country(
country_dict[db.country_id_from_name(p_country)]()
)
cls.current_mission.coalition["red"].add_country(
country_dict[db.country_id_from_name(e_country)]()
)
# Generate Activity on the map
airgen = AircraftConflictGenerator(
self.current_mission, self.conflict, self.game.settings, self.game,
radio_registry)
for cp in self.game.theater.controlpoints:
side = cp.captured
if side:
country = self.current_mission.country(self.game.player_country)
else:
country = self.current_mission.country(self.game.enemy_country)
if cp.id in self.game.planners.keys():
airgen.generate_flights(
cp,
country,
self.game.planners[cp.id],
groundobjectgen.runways
)
@classmethod
def inject_lua_trigger(cls, contents: str, comment: str) -> None:
trigger = TriggerStart(comment=comment)
trigger.add_action(DoScript(String(contents)))
cls.current_mission.triggerrules.triggers.append(trigger)
# Generate ground units on frontline everywhere
jtacs: List[JtacInfo] = []
for player_cp, enemy_cp in self.game.theater.conflicts(True):
conflict = Conflict.frontline_cas_conflict(self.attacker_name, self.defender_name,
self.current_mission.country(self.attacker_country),
self.current_mission.country(self.defender_country),
player_cp, enemy_cp, self.game.theater)
# Generate frontline ops
player_gp = self.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id]
enemy_gp = self.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id]
groundConflictGen = GroundConflictGenerator(self.current_mission, conflict, self.game, player_gp, enemy_gp, player_cp.stances[enemy_cp.id])
groundConflictGen.generate()
jtacs.extend(groundConflictGen.jtacs)
@classmethod
def bypass_plugin_script(cls, mnemonic: str) -> None:
cls.plugin_scripts.append(mnemonic)
# Setup combined arms parameters
self.current_mission.groundControl.pilot_can_control_vehicles = self.ca_slots > 0
if self.game.player_country in [country.name for country in self.current_mission.coalition["blue"].countries.values()]:
self.current_mission.groundControl.blue_tactical_commander = self.ca_slots
@classmethod
def inject_plugin_script(
cls, plugin_mnemonic: str, script: str, script_mnemonic: str
) -> None:
if script_mnemonic in cls.plugin_scripts:
logging.debug(f"Skipping already loaded {script} for {plugin_mnemonic}")
else:
self.current_mission.groundControl.red_tactical_commander = self.ca_slots
cls.plugin_scripts.append(script_mnemonic)
# Triggers
triggersgen = TriggersGenerator(self.current_mission, self.conflict,
self.game)
triggersgen.generate()
plugin_path = Path("./resources/plugins", plugin_mnemonic)
# Options
forcedoptionsgen = ForcedOptionsGenerator(self.current_mission,
self.conflict, self.game)
forcedoptionsgen.generate()
script_path = Path(plugin_path, script)
if not script_path.exists():
logging.error(f"Cannot find {script_path} for plugin {plugin_mnemonic}")
return
# Generate Visuals Smoke Effects
visualgen = VisualGenerator(self.current_mission, self.conflict,
self.game)
if self.game.settings.perf_smoke_gen:
visualgen.generate()
# Inject Plugins Lua Scripts
listOfPluginsScripts = []
plugin_file_path = Path("./resources/scripts/plugins/__plugins.lst")
if plugin_file_path.exists():
for line in plugin_file_path.read_text().splitlines():
name = line.strip()
if not name.startswith( '#' ):
trigger = TriggerStart(comment="Load " + name)
listOfPluginsScripts.append(name)
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/plugins/" + name)
trigger.add_action(DoScriptFile(fileref))
self.current_mission.triggerrules.triggers.append(trigger)
else:
logging.info(
f"Not loading plugins, {plugin_file_path} does not exist")
# Inject Mist Script if not done already in the plugins
if not "mist.lua" in listOfPluginsScripts and not "mist_4_3_74.lua" in listOfPluginsScripts: # don't load the script twice
trigger = TriggerStart(comment="Load Mist Lua framework")
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/mist_4_3_74.lua")
trigger = TriggerStart(comment=f"Load {script_mnemonic}")
filename = script_path.resolve()
fileref = cls.current_mission.map_resource.add_resource_file(filename)
trigger.add_action(DoScriptFile(fileref))
self.current_mission.triggerrules.triggers.append(trigger)
cls.current_mission.triggerrules.triggers.append(trigger)
# Inject JSON library if not done already in the plugins
if not "json.lua" in listOfPluginsScripts : # don't load the script twice
trigger = TriggerStart(comment="Load JSON Lua library")
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/json.lua")
trigger.add_action(DoScriptFile(fileref))
self.current_mission.triggerrules.triggers.append(trigger)
@classmethod
def notify_info_generators(
cls,
groundobjectgen: GroundObjectsGenerator,
airsupportgen: AirSupportConflictGenerator,
jtacs: List[JtacInfo],
airgen: AircraftConflictGenerator,
):
"""Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)"""
gens: List[MissionInfoGenerator] = [
KneeboardGenerator(cls.current_mission, cls.game),
BriefingGenerator(cls.current_mission, cls.game),
]
for gen in gens:
for dynamic_runway in groundobjectgen.runways.values():
gen.add_dynamic_runway(dynamic_runway)
# Inject Ciribob's JTACAutoLase if not done already in the plugins
if not "JTACAutoLase.lua" in listOfPluginsScripts : # don't load the script twice
trigger = TriggerStart(comment="Load JTACAutoLase.lua script")
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/JTACAutoLase.lua")
trigger.add_action(DoScriptFile(fileref))
self.current_mission.triggerrules.triggers.append(trigger)
for tanker in airsupportgen.air_support.tankers:
gen.add_tanker(tanker)
# set a LUA table with data from Liberation that we want to set
# at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function
# later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts
state_location = "[[" + os.path.abspath("state.json") + "]]"
lua = """
-- setting configuration table
env.info("DCSLiberation|: setting configuration table")
-- all data in this table is overridable.
dcsLiberation = {}
-- the base location for state.json; if non-existent, it'll be replaced with LIBERATION_EXPORT_DIR, TEMP, or DCS working directory
dcsLiberation.installPath=""" + state_location + """
-- you can override dcsLiberation.JTACAutoLase to make it use your own function ; it will be called with these parameters : ({jtac.unit_name}, {jtac.code}, {smoke}, 'vehicle') for all JTACs
if ctld then
dcsLiberation.JTACAutoLase=ctld.JTACAutoLase
elseif JTACAutoLase then
dcsLiberation.JTACAutoLase=JTACAutoLase
end
-- later, we'll add more data to the table
--dcsLiberation.POIs = {}
--dcsLiberation.BASEs = {}
--dcsLiberation.JTACs = {}
"""
if cls.player_awacs_enabled:
for awacs in airsupportgen.air_support.awacs:
gen.add_awacs(awacs)
trigger = TriggerStart(comment="Set DCS Liberation data")
trigger.add_action(DoScript(String(lua)))
self.current_mission.triggerrules.triggers.append(trigger)
for jtac in jtacs:
gen.add_jtac(jtac)
# Inject DCS-Liberation script if not done already in the plugins
if not "dcs_liberation.lua" in listOfPluginsScripts : # don't load the script twice
trigger = TriggerStart(comment="Load DCS Liberation script")
fileref = self.current_mission.map_resource.add_resource_file("./resources/scripts/dcs_liberation.lua")
trigger.add_action(DoScriptFile(fileref))
self.current_mission.triggerrules.triggers.append(trigger)
for flight in airgen.flights:
gen.add_flight(flight)
gen.generate()
# add a configuration for JTACAutoLase and start lasing for all JTACs
smoke = "true"
if hasattr(self.game.settings, "jtac_smoke_on"):
if not self.game.settings.jtac_smoke_on:
smoke = "false"
@classmethod
def create_unit_map(cls) -> None:
cls.unit_map = UnitMap()
for control_point in cls.game.theater.controlpoints:
if isinstance(control_point, Airfield):
cls.unit_map.add_airfield(control_point)
lua = """
-- setting and starting JTACs
env.info("DCSLiberation|: setting and starting JTACs")
"""
@classmethod
def create_radio_registries(cls) -> None:
unique_map_frequencies: Set[RadioFrequency] = set()
cls._create_tacan_registry(unique_map_frequencies)
cls._create_radio_registry(unique_map_frequencies)
for jtac in jtacs:
lua += f"if dcsLiberation.JTACAutoLase then dcsLiberation.JTACAutoLase('{jtac.unit_name}', {jtac.code}, {smoke}, 'vehicle') end\n"
assert cls.radio_registry is not None
for frequency in unique_map_frequencies:
cls.radio_registry.reserve(frequency)
trigger = TriggerStart(comment="Start JTACs")
trigger.add_action(DoScript(String(lua)))
self.current_mission.triggerrules.triggers.append(trigger)
self.assign_channels_to_flights(airgen.flights,
airsupportgen.air_support)
kneeboard_generator = KneeboardGenerator(self.current_mission)
for dynamic_runway in groundobjectgen.runways.values():
self.briefinggen.add_dynamic_runway(dynamic_runway)
for tanker in airsupportgen.air_support.tankers:
self.briefinggen.add_tanker(tanker)
kneeboard_generator.add_tanker(tanker)
if self.is_awacs_enabled:
for awacs in airsupportgen.air_support.awacs:
self.briefinggen.add_awacs(awacs)
kneeboard_generator.add_awacs(awacs)
for jtac in jtacs:
self.briefinggen.add_jtac(jtac)
kneeboard_generator.add_jtac(jtac)
for flight in airgen.flights:
self.briefinggen.add_flight(flight)
kneeboard_generator.add_flight(flight)
self.briefinggen.generate()
kneeboard_generator.generate()
def assign_channels_to_flights(self, flights: List[FlightData],
air_support: AirSupport) -> None:
@classmethod
def assign_channels_to_flights(
cls, flights: List[FlightData], air_support: AirSupport
) -> None:
"""Assigns preset radio channels for client flights."""
for flight in flights:
if not flight.client_units:
continue
self.assign_channels_to_flight(flight, air_support)
cls.assign_channels_to_flight(flight, air_support)
def assign_channels_to_flight(self, flight: FlightData,
air_support: AirSupport) -> None:
@staticmethod
def assign_channels_to_flight(flight: FlightData, air_support: AirSupport) -> None:
"""Assigns preset radio channels for a client flight."""
airframe = flight.aircraft_type
@@ -376,6 +226,377 @@ class Operation:
logging.warning(f"No aircraft data for {airframe.id}")
return
aircraft_data.channel_allocator.assign_channels_for_flight(
flight, air_support
if aircraft_data.channel_allocator is not None:
aircraft_data.channel_allocator.assign_channels_for_flight(
flight, air_support
)
@classmethod
def _create_tacan_registry(
cls, unique_map_frequencies: Set[RadioFrequency]
) -> None:
"""
Dedup beacon/radio frequencies, since some maps have some frequencies
used multiple times.
"""
cls.tacan_registry = TacanRegistry()
beacons = load_beacons_for_terrain(cls.game.theater.terrain.name)
for beacon in beacons:
unique_map_frequencies.add(beacon.frequency)
if beacon.is_tacan:
if beacon.channel is None:
logging.error(f"TACAN beacon has no channel: {beacon.callsign}")
else:
cls.tacan_registry.reserve(beacon.tacan_channel)
@classmethod
def _create_radio_registry(
cls, unique_map_frequencies: Set[RadioFrequency]
) -> None:
cls.radio_registry = RadioRegistry()
for data in AIRFIELD_DATA.values():
if data.theater == cls.game.theater.terrain.name and data.atc:
unique_map_frequencies.add(data.atc.hf)
unique_map_frequencies.add(data.atc.vhf_fm)
unique_map_frequencies.add(data.atc.vhf_am)
unique_map_frequencies.add(data.atc.uhf)
# No need to reserve ILS or TACAN because those are in the
# beacon list.
@classmethod
def _generate_ground_units(cls):
cls.groundobjectgen = GroundObjectsGenerator(
cls.current_mission,
cls.game,
cls.radio_registry,
cls.tacan_registry,
cls.unit_map,
)
cls.groundobjectgen.generate()
@classmethod
def _generate_destroyed_units(cls) -> None:
"""Add destroyed units to the Mission"""
for d in cls.game.get_destroyed_units():
try:
utype = db.unit_type_from_name(d["type"])
except KeyError:
continue
pos = Point(d["x"], d["z"])
if (
utype is not None
and not cls.game.position_culled(pos)
and cls.game.settings.perf_destroyed_units
):
cls.current_mission.static_group(
country=cls.current_mission.country(cls.game.player_country),
name="",
_type=utype,
hidden=True,
position=pos,
heading=d["orientation"],
dead=True,
)
@classmethod
def generate(cls) -> UnitMap:
"""Build the final Mission to be exported"""
cls.create_unit_map()
cls.create_radio_registries()
# Set mission time and weather conditions.
EnvironmentGenerator(cls.current_mission, cls.game.conditions).generate()
cls._generate_ground_units()
cls._generate_destroyed_units()
cls._generate_air_units()
cls.assign_channels_to_flights(
cls.airgen.flights, cls.airsupportgen.air_support
)
cls._generate_ground_conflicts()
# Triggers
triggersgen = TriggersGenerator(cls.current_mission, cls.game)
triggersgen.generate()
# Setup combined arms parameters
cls.current_mission.groundControl.pilot_can_control_vehicles = cls.ca_slots > 0
if cls.game.player_country in [
country.name
for country in cls.current_mission.coalition["blue"].countries.values()
]:
cls.current_mission.groundControl.blue_tactical_commander = cls.ca_slots
else:
cls.current_mission.groundControl.red_tactical_commander = cls.ca_slots
# Options
forcedoptionsgen = ForcedOptionsGenerator(cls.current_mission, cls.game)
forcedoptionsgen.generate()
# Generate Visuals Smoke Effects
visualgen = VisualGenerator(cls.current_mission, cls.game)
if cls.game.settings.perf_smoke_gen:
visualgen.generate()
cls.generate_lua(cls.airgen, cls.airsupportgen, cls.jtacs)
# Inject Plugins Lua Scripts and data
cls.plugin_scripts.clear()
for plugin in LuaPluginManager.plugins():
if plugin.enabled:
plugin.inject_scripts(cls)
plugin.inject_configuration(cls)
cls.assign_channels_to_flights(
cls.airgen.flights, cls.airsupportgen.air_support
)
cls.notify_info_generators(
cls.groundobjectgen, cls.airsupportgen, cls.jtacs, cls.airgen
)
cls.reset_naming_ids()
return cls.unit_map
@classmethod
def _generate_air_units(cls) -> None:
"""Generate the air units for the Operation"""
# Air Support (Tanker & Awacs)
assert cls.radio_registry and cls.tacan_registry
cls.airsupportgen = AirSupportConflictGenerator(
cls.current_mission,
cls.air_conflict(),
cls.game,
cls.radio_registry,
cls.tacan_registry,
)
cls.airsupportgen.generate()
# Generate Aircraft Activity on the map
cls.airgen = AircraftConflictGenerator(
cls.current_mission,
cls.game.settings,
cls.game,
cls.radio_registry,
cls.unit_map,
)
cls.airgen.clear_parking_slots()
cls.airgen.generate_flights(
cls.current_mission.country(cls.game.player_country),
cls.game.blue_ato,
cls.groundobjectgen.runways,
)
cls.airgen.generate_flights(
cls.current_mission.country(cls.game.enemy_country),
cls.game.red_ato,
cls.groundobjectgen.runways,
)
cls.airgen.spawn_unused_aircraft(
cls.current_mission.country(cls.game.player_country),
cls.current_mission.country(cls.game.enemy_country),
)
@classmethod
def _generate_ground_conflicts(cls) -> None:
"""For each frontline in the Operation, generate the ground conflicts and JTACs"""
for front_line in cls.game.theater.conflicts(True):
player_cp = front_line.control_point_a
enemy_cp = front_line.control_point_b
conflict = Conflict.frontline_cas_conflict(
cls.game.player_name,
cls.game.enemy_name,
cls.current_mission.country(cls.game.player_country),
cls.current_mission.country(cls.game.enemy_country),
player_cp,
enemy_cp,
cls.game.theater,
)
# Generate frontline ops
player_gp = cls.game.ground_planners[player_cp.id].units_per_cp[enemy_cp.id]
enemy_gp = cls.game.ground_planners[enemy_cp.id].units_per_cp[player_cp.id]
ground_conflict_gen = GroundConflictGenerator(
cls.current_mission,
conflict,
cls.game,
player_gp,
enemy_gp,
player_cp.stances[enemy_cp.id],
cls.unit_map,
)
ground_conflict_gen.generate()
cls.jtacs.extend(ground_conflict_gen.jtacs)
@classmethod
def reset_naming_ids(cls):
namegen.reset_numbers()
@classmethod
def generate_lua(
cls,
airgen: AircraftConflictGenerator,
airsupportgen: AirSupportConflictGenerator,
jtacs: List[JtacInfo],
) -> None:
# TODO: Refactor this
luaData = {
"AircraftCarriers": {},
"Tankers": {},
"AWACs": {},
"JTACs": {},
"TargetPoints": {},
} # type: ignore
for tanker in airsupportgen.air_support.tankers:
luaData["Tankers"][tanker.callsign] = {
"dcsGroupName": tanker.dcsGroupName,
"callsign": tanker.callsign,
"variant": tanker.variant,
"radio": tanker.freq.mhz,
"tacan": str(tanker.tacan.number) + tanker.tacan.band.name,
}
if airsupportgen.air_support.awacs:
for awacs in airsupportgen.air_support.awacs:
luaData["AWACs"][awacs.callsign] = {
"dcsGroupName": awacs.dcsGroupName,
"callsign": awacs.callsign,
"radio": awacs.freq.mhz,
}
for jtac in jtacs:
luaData["JTACs"][jtac.callsign] = {
"dcsGroupName": jtac.dcsGroupName,
"callsign": jtac.callsign,
"zone": jtac.region,
"dcsUnit": jtac.unit_name,
"laserCode": jtac.code,
}
for flight in airgen.flights:
if flight.friendly and flight.flight_type in [
FlightType.ANTISHIP,
FlightType.DEAD,
FlightType.SEAD,
FlightType.STRIKE,
]:
flightType = str(flight.flight_type)
flightTarget = flight.package.target
if flightTarget:
flightTargetName = None
flightTargetType = None
if isinstance(flightTarget, TheaterGroundObject):
flightTargetName = flightTarget.obj_name
flightTargetType = (
flightType + f" TGT ({flightTarget.category})"
)
elif hasattr(flightTarget, "name"):
flightTargetName = flightTarget.name
flightTargetType = flightType + " TGT (Airbase)"
luaData["TargetPoints"][flightTargetName] = {
"name": flightTargetName,
"type": flightTargetType,
"position": {
"x": flightTarget.position.x,
"y": flightTarget.position.y,
},
}
# set a LUA table with data from Liberation that we want to set
# at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function
# later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts
state_location = "[[" + os.path.abspath(".") + "]]"
lua = (
"""
-- setting configuration table
env.info("DCSLiberation|: setting configuration table")
-- all data in this table is overridable.
dcsLiberation = {}
-- the base location for state.json; if non-existent, it'll be replaced with LIBERATION_EXPORT_DIR, TEMP, or DCS working directory
dcsLiberation.installPath="""
+ state_location
+ """
"""
)
# Process the tankers
lua += """
-- list the tankers generated by Liberation
dcsLiberation.Tankers = {
"""
for key in luaData["Tankers"]:
data = luaData["Tankers"][key]
dcsGroupName = data["dcsGroupName"]
callsign = data["callsign"]
variant = data["variant"]
tacan = data["tacan"]
radio = data["radio"]
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', variant='{variant}', tacan='{tacan}', radio='{radio}' }}, \n"
# lua += f" {{name='{dcsGroupName}', description='{callsign} ({variant})', information='Tacan:{tacan} Radio:{radio}' }}, \n"
lua += "}"
# Process the AWACSes
lua += """
-- list the AWACs generated by Liberation
dcsLiberation.AWACs = {
"""
for key in luaData["AWACs"]:
data = luaData["AWACs"][key]
dcsGroupName = data["dcsGroupName"]
callsign = data["callsign"]
radio = data["radio"]
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', radio='{radio}' }}, \n"
# lua += f" {{name='{dcsGroupName}', description='{callsign} (AWACS)', information='Radio:{radio}' }}, \n"
lua += "}"
# Process the JTACs
lua += """
-- list the JTACs generated by Liberation
dcsLiberation.JTACs = {
"""
for key in luaData["JTACs"]:
data = luaData["JTACs"][key]
dcsGroupName = data["dcsGroupName"]
callsign = data["callsign"]
zone = data["zone"]
laserCode = data["laserCode"]
dcsUnit = data["dcsUnit"]
lua += f" {{dcsGroupName='{dcsGroupName}', callsign='{callsign}', zone='{zone}', laserCode='{laserCode}', dcsUnit='{dcsUnit}' }}, \n"
# lua += f" {{name='{dcsGroupName}', description='JTAC {callsign} ', information='Laser:{laserCode}', jtac={laserCode} }}, \n"
lua += "}"
# Process the Target Points
lua += """
-- list the target points generated by Liberation
dcsLiberation.TargetPoints = {
"""
for key in luaData["TargetPoints"]:
data = luaData["TargetPoints"][key]
name = data["name"]
pointType = data["type"]
positionX = data["position"]["x"]
positionY = data["position"]["y"]
lua += f" {{name='{name}', pointType='{pointType}', positionX='{positionX}', positionY='{positionY}' }}, \n"
# lua += f" {{name='{pointType} {name}', point{{x={positionX}, z={positionY} }} }}, \n"
lua += "}"
lua += """
-- list the airbases generated by Liberation
-- dcsLiberation.Airbases = {}
-- list the aircraft carriers generated by Liberation
-- dcsLiberation.Carriers = {}
-- later, we'll add more data to the table
"""
trigger = TriggerStart(comment="Set DCS Liberation data")
trigger.add_action(DoScript(String(lua)))
Operation.current_mission.triggerrules.triggers.append(trigger)

View File

@@ -2,68 +2,58 @@ import logging
import os
import pickle
import shutil
from typing import Optional
_dcs_saved_game_folder = None # type: str
_dcs_saved_game_folder: Optional[str] = None
_file_abs_path = None
def setup(user_folder: str):
global _dcs_saved_game_folder
_dcs_saved_game_folder = user_folder
_file_abs_path = os.path.join(base_path(), "default.liberation")
def base_path() -> str:
global _dcs_saved_game_folder
assert _dcs_saved_game_folder
return _dcs_saved_game_folder
def _save_file() -> str:
return os.path.join(base_path(), "default.liberation")
def _temporary_save_file() -> str:
return os.path.join(base_path(), "tmpsave.liberation")
def _autosave_path() -> str:
return os.path.join(base_path(), "autosave.liberation")
def _save_file_exists() -> bool:
return os.path.exists(_save_file())
def mission_path_for(name: str) -> str:
return os.path.join(base_path(), "Missions", "{}".format(name))
def restore_game():
if not _save_file_exists():
return None
with open(_save_file(), "rb") as f:
try:
save = pickle.load(f)
return save
except:
logging.error("Invalid Save game")
return None
def load_game(path):
with open(path, "rb") as f:
try:
save = pickle.load(f)
save.savepath = path
return save
except:
logging.error("Invalid Save game")
except Exception:
logging.exception("Invalid Save game")
return None
def save_game(game) -> bool:
try:
with open(_temporary_save_file(), "wb") as f:
pickle.dump(game, f)
shutil.copy(_temporary_save_file(), game.savepath)
return True
except Exception as e:
logging.error(e)
except Exception:
logging.exception("Could not save game")
return False
def autosave(game) -> bool:
"""
Autosave to the autosave location
@@ -74,7 +64,6 @@ def autosave(game) -> bool:
with open(_autosave_path(), "wb") as f:
pickle.dump(game, f)
return True
except Exception as e:
logging.error(e)
except Exception:
logging.exception("Could not save game")
return False

2
game/plugins/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from .luaplugin import LuaPlugin
from .manager import LuaPluginManager

187
game/plugins/luaplugin.py Normal file
View File

@@ -0,0 +1,187 @@
from __future__ import annotations
import json
import logging
import textwrap
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional, TYPE_CHECKING, Type
from game.settings import Settings
if TYPE_CHECKING:
from game.operation.operation import Operation
class LuaPluginWorkOrder:
def __init__(
self, parent_mnemonic: str, filename: str, mnemonic: str, disable: bool
) -> None:
self.parent_mnemonic = parent_mnemonic
self.filename = filename
self.mnemonic = mnemonic
self.disable = disable
def work(self, operation: Type[Operation]) -> None:
if self.disable:
operation.bypass_plugin_script(self.mnemonic)
else:
operation.inject_plugin_script(
self.parent_mnemonic, self.filename, self.mnemonic
)
class PluginSettings:
def __init__(self, identifier: str, enabled_by_default: bool) -> None:
self.identifier = identifier
self.enabled_by_default = enabled_by_default
self.settings = Settings()
self.initialize_settings()
def set_settings(self, settings: Settings):
self.settings = settings
self.initialize_settings()
def initialize_settings(self) -> None:
# 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)
@property
def enabled(self) -> bool:
return self.settings.plugin_option(self.identifier)
def set_enabled(self, enabled: bool) -> None:
self.settings.set_plugin_option(self.identifier, enabled)
class LuaPluginOption(PluginSettings):
def __init__(self, identifier: str, name: str, enabled_by_default: bool) -> None:
super().__init__(identifier, enabled_by_default)
self.name = name
@dataclass(frozen=True)
class LuaPluginDefinition:
identifier: str
name: str
present_in_ui: bool
enabled_by_default: bool
options: List[LuaPluginOption]
work_orders: List[LuaPluginWorkOrder]
config_work_orders: List[LuaPluginWorkOrder]
@classmethod
def from_json(cls, name: str, path: Path) -> LuaPluginDefinition:
data = json.loads(path.read_text())
options = []
for option in data.get("specificOptions"):
option_id = option["mnemonic"]
options.append(
LuaPluginOption(
identifier=f"{name}.{option_id}",
name=option.get("nameInUI", name),
enabled_by_default=option.get("defaultValue"),
)
)
work_orders = []
for work_order in data.get("scriptsWorkOrders"):
work_orders.append(
LuaPluginWorkOrder(
name,
work_order.get("file"),
work_order["mnemonic"],
work_order.get("disable", False),
)
)
config_work_orders = []
for work_order in data.get("configurationWorkOrders"):
config_work_orders.append(
LuaPluginWorkOrder(
name,
work_order.get("file"),
work_order["mnemonic"],
work_order.get("disable", False),
)
)
return cls(
identifier=name,
name=data["nameInUI"],
present_in_ui=not data.get("skipUI", False),
enabled_by_default=data.get("defaultValue", False),
options=options,
work_orders=work_orders,
config_work_orders=config_work_orders,
)
class LuaPlugin(PluginSettings):
def __init__(self, definition: LuaPluginDefinition) -> None:
self.definition = definition
super().__init__(self.definition.identifier, self.definition.enabled_by_default)
@property
def name(self) -> str:
return self.definition.name
@property
def show_in_ui(self) -> bool:
return self.definition.present_in_ui
@property
def options(self) -> List[LuaPluginOption]:
return self.definition.options
@classmethod
def from_json(cls, name: str, path: Path) -> Optional[LuaPlugin]:
try:
definition = LuaPluginDefinition.from_json(name, path)
except KeyError:
logging.exception("Required plugin configuration value missing")
return None
return cls(definition)
def set_settings(self, settings: Settings):
super().set_settings(settings)
for option in self.definition.options:
option.set_settings(self.settings)
def inject_scripts(self, operation: Type[Operation]) -> None:
for work_order in self.definition.work_orders:
work_order.work(operation)
def inject_configuration(self, operation: Type[Operation]) -> None:
# inject the plugin options
if self.options:
option_decls = []
for option in self.options:
enabled = str(option.enabled).lower()
name = option.identifier
option_decls.append(f" dcsLiberation.plugins.{name} = {enabled}")
joined_options = "\n".join(option_decls)
lua = textwrap.dedent(
f"""\
-- {self.identifier} plugin configuration.
if dcsLiberation then
if not dcsLiberation.plugins then
dcsLiberation.plugins = {{}}
end
dcsLiberation.plugins.{self.identifier} = {{}}
{joined_options}
end
"""
)
operation.inject_lua_trigger(lua, f"{self.identifier} plugin configuration")
for work_order in self.definition.config_work_orders:
work_order.work(operation)

51
game/plugins/manager.py Normal file
View File

@@ -0,0 +1,51 @@
import json
import logging
from pathlib import Path
from typing import Dict, List, Optional
from game.settings import Settings
from game.plugins.luaplugin import LuaPlugin
class LuaPluginManager:
_plugins_loaded = False
_plugins: Dict[str, LuaPlugin] = {}
@classmethod
def _load_plugins(cls) -> None:
plugins_path = Path("resources/plugins")
path = plugins_path / "plugins.json"
if not path.exists():
raise RuntimeError(f"{path} does not exist. Cannot continue.")
logging.info(f"Reading plugins list from {path}")
data = json.loads(path.read_text())
for name in data:
plugin_path = plugins_path / name / "plugin.json"
if not plugin_path.exists():
raise RuntimeError(
f"Invalid plugin configuration: required plugin {name} "
f"does not exist at {plugin_path}"
)
logging.info(f"Loading plugin {name} from {plugin_path}")
plugin = LuaPlugin.from_json(name, plugin_path)
if plugin is not None:
cls._plugins[name] = plugin
cls._plugins_loaded = True
@classmethod
def _get_plugins(cls) -> Dict[str, LuaPlugin]:
if not cls._plugins_loaded:
cls._load_plugins()
return cls._plugins
@classmethod
def plugins(cls) -> List[LuaPlugin]:
return list(cls._get_plugins().values())
@classmethod
def load_settings(cls, settings: Settings) -> None:
for plugin in cls.plugins():
plugin.set_settings(settings)

269
game/procurement.py Normal file
View File

@@ -0,0 +1,269 @@
from __future__ import annotations
import math
import random
from dataclasses import dataclass
from typing import Iterator, List, Optional, TYPE_CHECKING, Type
from dcs.unittype import FlyingType, VehicleType
from game import db
from game.factions.faction import Faction
from game.theater import ControlPoint, MissionTarget
from game.utils import Distance
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import FlightType
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
if TYPE_CHECKING:
from game import Game
@dataclass(frozen=True)
class AircraftProcurementRequest:
near: MissionTarget
range: Distance
task_capability: FlightType
number: int
def __str__(self) -> str:
task = self.task_capability.value
distance = self.range.nautical_miles
target = self.near.name
return f"{self.number} ship {task} within {distance} nm of {target}"
class ProcurementAi:
def __init__(
self,
game: Game,
for_player: bool,
faction: Faction,
manage_runways: bool,
manage_front_line: bool,
manage_aircraft: bool,
front_line_budget_share: float,
) -> None:
if front_line_budget_share > 1.0:
raise ValueError
self.game = game
self.is_player = for_player
self.faction = faction
self.manage_runways = manage_runways
self.manage_front_line = manage_front_line
self.manage_aircraft = manage_aircraft
self.front_line_budget_share = front_line_budget_share
self.threat_zones = self.game.threat_zone_for(not self.is_player)
def spend_budget(
self, budget: float, aircraft_requests: List[AircraftProcurementRequest]
) -> float:
if self.manage_runways:
budget = self.repair_runways(budget)
if self.manage_front_line:
armor_budget = math.ceil(budget * self.front_line_budget_share)
budget -= armor_budget
budget += self.reinforce_front_line(armor_budget)
# Don't sell overstock aircraft until after we've bought runways and
# front lines. Any budget we free up should be earmarked for aircraft.
if not self.is_player:
budget += self.sell_incomplete_squadrons()
if self.manage_aircraft:
budget = self.purchase_aircraft(budget, aircraft_requests)
return budget
def sell_incomplete_squadrons(self) -> float:
# Selling incomplete squadrons gives us more money to spend on the next
# turn. This serves as a short term fix for
# https://github.com/Khopa/dcs_liberation/issues/41.
#
# Only incomplete squadrons which are unlikely to get used will be sold
# rather than all unused aircraft because the unused aircraft are what
# make OCA strikes worthwhile.
#
# This option is only used by the AI since players cannot cancel sales
# (https://github.com/Khopa/dcs_liberation/issues/365).
total = 0.0
for cp in self.game.theater.control_points_for(self.is_player):
inventory = self.game.aircraft_inventory.for_control_point(cp)
for aircraft, available in inventory.all_aircraft:
# We only ever plan even groups, so the odd aircraft is unlikely
# to get used.
if available % 2 == 0:
continue
inventory.remove_aircraft(aircraft, 1)
total += db.PRICES[aircraft]
return total
def repair_runways(self, budget: float) -> float:
for control_point in self.owned_points:
if budget < db.RUNWAY_REPAIR_COST:
break
if control_point.runway_can_be_repaired:
control_point.begin_runway_repair()
budget -= db.RUNWAY_REPAIR_COST
if self.is_player:
self.game.message(
"OPFOR has begun repairing the runway at " f"{control_point}"
)
else:
self.game.message(
"We have begun repairing the runway at " f"{control_point}"
)
return budget
def random_affordable_ground_unit(
self, budget: float, cp: ControlPoint
) -> Optional[Type[VehicleType]]:
affordable_units = [
u
for u in self.faction.frontline_units + self.faction.artillery_units
if db.PRICES[u] <= budget
]
total_number_aa = (
cp.base.total_frontline_aa + cp.pending_frontline_aa_deliveries_count
)
total_non_aa = (
cp.base.total_armor + cp.pending_deliveries_count - total_number_aa
)
max_aa = math.ceil(total_non_aa / 8)
# Limit the number of AA units the AI will buy
if not total_number_aa < max_aa:
for unit in [u for u in affordable_units if u in TYPE_SHORAD]:
affordable_units.remove(unit)
if not affordable_units:
return None
return random.choice(affordable_units)
def reinforce_front_line(self, budget: float) -> float:
if not self.faction.frontline_units and not self.faction.artillery_units:
return budget
while budget > 0:
candidates = self.front_line_candidates()
if not candidates:
break
cp = random.choice(candidates)
unit = self.random_affordable_ground_unit(budget, cp)
if unit is None:
# Can't afford any more units.
break
budget -= db.PRICES[unit]
cp.pending_unit_deliveries.order({unit: 1})
return budget
def _affordable_aircraft_of_types(
self,
types: List[Type[FlyingType]],
airbase: ControlPoint,
number: int,
max_price: float,
) -> Optional[Type[FlyingType]]:
best_choice: Optional[Type[FlyingType]] = None
for unit in [u for u in self.faction.aircrafts if u in types]:
if db.PRICES[unit] * number > max_price:
continue
if not airbase.can_operate(unit):
continue
# Affordable and compatible. To keep some variety, skip with a 50/50
# chance. Might be a good idea to have the chance to skip based on
# the price compared to the rest of the choices.
best_choice = unit
if random.choice([True, False]):
break
return best_choice
def affordable_aircraft_for(
self, request: AircraftProcurementRequest, airbase: ControlPoint, budget: float
) -> Optional[Type[FlyingType]]:
return self._affordable_aircraft_of_types(
aircraft_for_task(request.task_capability), airbase, request.number, budget
)
def purchase_aircraft(
self, budget: float, aircraft_requests: List[AircraftProcurementRequest]
) -> float:
for request in aircraft_requests:
for airbase in self.best_airbases_for(request):
unit = self.affordable_aircraft_for(request, airbase, budget)
if unit is None:
# Can't afford any aircraft capable of performing the
# required mission that can operate from this airbase. We
# might be able to afford aircraft at other airbases though,
# in the case where the airbase we attempted to use is only
# able to operate expensive aircraft.
continue
budget -= db.PRICES[unit] * request.number
airbase.pending_unit_deliveries.order({unit: request.number})
return budget
@property
def owned_points(self) -> List[ControlPoint]:
if self.is_player:
return self.game.theater.player_points()
else:
return self.game.theater.enemy_points()
def best_airbases_for(
self, request: AircraftProcurementRequest
) -> Iterator[ControlPoint]:
distance_cache = ObjectiveDistanceCache.get_closest_airfields(request.near)
threatened = []
for cp in distance_cache.airfields_within(request.range):
if not cp.is_friendly(self.is_player):
continue
if not cp.runway_is_operational():
continue
if cp.unclaimed_parking(self.game) < request.number:
continue
if self.threat_zones.threatened(cp.position):
threatened.append(cp)
yield cp
yield from threatened
def front_line_candidates(self) -> List[ControlPoint]:
candidates = []
# Prefer to buy front line units at active front lines that are not
# already overloaded.
for cp in self.owned_points:
if cp.expected_ground_units_next_turn.total >= 30:
# Control point is already sufficiently defended.
continue
for connected in cp.connected_points:
if not connected.is_friendly(to_player=self.is_player):
candidates.append(cp)
if not candidates:
# Otherwise buy reserves, but don't exceed 10 reserve units per CP.
# These units do not exist in the world until the CP becomes
# connected to an active front line, at which point all these units
# will suddenly appear at the gates of the newly captured CP.
#
# To avoid sudden overwhelming numbers of units we avoid buying
# many.
#
# Also, do not bother buying units at bases that will never connect
# to a front line.
for cp in self.owned_points:
if not cp.can_deploy_ground_units:
continue
if cp.expected_ground_units_next_turn.total >= 10:
continue
if cp.is_global:
continue
candidates.append(cp)
return candidates

View File

@@ -1,43 +1,81 @@
from dataclasses import dataclass, field
from typing import Dict, Optional
from dcs.forcedoptions import ForcedOptions
@dataclass
class Settings:
def __init__(self):
# Generator settings
self.inverted = False
self.do_not_generate_carrier = False # TODO : implement
self.do_not_generate_lha = False # TODO : implement
self.do_not_generate_player_navy = True # TODO : implement
self.do_not_generate_enemy_navy = True # TODO : implement
# Difficulty settings
player_skill: str = "Good"
enemy_skill: str = "Average"
enemy_vehicle_skill: str = "Average"
map_coalition_visibility: ForcedOptions.Views = ForcedOptions.Views.All
labels: str = "Full"
only_player_takeoff: bool = True # Legacy parameter do not use
night_disabled: bool = False
external_views_allowed: bool = True
supercarrier: bool = False
generate_marks: bool = True
manpads: bool = True
version: Optional[str] = None
player_income_multiplier: float = 1.0
enemy_income_multiplier: float = 1.0
# Difficulty settings
self.player_skill = "Good"
self.enemy_skill = "Average"
self.enemy_vehicle_skill = "Average"
self.map_coalition_visibility = "All Units"
self.labels = "Full"
self.only_player_takeoff = True # Legacy parameter do not use
self.night_disabled = False
self.external_views_allowed = True
self.supercarrier = False
self.multiplier = 1
self.generate_marks = True
self.sams = True # Legacy parameter do not use
self.cold_start = False # Legacy parameter do not use
self.version = None
self.include_jtac_if_available = True
self.jtac_smoke_on = True
default_start_type: str = "Cold"
# Performance oriented
self.perf_red_alert_state = True
self.perf_smoke_gen = True
self.perf_artillery = True
self.perf_moving_units = True
self.perf_infantry = True
self.perf_ai_parking_start = True
self.perf_destroyed_units = True
# Campaign management
automate_runway_repair: bool = False
automate_front_line_reinforcements: bool = False
automate_aircraft_reinforcements: bool = False
restrict_weapons_by_date: bool = False
# Performance culling
self.perf_culling = False
self.perf_culling_distance = 100
# Performance oriented
perf_red_alert_state: bool = True
perf_smoke_gen: bool = True
perf_artillery: bool = True
perf_moving_units: bool = True
perf_infantry: bool = True
perf_destroyed_units: bool = True
# Performance culling
perf_culling: bool = False
perf_culling_distance: int = 100
perf_do_not_cull_carrier = True
# LUA Plugins system
plugins: Dict[str, bool] = field(default_factory=dict)
# Cheating
show_red_ato: bool = False
enable_frontline_cheats: bool = False
enable_base_capture_cheat: bool = False
never_delay_player_flights: bool = False
@staticmethod
def plugin_settings_key(identifier: str) -> str:
return f"plugins.{identifier}"
def initialize_plugin_option(self, identifier: str, default_value: bool) -> None:
try:
self.plugin_option(identifier)
except KeyError:
self.set_plugin_option(identifier, default_value)
def plugin_option(self, identifier: str) -> bool:
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 __setstate__(self, state) -> None:
# __setstate__ is called with the dict of the object being unpickled. We
# can provide save compatibility for new settings options (which
# normally would not be present in the unpickled object) by creating a
# new settings object, updating it with the unpickled state, and
# updating our dict with that.
new_state = Settings().__dict__
new_state.update(state)
self.__dict__.update(new_state)

5
game/theater/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
from .base import *
from .conflicttheater import *
from .controlpoint import *
from .missiontarget import MissionTarget
from .theatergroundobject import SamGroundObject

View File

@@ -1,14 +1,16 @@
import logging
import typing
import math
import itertools
import logging
import math
import typing
from typing import Dict, Type
from dcs.planes import *
from dcs.vehicles import *
from dcs.task import *
from dcs.task import CAP, CAS, Embarking, PinpointStrike, Task
from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.vehicles import AirDefence, Armor
from game import db
from gen import aaa
from game.db import PRICES
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
STRENGTH_AA_ASSEMBLE_MIN = 0.2
PLANES_SCRAMBLE_MIN_BASE = 2
@@ -20,52 +22,82 @@ BASE_MIN_STRENGTH = 0
class Base:
aircraft = {} # type: typing.Dict[PlaneType, int]
armor = {} # type: typing.Dict[Armor, int]
aa = {} # type: typing.Dict[AirDefence, int]
strength = 1 # type: float
commision_points = {}
def __init__(self):
self.aircraft = {}
self.armor = {}
self.aa = {}
self.commision_points = {}
self.aircraft: Dict[Type[FlyingType], int] = {}
self.armor: Dict[Type[VehicleType], int] = {}
self.aa: Dict[AirDefence, int] = {}
self.commision_points: Dict[Type, float] = {}
self.strength = 1
@property
def total_planes(self) -> int:
def total_aircraft(self) -> int:
return sum(self.aircraft.values())
@property
def total_armor(self) -> int:
return sum(self.armor.values())
@property
def total_armor_value(self) -> int:
total = 0
for unit_type, count in self.armor.items():
try:
total += PRICES[unit_type] * count
except KeyError:
logging.exception(f"No price found for {unit_type.id}")
return total
@property
def total_frontline_aa(self) -> int:
return sum([v for k, v in self.armor.items() if k in TYPE_SHORAD])
@property
def total_aa(self) -> int:
return sum(self.aa.values())
def total_units(self, task: Task) -> int:
return sum([c for t, c in itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items()) if t in db.UNIT_BY_TASK[task]])
return sum(
[
c
for t, c in itertools.chain(
self.aircraft.items(), self.armor.items(), self.aa.items()
)
if t in db.UNIT_BY_TASK[task]
]
)
def total_units_of_type(self, unit_type) -> int:
return sum([c for t, c in itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items()) if t == unit_type])
return sum(
[
c
for t, c in itertools.chain(
self.aircraft.items(), self.armor.items(), self.aa.items()
)
if t == unit_type
]
)
@property
def all_units(self):
return itertools.chain(self.aircraft.items(), self.armor.items(), self.aa.items())
return itertools.chain(
self.aircraft.items(), self.armor.items(), self.aa.items()
)
def _find_best_unit(self, dict, for_type: Task, count: int) -> typing.Dict:
def _find_best_unit(
self, available_units: Dict[UnitType, int], for_type: Task, count: int
) -> Dict[UnitType, int]:
if count <= 0:
logging.warning("{}: no units for {}".format(self, for_type))
return {}
sorted_units = [key for key in dict.keys() if key in db.UNIT_BY_TASK[for_type]]
sorted_units = [
key for key in available_units if key in db.UNIT_BY_TASK[for_type]
]
sorted_units.sort(key=lambda x: db.PRICES[x], reverse=True)
result = {}
result: Dict[UnitType, int] = {}
for unit_type in sorted_units:
existing_count = dict[unit_type] # type: int
existing_count = available_units[unit_type] # type: int
if not existing_count:
continue
@@ -81,14 +113,18 @@ class Base:
logging.info("{} for {} ({}): {}".format(self, for_type, count, result))
return result
def _find_best_planes(self, for_type: Task, count: int) -> typing.Dict[PlaneType, int]:
def _find_best_planes(
self, for_type: Task, count: int
) -> typing.Dict[FlyingType, int]:
return self._find_best_unit(self.aircraft, for_type, count)
def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]:
return self._find_best_unit(self.armor, for_type, count)
def append_commision_points(self, for_type, points: float) -> int:
self.commision_points[for_type] = self.commision_points.get(for_type, 0) + points
self.commision_points[for_type] = (
self.commision_points.get(for_type, 0) + points
)
points = self.commision_points[for_type]
if points >= 1:
self.commision_points[for_type] = points - math.floor(points)
@@ -97,15 +133,17 @@ class Base:
return 0
def filter_units(self, applicable_units: typing.Collection):
self.aircraft = {k: v for k, v in self.aircraft.items() if k in applicable_units}
self.aircraft = {
k: v for k, v in self.aircraft.items() if k in applicable_units
}
self.armor = {k: v for k, v in self.armor.items() if k in applicable_units}
def commision_units(self, units: typing.Dict[typing.Any, int]):
for value in units.values():
assert value > 0
assert value == math.floor(value)
for unit_type, unit_count in units.items():
if unit_count <= 0:
continue
for_task = db.unit_task(unit_type)
target_dict = None
@@ -116,8 +154,10 @@ class Base:
elif for_task == AirDefence:
target_dict = self.aa
assert target_dict is not None
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
if target_dict is not None:
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
else:
logging.error("Unable to determine target dict for " + str(unit_type))
def commit_losses(self, units_lost: typing.Dict[typing.Any, int]):
@@ -134,7 +174,7 @@ class Base:
if unit_type not in target_array:
print("Base didn't find event type {}".format(unit_type))
continue
target_array[unit_type] = max(target_array[unit_type] - count, 0)
if target_array[unit_type] == 0:
del target_array[unit_type]
@@ -146,14 +186,25 @@ class Base:
elif self.strength <= 0:
self.strength = BASE_MIN_STRENGTH
def set_strength_to_minimum(self) -> None:
self.strength = BASE_MIN_STRENGTH
def scramble_count(self, multiplier: float, task: Task = None) -> int:
if task:
count = sum([v for k, v in self.aircraft.items() if db.unit_task(k) == task])
count = sum(
[v for k, v in self.aircraft.items() if db.unit_task(k) == task]
)
else:
count = self.total_planes
count = self.total_aircraft
count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength))
return min(min(max(count, PLANES_SCRAMBLE_MIN_BASE), int(PLANES_SCRAMBLE_MAX_BASE * multiplier)), count)
return min(
min(
max(count, PLANES_SCRAMBLE_MIN_BASE),
int(PLANES_SCRAMBLE_MAX_BASE * multiplier),
),
count,
)
def assemble_count(self):
return int(self.total_armor * 0.5)
@@ -162,18 +213,18 @@ class Base:
# previous logic removed because we always want the full air defense capabilities.
return self.total_aa
def scramble_sweep(self, multiplier: float) -> typing.Dict[PlaneType, int]:
def scramble_sweep(self, multiplier: float) -> typing.Dict[FlyingType, int]:
return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP))
def scramble_last_defense(self):
# return as many CAP-capable aircraft as we can since this is the last defense of the base
# (but not more than 20 - that's just nuts)
return self._find_best_planes(CAP, min(self.total_planes, 20))
return self._find_best_planes(CAP, min(self.total_aircraft, 20))
def scramble_cas(self, multiplier: float) -> typing.Dict[PlaneType, int]:
def scramble_cas(self, multiplier: float) -> typing.Dict[FlyingType, int]:
return self._find_best_planes(CAS, self.scramble_count(multiplier, CAS))
def scramble_interceptors(self, multiplier: float) -> typing.Dict[PlaneType, int]:
def scramble_interceptors(self, multiplier: float) -> typing.Dict[FlyingType, int]:
return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP))
def assemble_attack(self) -> typing.Dict[Armor, int]:
@@ -184,4 +235,8 @@ class Base:
return self._find_best_armor(PinpointStrike, count)
def assemble_aa(self, count=None) -> typing.Dict[AirDefence, int]:
return self._find_best_unit(self.aa, AirDefence, count and min(count, self.total_aa) or self.assemble_aa_count())
return self._find_best_unit(
self.aa,
AirDefence,
count and min(count, self.total_aa) or self.assemble_aa_count(),
)

View File

@@ -0,0 +1,963 @@
from __future__ import annotations
import itertools
import json
import logging
from dataclasses import dataclass
from functools import cached_property
from itertools import tee
from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union, cast
from shapely import geometry
from shapely import ops
from dcs import Mission
from dcs.countries import (
CombinedJointTaskForcesBlue,
CombinedJointTaskForcesRed,
)
from dcs.country import Country
from dcs.mapping import Point
from dcs.planes import F_15C
from dcs.ships import (
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
USS_Arleigh_Burke_IIa,
)
from dcs.statics import Fortification
from dcs.terrain import (
caucasus,
nevada,
normandy,
persiangulf,
syria,
thechannel,
)
from dcs.terrain.terrain import Airport, Terrain
from dcs.unitgroup import (
FlyingGroup,
Group,
ShipGroup,
StaticGroup,
VehicleGroup,
)
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from gen.flights.flight import FlightType
from .controlpoint import (
Airfield,
Carrier,
ControlPoint,
Lha,
MissionTarget,
OffMapSpawn,
Fob,
)
from .landmap import Landmap, load_landmap, poly_contains
from ..utils import Distance, meters, nautical_miles
Numeric = Union[int, float]
SIZE_TINY = 150
SIZE_SMALL = 600
SIZE_REGULAR = 1000
SIZE_BIG = 2000
SIZE_LARGE = 3000
IMPORTANCE_LOW = 1
IMPORTANCE_MEDIUM = 1.2
IMPORTANCE_HIGH = 1.4
FRONTLINE_MIN_CP_DISTANCE = 5000
def pairwise(iterable):
"""
itertools recipe
s -> (s0,s1), (s1,s2), (s2, s3), ...
"""
a, b = tee(iterable)
next(b, None)
return zip(a, b)
class MizCampaignLoader:
BLUE_COUNTRY = CombinedJointTaskForcesBlue()
RED_COUNTRY = CombinedJointTaskForcesRed()
OFF_MAP_UNIT_TYPE = F_15C.id
CV_UNIT_TYPE = CVN_74_John_C__Stennis.id
LHA_UNIT_TYPE = LHA_1_Tarawa.id
FRONT_LINE_UNIT_TYPE = Armor.APC_M113.id
FOB_UNIT_TYPE = Unarmed.CP_SKP_11_ATC_Mobile_Command_Post.id
EWR_UNIT_TYPE = AirDefence.EWR_55G6.id
SAM_UNIT_TYPE = AirDefence.SAM_SA_10_S_300PS_SR_64H6E.id
GARRISON_UNIT_TYPE = AirDefence.SAM_SA_19_Tunguska_2S6.id
OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id
SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id
MISSILE_SITE_UNIT_TYPE = MissilesSS.SRBM_SS_1C_Scud_B_9K72_LN_9P117M.id
COASTAL_DEFENSE_UNIT_TYPE = MissilesSS.SS_N_2_Silkworm.id
# Multiple options for the required SAMs so campaign designers can more
# accurately see the coverage of their IADS for the expected type.
REQUIRED_LONG_RANGE_SAM_UNIT_TYPES = {
AirDefence.SAM_Patriot_LN_M901.id,
AirDefence.SAM_SA_10_S_300PS_LN_5P85C.id,
AirDefence.SAM_SA_10_S_300PS_LN_5P85D.id,
}
REQUIRED_MEDIUM_RANGE_SAM_UNIT_TYPES = {
AirDefence.SAM_Hawk_LN_M192.id,
AirDefence.SAM_SA_2_LN_SM_90.id,
AirDefence.SAM_SA_3_S_125_LN_5P73.id,
}
BASE_DEFENSE_RADIUS = nautical_miles(2)
def __init__(self, miz: Path, theater: ConflictTheater) -> None:
self.theater = theater
self.mission = Mission()
self.mission.load_file(str(miz))
self.control_point_id = itertools.count(1000)
# If there are no red carriers there usually aren't red units. Make sure
# both countries are initialized so we don't have to deal with None.
if self.mission.country(self.BLUE_COUNTRY.name) is None:
self.mission.coalition["blue"].add_country(self.BLUE_COUNTRY)
if self.mission.country(self.RED_COUNTRY.name) is None:
self.mission.coalition["red"].add_country(self.RED_COUNTRY)
@staticmethod
def control_point_from_airport(airport: Airport) -> ControlPoint:
# The wiki says this is a legacy property and to just use regular.
size = SIZE_REGULAR
# The importance is taken from the periodicity of the airport's
# warehouse divided by 10. 30 is the default, and out of range (valid
# values are between 1.0 and 1.4). If it is used, pick the default
# importance.
if airport.periodicity == 30:
importance = IMPORTANCE_MEDIUM
else:
importance = airport.periodicity / 10
cp = Airfield(airport, size, importance)
cp.captured = airport.is_blue()
# Use the unlimited aircraft option to determine if an airfield should
# be owned by the player when the campaign is "inverted".
cp.captured_invert = airport.unlimited_aircrafts
return cp
def country(self, blue: bool) -> Country:
country = self.mission.country(
self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name
)
# Should be guaranteed because we initialized them.
assert country
return country
@property
def blue(self) -> Country:
return self.country(blue=True)
@property
def red(self) -> Country:
return self.country(blue=False)
def off_map_spawns(self, blue: bool) -> Iterator[FlyingGroup]:
for group in self.country(blue).plane_group:
if group.units[0].type == self.OFF_MAP_UNIT_TYPE:
yield group
def carriers(self, blue: bool) -> Iterator[ShipGroup]:
for group in self.country(blue).ship_group:
if group.units[0].type == self.CV_UNIT_TYPE:
yield group
def lhas(self, blue: bool) -> Iterator[ShipGroup]:
for group in self.country(blue).ship_group:
if group.units[0].type == self.LHA_UNIT_TYPE:
yield group
def fobs(self, blue: bool) -> Iterator[VehicleGroup]:
for group in self.country(blue).vehicle_group:
if group.units[0].type == self.FOB_UNIT_TYPE:
yield group
@property
def ships(self) -> Iterator[ShipGroup]:
for group in self.blue.ship_group:
if group.units[0].type == self.SHIP_UNIT_TYPE:
yield group
@property
def ewrs(self) -> Iterator[VehicleGroup]:
for group in self.blue.vehicle_group:
if group.units[0].type == self.EWR_UNIT_TYPE:
yield group
@property
def sams(self) -> Iterator[VehicleGroup]:
for group in self.blue.vehicle_group:
if group.units[0].type == self.SAM_UNIT_TYPE:
yield group
@property
def garrisons(self) -> Iterator[VehicleGroup]:
for group in self.blue.vehicle_group:
if group.units[0].type == self.GARRISON_UNIT_TYPE:
yield group
@property
def offshore_strike_targets(self) -> Iterator[StaticGroup]:
for group in self.blue.static_group:
if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE:
yield group
@property
def missile_sites(self) -> Iterator[VehicleGroup]:
for group in self.blue.vehicle_group:
if group.units[0].type == self.MISSILE_SITE_UNIT_TYPE:
yield group
@property
def coastal_defenses(self) -> Iterator[VehicleGroup]:
for group in self.blue.vehicle_group:
if group.units[0].type == self.COASTAL_DEFENSE_UNIT_TYPE:
yield group
@property
def required_long_range_sams(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type in self.REQUIRED_LONG_RANGE_SAM_UNIT_TYPES:
yield group
@property
def required_medium_range_sams(self) -> Iterator[VehicleGroup]:
for group in self.red.vehicle_group:
if group.units[0].type in self.REQUIRED_MEDIUM_RANGE_SAM_UNIT_TYPES:
yield group
@cached_property
def control_points(self) -> Dict[int, ControlPoint]:
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)
control_points[control_point.id] = control_point
for blue in (False, True):
for group in self.off_map_spawns(blue):
control_point = OffMapSpawn(
next(self.control_point_id), str(group.name), group.position
)
control_point.captured = blue
control_point.captured_invert = group.late_activation
control_points[control_point.id] = control_point
for group in self.carriers(blue):
# TODO: Name the carrier.
control_point = Carrier(
"carrier", group.position, next(self.control_point_id)
)
control_point.captured = blue
control_point.captured_invert = group.late_activation
control_points[control_point.id] = control_point
for group in self.lhas(blue):
# TODO: Name the LHA.
control_point = Lha("lha", group.position, next(self.control_point_id))
control_point.captured = blue
control_point.captured_invert = group.late_activation
control_points[control_point.id] = control_point
for group in self.fobs(blue):
control_point = Fob(
str(group.name), group.position, next(self.control_point_id)
)
control_point.captured = blue
control_point.captured_invert = group.late_activation
control_points[control_point.id] = control_point
return control_points
@property
def front_line_path_groups(self) -> Iterator[VehicleGroup]:
for group in self.country(blue=True).vehicle_group:
if group.units[0].type == self.FRONT_LINE_UNIT_TYPE:
yield group
@cached_property
def front_lines(self) -> Dict[str, ComplexFrontLine]:
# Dict of front line ID to a front line.
front_lines = {}
for group in self.front_line_path_groups:
# The unit will have its first waypoint at the source CP and the
# final waypoint at the destination CP. Intermediate waypoints
# define the curve of the front line.
waypoints = [p.position for p in group.points]
origin = self.theater.closest_control_point(waypoints[0])
if origin is None:
raise RuntimeError(
f"No control point near the first waypoint of {group.name}"
)
destination = self.theater.closest_control_point(waypoints[-1])
if destination is None:
raise RuntimeError(
f"No control point near the final waypoint of {group.name}"
)
# Snap the begin and end points to the control points.
waypoints[0] = origin.position
waypoints[-1] = destination.position
front_line_id = f"{origin.id}|{destination.id}"
front_lines[front_line_id] = ComplexFrontLine(origin, waypoints)
self.control_points[origin.id].connect(self.control_points[destination.id])
self.control_points[destination.id].connect(self.control_points[origin.id])
return front_lines
def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]:
closest = self.theater.closest_control_point(group.position)
distance = meters(closest.position.distance_to_point(group.position))
return closest, distance
def add_preset_locations(self) -> None:
for group in self.garrisons:
closest, distance = self.objective_info(group)
if distance < self.BASE_DEFENSE_RADIUS:
closest.preset_locations.base_garrisons.append(group.position)
else:
logging.warning(f"Found garrison unit too far from base: {group.name}")
for group in self.sams:
closest, distance = self.objective_info(group)
if distance < self.BASE_DEFENSE_RADIUS:
closest.preset_locations.base_air_defense.append(group.position)
else:
closest.preset_locations.strike_locations.append(group.position)
for group in self.ewrs:
closest, distance = self.objective_info(group)
closest.preset_locations.ewrs.append(group.position)
for group in self.offshore_strike_targets:
closest, distance = self.objective_info(group)
closest.preset_locations.offshore_strike_locations.append(group.position)
for group in self.ships:
closest, distance = self.objective_info(group)
closest.preset_locations.ships.append(group.position)
for group in self.missile_sites:
closest, distance = self.objective_info(group)
closest.preset_locations.missile_sites.append(group.position)
for group in self.coastal_defenses:
closest, distance = self.objective_info(group)
closest.preset_locations.coastal_defenses.append(group.position)
for group in self.required_long_range_sams:
closest, distance = self.objective_info(group)
closest.preset_locations.required_long_range_sams.append(group.position)
for group in self.required_medium_range_sams:
closest, distance = self.objective_info(group)
closest.preset_locations.required_medium_range_sams.append(group.position)
def populate_theater(self) -> None:
for control_point in self.control_points.values():
self.theater.add_controlpoint(control_point)
self.add_preset_locations()
self.theater.set_frontline_data(self.front_lines)
@dataclass
class ReferencePoint:
world_coordinates: Point
image_coordinates: Point
class ConflictTheater:
terrain: Terrain
reference_points: Tuple[ReferencePoint, ReferencePoint]
overview_image: str
landmap: Optional[Landmap]
"""
land_poly = None # type: Polygon
"""
daytime_map: Dict[str, Tuple[int, int]]
_frontline_data: Optional[Dict[str, ComplexFrontLine]] = None
def __init__(self):
self.controlpoints: List[ControlPoint] = []
self._frontline_data: Optional[Dict[str, ComplexFrontLine]] = None
"""
self.land_poly = geometry.Polygon(self.landmap[0][0])
for x in self.landmap[1]:
self.land_poly = self.land_poly.difference(geometry.Polygon(x))
"""
@property
def frontline_data(self) -> Optional[Dict[str, ComplexFrontLine]]:
if self._frontline_data is None:
self.load_frontline_data_from_file()
return self._frontline_data
def load_frontline_data_from_file(self) -> None:
if self._frontline_data is not None:
logging.warning("Replacing existing frontline data from file")
self._frontline_data = FrontLine.load_json_frontlines(self)
if self._frontline_data is None:
self._frontline_data = {}
def set_frontline_data(self, data: Dict[str, ComplexFrontLine]) -> None:
if self._frontline_data is not None:
logging.warning("Replacing existing frontline data")
self._frontline_data = data
def add_controlpoint(
self, point: ControlPoint, connected_to: Optional[List[ControlPoint]] = None
):
if connected_to is None:
connected_to = []
for connected_point in connected_to:
point.connect(to=connected_point)
self.controlpoints.append(point)
def find_ground_objects_by_obj_name(self, obj_name):
found = []
for cp in self.controlpoints:
for g in cp.ground_objects:
if g.obj_name == obj_name:
found.append(g)
return found
def is_in_sea(self, point: Point) -> bool:
if not self.landmap:
return False
if self.is_on_land(point):
return False
for exclusion_zone in self.landmap.exclusion_zones:
if poly_contains(point.x, point.y, exclusion_zone):
return False
for sea in self.landmap.sea_zones:
if poly_contains(point.x, point.y, sea):
return True
return False
def is_on_land(self, point: Point) -> bool:
if not self.landmap:
return True
is_point_included = False
if poly_contains(point.x, point.y, self.landmap.inclusion_zones):
is_point_included = True
if not is_point_included:
return False
for exclusion_zone in self.landmap.exclusion_zones:
if poly_contains(point.x, point.y, exclusion_zone):
return False
return True
def nearest_land_pos(self, point: Point, extend_dist: int = 50) -> Point:
"""Returns the nearest point inside a land exclusion zone from point
`extend_dist` determines how far inside the zone the point should be placed"""
if self.is_on_land(point):
return point
point = geometry.Point(point.x, point.y)
nearest_points = []
if not self.landmap:
raise RuntimeError("Landmap not initialized")
for inclusion_zone in self.landmap.inclusion_zones:
nearest_pair = ops.nearest_points(point, inclusion_zone)
nearest_points.append(nearest_pair[1])
min_distance = point.distance(nearest_points[0]) # type: geometry.Point
nearest_point = nearest_points[0] # type: geometry.Point
for pt in nearest_points[1:]:
distance = point.distance(pt)
if distance < min_distance:
min_distance = distance
nearest_point = pt
assert isinstance(nearest_point, geometry.Point)
point = Point(point.x, point.y)
nearest_point = Point(nearest_point.x, nearest_point.y)
new_point = point.point_from_heading(
point.heading_between_point(nearest_point),
point.distance_to_point(nearest_point) + extend_dist,
)
return new_point
def control_points_for(self, player: bool) -> Iterator[ControlPoint]:
for point in self.controlpoints:
if point.captured == player:
yield point
def player_points(self) -> List[ControlPoint]:
return list(self.control_points_for(player=True))
def conflicts(self, from_player=True) -> Iterator[FrontLine]:
for cp in [x for x in self.controlpoints if x.captured == from_player]:
for connected_point in [
x for x in cp.connected_points if x.captured != from_player
]:
yield FrontLine(cp, connected_point, self)
def enemy_points(self) -> List[ControlPoint]:
return list(self.control_points_for(player=False))
def closest_control_point(self, point: Point) -> ControlPoint:
closest = self.controlpoints[0]
closest_distance = point.distance_to_point(closest.position)
for control_point in self.controlpoints[1:]:
distance = point.distance_to_point(control_point.position)
if distance < closest_distance:
closest = control_point
closest_distance = distance
return closest
def closest_target(self, point: Point) -> MissionTarget:
closest: MissionTarget = self.controlpoints[0]
closest_distance = point.distance_to_point(closest.position)
for control_point in self.controlpoints[1:]:
distance = point.distance_to_point(control_point.position)
if distance < closest_distance:
closest = control_point
closest_distance = distance
for tgo in control_point.ground_objects:
distance = point.distance_to_point(tgo.position)
if distance < closest_distance:
closest = tgo
closest_distance = distance
for conflict in self.conflicts():
distance = conflict.position.distance_to_point(point)
if distance < closest_distance:
closest = conflict
closest_distance = distance
return closest
def closest_opposing_control_points(self) -> Tuple[ControlPoint, ControlPoint]:
"""
Returns a tuple of the two nearest opposing ControlPoints in theater.
(player_cp, enemy_cp)
"""
all_cp_min_distances = {}
for idx, control_point in enumerate(self.controlpoints):
distances = {}
closest_distance = None
for i, cp in enumerate(self.controlpoints):
if i != idx and cp.captured is not control_point.captured:
dist = cp.position.distance_to_point(control_point.position)
if not closest_distance:
closest_distance = dist
distances[cp.id] = dist
if dist < closest_distance:
distances[cp.id] = dist
closest_cp_id = min(distances, key=distances.get) # type: ignore
all_cp_min_distances[(control_point.id, closest_cp_id)] = distances[
closest_cp_id
]
closest_opposing_cps = [
self.find_control_point_by_id(i)
for i in min(
all_cp_min_distances, key=all_cp_min_distances.get
) # type: ignore
] # type: List[ControlPoint]
assert len(closest_opposing_cps) == 2
if closest_opposing_cps[0].captured:
return cast(Tuple[ControlPoint, ControlPoint], tuple(closest_opposing_cps))
else:
return cast(
Tuple[ControlPoint, ControlPoint], tuple(reversed(closest_opposing_cps))
)
def find_control_point_by_id(self, id: int) -> ControlPoint:
for i in self.controlpoints:
if i.id == id:
return i
raise RuntimeError(f"Cannot find ControlPoint with ID {id}")
def add_json_cp(self, theater, p: dict) -> ControlPoint:
cp: ControlPoint
if p["type"] == "airbase":
airbase = theater.terrain.airports[p["id"]]
if "size" in p.keys():
size = p["size"]
else:
size = SIZE_REGULAR
if "importance" in p.keys():
importance = p["importance"]
else:
importance = IMPORTANCE_MEDIUM
cp = Airfield(airbase, size, importance)
elif p["type"] == "carrier":
cp = Carrier("carrier", Point(p["x"], p["y"]), p["id"])
else:
cp = Lha("lha", Point(p["x"], p["y"]), p["id"])
if "captured_invert" in p.keys():
cp.captured_invert = p["captured_invert"]
else:
cp.captured_invert = False
return cp
@staticmethod
def from_json(directory: Path, data: Dict[str, Any]) -> ConflictTheater:
theaters = {
"Caucasus": CaucasusTheater,
"Nevada": NevadaTheater,
"Persian Gulf": PersianGulfTheater,
"Normandy": NormandyTheater,
"The Channel": TheChannelTheater,
"Syria": SyriaTheater,
}
theater = theaters[data["theater"]]
t = theater()
miz = data.get("miz", None)
if miz is not None:
MizCampaignLoader(directory / miz, t).populate_theater()
return t
cps = {}
for p in data["player_points"]:
cp = t.add_json_cp(theater, p)
cp.captured = True
cps[p["id"]] = cp
t.add_controlpoint(cp)
for p in data["enemy_points"]:
cp = t.add_json_cp(theater, p)
cps[p["id"]] = cp
t.add_controlpoint(cp)
for l in data["links"]:
cps[l[0]].connect(cps[l[1]])
cps[l[1]].connect(cps[l[0]])
return t
class CaucasusTheater(ConflictTheater):
terrain = caucasus.Caucasus()
overview_image = "caumap.gif"
reference_points = (
ReferencePoint(caucasus.Gelendzhik.position, Point(176, 298)),
ReferencePoint(caucasus.Batumi.position, Point(1307, 1205)),
)
landmap = load_landmap("resources\\caulandmap.p")
daytime_map = {
"dawn": (6, 9),
"day": (9, 18),
"dusk": (18, 20),
"night": (0, 5),
}
class PersianGulfTheater(ConflictTheater):
terrain = persiangulf.PersianGulf()
overview_image = "persiangulf.gif"
reference_points = (
ReferencePoint(persiangulf.Jiroft_Airport.position, Point(1692, 1343)),
ReferencePoint(persiangulf.Liwa_Airbase.position, Point(358, 3238)),
)
landmap = load_landmap("resources\\gulflandmap.p")
daytime_map = {
"dawn": (6, 8),
"day": (8, 16),
"dusk": (16, 18),
"night": (0, 5),
}
class NevadaTheater(ConflictTheater):
terrain = nevada.Nevada()
overview_image = "nevada.gif"
reference_points = (
ReferencePoint(nevada.Mina_Airport_3Q0.position, Point(252, 295)),
ReferencePoint(nevada.Laughlin_Airport.position, Point(844, 909)),
)
landmap = load_landmap("resources\\nevlandmap.p")
daytime_map = {
"dawn": (4, 6),
"day": (6, 17),
"dusk": (17, 18),
"night": (0, 5),
}
class NormandyTheater(ConflictTheater):
terrain = normandy.Normandy()
overview_image = "normandy.gif"
reference_points = (
ReferencePoint(normandy.Needs_Oar_Point.position, Point(515, 329)),
ReferencePoint(normandy.Evreux.position, Point(2029, 1709)),
)
landmap = load_landmap("resources\\normandylandmap.p")
daytime_map = {
"dawn": (6, 8),
"day": (10, 17),
"dusk": (17, 18),
"night": (0, 5),
}
class TheChannelTheater(ConflictTheater):
terrain = thechannel.TheChannel()
overview_image = "thechannel.gif"
reference_points = (
ReferencePoint(thechannel.Abbeville_Drucat.position, Point(2005, 2390)),
ReferencePoint(thechannel.Detling.position, Point(706, 382)),
)
landmap = load_landmap("resources\\channellandmap.p")
daytime_map = {
"dawn": (6, 8),
"day": (10, 17),
"dusk": (17, 18),
"night": (0, 5),
}
class SyriaTheater(ConflictTheater):
terrain = syria.Syria()
overview_image = "syria.gif"
reference_points = (
ReferencePoint(syria.Eyn_Shemer.position, Point(564, 1289)),
ReferencePoint(syria.Tabqa.position, Point(1329, 491)),
)
landmap = load_landmap("resources\\syrialandmap.p")
daytime_map = {
"dawn": (6, 8),
"day": (8, 16),
"dusk": (16, 18),
"night": (0, 5),
}
@dataclass
class ComplexFrontLine:
"""
Stores data necessary for building a multi-segment frontline.
"points" should be ordered from closest to farthest distance originating from start_cp.position
"""
start_cp: ControlPoint
points: List[Point]
@dataclass
class FrontLineSegment:
"""
Describes a line segment of a FrontLine
"""
point_a: Point
point_b: Point
@property
def attack_heading(self) -> Numeric:
"""The heading of the frontline segment from player to enemy control point"""
return self.point_a.heading_between_point(self.point_b)
@property
def attack_distance(self) -> Numeric:
"""Length of the segment"""
return self.point_a.distance_to_point(self.point_b)
class FrontLine(MissionTarget):
"""Defines a front line location between two control points.
Front lines are the area where ground combat happens.
Overwrites the entirety of MissionTarget __init__ method to allow for
dynamic position calculation.
"""
def __init__(
self,
control_point_a: ControlPoint,
control_point_b: ControlPoint,
theater: ConflictTheater,
) -> None:
self.control_point_a = control_point_a
self.control_point_b = control_point_b
self.segments: List[FrontLineSegment] = []
self.theater = theater
self._build_segments()
self.name = f"Front line {control_point_a}/{control_point_b}"
def is_friendly(self, to_player: bool) -> bool:
"""Returns True if the objective is in friendly territory."""
return False
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
yield from [
FlightType.CAS,
# TODO: FlightType.TROOP_TRANSPORT
# TODO: FlightType.EVAC
]
yield from super().mission_types(for_player)
@property
def position(self):
"""
The position where the conflict should occur
according to the current strength of each control point.
"""
return self.point_from_a(self._position_distance)
@property
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
"""Returns a tuple of the two control points."""
return self.control_point_a, self.control_point_b
@property
def attack_distance(self):
"""The total distance of all segments"""
return sum(i.attack_distance for i in self.segments)
@property
def attack_heading(self):
"""The heading of the active attack segment from player to enemy control point"""
return self.active_segment.attack_heading
@property
def active_segment(self) -> FrontLineSegment:
"""The FrontLine segment where there can be an active conflict"""
if self._position_distance <= self.segments[0].attack_distance:
return self.segments[0]
remaining_dist = self._position_distance
for segment in self.segments:
if remaining_dist <= segment.attack_distance:
return segment
else:
remaining_dist -= segment.attack_distance
logging.error(
"Frontline attack distance is greater than the sum of its segments"
)
return self.segments[0]
def point_from_a(self, distance: Numeric) -> Point:
"""
Returns a point {distance} away from control_point_a along the frontline segments.
"""
if distance < self.segments[0].attack_distance:
return self.control_point_a.position.point_from_heading(
self.segments[0].attack_heading, distance
)
remaining_dist = distance
for segment in self.segments:
if remaining_dist < segment.attack_distance:
return segment.point_a.point_from_heading(
segment.attack_heading, remaining_dist
)
else:
remaining_dist -= segment.attack_distance
@property
def _position_distance(self) -> float:
"""
The distance from point "a" where the conflict should occur
according to the current strength of each control point
"""
total_strength = (
self.control_point_a.base.strength + self.control_point_b.base.strength
)
if self.control_point_a.base.strength == 0:
return self._adjust_for_min_dist(0)
if self.control_point_b.base.strength == 0:
return self._adjust_for_min_dist(self.attack_distance)
strength_pct = self.control_point_a.base.strength / total_strength
return self._adjust_for_min_dist(strength_pct * self.attack_distance)
def _adjust_for_min_dist(self, distance: Numeric) -> Numeric:
"""
Ensures the frontline conflict is never located within the minimum distance
constant of either end control point.
"""
if (distance > self.attack_distance / 2) and (
distance + FRONTLINE_MIN_CP_DISTANCE > self.attack_distance
):
distance = self.attack_distance - FRONTLINE_MIN_CP_DISTANCE
elif (distance < self.attack_distance / 2) and (
distance < FRONTLINE_MIN_CP_DISTANCE
):
distance = FRONTLINE_MIN_CP_DISTANCE
return distance
def _build_segments(self) -> None:
"""Create line segments for the frontline"""
control_point_ids = "|".join(
[str(self.control_point_a.id), str(self.control_point_b.id)]
) # from_cp.id|to_cp.id
reversed_cp_ids = "|".join(
[str(self.control_point_b.id), str(self.control_point_a.id)]
)
complex_frontlines = self.theater.frontline_data
if (complex_frontlines) and (
(control_point_ids in complex_frontlines)
or (reversed_cp_ids in complex_frontlines)
):
# The frontline segments must be stored in the correct order for the distance algorithms to work.
# The points in the frontline are ordered from the id before the | to the id after.
# First, check if control point id pair matches in order, and create segments if a match is found.
if control_point_ids in complex_frontlines:
point_pairs = pairwise(complex_frontlines[control_point_ids].points)
for i in point_pairs:
self.segments.append(FrontLineSegment(i[0], i[1]))
# Check the reverse order and build in reverse if found.
elif reversed_cp_ids in complex_frontlines:
point_pairs = pairwise(
reversed(complex_frontlines[reversed_cp_ids].points)
)
for i in point_pairs:
self.segments.append(FrontLineSegment(i[0], i[1]))
# If no complex frontline has been configured, fall back to the old straight line method.
else:
self.segments.append(
FrontLineSegment(
self.control_point_a.position, self.control_point_b.position
)
)
@staticmethod
def load_json_frontlines(
theater: ConflictTheater,
) -> Optional[Dict[str, ComplexFrontLine]]:
"""Load complex frontlines from json"""
try:
path = Path(f"resources/frontlines/{theater.terrain.name.lower()}.json")
with open(path, "r") as file:
logging.debug(f"Loading frontline from {path}...")
data = json.load(file)
return {
frontline: ComplexFrontLine(
data[frontline]["start_cp"],
[Point(i[0], i[1]) for i in data[frontline]["points"]],
)
for frontline in data
}
except OSError:
logging.warning(
f"Unable to load preset frontlines for {theater.terrain.name}"
)
return None

View File

@@ -0,0 +1,953 @@
from __future__ import annotations
import heapq
import itertools
import logging
import random
import re
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from functools import total_ordering
from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type
from dcs.mapping import Point
from dcs.ships import (
CVN_74_John_C__Stennis,
CV_1143_5_Admiral_Kuznetsov,
LHA_1_Tarawa,
Type_071_Amphibious_Transport_Dock,
)
from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.unittype import FlyingType
from game import db
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
from gen.ground_forces.combat_stance import CombatStance
from gen.runways import RunwayAssigner, RunwayData
from .base import Base
from .missiontarget import MissionTarget
from .theatergroundobject import (
BaseDefenseGroundObject,
EwrGroundObject,
GenericCarrierGroundObject,
SamGroundObject,
TheaterGroundObject,
VehicleGroupGroundObject,
)
from ..db import PRICES
from ..utils import nautical_miles
from ..weather import Conditions
if TYPE_CHECKING:
from game import Game
from gen.flights.flight import FlightType
class ControlPointType(Enum):
#: An airbase with slots for everything.
AIRBASE = 0
#: A group with a Stennis type carrier (F/A-18, F-14 compatible).
AIRCRAFT_CARRIER_GROUP = 1
#: A group with a Tarawa carrier (Helicopters & Harrier).
LHA_GROUP = 2
#: A FARP, with slots for helicopters
FARP = 4
#: A FOB (ground units only)
FOB = 5
OFF_MAP = 6
class LocationType(Enum):
BaseAirDefense = "base air defense"
Coastal = "coastal defense"
Ewr = "EWR"
Garrison = "garrison"
MissileSite = "missile site"
OffshoreStrikeTarget = "offshore strike target"
Sam = "SAM"
Ship = "ship"
Shorad = "SHORAD"
StrikeTarget = "strike target"
@dataclass
class PresetLocations:
"""Defines the preset locations loaded from the campaign mission file."""
#: Locations used for spawning ground defenses for bases.
base_garrisons: List[Point] = field(default_factory=list)
#: Locations used for spawning air defenses for bases. Used by SAMs, AAA,
#: and SHORADs.
base_air_defense: List[Point] = field(default_factory=list)
#: Locations used by EWRs.
ewrs: List[Point] = field(default_factory=list)
#: Locations used by non-carrier ships. Carriers and LHAs are not random.
ships: List[Point] = field(default_factory=list)
#: Locations used by coastal defenses.
coastal_defenses: List[Point] = field(default_factory=list)
#: Locations used by ground based strike objectives.
strike_locations: List[Point] = field(default_factory=list)
#: Locations used by offshore strike objectives.
offshore_strike_locations: List[Point] = field(default_factory=list)
#: Locations used by missile sites like scuds and V-2s.
missile_sites: List[Point] = field(default_factory=list)
#: Locations of long range SAMs which should always be spawned.
required_long_range_sams: List[Point] = field(default_factory=list)
#: Locations of medium range SAMs which should always be spawned.
required_medium_range_sams: List[Point] = field(default_factory=list)
@staticmethod
def _random_from(points: List[Point]) -> Optional[Point]:
"""Finds, removes, and returns a random position from the given list."""
if not points:
return None
point = random.choice(points)
points.remove(point)
return point
def random_for(self, location_type: LocationType) -> Optional[Point]:
"""Returns a position suitable for the given location type.
The location, if found, will be claimed by the caller and not available
to subsequent calls.
"""
if location_type == LocationType.BaseAirDefense:
return self._random_from(self.base_air_defense)
if location_type == LocationType.Coastal:
return self._random_from(self.coastal_defenses)
if location_type == LocationType.Ewr:
return self._random_from(self.ewrs)
if location_type == LocationType.Garrison:
return self._random_from(self.base_garrisons)
if location_type == LocationType.MissileSite:
return self._random_from(self.missile_sites)
if location_type == LocationType.OffshoreStrikeTarget:
return self._random_from(self.offshore_strike_locations)
if location_type == LocationType.Sam:
return self._random_from(self.strike_locations)
if location_type == LocationType.Ship:
return self._random_from(self.ships)
if location_type == LocationType.Shorad:
return self._random_from(self.base_garrisons)
if location_type == LocationType.StrikeTarget:
return self._random_from(self.strike_locations)
logging.error(f"Unknown location type: {location_type}")
return None
@dataclass(frozen=True)
class PendingOccupancy:
present: int
ordered: int
transferring: int
@property
def total(self) -> int:
return self.present + self.ordered + self.transferring
@dataclass
class RunwayStatus:
damaged: bool = False
repair_turns_remaining: Optional[int] = None
def damage(self) -> None:
self.damaged = True
# If the runway is already under repair and is damaged again, progress
# is reset.
self.repair_turns_remaining = None
def begin_repair(self) -> None:
if self.repair_turns_remaining is not None:
logging.error("Runway already under repair. Restarting.")
self.repair_turns_remaining = 4
def process_turn(self) -> None:
if self.repair_turns_remaining is not None:
if self.repair_turns_remaining == 1:
self.repair_turns_remaining = None
self.damaged = False
else:
self.repair_turns_remaining -= 1
@property
def needs_repair(self) -> bool:
return self.damaged and self.repair_turns_remaining is None
def __str__(self) -> str:
if not self.damaged:
return "Runway operational"
turns_remaining = self.repair_turns_remaining
if turns_remaining is None:
return "Runway damaged"
return f"Runway repairing, {turns_remaining} turns remaining"
@total_ordering
class GroundUnitDestination:
def __init__(self, control_point: ControlPoint) -> None:
self.control_point = control_point
@property
def total_value(self) -> float:
return self.control_point.base.total_armor_value
def __eq__(self, other: Any) -> bool:
if not isinstance(other, GroundUnitDestination):
raise TypeError
return self.total_value == other.total_value
def __lt__(self, other: Any) -> bool:
if not isinstance(other, GroundUnitDestination):
raise TypeError
return self.total_value < other.total_value
class ControlPoint(MissionTarget, ABC):
position = None # type: Point
name = None # type: str
captured = False
has_frontline = True
alt = 0
# TODO: Only airbases have IDs.
# TODO: has_frontline is only reasonable for airbases.
# TODO: cptype is obsolete.
def __init__(
self,
cp_id: int,
name: str,
position: Point,
at: db.StartingPosition,
size: int,
importance: float,
has_frontline=True,
cptype=ControlPointType.AIRBASE,
):
super().__init__(name, position)
# TODO: Should be Airbase specific.
self.id = cp_id
self.full_name = name
self.at = at
self.connected_objectives: List[TheaterGroundObject] = []
self.base_defenses: List[BaseDefenseGroundObject] = []
self.preset_locations = PresetLocations()
# TODO: Should be Airbase specific.
self.size = size
self.importance = importance
self.captured = False
self.captured_invert = False
# TODO: Should be Airbase specific.
self.has_frontline = has_frontline
self.connected_points: List[ControlPoint] = []
self.base: Base = Base()
self.cptype = cptype
# TODO: Should be Airbase specific.
self.stances: Dict[int, CombatStance] = {}
from ..event import UnitsDeliveryEvent
self.pending_unit_deliveries = UnitsDeliveryEvent(self)
self.target_position: Optional[Point] = None
def __repr__(self):
return f"<{__class__}: {self.name}>"
@property
def ground_objects(self) -> List[TheaterGroundObject]:
return list(itertools.chain(self.connected_objectives, self.base_defenses))
@property
@abstractmethod
def heading(self) -> int:
...
def __str__(self):
return self.name
@property
def is_global(self):
return not self.connected_points
@property
def is_carrier(self):
"""
:return: Whether this control point is an aircraft carrier
"""
return False
@property
def is_fleet(self):
"""
:return: Whether this control point is a boat (mobile)
"""
return False
@property
def is_lha(self):
"""
:return: Whether this control point is an LHA
"""
return False
@property
def moveable(self) -> bool:
"""
:return: Whether this control point can be moved around
"""
return False
@property
@abstractmethod
def can_deploy_ground_units(self) -> bool:
...
@property
@abstractmethod
def total_aircraft_parking(self):
"""
:return: The maximum number of aircraft that can be stored in this
control point
"""
...
# TODO: Should be Airbase specific.
def connect(self, to: ControlPoint) -> None:
self.connected_points.append(to)
self.stances[to.id] = CombatStance.DEFENSIVE
@abstractmethod
def runway_is_operational(self) -> bool:
"""
Check whether this control point supports taking offs and landings.
:return:
"""
...
# TODO: Should be naval specific.
def get_carrier_group_name(self):
"""
Get the carrier group name if the airbase is a carrier
:return: Carrier group name
"""
if self.cptype in [
ControlPointType.AIRCRAFT_CARRIER_GROUP,
ControlPointType.LHA_GROUP,
]:
for g in self.ground_objects:
if g.dcs_identifier == "CARRIER":
for group in g.groups:
for u in group.units:
if db.unit_type_from_name(u.type) in [
CVN_74_John_C__Stennis,
CV_1143_5_Admiral_Kuznetsov,
]:
return group.name
elif g.dcs_identifier == "LHA":
for group in g.groups:
for u in group.units:
if db.unit_type_from_name(u.type) in [LHA_1_Tarawa]:
return group.name
return None
# TODO: Should be Airbase specific.
def is_connected(self, to) -> bool:
return to in self.connected_points
def find_ground_objects_by_obj_name(self, obj_name):
found = []
for g in self.ground_objects:
if g.obj_name == obj_name:
found.append(g)
return found
def is_friendly(self, to_player: bool) -> bool:
return self.captured == to_player
# TODO: Should be Airbase specific.
def clear_base_defenses(self) -> None:
for base_defense in self.base_defenses:
if isinstance(base_defense, EwrGroundObject):
self.preset_locations.ewrs.append(base_defense.position)
elif isinstance(base_defense, SamGroundObject):
self.preset_locations.base_air_defense.append(base_defense.position)
elif isinstance(base_defense, VehicleGroupGroundObject):
self.preset_locations.base_garrisons.append(base_defense.position)
else:
logging.error(
"Could not determine preset location type for "
f"{base_defense}. Assuming garrison type."
)
self.preset_locations.base_garrisons.append(base_defense.position)
self.base_defenses = []
def capture_equipment(self, game: Game) -> None:
total = self.base.total_armor_value
self.base.armor.clear()
game.adjust_budget(total, player=not self.captured)
game.message(
f"{self.name} is not connected to any friendly points. Ground "
f"vehicles have been captured and sold for ${total}M."
)
def retreat_ground_units(self, game: Game):
# When there are multiple valid destinations, deliver units to whichever
# base is least defended first. The closest approximation of unit
# strength we have is price
destinations = [
GroundUnitDestination(cp)
for cp in self.connected_points
if cp.captured == self.captured
]
if not destinations:
self.capture_equipment(game)
return
heapq.heapify(destinations)
destination = heapq.heappop(destinations)
while self.base.armor:
unit_type, count = self.base.armor.popitem()
for _ in range(count):
destination.control_point.base.commision_units({unit_type: 1})
destination = heapq.heappushpop(destinations, destination)
def capture_aircraft(
self, game: Game, airframe: Type[FlyingType], count: int
) -> None:
try:
value = PRICES[airframe] * count
except KeyError:
logging.exception(f"Unknown price for {airframe.id}")
return
game.adjust_budget(value, player=not self.captured)
game.message(
f"No valid retreat destination in range of {self.name} for "
f"{airframe.id}. {count} aircraft have been captured and sold for "
f"${value}M."
)
def aircraft_retreat_destination(
self, game: Game, airframe: Type[FlyingType]
) -> Optional[ControlPoint]:
closest = ObjectiveDistanceCache.get_closest_airfields(self)
# TODO: Should be airframe dependent.
max_retreat_distance = nautical_miles(200)
# Skip the first airbase because that's the airbase we're retreating
# from.
airfields = list(closest.airfields_within(max_retreat_distance))[1:]
for airbase in airfields:
if not airbase.can_operate(airframe):
continue
if airbase.captured != self.captured:
continue
if airbase.unclaimed_parking(game) > 0:
return airbase
return None
def _retreat_air_units(
self, game: Game, airframe: Type[FlyingType], count: int
) -> None:
while count:
logging.debug(f"Retreating {count} {airframe.id} from {self.name}")
destination = self.aircraft_retreat_destination(game, airframe)
if destination is None:
self.capture_aircraft(game, airframe, count)
return
parking = destination.unclaimed_parking(game)
transfer_amount = min([parking, count])
destination.base.commision_units({airframe: transfer_amount})
count -= transfer_amount
def retreat_air_units(self, game: Game) -> None:
# TODO: Capture in order of price to retain maximum value?
while self.base.aircraft:
airframe, count = self.base.aircraft.popitem()
self._retreat_air_units(game, airframe, count)
# TODO: Should be Airbase specific.
def capture(self, game: Game, for_player: bool) -> None:
self.pending_unit_deliveries.refund_all(game)
self.retreat_ground_units(game)
self.retreat_air_units(game)
if for_player:
self.captured = True
else:
self.captured = False
self.base.set_strength_to_minimum()
self.clear_base_defenses()
from .start_generator import BaseDefenseGenerator
BaseDefenseGenerator(game, self).generate()
@abstractmethod
def can_operate(self, aircraft: Type[FlyingType]) -> bool:
...
def aircraft_transferring(self, game: Game) -> int:
if self.captured:
ato = game.blue_ato
else:
ato = game.red_ato
total = 0
for package in ato.packages:
for flight in package.flights:
if flight.departure == flight.arrival:
continue
if flight.departure == self:
total -= flight.count
elif flight.arrival == self:
total += flight.count
return total
def expected_aircraft_next_turn(self, game: Game) -> PendingOccupancy:
on_order = 0
for unit_bought in self.pending_unit_deliveries.units:
if issubclass(unit_bought, FlyingType):
on_order += self.pending_unit_deliveries.units[unit_bought]
return PendingOccupancy(
self.base.total_aircraft, on_order, self.aircraft_transferring(game)
)
def unclaimed_parking(self, game: Game) -> int:
return (
self.total_aircraft_parking - self.expected_aircraft_next_turn(game).total
)
@abstractmethod
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
) -> RunwayData:
...
@property
def parking_slots(self) -> Iterator[ParkingSlot]:
yield from []
@property
@abstractmethod
def runway_status(self) -> RunwayStatus:
...
@property
def runway_can_be_repaired(self) -> bool:
return self.runway_status.needs_repair
def begin_runway_repair(self) -> None:
if not self.runway_can_be_repaired:
logging.error(f"Cannot repair runway at {self}")
return
self.runway_status.begin_repair()
def process_turn(self, game: Game) -> None:
self.pending_unit_deliveries.process(game)
runway_status = self.runway_status
if runway_status is not None:
runway_status.process_turn()
# Process movements for ships control points group
if self.target_position is not None:
delta = self.target_position - self.position
self.position = self.target_position
self.target_position = None
# Move the linked unit groups
for ground_object in self.ground_objects:
if isinstance(ground_object, GenericCarrierGroundObject):
ground_object.position.x = ground_object.position.x + delta.x
ground_object.position.y = ground_object.position.y + delta.y
for group in ground_object.groups:
for u in group.units:
u.position.x = u.position.x + delta.x
u.position.y = u.position.y + delta.y
@property
def pending_frontline_aa_deliveries_count(self):
"""
Get number of pending frontline aa units
"""
if self.pending_unit_deliveries:
return sum(
[
v
for k, v in self.pending_unit_deliveries.units.items()
if k in TYPE_SHORAD
]
)
else:
return 0
@property
def pending_deliveries_count(self):
"""
Get number of pending units
"""
if self.pending_unit_deliveries:
return sum([v for k, v in self.pending_unit_deliveries.units.items()])
else:
return 0
@property
def expected_ground_units_next_turn(self) -> PendingOccupancy:
on_order = 0
for unit_bought in self.pending_unit_deliveries.units:
if issubclass(unit_bought, FlyingType):
continue
if unit_bought in TYPE_SHORAD:
continue
on_order += self.pending_unit_deliveries.units[unit_bought]
return PendingOccupancy(
self.base.total_armor,
on_order,
# Ground unit transfers not yet implemented.
transferring=0,
)
@property
def income_per_turn(self) -> int:
return 0
@property
def has_active_frontline(self) -> bool:
return any(not c.is_friendly(self.captured) for c in self.connected_points)
class Airfield(ControlPoint):
def __init__(
self, airport: Airport, size: int, importance: float, has_frontline=True
):
super().__init__(
airport.id,
airport.name,
airport.position,
airport,
size,
importance,
has_frontline,
cptype=ControlPointType.AIRBASE,
)
self.airport = airport
self._runway_status = RunwayStatus()
def can_operate(self, aircraft: FlyingType) -> bool:
# TODO: Allow helicopters.
# Need to implement ground spawns so the helos don't use the runway.
# TODO: Allow harrier.
# Needs ground spawns just like helos do, but also need to be able to
# limit takeoff weight to ~20500 lbs or it won't be able to take off.
return self.runway_is_operational()
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType
if self.is_friendly(for_player):
yield from [
# TODO: FlightType.INTERCEPTION
# TODO: FlightType.LOGISTICS
]
else:
yield from [
FlightType.OCA_AIRCRAFT,
FlightType.OCA_RUNWAY,
]
yield from super().mission_types(for_player)
@property
def total_aircraft_parking(self) -> int:
return len(self.airport.parking_slots)
@property
def heading(self) -> int:
return self.airport.runways[0].heading
def runway_is_operational(self) -> bool:
return not self.runway_status.damaged
@property
def runway_status(self) -> RunwayStatus:
return self._runway_status
def damage_runway(self) -> None:
self.runway_status.damage()
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
) -> RunwayData:
assigner = RunwayAssigner(conditions)
return assigner.get_preferred_runway(self.airport)
@property
def parking_slots(self) -> Iterator[ParkingSlot]:
yield from self.airport.parking_slots
@property
def can_deploy_ground_units(self) -> bool:
return True
@property
def income_per_turn(self) -> int:
return 20
class NavalControlPoint(ControlPoint, ABC):
@property
def is_fleet(self) -> bool:
return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType
if self.is_friendly(for_player):
yield from [
# TODO: FlightType.INTERCEPTION
# TODO: Buddy tanking for the A-4?
# TODO: Rescue chopper?
# TODO: Inter-ship logistics?
]
else:
yield FlightType.ANTISHIP
yield from super().mission_types(for_player)
@property
def heading(self) -> int:
return 0 # TODO compute heading
def runway_is_operational(self) -> bool:
# Necessary because it's possible for the carrier itself to have sunk
# while its escorts are still alive.
for g in self.ground_objects:
if g.dcs_identifier in ["CARRIER", "LHA"]:
for group in g.groups:
for u in group.units:
if db.unit_type_from_name(u.type) in [
CVN_74_John_C__Stennis,
LHA_1_Tarawa,
CV_1143_5_Admiral_Kuznetsov,
Type_071_Amphibious_Transport_Dock,
]:
return True
return False
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
) -> RunwayData:
# TODO: Assign TACAN and ICLS earlier so we don't need this.
fallback = RunwayData(self.full_name, runway_heading=0, runway_name="")
return dynamic_runways.get(self.name, fallback)
@property
def runway_status(self) -> RunwayStatus:
return RunwayStatus(damaged=not self.runway_is_operational())
@property
def runway_can_be_repaired(self) -> bool:
return False
@property
def moveable(self) -> bool:
return True
@property
def can_deploy_ground_units(self) -> bool:
return False
class Carrier(NavalControlPoint):
def __init__(self, name: str, at: Point, cp_id: int):
import game.theater.conflicttheater
super().__init__(
cp_id,
name,
at,
at,
game.theater.conflicttheater.SIZE_SMALL,
1,
has_frontline=False,
cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP,
)
def capture(self, game: Game, for_player: bool) -> None:
raise RuntimeError("Carriers cannot be captured")
@property
def is_carrier(self):
return True
def can_operate(self, aircraft: FlyingType) -> bool:
return aircraft in db.CARRIER_CAPABLE
@property
def total_aircraft_parking(self) -> int:
return 90
class Lha(NavalControlPoint):
def __init__(self, name: str, at: Point, cp_id: int):
import game.theater.conflicttheater
super().__init__(
cp_id,
name,
at,
at,
game.theater.conflicttheater.SIZE_SMALL,
1,
has_frontline=False,
cptype=ControlPointType.LHA_GROUP,
)
def capture(self, game: Game, for_player: bool) -> None:
raise RuntimeError("LHAs cannot be captured")
@property
def is_lha(self) -> bool:
return True
def can_operate(self, aircraft: FlyingType) -> bool:
return aircraft in db.LHA_CAPABLE
@property
def total_aircraft_parking(self) -> int:
return 20
class OffMapSpawn(ControlPoint):
def runway_is_operational(self) -> bool:
return True
def __init__(self, cp_id: int, name: str, position: Point):
from . import IMPORTANCE_MEDIUM, SIZE_REGULAR
super().__init__(
cp_id,
name,
position,
at=position,
size=SIZE_REGULAR,
importance=IMPORTANCE_MEDIUM,
has_frontline=False,
cptype=ControlPointType.OFF_MAP,
)
def capture(self, game: Game, for_player: bool) -> None:
raise RuntimeError("Off map control points cannot be captured")
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
yield from []
@property
def total_aircraft_parking(self) -> int:
return 1000
def can_operate(self, aircraft: FlyingType) -> bool:
return True
@property
def heading(self) -> int:
return 0
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
) -> RunwayData:
logging.warning("TODO: Off map spawns have no runways.")
return RunwayData(self.full_name, runway_heading=0, runway_name="")
@property
def runway_status(self) -> RunwayStatus:
return RunwayStatus()
@property
def can_deploy_ground_units(self) -> bool:
return False
class Fob(ControlPoint):
def __init__(self, name: str, at: Point, cp_id: int):
import game.theater.conflicttheater
super().__init__(
cp_id,
name,
at,
at,
game.theater.conflicttheater.SIZE_SMALL,
1,
has_frontline=True,
cptype=ControlPointType.FOB,
)
self.name = name
def runway_is_operational(self) -> bool:
return False
def active_runway(
self, conditions: Conditions, dynamic_runways: Dict[str, RunwayData]
) -> RunwayData:
logging.warning("TODO: FOBs have no runways.")
return RunwayData(self.full_name, runway_heading=0, runway_name="")
@property
def runway_status(self) -> RunwayStatus:
return RunwayStatus()
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType
if self.is_friendly(for_player):
yield from [
FlightType.BARCAP,
# TODO: FlightType.LOGISTICS
]
else:
yield from [
FlightType.STRIKE,
FlightType.SWEEP,
FlightType.ESCORT,
FlightType.SEAD,
]
@property
def total_aircraft_parking(self) -> int:
return 0
def can_operate(self, aircraft: FlyingType) -> bool:
return False
@property
def heading(self) -> int:
return 0
@property
def can_deploy_ground_units(self) -> bool:
return True
@property
def income_per_turn(self) -> int:
return 10

48
game/theater/landmap.py Normal file
View File

@@ -0,0 +1,48 @@
from dataclasses import dataclass
import pickle
from functools import cached_property
from typing import Optional, Tuple, Union
import logging
from shapely import geometry
from shapely.geometry import MultiPolygon, Polygon
@dataclass(frozen=True)
class Landmap:
inclusion_zones: MultiPolygon
exclusion_zones: MultiPolygon
sea_zones: MultiPolygon
def __post_init__(self):
if not self.inclusion_zones.is_valid:
raise RuntimeError("Inclusion zones not valid")
if not self.exclusion_zones.is_valid:
raise RuntimeError("Exclusion zones not valid")
if not self.sea_zones.is_valid:
raise RuntimeError("Sea zones not valid")
@cached_property
def inclusion_zone_only(self) -> MultiPolygon:
return self.inclusion_zones - self.exclusion_zones - self.sea_zones
def load_landmap(filename: str) -> Optional[Landmap]:
try:
with open(filename, "rb") as f:
return pickle.load(f)
except:
logging.exception(f"Failed to load landmap {filename}")
return None
def poly_contains(x, y, poly: Union[MultiPolygon, Polygon]):
return poly.contains(geometry.Point(x, y))
def poly_centroid(poly) -> Tuple[float, float]:
x_list = [vertex[0] for vertex in poly]
y_list = [vertex[1] for vertex in poly]
x = sum(x_list) / len(poly)
y = sum(y_list) / len(poly)
return (x, y)

View File

@@ -0,0 +1,44 @@
from __future__ import annotations
from typing import Iterator, TYPE_CHECKING
from dcs.mapping import Point
if TYPE_CHECKING:
from gen.flights.flight import FlightType
class MissionTarget:
def __init__(self, name: str, position: Point) -> None:
"""Initializes a mission target.
Args:
name: The name of the mission target.
position: The location of the mission target.
"""
self.name = name
self.position = position
def distance_to(self, other: MissionTarget) -> int:
"""Computes the distance to the given mission target."""
return self.position.distance_to_point(other.position)
def is_friendly(self, to_player: bool) -> bool:
"""Returns True if the objective is in friendly territory."""
raise NotImplementedError
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType
if self.is_friendly(for_player):
yield FlightType.BARCAP
else:
yield from [
FlightType.ESCORT,
FlightType.TARCAP,
FlightType.SEAD,
FlightType.SWEEP,
# TODO: FlightType.ELINT,
# TODO: FlightType.EWAR,
# TODO: FlightType.RECON,
]

View File

@@ -0,0 +1,821 @@
from __future__ import annotations
import logging
import pickle
import random
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Dict, Iterable, List, Optional, Set
from dcs.mapping import Point
from dcs.task import CAP, CAS, PinpointStrike
from dcs.vehicles import AirDefence
from game import Game, db
from game.factions.faction import Faction
from game.theater import Carrier, Lha, LocationType
from game.theater.theatergroundobject import (
BuildingGroundObject,
CarrierGroundObject,
EwrGroundObject,
LhaGroundObject,
MissileSiteGroundObject,
SamGroundObject,
ShipGroundObject,
VehicleGroupGroundObject,
)
from game.version import VERSION
from gen import namegen
from gen.defenses.armor_group_generator import generate_armor_group
from gen.fleet.ship_group_generator import (
generate_carrier_group,
generate_lha_group,
generate_ship_group,
)
from gen.locations.preset_location_finder import MizDataLocationFinder
from gen.missiles.missiles_group_generator import generate_missile_group
from gen.sam.airdefensegroupgenerator import AirDefenseRange
from gen.sam.sam_group_generator import (
generate_anti_air_group,
generate_ewr_group,
)
from . import (
ConflictTheater,
ControlPoint,
ControlPointType,
Fob,
OffMapSpawn,
)
from ..settings import Settings
GroundObjectTemplates = Dict[str, Dict[str, Any]]
UNIT_VARIETY = 6
UNIT_AMOUNT_FACTOR = 16
UNIT_COUNT_IMPORTANCE_LOG = 1.3
COUNT_BY_TASK = {
PinpointStrike: 12,
CAP: 8,
CAS: 4,
AirDefence: 1,
}
@dataclass(frozen=True)
class GeneratorSettings:
start_date: datetime
player_budget: int
enemy_budget: int
midgame: bool
inverted: bool
no_carrier: bool
no_lha: bool
no_player_navy: bool
no_enemy_navy: bool
class GameGenerator:
def __init__(
self,
player: str,
enemy: str,
theater: ConflictTheater,
settings: Settings,
generator_settings: GeneratorSettings,
) -> None:
self.player = player
self.enemy = enemy
self.theater = theater
self.settings = settings
self.generator_settings = generator_settings
def generate(self) -> Game:
# Reset name generator
namegen.reset()
self.prepare_theater()
game = Game(
player_name=self.player,
enemy_name=self.enemy,
theater=self.theater,
start_date=self.generator_settings.start_date,
settings=self.settings,
player_budget=self.generator_settings.player_budget,
enemy_budget=self.generator_settings.enemy_budget,
)
GroundObjectGenerator(game, self.generator_settings).generate()
game.settings.version = VERSION
return game
def prepare_theater(self) -> None:
to_remove: List[ControlPoint] = []
# Auto-capture half the bases if midgame.
if self.generator_settings.midgame:
control_points = self.theater.controlpoints
for control_point in control_points[: len(control_points) // 2]:
control_point.captured = True
# Remove carrier and lha, invert situation if needed
for cp in self.theater.controlpoints:
if isinstance(cp, Carrier) and self.generator_settings.no_carrier:
to_remove.append(cp)
elif isinstance(cp, Lha) and self.generator_settings.no_lha:
to_remove.append(cp)
if self.generator_settings.inverted:
cp.captured = cp.captured_invert
# do remove
for cp in to_remove:
self.theater.controlpoints.remove(cp)
# TODO: Fix this. This captures all bases for blue.
# reapply midgame inverted if needed
if self.generator_settings.midgame and self.generator_settings.inverted:
for i, cp in enumerate(reversed(self.theater.controlpoints)):
if i > len(self.theater.controlpoints):
break
else:
cp.captured = True
class LocationFinder:
def __init__(self, game: Game, control_point: ControlPoint) -> None:
self.game = game
self.control_point = control_point
self.miz_data = MizDataLocationFinder.compute_possible_locations(
game.theater.terrain.name, control_point.full_name
)
def location_for(self, location_type: LocationType) -> Optional[Point]:
position = self.control_point.preset_locations.random_for(location_type)
if position is not None:
return position
logging.warning(
f"No campaign location for %s at %s",
location_type.value,
self.control_point,
)
position = self.random_from_miz_data(
location_type == LocationType.OffshoreStrikeTarget
)
if position is not None:
return position
logging.debug(
f"No mizdata location for %s at %s", location_type.value, self.control_point
)
position = self.random_position(location_type)
if position is not None:
return position
logging.error(
f"Could not find position for %s at %s",
location_type.value,
self.control_point,
)
return None
def random_from_miz_data(self, offshore: bool) -> Optional[Point]:
if offshore:
locations = self.miz_data.offshore_locations
else:
locations = self.miz_data.ashore_locations
if self.miz_data.offshore_locations:
preset = random.choice(locations)
locations.remove(preset)
return preset.position
return None
def random_position(self, location_type: LocationType) -> Optional[Point]:
# TODO: Flesh out preset locations so we never hit this case.
logging.warning(
"Falling back to random location for %s at %s",
location_type.value,
self.control_point,
)
is_base_defense = location_type in {
LocationType.BaseAirDefense,
LocationType.Garrison,
LocationType.Shorad,
}
on_land = location_type not in {
LocationType.OffshoreStrikeTarget,
LocationType.Ship,
}
avoid_others = location_type not in {
LocationType.Garrison,
LocationType.MissileSite,
LocationType.Sam,
LocationType.Ship,
LocationType.Shorad,
}
if is_base_defense:
min_range = 400
max_range = 3200
elif location_type == LocationType.Ship:
min_range = 5000
max_range = 40000
elif location_type == LocationType.MissileSite:
min_range = 2500
max_range = 40000
else:
min_range = 10000
max_range = 40000
position = self._find_random_position(
min_range, max_range, on_land, is_base_defense, avoid_others
)
# Retry once, searching a bit further (On some big airbases, 3200 is too
# short (Ex : Incirlik)), but searching farther on every base would be
# problematic, as some base defense units would end up very far away
# from small airfields.
if position is None and is_base_defense:
position = self._find_random_position(
3200, 4800, on_land, is_base_defense, avoid_others
)
return position
def _find_random_position(
self,
min_range: int,
max_range: int,
on_ground: bool,
is_base_defense: bool,
avoid_others: bool,
) -> Optional[Point]:
"""
Find a valid ground object location
:param on_ground: Whether it should be on ground or on sea (True = on
ground)
:param min_range: Minimal range from point
:param max_range: Max range from point
:param is_base_defense: True if the location is for base defense.
:return:
"""
near = self.control_point.position
others = self.control_point.ground_objects
def is_valid(point: Optional[Point]) -> bool:
if point is None:
return False
if on_ground and not self.game.theater.is_on_land(point):
return False
elif not on_ground and not self.game.theater.is_in_sea(point):
return False
if avoid_others:
for other in others:
if other.position.distance_to_point(point) < 10000:
return False
if is_base_defense:
# If it's a base defense we don't care how close it is to other
# points.
return True
# Else verify that it's not too close to another control point.
for control_point in self.game.theater.controlpoints:
if control_point != self.control_point:
if control_point.position.distance_to_point(point) < 30000:
return False
for ground_obj in control_point.ground_objects:
if ground_obj.position.distance_to_point(point) < 10000:
return False
return True
for _ in range(300):
# Check if on land or sea
p = near.random_point_within(max_range, min_range)
if is_valid(p):
return p
return None
class ControlPointGroundObjectGenerator:
def __init__(
self,
game: Game,
generator_settings: GeneratorSettings,
control_point: ControlPoint,
) -> None:
self.game = game
self.generator_settings = generator_settings
self.control_point = control_point
self.location_finder = LocationFinder(game, control_point)
@property
def faction_name(self) -> str:
if self.control_point.captured:
return self.game.player_name
else:
return self.game.enemy_name
@property
def faction(self) -> Faction:
return db.FACTIONS[self.faction_name]
def generate(self) -> bool:
self.control_point.connected_objectives = []
if self.faction.navy_generators:
# Even airbases can generate navies if they are close enough to the
# water. This is not controlled by the control point definition, but
# rather by whether or not the generator can find a valid position
# for the ship.
self.generate_navy()
return True
def generate_navy(self) -> None:
skip_player_navy = self.generator_settings.no_player_navy
if self.control_point.captured and skip_player_navy:
return
skip_enemy_navy = self.generator_settings.no_enemy_navy
if not self.control_point.captured and skip_enemy_navy:
return
for _ in range(self.faction.navy_group_count):
self.generate_ship()
def generate_ship(self) -> None:
point = self.location_finder.location_for(LocationType.OffshoreStrikeTarget)
if point is None:
return
group_id = self.game.next_group_id()
g = ShipGroundObject(
namegen.random_objective_name(), group_id, point, self.control_point
)
group = generate_ship_group(self.game, g, self.faction_name)
g.groups = []
if group is not None:
g.groups.append(group)
self.control_point.connected_objectives.append(g)
class NoOpGroundObjectGenerator(ControlPointGroundObjectGenerator):
def generate(self) -> bool:
return True
class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
def generate(self) -> bool:
if not super().generate():
return False
carrier_names = self.faction.carrier_names
if not carrier_names:
logging.info(
f"Skipping generation of {self.control_point.name} because "
f"{self.faction_name} has no carriers"
)
return False
# Create ground object group
group_id = self.game.next_group_id()
g = CarrierGroundObject(
namegen.random_objective_name(), group_id, self.control_point
)
group = generate_carrier_group(self.faction_name, self.game, g)
g.groups = []
if group is not None:
g.groups.append(group)
self.control_point.connected_objectives.append(g)
self.control_point.name = random.choice(carrier_names)
return True
class LhaGroundObjectGenerator(ControlPointGroundObjectGenerator):
def generate(self) -> bool:
if not super().generate():
return False
lha_names = self.faction.helicopter_carrier_names
if not lha_names:
logging.info(
f"Skipping generation of {self.control_point.name} because "
f"{self.faction_name} has no LHAs"
)
return False
# Create ground object group
group_id = self.game.next_group_id()
g = LhaGroundObject(
namegen.random_objective_name(), group_id, self.control_point
)
group = generate_lha_group(self.faction_name, self.game, g)
g.groups = []
if group is not None:
g.groups.append(group)
self.control_point.connected_objectives.append(g)
self.control_point.name = random.choice(lha_names)
return True
class BaseDefenseGenerator:
def __init__(self, game: Game, control_point: ControlPoint) -> None:
self.game = game
self.control_point = control_point
self.location_finder = LocationFinder(game, control_point)
@property
def faction_name(self) -> str:
if self.control_point.captured:
return self.game.player_name
else:
return self.game.enemy_name
@property
def faction(self) -> Faction:
return db.FACTIONS[self.faction_name]
def generate(self) -> None:
self.generate_ewr()
self.generate_garrison()
self.generate_base_defenses()
def generate_ewr(self) -> None:
position = self.location_finder.location_for(LocationType.Ewr)
if position is None:
return
group_id = self.game.next_group_id()
g = EwrGroundObject(
namegen.random_objective_name(), group_id, position, self.control_point
)
group = generate_ewr_group(self.game, g, self.faction)
if group is None:
logging.error(f"Could not generate EWR at {self.control_point}")
return
g.groups = [group]
self.control_point.base_defenses.append(g)
def generate_base_defenses(self) -> None:
# First group has a 1/2 chance of being a SAM, 1/6 chance of SHORAD,
# and a 1/6 chance of a garrison.
#
# Further groups have a 1/3 chance of being SHORAD and 2/3 chance of
# being a garrison.
for i in range(random.randint(2, 5)):
if i == 0 and random.randint(0, 1) == 0:
self.generate_sam()
elif random.randint(0, 2) == 1:
self.generate_shorad()
else:
self.generate_garrison()
def generate_garrison(self) -> None:
position = self.location_finder.location_for(LocationType.Garrison)
if position is None:
return
group_id = self.game.next_group_id()
g = VehicleGroupGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
for_airbase=True,
)
group = generate_armor_group(self.faction_name, self.game, g)
if group is None:
logging.error(f"Could not generate garrison at {self.control_point}")
return
g.groups.append(group)
self.control_point.base_defenses.append(g)
def generate_sam(self) -> None:
position = self.location_finder.location_for(LocationType.BaseAirDefense)
if position is None:
return
group_id = self.game.next_group_id()
g = SamGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
for_airbase=True,
)
groups = generate_anti_air_group(self.game, g, self.faction)
if not groups:
logging.error(f"Could not generate SAM at {self.control_point}")
return
g.groups = groups
self.control_point.base_defenses.append(g)
def generate_shorad(self) -> None:
position = self.location_finder.location_for(LocationType.BaseAirDefense)
if position is None:
return
group_id = self.game.next_group_id()
g = SamGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
for_airbase=True,
)
groups = generate_anti_air_group(
self.game, g, self.faction, ranges=[{AirDefenseRange.Short}]
)
if not groups:
logging.error(f"Could not generate SHORAD group at {self.control_point}")
return
g.groups = groups
self.control_point.base_defenses.append(g)
class FobDefenseGenerator(BaseDefenseGenerator):
def generate(self) -> None:
self.generate_garrison()
self.generate_fob_defenses()
def generate_fob_defenses(self):
# First group has a 1/2 chance of being a SHORAD,
# and a 1/2 chance of a garrison.
#
# Further groups have a 1/3 chance of being SHORAD and 2/3 chance of
# being a garrison.
for i in range(random.randint(2, 5)):
if i == 0 and random.randint(0, 1) == 0:
self.generate_shorad()
elif i == 0 and random.randint(0, 1) == 0:
self.generate_garrison()
elif random.randint(0, 2) == 1:
self.generate_shorad()
else:
self.generate_garrison()
class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator):
def __init__(
self,
game: Game,
generator_settings: GeneratorSettings,
control_point: ControlPoint,
templates: GroundObjectTemplates,
) -> None:
super().__init__(game, generator_settings, control_point)
self.templates = templates
def generate(self) -> bool:
if not super().generate():
return False
BaseDefenseGenerator(self.game, self.control_point).generate()
self.generate_ground_points()
if self.faction.missiles:
self.generate_missile_sites()
return True
def generate_ground_points(self) -> None:
"""Generate ground objects and AA sites for the control point."""
skip_sams = self.generate_required_aa()
if self.control_point.is_global:
return
# Always generate at least one AA point.
self.generate_aa_site()
# And between 2 and 7 other objectives.
amount = random.randrange(2, 7)
for i in range(amount):
# 1 in 4 additional objectives are AA.
if random.randint(0, 3) == 0:
if skip_sams > 0:
skip_sams -= 1
else:
self.generate_aa_site()
else:
self.generate_ground_point()
def generate_required_aa(self) -> int:
"""Generates the AA sites that are required by the campaign.
Returns:
The number of AA sites that were generated.
"""
presets = self.control_point.preset_locations
for position in presets.required_long_range_sams:
self.generate_aa_at(
position,
ranges=[
{AirDefenseRange.Long},
{AirDefenseRange.Medium},
{AirDefenseRange.Short},
],
)
for position in presets.required_medium_range_sams:
self.generate_aa_at(
position,
ranges=[
{AirDefenseRange.Medium},
{AirDefenseRange.Short},
],
)
return len(presets.required_long_range_sams) + len(
presets.required_medium_range_sams
)
def generate_ground_point(self) -> None:
try:
category = random.choice(self.faction.building_set)
except IndexError:
logging.exception("Faction has no buildings defined")
return
obj_name = namegen.random_objective_name()
template = random.choice(list(self.templates[category].values()))
if category == "oil":
location_type = LocationType.OffshoreStrikeTarget
else:
location_type = LocationType.StrikeTarget
# Pick from preset locations
point = self.location_finder.location_for(location_type)
if point is None:
return
object_id = 0
group_id = self.game.next_group_id()
# TODO: Create only one TGO per objective, each with multiple units.
for unit in template:
object_id += 1
template_point = Point(unit["offset"].x, unit["offset"].y)
g = BuildingGroundObject(
obj_name,
category,
group_id,
object_id,
point + template_point,
unit["heading"],
self.control_point,
unit["type"],
)
self.control_point.connected_objectives.append(g)
def generate_aa_site(self) -> None:
position = self.location_finder.location_for(LocationType.Sam)
if position is None:
return
self.generate_aa_at(
position,
ranges=[
# Prefer to use proper SAMs, but fall back to SHORADs if needed.
{AirDefenseRange.Long, AirDefenseRange.Medium},
{AirDefenseRange.Short},
],
)
def generate_aa_at(
self, position: Point, ranges: Iterable[Set[AirDefenseRange]]
) -> None:
group_id = self.game.next_group_id()
g = SamGroundObject(
namegen.random_objective_name(),
group_id,
position,
self.control_point,
for_airbase=False,
)
groups = generate_anti_air_group(self.game, g, self.faction, ranges)
if not groups:
logging.error(
"Could not generate air defense group for %s at %s",
g.name,
self.control_point,
)
return
g.groups = groups
self.control_point.connected_objectives.append(g)
def generate_missile_sites(self) -> None:
for i in range(self.faction.missiles_group_count):
self.generate_missile_site()
def generate_missile_site(self) -> None:
position = self.location_finder.location_for(LocationType.MissileSite)
if position is None:
return
group_id = self.game.next_group_id()
g = MissileSiteGroundObject(
namegen.random_objective_name(), group_id, position, self.control_point
)
group = generate_missile_group(self.game, g, self.faction_name)
g.groups = []
if group is not None:
g.groups.append(group)
self.control_point.connected_objectives.append(g)
return
class FobGroundObjectGenerator(AirbaseGroundObjectGenerator):
def generate(self) -> bool:
self.generate_fob()
FobDefenseGenerator(self.game, self.control_point).generate()
self.generate_required_aa()
return True
def generate_fob(self) -> None:
try:
category = self.faction.building_set[self.faction.building_set.index("fob")]
except IndexError:
logging.exception("Faction has no fob buildings defined")
return
obj_name = self.control_point.name
template = random.choice(list(self.templates[category].values()))
point = self.control_point.position
# Pick from preset locations
object_id = 0
group_id = self.game.next_group_id()
# TODO: Create only one TGO per objective, each with multiple units.
for unit in template:
object_id += 1
template_point = Point(unit["offset"].x, unit["offset"].y)
g = BuildingGroundObject(
obj_name,
category,
group_id,
object_id,
point + template_point,
unit["heading"],
self.control_point,
unit["type"],
airbase_group=True,
)
self.control_point.connected_objectives.append(g)
class GroundObjectGenerator:
def __init__(self, game: Game, generator_settings: GeneratorSettings) -> None:
self.game = game
self.generator_settings = generator_settings
with open("resources/groundobject_templates.p", "rb") as f:
self.templates: GroundObjectTemplates = pickle.load(f)
def generate(self) -> None:
# Copied so we can remove items from the original list without breaking
# the iterator.
control_points = list(self.game.theater.controlpoints)
for control_point in control_points:
if not self.generate_for_control_point(control_point):
self.game.theater.controlpoints.remove(control_point)
def generate_for_control_point(self, control_point: ControlPoint) -> bool:
generator: ControlPointGroundObjectGenerator
if control_point.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP:
generator = CarrierGroundObjectGenerator(
self.game, self.generator_settings, control_point
)
elif control_point.cptype == ControlPointType.LHA_GROUP:
generator = LhaGroundObjectGenerator(
self.game, self.generator_settings, control_point
)
elif isinstance(control_point, OffMapSpawn):
generator = NoOpGroundObjectGenerator(
self.game, self.generator_settings, control_point
)
elif isinstance(control_point, Fob):
generator = FobGroundObjectGenerator(
self.game, self.generator_settings, control_point, self.templates
)
else:
generator = AirbaseGroundObjectGenerator(
self.game, self.generator_settings, control_point, self.templates
)
return generator.generate()

View File

@@ -0,0 +1,474 @@
from __future__ import annotations
import itertools
import logging
from typing import Iterator, List, TYPE_CHECKING
from dcs.mapping import Point
from dcs.unit import Unit
from dcs.unitgroup import Group
from .. import db
from ..data.radar_db import UNITS_WITH_RADAR
from ..utils import Distance, meters
if TYPE_CHECKING:
from .controlpoint import ControlPoint
from gen.flights.flight import FlightType
from .missiontarget import MissionTarget
NAME_BY_CATEGORY = {
"power": "Power plant",
"ammo": "Ammo depot",
"fuel": "Fuel depot",
"aa": "AA Defense Site",
"ware": "Warehouse",
"farp": "FARP",
"fob": "FOB",
"factory": "Factory",
"comms": "Comms. tower",
"oil": "Oil platform",
"derrick": "Derrick",
"ww2bunker": "Bunker",
"village": "Village",
"allycamp": "Camp",
"EWR": "EWR",
}
ABBREV_NAME = {
"power": "PLANT",
"ammo": "AMMO",
"fuel": "FUEL",
"aa": "AA",
"ware": "WARE",
"farp": "FARP",
"fob": "FOB",
"factory": "FACTORY",
"comms": "COMMST",
"oil": "OILP",
"derrick": "DERK",
"ww2bunker": "BUNK",
"village": "VLG",
"allycamp": "CMP",
}
CATEGORY_MAP = {
# Special cases
"CARRIER": ["CARRIER"],
"LHA": ["LHA"],
"aa": ["AA"],
# Buildings
"power": [
"Workshop A",
"Electric power box",
"Garage small A",
"Farm B",
"Repair workshop",
"Garage B",
],
"ware": ["Warehouse", "Hangar A"],
"fuel": ["Tank", "Tank 2", "Tank 3", "Fuel tank"],
"ammo": [".Ammunition depot", "Hangar B"],
"farp": [
"FARP Tent",
"FARP Ammo Dump Coating",
"FARP Fuel Depot",
"FARP Command Post",
"FARP CP Blindage",
],
"fob": ["Bunker 2", "Bunker 1", "Garage small B", ".Command Center", "Barracks 2"],
"factory": ["Tech combine", "Tech hangar A"],
"comms": ["TV tower", "Comms tower M"],
"oil": ["Oil platform"],
"derrick": ["Oil derrick", "Pump station", "Subsidiary structure 2"],
"ww2bunker": [
"Siegfried Line",
"Fire Control Bunker",
"SK_C_28_naval_gun",
"Concertina Wire",
"Czech hedgehogs 1",
],
"village": ["Small house 1B", "Small House 1A", "Small warehouse 1"],
"allycamp": [],
}
class TheaterGroundObject(MissionTarget):
def __init__(
self,
name: str,
category: str,
group_id: int,
position: Point,
heading: int,
control_point: ControlPoint,
dcs_identifier: str,
airbase_group: bool,
sea_object: bool,
) -> None:
super().__init__(name, position)
self.category = category
self.group_id = group_id
self.heading = heading
self.control_point = control_point
self.dcs_identifier = dcs_identifier
self.airbase_group = airbase_group
self.sea_object = sea_object
self.groups: List[Group] = []
@property
def is_dead(self) -> bool:
return self.alive_unit_count == 0
@property
def units(self) -> List[Unit]:
"""
:return: all the units at this location
"""
return list(itertools.chain.from_iterable([g.units for g in self.groups]))
@property
def group_name(self) -> str:
"""The name of the unit group."""
return f"{self.category}|{self.group_id}"
@property
def waypoint_name(self) -> str:
return f"[{self.name}] {self.category}"
def __str__(self) -> str:
return NAME_BY_CATEGORY[self.category]
def is_same_group(self, identifier: str) -> bool:
return self.group_id == identifier
@property
def obj_name(self) -> str:
return self.name
@property
def faction_color(self) -> str:
return "BLUE" if self.control_point.captured else "RED"
def is_friendly(self, to_player: bool) -> bool:
return self.control_point.is_friendly(to_player)
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType
if self.is_friendly(for_player):
yield from [
# TODO: FlightType.LOGISTICS
# TODO: FlightType.TROOP_TRANSPORT
]
else:
yield from [
FlightType.STRIKE,
FlightType.BAI,
]
yield from super().mission_types(for_player)
@property
def alive_unit_count(self) -> int:
return sum(len(g.units) for g in self.groups)
@property
def might_have_aa(self) -> bool:
return False
@property
def has_radar(self) -> bool:
"""Returns True if the ground object contains a unit with radar."""
for group in self.groups:
for unit in group.units:
if db.unit_type_from_name(unit.type) in UNITS_WITH_RADAR:
return True
return False
def _max_range_of_type(self, group: Group, range_type: str) -> Distance:
if not self.might_have_aa:
return meters(0)
max_range = meters(0)
for u in group.units:
unit = db.unit_type_from_name(u.type)
if unit is None:
logging.error(f"Unknown unit type {u.type}")
continue
# Some units in pydcs have detection_range/threat_range defined,
# but explicitly set to None.
unit_range = getattr(unit, range_type, None)
if unit_range is not None:
max_range = max(max_range, meters(unit_range))
return max_range
def detection_range(self, group: Group) -> Distance:
return self._max_range_of_type(group, "detection_range")
def threat_range(self, group: Group) -> Distance:
if not self.detection_range(group):
# For simple SAMs like shilkas, the unit has both a threat and
# detection range. For complex sites like SA-2s, the launcher has a
# threat range and the search/track radars have detection ranges. If
# the site has no detection range it has no radars and can't fire,
# so it's not actually a threat even if it still has launchers.
return meters(0)
return self._max_range_of_type(group, "threat_range")
class BuildingGroundObject(TheaterGroundObject):
def __init__(
self,
name: str,
category: str,
group_id: int,
object_id: int,
position: Point,
heading: int,
control_point: ControlPoint,
dcs_identifier: str,
airbase_group=False,
) -> None:
super().__init__(
name=name,
category=category,
group_id=group_id,
position=position,
heading=heading,
control_point=control_point,
dcs_identifier=dcs_identifier,
airbase_group=airbase_group,
sea_object=False,
)
self.object_id = object_id
# Other TGOs track deadness based on the number of alive units, but
# buildings don't have groups assigned to the TGO.
self._dead = False
@property
def group_name(self) -> str:
"""The name of the unit group."""
return f"{self.category}|{self.group_id}|{self.object_id}"
@property
def waypoint_name(self) -> str:
return f"{super().waypoint_name} #{self.object_id}"
@property
def is_dead(self) -> bool:
if not hasattr(self, "_dead"):
self._dead = False
return self._dead
def kill(self) -> None:
self._dead = True
class NavalGroundObject(TheaterGroundObject):
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType
if not self.is_friendly(for_player):
yield FlightType.ANTISHIP
yield from super().mission_types(for_player)
@property
def might_have_aa(self) -> bool:
return True
class GenericCarrierGroundObject(NavalGroundObject):
pass
# TODO: Why is this both a CP and a TGO?
class CarrierGroundObject(GenericCarrierGroundObject):
def __init__(self, name: str, group_id: int, control_point: ControlPoint) -> None:
super().__init__(
name=name,
category="CARRIER",
group_id=group_id,
position=control_point.position,
heading=0,
control_point=control_point,
dcs_identifier="CARRIER",
airbase_group=True,
sea_object=True,
)
@property
def group_name(self) -> str:
# Prefix the group names with the side color so Skynet can find them,
# add to EWR.
return f"{self.faction_color}|EWR|{super().group_name}"
# TODO: Why is this both a CP and a TGO?
class LhaGroundObject(GenericCarrierGroundObject):
def __init__(self, name: str, group_id: int, control_point: ControlPoint) -> None:
super().__init__(
name=name,
category="LHA",
group_id=group_id,
position=control_point.position,
heading=0,
control_point=control_point,
dcs_identifier="LHA",
airbase_group=True,
sea_object=True,
)
@property
def group_name(self) -> str:
# Prefix the group names with the side color so Skynet can find them,
# add to EWR.
return f"{self.faction_color}|EWR|{super().group_name}"
class MissileSiteGroundObject(TheaterGroundObject):
def __init__(
self, name: str, group_id: int, position: Point, control_point: ControlPoint
) -> None:
super().__init__(
name=name,
category="aa",
group_id=group_id,
position=position,
heading=0,
control_point=control_point,
dcs_identifier="AA",
airbase_group=False,
sea_object=False,
)
class BaseDefenseGroundObject(TheaterGroundObject):
"""Base type for all base defenses."""
# TODO: Differentiate types.
# This type gets used both for AA sites (SAM, AAA, or SHORAD). These should each
# be split into their own types.
class SamGroundObject(BaseDefenseGroundObject):
def __init__(
self,
name: str,
group_id: int,
position: Point,
control_point: ControlPoint,
for_airbase: bool,
) -> None:
super().__init__(
name=name,
category="aa",
group_id=group_id,
position=position,
heading=0,
control_point=control_point,
dcs_identifier="AA",
airbase_group=for_airbase,
sea_object=False,
)
# Set by the SAM unit generator if the generated group is compatible
# with Skynet.
self.skynet_capable = False
@property
def group_name(self) -> str:
if self.skynet_capable:
# Prefix the group names of SAM sites with the side color so Skynet
# can find them.
return f"{self.faction_color}|SAM|{self.group_id}"
else:
return super().group_name
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType
if not self.is_friendly(for_player):
yield FlightType.DEAD
yield from super().mission_types(for_player)
@property
def might_have_aa(self) -> bool:
return True
class VehicleGroupGroundObject(BaseDefenseGroundObject):
def __init__(
self,
name: str,
group_id: int,
position: Point,
control_point: ControlPoint,
for_airbase: bool,
) -> None:
super().__init__(
name=name,
category="aa",
group_id=group_id,
position=position,
heading=0,
control_point=control_point,
dcs_identifier="AA",
airbase_group=for_airbase,
sea_object=False,
)
class EwrGroundObject(BaseDefenseGroundObject):
def __init__(
self, name: str, group_id: int, position: Point, control_point: ControlPoint
) -> None:
super().__init__(
name=name,
category="EWR",
group_id=group_id,
position=position,
heading=0,
control_point=control_point,
dcs_identifier="EWR",
airbase_group=True,
sea_object=False,
)
@property
def group_name(self) -> str:
# Prefix the group names with the side color so Skynet can find them.
return f"{self.faction_color}|{super().group_name}"
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
from gen.flights.flight import FlightType
if not self.is_friendly(for_player):
yield FlightType.DEAD
yield from super().mission_types(for_player)
@property
def might_have_aa(self) -> bool:
return True
class ShipGroundObject(NavalGroundObject):
def __init__(
self, name: str, group_id: int, position: Point, control_point: ControlPoint
) -> None:
super().__init__(
name=name,
category="aa",
group_id=group_id,
position=position,
heading=0,
control_point=control_point,
dcs_identifier="AA",
airbase_group=False,
sea_object=True,
)
@property
def group_name(self) -> str:
# Prefix the group names with the side color so Skynet can find them,
# add to EWR.
return f"{self.faction_color}|EWR|{super().group_name}"

160
game/threatzones.py Normal file
View File

@@ -0,0 +1,160 @@
from __future__ import annotations
from functools import singledispatchmethod
from typing import Optional, TYPE_CHECKING, Union
from dcs.mapping import Point as DcsPoint
from shapely.geometry import (
LineString,
MultiPolygon,
Point as ShapelyPoint,
Polygon,
)
from shapely.geometry.base import BaseGeometry
from shapely.ops import nearest_points, unary_union
from game.theater import ControlPoint
from game.utils import Distance, meters, nautical_miles
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import Flight
if TYPE_CHECKING:
from game import Game
ThreatPoly = Union[MultiPolygon, Polygon]
class ThreatZones:
def __init__(self, airbases: ThreatPoly, air_defenses: ThreatPoly) -> None:
self.airbases = airbases
self.air_defenses = air_defenses
self.all = unary_union([airbases, air_defenses])
def closest_boundary(self, point: DcsPoint) -> DcsPoint:
boundary, _ = nearest_points(
self.all.boundary, self.dcs_to_shapely_point(point)
)
return DcsPoint(boundary.x, boundary.y)
@singledispatchmethod
def threatened(self, position) -> bool:
raise NotImplementedError
@threatened.register
def _threatened_geometry(self, position: BaseGeometry) -> bool:
return self.all.intersects(position)
@threatened.register
def _threatened_dcs_point(self, position: DcsPoint) -> bool:
return self.all.intersects(self.dcs_to_shapely_point(position))
def path_threatened(self, a: DcsPoint, b: DcsPoint) -> bool:
return self.threatened(
LineString([self.dcs_to_shapely_point(a), self.dcs_to_shapely_point(b)])
)
@singledispatchmethod
def threatened_by_aircraft(self, target) -> bool:
raise NotImplementedError
@threatened_by_aircraft.register
def _threatened_by_aircraft_geom(self, position: BaseGeometry) -> bool:
return self.airbases.intersects(position)
@threatened_by_aircraft.register
def _threatened_by_aircraft_flight(self, flight: Flight) -> bool:
return self.threatened_by_aircraft(
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
)
@singledispatchmethod
def threatened_by_air_defense(self, target) -> bool:
raise NotImplementedError
@threatened_by_air_defense.register
def _threatened_by_air_defense_geom(self, position: BaseGeometry) -> bool:
return self.air_defenses.intersects(position)
@threatened_by_air_defense.register
def _threatened_by_air_defense_flight(self, flight: Flight) -> bool:
return self.threatened_by_air_defense(
LineString((self.dcs_to_shapely_point(p.position) for p in flight.points))
)
@classmethod
def closest_enemy_airbase(
cls, location: ControlPoint, max_distance: Distance
) -> Optional[ControlPoint]:
airfields = ObjectiveDistanceCache.get_closest_airfields(location)
for airfield in airfields.airfields_within(max_distance):
if airfield.captured != location.captured:
return airfield
return None
@classmethod
def barcap_threat_range(cls, game: Game, control_point: ControlPoint) -> Distance:
doctrine = game.faction_for(control_point.captured).doctrine
cap_threat_range = (
doctrine.cap_max_distance_from_cp + doctrine.cap_engagement_range
)
opposing_airfield = cls.closest_enemy_airbase(
control_point, cap_threat_range * 2
)
if opposing_airfield is None:
return cap_threat_range
airfield_distance = meters(
opposing_airfield.position.distance_to_point(control_point.position)
)
# BARCAPs should not commit further than halfway to the closest enemy
# airfield (with some breathing room) to avoid those missions becoming
# offensive. For dissimilar doctrines we could weight this so that, as
# an example, modern US goes no closer than 70% of the way to the WW2
# German base, and the Germans go no closer than 30% of the way to the
# US base, but for now equal weighting is fine.
max_distance = airfield_distance * 0.45
return min(cap_threat_range, max_distance)
@classmethod
def for_faction(cls, game: Game, player: bool) -> ThreatZones:
"""Generates the threat zones projected by the given coalition.
Args:
game: The game to generate the threat zone for.
player: True if the coalition projecting the threat zone belongs to
the player.
Returns:
The threat zones projected by the given coalition. If the threat
zone belongs to the player, it is the zone that will be avoided by
the enemy and vice versa.
"""
airbases = []
air_defenses = []
for control_point in game.theater.controlpoints:
if control_point.captured != player:
continue
if control_point.runway_is_operational():
point = ShapelyPoint(control_point.position.x, control_point.position.y)
cap_threat_range = cls.barcap_threat_range(game, control_point)
airbases.append(point.buffer(cap_threat_range.meters))
for tgo in control_point.ground_objects:
for group in tgo.groups:
threat_range = tgo.threat_range(group)
# Any system with a shorter range than this is not worth
# even avoiding.
if threat_range > nautical_miles(3):
point = ShapelyPoint(tgo.position.x, tgo.position.y)
threat_zone = point.buffer(threat_range.meters)
air_defenses.append(threat_zone)
return cls(
airbases=unary_union(airbases), air_defenses=unary_union(air_defenses)
)
@staticmethod
def dcs_to_shapely_point(point: DcsPoint) -> ShapelyPoint:
return ShapelyPoint(point.x, point.y)

140
game/unitmap.py Normal file
View File

@@ -0,0 +1,140 @@
"""Maps generated units back to their Liberation types."""
from dataclasses import dataclass
from typing import Dict, Optional, Type
from dcs.unit import Unit
from dcs.unitgroup import FlyingGroup, Group, VehicleGroup
from dcs.unittype import VehicleType
from game import db
from game.theater import Airfield, ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import BuildingGroundObject
from gen.flights.flight import Flight
@dataclass(frozen=True)
class FrontLineUnit:
unit_type: Type[VehicleType]
origin: ControlPoint
@dataclass(frozen=True)
class GroundObjectUnit:
ground_object: TheaterGroundObject
group: Group
unit: Unit
@dataclass(frozen=True)
class Building:
ground_object: BuildingGroundObject
class UnitMap:
def __init__(self) -> None:
self.aircraft: Dict[str, Flight] = {}
self.airfields: Dict[str, Airfield] = {}
self.front_line_units: Dict[str, FrontLineUnit] = {}
self.ground_object_units: Dict[str, GroundObjectUnit] = {}
self.buildings: Dict[str, Building] = {}
def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None:
for unit in group.units:
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
name = str(unit.name)
if name in self.aircraft:
raise RuntimeError(f"Duplicate unit name: {name}")
self.aircraft[name] = flight
def flight(self, unit_name: str) -> Optional[Flight]:
return self.aircraft.get(unit_name, None)
def add_airfield(self, airfield: Airfield) -> None:
if airfield.name in self.airfields:
raise RuntimeError(f"Duplicate airfield: {airfield.name}")
self.airfields[airfield.name] = airfield
def airfield(self, name: str) -> Optional[Airfield]:
return self.airfields.get(name, None)
def add_front_line_units(self, group: Group, origin: ControlPoint) -> None:
for unit in group.units:
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
name = str(unit.name)
if name in self.front_line_units:
raise RuntimeError(f"Duplicate front line unit: {name}")
unit_type = db.unit_type_from_name(unit.type)
if unit_type is None:
raise RuntimeError(f"Unknown unit type: {unit.type}")
if not issubclass(unit_type, VehicleType):
raise RuntimeError(
f"{name} is a {unit_type.__name__}, expected a VehicleType"
)
self.front_line_units[name] = FrontLineUnit(unit_type, origin)
def front_line_unit(self, name: str) -> Optional[FrontLineUnit]:
return self.front_line_units.get(name, None)
def add_ground_object_units(
self,
ground_object: TheaterGroundObject,
persistence_group: Group,
miz_group: Group,
) -> None:
"""Adds a group associated with a TGO to the unit map.
Args:
ground_object: The TGO the group is associated with.
persistence_group: The Group tracked by the TGO itself.
miz_group: The Group spawned for the miz to match persistence_group.
"""
# Deaths for units at TGOs are recorded in the Group that is contained
# by the TGO, but when groundobjectsgen populates the miz it creates new
# groups based on that template, so the units and groups in the miz are
# not a direct match for the units and groups that persist in the TGO.
#
# This means that we need to map the spawned unit names back to the
# original TGO units, not the ones in the miz.
if len(persistence_group.units) != len(miz_group.units):
raise ValueError("Persistent group does not match generated group")
unit_pairs = zip(persistence_group.units, miz_group.units)
for persistent_unit, miz_unit in unit_pairs:
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
name = str(miz_unit.name)
if name in self.ground_object_units:
raise RuntimeError(f"Duplicate TGO unit: {name}")
self.ground_object_units[name] = GroundObjectUnit(
ground_object, persistence_group, persistent_unit
)
def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]:
return self.ground_object_units.get(name, None)
def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None:
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
# The name of the initiator in the DCS dead event will have " object"
# appended for statics.
name = f"{group.name} object"
if name in self.buildings:
raise RuntimeError(f"Duplicate TGO unit: {name}")
self.buildings[name] = Building(ground_object)
def add_fortification(
self, ground_object: BuildingGroundObject, group: VehicleGroup
) -> None:
if len(group.units) != 1:
raise ValueError("Fortification groups must have exactly one unit.")
unit = group.units[0]
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
name = str(unit.name)
if name in self.buildings:
raise RuntimeError(f"Duplicate TGO unit: {name}")
self.buildings[name] = Building(ground_object)
def building_or_fortification(self, name: str) -> Optional[Building]:
return self.buildings.get(name, None)

View File

@@ -1,14 +1,180 @@
def meter_to_feet(value_in_meter):
return int(3.28084 * value_in_meter)
from __future__ import annotations
import math
from dataclasses import dataclass
from typing import Union
METERS_TO_FEET = 3.28084
FEET_TO_METERS = 1 / METERS_TO_FEET
NM_TO_METERS = 1852
METERS_TO_NM = 1 / NM_TO_METERS
KNOTS_TO_KPH = 1.852
KPH_TO_KNOTS = 1 / KNOTS_TO_KPH
MS_TO_KPH = 3.6
KPH_TO_MS = 1 / MS_TO_KPH
def feet_to_meter(value_in_feet):
return int(float(value_in_feet)/3.048)
def heading_sum(h, a) -> int:
h += a
if h > 360:
return h - 360
elif h < 0:
return 360 + h
else:
return h
def meter_to_nm(value_in_meter):
return int(float(value_in_meter)*0.000539957)
def opposite_heading(h):
return heading_sum(h, 180)
def nm_to_meter(value_in_nm):
return int(float(value_in_nm)*1852)
@dataclass(frozen=True, order=True)
class Distance:
distance_in_meters: float
@property
def feet(self) -> float:
return self.distance_in_meters * METERS_TO_FEET
@property
def meters(self) -> float:
return self.distance_in_meters
@property
def nautical_miles(self) -> float:
return self.distance_in_meters * METERS_TO_NM
@classmethod
def from_feet(cls, value: float) -> Distance:
return cls(value * FEET_TO_METERS)
@classmethod
def from_meters(cls, value: float) -> Distance:
return cls(value)
@classmethod
def from_nautical_miles(cls, value: float) -> Distance:
return cls(value * NM_TO_METERS)
def __add__(self, other: Distance) -> Distance:
return meters(self.meters + other.meters)
def __sub__(self, other: Distance) -> Distance:
return meters(self.meters - other.meters)
def __mul__(self, other: Union[float, int]) -> Distance:
return meters(self.meters * other)
def __truediv__(self, other: Union[float, int]) -> Distance:
return meters(self.meters / other)
def __floordiv__(self, other: Union[float, int]) -> Distance:
return meters(self.meters // other)
def __bool__(self) -> bool:
return not math.isclose(self.meters, 0.0)
def feet(value: float) -> Distance:
return Distance.from_feet(value)
def meters(value: float) -> Distance:
return Distance.from_meters(value)
def nautical_miles(value: float) -> Distance:
return Distance.from_nautical_miles(value)
@dataclass(frozen=True, order=True)
class Speed:
speed_in_kph: float
@property
def knots(self) -> float:
return self.speed_in_kph * KPH_TO_KNOTS
@property
def kph(self) -> float:
return self.speed_in_kph
@property
def meters_per_second(self) -> float:
return self.speed_in_kph * KPH_TO_MS
def mach(self, altitude: Distance = meters(0)) -> float:
c_sound = mach(1, altitude)
return self.speed_in_kph / c_sound.kph
@classmethod
def from_knots(cls, value: float) -> Speed:
return cls(value * KNOTS_TO_KPH)
@classmethod
def from_kph(cls, value: float) -> Speed:
return cls(value)
@classmethod
def from_meters_per_second(cls, value: float) -> Speed:
return cls(value * MS_TO_KPH)
@classmethod
def from_mach(cls, value: float, altitude: Distance) -> Speed:
# https://www.grc.nasa.gov/WWW/K-12/airplane/atmos.html
if altitude <= feet(36152):
temperature_f = 59 - 0.00356 * altitude.feet
else:
# There's another formula for altitudes over 82k feet, but we better
# not be planning waypoints that high...
temperature_f = -70
temperature_k = (temperature_f + 459.67) * (5 / 9)
# https://www.engineeringtoolbox.com/specific-heat-ratio-d_602.html
# Dependent on temperature, but varies very little (+/-0.001)
# between -40F and 180F.
heat_capacity_ratio = 1.4
# https://www.grc.nasa.gov/WWW/K-12/airplane/sound.html
gas_constant = 286 # m^2/s^2/K
c_sound = math.sqrt(heat_capacity_ratio * gas_constant * temperature_k)
return mps(c_sound) * value
def __add__(self, other: Speed) -> Speed:
return kph(self.kph + other.kph)
def __sub__(self, other: Speed) -> Speed:
return kph(self.kph - other.kph)
def __mul__(self, other: Union[float, int]) -> Speed:
return kph(self.kph * other)
def __truediv__(self, other: Union[float, int]) -> Speed:
return kph(self.kph / other)
def __floordiv__(self, other: Union[float, int]) -> Speed:
return kph(self.kph // other)
def __bool__(self) -> bool:
return not math.isclose(self.kph, 0.0)
def knots(value: float) -> Speed:
return Speed.from_knots(value)
def kph(value: float) -> Speed:
return Speed.from_kph(value)
def mps(value: float) -> Speed:
return Speed.from_meters_per_second(value)
def mach(value: float, altitude: Distance) -> Speed:
return Speed.from_mach(value, altitude)
SPEED_OF_SOUND_AT_SEA_LEVEL = knots(661.5)

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