369 Commits

Author SHA1 Message Date
Raffson
8c8fc7cc26 Changelog updates for v1.2.1 hotfix 2023-07-10 22:26:30 +02:00
Raffson
17211e1597 Include SEAD Sweep in common escorts 2023-07-10 22:26:30 +02:00
Raffson
f76c8a80b5 Restore correct amount of pylons for F16 after ejecting sufa mod 2023-07-10 22:02:12 +02:00
Raffson
7a6ca986db Don't use Python-4 in pydcs_extension
The Python-4 overwrites the AIM-9X, causing issues...
2023-07-10 22:01:50 +02:00
Raffson
8f35546d84 Fine-tuning some typing issues 2023-07-09 22:59:23 +02:00
Raffson
96828ef3db Fix last quirks wrt list->set refactor 2023-07-09 22:27:34 +02:00
Raffson
9dd76bdd37 Ignore tying issues in migrator 2023-07-09 21:39:43 +02:00
Raffson
2b5f6f7998 Migration changes for using sets instead of lists 2023-07-09 21:35:37 +02:00
Raffson
d6e7451f8a Add F-15I support
Co-Authored-By: M Chimiste <37678080+M-Chimiste@users.noreply.github.com>
2023-07-09 19:32:15 +02:00
Raffson
06d98db55b Replace lists with sets 2023-07-09 19:32:00 +02:00
Raffson
533057a0cc Introduce pylon ejector for Sufa mod
Will prevent the user having to restart Retribution
2023-07-09 18:21:28 +02:00
Raffson
e86fc60b41 Spawn idle aircraft at helipads
Applicable for helicopters at airbases & both helicopters and LHA-capable aircraft at FOBs
2023-07-09 15:14:46 +02:00
Raffson
737df98988 Provide better feedback when declaring max-size without a value 2023-07-08 20:35:21 +02:00
Raffson
6734b80684 Follow-up on package refueler
Fixing some bugs that were caused by missing 'mission data' for package-refueling tankers
2023-07-07 21:56:29 +02:00
Raffson
af3a80a294 Bump pydcs 2023-07-07 20:46:30 +02:00
MetalStormGhost
8face817e2 Corrected the name of SEAD Sweep engagement range
Corrected the name of SEAD Sweep engagement range (nmi) setting from incorrect SEAD Sweep threat buffer distance (nmi).
2023-07-06 17:06:58 +02:00
Raffson
da10cc091c Tweak 'release untasked aircraft' in migrator 2023-07-05 23:34:31 +02:00
Raffson
2ec187bc9c Fixing OCA-bug against FOBs
OCA Aircraft should be properly allowed against FOBs given Ghosti's roadbases
2023-07-05 23:29:48 +02:00
Raffson
785ac6eacc Remove double call to "return_pilots_and_aircraft"
This is already done in 'package.remove_flight(flight)'
2023-07-05 22:30:47 +02:00
MetalStormGhost
be31a97c71 Added new option in settings: SEAD Sweep engagement range (nmi).
Renamed SEAD Sweep threat buffer distance (nmi) to SEAD Escort/Sweep threat buffer distance (nmi) since it also applies to SEAD Escort flights.
2023-07-04 23:19:47 +02:00
MetalStormGhost
aecb171b8f SEAD Sweep threat buffer distance
Added a new option in settings: SEAD Sweep threat buffer distance (nmi).
2023-07-04 23:19:47 +02:00
MetalStormGhost
aafc832e24 Settings option: Generate SCENERY REMOVE OBJECTS ZONE triggers at roadbase
Implemented an option in settings to disable or enable the SCENERY REMOVE OBJECTS ZONE triggers generated at roadbases (at the first waypoint).
2023-07-03 21:09:25 +02:00
Raffson
6c210c9d15 Fine-tuning stuff for 'SEAD Sweep' 2023-07-03 00:38:50 +02:00
MetalStormGhost
031feeed6f F-16A hotifx (#158)
* F-16A hotifx

Added SEAD capability to F-16A MLU and SEAD Escort & SEAD to F-16A. Fixed F-16A MLU icon and banner. Added new F-16A and F-16A MLU loadouts.

* Updated changelog.
2023-07-02 21:13:00 +00:00
Raffson
6ced738256 Reset auto-assignable mission types if liberation save 2023-07-02 21:14:20 +02:00
Raffson
db038ecdea Add SEAD Sweep flight plan
Reintroduce legacy SEAD Escort flight plan, but under a separate type because it didn't really escort the primary flight...
2023-07-02 16:53:55 +02:00
Raffson
e2d9a794b8 Invert factions when "Invert Map" is set in NGW 2023-07-02 13:56:11 +02:00
Raffson
ae17f195fe Fine-tune inversion rules for Operation Desert Sabre 2023-07-02 13:29:29 +02:00
Raffson
46f98a0684 Update Exercise Quasar
Fine-tuned inversion process + add note in description
2023-07-02 13:21:28 +02:00
Raffson
9c6b070dbd Force 'Nothing' task for ferry flights
pydcs doesn't export this task, although every aircraft is capable of doing 'Nothing' 😂
2023-07-02 12:52:19 +02:00
Raffson
569efdbb12 Remove DEAD from Tu-160 & Tu-95MS
You can still create a package against an air defense site using STRIKE...
2023-07-02 12:43:00 +02:00
Raffson
9086262f2b Update changelog 2023-07-02 12:30:34 +02:00
MetalStormGhost
861ed584df Restored TheaterGroundObject handling in antishipingress.py
Restores handling of TheaterGroundObject targets in antishipingress.py
This is needed when anti-ship missions are planned against other ship groups than NavalControlPoints (carrier groups or LHA strike groups).
2023-07-02 12:12:57 +02:00
Raffson
a5083e3e1f Add CHN in country_dict for migration 2023-07-02 00:47:57 +02:00
MetalStormGhost
6e37cadb84 Settings doctrine page + streamlining (#156)
* Added a separate Doctrine page in settings with the following new options:
- Minimum number of aircraft for autoplanner to plan OCA packages against
- Airbase threat range (nmi)
- TARCAP threat buffer distance (nmi)
- AEW&C threat buffer distance (nmi)
- Theater tanker threat buffer distance (nmi)

Implemented handling for the OPFOR autoplanner aggressiveness in objectivefinder.py vulnerable_control_points().

* * Added three new options in Settings:
- Autoplanner plans refueling flights for Strike packages
- Autoplanner plans refueling flights for OCA packages
- Autoplanner plans refueling flights for DEAD packages

Fixed a bug in faction.py where F-16Ds were not correctly removed from the faction when the F-16I/F-16D mod was not selected.

* Renamed Maximum frontline length -> Maximum frontline width.
2023-07-01 23:54:27 +02:00
Raffson
4b4ec8d9ad Add livery selector in SquadronDialog 2023-07-01 23:42:13 +02:00
holyorangejuice
ef0bc31b8c WRL Faction Updates (#155)
* WRL Faction Updates

I've updated all factions to have the same exact air assets to streamline the ability to use my factions anywhere. The difference in my factions is the units used in frontlines, SAMs, and so on. I still have to use custom factions to ensure my missions are played as I designed them. People can customize them from there. The new WRL mixed faction is going to be used in all wargame scenarios and some Sinai map stuff I've been looking at making.
2023-07-01 19:11:59 +00:00
Raffson
f6f1d69048 Fix mypy's complaints 2023-07-01 17:20:02 +02:00
Raffson
3b745c43eb Save-compat changes to allow loading Liberation saves
May be incomplete, some discrepancies in naming may still exist, but at least Kerbo's campaign can be loaded.
2023-07-01 17:08:12 +02:00
Dan Albert
aaaa9847b1 Add channel naming for the warthogs.
The manual for the legacy warthog usually calls these VHF 1/2 and UHF,
or VHF AM/FM and UHF. The AM/FM nomenclature is what I usually hear
people call them and it's clearer, so go with that.

For the A-10C II, that manual hasn't been updated for the AN/ARC-210
yet, so I'm not really sure what that ought to be called. The UFC calls
it COM 1 though, so I went with that. The alternative would be something
like VHF/UHF for the 210 and UHF for the 164, but I don't know if that's
actually better. Could be completely explicit and call them by their
full names, but that's probably less clear to people that aren't
fiddling with the radio implementation constantly (and even I confuse
the 164 and the 186 all the damn time).
2023-07-01 16:05:40 +02:00
Raffson
9e42f7f255 Execute EWRJ triggers after EWRJ script has initialized 2023-07-01 15:27:11 +02:00
holyorangejuice
03a3fd37ef Campaign Release / Updates
All campaigns have F15e added to the roster plus some other minor roster changes. Battle4SyriaNorth released.
2023-06-29 22:11:53 +02:00
MetalStormGhost
e5bb341f5b A-6A Intruder with Adecarcer's hardpoint options from A-7E mod (#153)
* A-6A Intruder with Adecarcer's hardpoint options from A-7E mod

Enabled Adecarcer's modified hardpoint options for the A-6A Intruder mod, now also including weapons from the A-7E Corsair II mod. Please note that the A-7E mod will need to be installed if one wants to use those weapons.

* Moved the import "from pydcs_extensions import WeaponsA7E" to within the VSN_A6A pylon objects to help solve the "cannot import from partially initialized module" error.

* Import pydcs_extensions.a7e before pydcs_extensions.a6a to help solve the "cannot import from partially initialized module" error.

* Added inject_weapons(WeaponsA7E) in a6a.py
2023-06-25 18:21:15 +02:00
Raffson
57a0231699 Move LuaGenerator back to its original location
I can't even remember why I moved this line, but that's the one that broke CTLD...
2023-06-25 17:07:55 +02:00
Raffson
bf5807d9c1 Fix parking space info in AWCD 2023-06-25 15:38:22 +02:00
Raffson
5368c06650 Only use 'Player' skill as flight-lead 2023-06-25 13:43:59 +02:00
Raffson
ab66846967 Add CTLD zones to Exercise Quasar 2023-06-25 13:36:25 +02:00
Raffson
a2b7477404 Update CTLD to latest version 2023-06-25 13:01:22 +02:00
Raffson
3bc4203954 Pydcs fix for FARPs 2023-06-25 03:23:50 +02:00
Raffson
51fc0215d6 Link landing wpt to pad in flightgroupspawner
Fixes a bug where helipads would try to pop from an empty list
2023-06-25 03:21:58 +02:00
Raffson
768f2dbc57 Update control_points earlier in migrator 2023-06-25 01:17:05 +02:00
Raffson
928a679e7d Cleanup import 2023-06-25 01:17:04 +02:00
Raffson
430fcfee20 Fix 'ParkingType is not defined' exception
Would occur when starting at full strength
2023-06-25 01:17:04 +02:00
Dan Albert
50975d5eb6 Razbam F-15E banner and icon.
Just reusing the old one.

https://github.com/dcs-liberation/dcs_liberation/issues/3028
2023-06-23 22:08:39 +02:00
Dan Albert
918716044c Add radio config for the new F-15E.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/3028.
2023-06-23 22:08:38 +02:00
Dan Albert
c7a214201c Add YAML file for Razbam Strike Eagle.
The old DCS AI F-15E is sticking around because the two have very
different weapon sets for now, so it's probably better to use the AI-
only one for squadrons that don't expect players.

I've avoided renaming the old one (we probably should name it "... (AI)"
for clarity) because the rename will break save compat. I have added a
_new_ name that new campaigns can use though.

https://github.com/dcs-liberation/dcs_liberation/issues/3028
2023-06-23 22:08:38 +02:00
Starfire13
cd563d837b Add loadouts for Razbam F-15E Strike Eagle. 2023-06-23 22:08:38 +02:00
Raffson
32df683720 Bump pydcs for Strike Eagle 2023-06-23 22:08:35 +02:00
M Chimiste
e15002dff5 Added F-15E support to factions and campaign YAMLs (#152)
* Added F-15E support to factions and campaign YAMLs

* Update Caucasus_Multi_Full.yaml

* Update Caucasus_Multi_Russia.yaml

* Update Operation-Desert-Sabre.yaml

* Update black_sea.yaml
2023-06-23 19:51:01 +00:00
MetalStormGhost
fb2598a671 Github URL update
Updated the URLs to point correctly to Retribution code, Retribution wiki or Liberation code, as appropriate.
2023-06-21 10:28:26 +02:00
MetalStormGhost
208c517882 Roadbase support in the Migrator, OffMapSpawn
Added support in migrator.py to add the new ground spawn, helipad and roadbase attributes to control points. This time around, also supporting OffMapSpawns and other types of control point besides just Airfield & Fob.
2023-06-20 07:48:32 +02:00
MetalStormGhost
f970801243 Roadbase support in the Migrator
Added support in migrator.py to add the new ground spawn, helipad and roadbase attributes to control points.
2023-06-19 22:05:32 +02:00
MetalStormGhost
e273e93012 Roadbase and ground spawn support (#132)
* Roadbase and ground spawn support

Implemented support for roadbases and ground spawn slots at airfields and FOBs. The ground spawn slots can be inserted in campaigns by placing either an A-10A or an AJS37 at a runway or ramp. This will cause an invisible FARP, an ammo dump and a fuel dump to be placed (behind the slot in case of A-10A, to the side in case of AJS37) in the generated campaigns. The ground spawn slot can be used by human controlled aircraft in generated missions. Also allowed the use of the four-slot FARP and the single helipad in campaigns, in addition to the invisible FARP. The first waypoint of the placed aircraft will be the center of a Remove Statics trigger zone (which might or might not work in multiplayer due to a DCS limitation).

Also implemented three new options in settings:
 - AI fixed-wing aircraft can use roadbases / bases with only ground spawns
   - This setting will allow the AI to use the roadbases for flights and transfers. AI flights will air-start from these bases, since the AI in DCS is not currently able to take off from ground spawns.
 - Spawn trucks at ground spawns in airbases instead of FARP statics
 - Spawn trucks at ground spawns in roadbases instead of FARP statics
   - These settings will replace the FARP statics with refueler and ammo trucks at roadbases. Enabling them might have a negative performance impact.

* Modified calculate_parking_slots() so it now takes into account also helicopter slots on FARPs and also ground start slots (but only if the aircraft is flyable or the "AI fixed-wing aircraft can use roadbases / bases with only ground spawns" option is enabled in settings).

* Improved the way parking slots are communicated on the basemenu window.

* Refactored helipad and ground spawn appends to static methods _add_helipad and _add_ground_spawn in mizcampaignloader.py
Added missing changelog entries.
Fixed tgogenerator.py imports.
Cleaned up ParkingType() construction.

* Added test_control_point_parking for testing that the correct number of parking slots are returned for control point in test_controlpoint.py

* Added test_parking_type_from_squadron to test the correct ParkingType object is returned for a squadron of Viggens in test_controlpoint.py

* Added test_parking_type_from_aircraft to test the correct ParkingType object is returned for Viggen aircraft type in test_controlpoint.py

---------

Co-authored-by: Raffson <Raffson@users.noreply.github.com>
2023-06-19 00:02:08 +03:00
Raffson
5a71806651 Add Sinai campaign: Exercise Quasar
Modified Starfire's 'Exercise Bright Star' campaign
2023-06-18 19:10:38 +02:00
Raffson
430d33c144 Add Mi-8 to Egypt's faction 2023-06-18 16:44:19 +02:00
Raffson
fa63beeb52 Add Gepard & Vulcan to Bluefor Modern 2023-06-18 12:37:40 +02:00
Raffson
e952e0b536 Pydcs fix for ACLS
Fix #147
2023-06-17 22:31:08 +02:00
Raffson
19a6eaeaa3 Yesterday's fix was bogus... 2023-06-17 21:50:00 +02:00
Raffson
5818367f2e Fine-tune SEAD search point 2023-06-17 21:28:33 +02:00
Raffson
d3069bdaad Bold font for waypoint table in kneeboard 2023-06-17 21:28:02 +02:00
Raffson
471654aa5e Forgot to make humvees CJFT blue for convoy spawns -_- 2023-06-17 14:40:34 +02:00
Raffson
56e712f948 Make 'exercise bright star' compatible 2023-06-17 14:31:57 +02:00
Raffson
74bc3b004b Rename beacons file 2023-06-17 14:30:54 +02:00
Dan Albert
6f1333d26a Test NavMeshLayer. 2023-06-17 13:57:08 +02:00
Dan Albert
42a0545630 Test FrontLinesLayer. 2023-06-17 13:57:07 +02:00
Starfire13
1c5f872b3c Add Sinai campaign: Exercise Bright Star. 2023-06-17 13:57:07 +02:00
Dan Albert
e4e7155f02 Partial tests for FrontLine.
We need to mock the backend to usefully test the contextmenu handler.
I'd like to finish all the low hanging fruit before going for that.
2023-06-17 13:56:55 +02:00
Raffson
c924130356 Apply mod-settings to faction before generating game 2023-06-17 04:40:10 +02:00
Raffson
095947333f Remove remnant from testing 2023-06-17 04:40:10 +02:00
Raffson
6356d0b3f5 Update changelog 2023-06-17 04:40:09 +02:00
Raffson
df5b5839e2 Overhauling convoy routes & spawns 2023-06-17 04:40:09 +02:00
M Chimiste
3abb9375fb Create Campaign Operation Desert Sabre (#146)
The very first Sinai campaign by Mr. Chimiste!
2023-06-16 23:41:52 +02:00
zhexu14
322b8245f7 Make waypoint altitudes editable. 2023-06-16 22:42:59 +02:00
Dan Albert
2286d155c4 Test FlightPlansLayer. 2023-06-16 22:42:58 +02:00
Dan Albert
7c1b642aa8 Test CullingExclusionZones. 2023-06-16 22:42:57 +02:00
Dan Albert
198f93fe4a Test ControlPointsLayer. 2023-06-16 22:42:56 +02:00
Dan Albert
ea25e2ceb8 Add tests for CombatLayer. 2023-06-16 22:42:56 +02:00
Dan Albert
453343849f Test Combat. 2023-06-16 22:42:55 +02:00
Dan Albert
cee9122680 Add tests for AirDefenseRangeLayer. 2023-06-16 22:42:55 +02:00
Raffson
ce3e358f8c Fix map objects not being registered when killed 2023-06-16 22:42:55 +02:00
Raffson
018a4b4718 Remove forgotten print-statement 2023-06-16 22:42:54 +02:00
Raffson
e677a1e910 Add EWR Jammer plugin 2023-06-16 22:42:53 +02:00
Raffson
b2fbcdd86a Add DCS Dismounts plugin 2023-06-16 11:32:26 +02:00
Nosajthedevil
1474dfd200 Update OV10A Weapons files 2023-06-15 15:27:53 +02:00
Nosajthedevil
3edb1b6c10 Added and updated weapons files.
Added or updated weapons files including 

The various Hellfire II iterations - this covers fallbacks to rockets
for all 3 Apache variants, the Supercobra, and the Kiowa.
This also adds the 184 Long and 131 pods. 
Lastly, this adds date and fallback information to the shrikes in
advance of the AGM-45B being added to the A-4E mod.
2023-06-15 15:25:44 +02:00
Raffson
0de4b01917 Forgot to run local checks -_- 2023-06-15 04:26:25 +02:00
Raffson
04876c8c66 Downgrade starlette back to 0.27 2023-06-15 04:22:16 +02:00
Raffson
8c29071d04 Fix AI RTB immediately with forced air-start 2023-06-15 03:49:32 +02:00
Raffson
91ee04bf2a Fix save-compat issues 2023-06-15 03:11:12 +02:00
Raffson
666f2908b7 Update dependencies 2023-06-15 01:54:47 +02:00
Raffson
45b8757593 Support for convoy "spawn-route" in campaign design
Resolves #91

'Assault On Damascus' was revamped using this new technique
2023-06-15 01:47:08 +02:00
Raffson
057ea58d8e Modifiers for ground unit transfer window 2023-06-14 03:44:03 +02:00
Raffson
ad227965ea Replace SEAD-Escort with SEAD for Naval TGOs
SEAD Escort is available by default
2023-06-13 17:46:16 +02:00
Raffson
2048910c78 Fine-tune Sinai landmap 2023-06-13 17:13:17 +02:00
Raffson
245bab57c2 Avoid moving empty squadrons if aircraft-type can't fit any slot 2023-06-13 13:30:32 +02:00
Raffson
ef63fe5b5c Renaming what's left in banners 2023-06-12 21:20:01 +02:00
Raffson
e2a14f861e No need for version bump 2023-06-12 20:59:15 +02:00
Dan Albert
2468b1c6d4 Fix Scenic Inland yaml, fixing NGW. 2023-06-12 20:59:15 +02:00
Dan Albert
b62b646e3b Make patch coverage task informational.
Not reasonable to require all PRs to avoid regressing coverage yet...
2023-06-12 20:59:15 +02:00
Dan Albert
5ab97561cc Update Fuzzle's campaigns.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2970.
2023-06-12 20:59:15 +02:00
Dan Albert
b45139bf02 Stop preloading images that are rarely used.
The aircraft banners are only used for the unit info window, and that's
not a normal part of gameplay. We spend a bit over 1.5 seconds
preloading this data at startup (about 25% of the non-game load startup
time). This data is only expensive to load in aggregate, and we never
need all of it. The unit info window is not noticeably slowed by this,
but startup is noticeably faster without it.
2023-06-12 20:59:14 +02:00
Dan Albert
88db6eb5f0 Remove pointless suffixes on banner files.
I think someone just copied this pattern from the icons where the suffix
represented the icon size. These are definitely not 24px banners, and
some of the suffixes are even wrong (_25).
2023-06-12 20:59:14 +02:00
Dan Albert
3b40aef70c Add tests for AircraftLayer. 2023-06-12 20:59:12 +02:00
Dan Albert
389d230ea3 Fix Python coverage reporting.
Apparently the fact that I want the coverage report to be XML isn't
enough of a hint that I want coverage.
2023-06-12 20:59:09 +02:00
MetalStormGhost
765e6f8dbf A-6A Intruder with Adecarcer's hardpoint options
Enabled Adecarcer's modified hardpoint options for the A-6A Intruder mod. This will, for instance, fix the MER bomb racks which have the "missing texture" material in the stock version of the mod. It will also allow mounting some weapons which are more appropriate for the A-6E variant of the aircraft. While not historically and technically accurate, this will allow employing the aircraft in a role which is closer to the EA-6B or A-6E, should the user wish to do so. The EA-6B mod is AI-only, so this will grant a player-flyable option. The use of custom loadouts is recommended.
2023-06-12 20:51:09 +02:00
MetalStormGhost
0e307683b6 Replaced the A-7E Corsair II OCA/Runway loadout AGM-122 Sidearm ARM with an AIM-9 Sidewinder. 2023-06-12 16:45:14 +02:00
Pande4360
5065f4e505 Removed SEAD Escort from fighters. 2023-06-12 05:12:36 +02:00
Raffson
a31017e05a Add AFAC as fallback for CAS/BAI 2023-06-12 01:20:58 +02:00
Raffson
59f04238cb Update gazelle's payloads
Last update introduced some changes to pylons and such..
2023-06-12 01:20:13 +02:00
M Chimiste
c4c23a6692 Create egypt_2010s.json (#137)
First Draft of modernish Egyptian faction
2023-06-11 23:50:17 +02:00
Pande4360
e4fbaf8480 Sw patch (#140)
* Fixed some tasks

* Fixed some tasks

* slimmed down faction

* slimmed down faction

* Fixed Tasks

* test size fix

* -test size fix
+tasks

* tasks

* Loadout: added BAI and strike
2023-06-11 23:49:17 +02:00
holyorangejuice
143cb61734 WRL Missions Brought to 10.7 Standards
I have spent hours making the WRL missions up to 10.7 standards. CTLD zones added to the miz files. Squadron limits added to the yamls. Most importantly the descriptions are all up to date and similar. Might be some minor touch ups from here on out but that majority of it is ready to go. I also deleted up the coast, retiring that legacy campaign which was my first release. Originally designed for liberation.
2023-06-11 23:46:55 +02:00
Raffson
d3e727f205 Fix UI not updating properly after loading settings 2023-06-11 21:29:24 +02:00
Raffson
7e9380162c Don't remove transfers but disable if insufficient room 2023-06-11 21:29:23 +02:00
holyorangejuice
e9760e4a7d WRL File Name Updates (#136)
* WRL Campaign Update Kutaisi2Vaziani

WRL Campaign Update Kutaisi2Vaziani

* WRL File Name Changes

I'm planning some full updates to the campaigns. I wanted an easier file structure to manage. File name changes only, tested prior to commit.
2023-06-11 18:48:01 +02:00
holyorangejuice
0d070d7652 New WRL Campaign
New WRL campaign added, also name fix for Caucasus - 1968 Yankee Station. I was annoyed by the naming convention not matching the rest.
2023-06-11 03:24:03 +02:00
Raffson
57c2850c87 Finish Sinai support
Resolves #134
2023-06-11 03:22:09 +02:00
Raffson
ce9c3074ff Add arcgis data for Sinai 2023-06-11 03:21:31 +02:00
Raffson
4cfc11bc79 Another pydcs bump/fix 2023-06-10 13:17:53 +02:00
Raffson
27fcef7517 Pydcs fix 2023-06-10 02:51:00 +02:00
Raffson
e980e7a5f8 More preparations for Sinai 2023-06-10 02:29:28 +02:00
Raffson
3b5af93e2d Add beacons for Sinai 2023-06-10 02:03:41 +02:00
Raffson
a3d43e1815 Weapon updates for mods 2023-06-10 01:03:29 +02:00
Raffson
f9af1ed184 Missing dependency for import_beacons.py 2023-06-10 00:43:16 +02:00
Raffson
abe6691206 Pydcs update
Support for 2.8.6 + Sinai
2023-06-10 00:30:50 +02:00
Raffson
9c1be534c7 Fix bugs reported in Discord
- Fixes ferry flights causing errors when "Nothing" is not available
- Logs a warning when a frontline stance is suddenly no longer available and uses defense stance as fallback which used to be the default. An investigation is still required to determine the cause of this...
2023-06-09 19:28:18 +02:00
Raffson
055519411b Apparently stances use the connected CP's ID -_- 2023-06-05 21:09:38 +02:00
Raffson
9b26c2ddca Use frontline stance for group sizes 2023-06-04 21:45:38 +02:00
MetalStormGhost
aad908a716 Now applies the ratio between ground unit limit and the total number of ground units to each unit type when planning the ground war. This will help with monocultures of certain unit types when the control point has more units than can be spawned in one mission. 2023-06-04 21:45:38 +02:00
Raffson
c69f536639 Improve accuracy w.r.t. parking space info 2023-06-04 21:45:37 +02:00
Raffson
fbdc3e896e Tweak parking info in air wing config dialog
Update changelog.md
2023-06-04 19:12:06 +02:00
MetalStormGhost
f7f7925260 Banners for Swedish Military Assets Pack v1.10
Added banners for Swedish Military Assets Pack v1.10 and flipped the pre-existing icons horizontally to match the rest of the ground vehicle icons. Source images for the banners made by Currenthill, used here with his explicit permission.
2023-06-04 14:29:05 +02:00
Dan Albert
a97fd69828 Show parking capacities in air wing config.
This does show the theoretical parking use of full squadrons even when
the new rules are not enabled. Since limits can be enabled manually
later in the game, it's still useful information, even if it's a bit
misleading.

https://github.com/dcs-liberation/dcs_liberation/issues/2910
2023-06-04 14:15:58 +02:00
Dan Albert
8a57104b0f Configure squadron sizes for Abu Dhabi. 2023-06-04 13:34:57 +02:00
Dan Albert
d88b5da382 Fix Black Sea LHA parking limits.
Everything else was within the limits, but I had forgotten to check the
LHAs.
2023-06-04 13:34:57 +02:00
Starfire13
c69b797ff3 Add Final Countdown II campaign.
Designed for Normandy 2.0
2023-06-04 13:30:15 +02:00
Raffson
e3c9806afb Update china_2010.json
Adding submarine and replace T-55 with Type 59

Co-Authored-By: ColonelAkirNakesh <93631947+ColonelAkirNakesh@users.noreply.github.com>
2023-06-04 13:28:57 +02:00
Raffson
fa27ad96c7 Updating Russian factions 2010 & 2020
Adding ships...
2023-06-04 13:27:32 +02:00
Raffson
9d60418aef Add 'FH M2A1 105mm' howitzer to allies_1944
Co-Authored-By: ColonelAkirNakesh <93631947+ColonelAkirNakesh@users.noreply.github.com>
2023-06-04 13:24:00 +02:00
Raffson
bf277c6c06 Enforces Topgun: Maverick Rogue Nation livery for Iranian Tomcat
Co-Authored-By: ColonelAkirNakesh <93631947+ColonelAkirNakesh@users.noreply.github.com>
2023-06-04 13:21:51 +02:00
ColonelAkirNakesh
b398b72f94 Create TYPE-59.yaml 2023-06-04 13:17:17 +02:00
ColonelAkirNakesh
f2bdc3e0ee Create Horch_901_typ_40_kfz_21.yaml 2023-06-04 13:16:51 +02:00
ColonelAkirNakesh
813d68bf31 Create Pak40.yaml 2023-06-04 13:16:51 +02:00
ColonelAkirNakesh
883d08209d Create Wespe124.yaml 2023-06-04 13:16:51 +02:00
ColonelAkirNakesh
2aeee4ced6 Create LeFH_18-40-105.yaml 2023-06-04 13:16:50 +02:00
ColonelAkirNakesh
1b39d21750 Create M2A1-105.yaml 2023-06-04 13:16:15 +02:00
ColonelAkirNakesh
6d7d7a4439 Create BDK-775.yaml 2023-06-04 13:11:39 +02:00
ColonelAkirNakesh
6452f879bf Create santafe.yaml 2023-06-04 13:11:11 +02:00
ColonelAkirNakesh
0d8064c582 Create leander-gun-condell.yaml 2023-06-04 13:10:46 +02:00
ColonelAkirNakesh
83e63487e5 Create leander-gun-lynch.yaml 2023-06-04 13:10:28 +02:00
Dan Albert
4f00ec9546 Add missing note about 7.0.0 -> 7.1.0 save compat. 2023-06-04 13:05:19 +02:00
Dan Albert
402e4a54d5 Update beacon data.
Did this for Normandy 2... but unsurprisingly there aren't a whole lot
of beacons in a WW2 map.
2023-06-04 13:04:31 +02:00
Raffson
d8e4fa6f80 Update pydcs 4 Normandy 2.0 2023-06-04 13:03:36 +02:00
Dan Albert
be9f83e221 Update Normandy landmap for Normandy 2.
https://github.com/dcs-liberation/dcs_liberation/issues/2804
2023-06-04 13:01:10 +02:00
Dan Albert
cc0d1d5ab6 Add Normandy 2 landmap inputs.
https://github.com/dcs-liberation/dcs_liberation/issues/2804
2023-06-04 13:01:09 +02:00
Dan Albert
df922e9ca7 Force polygons into validity during GIS import.
Not sure why, but some polygons become invalid (which usually means a
self-intersecting "polygon", such as two triangles that meet at a point)
during this transformation. Shapely includes a tool to reshape polygons
into validity, so use that.
2023-06-04 13:01:09 +02:00
Dan Albert
5600257442 Add performance option to prevent missile tasks.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2508.
2023-06-04 12:57:16 +02:00
Raffson
e9a3bd0640 Replace more Patriot STRs with real EWRs.
Not all of these nations actually field this radar (according to
Wikipedia), but at least it's a real EWR, and it's the only blue one
we've got.
2023-06-04 12:56:11 +02:00
Dan Albert
6cb67edfcb Add changelog note for BAI fix.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2922.
2023-06-04 12:51:36 +02:00
zhexu14
7e53c36d2c Add ability to convert landmap to/from miz.
This PR adds utility functions that import/export landmap files to .miz
polygons. In addition to the unit test, this PR has been tested by
writing the Caucuses & Syria landmaps to a .miz file, loading the
generated .miz file back in and checking that the loaded landmap object
is identical to the original files.
2023-06-04 12:51:36 +02:00
zhexu14
a54f836cde issue 2922: make BAI plannable against missile and costal sites 2023-06-04 12:48:34 +02:00
zhexu14
aa64f28a4b make BAI plannable against vehicles only 2023-06-04 12:48:33 +02:00
Raffson
2251018de2 Revert attempt at improving convoy spawning
Unfortunately this quick fix didn't do the trick, we'll probably have to stick with #91
2023-06-04 12:39:58 +02:00
Raffson
c559d6e2be Follow-up on flight TOT offset 2023-06-04 01:50:46 +02:00
Dan Albert
529eea0c01 Fix formatting of takeoff time. 2023-06-04 00:38:47 +02:00
Dan Albert
0acf970443 Add UI for TOT offset adjustment.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2912.
2023-06-04 00:36:51 +02:00
Dan Albert
7a57bd3ee0 Save the TOT offset in the flight plan.
Prep work for exposing this to the UI.
2023-06-04 00:31:38 +02:00
Raffson
5d127744ff Updating loadouts 2023-06-03 21:01:07 +02:00
Raffson
6e15599691 Update changelog 2023-06-03 19:38:03 +02:00
Raffson
e018ef7f11 Formatting + typing 2023-06-03 19:27:50 +02:00
Raffson
b318bc4941 Tweak ingress waypoint tasks for OCA-Runway, STRIKE & FighterSweep 2023-06-03 19:04:38 +02:00
Raffson
962c64d065 Refine DCS tasks for different mission types 2023-06-03 19:04:37 +02:00
Raffson
bbf8e69659 Split Anti-Ship from BAI 2023-06-03 19:04:37 +02:00
Raffson
2ed85792b9 Split full-strength start from squadron aircraft limits 2023-06-03 16:04:27 +02:00
Raffson
db7bcaa45c Properly indicate save-incompatibility issues to users 2023-06-03 16:04:27 +02:00
holyorangejuice
2225d6ef8e WRL Mission Updates
Yamls are updated for units and description. Damascus Miz updated so I could name the SAM sites for the yaml definitions.
2023-06-03 14:11:59 +02:00
Raffson
703da303ee Migrator fix for 'Hipico' 2023-05-30 21:36:50 +02:00
MetalStormGhost
54a471fe82 Improved convoy form-up behaviour
Adds one waypoint with PointAction.Cone and then orders convoys on road. This is done to make convoys start to move immediately, instead of waiting (potentially) a long time for them to form up and actually start moving.

Credit to Farrago for suggesting this.
2023-05-30 21:12:05 +02:00
Raffson
f8f37e8986 Adjust Tornado's task priorities 2023-05-29 16:21:36 +02:00
Raffson
027dd88feb Replaces Patriot STR with AN/FPS-117 EWR, adds USS Harry Truman
Co-Authored-By: ColonelAkirNakesh <93631947+ColonelAkirNakesh@users.noreply.github.com>
2023-05-29 12:40:05 +02:00
Raffson
118005528c Remove duplicate changelog entry 2023-05-29 12:33:59 +02:00
Dan Albert
bcd81bae7c Make the flight details menu modal.
Prevents players from accidentally deleting flights they're currently
viewing, which would cause an error.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2911.
2023-05-29 12:24:28 +02:00
Dan Albert
fa70ba5d29 Fix line endings.
These get broken whenever someone uses the GitHub file upload editor,
since that doesn't understand .gitattributes.
2023-05-29 12:24:28 +02:00
Starfire13
0c93d281bd Update Golan Heights and Caen to Evreux campaigns.
I had asked Khopa for permission to edit two of his campaigns to fix
some issues. Only the YAMLs have been edited, .miz files did not need
changes. I have tested both YAMLs to make sure campaigns will generate.
Also tested generating turn 1 .miz and ran it in DCS.

Golan Heights:
1. Removed the 2 problematic squadrons from Marj Ruhayyil that were
causing aircraft losses due to larger aircraft sizes not fitting at that
airfield (which is intended for helicopters).
2. Implemented squadron limits.

Caen to Evreux:
1. Re-arranged squadrons for better force distribution and revised
primary and secondary mission types for better default play experience.
2. Implemented squadron limits.
2023-05-29 12:24:27 +02:00
Starfire13
3e7968adce Update Apache loadouts.
BAI loadout updated to use the new radar guided hellfires. Aux tanks
removed in favour of extra cannon ammo.
2023-05-29 12:23:36 +02:00
MetalStormGhost
e5a4e54544 Updated changelog. 2023-05-29 12:20:04 +02:00
MetalStormGhost
1540b2cf31 Use SEAD task for stock Tornado SEAD/DEAD
The stock Tornado is capable of the SEAD task in DCS, but not the CAS task, so use SEAD for Tornadoes when flying SEAD or DEAD missions. Also, feel free to add to the list if other airframes should use SEAD as well for cosmetic reasons (would show SEAD task in the mission editor and F10 map in-game).
2023-05-29 12:20:04 +02:00
Raffson
198ff7d8a3 Allow deletion of certain types of waypoints
Resolves #60
NAV/REFUEL/DIVERT waypoints should have no effect on the timings.
2023-05-29 00:11:27 +02:00
SnappyComebacks
8363a7e8fa Set units on the frontline to Hidden On MFD. (#2669)
All groups (friendly and enemy) that are part of the front line are set
to `Hidden On MFD`. This is a group level filter, and can not be applied
on a per unit basis.
2023-05-28 21:20:12 +02:00
Raffson
33ee4b1d1a Tweaking F-18s task-priorities 2023-05-28 21:11:14 +02:00
Raffson
7582040d41 Allow harriers to operate from FOBs/FARPs
Resolve #109
2023-05-28 21:05:17 +02:00
Raffson
b8cb1a6273 Fix corruptions due to overpopulation 2023-05-28 15:17:40 +02:00
Raffson
b958e9b095 Avoid overpopulating bases
With the new aircraft limitations for squadrons, airfields/fobs/farps could get overpopulated at campaign start. This would cause all sorts of gnarly things like squadrons ending up with a negative amount of "owned aircraft".
2023-05-28 14:28:41 +02:00
Raffson
35607515f6 Fix potential bug when switching campaigns
Without calling __setstate__ the duration options are not properly transformed to timedelta objects
2023-05-28 13:33:49 +02:00
Raffson
28deb36f2c Fix exception when changing campaigns
Calling setCurrentText with an Enum will obviously cause issues...
2023-05-28 12:51:51 +02:00
Raffson
7e6a6cc61a Pydcs fix for invalid pylons 2023-05-28 00:33:41 +02:00
Raffson
78990b8953 Use actual procurement ratio for turn 0 2023-05-28 00:23:53 +02:00
Raffson
4898cdb614 Reuse the settings screen in NewGameWizard
Resolves #123
2023-05-27 21:50:08 +02:00
Raffson
0923add514 Break QNewGameWizard up into pieces 2023-05-27 16:44:01 +02:00
Raffson
d5e019b078 Fix the fix -_- 2023-05-21 21:16:19 +02:00
Raffson
b81030849d Yet another pydcs fix wrt livery scanner -_- 2023-05-21 20:29:34 +02:00
Raffson
0cbf859c94 Fix mistake from yesterday -_- 2023-05-21 20:13:49 +02:00
Raffson
e224990c85 Pydcs fix error when callsigns run out 2023-05-21 17:15:29 +02:00
Raffson
ca78baa008 Fix AWCD choosing wrong liveries for preset squadrons 2023-05-21 14:14:19 +02:00
Raffson
72eb6622ac Cleanup country_shortname, use country.shortname instead 2023-05-21 14:13:33 +02:00
Raffson
f9e15951f2 Support for Iron Dome v1.2 by IDF Mods Project
Resolves #124
2023-05-20 23:48:53 +02:00
Raffson
a46e32cdf1 Add Spanish Naval Assets pack v3.2.0 by desdemicabina
Resolves #119
2023-05-20 20:17:54 +02:00
Raffson
2b89307897 Remove VINSON picture 2023-05-20 20:16:39 +02:00
Raffson
ffcdecad30 Add HMS Invincible picture for CP window 2023-05-20 18:27:17 +02:00
Raffson
167a870f6a Add some missing ship yaml files 2023-05-20 18:00:22 +02:00
Raffson
95083929ce Another pydcs livery-scanner fix
The code to gracefully handle bad zip-files also went lost during the the updates from upstream -_-
2023-05-20 15:59:35 +02:00
Raffson
50378e624a Update README.md 2023-05-20 15:46:22 +02:00
Raffson
93b3157c35 Add 'Fog' class to CustomUnpickler
This class also got refactored and moved into its own package, hence we need to add it to the custom unpickler
2023-05-20 01:41:37 +02:00
Raffson
5eee136189 Fix final country-refactor related issues? 2023-05-20 01:41:37 +02:00
Raffson
1a5e31c01a Migrate squadron countries 2023-05-20 01:41:37 +02:00
Raffson
1b8df76682 Fix indentation error 2023-05-19 22:45:58 +02:00
Raffson
70fb387268 Bump starlette + fastapi 2023-05-19 22:45:57 +02:00
Raffson
27686f6d78 Update contributors 2023-05-19 22:20:40 +02:00
Dan Albert
e7f20e7d10 Fuzzle campaign updates.
https://github.com/dcs-liberation/dcs_liberation/issues/2889
2023-05-19 20:54:09 +02:00
Dan Albert
dbc6a25791 Update contributors list.
<3
2023-05-19 20:52:05 +02:00
Dan Albert
d7d803d4f9 Fix campaign line endings. 2023-05-19 20:51:35 +02:00
Raffson
461c239cc8 Use regex-extractor again to process livery-description files 2023-05-19 20:24:36 +02:00
Raffson
05fb5872d0 Gracefully handling parse-errors in pdycs' livery scanner 2023-05-19 17:46:36 +02:00
Raffson
f72f3316f4 Fix bug in migrator 2023-05-18 23:05:57 +02:00
Raffson
5909ce372b Remove debugging try-except 2023-05-18 22:36:17 +02:00
Raffson
386844378e More save-compatibility changes 2023-05-18 22:33:11 +02:00
Raffson
a9e48770b7 Read A7 mod setting from campaign 2023-05-18 19:08:28 +02:00
Raffson
99b56f1ab7 Update campaign version 2023-05-18 18:51:35 +02:00
Raffson
8cc7f66169 Update mods wrt updated weapons 2023-05-18 18:46:07 +02:00
Raffson
733645e370 Bump pydcs 2023-05-18 18:11:14 +02:00
Raffson
a20fc50aed Add pytest-cov to requirements.txt 2023-05-18 16:54:28 +02:00
Raffson
978e3e4de2 Formatting + type annotations 2023-05-18 16:29:20 +02:00
Raffson
da109146c9 Fixing issues after using actual Country in Faction 2023-05-18 16:24:49 +02:00
Raffson
cca45d3729 Fix save-compat issues 2023-05-18 14:37:34 +02:00
Raffson
b14059e134 Fixing tests + mypy issues 2023-05-18 14:12:41 +02:00
Starfire13
cb6a7d4a92 Update Starfire's campaign updates for 10.7. 2023-05-18 14:03:20 +02:00
Dan Albert
51d823a0b2 Add changelog note for pydcs livery scanner crash fix.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2747.
2023-05-18 14:02:39 +02:00
Dan Albert
5cfc9aa518 Add changelog note for TALD overfly fix.
https://github.com/dcs-liberation/dcs_liberation/issues/2781
2023-05-18 14:02:39 +02:00
Dan Albert
0dea18f582 Add changelog note for "recreate as" fix.
https://github.com/dcs-liberation/dcs_liberation/issues/2779
2023-05-18 14:02:39 +02:00
Dan Albert
53df67e641 Add changelog note for fixed recovery tanker tasks.
https://github.com/dcs-liberation/dcs_liberation/issues/2771
2023-05-18 14:02:39 +02:00
Dan Albert
2448ffe337 Add changelog note for improved dead event handling.
https://github.com/dcs-liberation/dcs_liberation/issues/2765
2023-05-18 14:02:38 +02:00
Dan Albert
9ace5bab94 Add changelog note for air wing reset fix.
https://github.com/dcs-liberation/dcs_liberation/issues/2751
2023-05-18 14:02:38 +02:00
Dan Albert
ccf2d11034 Add changelog note for BAI planning fix.
https://github.com/dcs-liberation/dcs_liberation/issues/2618
2023-05-18 14:02:25 +02:00
Dan Albert
190b6c6e7a Add changelog note for built-in TGP fix. 2023-05-18 14:02:25 +02:00
Dan Albert
573ccb0bc6 Add changelog note for AEW&C planning fix.
https://github.com/dcs-liberation/dcs_liberation/issues/2048
2023-05-18 14:02:25 +02:00
Dan Albert
910fd27c14 Add changelog note for TALD fix.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2780.
2023-05-18 14:02:06 +02:00
Dan Albert
5f65298728 Finish wiring up SAM orientation control.
The UI works beautifully, but that's not worth much if it doesn't
actually change the data...

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2479.
2023-05-18 14:02:05 +02:00
Raffson
4ca150198e Delete germany_1944_free.yaml 2023-05-18 14:01:12 +02:00
Raffson
71f6186a88 Reuse Ghosti's NightMission conditions 2023-05-18 13:40:29 +02:00
zhexu14
e3fce5fddc add unit tests for mission_types function in controlpoint objects (#2870)
This PR adds unit tests that can be planned against control points.
2023-05-18 13:33:49 +02:00
zhexu14
958252515e Update ADM141 data to include clsids for hornet 2023-05-18 13:33:48 +02:00
Dan Albert
0e139b8640 Make wind speed moddable.
These should probably be overridable per theater and per season, but
even with that we'll want some defaults.

https://github.com/dcs-liberation/dcs_liberation/issues/2862
2023-05-18 13:33:39 +02:00
Dan Albert
8ed843a9cf Move and split up weather.py.
This is getting out of hand, and I'm about to make it worse.
2023-05-18 13:31:26 +02:00
Dan Albert
080d346517 Rework wind speed Weibull inputs, tune.
The previous method of using a uniform scalar of the MSL wind speed for
higher altitudes didn't offer enough control. In particular, the shape
needs to be quite different to skew low, mid, high.

This patch reworks that system so the parameters of each distribution
are configured per-altitude level. To keep some continuity between
altitudes (on a windy day, all levels should have higher wind speeds on
average), the wind speed of the lower altitude will be added to the
scale value of the higher altitude.

Since it wasn't practical to approximate the previous behavior with the
new system, this also handles the tuning of each. The low altitude
speeds remain mostly unchanged (typically around 5 knots expect for
thunderstorms), but the average speeds for other altitudes went up to
more closely match the previous intent but without the massive
overshoot. At 2000m wind speeds are typically in the 20-25 knot range
now, and 8000m 30-50 knots.

https://www.quora.com/What-is-the-average-wind-speed-at-different-altitudes
has some of the source data, and Quora is the most authoritative source
there is. It claims that cruise altitude winds can get "as high as 150
knots", but doesn't claim anything about the average. I had a
surprisingly difficult time finding good data for cruise altitude air
speeds for non-jet stream paths (though many of our maps are in jet
streams), so I just eyeballed it from
https://turbli.com/wind-during-flights/.

https://github.com/dcs-liberation/dcs_liberation/issues/2861

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2863.
2023-05-18 13:29:02 +02:00
Dan Albert
73e0cbe182 Use weibull distribution for wind generation.
Wind speeds should not be uniformly distributed. This switches to a
Weibull distribution which allegedly (see the bug) is good enough.
Experimentally that seems true as well, though I know nothing about how
wind works irl. This at least looks like it'll generate reasonable
variation in missions while keeping the 1st through 3rd quartile
behaviors from getting out of hand.

I'm very uncertain about the scaling factor aspect of this. Naively the
wind speeds at different altitudes ought to be somewhat correlated, but
I'm not sure how much, and whether this kind of scaling is at all the
right way to do it. As before, meh, close enough?

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2861.
2023-05-18 13:29:02 +02:00
Raffson
b750099b16 Add missing tasks in aircraft yamls 2023-05-18 13:14:22 +02:00
Raffson
f00a3fb1d2 pydcs update: refactored livery-scanner 2023-05-16 21:45:27 +02:00
Raffson
1b1090d78f Fix exception wrt typing 2023-05-16 21:45:26 +02:00
Raffson
34645560bf Migration changes 2023-05-16 21:45:26 +02:00
Raffson
785beffee0 Cleanup after cherry-picking... 2023-05-14 21:37:24 +02:00
Dan Albert
f9e9c2793a Allow manual SAM orientation.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2479.
2023-05-14 21:08:29 +02:00
Dan Albert
83916b9fb0 Wrap settings titles. 2023-05-14 21:08:09 +02:00
zhexu14
ac5c13663b add tests for mission_types for various theather ground objects 2023-05-14 21:08:09 +02:00
ColonelAkirNakesh
e92d6d0c1c Makes guard tower (house2arm) a AAA unit 2023-05-14 21:08:09 +02:00
Dan Albert
b44249317a Remove old aircraft selection mode.
New mode seems to be working well.
2023-05-14 21:04:43 +02:00
Starfire13
e8e0452f32 Update B-52H.yaml
Added antiship mission type to B52
2023-05-14 21:04:09 +02:00
Dan Albert
0608089eb0 Use the actual Country type instead of the name.
We want other pieces of country information (in particular the short
names). This cleans up a lot of code anyway.

As an added bonus, this now catches squadrons that used invalid names
which would previously be passed through to pydcs and... then I don't
know what would happen.
2023-05-14 21:04:09 +02:00
Dan Albert
b3e21498fd Fix broken squadron.
It's "The Netherlands", not "Netherlands".
2023-05-14 20:57:43 +02:00
ColonelAkirNakesh
9c407a6204 Germany 1944 free unit list fixes.
1. Removes Flak18 as these currently require Kdo.G.40. (part of the WW2
Asset Pack) to fire at player. ED-supplied single missions are having
Flak18s w/out WW2 Asset Pack firing at a point in the air.

2. Adds Opel Blitz to frontline for more variety, balance, and some
little bit of historical accuracy. My understanding is late war was more
truck strafing than HVARing Panzers.

3. Adds guard tower to AAA units to have a functioning AAA since Flak18s
don't work.
2023-05-14 20:57:43 +02:00
Dan Albert
9ec879cea0 Move code docs to a readthedocs project.
Ported the existing docs, but the real goal is getting the docs for
campaign version moved here, as well as the manual. RTD will have an
"edit on github" button that'll keep this accessible to players, but
it'll be much easier to keep docs up to date while developing features
if it's part of the code base.
2023-05-14 20:57:29 +02:00
Dan Albert
5c07a2556e Add option to limit squadron sizes and begin full.
Adding temporarily as an option to make sure it's not a terrible idea,
but the old mode will probably go away.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1583.
Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2808.
2023-05-14 20:56:58 +02:00
Dan Albert
f719e1cfe7 Add an option to prefer primary tasked aircraft.
We're still using mostly the same aircraft selection as we have before
we added squadrons: the closest aircraft is the best choice.

This adds an option to obey the primary task set by the campaign
designer (can be overridden by players), even if the squadron is farther
away than one that is capable of it as a secondary task.

I don't expect this option to live very long. I'm making it optional for
now to give people a chance to test it, but it'll either replace the old
selection strategy or will be removed.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/1892.
2023-05-14 20:33:55 +02:00
Dan Albert
e95a6bf759 Support replacing squadrons in-place. 2023-05-14 17:37:53 +02:00
Raffson
7bbf5d59e4 Update package-lock.json 2023-05-14 17:31:18 +02:00
zhexu14
790d08fa13 enable AEWC missions on FOBs 2023-05-14 17:30:20 +02:00
Dan Albert
0a4706e6f4 Make patch check informational as well. 2023-05-14 17:28:54 +02:00
Dan Albert
af833f6682 Expand python coverage, use coveragerc. 2023-05-14 17:28:54 +02:00
Dan Albert
6de4df23b2 Make coverage checks informational.
PRs shouldn't be failing because coverage dropped. Not until our tests
aren't terrible, anyway.
2023-05-14 17:28:47 +02:00
Dan Albert
2c9dcd430f Add a test for the Aircraft component.
Leaflet (or maybe react-leaflet?) isn't very testable, so we can really
only test that mocks were called with the right props for the leaflet
components we expect, but that's still better than nothing.
2023-05-14 17:28:46 +02:00
Dan Albert
71c2eb3567 Test typescript and collect coverage.
We don't actually have any tests yet :(
2023-05-14 17:28:46 +02:00
Dan Albert
c02351fde3 Don't fail PRs that regress coverage.
Most of our code is barely testable and our coverage isn't anything to
brag about.
2023-05-14 17:27:39 +02:00
Dan Albert
968a360fde Gather and upload coverage of python tests. 2023-05-14 17:27:38 +02:00
Dan Albert
03c48376c4 Disallow squadrons from disabling mission types.
After this change, players will always have the final say in what
missions a squadron can be assigned to. Squadrons are not able to
influence the default auto-assignable missions either because that
property is always overridden by the campaign's air wing configuration
(the primary and secondary task properties). The `mission-types` field
of the squadron definition has been removed since it is no longer
capable of influencing anything. I haven't bothered cleaning up the now
useless data in all the existing squadrons though.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2785.
2023-05-14 17:18:55 +02:00
Raffson
5e2625ad21 Propagate changes from Liberation 2023-05-14 17:09:41 +02:00
Raffson
711d102425 Only use bold font on kneeboard
Should fix an issue where letters seem transparent in VR
2023-05-14 16:54:48 +02:00
Raffson
55f1f5c548 Init files for pydcs_extensions 2023-05-14 16:54:48 +02:00
Raffson
b8e2ae9bb7 Fix copy-paste error 2023-05-14 16:07:11 +02:00
Raffson
6ed5283454 Tweaking tasks in aircraft yamls 2023-05-14 16:06:57 +02:00
Raffson
231a857813 Fix mypy errors 2023-05-14 15:16:21 +02:00
Raffson
40f4b1c926 Update dependecies 2023-05-14 15:16:00 +02:00
Raffson
3ce0473c86 Cleanup 2023-05-14 15:13:44 +02:00
Dan Albert
0b6575ca97 Use task priorities from aircraft yamls.
Preferred aircraft per task are now determined by a ranking of weights
stored in the aircraft yaml files. To aid in visualizing the priorities
across aircraft, Liberation can be run with the argument
dump-task-priorities to dump a yaml file in Saved
Games/DCS/Liberation/Debug/priorities.yaml, which will show each task
along with priority sorted aircraft and their weights.

The current weights in the data were exported from the existing lists,
where each position from the bottom of the list was worth 10 (to allow
some games for less shuffling later).

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2809.
2023-05-14 14:50:30 +02:00
Dan Albert
9ebbe11d83 Add debug command to dump aircraft priorities.
https://github.com/dcs-liberation/dcs_liberation/issues/2809
2023-05-14 14:30:19 +02:00
Dan Albert
016a9b5762 Load task priorities from YAML.
Not used yet.

https://github.com/dcs-liberation/dcs_liberation/issues/2809
2023-05-14 14:19:47 +02:00
Dan Albert
ddd6063f25 Export current task priorities to aircraft yamls.
Nothing fancy. Rank in reversed (so earliest items in the original list
have the highest index) list * 10 (to leave gaps for balancing).

https://github.com/dcs-liberation/dcs_liberation/issues/2809
2023-05-14 14:14:59 +02:00
Dan Albert
81554c510e Fix merged classvars in UnitType descendants.
```
>>> class Foo:
...     bar = 0
...     @classmethod
...     def set_bar(cls, v):
...             cls.bar = v
...
>>> class Bar(Foo):
...     ...
...
>>> Bar.set_bar(1)
>>> Bar.bar
1
>>> Foo.bar
0
>>> class Foo:
...     bar = {}
...     @classmethod
...     def add(cls, k, v):
...             cls.bar[k] = v
...
>>> class Bar(Foo):
...     pass
...
>>> Bar.add(0, 1)
>>> Bar.bar
{0: 1}
>>> Foo.bar
{0: 1}
```

The collections are copied by reference into the descendants, whereas
_loaded is copied by value, so that one can stay. Before this patch,
every subtype was loading because _loaded was set per subclass, but they
were all registering with a common collection defined by UnitType rather
than their own class.
2023-05-14 14:08:37 +02:00
Pande4360
1d1df15d5d SW2.6 Fixed Faction 2023-05-12 07:58:57 +02:00
Pande4360
8e12f4e790 SW2.6 Fixed loadouts x/y wing 2023-05-12 07:58:57 +02:00
Pande4360
179850cdb6 Support for SW v2.6 mod (#120)
* Removed AI varients

* Removed AI varients Logo/Banners

* Removed AI varients Logo/Banners

* Removed AI varients ai flgith planner

* Removed AI varients SWPack Fix

* Removed AI varients SWPack - SWWeapon fix
2023-05-11 21:48:19 +02:00
holyorangejuice
bf14c8e32d WRL Campaign Update Kutaisi2Vaziani (#118) 2023-05-08 00:21:40 +02:00
zhexu14
f965c9ffa7 Handle edge case where aircraft have built in TPGs 2023-05-07 21:43:41 +02:00
zhexu14
0ff60283c2 Add GBU-27 yaml 2023-05-07 21:41:18 +02:00
Dan Albert
06094c54b3 Format aircraft yamls. 2023-05-07 21:33:46 +02:00
zhexu14
065f90ea98 Reset flight's flight plan builder when changing task type.
The mechanism for how this bug arises is that the *WaypointGenerator*
uses the *FlightWaypoint.waypoint_type* to decide whether to generate
the waypoint in the .miz file using a *DeadIngressBuilder* or a
*SeadIngressBuilder*. This *waypoint_type* is set by
*ato.flightplans.<sead|dead>.Builder*, which is set when *ato.flight* is
initialised in the *Flight._flight_plan_builder* member variable based
on *Flight.flight_type*. When *Flight.flight_type* is updated when the
flight is changed from SEAD->DEAD, *Flight._flight_plan_builder* is not
updated in the development build, resulting in it continuing to generate
SEAD waypoints.

This PR adds *set_flight_type()* which sets the *flight_type* property
and updates *Flight._flight_plan_builder* and uses this function when
converting flight types. Ideally, *flight_type* should be made private
and only accessed through getter/setter functions that encapsulate this
behavior, but that would mess up any existing liberation save files.

This PR was tested by:
1. Opening the save file from Issue 2779 in the development build
2. Clicking "Take Off" and confirming that the Weapon Release Type is
"Guided" at the Ingress Waypoint as described in the issue.
3. Opening the save file from Issue 2779 in this PR
4. Converting the SEAD2DEAD flight from DEAD back to SEAD, and then from
SEAD to DEAD
5. Clicking "Take Off" and confirming in the mission editor that the
SEAD2DEAD flight has Weapon Release Type set to "Auto" at the Ingress
Waypoint.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2779.
2023-05-07 21:19:53 +02:00
zhexu14
c44fe4c89e Track S_EVENT_KILL and S_EVENT_UNIT_LOST as well.
Catch a few more death signals. Still not perfect but a bit better.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2765.
2023-05-07 21:19:53 +02:00
Nosajthedevil
1964f30bfc Added many premade squadrons.
Added a number of squadrons - US Vietnam War era squadrons for the
OV-10A and F-4B/C, US squadrons for the A-10A, iran squadrons for the
F-4E, and russian, iranian, vietnamese, syrian, and north korean
squadrons for the Mig-21.
2023-05-07 21:19:52 +02:00
Dan Albert
07bd76cd61 Fix typo in Germany 1944.
Oops.
2023-05-07 21:04:15 +02:00
Dan Albert
32b0a3bb12 Replace a bunch of escape sequences.
Unify on using the real letter instead of an escape sequence for better
searchability/readability. There should be no functional change here (if
any, they should be bug fixes).

https://github.com/dcs-liberation/dcs_liberation/issues/2786
2023-05-07 21:04:14 +02:00
Dan Albert
4b8bfae7c9 Fix bad character escape in Caen to Evreux.
The escape sequence looks to be valid according to the spec (although it
would need to be double quoted, but that doesn't fix it), but Python's
parser doesn't appear to handle that. Just use ü.

https://github.com/dcs-liberation/dcs_liberation/issues/2786
2023-05-07 19:31:20 +02:00
Dan Albert
0697a0dd5b Fix file encoding for some loads.
We've actually been pretty good at getting this right in most of the
code base, but we've missed it in a few places. Python defaults to the
encoding of the current locale unless otherwise specified, and for a US
English Windows install that's cp1252, not UTF-8. I'm not brave enough
to change the locale at startup because I don't know how that might
affect CJK encoding users (or for that matter, non-Latin derived
alphabet UTF-8 variants).

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/2786.
2023-05-07 19:31:20 +02:00
Dan Albert
4cf7864916 Improve logging in defaultsquadronassigner.py.
https://github.com/dcs-liberation/dcs_liberation/issues/2786
2023-05-07 19:30:44 +02:00
Starfire13
52fb153790 Fixes F-14B squadrons that don't have SEAD as available mission type (#2777)
Fixes the YAMLs for 3 F-14B squadrons that didn't have SEAD as an
available mission type. F-14s use SEAD for TALD deployment. The other
F-14B squadrons and all the F-14A squadrons are fine. I also removed
"Bases:" from one of the squadrons as the other squadron yamls don't
have it and it's unecessary (F-14Bs can already be based at airfields
and carriers.
2023-05-07 19:30:41 +02:00
Raffson
49d965ae7b Revisit/Update custom payloads 2023-05-07 18:51:51 +02:00
Raffson
55b76de3f4 Formatting -_- 2023-05-06 18:13:36 +02:00
Raffson
29f3de3587 Fixing some issues with mod weapons 2023-05-06 17:19:56 +02:00
Raffson
df4a071a0f Adjust Strike formation before and after bombing task 2023-05-06 17:08:48 +02:00
Raffson
cb4cf4c3ac Bumb pydcs 2023-05-06 17:03:12 +02:00
Raffson
cd579528b5 Update changelog.md 2023-05-06 16:58:04 +02:00
Pande4360
b36e7ea750 Support for SW mod v2.55 (#116) 2023-05-04 21:45:26 +02:00
Raffson
2ce63abf04 Use Courier font for kneeboard 2023-05-01 18:15:58 +02:00
Raffson
199467f31b Export txt version of kneeboard pages 2023-05-01 18:12:17 +02:00
Raffson
b4b19d3ad5 Avoid claiming unused aircraft
Probably the final Fix #97
Unused aircraft (assigned upon takeoff) would get claimed but since it's not possible to delete those flights after aborting, these flights wouldn't get released anymore. This should fix that issue, including a migrator change to correct the number of claimed aircraft per squadron.
2023-05-01 18:12:17 +02:00
Raffson
31cfc333c7 Fine-tuning kneeboard
Partial fix #106
2023-05-01 18:12:16 +02:00
Raffson
d18b89484a Update changelog.md 2023-04-23 21:27:21 +02:00
Raffson
6be517ad3e Add A-7E Corsair II mod
Banner, icon, payloads & export by Adecarcer

Create A-7E.lua

Co-Authored-By: Antonio <54774215+Adecarcer@users.noreply.github.com>
2023-04-23 18:50:04 +02:00
Antonio
81e6e682bb New Squadrons for Vietnam Era
Adding several different squadrons (A-4, A-6, F-4B/C, Broncos, etc...) for "Yankee Station" campaign.  All Vietnam era related.
All of them with corresponding liveries that can be easily found on DCS User Files download pages
2023-04-23 18:19:06 +02:00
Raffson
907acefbc8 Update F-4E payloads (#112) 2023-04-23 17:39:57 +02:00
Raffson
449a5bf0fc Update SplashDamage2 script 2023-04-22 17:53:03 +02:00
Raffson
350caefbf8 Tweak F-14A-135-GR & F/A-18C payloads 2023-04-15 20:55:12 +02:00
Raffson
fad48ebaed Improve AI SEAD capabilities 2023-04-15 20:44:57 +02:00
Raffson
bf23ee4e8f Fix migrator issue in _update_packagewaypoints 2023-04-15 20:38:51 +02:00
Raffson
6517dc0de8 Set ECM as first task for SEAD ingress 2023-04-14 22:46:11 +02:00
Raffson
96cb39c334 Pydcs bump
Streamlining parking slot assignments
2023-04-14 22:46:10 +02:00
Raffson
845888bc75 Set ECM option as first task for DEAD-ingress 2023-04-14 22:46:10 +02:00
Raffson
1582b3ee43 Move F-16A_MLU to DEAD capable instead
For some reason an old loadout suggested it could carry HARMs...
2023-04-14 22:46:10 +02:00
Raffson
c0406e7c3c Refactor ingress altitude 2023-04-14 22:46:09 +02:00
Raffson
2746636119 Force usage of Decoys before ARMs 2023-04-11 20:09:58 +02:00
Raffson
c4338c1a6f Update changelog.md 2023-04-11 19:46:18 +02:00
Raffson
e10112be3b Expose Splash Damage's "game_messages" option 2023-04-11 19:46:04 +02:00
Raffson
5b7ff8bdd6 Add fuel quantity selector to EditFlight's payload tab
Resolves #99
2023-04-10 17:11:30 +02:00
Raffson
efd2c40cfc Avoid generating/planning flights without an operational runway 2023-04-10 14:38:06 +02:00
Raffson
7eb652b970 Require all bases to be captured to win/lose 2023-04-10 14:10:46 +02:00
Raffson
8973100f95 Fix bug preventing mission generation 2023-04-10 00:02:14 +02:00
Raffson
13c1f429b1 Update changelog.md
Resolves #110
2023-04-09 22:06:23 +02:00
Raffson
3f509a876e #110 Ability to load/save settings
Also includes support for remaining enum/timedelta settings through the yaml file.
- timedelta's in minutes
- enum's should be written out: enumType.enumValue
2023-04-09 22:03:08 +02:00
Raffson
5916ed43d2 Allow definition of settings in campaign's yaml file
Resolves #92
2023-04-08 19:51:38 +02:00
holyorangejuice
5c4ab2578d WRL Faction & Campaign Update (#108) 2023-04-07 19:11:24 +02:00
Raffson
8692c3970d Rename SA-2 with AAA to Sa-2 with ZSU-23/57 + changelog 2023-04-02 22:52:39 +02:00
Raffson
8a95be7473 Fix mypy complaining about something that was already covered -_- 2023-04-02 22:39:34 +02:00
Raffson
5db53aa0b1 Inject ground units to TgoConfig preset group if fill and has no unit 2023-04-02 22:39:33 +02:00
Raffson
7a0ccd0076 Update JAS-39 2023-04-02 22:39:33 +02:00
Raffson
a04196b5be Update Sufa to v3.6 2023-04-01 23:27:43 +02:00
Raffson
ce7bd9def7 Fix edge-case bug in layout's group size 2023-04-01 17:44:50 +02:00
Raffson
d7fda2d598 Add SA-2 with AAA preset group 2023-04-01 17:44:49 +02:00
Raffson
19c60876d1 Placeholder v1.2.0 and add v's 2023-04-01 17:44:49 +02:00
Raffson
67e609f800 Changelog 1.1.1 2023-04-01 17:44:48 +02:00
Raffson
7c3d2367f2 Fix incorrect plugin (option) values 2023-04-01 17:44:48 +02:00
Raffson
83eea8974b Fix procurement bug when coalition has no squadrons
If a user removes all squadrons manually, the faction may still contain aircraft but no budget for aircraft should be allocated at all. In such a case, all money should go to ground units...
2023-03-25 14:58:36 +01:00
Raffson
a1fc92bd00 Initial changes for v1.2 2023-03-18 23:02:11 +01:00
1083 changed files with 34149 additions and 11449 deletions

8
.coveragerc Normal file
View File

@@ -0,0 +1,8 @@
[report]
exclude_lines =
pragma: no cover
if TYPE_CHECKING:
[run]
branch = True
source = game,pydcs_extensions,qt_ui,resources/tools

View File

@@ -31,7 +31,7 @@ body:
If the bug was found in a test/development build, select "Test build" or "Development build"
and provide a link to the applicable build in the field below.
options:
- 1.0.0
- v1.1
- Test build
- Development build
- type: input

View File

@@ -15,4 +15,24 @@ jobs:
- name: run tests
run: |
./venv/scripts/activate
pytest tests
pytest --cov --cov-report=xml tests
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
ts-tests:
name: Typescript tests
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Set up JS environment
uses: ./.github/actions/setup-liberation-js
- name: run tests
run: |
cd client
npm test -- --coverage
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3

4
.gitignore vendored
View File

@@ -1,11 +1,15 @@
*.pyc
__pycache__
build/**
# Sphinx
docs/_build
resources/payloads/*.lua
venv
.DS_Store
.vscode/settings.json
dist/**
/.coverage
/coverage.xml
# User-specific stuff
.idea/
.env

13
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,13 @@
version: 2
build:
os: ubuntu-22.04
tools:
python: "3.11"
sphinx:
configuration: docs/conf.py
python:
install:
- requirements: docs/requirements.txt

View File

@@ -11,7 +11,7 @@ Note that you may need to remove the filter for open bugs if it's something we'v
## Making content for Liberation
You can create new campaigns : See [campaign creation wiki](https://github.com/dcs-liberation/dcs_liberation/wiki/Custom-Campaigns).
You can create new campaigns : See [campaign creation wiki](https://github.com/dcs-retribution/dcs-retribution/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.

View File

@@ -12,28 +12,29 @@
[![GitHub issues](https://img.shields.io/github/issues/dcs-retribution/dcs-retribution)](https://github.com/dcs-retribution/dcs-retribution/issues)
![GitHub stars](https://img.shields.io/github/stars/dcs-retribution/dcs-retribution?style=social)
## About DCS Retribution
DCS Retribution was forked from [DCS Liberation](https://github.com/dcs-retribution/dcs-retribution),
## About DCS Retribution
(Last update: 2023-05-20)
DCS Retribution was forked from [DCS Liberation](https://github.com/dcs-liberation/dcs_liberation),
which 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.
![Screenshot](https://user-images.githubusercontent.com/315852/120939254-0b4a9f80-c6cc-11eb-82f5-ce3f8d714bfe.png)
DCS Retribution will rely heavily on Liberation in the near future,
and possibly in a more distant future as well since Retribution will attempt
to stay backwards compatible for as long as possible. Therefore,
DCS Retribution is still relying on Liberation for certain updates,
and will possibly stay that way in a more distant future. Therefore,
the patreon link will keep pointing to [Khopa's](https://github.com/Khopa) account until further notice.
However, we are no longer backwards compatible with Liberation, and will no longer attempt to do so.
Currently, Retribution supports the following new features:
* A livery selector in the air wing configuration dialog
* Support for campaigns with quad-point triggers
* Support for the F-15D 'Baz' mod
* Support for the Su-30 mod
* Elementary end-game mechanics
* Variation in flight-size for BAI/ANTISHIP/DEAD/STRIKE main flight (randomize 2/3/4-ships)
* More performance options
* Various bug-fixes (including a fix for the zombie carrier)
* And more... (See [changelog](https://github.com/dcs-retribution/dcs-retribution/blob/main/changelog.md))
Instead, we are focussing on keeping our campaigns forward compatible so that you as a user
can continue on whatever campaign you had going on.
Please keep in mind that once you save a campaign that was started
in a previous (save-compatibility breaking) build, your save file
will have been migrated and thus no longer compatible with the previous build.
Therefore, we recommend backing up your saves and perhaps organizing them by version/build number.
For a more complete overview of our features, check the
[changelog](https://github.com/dcs-retribution/dcs-retribution/blob/main/changelog.md).
## Downloads
@@ -43,11 +44,16 @@ To download preview builds of the next version of DCS Retribution, see https://g
## DCS bugs
These DCS bugs prevent us from improving AI behavior. Please upvote them! (But please
_don't_ spam them with comments):
~~These DCS bugs prevent us from improving AI behavior. Please upvote them! (But please
_don't_ spam them with comments):~~
* [A2A and SEAD escorts don't escort](https://forums.eagle.ru/topic/251798-options-for-alternate-ai-escort-behavior/?tab=comments#comment-4668033)
* [DEAD can't use mixed loadouts effectively](https://forums.eagle.ru/topic/271941-ai-rtbs-after-firing-decoys-despite-full-load-of-bombs/)
* ~~[A2A and SEAD escorts don't escort](https://forums.eagle.ru/topic/251798-options-for-alternate-ai-escort-behavior/?tab=comments#comment-4668033)~~
* ~~[DEAD can't use mixed loadouts effectively](https://forums.eagle.ru/topic/271941-ai-rtbs-after-firing-decoys-despite-full-load-of-bombs/)~~
While DCS bugs are still a thing, we don't quite agree with the ones stated above.
We believe to have addressed these, although some quirks still exist.
For example, SEAD Escorts tend to be a little picky with what they engage,
especially with older SAM systems.
## Bugs and feature requests
@@ -70,7 +76,7 @@ for that release. Items in the To do column are planned to be added to that rele
Tutorials, contributors and developer's guides are available in the project's
[Wiki](https://github.com/dcs-retribution/dcs-retribution/wiki/)
(These need to be written again, meanwhile you can refer to
(Some historical information is also availabe on
[Liberation's Wiki](https://github.com/dcs-liberation/dcs_liberation/wiki/))
## Special Thanks

View File

@@ -1,4 +1,88 @@
# Retribution 1.1.0
# Retribution v1.2.1 (hotfix)
## Fixes
* **[Flight Plans]** SEAD Sweep not being auto-planned
* **[Modding]** Python-4 no longer overwrites AIM-9X
* **[Modding]** Restore original amount of pylons to F-16C when "ejecting" sufa mod
# Retribution v1.2.0
## Features/Improvements
* **[Preset Groups]** Add SA-2 with ZSU-23/57
* **[Campaign Design]** Ability to define almost all possible settings in the campaign's yaml file.
* **[Campaign Design]** Ability to add roadbases and/or ground spawns to campaigns.
* **[Campaign Design]** Ability to define SCENERY REMOVE OBJECTS ZONE triggers with the roadbase objects in campaign miz. This might not work reliably in multiplayer due to DCS issues. FARPs can be used to remove scenery objects in multiplayer.
* **[Options]** Implemented an option in settings to disable the above SCENERY REMOVE OBJECTS ZONE triggers.
* **[Campaign Management]** Improved squadron retreat logic at longer ranges.
* **[Options]** Ability to load & save your settings.
* **[Options]** Added a separate Doctrine page in settings with the following new options:
* Minimum number of aircraft for autoplanner to plan OCA packages against
* Airbase threat range (nmi)
* SEAD Sweep threat buffer distance (nmi)
* SEAD Escort/Sweep threat buffer distance (nmi)
* TARCAP threat buffer distance (nmi)
* AEW&C threat buffer distance (nmi)
* Theater tanker threat buffer distance (nmi)
* **[Options]** Improved the option to configure OPFOR autoplanner aggressiveness. The AI might now take even more risks and plan missions against defended targets.
* Added three new options in Settings:
* Autoplanner plans refueling flights for Strike packages
* Autoplanner plans refueling flights for OCA packages
* Autoplanner plans refueling flights for DEAD packages
* **[UI]** Added fuel selector in flight's edit window.
* **[Plugins]** Expose Splash Damage's "game_messages" option and set its default to false.
* **[Mission Generation]** Improved AI SEAD capabilities, allowing for mixed loadouts using Decoys, ARMs & ASMs.
* **[Modding]** Support for A-7E Corsair II (presumed latest available version)
* **[Squadrons]** Added many new squadron's by Adecarcer
* **[Plugins]** Updated 'expl_table' in Splash Damage script.
* **[Mission Generation]** Also save kneeboards in txt-format, found under "kneeboards" within Retribution's installation folder after pressing take-off.
* **[Modding]** Support for SW mod v2.55
* **[Modding]** Support for Spanish & Australian Naval Assets v3.2.0 by desdemicabina
* **[Modding]** Support for Iron Dome v1.2 by IDF Mods Project
* **[New Game Wizard]** Re-organized generator options & show the regular settings menu instead of the limited "Difficulty & Automation" page.
* **[Campaign Management]** Ability to operate harriers from FOBs/FARPs for <ins>__human pilots only__</ins>. Please note that the autoplanner won't generate flights for harriers at FOBs/FARPs, which means you need to plan your missions manually.
* **[Mission Planning]** Allow NAV/REFUEL/DIVERT waypoints to be deleted without degrading to a custom flight-plan, also warning the user before actually degrading the flight-plan.
* **[Campaign Generation]** Split "full-strength start" from "squadron aircraft limits" option.
* **[Mission Generation]** General improvement w.r.t. DCS tasking, including a check for incompatible tasking.
* **[Mission Generation]** OCA-Runway flights will remain at altitude when using guided bombs.
* **[UX]** Added error message to indicate save-compatibility issues + fix to avoid total crash upon loading of last save.
* **[UI]** Improved parking space information in air wing configuration dialog.
* **[Squadrons]** Warning messages when opening up a squadron through the air wing dialog, indicating squadrons that potentially won't fit w.r.t. parking space.
* **[Squadrons Transfers]** Determine number of available parking slots more accurately w.r.t. squadron transfers, taking aircraft dimensions into account which should prevent forced air-starts.
* **[UX]** Allow usage of CTRL/SHIFT modifiers in ground unit transfer window.
* **[Campaign Design]** Ability to define "spawn-routes" for convoys, allowing them to start from the road without having to edit the mission
* **[Plugins]** Added "DCS Dismount" plugin.
* **[Plugins]** Added "EWR Jammer" plugin (only for humans, may change in the future).
* **[Campaign]** New campaign (Operation Desert Sabre) by Chimiste
* **[Plugins]** Updated CTLD to latest released version
* **[Options]** Renamed Maximum frontline length -> Maximum frontline width.
* **[Squadrons]** Add livery selector in Squadron Dialog, allowing you to change the livery during the campaign.
* **[New Game Wizard]** Automatically invert factions when 'Invert Map' is selected.
* **[Flight Plans]** Added "SEAD Sweep" flight plan, which basically reintroduces the legacy "SEAD Escort" flight plan where the flight will engage whatever it can find without actually escorting the primary flight.
* **[Flight Plans]** Added SEAD capability to F-16A MLU and SEAD Escort & SEAD to F-16A.
* **[Mission Generation]** Spawn unused helicopters or LHA-capable aircraft at helipads at FOBs
* **[Modding]** Support for F-15I Ra'am v1.0 by IDF Mods Project
## Fixes
* **[New Game Wizard]** Settings would not persist when going back to a previous page (obsolete due to overhaul).
* **[Mission Generation]** Unused aircraft are no longer claimed, fixing a bug where these aircraft would no longer be available after aborting the mission.
* **[Mission Generation]** Fixed (potential) bug in helipad assignments at FOBs/FARPs.
* **[Mission Generation]** Fix AI immediately returning to base when forced to air-start due to insufficient parking space.
* **[Modding]** Fixed a bug where F-16Ds were not correctly removed from the faction when the F-16I/F-16D mod was not selected
* **[UI]** Fixed F-16A MLU icon and banner.
# Retribution v1.1.1 (hotfix)
## Features/Improvements
* **[Modding]** Support for IDF Mod Project F-16I Sufa & F-16D v3.6 mod
* **[Modding]** Support for JAS-39 Gripen v1.8.5-beta mod
## Fixes
* **[Plugins]** Fix bug where changes to plugin options doesn't do anything.
* **[Campaign Management]** Fix bug in procurement when no squadrons are present.
* **[Layouts]** Fix edge-case bug layout's group size.
* **[Campaign Design]** Preset groups assigned to specific TGOs not working as intended.
# Retribution v1.1.0
## Features/Improvements
* **[Mission Generation]** Given a CAS flight was planned, delay ground force attack until first CAS flight is on station
@@ -32,6 +116,7 @@
* **[Campaign Management]** New options to allow more control of randomized flight sizes (applicable for BARCAP/CAS/OCA/ANTI-SHIP).
* **[Plugins]** Updated Splash Damage script to v2.0 by RotorOps.
* **[Mission Generation]** Improvements to DEAD & STRIKE flights, allowing AI to handle a larger variety of weapons.
* **[Campaign]** New campaign (1968 Yankee Station) by Adecarcer
## Fixes
* **[UI]** Removed deprecated options
@@ -44,12 +129,11 @@
* **[Modding]** Fixed conflicts caused by HDS units
* **[UX]** Gracefully handle corrupted preferences file.
* **[Mission Generation]** Aircraft not using decoys during SEAD.
* **[Campaign]** New campaign (1968 Yankee Station) by Adecarcer
# Retribution 1.0.1 (hotfix)
# Retribution v1.0.1 (hotfix)
* **[Mission Generation]** Fix serialization issue when STRIKE flight has no escorts
# Retribution 1.0.0
# Retribution v1.0.0
## Features/Improvements
* **[Engine]** Support for DCS v2.8.1.34437.
@@ -88,7 +172,49 @@ BAI/ANTISHIP/DEAD/STRIKE/BARCAP/CAS/OCA/AIR-ASSAULT (main) missions
* **[Mission Generation]** Kneeboard STRIKE coordinates would sometimes get clipped when not fitting.
* **[UI]** Fix exception when trying to add a waypoints to a flightplan.
# Liberation:
## Features/Improvements
* **[Engine]** Support for DCS 2.8.6.41363, including F-15E support.
## Fixes
# 8.0.0
Saves from 7.x are not compatible with 8.0.
## Features/Improvements
* **[Engine]** Support for DCS 2.8.6.41066, including the new Sinai map.
* **[UI]** Limited size of overfull airbase display and added scrollbar.
* **[UI]** Waypoint altitudes can be edited in Waypoints tab of Edit Flight window.
* **[UI]** Moved air wing and transfer menus to the toolbar to improve UI fit on low resolution displays.
* **[UI]** Added basic game over dialog.
## Fixes
* **[Campaign]** Fix bug introduced in 7.0 where map strike target deaths are no longer tracked.
* **[Mission Generation]** Fix crash during mission generation caused by out of date DCS data for the Gazelle.
* **[Mission Generation]** Fix crash during mission generation when DCS beacon data is inconsistent.
# 7.1.0
Saves from 7.0.0 are compatible with 7.1.0
## Features/Improvements
* **[Factions]** Replaced Patriot STRs "EWRs" with AN/FPS-117 for blue factions 1980 or newer.
* **[Mission Generation]** Added option to prevent scud and V2 sites from firing at the start of the mission.
* **[Mission Planning]** Per-flight TOT offsets can now be set in the flight details UI. This allows individual flights to be scheduled ahead of or behind the rest of the package.
* **[UI]** Waypoint altitudes can be edited in Waypoints tab of Edit Flight window.
* **[UI]** Parking capacity of each squadron's base is now shown during air wing configuration to avoid overcrowding bases when beginning the game with full squadrons.
## Fixes
* **[Mission Planning]** BAI is once again plannable against missile sites and coastal defense batteries.
* **[UI]** Fixed formatting of departure time in flight details dialog.
# 7.0.0
Saves from 6.x are not compatible with 7.0.
@@ -97,15 +223,47 @@ Saves from 6.x are not compatible with 7.0.
* **[Engine]** Support for DCS 2.8.3.37556.
* **[Engine]** Saved games are now a zip file of save assets for easier bug reporting. The new extension is .liberation.zip. Drag and drop that file into bug reports.
* **[Campaign]** Added options to limit squadron sizes and to begin all squadrons at maximum strength. Maximum squadron size is defined during air wing configuration with default values provided by the campaign.
* **[Campaign]** Added handling for more DCS death events. This probably does not catch any deaths that weren't previously tracked, but it should record them sooner, which will improve results for game crashes or other early exits.
* **[Campaign AI]** The campaign AI now prefers fulfilling missions with squadrons which have a matching primary task. Previously distance from target held a stronger influence than task preference. Primary tasks for squadrons are set by campaign designers but are user-configurable.
* **[Flight Planning]** Package TOT and composition can be modified after advancing time in Liberation.
* **[Mission Generation]** Units on the front line are now hidden on MFDs.
* **[Mission Generation]** Preset radio channels will now be configured for both A-10C modules.
* **[Mission Generation]** Both A-10C modules now use separate radios for inter- and intra-flight comms (similar to other modern aircraft).
* **[Mission Generation]** The A-10C II now uses separate radios for inter- and intra-flight comms (similar to other modern aircraft).
* **[Mission Generation]** Wind speeds no longer follow a uniform distribution. Median wind speeds are now much lower and the standard deviation has been reduced considerably at altitude but increased somewhat at MSL.
* **[Mission Generation]** Improved task generation for SEAD flights carrying TALDs.
* **[Mission Generation]** Added task timeout for SEAD flights with TALDs to prevent AI from overflying the target.
* **[Modding]** Updated Community A-4E-C mod version support to 2.1.0 release.
* **[Modding]** Add support for VSN F-4B and F-4C mod.
* **[Modding]** Aircraft task capabilities and preferred aircraft for each task are now moddable in the aircraft unit yaml files. Each aircraft has a weight per task. Higher weights are given higher preference.
* **[Modding]** The `mission_types` field in squadron files has been removed. Squadron task capability is now determined by airframe, and the auto-assignable list has always been overridden by the campaign settings.
* **[Modding]** Wind speed generation inputs are now moddable. See https://dcs-liberation.rtfd.io/en/latest/modding/weather.html.
* **[New Game Wizard]** Choices for some options will be remembered for the next new game. Not all settings will be preserved, as many are campaign dependent.
* **[New Game Wizard]** Lua plugins can now be set while creating a new game.
* **[New Game Wizard]** Squadrons can be directly replaced with a preset during air wing configuration rather than needing to remove and create a new squadron.
* **[New Game Wizard]** Squadron liveries can now be selected during air wing configuration.
* **[Squadrons]** Squadron-specific mission capability lists no longer restrict players from assigning missions outside the squadron's preferences.
* **[New Game Wizard]** Squadrons can be directly replaced with a preset during air wing configuration rather than needing to remove and create a new squadron.
* **[UI]** The orientation of objects like SAMs, EWRs, garrisons, and ships can now be manually adjusted.
## Fixes
* **[Campaign]** Fixed a longstanding bug where oversized airlifts could corrupt a save with empty convoys.
* **[Campaign]** Aircraft with built-in TGPs but without an external pod will no longer degrade automatic loadouts to iron bombs.
* **[Engine]** Fixed crash in startup caused by a corrupted Liberation preferences file.
* **[Flight Planning]** AEW&C missions are now plannable over FOBs and LHAs.
* **[Flight Planning]** BAI is no longer plannable against buildings.
* **[Modding]** Fixed an issue where Falklands campaigns created or edited with new versions of DCS could not be loaded.
* **[Modding]** Fixed decoding of campaign yaml files to use UTF-8 rather than the system locale's default. It's now possible to use "Bf 109 K-4 Kurfürst" as a preferred aircraft type.
* **[Mission Generation]** Planes will no longer spawn in helipads that are not also designated for fixed wing parking.
* **[Mission Generation]** Potentially an issue where ground war planning game state could become corrupted, preventing mission generation.
* **[Mission Generation]** Refueling tasks will now only be created for flights that have a tanker in their package.
* **[Mission Generation]** Fixed missing Tanker task on recovery tanker missions.
* **[UI]** Fixed error when resetting air wing configuration during game setup.
* **[UI]** Fixed flight plan recreation when changing mission type with "Recreate as" flight options.
* **[UI]** Fixed failure to launch UI when Liberation persistent preferences file was corrupt.
# 6.1.1
## Fixes

365
client/package-lock.json generated
View File

@@ -41,7 +41,7 @@
"electron": "^21.1.0",
"electron-is-dev": "^2.0.0",
"generate-license-file": "^2.0.0",
"got": "^11.8.5",
"identity-obj-proxy": "^3.0.0",
"license-checker": "^25.0.1",
"nth-check": "^2.0.1",
"react-scripts": "5.0.1",
@@ -4570,18 +4570,6 @@
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz",
"integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g=="
},
"node_modules/@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@sinonjs/commons": {
"version": "1.8.6",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
@@ -4833,18 +4821,6 @@
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
"dev": true,
"dependencies": {
"defer-to-connect": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@terraformer/arcgis": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@terraformer/arcgis/-/arcgis-2.1.2.tgz",
@@ -5090,18 +5066,6 @@
"@types/node": "*"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
"dev": true,
"dependencies": {
"@types/http-cache-semantics": "*",
"@types/keyv": "^3.1.4",
"@types/node": "*",
"@types/responselike": "^1.0.0"
}
},
"node_modules/@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
@@ -5200,12 +5164,6 @@
"integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==",
"dev": true
},
"node_modules/@types/http-cache-semantics": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
"dev": true
},
"node_modules/@types/http-proxy": {
"version": "1.17.9",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz",
@@ -5286,15 +5244,6 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"node_modules/@types/keyv": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/leaflet": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.0.tgz",
@@ -5397,15 +5346,6 @@
"@types/node": "*"
}
},
"node_modules/@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
@@ -7079,33 +7019,6 @@
"node": ">= 0.8"
}
},
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
"dev": true,
"engines": {
"node": ">=10.6.0"
}
},
"node_modules/cacheable-request": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
"integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
"dev": true,
"dependencies": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -8198,33 +8111,6 @@
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
"dev": true
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dev": true,
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/decompress-response/node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
@@ -8302,15 +8188,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -10753,31 +10630,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/got": {
"version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
"dev": true,
"dependencies": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
},
"engines": {
"node": ">=10.19.0"
},
"funding": {
"url": "https://github.com/sindresorhus/got?sponsor=1"
}
},
"node_modules/graceful-fs": {
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
@@ -11165,19 +11017,6 @@
"integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==",
"dev": true
},
"node_modules/http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"dev": true,
"dependencies": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
},
"engines": {
"node": ">=10.19.0"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -14003,12 +13842,6 @@
"node": ">=4"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -14088,15 +13921,6 @@
"integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==",
"peer": true
},
"node_modules/keyv": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
"integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
"dev": true,
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -15421,15 +15245,6 @@
"os-tmpdir": "^1.0.0"
}
},
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -17990,12 +17805,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"dev": true
},
"node_modules/resolve-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
@@ -18095,18 +17904,6 @@
"node": ">=10"
}
},
"node_modules/responselike": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
"dev": true,
"dependencies": {
"lowercase-keys": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -24614,12 +24411,6 @@
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.21.tgz",
"integrity": "sha512-gFukHN4t8K4+wVC+ECqeqwzBDeFeTzBXroBTqE6vcWrQGbEUpHO7LYdG0f4xnvYq4VOEwITSlHlp0JBAIFMS/g=="
},
"@sindresorhus/is": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
"dev": true
},
"@sinonjs/commons": {
"version": "1.8.6",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz",
@@ -24773,15 +24564,6 @@
"loader-utils": "^2.0.0"
}
},
"@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
"integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
"dev": true,
"requires": {
"defer-to-connect": "^2.0.0"
}
},
"@terraformer/arcgis": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@terraformer/arcgis/-/arcgis-2.1.2.tgz",
@@ -24989,18 +24771,6 @@
"@types/node": "*"
}
},
"@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
"integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
"dev": true,
"requires": {
"@types/http-cache-semantics": "*",
"@types/keyv": "^3.1.4",
"@types/node": "*",
"@types/responselike": "^1.0.0"
}
},
"@types/connect": {
"version": "3.4.35",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz",
@@ -25099,12 +24869,6 @@
"integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==",
"dev": true
},
"@types/http-cache-semantics": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
"integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==",
"dev": true
},
"@types/http-proxy": {
"version": "1.17.9",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz",
@@ -25178,15 +24942,6 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"@types/keyv": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
"integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/leaflet": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.0.tgz",
@@ -25289,15 +25044,6 @@
"@types/node": "*"
}
},
"@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
@@ -26591,27 +26337,6 @@
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"dev": true
},
"cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
"dev": true
},
"cacheable-request": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz",
"integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==",
"dev": true,
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^6.0.1",
"responselike": "^2.0.0"
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@@ -27414,23 +27139,6 @@
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
"dev": true
},
"decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dev": true,
"requires": {
"mimic-response": "^3.1.0"
},
"dependencies": {
"mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"dev": true
}
}
},
"dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
@@ -27496,12 +27204,6 @@
"clone": "^1.0.2"
}
},
"defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
"integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
"dev": true
},
"define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
@@ -29358,25 +29060,6 @@
"get-intrinsic": "^1.1.3"
}
},
"got": {
"version": "11.8.6",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
"integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
"dev": true,
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
}
},
"graceful-fs": {
"version": "4.2.10",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
@@ -29677,16 +29360,6 @@
"integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==",
"dev": true
},
"http2-wrapper": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
"integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
"dev": true,
"requires": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
}
},
"https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
@@ -31899,12 +31572,6 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true
},
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -31972,15 +31639,6 @@
"integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==",
"peer": true
},
"keyv": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.2.tgz",
"integrity": "sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==",
"dev": true,
"requires": {
"json-buffer": "3.0.1"
}
},
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -33009,12 +32667,6 @@
"os-tmpdir": "^1.0.0"
}
},
"p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
"integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
"dev": true
},
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -34750,12 +34402,6 @@
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"resolve-alpn": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
"integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
"dev": true
},
"resolve-cwd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
@@ -34823,15 +34469,6 @@
"integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==",
"dev": true
},
"responselike": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
"integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
"dev": true,
"requires": {
"lowercase-keys": "^2.0.0"
}
},
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",

View File

@@ -69,11 +69,19 @@
"electron": "^21.1.0",
"electron-is-dev": "^2.0.0",
"generate-license-file": "^2.0.0",
"got": "^11.8.5",
"identity-obj-proxy": "^3.0.0",
"license-checker": "^25.0.1",
"nth-check": "^2.0.1",
"react-scripts": "5.0.1",
"ts-node": "^10.9.1",
"wait-on": "^6.0.1"
},
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!(@?react-leaflet|axios)/)"
],
"moduleNameMapper": {
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "identity-obj-proxy"
}
}
}

View File

@@ -1,11 +1,11 @@
import App from "./App";
import { store } from "./app/store";
import { setupStore } from "./app/store";
import { render } from "@testing-library/react";
import { Provider } from "react-redux";
test("app renders", () => {
render(
<Provider store={store}>
<Provider store={setupStore()}>
<App />
</Provider>
);

View File

@@ -3,36 +3,48 @@ import combatReducer from "../api/combatSlice";
import controlPointsReducer from "../api/controlPointsSlice";
import flightsReducer from "../api/flightsSlice";
import frontLinesReducer from "../api/frontLinesSlice";
import iadsNetworkReducer from "../api/iadsNetworkSlice";
import mapReducer from "../api/mapSlice";
import navMeshReducer from "../api/navMeshSlice";
import supplyRoutesReducer from "../api/supplyRoutesSlice";
import tgosReducer from "../api/tgosSlice";
import iadsNetworkReducer from "../api/iadsNetworkSlice";
import threatZonesReducer from "../api/threatZonesSlice";
import unculledZonesReducer from "../api/unculledZonesSlice";
import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
import unculledZonesReducer from "../api/unculledZonesSlice";
import {
Action,
PreloadedState,
ThunkAction,
combineReducers,
configureStore,
} from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {
combat: combatReducer,
controlPoints: controlPointsReducer,
flights: flightsReducer,
frontLines: frontLinesReducer,
map: mapReducer,
navmeshes: navMeshReducer,
supplyRoutes: supplyRoutesReducer,
iadsNetwork: iadsNetworkReducer,
tgos: tgosReducer,
threatZones: threatZonesReducer,
[baseApi.reducerPath]: baseApi.reducer,
unculledZones: unculledZonesReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(baseApi.middleware),
const rootReducer = combineReducers({
combat: combatReducer,
controlPoints: controlPointsReducer,
flights: flightsReducer,
frontLines: frontLinesReducer,
map: mapReducer,
navmeshes: navMeshReducer,
supplyRoutes: supplyRoutesReducer,
iadsNetwork: iadsNetworkReducer,
tgos: tgosReducer,
threatZones: threatZonesReducer,
[baseApi.reducerPath]: baseApi.reducer,
unculledZones: unculledZonesReducer,
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export function setupStore(preloadedState?: PreloadedState<RootState>) {
return configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(baseApi.middleware),
preloadedState: preloadedState,
});
}
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = AppStore["dispatch"];
export type RootState = ReturnType<typeof rootReducer>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,

View File

@@ -0,0 +1,53 @@
import Aircraft from "./Aircraft";
import { render } from "@testing-library/react";
import { Icon } from "leaflet";
const mockMarker = jest.fn();
jest.mock("react-leaflet", () => ({
Marker: (props: any) => {
mockMarker(props);
},
}));
test("grounded aircraft do not render", async () => {
const { container } = render(
<Aircraft
flight={{
id: "",
blue: true,
position: undefined,
sidc: "",
waypoints: [],
}}
/>
);
expect(container).toBeEmptyDOMElement();
});
test("in-flight aircraft render", async () => {
render(
<Aircraft
flight={{
id: "",
blue: true,
position: {
lat: 10,
lng: 20,
},
sidc: "foobar",
waypoints: [],
}}
/>
);
expect(mockMarker).toHaveBeenCalledWith(
expect.objectContaining({
position: {
lat: 10,
lng: 20,
},
icon: expect.any(Icon),
})
);
});

View File

@@ -0,0 +1,53 @@
import { renderWithProviders } from "../../testutils";
import AircraftLayer from "./AircraftLayer";
import { PropsWithChildren } from "react";
const mockLayerGroup = jest.fn();
const mockMarker = jest.fn();
jest.mock("react-leaflet", () => ({
LayerGroup: (props: PropsWithChildren<any>) => {
mockLayerGroup(props);
return <>{props.children}</>;
},
Marker: (props: any) => {
mockMarker(props);
},
}));
test("layer is empty by default", async () => {
renderWithProviders(<AircraftLayer />);
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
expect(mockMarker).not.toHaveBeenCalled();
});
test("layer has aircraft if non-empty", async () => {
renderWithProviders(<AircraftLayer />, {
preloadedState: {
flights: {
flights: {
foo: {
id: "foo",
blue: true,
sidc: "",
position: {
lat: 0,
lng: 0,
},
},
bar: {
id: "bar",
blue: false,
sidc: "",
position: {
lat: 0,
lng: 0,
},
},
},
selected: null,
},
},
});
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
expect(mockMarker).toHaveBeenCalledTimes(2);
});

View File

@@ -0,0 +1,146 @@
import { renderWithProviders } from "../../testutils";
import AirDefenseRangeLayer, { colorFor } from "./AirDefenseRangeLayer";
import { PropsWithChildren } from "react";
const mockLayerGroup = jest.fn();
const mockCircle = jest.fn();
jest.mock("react-leaflet", () => ({
LayerGroup: (props: PropsWithChildren<any>) => {
mockLayerGroup(props);
return <>{props.children}</>;
},
Circle: (props: any) => {
mockCircle(props);
},
}));
describe("colorFor", () => {
it("has a unique color for each configuration", () => {
const params = [
[false, false],
[false, true],
[true, false],
[true, true],
];
var colors = new Set<string>();
for (const [blue, detection] of params) {
colors.add(colorFor(blue, detection));
}
expect(colors.size).toEqual(4);
});
});
describe("AirDefenseRangeLayer", () => {
it("draws nothing when there are no TGOs", () => {
renderWithProviders(<AirDefenseRangeLayer blue={true} />);
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
expect(mockCircle).not.toHaveBeenCalled();
});
it("does not draw wrong range types", () => {
renderWithProviders(<AirDefenseRangeLayer blue={true} />, {
preloadedState: {
tgos: {
tgos: {
foo: {
id: "foo",
name: "Foo",
control_point_name: "Bar",
category: "AA",
blue: false,
position: {
lat: 0,
lng: 0,
},
units: [],
threat_ranges: [],
detection_ranges: [20],
dead: false,
sidc: "",
},
},
},
},
});
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
expect(mockCircle).not.toHaveBeenCalled();
});
it("draws threat ranges", () => {
renderWithProviders(<AirDefenseRangeLayer blue={true} />, {
preloadedState: {
tgos: {
tgos: {
foo: {
id: "foo",
name: "Foo",
control_point_name: "Bar",
category: "AA",
blue: true,
position: {
lat: 10,
lng: 20,
},
units: [],
threat_ranges: [10],
detection_ranges: [20],
dead: false,
sidc: "",
},
},
},
},
});
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
expect(mockCircle).toHaveBeenCalledWith(
expect.objectContaining({
center: {
lat: 10,
lng: 20,
},
radius: 10,
color: colorFor(true, false),
interactive: false,
})
);
});
it("draws detection ranges", () => {
renderWithProviders(<AirDefenseRangeLayer blue={true} detection />, {
preloadedState: {
tgos: {
tgos: {
foo: {
id: "foo",
name: "Foo",
control_point_name: "Bar",
category: "AA",
blue: true,
position: {
lat: 10,
lng: 20,
},
units: [],
threat_ranges: [10],
detection_ranges: [20],
dead: false,
sidc: "",
},
},
},
},
});
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
expect(mockCircle).toHaveBeenCalledWith(
expect.objectContaining({
center: {
lat: 10,
lng: 20,
},
radius: 20,
color: colorFor(true, true),
interactive: false,
})
);
});
});

View File

@@ -9,7 +9,7 @@ interface TgoRangeCirclesProps {
detection?: boolean;
}
function colorFor(blue: boolean, detection: boolean) {
export function colorFor(blue: boolean, detection: boolean) {
if (blue) {
return detection ? "#bb89ff" : "#0084ff";
}

View File

@@ -0,0 +1,132 @@
import { renderWithProviders } from "../../testutils";
import Combat from "./Combat";
import { LatLng } from "leaflet";
const mockPolyline = jest.fn();
const mockPolygon = jest.fn();
jest.mock("react-leaflet", () => ({
Polyline: (props: any) => {
mockPolyline(props);
},
Polygon: (props: any) => {
mockPolygon(props);
},
}));
describe("Combat", () => {
describe("footprint", () => {
it("is not interactive", () => {
renderWithProviders(
<Combat
combat={{
id: "foo",
flight_position: null,
target_positions: null,
footprint: [[new LatLng(0, 0), new LatLng(0, 1), new LatLng(1, 0)]],
}}
/>
);
expect(mockPolygon).toBeCalledWith(
expect.objectContaining({ interactive: false })
);
});
// Fails because we don't handle multi-poly combat footprints correctly.
it.skip("renders single polygons", () => {
const boundary = [new LatLng(0, 0), new LatLng(0, 1), new LatLng(1, 0)];
renderWithProviders(
<Combat
combat={{
id: "foo",
flight_position: null,
target_positions: null,
footprint: [boundary],
}}
/>
);
expect(mockPolygon).toBeCalledWith(
expect.objectContaining({ positions: boundary })
);
});
// Fails because we don't handle multi-poly combat footprints correctly.
it.skip("renders multiple polygons", () => {
const boundary = [new LatLng(0, 0), new LatLng(0, 1), new LatLng(1, 0)];
renderWithProviders(
<Combat
combat={{
id: "foo",
flight_position: null,
target_positions: null,
footprint: [boundary, boundary],
}}
/>
);
expect(mockPolygon).toBeCalledTimes(2);
});
});
describe("lines", () => {
it("is not interactive", () => {
renderWithProviders(
<Combat
combat={{
id: "foo",
flight_position: new LatLng(0, 0),
target_positions: [new LatLng(1, 0)],
footprint: null,
}}
/>
);
expect(mockPolyline).toBeCalledWith(
expect.objectContaining({ interactive: false })
);
});
it("renders single line", () => {
renderWithProviders(
<Combat
combat={{
id: "foo",
flight_position: new LatLng(0, 0),
target_positions: [new LatLng(0, 1)],
footprint: null,
}}
/>
);
expect(mockPolyline).toBeCalledWith(
expect.objectContaining({
positions: [new LatLng(0, 0), new LatLng(0, 1)],
})
);
});
it("renders multiple lines", () => {
renderWithProviders(
<Combat
combat={{
id: "foo",
flight_position: new LatLng(0, 0),
target_positions: [new LatLng(0, 1), new LatLng(1, 0)],
footprint: null,
}}
/>
);
expect(mockPolyline).toBeCalledTimes(2);
});
});
it("renders nothing if no footprint or targets", () => {
const { container } = renderWithProviders(
<Combat
combat={{
id: "foo",
flight_position: new LatLng(0, 0),
target_positions: null,
footprint: null,
}}
/>
);
expect(container).toBeEmptyDOMElement();
});
});

View File

@@ -0,0 +1,48 @@
import { renderWithProviders } from "../../testutils";
import CombatLayer from "./CombatLayer";
import { LatLng } from "leaflet";
import { PropsWithChildren } from "react";
const mockPolyline = jest.fn();
const mockLayerGroup = jest.fn();
jest.mock("react-leaflet", () => ({
LayerGroup: (props: PropsWithChildren<any>) => {
mockLayerGroup(props);
return <>{props.children}</>;
},
Polyline: (props: any) => {
mockPolyline(props);
},
}));
describe("CombatLayer", () => {
it("renders each combat", () => {
renderWithProviders(<CombatLayer />, {
preloadedState: {
combat: {
combat: {
foo: {
id: "foo",
flight_position: new LatLng(0, 0),
target_positions: [new LatLng(0, 1)],
footprint: null,
},
bar: {
id: "foo",
flight_position: new LatLng(0, 0),
target_positions: [new LatLng(0, 1)],
footprint: null,
},
},
},
},
});
expect(mockPolyline).toBeCalledTimes(2);
});
it("renders LayerGroup but no contents if no combat", () => {
renderWithProviders(<CombatLayer />);
expect(mockLayerGroup).toBeCalledTimes(1);
expect(mockPolyline).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,52 @@
import { renderWithProviders } from "../../testutils";
import ControlPointsLayer from "./ControlPointsLayer";
import { LatLng } from "leaflet";
import { PropsWithChildren } from "react";
const mockMarker = jest.fn();
const mockLayerGroup = jest.fn();
jest.mock("react-leaflet", () => ({
LayerGroup: (props: PropsWithChildren<any>) => {
mockLayerGroup(props);
return <>{props.children}</>;
},
Marker: (props: any) => {
mockMarker(props);
},
}));
describe("ControlPointsLayer", () => {
it("renders each control point", () => {
renderWithProviders(<ControlPointsLayer />, {
preloadedState: {
controlPoints: {
controlPoints: {
foo: {
id: "foo",
name: "Foo",
blue: true,
position: new LatLng(0, 0),
mobile: false,
sidc: "",
},
bar: {
id: "bar",
name: "Bar",
blue: false,
position: new LatLng(1, 0),
mobile: false,
sidc: "",
},
},
},
},
});
expect(mockMarker).toBeCalledTimes(2);
});
it("renders LayerGroup but no contents if no combat", () => {
renderWithProviders(<ControlPointsLayer />);
expect(mockLayerGroup).toBeCalledTimes(1);
expect(mockMarker).not.toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,78 @@
import { renderWithProviders } from "../../testutils";
import CullingExclusionZones from "./CullingExclusionZones";
import { PropsWithChildren } from "react";
const mockCircle = jest.fn();
const mockLayerGroup = jest.fn();
const mockLayerControlOverlay = jest.fn();
jest.mock("react-leaflet", () => ({
LayerGroup: (props: PropsWithChildren<any>) => {
mockLayerGroup(props);
return <>{props.children}</>;
},
LayersControl: {
Overlay: (props: PropsWithChildren<any>) => {
mockLayerControlOverlay(props);
return <>{props.children}</>;
},
},
Circle: (props: any) => {
mockCircle(props);
},
}));
describe("CullingExclusionZones", () => {
it("is empty there are no exclusion zones", () => {
renderWithProviders(<CullingExclusionZones />);
expect(mockCircle).not.toHaveBeenCalled();
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
expect(mockLayerControlOverlay).toHaveBeenCalledTimes(1);
});
describe("zone circles", () => {
it("are drawn in the correct locations", () => {
renderWithProviders(<CullingExclusionZones />, {
preloadedState: {
unculledZones: {
zones: [
{
position: {
lat: 0,
lng: 0,
},
radius: 10,
},
{
position: {
lat: 1,
lng: 1,
},
radius: 2,
},
],
},
},
});
expect(mockCircle).toHaveBeenCalledTimes(2);
expect(mockCircle).toHaveBeenCalledWith(
expect.objectContaining({
center: {
lat: 0,
lng: 0,
},
radius: 10,
})
);
expect(mockCircle).toHaveBeenCalledWith(
expect.objectContaining({
center: {
lat: 1,
lng: 1,
},
radius: 2,
})
);
});
it("are not interactive", () => {});
});
});

View File

@@ -30,18 +30,10 @@ const CullingExclusionCircles = (props: CullingExclusionCirclesProps) => {
export default function CullingExclusionZones() {
const data = useAppSelector(selectUnculledZones).zones;
var cez = <></>;
if (!data) {
console.log("Empty response when loading culling exclusion zones");
} else {
cez = (
<CullingExclusionCircles zones={data}></CullingExclusionCircles>
);
}
return (
<LayersControl.Overlay name="Culling exclusion zones">
{cez}
<CullingExclusionCircles zones={data}></CullingExclusionCircles>
</LayersControl.Overlay>
);
}

View File

@@ -0,0 +1,405 @@
import { renderWithProviders } from "../../testutils";
import FlightPlansLayer from "./FlightPlansLayer";
import { PropsWithChildren } from "react";
const mockPolyline = jest.fn();
const mockLayerGroup = jest.fn();
jest.mock("react-leaflet", () => ({
LayerGroup: (props: PropsWithChildren<any>) => {
mockLayerGroup(props);
return <>{props.children}</>;
},
Polyline: (props: any) => {
mockPolyline(props);
},
}));
// The waypoints in test data below should all use `should_make: false`. Markers
// need useMap() to check the zoom level to decide if they should be drawn or
// not, and we don't have good options here for mocking that behavior.
describe("FlightPlansLayer", () => {
describe("unselected flights", () => {
it("are drawn", () => {
renderWithProviders(<FlightPlansLayer blue={true} />, {
preloadedState: {
flights: {
flights: {
foo: {
id: "foo",
blue: true,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
bar: {
id: "bar",
blue: true,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
},
selected: null,
},
},
});
expect(mockPolyline).toHaveBeenCalledTimes(2);
expect(mockLayerGroup).toBeCalledTimes(1);
});
it("are not drawn if wrong coalition", () => {
renderWithProviders(<FlightPlansLayer blue={true} />, {
preloadedState: {
flights: {
flights: {
foo: {
id: "foo",
blue: true,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
bar: {
id: "bar",
blue: false,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
},
selected: null,
},
},
});
expect(mockPolyline).toHaveBeenCalledTimes(1);
expect(mockLayerGroup).toBeCalledTimes(1);
});
it("are not drawn when only selected flights are to be drawn", () => {
renderWithProviders(<FlightPlansLayer blue={true} selectedOnly />, {
preloadedState: {
flights: {
flights: {
foo: {
id: "foo",
blue: true,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
},
selected: null,
},
},
});
expect(mockPolyline).not.toHaveBeenCalled();
expect(mockLayerGroup).toBeCalledTimes(1);
});
});
describe("selected flights", () => {
it("are drawn", () => {
renderWithProviders(<FlightPlansLayer blue={true} />, {
preloadedState: {
flights: {
flights: {
foo: {
id: "foo",
blue: true,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
bar: {
id: "bar",
blue: true,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
},
selected: "foo",
},
},
});
expect(mockPolyline).toHaveBeenCalledTimes(2);
expect(mockLayerGroup).toBeCalledTimes(1);
});
it("are not drawn twice", () => {
renderWithProviders(<FlightPlansLayer blue={true} />, {
preloadedState: {
flights: {
flights: {
foo: {
id: "foo",
blue: true,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
},
selected: "foo",
},
},
});
expect(mockPolyline).toHaveBeenCalledTimes(1);
expect(mockLayerGroup).toBeCalledTimes(1);
});
it("are not drawn if red", () => {
renderWithProviders(<FlightPlansLayer blue={false} selectedOnly />, {
preloadedState: {
flights: {
flights: {
foo: {
id: "foo",
blue: false,
sidc: "",
waypoints: [
{
name: "",
position: {
lat: 0,
lng: 0,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
{
name: "",
position: {
lat: 1,
lng: 1,
},
altitude_ft: 0,
altitude_reference: "MSL",
is_movable: true,
should_mark: false,
include_in_path: true,
timing: "",
},
],
},
},
selected: "foo",
},
},
});
expect(mockPolyline).not.toHaveBeenCalled();
expect(mockLayerGroup).toBeCalledTimes(1);
});
});
it("are not drawn if there are no flights", () => {
renderWithProviders(<FlightPlansLayer blue={true} />);
expect(mockPolyline).not.toHaveBeenCalled();
expect(mockLayerGroup).toBeCalledTimes(1);
});
});

View File

@@ -0,0 +1,32 @@
import { renderWithProviders } from "../../testutils";
import FrontLine from "./FrontLine";
import { PolylineProps } from "react-leaflet";
const mockPolyline = jest.fn();
jest.mock("react-leaflet", () => ({
Polyline: (props: PolylineProps) => {
mockPolyline(props);
},
}));
describe("FrontLine", () => {
it("is drawn in the correct location", () => {
const extents = [
{ lat: 0, lng: 0 },
{ lat: 1, lng: 0 },
];
renderWithProviders(
<FrontLine
front={{
id: "",
extents: extents,
}}
/>
);
expect(mockPolyline).toHaveBeenCalledWith(
expect.objectContaining({
positions: extents,
})
);
});
});

View File

@@ -0,0 +1,56 @@
import { renderWithProviders } from "../../testutils";
import FrontLinesLayer from "./FrontLinesLayer";
import { PropsWithChildren } from "react";
const mockPolyline = jest.fn();
const mockLayerGroup = jest.fn();
jest.mock("react-leaflet", () => ({
LayerGroup: (props: PropsWithChildren<any>) => {
mockLayerGroup(props);
return <>{props.children}</>;
},
Polyline: (props: any) => {
mockPolyline(props);
},
}));
// The waypoints in test data below should all use `should_make: false`. Markers
// need useMap() to check the zoom level to decide if they should be drawn or
// not, and we don't have good options here for mocking that behavior.
describe("FrontLinesLayer", () => {
it("draws nothing when there are no front lines", () => {
renderWithProviders(<FrontLinesLayer />);
expect(mockPolyline).not.toHaveBeenCalled();
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
});
it("draws front lines", () => {
const extents = [
{ lat: 0, lng: 0 },
{ lat: 1, lng: 1 },
];
renderWithProviders(<FrontLinesLayer />, {
preloadedState: {
frontLines: {
fronts: {
foo: {
id: "foo",
extents: extents,
},
bar: {
id: "bar",
extents: extents,
},
},
},
},
});
expect(mockPolyline).toHaveBeenCalledTimes(2);
expect(mockPolyline).toHaveBeenCalledWith(
expect.objectContaining({
positions: extents,
})
);
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
});
});

View File

@@ -3,7 +3,7 @@ import { useAppSelector } from "../../app/hooks";
import FrontLine from "../frontline";
import { LayerGroup } from "react-leaflet";
export default function SupplyRoutesLayer() {
export default function FrontLinesLayer() {
const fronts = useAppSelector(selectFrontLines).fronts;
return (
<LayerGroup>

View File

@@ -0,0 +1,125 @@
import { renderWithProviders } from "../../testutils";
import NavMeshLayer from "./NavMeshLayer";
import { PropsWithChildren } from "react";
const mockPolygon = jest.fn();
const mockLayerGroup = jest.fn();
jest.mock("react-leaflet", () => ({
LayerGroup: (props: PropsWithChildren<any>) => {
mockLayerGroup(props);
return <>{props.children}</>;
},
Polygon: (props: any) => {
mockPolygon(props);
},
}));
// The waypoints in test data below should all use `should_make: false`. Markers
// need useMap() to check the zoom level to decide if they should be drawn or
// not, and we don't have good options here for mocking that behavior.
describe("NavMeshLayer", () => {
it("draws blue meshes", () => {
const poly1 = [
[
{ lat: -1, lng: 0 },
{ lat: 0, lng: 1 },
{ lat: 1, lng: 0 },
],
];
const poly2 = [
[
{ lat: -1, lng: 0 },
{ lat: 0, lng: -1 },
{ lat: 1, lng: 0 },
],
];
renderWithProviders(<NavMeshLayer blue={true} />, {
preloadedState: {
navmeshes: {
blue: [
{
poly: poly1,
threatened: false,
},
{
poly: poly2,
threatened: true,
},
],
red: [
{
poly: [
[
{ lat: -1, lng: 0 },
{ lat: 0, lng: 2 },
{ lat: 1, lng: 0 },
],
],
threatened: false,
},
],
},
},
});
expect(mockPolygon).toHaveBeenCalledTimes(2);
expect(mockPolygon).toHaveBeenCalledWith(
expect.objectContaining({
fillColor: "#00ff00",
positions: poly1,
interactive: false,
})
);
expect(mockPolygon).toHaveBeenCalledWith(
expect.objectContaining({
fillColor: "#ff0000",
positions: poly2,
interactive: false,
})
);
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
});
it("draws red navmesh", () => {
renderWithProviders(<NavMeshLayer blue={false} />, {
preloadedState: {
navmeshes: {
blue: [
{
poly: [
[
{ lat: -1, lng: 0 },
{ lat: 0, lng: 1 },
{ lat: 1, lng: 0 },
],
],
threatened: false,
},
{
poly: [
[
{ lat: -1, lng: 0 },
{ lat: 0, lng: -1 },
{ lat: 1, lng: 0 },
],
],
threatened: true,
},
],
red: [
{
poly: [
[
{ lat: -1, lng: 0 },
{ lat: 0, lng: 2 },
{ lat: 1, lng: 0 },
],
],
threatened: false,
},
],
},
},
});
expect(mockPolygon).toHaveBeenCalledTimes(1);
expect(mockLayerGroup).toHaveBeenCalledTimes(1);
});
});

View File

@@ -1,5 +1,5 @@
import App from "./App";
import { store } from "./app/store";
import { setupStore } from "./app/store";
import { SocketProvider } from "./components/socketprovider/socketprovider";
import "./index.css";
import * as serviceWorker from "./serviceWorker";
@@ -12,7 +12,7 @@ const root = ReactDOM.createRoot(
);
root.render(
<React.StrictMode>
<Provider store={store}>
<Provider store={setupStore()}>
<SocketProvider>
<App />
</SocketProvider>

View File

@@ -0,0 +1,30 @@
// https://redux.js.org/usage/writing-tests
import { setupStore } from "../app/store";
import type { AppStore, RootState } from "../app/store";
import type { PreloadedState } from "@reduxjs/toolkit";
import { render } from "@testing-library/react";
import type { RenderOptions } from "@testing-library/react";
import React, { PropsWithChildren } from "react";
import { Provider } from "react-redux";
// This type interface extends the default options for render from RTL, as well
// as allows the user to specify other things such as initialState, store.
interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
preloadedState?: PreloadedState<RootState>;
store?: AppStore;
}
export function renderWithProviders(
ui: React.ReactElement,
{
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = setupStore(preloadedState),
...renderOptions
}: ExtendedRenderOptions = {}
) {
function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
return <Provider store={store}>{children}</Provider>;
}
return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}

8
codecov.yaml Normal file
View File

@@ -0,0 +1,8 @@
coverage:
status:
patch:
default:
informational: true
project:
default:
informational: true

20
docs/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

34
docs/conf.py Normal file
View File

@@ -0,0 +1,34 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "DCS Liberation"
copyright = "2023, DCS Liberation Team"
author = "DCS Liberation Team"
release = "7.0.0"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"myst_parser",
"sphinx_rtd_theme",
"sphinx.ext.autosectionlabel",
"sphinx.ext.todo",
]
templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "sphinx_rtd_theme"
html_static_path = ["_static"]
todo_include_todos = True

View File

@@ -0,0 +1,8 @@
Design docs
===========
.. toctree::
:maxdepth: 2
:caption: Contents:
turnless.md

8
docs/dev/index.rst Normal file
View File

@@ -0,0 +1,8 @@
Developer documentation
=======================
.. toctree::
:maxdepth: 2
:caption: Contents:
design/index.rst

6
docs/game/index.rst Normal file
View File

@@ -0,0 +1,6 @@
Manual
======
.. toctree::
:maxdepth: 2
:caption: Contents:

16
docs/index.rst Normal file
View File

@@ -0,0 +1,16 @@
DCS Liberation
==============
.. toctree::
:maxdepth: 2
:caption: Contents:
game/index.rst
modding/index.rst
dev/index.rst
Indices and tables
==================
* :ref:`genindex`
* :ref:`search`

35
docs/make.bat Normal file
View File

@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

10
docs/modding/index.rst Normal file
View File

@@ -0,0 +1,10 @@
Modding guide
=============
.. toctree::
:maxdepth: 2
:caption: Contents:
fuel-consumption-measurement.md
layouts.rst
weather.rst

397
docs/modding/layouts.rst Normal file
View File

@@ -0,0 +1,397 @@
The Layout System
=================
.. note::
The documentation of the layout system is still WIP and not
complete as the development of this feature involves a major refactoring
of the base code. Therefore this documentation is currently used for
development purpose primarily. The documentation will be updated soon.
Any help in updating this wiki page is appreciated!
The Layout System is a new way of defining how ground objects like SAM
Sites or other Vehicle / Ship Groups will be generated (which type of
units, how many units, alignment and orientation). It is a complete
rework of the previous generator-based logic which was written in python
code. The new system allows to define layouts with easy to write yaml
code and the use of the DCS Mission Editor for easier placement of the
units. The layout system also introduced a new logical grouping of Units
and layouts for them, the Armed Forces, which will allow major
improvements to the Ground Warfare in upcoming features.
**Armed Forces**
The Armed Forces is a new system introduced with the layout system which will
allow to identitfy and group possible units from the faction together with
available layouts for these groups. It is comparable to the AirWing and Squadron
implementation but just for Ground and Naval Forces. All possible Force Groups
(grouping of 1 or more units and and the available layouts for them) will be
generated during campaign initialization and will be used later by many
different systems. A Force Group can also include static objects which was not
possible before the introduction of the layout system. It is also possible to
define presets of these Force Groups within the faction file which is handy for
more complex groups like a SA-10 Battery or similar. Example: `SA-10.yaml`_.
which includes all the units like SR, TR, LN and has the layout of a
`S-300_Site.yaml`_.
.. _SA-10.yaml: https://github.com/dcs-liberation/dcs_liberation/blob/develop/resources/groups/SA-10.yaml
.. _S-300_Site.yaml: https://github.com/dcs-liberation/dcs_liberation/blob/develop/resources/layouts/anti_air/S-300_Site.yaml
**The Layout System**
In the previous system the generator which created the ground object was written
in python which made modifications and reusability very complicated. To allow
easier handling of the layouts and decoupling of alignment of units and the
actual unit type (for example Ural-375) the layout system was introduced.
Previously we had a generator for every different SAM Site, now we can just
reuse the alignemnt (e.g. 6 Launchers in a circle alignment) for multiple SAM
Systems and introduce more variety.
This new System allows Users and Designers to easily create or modify
layouts as the new alginment and orientation of units is defined with
the DCS Mission editor. An additional .yaml file allows the
configuration of the layout with settings like allow unit types or
random amounts of units. In total the new system reduces the complexity
and allows to precisely align / orient units as needed and create
realistic looking ground units.
As the whole ground unit generation and handling was reworked it is now
also possible to add static units to a ground object, so even
Fortifcation or similar can be added to templates in the future.
General Concept
~~~~~~~~~~~~~~~
.. figure:: images/layouts.png
:alt: Overview
Overview
All possible Force Groups will be generated during campaign
initialization by checking all possible units for the specific faction
and all available layouts. The code will automatically match general
layouts with available units. It is also possible to define preset
groups within the faction files which group many units and the prefered
layouts for the group. This is especially handy for unique layouts which
are not defined as ``global``. For example complex sam sites like the
S-300 or Patriot which have very specific alignment of the different
units.
Layouts will be matched to units based on the special definition given
in the corresponding yaml file. For example a layout which is defined as
global and allows the unit_class SHORAD will automatically be used for
all available SHORAD units which are defined in the faction file.
.. todo:: Describe the optional flag.
All these generated ForceGroups will be managed by the ArmedForces class
of the specific coalition. This class will be used by other parts of the
system like the start_generator or the BuyMenu. The ArmedForces class
will then generate the TheaterGroundObject which will be used by
liberation.
Example for a customized Ground Object Buy Menu which makes use of
Templates and UnitGroups:
.. figure:: images/ground_object_buy_menu.png
:alt: Ground object buy menu
Ground object buy menu
How to modify or add layouts
----------------------------
.. warning::
Whenever changes were made to layouts they have to be re-imported into
Liberation. See :ref:`Import Layouts into Liberation`.
A layout consists of two special files:
- layout.miz which defines the actual positioning and alignment of the
groups / units
- layout.yaml which defines the necessary information like amount of
units, possible types or classes.
To add a new template a new yaml has to be created as every yaml can
only define exact one template. Best practice is to copy paste an
existing template which is similar to the one to be created as starting
point. The next step is to create a new .miz file and align Units and
statics to the wishes. Even if existing ones can be reused, best
practice is to always create a fresh one to prevent side effects. The
most important part is to use a new Group for every different Unit Type.
It is not possible to mix Unit Types in one group within a template. For
example it is not possible to have a logistic truck and a AAA in the
same group. The miz file will only be used to get the exact position and
orientation of the units, therefore it is irrelevant which type of unit
will be used. The unit type will be later defined inside the yaml file.
For the next step all Group names have to be added to the yaml file.
Take care to that these names match exactly! Assign the unit_types or
unit_classes properties to math the needs.
The Layout miz
~~~~~~~~~~~~~~
The miz file is used to define the positioning and orientation of the
different units for the template. The actual unit which is used is
irrelevant. It is important to use a unique and meaningful name for the
groups as this will be used in the yaml file as well. The information
which will be extracted from the miz file are just the coordinates and
heading of the units.
*Important*: Every different unit type has to be in a separate Group for
the template to work. You can not add units of different types to the
same group. They can get merged back together during generation by
setting the group property. In the example below both groups
``AAA Site 0`` and ``AAA Site 1`` have the group = 1 which means that
they will be in the same dcs group during generation.
*Important*: Liberation expects every template to be designed with an
orientation of heading 0 (North) in mind. The complete GroundObject will
during the campaign generation process be rotated to match the
orientation defined by the campaign designer. If the layout was not
created with an orientation of heading 0 the later generated
GroundObject will likely be misaligned and not work properly.
.. todo::
max amount of possible units is defined from the miz. Example if later the
group should have 6 units than there have to be 6 defined in the miz.
.. figure:: images/layout_miz_example.png
:alt: Example template mission
Example template mission
The Layout configuration file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. todo:: Description about the layout yaml file.
Possible Information:
.. list-table::
:header-rows: 1
* - Property
- Type
- Required
- Description
- Example
* - name
- ``str``
- Yes
- A name to identify the template
- .. code:: yaml
name: Armor Group
* - tasks
- list of ``GroupTask``
- Yes
- A list of tasks which the template can fulfill
- .. code:: yaml
tasks:
- AAA
- SHORAD
* - generic
- ``bool``, default false
- No
- True if this template will be used to create general ``UnitGroups``
-
* - description
- ``str``
- No
- Short description of the template
-
* - groups
- List of ``Groups``
- Yes
- See below for definition of a group
-
* - layout_file
- ``str``
- No
- The .miz file which has the groups/units of the layout included. Only
needed if the file has a different name than the yaml file
- .. code:: yaml
layout_file: resources/layouts/naval/legacy_naval_templates.miz
.. todo:: Group and SubGroup
A group has 1..N sub groups. The name of the Group will be used later
within the DCS group name.
All SubGroups will be merged into one DCS Group
Every unit type has to be defined as a sub group as following:
.. list-table::
:header-rows: 1
* - Property
- Type
- Required
- Description
* - name
- ``str``
- Yes
- The group name used in the .miz. Must match exactly!
* - optional
- ``bool``, default: false
- No
- Defines wether the layout can be used without this group if the faction
has no access to the unit type or the user wants to disable this group
* - fill
- ``bool``, default: false
- No
- If the group is optional the layout is used from a PresetGroup this
property tells the system if it should use any possible faction
accessible unit to fill up this slot if no capable one was defined in
the preset yaml.
* - unit_count
- list of ``int``
- No
- Amount of units to be generated for this group. Can be fixed or a range
where it will be picked randomly
* - unit_types
- list of DCS unit type IDs
- No
- Specific unit_types for ground units. Complete list from `vehicles.py`_.
This list is extended by all supported mods!
* - unit_classes
- list of unit classes
- No
- Unit classes of supported units. Defined by ``UnitClass`` in
`game/data/units.py`_.
* - statics
- list of static types
- No
- Specific unit_types of statics. Complete list from `statics.py`_
.. _vehicles.py: https://github.com/pydcs/dcs/blob/master/dcs/vehicles.py
.. _game/data/units.py: https://github.com/dcs-liberation/dcs_liberation/blob/develop/game/data/units.py
.. _statics.py: https://github.com/pydcs/dcs/blob/master/dcs/statics.py
Complete example of a generic template for an Aircraft Carrier group:
.. code:: yaml
name: Carrier Group
generic: true
tasks:
- AircraftCarrier
groups:
- Carrier: # Group Name of the DCS Group
- name: Carrier Group 0 # Sub Group used in the layout.miz
unit_count:
- 1
unit_classes:
- AircraftCarrier
- Escort: # Group name of the 2nd Group
- name: Carrier Group 1
unit_count:
- 4
unit_classes:
- Destroyer
layout_file: resources/layouts/naval/legacy_naval_templates.miz
Import Layouts into Liberation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For performance improvements all layouts are serialized to a so called
pickle file inside the save folder defined in the liberation
preferences. Every time changes are made to the layouts this file has to
be recreated. It can be recreated by either deleting the layouts.p file
manually or using the special option in the Liberation Toolbar
(Developer Tools -> Import Layouts). It will also be recreated after
each Liberation update as it will check the Version Number and recreate
it when changes are recognized.
Migration from Generators
-------------------------
The previous generators were migrated using a script which build a group using
the generator. All of these groups were save into one .miz file
`original_generator_layouts.miz`_. This miz file can be used to verify the
templates and to generalize similar templates to decouple the layout from the
actual units. As this is a time-consuming and sphisticated task this will be
done over time. With the first step the technical requirements will be fulfilled
so that the generalization can happen afterwards the technical pr gets merged.
.. _original_generator_layouts.miz: https://github.com/dcs-liberation/dcs_liberation/blob/develop/resources/layouts/original_generator_layouts.miz
Updates for Factions
~~~~~~~~~~~~~~~~~~~~
With the rework there were also some changes to the faction file
definitions. Older faction files can not be loaded anymore and have to
be adopted to the new changes. During migration all default factions
were automatically updated, so they will work out of the box.
You can find more detailed information about how to customize the
faction file in `Custom factions`_.
What was changed:
* Removed the ``ewrs`` list. All EWRs are now defined in the list
``air_defense_units``.
* Added the ``air_defense_units`` list. All units with the Role AntiAir can be
defined here as `GroundUnitType`_. All possible units are defined in
`resources/units/ground_units`_.
* Added ``preset_groups``. This list allows to define Preset Groups (described
above) like SAM Systems consisting of Launcher, SR, TR and so on instead of
adding them each to “air_defense_units”. The presets are defined in
`resources/groups`_
* Migrated ``air_defenses`` to air_defense_units and preset_sets.
* ``Missiles`` are migrated to GroundUnitTypes instead of Generator names (see
air_defense_units for how to use)
* Removed ``cruisers``, ``destroyers`` and ``naval_generators``. Migrated them
to naval_units and preset_groups
* Added ``naval_units`` with the correct ship name found in
`resources/units/ships`_.
* ``aircraft_carrier`` and ``helicopter_carrier`` were moved to ``naval_units``
as well.
.. _Custom factions: https://github.com/dcs-liberation/dcs_liberation/wiki/Custom-Factions
.. _GroundUnitType: https://github.com/dcs-liberation/dcs_liberation/blob/develop/game/dcs/groundunittype.py
.. _resources/units/ground_units: https://github.com/dcs-liberation/dcs_liberation/blob/develop/resources/units/ground_units
.. _resources/units/ships: https://github.com/dcs-liberation/dcs_liberation/blob/develop/resources/units/ships
.. _resources/groups: https://github.com/dcs-liberation/dcs_liberation/blob/develop/resources/groups
Preset Groups
-------------
Instead of adding the exact name of the previous generator to add
complex groups like SAM sites or similar to the faction it is now
possible to add preset groups to the faction file. As described earlier
such a preset group (Force Group) can be defined very easy with a yaml
file. This file allows to define the name, tasking, units, statics and
the prefered layouts. The first task defines the primary role of the
ForceGroup which gets generated from the preset.
Example:
.. code:: yaml
name: SA-10/S-300PS # The name of the group
tasks: # Define at least 1 task
- LORAD # The task(s) the Group can fulfill
units: # Define at least 1 unit
- SAM SA-10 S-300 "Grumble" Clam Shell SR
- SAM SA-10 S-300 "Grumble" Big Bird SR
- SAM SA-10 S-300 "Grumble" C2
- SAM SA-10 S-300 "Grumble" Flap Lid TR
- SAM SA-10 S-300 "Grumble" TEL D
- SAM SA-10 S-300 "Grumble" TEL C
statics: # Optional
- # Add some statics here
layouts: # Define at least one layout
- S-300 Site # prefered layouts for these groups
Resources:
* A list of all available preset groups can be found here: `resources/groups`_
* All possible tasks can be found in the `game/data/groups.py`_
* Units are defined with the variant name found in `resources/units`_
.. _game/data/groups.py: https://github.com/dcs-liberation/dcs_liberation/blob/develop/game/data/groups.py
.. _resources/units: https://github.com/dcs-liberation/dcs_liberation/tree/develop/resources/units

76
docs/modding/weather.rst Normal file
View File

@@ -0,0 +1,76 @@
#######
Weather
#######
Weather conditions in DCS Liberation are randomly generated at the start of each
turn. Some of the inputs to that generator (more to come) can be controlled via
the config files in ``resources/weather``.
**********
Archetypes
**********
A weather archetype defines the the conditions for a style of weather, such as
"clear", or "raining". There are currently four archetypes:
1. clear
2. cloudy
3. raining
4. thunderstorm
The odds of each archetype appearing in each season are defined in the theater
yaml files (``resources/theaters/*/info.yaml``).
.. literalinclude:: ../../resources/weather/archetypes/clear.yaml
:language: yaml
:linenos:
:caption: resources/weather/archetypes/clear.yaml
Wind speeds
===========
DCS missions define wind with a speed and heading at each of three altitudes:
1. MSL
2. 2000 meters
3. 8000 meters
Blending between each altitude band is done in a manner defined by DCS.
Liberation randomly generates a direction for the wind at MSL, and each other
altitude band will have wind within +/- 90 degrees of that heading.
Wind speeds can be modded by altering the ``speed`` dict in the archetype yaml.
The only random distribution currently supported is the Weibull distribution, so
all archetypes currently use:
.. code:: yaml
speed:
weibull:
...
The Weibull distribution has two parameters: a shape and a scale.
The scale is simplest to understand. 63.2% of all outcomes of the distribution
are below the scale parameter.
The shape controls where the peak of the distribution is. See the examples in
the links below for illustrations and guidelines, but generally speaking low
values (between 1 and 2.6) will cause low speeds to be more common, medium
values (around 3) will be fairly evenly distributed around the median, and high
values (greater than 3.7) will cause high speeds to be more common. As wind
speeds tend to be higher at higher altitudes and fairly slow close to the
ground, you typically want a low value for MSL, a medium value for 2000m, and a
high value for 8000m.
For examples, see https://statisticsbyjim.com/probability/weibull-distribution/.
To experiment with different inputs, use Wolfram Alpha, e.g.
https://www.wolframalpha.com/input?i=weibull+distribution+1.5+5.
When generating wind speeds, each subsequent altitude band will have the lower
band's speed added to its scale parameter. That is, for the example above, the
actual scale parameter of ``at_2000m`` will be ``20 + wind speed at MSL``, and
the scale parameter of ``at_8000m`` will be ``20 + wind speed at 2000m``. This
is to ensure that a generally windy day (high wind speed at MSL) will create
similarly high winds at higher altitudes and vice versa.

2
docs/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
myst-parser
sphinx_rtd_theme

View File

@@ -1,774 +0,0 @@
import logging
from collections.abc import Sequence
from typing import Type
from dcs.helicopters import (
AH_1W,
AH_64A,
AH_64D,
AH_64D_BLK_II,
CH_47D,
CH_53E,
Ka_50,
Ka_50_3,
Mi_24P,
Mi_24V,
Mi_26,
Mi_28N,
Mi_8MT,
OH_58D,
SA342L,
SA342M,
SH_60B,
UH_1H,
UH_60A,
)
from dcs.planes import (
AJS37,
AV8BNA,
A_10A,
A_10C,
A_10C_2,
A_20G,
A_50,
An_26B,
B_17G,
B_1B,
B_52H,
Bf_109K_4,
C_101CC,
C_130,
C_17A,
C_47,
E_2C,
E_3A,
FA_18C_hornet,
FW_190A8,
FW_190D9,
F_117A,
F_14A,
F_14A_135_GR,
F_14B,
F_15C,
F_15E,
F_16A,
F_16A_MLU,
F_16C_50,
F_4E,
F_5E_3,
F_86F_Sabre,
H_6J,
IL_76MD,
IL_78M,
I_16,
JF_17,
J_11A,
Ju_88A4,
KC130,
KC135MPRS,
KC_135,
KJ_2000,
L_39ZA,
MQ_9_Reaper,
M_2000C,
MiG_15bis,
MiG_19P,
MiG_21Bis,
MiG_23MLD,
MiG_25PD,
MiG_27K,
MiG_29A,
MiG_29G,
MiG_29S,
MiG_31,
Mirage_2000_5,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1C_200,
Mirage_F1CT,
MosquitoFBMkVI,
P_47D_30,
P_47D_30bl1,
P_47D_40,
P_51D,
P_51D_30_NA,
RQ_1A_Predator,
S_3B,
S_3B_Tanker,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
Su_17M4,
Su_24M,
Su_25,
Su_25T,
Su_25TM,
Su_27,
Su_30,
Su_33,
Su_34,
Tornado_GR4,
Tornado_IDS,
Tu_142,
Tu_160,
Tu_22M3,
Tu_95MS,
WingLoong_I,
Yak_40,
MB_339A,
)
from dcs.unittype import FlyingType
from game.dcs.aircrafttype import AircraftType
from pydcs_extensions.a4ec.a4ec import A_4E_C
from pydcs_extensions.a6a.a6a import VSN_A6A
from pydcs_extensions.f100.f100 import VSN_F100
from pydcs_extensions.f104.f104 import VSN_F104C, VSN_F104G, VSN_F104S, VSN_F104S_AG
from pydcs_extensions.f105.f105 import VSN_F105D, VSN_F105G
from pydcs_extensions.f15d.f15d import F_15D
from pydcs_extensions.f16i_idf.f16i_idf import (
F_16D_50,
F_16D_52,
F_16D_50_NS,
F_16D_52_NS,
F_16D_Barak_30,
F_16D_Barak_40,
F_16I,
)
from pydcs_extensions.f22a.f22a import F_22A
from pydcs_extensions.f4b.f4b import VSN_F4B, VSN_F4C
from pydcs_extensions.f84g.f84g import VSN_F84G
from pydcs_extensions.fa18efg.fa18efg import FA_18E, FA_18F, EA_18G
from pydcs_extensions.hercules.hercules import Hercules
from pydcs_extensions.jas39.jas39 import JAS39Gripen, JAS39Gripen_AG
from pydcs_extensions.su30.su30 import Su_30MKA, Su_30MKI, Su_30MKM, Su_30SM
from pydcs_extensions.su57.su57 import Su_57
from pydcs_extensions.ov10a.ov10a import Bronco_OV_10A
from pydcs_extensions.uh60l.uh60l import KC130J, UH_60L
from .flighttype import FlightType
# All aircraft lists are in priority order. Aircraft higher in the list will be
# preferred over those lower in the list.
# TODO: These lists really ought to be era (faction) dependent.
# Factions which have F-5s, F-86s, and A-4s will should prefer F-5s for CAP, but
# factions that also have F-4s should not.
# Used for CAP, Escort, and intercept if there is not a specialised aircraft available
ESCORT_CAPABLE = [
Su_57,
F_22A,
F_15C,
F_15D,
F_14B,
F_14A_135_GR,
F_14A,
Su_33,
J_11A,
Su_30,
Su_30MKA,
Su_30MKI,
Su_30MKM,
Su_30SM,
Su_27,
MiG_29S,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
JF_17,
JAS39Gripen,
F_16A_MLU,
F_16A,
F_4E,
VSN_F4C,
VSN_F4B,
MiG_31,
MiG_25PD,
MiG_29G,
MiG_29A,
MiG_23MLD,
MiG_21Bis,
Mirage_2000_5,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1C_200,
Mirage_F1CT,
F_15E,
M_2000C,
F_5E_3,
VSN_F104S,
VSN_F104G,
VSN_F104C,
MiG_19P,
VSN_F100,
A_4E_C,
F_86F_Sabre,
MiG_15bis,
C_101CC,
VSN_F84G,
P_51D_30_NA,
P_51D,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
MosquitoFBMkVI,
Bf_109K_4,
FW_190D9,
FW_190A8,
P_47D_30,
P_47D_30bl1,
P_47D_40,
I_16,
]
# Types to be skipped for escorts
CAP_CAPABLE = ESCORT_CAPABLE + [
L_39ZA,
]
# Used for CAS (Close air support) and BAI (Battlefield Interdiction)
CAS_CAPABLE = [
A_10C_2,
A_10C,
Hercules,
Su_34,
Su_25TM,
Su_25T,
Su_25,
F_15D,
F_15E,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
Tornado_GR4,
Tornado_IDS,
JAS39Gripen_AG,
JF_17,
AV8BNA,
A_10A,
F_16A_MLU,
F_16A,
B_1B,
A_4E_C,
F_14B,
F_14A_135_GR,
AJS37,
Su_24M,
Su_17M4,
Su_33,
F_4E,
S_3B,
Su_30,
Su_30MKA,
Su_30MKI,
Su_30MKM,
Su_30SM,
MiG_29S,
MiG_27K,
MiG_29A,
MiG_21Bis,
AH_64D_BLK_II,
AH_64D,
AH_64A,
AH_1W,
OH_58D,
SA342M,
SA342L,
Ka_50,
Ka_50_3,
Mi_28N,
Mi_24P,
Mi_24V,
Mi_8MT,
H_6J,
MiG_19P,
MiG_15bis,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1CT,
F_5E_3,
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
Bronco_OV_10A,
UH_1H,
A_20G,
Ju_88A4,
P_47D_40,
P_47D_30bl1,
P_47D_30,
P_51D_30_NA,
P_51D,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
MosquitoFBMkVI,
I_16,
Bf_109K_4,
FW_190D9,
FW_190A8,
WingLoong_I,
MQ_9_Reaper,
RQ_1A_Predator,
VSN_A6A,
VSN_F4C,
VSN_F4B,
VSN_F100,
VSN_F105G,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F84G,
]
# Aircraft used for SEAD and SEAD Escort tasks. Must be capable of the CAS DCS task.
SEAD_ESCORT_CAPABLE = [
JF_17,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
EA_18G,
FA_18E,
FA_18F,
FA_18C_hornet,
Tornado_IDS,
Su_25T,
Su_25TM,
F_4E,
A_4E_C,
JAS39Gripen_AG,
AV8BNA,
Su_24M,
Su_17M4,
Su_34,
Su_30,
Su_30MKA,
Su_30MKI,
Su_30MKM,
Su_30SM,
MiG_27K,
Tornado_GR4,
VSN_F105G,
VSN_F100,
]
SEAD_CAPABLE = SEAD_ESCORT_CAPABLE + [
F_16A_MLU,
F_14B,
F_14A_135_GR,
]
# Aircraft used for DEAD tasks. Must be capable of the CAS DCS task.
DEAD_CAPABLE = SEAD_CAPABLE + [
AJS37,
F_16A,
F_15E,
JAS39Gripen_AG,
B_1B,
B_52H,
Tu_160,
Tu_95MS,
H_6J,
A_20G,
Ju_88A4,
P_47D_40,
P_47D_30bl1,
P_47D_30,
P_51D_30_NA,
P_51D,
Bronco_OV_10A,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
MosquitoFBMkVI,
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F100,
VSN_F84G,
]
# Aircraft used for Strike mission
STRIKE_CAPABLE = [
F_117A,
B_1B,
B_52H,
Tu_160,
Tu_95MS,
Tu_22M3,
H_6J,
F_15D,
F_15E,
AJS37,
Tornado_GR4,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
AV8BNA,
JF_17,
F_16A_MLU,
F_16A,
F_14B,
F_14A_135_GR,
JAS39Gripen_AG,
Tornado_IDS,
Su_17M4,
Su_24M,
Su_25TM,
Su_25T,
Su_25,
Su_34,
Su_33,
Su_30,
Su_30MKA,
Su_30MKI,
Su_30MKM,
Su_30SM,
Su_27,
MiG_29S,
MiG_29G,
MiG_29A,
F_4E,
A_10C_2,
A_10C,
VSN_F4C,
VSN_F4B,
S_3B,
A_4E_C,
Bronco_OV_10A,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1CT,
MiG_27K,
MiG_21Bis,
MiG_15bis,
F_5E_3,
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
B_17G,
A_20G,
Ju_88A4,
P_47D_40,
P_47D_30bl1,
P_47D_30,
P_51D_30_NA,
P_51D,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
MosquitoFBMkVI,
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F100,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F105G,
VSN_F105D,
VSN_F84G,
]
ANTISHIP_CAPABLE = [
AJS37,
Tu_142,
Tu_22M3,
H_6J,
FA_18E,
FA_18F,
FA_18C_hornet,
JAS39Gripen_AG,
F_16A_MLU,
F_16A,
Su_24M,
Su_17M4,
JF_17,
Su_34,
Su_30,
Su_30MKA,
Su_30MKI,
Su_30MKM,
Su_30SM,
Tornado_IDS,
Tornado_GR4,
AV8BNA,
S_3B,
A_20G,
Ju_88A4,
MosquitoFBMkVI,
C_101CC,
SH_60B,
]
# This list does not "inherit" from the strike list because some strike aircraft can
# only carry guided weapons, and the AI cannot do runway attack with dguided weapons.
# https://github.com/dcs-liberation/dcs_liberation/issues/1703
RUNWAY_ATTACK_CAPABLE = [
JF_17,
Tornado_IDS,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1CT,
H_6J,
B_1B,
B_52H,
Tu_22M3,
H_6J,
F_15E,
AJS37,
F_16C_50,
F_16I,
F_16D_Barak_40,
F_16D_Barak_30,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18E,
FA_18F,
FA_18C_hornet,
AV8BNA,
JF_17,
F_16A_MLU,
F_16A,
F_14B,
F_14A_135_GR,
JAS39Gripen_AG,
Tornado_IDS,
Su_17M4,
Su_24M,
Su_25TM,
Su_25T,
Su_25,
Su_34,
Su_33,
Su_30,
Su_30MKA,
Su_30MKI,
Su_30MKM,
Su_30SM,
Su_27,
MiG_29S,
MiG_29G,
MiG_29A,
F_4E,
A_10C_2,
A_10C,
VSN_F4C,
VSN_F4B,
S_3B,
A_4E_C,
Bronco_OV_10A,
M_2000C,
MiG_27K,
MiG_21Bis,
MiG_15bis,
F_5E_3,
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
B_17G,
A_20G,
Ju_88A4,
P_47D_40,
P_47D_30bl1,
P_47D_30,
P_51D_30_NA,
P_51D,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
MosquitoFBMkVI,
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F105G,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F100,
]
# For any aircraft that isn't necessarily directly involved in strike
# missions in a direct combat sense, but can transport objects and infantry.
TRANSPORT_CAPABLE = [
C_17A,
Hercules,
C_130,
C_47,
IL_76MD,
An_26B,
Yak_40,
CH_53E,
CH_47D,
UH_60L,
SH_60B,
UH_60A,
UH_1H,
Mi_8MT,
Mi_8MT,
Mi_26,
]
AIR_ASSAULT_CAPABLE = [
CH_53E,
CH_47D,
UH_60L,
SH_60B,
UH_60A,
UH_1H,
Mi_8MT,
Mi_26,
Mi_24P,
Mi_24V,
Hercules,
]
DRONES = [MQ_9_Reaper, RQ_1A_Predator, WingLoong_I]
AEWC_CAPABLE = [
E_3A,
E_2C,
A_50,
KJ_2000,
]
# Priority is given to the tankers that can carry the most fuel.
REFUELING_CAPABALE = [
KC_135,
KC135MPRS,
IL_78M,
KC130J,
KC130,
S_3B_Tanker,
]
def dcs_types_for_task(task: FlightType) -> Sequence[Type[FlyingType]]:
cap_missions = (
FlightType.BARCAP,
FlightType.INTERCEPTION,
FlightType.SWEEP,
FlightType.TARCAP,
)
if task in cap_missions:
return CAP_CAPABLE
elif task == FlightType.ANTISHIP:
return ANTISHIP_CAPABLE
elif task == FlightType.BAI:
return CAS_CAPABLE
elif task == FlightType.CAS:
return CAS_CAPABLE
elif task == FlightType.SEAD:
return SEAD_CAPABLE
elif task == FlightType.SEAD_ESCORT:
return SEAD_ESCORT_CAPABLE
elif task == FlightType.DEAD:
return DEAD_CAPABLE
elif task == FlightType.OCA_AIRCRAFT:
return CAS_CAPABLE
elif task == FlightType.OCA_RUNWAY:
return RUNWAY_ATTACK_CAPABLE
elif task == FlightType.STRIKE:
return STRIKE_CAPABLE
elif task == FlightType.ESCORT:
return ESCORT_CAPABLE
elif task == FlightType.AEWC:
return AEWC_CAPABLE
elif task == FlightType.REFUELING:
return REFUELING_CAPABALE
elif task == FlightType.TRANSPORT:
return TRANSPORT_CAPABLE
elif task == FlightType.AIR_ASSAULT:
return AIR_ASSAULT_CAPABLE
else:
logging.error(f"Unplannable flight type: {task}")
return []
def aircraft_for_task(task: FlightType) -> list[AircraftType]:
dcs_types = dcs_types_for_task(task)
types: list[AircraftType] = []
for dcs_type in dcs_types:
types.extend(AircraftType.for_dcs_type(dcs_type))
return types
def tasks_for_aircraft(aircraft: AircraftType) -> list[FlightType]:
tasks: list[FlightType] = []
for task in FlightType:
if task is FlightType.FERRY:
# Not a plannable task, so skip it.
continue
if aircraft in aircraft_for_task(task):
tasks.append(task)
return tasks

View File

@@ -7,6 +7,7 @@ from typing import Any, List, Optional, TYPE_CHECKING
from dcs import Point
from dcs.planes import C_101CC, C_101EB, Su_33, FA_18C_hornet
from game.dcs.aircrafttype import AircraftType
from pydcs_extensions.hercules.hercules import Hercules
from .flightroster import FlightRoster
from .flightstate import FlightState, Navigating, Uninitialized
@@ -23,7 +24,6 @@ from ..sidc import (
Status,
SymbolSet,
)
from game.dcs.aircrafttype import AircraftType
if TYPE_CHECKING:
from game.sim.gameupdateevents import GameUpdateEvents
@@ -44,7 +44,6 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
def __init__(
self,
package: Package,
country: str,
squadron: Squadron,
count: int,
flight_type: FlightType,
@@ -56,13 +55,14 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
frequency: Optional[RadioFrequency] = None,
channel: Optional[TacanChannel] = None,
callsign: Optional[str] = None,
claim_inv: bool = True,
) -> None:
self.id = uuid.uuid4()
self.package = package
self.country = country
self.coalition = squadron.coalition
self.squadron = squadron
self.squadron.claim_inventory(count)
if claim_inv:
self.squadron.claim_inventory(count)
if roster is None:
self.roster = FlightRoster(self.squadron, initial_size=count)
else:
@@ -70,6 +70,10 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
self.divert = divert
self.flight_type = flight_type
self.loadout = Loadout.default_for(self)
if self.squadron.aircraft.name == "F-15I Ra'am":
self.loadout.pylons[16] = Weapon.with_clsid(
"{IDF_MODS_PROJECT_F-15I_Raam_Dome}"
)
self.start_type = start_type
self.use_custom_loadout = False
self.custom_name = custom_name
@@ -80,6 +84,8 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
self.tacan = channel
self.tcn_name = callsign
self.initialize_fuel()
# Only used by transport missions.
self.cargo = cargo
@@ -170,9 +176,13 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
def unit_type(self) -> AircraftType:
return self.squadron.aircraft
@property
def is_lha(self) -> bool:
return self.unit_type.lha_capable
@property
def is_helo(self) -> bool:
return self.unit_type.dcs_unit_type.helicopter
return self.unit_type.helicopter
@property
def is_hercules(self) -> bool:
@@ -200,23 +210,34 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
def missing_pilots(self) -> int:
return self.roster.missing_pilots
def set_flight_type(self, var: FlightType) -> None:
self.flight_type = var
# Update _flight_plan_builder so that the builder class remains relevant
# to the flight type
from .flightplans.flightplanbuildertypes import FlightPlanBuilderTypes
self._flight_plan_builder = FlightPlanBuilderTypes.for_flight(self)(self)
def return_pilots_and_aircraft(self) -> None:
self.roster.clear()
self.squadron.claim_inventory(-self.count)
def max_takeoff_fuel(self) -> Optional[float]:
# Special case so Su 33 and C101 can take off
def initialize_fuel(self) -> None:
unit_type = self.unit_type.dcs_unit_type
self.fuel = unit_type.fuel_max
# Special cases where we want less fuel for takeoff
if unit_type == Su_33:
if self.flight_type.is_air_to_air:
return Su_33.fuel_max / 2.2
self.fuel = Su_33.fuel_max / 2.2
else:
return Su_33.fuel_max * 0.8
self.fuel = Su_33.fuel_max * 0.8
elif unit_type in {C_101EB, C_101CC}:
return unit_type.fuel_max * 0.5
self.fuel = unit_type.fuel_max * 0.5
elif unit_type == Hercules:
return unit_type.fuel_max * 0.75
return None
self.fuel = unit_type.fuel_max * 0.75
elif self.departure.cptype.name in ["FARP", "FOB"] and not self.is_helo:
self.fuel = unit_type.fuel_max * 0.75
def __repr__(self) -> str:
return self.__str__()
@@ -288,7 +309,6 @@ class Flight(SidcDescribable, RadioFrequencyContainer, TacanContainer):
def clone_flight(flight: Flight) -> Flight:
return Flight(
flight.package,
flight.country,
flight.squadron,
flight.count,
flight.flight_type,

View File

@@ -48,7 +48,9 @@ class Builder(IBuilder[AewcFlightPlan, PatrollingLayout]):
orbit_heading = heading_to_threat_boundary
# Station 80nm outside the threat zone.
threat_buffer = nautical_miles(80)
threat_buffer = nautical_miles(
self.flight.coalition.game.settings.aewc_threat_buffer_min_distance
)
if self.threat_zones.threatened(location.position):
orbit_distance = distance_to_threat + threat_buffer
else:

View File

@@ -4,11 +4,11 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import Iterator, TYPE_CHECKING, Type
from ._common_ctld import generate_random_ctld_point
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
from game.theater.controlpoint import ControlPointType
from game.theater.missiontarget import MissionTarget
from game.utils import Distance, feet, meters
from ._common_ctld import generate_random_ctld_point
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .uizonedisplay import UiZone, UiZoneDisplay
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class AirAssaultLayout(StandardLayout):
# The pickup point is optional because we don't always need to load the cargo. When
# departing from a carrier, LHA, or off-map spawn, the cargo is pre-loaded.

View File

@@ -5,9 +5,9 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import TYPE_CHECKING, Type
from ._common_ctld import generate_random_ctld_point
from game.theater.missiontarget import MissionTarget
from game.utils import feet
from ._common_ctld import generate_random_ctld_point
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .standard import StandardFlightPlan, StandardLayout
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
from dcs import Point
@dataclass(frozen=True)
@dataclass
class AirliftLayout(StandardLayout):
nav_to_pickup: list[FlightWaypoint]
# There will not be a pickup waypoint when the pickup airfield is the departure
@@ -38,6 +38,20 @@ class AirliftLayout(StandardLayout):
ctld_drop_off_zone: FlightWaypoint | None
nav_to_home: list[FlightWaypoint]
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if super().delete_waypoint(waypoint):
return True
if waypoint in self.nav_to_pickup:
self.nav_to_pickup.remove(waypoint)
return True
elif waypoint in self.nav_to_drop_off:
self.nav_to_drop_off.remove(waypoint)
return True
elif waypoint in self.nav_to_home:
self.nav_to_home.remove(waypoint)
return True
return False
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to_pickup

View File

@@ -35,7 +35,7 @@ class Builder(FormationAttackBuilder[AntiShipFlightPlan, FormationAttackLayout])
else:
raise InvalidObjectiveLocation(self.flight.flight_type, location)
return self._build(FlightWaypointType.INGRESS_BAI, targets)
return self._build(FlightWaypointType.INGRESS_ANTI_SHIP, targets)
@staticmethod
def anti_ship_targets_for_tgo(tgo: NavalGroundObject) -> list[StrikeTarget]:

View File

@@ -60,7 +60,9 @@ class CapBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
# distance from the nearest enemy airbase, but since they are by
# definition in enemy territory they can't avoid the threat zone
# without being useless.
min_distance_from_enemy = nautical_miles(20)
min_distance_from_enemy = nautical_miles(
self.coalition.game.settings.tarcap_threat_buffer_min_distance
)
distance_to_airfield = meters(
closest_airfield.position.distance_to_point(
self.package.target.position

View File

@@ -18,7 +18,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class CasLayout(PatrollingLayout):
target: FlightWaypoint
@@ -53,7 +53,7 @@ class CasFlightPlan(PatrollingFlightPlan[CasLayout], UiZoneDisplay):
@property
def engagement_distance(self) -> Distance:
max_length = self.flight.coalition.game.settings.max_frontline_length * 1000
max_length = self.flight.coalition.game.settings.max_frontline_width * 1000
return meters(max_length) / 2
@property

View File

@@ -15,7 +15,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class CustomLayout(Layout):
custom_waypoints: list[FlightWaypoint]
@@ -44,7 +44,7 @@ class CustomFlightPlan(FlightPlan[CustomLayout]):
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.tot_waypoint:
return self.package.time_over_target
return self.package.time_over_target + self.tot_offset
return None
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:

View File

@@ -15,10 +15,18 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class FerryLayout(StandardLayout):
nav_to_destination: list[FlightWaypoint]
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if super().delete_waypoint(waypoint):
return True
if waypoint in self.nav_to_destination:
self.nav_to_destination.remove(waypoint)
return True
return False
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to_destination

View File

@@ -41,7 +41,7 @@ INGRESS_TYPES = {
}
@dataclass(frozen=True)
@dataclass
class Layout(ABC):
departure: FlightWaypoint
@@ -50,6 +50,9 @@ class Layout(ABC):
"""A list of all waypoints in the flight plan, in order."""
return list(self.iter_waypoints())
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
return False
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
"""Iterates over all waypoints in the flight plan, in order."""
raise NotImplementedError
@@ -62,6 +65,12 @@ class FlightPlan(ABC, Generic[LayoutT]):
def __init__(self, flight: Flight, layout: LayoutT) -> None:
self.flight = flight
self.layout = layout
self.tot_offset = self.default_tot_offset()
def __setstate__(self, state: dict[str, Any]) -> None:
if "tot_offset" not in state:
state["tot_offset"] = self.default_tot_offset()
self.__dict__.update(state)
@property
def package(self) -> Package:
@@ -204,8 +213,7 @@ class FlightPlan(ABC, Generic[LayoutT]):
[meters(cp.position.distance_to_point(w.position)) for w in self.waypoints]
)
@property
def tot_offset(self) -> timedelta:
def default_tot_offset(self) -> timedelta:
"""This flight's offset from the package's TOT.
Positive values represent later TOTs. An offset of -2 minutes is used

View File

@@ -19,6 +19,7 @@ from .ocarunway import OcaRunwayFlightPlan
from .packagerefueling import PackageRefuelingFlightPlan
from .planningerror import PlanningError
from .sead import SeadFlightPlan
from .seadsweep import SeadSweepFlightPlan
from .strike import StrikeFlightPlan
from .sweep import SweepFlightPlan
from .tarcap import TarCapFlightPlan
@@ -51,6 +52,7 @@ class FlightPlanBuilderTypes:
FlightType.OCA_RUNWAY: OcaRunwayFlightPlan.builder_type(),
FlightType.SEAD: SeadFlightPlan.builder_type(),
FlightType.SEAD_ESCORT: EscortFlightPlan.builder_type(),
FlightType.SEAD_SWEEP: SeadSweepFlightPlan.builder_type(),
FlightType.STRIKE: StrikeFlightPlan.builder_type(),
FlightType.SWEEP: SweepFlightPlan.builder_type(),
FlightType.TARCAP: TarCapFlightPlan.builder_type(),

View File

@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import timedelta
from functools import cached_property
from typing import Any, TYPE_CHECKING, TypeGuard
from typing import Any, TYPE_CHECKING, TypeGuard, Optional
from game.typeguard import self_type_guard
from game.utils import Speed
@@ -16,14 +16,28 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class FormationLayout(LoiterLayout, ABC):
nav_to: list[FlightWaypoint]
join: FlightWaypoint
join: Optional[FlightWaypoint]
split: FlightWaypoint
refuel: FlightWaypoint
refuel: Optional[FlightWaypoint]
nav_from: list[FlightWaypoint]
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if super().delete_waypoint(waypoint):
return True
if waypoint in self.nav_to:
self.nav_to.remove(waypoint)
return True
elif waypoint in self.nav_from:
self.nav_from.remove(waypoint)
return True
elif waypoint == self.refuel:
self.refuel = None
return True
return False
class FormationFlightPlan(LoiterFlightPlan, ABC):
@property
@@ -59,10 +73,7 @@ class FormationFlightPlan(LoiterFlightPlan, ABC):
return min(speeds)
def speed_between_waypoints(self, a: FlightWaypoint, b: FlightWaypoint) -> Speed:
if b in self.package_speed_waypoints:
# Should be impossible, as any package with at least one
# FormationFlightPlan flight needs a formation speed.
assert self.package.formation_speed is not None
if self.package.formation_speed and b in self.package_speed_waypoints:
return self.package.formation_speed
return super().speed_between_waypoints(a, b)
@@ -83,9 +94,9 @@ class FormationFlightPlan(LoiterFlightPlan, ABC):
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.layout.join:
return self.join_time
return self.join_time + self.tot_offset
elif waypoint == self.layout.split:
return self.split_time
return self.split_time + self.tot_offset
return None
@property

View File

@@ -10,7 +10,7 @@ from dcs import Point
from game.flightplan import HoldZoneGeometry
from game.theater import MissionTarget
from game.utils import Speed, meters, Distance
from game.utils import Speed, meters
from .flightplan import FlightPlan
from .formation import FormationFlightPlan, FormationLayout
from .ibuilder import IBuilder
@@ -25,10 +25,6 @@ if TYPE_CHECKING:
class FormationAttackFlightPlan(FormationFlightPlan, ABC):
@property
def lead_time(self) -> timedelta:
return timedelta()
@property
def package_speed_waypoints(self) -> set[FlightWaypoint]:
return {
@@ -50,13 +46,6 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.targets[0]
@property
def tot_offset(self) -> timedelta:
try:
return -self.lead_time
except AttributeError:
return timedelta()
@property
def target_area_waypoint(self) -> FlightWaypoint:
return FlightWaypoint(
@@ -122,15 +111,25 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
)
return tot - travel_time
@property
def initial_time(self) -> timedelta:
tot = self.tot
travel_time = self.travel_time_between_waypoints(
self.layout.initial, self.target_area_waypoint
)
return tot - travel_time
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.layout.ingress:
return self.ingress_time
elif waypoint == self.layout.initial:
return self.initial_time
elif waypoint in self.layout.targets:
return self.tot
return super().tot_for_waypoint(waypoint)
@dataclass(frozen=True)
@dataclass
class FormationAttackLayout(FormationLayout):
ingress: FlightWaypoint
targets: list[FlightWaypoint]
@@ -138,9 +137,11 @@ class FormationAttackLayout(FormationLayout):
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield self.hold
if self.hold:
yield self.hold
yield from self.nav_to
yield self.join
if self.join:
yield self.join
yield self.ingress
if self.initial is not None:
yield self.initial
@@ -181,8 +182,17 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
)
)
hold = builder.hold(self._hold_point())
join = builder.join(self.package.waypoints.join)
hold = None
join = None
if (
self.flight is self.package.primary_flight
or self.package.primary_flight
and isinstance(
self.package.primary_flight.flight_plan, FormationAttackFlightPlan
)
):
hold = builder.hold(self._hold_point())
join = builder.join(self.package.waypoints.join)
split = builder.split(self.package.waypoints.split)
refuel = builder.refuel(self.package.waypoints.refuel)
@@ -190,14 +200,23 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
ingress_type, self.package.waypoints.ingress, self.package.target
)
initial = None
if ingress_type == FlightWaypointType.INGRESS_SEAD:
initial = builder.sead_search(self.package.target)
elif ingress_type == FlightWaypointType.INGRESS_SEAD_SWEEP:
initial = builder.sead_sweep(self.package.target)
return FormationAttackLayout(
departure=builder.takeoff(self.flight.departure),
hold=hold,
nav_to=builder.nav_path(
hold.position, join.position, self.doctrine.ingress_altitude
hold.position if hold else self.flight.departure.position,
join.position if join else ingress.position,
self.doctrine.ingress_altitude,
),
join=join,
ingress=ingress,
initial=initial,
targets=target_waypoints,
split=split,
refuel=refuel,
@@ -219,7 +238,7 @@ class FormationAttackBuilder(IBuilder[FlightPlanT, LayoutT], ABC):
return builder.bai_group(target)
elif flight.flight_type == FlightType.DEAD:
return builder.dead_point(target)
elif flight.flight_type == FlightType.SEAD:
elif flight.flight_type in {FlightType.SEAD, FlightType.SEAD_SWEEP}:
return builder.sead_point(target)
else:
return builder.strike_point(target)

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import timedelta
from typing import Any, TYPE_CHECKING, TypeGuard
from typing import Any, TYPE_CHECKING, TypeGuard, Optional
from game.typeguard import self_type_guard
from .flightplan import FlightPlan
@@ -13,9 +13,9 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class LoiterLayout(StandardLayout, ABC):
hold: FlightWaypoint
hold: Optional[FlightWaypoint]
class LoiterFlightPlan(StandardFlightPlan[Any], ABC):
@@ -30,7 +30,7 @@ class LoiterFlightPlan(StandardFlightPlan[Any], ABC):
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.layout.hold:
return self.push_time
return self.push_time + self.tot_offset
return None
def travel_time_between_waypoints(

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import logging
from typing import Type
from game.theater import Airfield
from game.theater import Airfield, Fob
from .formationattack import (
FormationAttackBuilder,
FormationAttackFlightPlan,
@@ -23,7 +23,7 @@ class Builder(FormationAttackBuilder[OcaAircraftFlightPlan, FormationAttackLayou
def layout(self) -> FormationAttackLayout:
location = self.package.target
if not isinstance(location, Airfield):
if not isinstance(location, Airfield) and not isinstance(location, Fob):
logging.exception(
f"Invalid Objective Location for OCA/Aircraft flight "
f"{self.flight=} at {location=}."

View File

@@ -16,13 +16,24 @@ if TYPE_CHECKING:
from .flightplan import FlightPlan
@dataclass(frozen=True)
@dataclass
class PatrollingLayout(StandardLayout):
nav_to: list[FlightWaypoint]
patrol_start: FlightWaypoint
patrol_end: FlightWaypoint
nav_from: list[FlightWaypoint]
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if super().delete_waypoint(waypoint):
return True
if waypoint in self.nav_to:
self.nav_to.remove(waypoint)
return True
elif waypoint in self.nav_from:
self.nav_from.remove(waypoint)
return True
return False
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to
@@ -62,7 +73,7 @@ class PatrollingFlightPlan(StandardFlightPlan[LayoutT], UiZoneDisplay, ABC):
@property
def patrol_start_time(self) -> timedelta:
return self.package.time_over_target
return self.package.time_over_target + self.tot_offset
@property
def patrol_end_time(self) -> timedelta:

View File

@@ -15,7 +15,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class RtbLayout(StandardLayout):
abort_location: FlightWaypoint
nav_to_destination: list[FlightWaypoint]

View File

@@ -16,9 +16,8 @@ class SeadFlightPlan(FormationAttackFlightPlan):
def builder_type() -> Type[Builder]:
return Builder
@property
def lead_time(self) -> timedelta:
return timedelta(minutes=1)
def default_tot_offset(self) -> timedelta:
return -timedelta(minutes=1)
class Builder(FormationAttackBuilder[SeadFlightPlan, FormationAttackLayout]):

View File

@@ -0,0 +1,28 @@
from __future__ import annotations
from datetime import timedelta
from typing import Type
from .formationattack import (
FormationAttackBuilder,
FormationAttackFlightPlan,
FormationAttackLayout,
)
from ..flightwaypointtype import FlightWaypointType
class SeadSweepFlightPlan(FormationAttackFlightPlan):
@staticmethod
def builder_type() -> Type[Builder]:
return Builder
def default_tot_offset(self) -> timedelta:
return -timedelta(minutes=2)
class Builder(FormationAttackBuilder[SeadSweepFlightPlan, FormationAttackLayout]):
def layout(self) -> FormationAttackLayout:
return self._build(FlightWaypointType.INGRESS_SEAD_SWEEP)
def build(self) -> SeadSweepFlightPlan:
return SeadSweepFlightPlan(self.flight, self.layout())

View File

@@ -10,12 +10,18 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class StandardLayout(Layout, ABC):
arrival: FlightWaypoint
divert: FlightWaypoint | None
bullseye: FlightWaypoint
def delete_waypoint(self, waypoint: FlightWaypoint) -> bool:
if waypoint is self.divert:
self.divert = None
return True
return False
LayoutT = TypeVar("LayoutT", bound=StandardLayout)

View File

@@ -17,7 +17,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class SweepLayout(LoiterLayout):
nav_to: list[FlightWaypoint]
sweep_start: FlightWaypoint
@@ -26,7 +26,8 @@ class SweepLayout(LoiterLayout):
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield self.hold
if self.hold:
yield self.hold
yield from self.nav_to
yield self.sweep_start
yield self.sweep_end
@@ -38,10 +39,6 @@ class SweepLayout(LoiterLayout):
class SweepFlightPlan(LoiterFlightPlan):
@property
def lead_time(self) -> timedelta:
return timedelta(minutes=5)
@staticmethod
def builder_type() -> Type[Builder]:
return Builder
@@ -54,9 +51,8 @@ class SweepFlightPlan(LoiterFlightPlan):
def tot_waypoint(self) -> FlightWaypoint:
return self.layout.sweep_end
@property
def tot_offset(self) -> timedelta:
return -self.lead_time
def default_tot_offset(self) -> timedelta:
return -timedelta(minutes=5)
@property
def sweep_start_time(self) -> timedelta:
@@ -71,14 +67,14 @@ class SweepFlightPlan(LoiterFlightPlan):
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.layout.sweep_start:
return self.sweep_start_time
return self.sweep_start_time + self.tot_offset
if waypoint == self.layout.sweep_end:
return self.sweep_end_time
return self.sweep_end_time + self.tot_offset
return None
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.layout.hold:
return self.push_time
return self.push_time + self.tot_offset
return None
@property

View File

@@ -15,7 +15,7 @@ if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
@dataclass
class TarCapLayout(PatrollingLayout):
refuel: FlightWaypoint | None
@@ -34,10 +34,6 @@ class TarCapLayout(PatrollingLayout):
class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
@property
def lead_time(self) -> timedelta:
return timedelta(minutes=2)
@property
def patrol_duration(self) -> timedelta:
# Note that this duration only has an effect if there are no
@@ -64,9 +60,8 @@ class TarCapFlightPlan(PatrollingFlightPlan[TarCapLayout]):
def combat_speed_waypoints(self) -> set[FlightWaypoint]:
return {self.layout.patrol_start, self.layout.patrol_end}
@property
def tot_offset(self) -> timedelta:
return -self.lead_time
def default_tot_offset(self) -> timedelta:
return -timedelta(minutes=2)
def depart_time_for_waypoint(self, waypoint: FlightWaypoint) -> timedelta | None:
if waypoint == self.layout.patrol_end:

View File

@@ -36,7 +36,9 @@ class Builder(IBuilder[TheaterRefuelingFlightPlan, PatrollingLayout]):
orbit_heading = heading_to_threat_boundary
# Station 70nm outside the threat zone.
threat_buffer = nautical_miles(70)
threat_buffer = nautical_miles(
self.flight.coalition.game.settings.tanker_threat_buffer_min_distance
)
if self.threat_zones.threatened(location.position):
orbit_distance = distance_to_threat + threat_buffer
else:

View File

@@ -290,12 +290,9 @@ class WaypointBuilder:
return self._target_area(f"STRIKE {target.name}", target)
def sead_area(self, target: MissionTarget) -> FlightWaypoint:
# Set flyover with ingress altitude to allow the flight to search and engage
# the target group at the ingress alt without suicide dive
return self._target_area(
f"SEAD on {target.name}",
target,
flyover=True,
altitude=self.doctrine.ingress_altitude,
alt_type="BARO",
)
@@ -316,8 +313,8 @@ class WaypointBuilder:
# plan.
return self._target_area(f"ASSAULT {target.name}", target)
@staticmethod
def _target_area(
self,
name: str,
location: MissionTarget,
flyover: bool = False,
@@ -425,6 +422,55 @@ class WaypointBuilder:
pretty_name="Orbit",
)
def sead_search(self, target: MissionTarget) -> FlightWaypoint:
hold = self._sead_search_point(target)
return FlightWaypoint(
"SEAD Search",
FlightWaypointType.NAV,
hold,
self.doctrine.ingress_altitude,
alt_type="BARO",
description="Anchor and search from this point",
pretty_name="SEAD Search",
)
def sead_sweep(self, target: MissionTarget) -> FlightWaypoint:
hold = self._sead_search_point(target)
return FlightWaypoint(
"SEAD Sweep",
FlightWaypointType.NAV,
hold,
self.doctrine.ingress_altitude,
alt_type="BARO",
description="Anchor and search from this point",
pretty_name="SEAD Sweep",
)
def _sead_search_point(self, target: MissionTarget) -> Point:
"""Creates custom waypoint for AI SEAD flights
to avoid having them fly all the way to the SAM site.
Args:
target: Target information.
"""
# Use the threat range as offset distance to avoid flying all the way to the SAM site
assert self.flight.package.waypoints
ingress = self.flight.package.waypoints.ingress
ingress2tgt_dist = ingress.distance_to_point(target.position)
threat_range = nautical_miles(
self.flight.coalition.game.settings.sead_threat_buffer_min_distance
).meters
if target.strike_targets:
threat_range = (
1.1 * max([x.threat_range for x in target.strike_targets]).meters
)
hdg = target.position.heading_between_point(ingress)
hold = target.position.point_from_heading(
hdg, min(threat_range, ingress2tgt_dist * 0.95)
)
return hold
@staticmethod
def escort_hold(start: Point, altitude: Distance) -> FlightWaypoint:
"""Creates custom waypoint for escort flights that need to hold.

View File

@@ -82,9 +82,7 @@ class FlightState(ABC):
def estimate_fuel(self) -> float:
"""Returns the estimated remaining fuel **in kilograms**."""
if (max_takeoff_fuel := self.flight.max_takeoff_fuel()) is not None:
return max_takeoff_fuel
return self.flight.unit_type.dcs_unit_type.fuel_max
return self.flight.fuel
@property
@abstractmethod

View File

@@ -24,8 +24,8 @@ class FlightType(Enum):
* Implementations of MissionTarget.mission_types: A mission type can only be planned
against compatible targets. The mission_types method of each target class defines
which missions may target it.
* ai_flight_planner_db.py: Add the new mission type to aircraft_for_task that
returns the list of compatible aircraft in order of preference.
* resources/units/aircraft/*.yaml: Assign aircraft weight for the new task type in
the `tasks` dict for all capable aircraft.
You may also need to update:
@@ -57,6 +57,7 @@ class FlightType(Enum):
REFUELING = "Refueling"
FERRY = "Ferry"
AIR_ASSAULT = "Air Assault"
SEAD_SWEEP = "SEAD Sweep" # Reintroduce legacy "engage-whatever-you-can-find" SEAD
def __str__(self) -> str:
return self.value
@@ -91,6 +92,7 @@ class FlightType(Enum):
FlightType.OCA_AIRCRAFT,
FlightType.SEAD_ESCORT,
FlightType.AIR_ASSAULT,
FlightType.SEAD_SWEEP,
}
@property
@@ -110,6 +112,7 @@ class FlightType(Enum):
FlightType.REFUELING: AirEntity.TANKER,
FlightType.SEAD: AirEntity.SUPPRESSION_OF_ENEMY_AIR_DEFENCE,
FlightType.SEAD_ESCORT: AirEntity.SUPPRESSION_OF_ENEMY_AIR_DEFENCE,
FlightType.SEAD_SWEEP: AirEntity.SUPPRESSION_OF_ENEMY_AIR_DEFENCE,
FlightType.STRIKE: AirEntity.ATTACK_STRIKE,
FlightType.SWEEP: AirEntity.FIGHTER,
FlightType.TARCAP: AirEntity.FIGHTER,

View File

@@ -49,3 +49,5 @@ class FlightWaypointType(IntEnum):
REFUEL = 29 # Should look for nearby tanker to refuel from.
CARGO_STOP = 30 # Stopover landing point using the LandingReFuAr waypoint type
INGRESS_AIR_ASSAULT = 31
INGRESS_ANTI_SHIP = 32
INGRESS_SEAD_SWEEP = 33

View File

@@ -92,6 +92,9 @@ class Loadout:
if self.has_weapon_of_type(WeaponType.TGP):
return
if unit_type.has_built_in_target_pod:
return
new_pylons = dict(self.pylons)
for pylon_number, weapon in self.pylons.items():
if weapon is not None and weapon.weapon_group.type is WeaponType.LGB:
@@ -181,6 +184,9 @@ class Loadout:
# A SEAD escort typically does not need a different loadout than a regular
# SEAD flight, so fall back to SEAD if needed.
loadout_names[FlightType.SEAD_ESCORT].extend(loadout_names[FlightType.SEAD])
loadout_names[FlightType.SEAD_SWEEP].extend(
loadout_names[FlightType.SEAD_ESCORT]
)
# Sweep and escort can fall back to TARCAP.
loadout_names[FlightType.ESCORT].extend(loadout_names[FlightType.TARCAP])
loadout_names[FlightType.SWEEP].extend(loadout_names[FlightType.TARCAP])

View File

@@ -176,6 +176,7 @@ class Package(RadioFrequencyContainer):
FlightType.TRANSPORT,
FlightType.AIR_ASSAULT,
FlightType.SEAD,
FlightType.SEAD_SWEEP,
FlightType.TARCAP,
FlightType.BARCAP,
FlightType.AEWC,

View File

@@ -56,10 +56,11 @@ class Campaign:
data: Dict[str, Any]
path: Path
advanced_iads: bool
settings: Dict[str, Any]
@classmethod
def from_file(cls, path: Path) -> Campaign:
with path.open() as campaign_file:
with path.open(encoding="utf-8") as campaign_file:
data = yaml.safe_load(campaign_file)
sanitized_theater = data["theater"].replace(" ", "")
@@ -108,6 +109,7 @@ class Campaign:
data,
path,
data.get("advanced_iads", False),
data.get("settings", {}),
)
def load_theater(self, advanced_iads: bool) -> ConflictTheater:
@@ -141,7 +143,7 @@ class Campaign:
def load_ground_forces_config(self) -> TgoConfig:
ground_forces = self.data.get("ground_forces", {})
if not ground_forces:
logging.warning(f"Campaign {self.name} does not define any squadrons")
logging.warning(f"Campaign {self.name} does not define any ground_forces")
return TgoConfig({})
return TgoConfig.from_campaign_data(ground_forces)
@@ -181,7 +183,7 @@ class Campaign:
@classmethod
def iter_campaign_defs(cls) -> Iterator[Path]:
yield from cls.iter_campaigns_in_dir(
Path(persistency.base_path()) / "Retribution/Campaigns"
persistency.base_path() / "Retribution/Campaigns"
)
yield from cls.iter_campaigns_in_dir(Path("resources/campaigns"))

View File

@@ -11,11 +11,15 @@ if TYPE_CHECKING:
from game.theater import ConflictTheater
DEFAULT_SQUADRON_SIZE = 12
@dataclass(frozen=True)
class SquadronConfig:
primary: FlightType
secondary: list[FlightType]
aircraft: list[str]
max_size: int
name: Optional[str]
nickname: Optional[str]
@@ -35,10 +39,18 @@ class SquadronConfig:
else:
secondary = [FlightType(s) for s in secondary_raw]
max_size = data.get("size", DEFAULT_SQUADRON_SIZE)
if max_size is None:
raise RuntimeError(
"Squadron's size is defined in campaign but is missing a value:\n"
f"{data}"
)
return SquadronConfig(
FlightType(data["primary"]),
secondary,
data.get("aircraft", []),
max_size,
data.get("name", None),
data.get("nickname", None),
data.get("female_pilot_percentage", None),

View File

@@ -42,7 +42,12 @@ class DefaultSquadronAssigner:
continue
squadron = Squadron.create_from(
squadron_def, control_point, self.coalition, self.game
squadron_def,
squadron_config.primary,
squadron_config.max_size,
control_point,
self.coalition,
self.game,
)
squadron.set_auto_assignable_mission_types(
squadron_config.auto_assignable
@@ -86,7 +91,11 @@ class DefaultSquadronAssigner:
try:
aircraft = AircraftType.named(preferred_aircraft)
except KeyError:
# No aircraft with this name.
logging.warning(
"%s is neither a compatible squadron or a known aircraft type, "
"ignoring",
preferred_aircraft,
)
return None
if aircraft not in self.coalition.faction.aircrafts:
@@ -110,7 +119,7 @@ class DefaultSquadronAssigner:
) -> bool:
if ignore_base_preference:
return control_point.can_operate(squadron.aircraft)
return squadron.operates_from(control_point) and task in squadron.mission_types
return squadron.operates_from(control_point) and squadron.capable_of(task)
def find_squadron_for_airframe(
self, aircraft: AircraftType, task: FlightType, control_point: ControlPoint

View File

@@ -3,19 +3,20 @@ from __future__ import annotations
import itertools
from functools import cached_property
from pathlib import Path
from typing import Iterator, List, TYPE_CHECKING, Tuple
from typing import Iterator, List, TYPE_CHECKING, Tuple, Optional
from uuid import UUID
from dcs import Mission
from dcs.countries import CombinedJointTaskForcesBlue, CombinedJointTaskForcesRed
from dcs.country import Country
from dcs.planes import F_15C
from dcs.planes import F_15C, A_10A, AJS37
from dcs.ships import HandyWind, LHA_Tarawa, Stennis, USS_Arleigh_Burke_IIa
from dcs.statics import Fortification, Warehouse
from dcs.terrain import Airport
from dcs.unitgroup import PlaneGroup, ShipGroup, StaticGroup, VehicleGroup
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from game.point_with_heading import PointWithHeading
from game.positioned import Positioned
from game.profiling import logged_duration
from game.scenery_group import SceneryGroup
@@ -28,7 +29,7 @@ from game.theater.controlpoint import (
OffMapSpawn,
)
from game.theater.presetlocation import PresetLocation
from game.utils import Distance, meters
from game.utils import Distance, meters, feet, Heading
if TYPE_CHECKING:
from game.theater.conflicttheater import ConflictTheater
@@ -40,14 +41,17 @@ class MizCampaignLoader:
RED_COUNTRY = CombinedJointTaskForcesRed()
OFF_MAP_UNIT_TYPE = F_15C.id
GROUND_SPAWN_UNIT_TYPE = A_10A.id
GROUND_SPAWN_ROADBASE_UNIT_TYPE = AJS37.id
CV_UNIT_TYPE = Stennis.id
LHA_UNIT_TYPE = LHA_Tarawa.id
FRONT_LINE_UNIT_TYPE = Armor.M_113.id
SHIPPING_LANE_UNIT_TYPE = HandyWind.id
CP_CONVOY_SPAWN_TYPE = Armor.M1043_HMMWV_Armament.id
FOB_UNIT_TYPE = Unarmed.SKP_11.id
FARP_HELIPADS_TYPE = ["Invisible FARP", "SINGLE_HELIPAD"]
FARP_HELIPADS_TYPE = ["Invisible FARP", "SINGLE_HELIPAD", "FARP"]
OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id
SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id
@@ -95,6 +99,8 @@ class MizCampaignLoader:
STRIKE_TARGET_UNIT_TYPE = Fortification.Tech_combine.id
GROUND_SPAWN_WAYPOINT_DISTANCE = 1000
def __init__(self, miz: Path, theater: ConflictTheater) -> None:
self.theater = theater
self.mission = Mission()
@@ -223,6 +229,18 @@ class MizCampaignLoader:
if group.units[0].type in self.FARP_HELIPADS_TYPE:
yield group
@property
def ground_spawns_roadbase(self) -> Iterator[PlaneGroup]:
for group in itertools.chain(self.blue.plane_group, self.red.plane_group):
if group.units[0].type == self.GROUND_SPAWN_ROADBASE_UNIT_TYPE:
yield group
@property
def ground_spawns(self) -> Iterator[PlaneGroup]:
for group in itertools.chain(self.blue.plane_group, self.red.plane_group):
if group.units[0].type == self.GROUND_SPAWN_UNIT_TYPE:
yield group
@property
def factories(self) -> Iterator[StaticGroup]:
for group in self.blue.static_group:
@@ -317,6 +335,81 @@ class MizCampaignLoader:
if group.units[0].type in self.POWER_SOURCE_UNIT_TYPE:
yield group
@property
def cp_convoy_spawns(self) -> Iterator[VehicleGroup]:
for group in self.country(blue=True).vehicle_group:
if group.units[0].type == self.CP_CONVOY_SPAWN_TYPE:
yield group
def _construct_cp_spawnpoints(self, start: Point) -> Tuple[Point, ...]:
closest = self._find_closest_cp_spawn(start)
if closest:
return self._interpolate_points(closest, start)
return tuple()
@staticmethod
def _interpolate_points(closest: VehicleGroup, start: Point) -> Tuple[Point, ...]:
points = [start]
waypoints = points + [wpt.position for wpt in closest.points]
last = waypoints[0]
meters100ft = feet(100).meters
residual = 0.0
for wpt in waypoints[1:]:
dist = wpt.distance_to_point(last)
fraction = meters100ft / dist # 100ft separation
interpol_count = int((dist + residual) / meters100ft - 1)
offset = (meters100ft - residual) / dist
if offset <= 1:
points.append(last.lerp(wpt, offset))
if offset + fraction <= 1:
for i in range(1, interpol_count + 1):
points.append(last.lerp(wpt, i * fraction + offset))
residual = (residual + dist - meters100ft * interpol_count) % meters100ft
last = wpt
return tuple(points)
def _find_closest_cp_spawn(self, start: Point) -> Optional[VehicleGroup]:
closest: Optional[VehicleGroup] = None
for spawn in self.cp_convoy_spawns:
if closest is None:
closest = spawn
continue
dist = start.distance_to_point(closest.position)
if start.distance_to_point(spawn.position) < dist:
closest = spawn
return closest
@staticmethod
def _add_helipad(helipads: list[PointWithHeading], static: StaticGroup) -> None:
helipads.append(
PointWithHeading.from_point(
static.position, Heading.from_degrees(static.units[0].heading)
)
)
def _add_ground_spawn(
self,
ground_spawns: list[tuple[PointWithHeading, Point]],
plane_group: PlaneGroup,
) -> None:
if len(plane_group.points) >= 2:
first_waypoint = plane_group.points[1].position
else:
first_waypoint = plane_group.position.point_from_heading(
plane_group.units[0].heading,
self.GROUND_SPAWN_WAYPOINT_DISTANCE,
)
ground_spawns.append(
(
PointWithHeading.from_point(
plane_group.position,
Heading.from_degrees(plane_group.units[0].heading),
),
first_waypoint,
)
)
def add_supply_routes(self) -> None:
for group in self.front_line_path_groups:
# The unit will have its first waypoint at the source CP and the final
@@ -334,9 +427,14 @@ class MizCampaignLoader:
f"No control point near the final waypoint of {group.name}"
)
self.control_points[origin.id].create_convoy_route(destination, waypoints)
o_spawns = self._construct_cp_spawnpoints(waypoints[0])
d_spawns = self._construct_cp_spawnpoints(waypoints[-1])
self.control_points[origin.id].create_convoy_route(
destination, waypoints, o_spawns
)
self.control_points[destination.id].create_convoy_route(
origin, list(reversed(waypoints))
origin, list(reversed(waypoints)), d_spawns
)
def add_shipping_lanes(self) -> None:
@@ -425,7 +523,20 @@ class MizCampaignLoader:
for static in self.helipads:
closest, distance = self.objective_info(static)
closest.helipads.append(PresetLocation.from_group(static))
if static.units[0].type == "SINGLE_HELIPAD":
self._add_helipad(closest.helipads, static)
elif static.units[0].type == "FARP":
self._add_helipad(closest.helipads_quad, static)
else:
self._add_helipad(closest.helipads_invisible, static)
for plane_group in self.ground_spawns_roadbase:
closest, distance = self.objective_info(plane_group)
self._add_ground_spawn(closest.ground_spawns_roadbase, plane_group)
for plane_group in self.ground_spawns:
closest, distance = self.objective_info(plane_group)
self._add_ground_spawn(closest.ground_spawns, plane_group)
for static in self.factories:
closest, distance = self.objective_info(static)

View File

@@ -4,7 +4,6 @@ import itertools
import random
from typing import Optional, TYPE_CHECKING
from game.ato.ai_flight_planner_db import aircraft_for_task, tasks_for_aircraft
from game.ato.flighttype import FlightType
from game.dcs.aircrafttype import AircraftType
from game.squadrons.operatingbases import OperatingBases
@@ -25,7 +24,7 @@ class SquadronDefGenerator:
self, task: FlightType, control_point: ControlPoint
) -> Optional[SquadronDef]:
aircraft_choice: Optional[AircraftType] = None
for aircraft in aircraft_for_task(task):
for aircraft in AircraftType.priority_list_for_task(task):
if aircraft not in self.faction.aircrafts:
continue
if not control_point.can_operate(aircraft):
@@ -48,7 +47,7 @@ class SquadronDefGenerator:
role="Flying Squadron",
aircraft=aircraft,
livery=None,
mission_types=tuple(tasks_for_aircraft(aircraft)),
auto_assignable_mission_types=set(aircraft.iter_task_capabilities()),
operating_bases=OperatingBases.default_for_aircraft(aircraft),
female_pilot_percentage=6,
pilot_pool=[],

View File

@@ -70,14 +70,6 @@ class Coalition:
return 2
return 1
@property
def country_name(self) -> str:
return self.faction.country
@property
def country_shortname(self) -> str:
return self.faction.country_shortname
@property
def opponent(self) -> Coalition:
assert self._opponent is not None
@@ -161,14 +153,14 @@ class Coalition:
# is handled correctly.
self.transfers.perform_transfers()
def preinit_turn_0(self) -> None:
def preinit_turn_0(self, squadrons_start_full: bool) -> None:
"""Runs final Coalition initialization.
Final initialization occurs before Game.initialize_turn runs for turn 0.
"""
self.air_wing.populate_for_turn_0()
self.air_wing.populate_for_turn_0(squadrons_start_full)
def initialize_turn(self) -> None:
def initialize_turn(self, is_turn_0: bool) -> None:
"""Processes coalition-specific turn initialization.
For more information on turn initialization in general, see the documentation
@@ -187,7 +179,8 @@ class Coalition:
with logged_duration("Transport planning"):
self.transfers.plan_transports()
self.plan_missions()
if not is_turn_0 or not self.game.settings.enable_squadron_aircraft_limits:
self.plan_missions()
self.plan_procurement()
def refund_outstanding_orders(self) -> None:

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
import math
import operator
from collections.abc import Iterable, Iterator
from random import randint
from typing import TYPE_CHECKING, TypeVar
from game.ato.closestairfields import ClosestAirfields, ObjectiveDistanceCache
@@ -13,6 +14,7 @@ from game.theater import (
FrontLine,
MissionTarget,
OffMapSpawn,
ParkingType,
)
from game.theater.theatergroundobject import (
BuildingGroundObject,
@@ -32,10 +34,6 @@ MissionTargetType = TypeVar("MissionTargetType", bound=MissionTarget)
class ObjectiveFinder:
"""Identifies potential objectives for the mission planner."""
# TODO: Merge into doctrine.
AIRFIELD_THREAT_RANGE = nautical_miles(150)
SAM_THREAT_RANGE = nautical_miles(100)
def __init__(self, game: Game, is_player: bool) -> None:
self.game = game
self.is_player = is_player
@@ -150,9 +148,18 @@ class ObjectiveFinder:
# Off-map spawn locations don't need protection.
continue
airfields_in_proximity = self.closest_airfields_to(cp)
airbase_threat_range = self.game.settings.airbase_threat_range
if (
not self.is_player
and randint(1, 100)
> self.game.settings.opfor_autoplanner_aggressiveness
):
# Chance that the airfield threat range will be evaluated as zero,
# causing the OPFOR autoplanner to plan offensively
airbase_threat_range = 0
airfields_in_threat_range = (
airfields_in_proximity.operational_airfields_within(
self.AIRFIELD_THREAT_RANGE
nautical_miles(airbase_threat_range)
)
)
for airfield in airfields_in_threat_range:
@@ -161,11 +168,21 @@ class ObjectiveFinder:
break
def oca_targets(self, min_aircraft: int) -> Iterator[ControlPoint]:
parking_type = ParkingType()
parking_type.include_rotary_wing = True
parking_type.include_fixed_wing = True
parking_type.include_fixed_wing_stol = True
airfields = []
for control_point in self.enemy_control_points():
if not isinstance(control_point, Airfield):
if not isinstance(control_point, Airfield) and not isinstance(
control_point, Fob
):
continue
if control_point.allocated_aircraft().total_present >= min_aircraft:
if (
control_point.allocated_aircraft(parking_type).total_present
>= min_aircraft
):
airfields.append(control_point)
return self._targets_by_range(airfields)

View File

@@ -26,13 +26,11 @@ class PackageBuilder:
air_wing: AirWing,
flight_db: Database[Flight],
is_player: bool,
package_country: str,
start_type: StartType,
asap: bool,
) -> None:
self.closest_airfields = closest_airfields
self.is_player = is_player
self.package_country = package_country
self.package = Package(location, flight_db, auto_asap=asap)
self.air_wing = air_wing
self.start_type = start_type
@@ -56,7 +54,6 @@ class PackageBuilder:
flight = Flight(
self.package,
self.package_country,
squadron,
plan.num_aircraft,
plan.task,

View File

@@ -141,7 +141,6 @@ class PackageFulfiller:
self.air_wing,
self.flight_db,
self.is_player,
self.coalition.country_name,
self.default_start_type,
mission.asap,
)

View File

@@ -2,10 +2,10 @@ from __future__ import annotations
import itertools
import operator
import random
from abc import abstractmethod
from dataclasses import dataclass, field
from enum import IntEnum, auto, unique
import random
from typing import Generic, Iterator, Optional, TYPE_CHECKING, TypeVar, Union
from game.ato.flighttype import FlightType
@@ -112,6 +112,7 @@ class PackagePlanningTask(TheaterCommanderTask, Generic[MissionTargetT]):
def propose_common_escorts(self) -> None:
self.propose_flight(FlightType.SEAD_ESCORT, 2, EscortType.Sead)
self.propose_flight(FlightType.ESCORT, 2, EscortType.AirToAir)
self.propose_flight(FlightType.SEAD_SWEEP, 2, EscortType.Sead)
def iter_iads_ranges(
self, state: TheaterState, range_type: RangeType

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
@@ -33,3 +32,4 @@ class PlanCas(PackagePlanningTask[FrontLine]):
size = self.get_flight_size()
self.propose_flight(FlightType.CAS, size)
self.propose_flight(FlightType.TARCAP, 2)
self.propose_flight(FlightType.SEAD_SWEEP, 2)

View File

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

View File

@@ -1,7 +1,6 @@
from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
@@ -25,7 +24,10 @@ class PlanOcaStrike(PackagePlanningTask[ControlPoint]):
def propose_flights(self) -> None:
size = self.get_flight_size()
self.propose_flight(FlightType.OCA_RUNWAY, size)
if self.target.cptype == self.target.cptype.AIRBASE:
self.propose_flight(FlightType.OCA_RUNWAY, size)
if self.aircraft_cold_start:
self.propose_flight(FlightType.OCA_AIRCRAFT, 2)
self.propose_common_escorts()
if self.target.coalition.game.settings.autoplan_tankers_for_oca:
self.propose_flight(FlightType.REFUELING, 1)

View File

@@ -24,3 +24,5 @@ class PlanStrike(PackagePlanningTask[TheaterGroundObject]):
tgt_count = self.target.alive_unit_count
self.propose_flight(FlightType.STRIKE, min(4, (tgt_count // 2) + 1))
self.propose_common_escorts()
if self.target.coalition.game.settings.autoplan_tankers_for_strike:
self.propose_flight(FlightType.REFUELING, 1)

View File

@@ -173,7 +173,11 @@ class TheaterState(WorldState["TheaterState"]):
cp: BattlePositions.for_control_point(cp)
for cp in ordered_capturable_points
},
oca_targets=list(finder.oca_targets(min_aircraft=20)),
oca_targets=list(
finder.oca_targets(
min_aircraft=game.settings.oca_target_autoplanner_min_aircraft_count
)
),
strike_targets=list(finder.strike_targets()),
enemy_barcaps=list(game.theater.control_points_for(not player)),
threat_zones=game.threat_zone_for(not player),

View File

@@ -31,6 +31,17 @@ def weapons_migrator(name: str) -> str:
return name
def weapons_migrator_lib(name: str) -> str:
# Splitting this from our own migrations
if "KH" in name:
return "Kh" + name[2:]
# UNCOMMENT BELOW WHEN IT BECOMES APPLICABLE
# migration_map = {}
# while name in migration_map:
# name = migration_map[name]
return name
@dataclass(frozen=True)
class Weapon:
"""Wrapper for DCS weapons."""
@@ -110,6 +121,8 @@ class WeaponType(Enum):
ARM = "ARM"
LGB = "LGB"
TGP = "TGP"
DECOY = "DECOY"
JAMMER = "JAMMER"
UNKNOWN = "unknown"
@@ -151,7 +164,9 @@ class WeaponGroup:
def __setstate__(self, state: dict[str, Any]) -> None:
# Update any existing models with new data on load.
updated = WeaponGroup.named(weapons_migrator(state["name"]))
name = weapons_migrator(state["name"])
name = weapons_migrator_lib(name)
updated = WeaponGroup.named(name)
state.update(updated.__dict__)
self.__dict__.update(state)

View File

@@ -1,10 +1,11 @@
from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from functools import cached_property
from functools import cache, cached_property
from pathlib import Path
from typing import Any, Dict, Iterator, Optional, TYPE_CHECKING, Type
from typing import Any, ClassVar, Dict, Iterator, Optional, TYPE_CHECKING, Type
import yaml
from dcs.helicopters import helicopter_map
@@ -20,6 +21,7 @@ from game.radio.channels import (
CommonRadioChannelAllocator,
FarmerRadioChannelAllocator,
HueyChannelNamer,
LegacyWarthogChannelNamer,
MirageChannelNamer,
MirageF1CEChannelNamer,
NoOpChannelAllocator,
@@ -31,6 +33,7 @@ from game.radio.channels import (
ViggenChannelNamer,
ViggenRadioChannelAllocator,
ViperChannelNamer,
WarthogChannelNamer,
)
from game.utils import (
Distance,
@@ -47,6 +50,7 @@ from game.utils import (
)
if TYPE_CHECKING:
from game.ato import FlightType
from game.missiongenerator.aircraft.flightdata import FlightData
from game.missiongenerator.missiondata import MissionData
from game.radio.radios import Radio, RadioFrequency, RadioRegistry
@@ -104,6 +108,8 @@ class RadioConfig:
"viggen": ViggenChannelNamer,
"viper": ViperChannelNamer,
"apache": ApacheChannelNamer,
"a10c-legacy": LegacyWarthogChannelNamer,
"a10c-ii": WarthogChannelNamer,
}[config.get("namer", "default")]
@@ -192,6 +198,23 @@ class AircraftType(UnitType[Type[FlyingType]]):
# will be set to true for helos by default
can_carry_crates: bool
# Set to True when aircraft mounts a targeting pod by default i.e. the pod does
# not take up a weapons station. If True, do not replace LGBs with dumb bombs
# when no TGP is mounted on any station.
has_built_in_target_pod: bool
task_priorities: dict[FlightType, int]
_by_name: ClassVar[dict[str, AircraftType]] = {}
_by_unit_type: ClassVar[dict[type[FlyingType], list[AircraftType]]] = defaultdict(
list
)
@classmethod
def register(cls, unit_type: AircraftType) -> None:
cls._by_name[unit_type.name] = unit_type
cls._by_unit_type[unit_type.dcs_unit_type].append(unit_type)
@property
def flyable(self) -> bool:
return self.dcs_unit_type.flyable
@@ -200,6 +223,10 @@ class AircraftType(UnitType[Type[FlyingType]]):
def helicopter(self) -> bool:
return self.dcs_unit_type.helicopter
@property
def max_fuel(self) -> float:
return self.dcs_unit_type.fuel_max
@cached_property
def max_speed(self) -> Speed:
return kph(self.dcs_unit_type.max_speed)
@@ -302,6 +329,12 @@ class AircraftType(UnitType[Type[FlyingType]]):
def iter_props(self) -> Iterator[UnitProperty[Any]]:
return UnitProperty.for_aircraft(self.dcs_unit_type)
def capable_of(self, task: FlightType) -> bool:
return task in self.task_priorities
def task_priority(self, task: FlightType) -> int:
return self.task_priorities[task]
def __setstate__(self, state: dict[str, Any]) -> None:
# Update any existing models with new data on load.
updated = AircraftType.named(state["name"])
@@ -312,17 +345,31 @@ class AircraftType(UnitType[Type[FlyingType]]):
def named(cls, name: str) -> AircraftType:
if not cls._loaded:
cls._load_all()
unit = cls._by_name[name]
assert isinstance(unit, AircraftType)
return unit
return cls._by_name[name]
@classmethod
def for_dcs_type(cls, dcs_unit_type: Type[FlyingType]) -> Iterator[AircraftType]:
if not cls._loaded:
cls._load_all()
for unit in cls._by_unit_type[dcs_unit_type]:
assert isinstance(unit, AircraftType)
yield unit
yield from cls._by_unit_type[dcs_unit_type]
@classmethod
def iter_all(cls) -> Iterator[AircraftType]:
if not cls._loaded:
cls._load_all()
yield from cls._by_name.values()
@classmethod
@cache
def priority_list_for_task(cls, task: FlightType) -> list[AircraftType]:
capable = []
for aircraft in cls.iter_all():
if aircraft.capable_of(task):
capable.append(aircraft)
return list(reversed(sorted(capable, key=lambda a: a.task_priority(task))))
def iter_task_capabilities(self) -> Iterator[FlightType]:
yield from self.task_priorities
@staticmethod
def each_dcs_type() -> Iterator[Type[FlyingType]]:
@@ -406,6 +453,15 @@ class AircraftType(UnitType[Type[FlyingType]]):
if prop_overrides is not None:
cls._set_props_overrides(prop_overrides, aircraft, data_path)
from game.ato.flighttype import FlightType
task_priorities: dict[FlightType, int] = {}
for task_name, priority in data.get("tasks", {}).items():
task_priorities[FlightType(task_name)] = priority
if FlightType.SEAD in task_priorities:
task_priorities[FlightType.SEAD_SWEEP] = task_priorities[FlightType.SEAD]
for variant in data.get("variants", [aircraft.id]):
yield AircraftType(
dcs_unit_type=aircraft,
@@ -437,4 +493,9 @@ class AircraftType(UnitType[Type[FlyingType]]):
unit_class=unit_class,
cabin_size=data.get("cabin_size", 10 if aircraft.helicopter else 0),
can_carry_crates=data.get("can_carry_crates", aircraft.helicopter),
has_built_in_target_pod=data.get("has_built_in_target_pod", False),
task_priorities=task_priorities,
)
def __hash__(self) -> int:
return hash(self.name)

9
game/dcs/countries.py Normal file
View File

@@ -0,0 +1,9 @@
from dcs.countries import country_dict
from dcs.country import Country
def country_with_name(name: str) -> Country:
for country in country_dict.values():
if country.name == name:
return country()
raise KeyError(f"No country found named {name}")

View File

@@ -1,9 +1,10 @@
from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterator, Optional, Type
from typing import Any, ClassVar, Iterator, Optional, Type
import yaml
from dcs.unittype import VehicleType
@@ -59,21 +60,27 @@ class GroundUnitType(UnitType[Type[VehicleType]]):
# Some units like few Launchers have to be placed backwards to be able to fire.
reversed_heading: bool = False
_by_name: ClassVar[dict[str, GroundUnitType]] = {}
_by_unit_type: ClassVar[
dict[type[VehicleType], list[GroundUnitType]]
] = defaultdict(list)
@classmethod
def register(cls, unit_type: GroundUnitType) -> None:
cls._by_name[unit_type.name] = unit_type
cls._by_unit_type[unit_type.dcs_unit_type].append(unit_type)
@classmethod
def named(cls, name: str) -> GroundUnitType:
if not cls._loaded:
cls._load_all()
unit = cls._by_name[name]
assert isinstance(unit, GroundUnitType)
return unit
return cls._by_name[name]
@classmethod
def for_dcs_type(cls, dcs_unit_type: Type[VehicleType]) -> Iterator[GroundUnitType]:
if not cls._loaded:
cls._load_all()
for unit in cls._by_unit_type[dcs_unit_type]:
assert isinstance(unit, GroundUnitType)
yield unit
yield from cls._by_unit_type[dcs_unit_type]
@staticmethod
def each_dcs_type() -> Iterator[Type[VehicleType]]:

View File

@@ -1,9 +1,10 @@
from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from pathlib import Path
from typing import Iterator, Type
from typing import ClassVar, Iterator, Type
import yaml
from dcs.ships import ship_map
@@ -15,21 +16,27 @@ from game.dcs.unittype import UnitType
@dataclass(frozen=True)
class ShipUnitType(UnitType[Type[ShipType]]):
_by_name: ClassVar[dict[str, ShipUnitType]] = {}
_by_unit_type: ClassVar[dict[type[ShipType], list[ShipUnitType]]] = defaultdict(
list
)
@classmethod
def register(cls, unit_type: ShipUnitType) -> None:
cls._by_name[unit_type.name] = unit_type
cls._by_unit_type[unit_type.dcs_unit_type].append(unit_type)
@classmethod
def named(cls, name: str) -> ShipUnitType:
if not cls._loaded:
cls._load_all()
unit = cls._by_name[name]
assert isinstance(unit, ShipUnitType)
return unit
return cls._by_name[name]
@classmethod
def for_dcs_type(cls, dcs_unit_type: Type[ShipType]) -> Iterator[ShipUnitType]:
if not cls._loaded:
cls._load_all()
for unit in cls._by_unit_type[dcs_unit_type]:
assert isinstance(unit, ShipUnitType)
yield unit
yield from cls._by_unit_type[dcs_unit_type]
@staticmethod
def each_dcs_type() -> Iterator[Type[ShipType]]:

View File

@@ -1,10 +1,10 @@
from __future__ import annotations
from abc import ABC
from collections import defaultdict
from dataclasses import dataclass
from functools import cached_property
from typing import Any, ClassVar, Generic, Iterator, Type, TypeVar
from typing import ClassVar, Generic, Iterator, Type, TypeVar
from typing_extensions import Self
from dcs.unittype import UnitType as DcsUnitType
@@ -25,10 +25,6 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
price: int
unit_class: UnitClass
_by_name: ClassVar[dict[str, UnitType[Any]]] = {}
_by_unit_type: ClassVar[dict[Type[DcsUnitType], list[UnitType[Any]]]] = defaultdict(
list
)
_loaded: ClassVar[bool] = False
def __str__(self) -> str:
@@ -39,16 +35,15 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
return self.dcs_unit_type.id
@classmethod
def register(cls, unit_type: UnitType[Any]) -> None:
cls._by_name[unit_type.name] = unit_type
cls._by_unit_type[unit_type.dcs_unit_type].append(unit_type)
@classmethod
def named(cls, name: str) -> UnitType[Any]:
def register(cls, unit_type: Self) -> None:
raise NotImplementedError
@classmethod
def for_dcs_type(cls, dcs_unit_type: DcsUnitTypeT) -> Iterator[UnitType[Any]]:
def named(cls, name: str) -> Self:
raise NotImplementedError
@classmethod
def for_dcs_type(cls, dcs_unit_type: DcsUnitTypeT) -> Iterator[Self]:
raise NotImplementedError
@staticmethod
@@ -56,7 +51,7 @@ class UnitType(ABC, Generic[DcsUnitTypeT]):
raise NotImplementedError
@classmethod
def _each_variant_of(cls, unit: DcsUnitTypeT) -> Iterator[UnitType[Any]]:
def _each_variant_of(cls, unit: DcsUnitTypeT) -> Iterator[Self]:
raise NotImplementedError
@classmethod

View File

@@ -109,16 +109,41 @@ class StateData:
base_capture_events: List[str]
@classmethod
def from_json(cls, data: Dict[str, Any]) -> StateData:
def from_json(cls, data: Dict[str, Any], unit_map: UnitMap) -> StateData:
def clean_unit_list(unit_list: List[Any]) -> List[str]:
# Cleans list of units in state.json by
# - Removing duplicates. Airfields emit a new "dead" event every time a bomb
# is dropped on them when they've already dead.
# - Normalise dead map objects (which are ints) to strings. The unit map
# only stores strings
units = set()
for unit in unit_list:
units.add(str(unit))
return list(units)
killed_aircraft = []
killed_ground_units = []
# Process killed units from S_EVENT_UNIT_LOST, S_EVENT_CRASH, S_EVENT_DEAD & S_EVENT_KILL
# Try to process every event that could indicate a unit was killed, even if it is
# inefficient and results in duplication as the logic DCS uses to trigger the various
# event types is not clear and may change over time.
killed_units = clean_unit_list(
data["unit_lost_events"]
+ data["kill_events"]
+ data["crash_events"]
+ data["dead_events"]
)
for unit in killed_units: # organize killed units into aircraft vs ground
if unit_map.flight(unit) is not None:
killed_aircraft.append(unit)
else:
killed_ground_units.append(unit)
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.
#
# Also normalize dead map objects (which are ints) to strings. The unit map
# only stores strings.
killed_ground_units=list({str(u) for u in data["killed_ground_units"]}),
killed_aircraft=killed_aircraft,
killed_ground_units=killed_ground_units,
destroyed_statics=data["destroyed_objects_positions"],
base_capture_events=data["base_capture_events"],
)
@@ -128,12 +153,12 @@ 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.state_data = StateData.from_json(state_data, unit_map)
self.game = game
self.unit_map = unit_map
self.player_country = game.blue.country_name
self.enemy_country = game.red.country_name
self.player_country = game.blue.faction.country.name
self.enemy_country = game.red.faction.country.name
self.air_losses = self.dead_aircraft()
self.ground_losses = self.dead_ground_units()

View File

@@ -4,12 +4,11 @@ import itertools
import logging
from dataclasses import dataclass, field
from functools import cached_property
from typing import Optional, Dict, Type, List, Any, Iterator, TYPE_CHECKING
from typing import Optional, Dict, Type, List, Any, Iterator, TYPE_CHECKING, Set
import dcs
from dcs.countries import country_dict
from dcs.unittype import ShipType, StaticType
from dcs.unittype import UnitType as DcsUnitType
from dcs.country import Country
from dcs.unittype import ShipType, StaticType, UnitType as DcsUnitType
from game.armedforces.forcegroup import ForceGroup
from game.data.building_data import (
@@ -29,10 +28,12 @@ from game.data.doctrine import (
from game.data.groups import GroupRole
from game.data.units import UnitClass
from game.dcs.aircrafttype import AircraftType
from game.dcs.countries import country_with_name
from game.dcs.groundunittype import GroundUnitType
from game.dcs.shipunittype import ShipUnitType
from game.dcs.unittype import UnitType
from pydcs_extensions.f16i_idf.f16i_idf import inject_F16I
from pydcs_extensions import inject_F15I, eject_F15I
from pydcs_extensions.f16i_idf.f16i_idf import inject_F16I, eject_F16I
if TYPE_CHECKING:
from game.theater.start_generator import ModSettings
@@ -45,10 +46,7 @@ class Faction:
locales: Optional[List[str]]
# Country used by this faction
country: str = field(default="")
# Country's short name used by this faction
country_shortname: str = field(default="")
country: Country
# Nice name of the faction
name: str = field(default="")
@@ -60,46 +58,46 @@ class Faction:
description: str = field(default="")
# Available aircraft
aircraft: List[AircraftType] = field(default_factory=list)
aircraft: Set[AircraftType] = field(default_factory=set)
# Available awacs aircraft
awacs: List[AircraftType] = field(default_factory=list)
awacs: Set[AircraftType] = field(default_factory=set)
# Available tanker aircraft
tankers: List[AircraftType] = field(default_factory=list)
tankers: Set[AircraftType] = field(default_factory=set)
# Available frontline units
frontline_units: List[GroundUnitType] = field(default_factory=list)
frontline_units: Set[GroundUnitType] = field(default_factory=set)
# Available artillery units
artillery_units: List[GroundUnitType] = field(default_factory=list)
artillery_units: Set[GroundUnitType] = field(default_factory=set)
# Infantry units used
infantry_units: List[GroundUnitType] = field(default_factory=list)
infantry_units: Set[GroundUnitType] = field(default_factory=set)
# Logistics units used
logistics_units: List[GroundUnitType] = field(default_factory=list)
logistics_units: Set[GroundUnitType] = field(default_factory=set)
# Possible Air Defence units, Like EWRs
air_defense_units: List[GroundUnitType] = field(default_factory=list)
air_defense_units: Set[GroundUnitType] = field(default_factory=set)
# A list of all supported sets of units
preset_groups: list[ForceGroup] = field(default_factory=list)
# Possible Missile site generators for this faction
missiles: List[GroundUnitType] = field(default_factory=list)
missiles: Set[GroundUnitType] = field(default_factory=set)
# Required mods or asset packs
requirements: Dict[str, str] = field(default_factory=dict)
# Possible carrier names
carrier_names: List[str] = field(default_factory=list)
carrier_names: Set[str] = field(default_factory=set)
# Possible helicopter carrier names
helicopter_carrier_names: List[str] = field(default_factory=list)
helicopter_carrier_names: Set[str] = field(default_factory=set)
# Available Naval Units
naval_units: List[ShipUnitType] = field(default_factory=list)
naval_units: Set[ShipUnitType] = field(default_factory=set)
# Whether this faction has JTAC access
has_jtac: bool = field(default=False)
@@ -111,7 +109,7 @@ class Faction:
doctrine: Doctrine = field(default=MODERN_DOCTRINE)
# List of available building layouts for this faction
building_set: List[str] = field(default_factory=list)
building_set: Set[str] = field(default_factory=set)
# List of default livery overrides
liveries_overrides: Dict[AircraftType, List[str]] = field(default_factory=dict)
@@ -180,28 +178,23 @@ class Faction:
@cached_property
def aircrafts(self) -> list[UnitType[Any]]:
return list(self.aircraft + self.awacs + self.tankers)
# Migrator can't cope with this, so we need to do it here...
self.aircraft = set(self.aircraft)
self.awacs = set(self.awacs)
self.tankers = set(self.tankers)
return list(self.aircraft.union(self.awacs.union(self.tankers)))
@classmethod
def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
faction = Faction(locales=json.get("locales"))
def from_dict(cls: Type[Faction], json: Dict[str, Any]) -> Faction:
try:
country = country_with_name(json["country"])
except KeyError as ex:
raise KeyError(
f'Faction\'s country ("{json.get("country")}") is not a valid DCS '
"country ID"
) from ex
faction.country = json.get("country", "/")
country = None
for c in country_dict.values():
if c.name == faction.country:
country = c
break
if country is None:
raise AssertionError(
'Faction\'s country ("{}") is not a valid DCS country ID'.format(
faction.country
)
)
faction.country_shortname = country.shortname
faction = Faction(locales=json.get("locales"), country=country)
faction.name = json.get("name", "")
if not faction.name:
@@ -210,30 +203,30 @@ class Faction:
faction.authors = json.get("authors", "")
faction.description = json.get("description", "")
faction.aircraft = [AircraftType.named(n) for n in json.get("aircrafts", [])]
faction.awacs = [AircraftType.named(n) for n in json.get("awacs", [])]
faction.tankers = [AircraftType.named(n) for n in json.get("tankers", [])]
faction.aircraft = {AircraftType.named(n) for n in json.get("aircrafts", [])}
faction.awacs = {AircraftType.named(n) for n in json.get("awacs", [])}
faction.tankers = {AircraftType.named(n) for n in json.get("tankers", [])}
faction.frontline_units = [
faction.frontline_units = {
GroundUnitType.named(n) for n in json.get("frontline_units", [])
]
faction.artillery_units = [
}
faction.artillery_units = {
GroundUnitType.named(n) for n in json.get("artillery_units", [])
]
faction.infantry_units = [
}
faction.infantry_units = {
GroundUnitType.named(n) for n in json.get("infantry_units", [])
]
faction.logistics_units = [
}
faction.logistics_units = {
GroundUnitType.named(n) for n in json.get("logistics_units", [])
]
faction.air_defense_units = [
}
faction.air_defense_units = {
GroundUnitType.named(n) for n in json.get("air_defense_units", [])
]
faction.missiles = [GroundUnitType.named(n) for n in json.get("missiles", [])]
}
faction.missiles = {GroundUnitType.named(n) for n in json.get("missiles", [])}
faction.naval_units = [
faction.naval_units = {
ShipUnitType.named(n) for n in json.get("naval_units", [])
]
}
faction.preset_groups = [
ForceGroup.from_preset_group(g) for g in json.get("preset_groups", [])
@@ -263,22 +256,22 @@ class Faction:
faction.doctrine = MODERN_DOCTRINE
# Load the building set
faction.building_set = []
faction.building_set = set()
building_set = json.get("building_set", "default")
if building_set == "default":
faction.building_set.extend(DEFAULT_AVAILABLE_BUILDINGS)
faction.building_set.update(DEFAULT_AVAILABLE_BUILDINGS)
elif building_set == "ww2free":
faction.building_set.extend(WW2_FREE)
faction.building_set.update(WW2_FREE)
elif building_set == "ww2ally":
faction.building_set.extend(WW2_ALLIES_BUILDINGS)
faction.building_set.update(WW2_ALLIES_BUILDINGS)
elif building_set == "ww2germany":
faction.building_set.extend(WW2_GERMANY_BUILDINGS)
faction.building_set.update(WW2_GERMANY_BUILDINGS)
else:
faction.building_set.extend(DEFAULT_AVAILABLE_BUILDINGS)
faction.building_set.update(DEFAULT_AVAILABLE_BUILDINGS)
# Add required buildings for the game logic (e.g. ammo, factory..)
faction.building_set.extend(REQUIRED_BUILDINGS)
faction.building_set.extend(IADS_BUILDINGS)
faction.building_set.update(REQUIRED_BUILDINGS)
faction.building_set.update(IADS_BUILDINGS)
# Load liveries override
faction.liveries_overrides = {}
@@ -340,14 +333,23 @@ class Faction:
self.remove_aircraft("VSN_F4C")
if not mod_settings.f15d_baz:
self.remove_aircraft("F-15D")
if not mod_settings.f_15_idf:
eject_F15I()
if AircraftType.named("F-15I Ra'am") in self.aircraft:
self.aircraft.remove(AircraftType.named("F-15I Ra'am"))
else:
inject_F15I()
if AircraftType.named("F-15E Strike Eagle (Suite 4+)") in self.aircraft:
self.aircraft.add(AircraftType.named("F-15I Ra'am"))
if not mod_settings.f_16_idf:
self.remove_aircraft("F-16I")
self.remove_aircraft("F_16D_52")
self.remove_aircraft("F_16D_50")
self.remove_aircraft("F_16D_50_NS")
self.remove_aircraft("F_16D_52_NS")
self.remove_aircraft("F-16D_52")
self.remove_aircraft("F-16D_50")
self.remove_aircraft("F-16D_50_NS")
self.remove_aircraft("F-16D_52_NS")
self.remove_aircraft("F-16D_Barak_30")
self.remove_aircraft("F-16D_Barak_40")
eject_F16I()
else:
inject_F16I()
if not mod_settings.f22_raptor:
@@ -368,6 +370,7 @@ class Faction:
self.remove_aircraft("VSN_A6A")
if not mod_settings.jas39_gripen:
self.remove_aircraft("JAS39Gripen")
self.remove_aircraft("JAS39Gripen_BVR")
self.remove_aircraft("JAS39Gripen_AG")
if not mod_settings.su30_flanker_h:
self.remove_aircraft("Su-30MKA")
@@ -378,6 +381,8 @@ class Faction:
self.remove_aircraft("Su-57")
if not mod_settings.ov10a_bronco:
self.remove_aircraft("Bronco-OV-10A")
if not mod_settings.a7e_corsair2:
self.remove_aircraft("A-7E")
# frenchpack
if not mod_settings.frenchpack:
self.remove_vehicle("AMX10RCR")
@@ -435,6 +440,22 @@ class Faction:
self.remove_aircraft("FA-18E")
self.remove_aircraft("FA-18F")
self.remove_aircraft("EA-18G")
# spanish naval assets pack
if not mod_settings.spanishnavypack:
self.remove_ship("L61")
self.remove_ship("F100")
self.remove_ship("F105")
self.remove_ship("L52")
self.remove_ship("L02")
self.remove_ship("DDG39")
if not mod_settings.irondome:
self.remove_vehicle("I9K51_GRAD")
self.remove_vehicle("I9K57_URAGAN")
self.remove_vehicle("I9K58_SMERCH")
self.remove_vehicle("IRON_DOME_CP")
self.remove_vehicle("IRON_DOME_LN")
self.remove_vehicle("ELM2048_MMR")
self.remove_preset("Iron Dome")
# swedish military assets pack
if not mod_settings.swedishmilitaryassetspack:
self.remove_vehicle("BV410_RBS70")
@@ -474,14 +495,45 @@ class Faction:
self.remove_ship("HSwMS_Visby")
self.remove_ship("Strb90")
self.remove_aircraft("HKP15B")
# SWPack
if not mod_settings.SWPack:
self.remove_aircraft("AWINGA")
self.remove_aircraft("AWING")
self.remove_aircraft("XWING")
self.remove_aircraft("XWINGAI")
self.remove_aircraft("TIE_BA")
self.remove_aircraft("tie_bomber_2")
self.remove_aircraft("YWINGA")
self.remove_aircraft("YWING")
self.remove_aircraft("CORVETTE")
self.remove_aircraft("CORVETTEA")
self.remove_aircraft("FAUCON")
self.remove_aircraft("FAUCON_AI")
self.remove_aircraft("TIE")
self.remove_aircraft("TIE_AI")
self.remove_aircraft("HUNTER")
self.remove_aircraft("HUNTERA")
self.remove_aircraft("TIE_INTER")
self.remove_aircraft("TIE_INTERA")
self.remove_aircraft("naboo_starfighter")
self.remove_aircraft("naboo_starfighter_AI")
self.remove_vehicle("MBT9_REBEL")
self.remove_vehicle("MBT9_AAA EMPIRE")
self.remove_vehicle("MBT9_EMPIRE")
self.remove_vehicle("MBT9_AAA REBEL")
self.remove_vehicle("Jugger")
self.remove_vehicle("TB_TT")
self.remove_vehicle("TR_TT")
self.remove_vehicle("Gozanti")
self.remove_ship("Destroyer_carrier")
def remove_aircraft(self, name: str) -> None:
for i in self.aircrafts:
for i in list(self.aircrafts):
if i.dcs_unit_type.id == name:
self.aircrafts.remove(i)
def remove_aircraft_by_name(self, name: str) -> None:
for i in self.aircrafts:
for i in list(self.aircrafts):
if i.name == name:
self.aircrafts.remove(i)
@@ -491,12 +543,19 @@ class Faction:
self.preset_groups.remove(pg)
def remove_vehicle(self, name: str) -> None:
for i in self.frontline_units:
if i.dcs_unit_type.id == name:
self.frontline_units.remove(i)
for sequence in [
self.frontline_units,
self.infantry_units,
self.air_defense_units,
self.artillery_units,
self.logistics_units,
]:
for i in list(sequence):
if i.dcs_unit_type.id == name:
sequence.remove(i)
def remove_ship(self, name: str) -> None:
for i in self.naval_units:
for i in list(self.naval_units):
if i.dcs_unit_type.id == name:
self.naval_units.remove(i)

View File

@@ -31,7 +31,7 @@ class FactionLoader:
@classmethod
def load_factions(cls: Type[FactionLoader]) -> Dict[str, Faction]:
user_faction_path = Path(persistency.base_path()) / "Retribution/Factions"
user_faction_path = persistency.base_path() / "Retribution/Factions"
files = cls.find_faction_files_in(
FACTION_DIRECTORY
) + cls.find_faction_files_in(user_faction_path)
@@ -41,7 +41,7 @@ class FactionLoader:
try:
with f.open("r", encoding="utf-8") as fdata:
data = json.load(fdata)
factions[data["name"]] = Faction.from_json(data)
factions[data["name"]] = Faction.from_dict(data)
logging.info("Loaded faction : " + str(f))
except Exception:
logging.exception(f"Unable to load faction : {f}")

View File

@@ -26,6 +26,7 @@ from .ato.flighttype import FlightType
from .campaignloader import CampaignAirWingConfig
from .coalition import Coalition
from .db.gamedb import GameDb
from .dcs.countries import country_with_name
from .infos.information import Information
from .profiling import logged_duration
from .settings import Settings
@@ -38,7 +39,7 @@ from .theater.theatergroundobject import (
)
from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder
from .timeofday import TimeOfDay
from .weather import Conditions
from .weather.conditions import Conditions
if TYPE_CHECKING:
from .ato.airtaaskingorder import AirTaskingOrder
@@ -89,6 +90,8 @@ class TurnState(Enum):
class Game:
scenery_clear_zones: List[Point]
def __init__(
self,
player_faction: Faction,
@@ -179,13 +182,15 @@ class Game:
Make sure the opposing factions are using different countries
:return:
"""
# TODO: This should just be rejected and sent back to the user to fix.
# This isn't always something that the original faction can support.
if player_faction.country == enemy_faction.country:
if player_faction.country == "USA":
enemy_faction.country = "USAF Aggressors"
elif player_faction.country == "Russia":
enemy_faction.country = "USSR"
if player_faction.country.name == "USA":
enemy_faction.country = country_with_name("USAF Aggressors")
elif player_faction.country.name == "Russia":
enemy_faction.country = country_with_name("USSR")
else:
enemy_faction.country = "Russia"
enemy_faction.country = country_with_name("Russia")
def faction_for(self, player: bool) -> Faction:
return self.coalition_for(player).faction
@@ -196,13 +201,10 @@ class Game:
def air_wing_for(self, player: bool) -> AirWing:
return self.coalition_for(player).air_wing
def country_for(self, player: bool) -> str:
return self.coalition_for(player).country_name
@property
def neutral_country(self) -> Type[Country]:
"""Return the best fitting country that can be used as neutral faction in the generated mission"""
countries_in_use = [self.red.country_name, self.blue.country_name]
countries_in_use = {self.red.faction.country, self.blue.faction.country}
if UnitedNationsPeacekeepers not in countries_in_use:
return UnitedNationsPeacekeepers
elif Switzerland.name not in countries_in_use:
@@ -294,7 +296,7 @@ class Game:
if self.turn > 1:
self.conditions = self.generate_conditions()
def begin_turn_0(self) -> None:
def begin_turn_0(self, squadrons_start_full: bool) -> None:
"""Initialization for the first turn of the game."""
from .sim import GameUpdateEvents
@@ -319,8 +321,9 @@ class Game:
# Rotate the whole TGO with the new heading
tgo.rotate(heading or tgo.heading)
self.blue.preinit_turn_0()
self.red.preinit_turn_0()
self.blue.preinit_turn_0(squadrons_start_full)
self.red.preinit_turn_0(squadrons_start_full)
# TODO: Check for overfull bases.
# We don't need to actually stream events for turn zero because we haven't given
# *any* state to the UI yet, so it will need to do a full draw once we do.
self.initialize_turn(GameUpdateEvents())
@@ -351,16 +354,10 @@ class Game:
persistency.autosave(self)
def check_win_loss(self) -> TurnState:
player_airbases = {
cp for cp in self.theater.player_points() if cp.runway_is_operational()
}
if not player_airbases:
if not self.theater.player_points():
return TurnState.LOSS
enemy_airbases = {
cp for cp in self.theater.enemy_points() if cp.runway_is_operational()
}
if not enemy_airbases:
if not self.theater.enemy_points():
return TurnState.WIN
return TurnState.CONTINUE
@@ -371,7 +368,10 @@ class Game:
self.red.bullseye = Bullseye(player_cp.position)
def initialize_turn(
self, events: GameUpdateEvents, for_red: bool = True, for_blue: bool = True
self,
events: GameUpdateEvents,
for_red: bool = True,
for_blue: bool = True,
) -> None:
"""Performs turn initialization for the specified players.
@@ -424,9 +424,9 @@ class Game:
# Plan Coalition specific turn
if for_blue:
self.blue.initialize_turn()
self.blue.initialize_turn(self.turn == 0)
if for_red:
self.red.initialize_turn()
self.red.initialize_turn(self.turn == 0)
# Plan GroundWar
self.ground_planners = {}

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