197 Commits

Author SHA1 Message Date
holyorangejuice
782759bd48 WRL mission yaml updates (#47)
- Removed/changed some units from WRL missions.
- Minor changes across all missions.
- Added frontline length recommendations to each mission yaml.
2022-12-23 12:54:56 +01:00
Raffson
653e507e89 Set "RTB on bingo" false for ferry flights 2022-12-20 20:56:30 +01:00
Raffson
4f9c7a7370 Adjust issue templates 2022-12-20 20:46:33 +01:00
Raffson
c114256b82 Renaming Liberation
Resolves #42
2022-12-20 20:46:32 +01:00
MetalStormGhost
cdd72820b4 A-4E-C mod support v2.1.0 (#43)
* Updated a4ec.py to Community A-4E-C mod version 2.1.0

* Updated changelog.

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

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

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

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

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

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

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

Making this change uncovered some latent type errors.

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

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

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

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

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

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

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

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

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

Note increased wind speeds in the changelog.

Limit wind speed to 97 knots.

Made minor adjustments to wind speed calculation.

Calculate turbulance.

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

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

- rename Stopover back to CargoStop
- precise some waypoint naming

Cleanup and refine airlift and airassault waypoints

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

Forbid planes from air assault.

Make air assault drop-off non-optional.

There is always a drop-off location for troops.

Add docs explaining what the assault area is.

Add error handling for cargo drop waypoints.

Document some airlift waypoint behavior.

Remove unnecessary refuel waypoint in airlifts.

Remove CTLD logic from Airlift flightplan for AI

Add Ingress Point to AirAssault FlightPlan

Add simulation halt at AirAssault ingress

Remove AirAssault completly from AutoPlanner

Remove unneeded LandRefuel from Airlift

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

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

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

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

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

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

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

Adds recommended factions for the given campaigns

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,57 +1,119 @@
# Retribution 1.0.0
## Features/Improvements
* **[Engine]** Support for DCS v2.8.1.34437.
* **[Briefing]** Add tanker info to mission briefing
* **[Campaign]** Add 5 new campaigns by Oscar Juliet from WRL
* **[Campaign]** Add ability to define livery overrides also for ground/naval units
* **[Data]** Added data to support C-47 Skytrain.
* **[Data]** Added data to support F-16A MLU.
* **[Data]** Added data to support KS-19 & SON-9, including support for "AAA Site" layout.
* **[Mission Generation]** Add option to configure the maximum front-line length in settings
* **[Mission Generation]** Variable flight-size (2/3/4-ship) for BAI/ANTISHIP/DEAD/STRIKE (main) missions
* **[Mission Generation]** Use Escort & SEAD tasks for Escort & SEAD Escort flights
* **[Mission Generation]** Variable flight-size (2/3/4-ship) for
BAI/ANTISHIP/DEAD/STRIKE/BARCAP/CAS/OCA/AIR-ASSAULT (main) missions
* **[Mission Generation]** Add option to only generate night missions
* **[Modding]** Support for F-15D 'Baz' mod version 1.0
* **[Modding]** Support for Su-30 mod version 2.01B
* **[Modding]** Support for A-6A Intruder version 2.7.5.01
* **[Modding]** Support for F-4B Phantom II mod version v2.7.10.02, patch 2022.10.02
* **[Modding]** Support for F-100 Super Sabre mod versions v2.7.18.01 & 2.7.18.30765 and patch 20.10.22
* **[Modding]** Support for F-105 mod version 2.7.12.23x
* **[Modding]** Support IDF Mod Project F-16I Sufa & F-16D v2.2 mod
* **[Modding]** Support for F-84G mod version 2.5.7.01
* **[Modding]** Updated F-104 mod version support to 2.7.11.222.01
* **[Modding]** Updated Community A-4E-C mod version support to 2.1.0 release.
* **[UI]** Add livery selector to Air Wing Configurator's squadrons.
* **[Performance]** Added performance option: Maximum front-line unit supply per control point.
* **[Performance]** Added performance option: Disable convoys.
* **[Performance]** Added performance option: Front-line troops prefer roads.
* **[Data]** Added data to support C-47 Skytrain.
* **[Performance]** Added performance option: Disable idle aircraft at airfields.
* **[Squadrons]** Squadron pilot limits enabled by default.
* **[UI]** Add livery selector to Air Wing Configurator's squadrons.
## Fixes
* **[Mission Generation]** Fixed issue where aircraft carriers would return after being killed.
* **[Mission Generation]** Kneeboard STRIKE coordinates would sometimes get clipped when not fitting.
* **[UI]** Fix exception when trying to add a waypoints to a flightplan.
# Liberation:
# 7.0.0
Saves from 6.x are not compatible with 7.0.
## Features/Improvements
## Fixes
# 6.1.0
Saves from 6.0.0 are compatible with 6.1.0
## Features/Improvements
* **[Factions]** Defaulted bluefor modern to use Georgian and Ukrainian liveries for Russian aircraft.
* **[Factions]** Added Peru.
* **[Modding]** Added support for the HMS Ariadne, Achilles, and Castle class.
## Fixes
* **[Flight Planning]** Fixes CAS flights not having landing waypoints.
* **[Squadrons]** Fixed the livery for the VF-33 F-14A squadron.
* **[UI]** Fixed an issue where manual submit of mission results did not end the mission correctly.
# 6.0.0
Saves from 5.x are not compatible with 6.0.
## Features/Improvements
* **[Engine]** Support for DCS 2.7.18.30348.
* **[Mission Generation]** Added an option to fast-forward mission generation until the point of first contact (WIP).
* **[Mission Generation]** Added performance option to not cull IADS when culling would effect how mission is played at target area.
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
* **[Engine]** Support for DCS 2.8.0.33006.
* **[Factions]** Updated the Faction file structure. Older custom faction files will not work correctly and have to be updated to the new structure.
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. AI does not plan.
* **[Flight Planning]** Added preset formations for different flight types at hold, join, ingress, and split waypoints. Air to Air flights will tend toward line-abreast and spread-four formations. Air to ground flights will tend towards trail formation.
* **[Flight Planning]** Added the ability to plan tankers for recovery on package flights. This mission type will not be planned automatically.
* **[Flight Planning]** Air to Ground flights now have ECM enabled on lock at the join point, and SEAD/DEAD also have ECM enabled on detection and lock at ingress.
* **[Flight Planning]** AWACS flightplan changed from orbit to a racetrack to reduce data link disconnects which were caused by blind spots as a result of the bank angle.
* **[Flight Planning]** Added a new helo mission type: AirAssault which can be used to load and transport infantry troops from a pickup zone or a carrier to an enemy CP to capture it.
* **[Flight Planning]** Improved the Airlift mission type so that it now can be enforced within the unit transfer dialog and implemented CTLD support. This allows user to spawn sling loadable crates at the pickup location and fly transport flights.
* **[Flight Planning]** Added a new helo mission type: AirAssault which can be used to load and transport infantry troops from a pickup zone or a carrier to an enemy CP to capture it.
* **[Flight Planning]** Improved the Airlift mission type so that it now can be enforced within the unit transfer dialog and implemented CTLD support. This allows user to spawn sling loadable crates at the pickup location and fly transport flights.
* **[Mission Generation]** Added an option to fast-forward mission generation until the point of first contact (WIP).
* **[Mission Generation]** Added performance option to not cull IADS when culling would affect how mission is played at target area.
* **[Mission Generation]** Reworked the ground object generation which now uses a new layout system
* **[Mission Generation]** Added information about the modulation (AM/FM) of the assigned frequencies to the kneeboard and assign AM modulation instead of FM for JTAC.
* **[Mission Generation]** Added ice halos.
* **[Mission Generation]** Adjusted wind speeds. Wind speeds at high altitude are generally higher now.
* **[Mission Generation]** Added turbulence. Higher in Summer and Winter, also higher at day time than at nighttime.
* **[Modding]** Updated UH-60L mod version support to 1.3.1
* **[Modding]** Updated the High Digit SAMs implementation and added the HQ-2 as well as the upgraded SA-2 and SA-3 Launchers from the mod. Threat range circles will now also be displayed correctly.
* **[Modding]** Theater information such as climate properties is now moddable.
* **[Modding]** Allow campaign designers to define default values for the economy settings (starting budget and multiplier).
* **[Modding]** Campaigns can now optionally define their start time by including a time in the `recommended_start_date` field. There is not currently a way to override the start time in the UI.
* **[Plugins]** Allow full support of the SkynetIADS plugin with all advanced features (connection nodes, power sources, command centers) if campaign supports it.
* **[Plugins]** Added support for the CTLD script by ciribob with many possible customization options and updated the JTAC Autolase to the CTLD included script.
* **[UI]** Added options to the loadout editor for setting properties such as HMD choice.
* **[UI]** Added separate images for the different carrier types.
* **[UI]** Add Accept/Reset buttons to Air Wing Configurator screen.
* **[Campaign]** Allow campaign designers to define default values for the economy settings (starting budget and multiplier).
* **[Plugins]** Allow full support of the SkynetIADS plugin with all advanced features (connection nodes, power sources, command centers) if campaign supports it.
* **[Plugins]** Added support for the CTLD script by ciribob with many possible customization options and updated the JTAC Autolase to the CTLD included script.
## Fixes
* **[Engine]** Fixed issue that prevented some weapon types like torpedoes from being recognized.
* **[Flight Planning]** Fixed a miscalculation of waypoint TOTs that would require time travel.
* **[Loadouts]** Improved the range of the F-16 CAS loadout by adding bags.
* **[Mission Generation]** AAA ground units now spawn correctly at the frontline
* **[Mission Generation]** Fixed SA-13 incorrectly created as SA-8 Loading Unit which will not be spawned in the generated mission.
* **[Mission Generation]** Fixed adding additional mission types for a squadron causing error messages when the mission type is not supported by the aircraft type by default
* **[Mission Generation]** Fixed an issue where SEAD/DEAD/BAI flights fired all missiles / bombs against a single unit in a group instead of targeting the whole group.
* **[Mission Generation]** Fixed an issue which generated the helipads at FARPs incorrectly and placed the helicopters within each other.
* **[Mission Generation]** Fixed an issue with SEAD missions flown by the AI when using the Skynet Plugin and anti-radiation missiles (ARM). The AI now correctly engages the SAM when it comes alive instead of diving into it.
* **[Mission Generation]** Fixed an issue where SEAD/DEAD/BAI flights fired all missiles / bombs against a single unit in a group instead of targeting the whole group.
* **[Mission Generation]** Fixed adding additional mission types for a squadron causing error messages when the mission type is not supported by the aircraft type by default
* **[Mission Generation]** AAA ground units now spawn correctly at the frontline
* **[UI]** Fixed and issue where the liberation main exe was still running after application close.
* **[Mission Generation]** Fixed generation issue that would cause AI helicopters to get stuck after taking off from a FARP.
* **[Mission Generation]** Fixed mission scripting error caused by control points with apostrophes in their names, such as Tha'lah.
* **[Modding]** Campaigns that used quad zones for scenery targets will no longer load. Only circular zones were ever supported, but an implementation quirk allowed them to load in a way that would misbehave. A "No white triggerzones found" message during campaign generation is the sign of a broken campaign.
* **[Modding]** Loadouts with invalid weapons (typically new DCS weapons not yet available in Liberation) will be ignored rather than causing an error.
* **[Squadrons]** Fixed issue in air wing configuration that would allow squadrons to be created with no home base if no base was available.
* **[Squadrons]** Helicopter squadrons can no longer be assigned to FOBs that are not FARPs.
* **[UI]** Add vanilla theme weather and time of day icons
* **[UI]** Disable player slots for non-flyable aircraft.
* **[UI]** Fixed and issue where the liberation main exe was still running after application close.
# 5.2.0

767
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ from dcs.helicopters import (
CH_47D,
CH_53E,
Ka_50,
Ka_50_3,
Mi_24P,
Mi_24V,
Mi_26,
@@ -45,11 +46,13 @@ from dcs.planes import (
FW_190A8,
FW_190D9,
F_117A,
F_14A,
F_14A_135_GR,
F_14B,
F_15C,
F_15E,
F_16A,
F_16A_MLU,
F_16C_50,
F_4E,
F_5E_3,
@@ -116,18 +119,33 @@ from dcs.planes import (
Tu_95MS,
WingLoong_I,
Yak_40,
MB_339A,
)
from dcs.unittype import FlyingType
from game.dcs.aircrafttype import AircraftType
from pydcs_extensions.a4ec.a4ec import A_4E_C
from pydcs_extensions.f104.f104 import VSN_F104G, VSN_F104S, VSN_F104S_AG
from pydcs_extensions.a6a.a6a import VSN_A6A
from pydcs_extensions.f100.f100 import VSN_F100
from pydcs_extensions.f104.f104 import VSN_F104C, VSN_F104G, VSN_F104S, VSN_F104S_AG
from pydcs_extensions.f105.f105 import VSN_F105D, VSN_F105G
from pydcs_extensions.f15d.f15d import F_15D
from pydcs_extensions.f16i_idf.f16i_idf import (
F_16D_50,
F_16D_52,
F_16D_50_NS,
F_16D_52_NS,
F_16D_Barak,
F_16I,
)
from pydcs_extensions.f22a.f22a import F_22A
from pydcs_extensions.f4b.f4b import VSN_F4B
from pydcs_extensions.f84g.f84g import VSN_F84G
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
@@ -138,13 +156,14 @@ from .flighttype import FlightType
# factions that also have F-4s should not.
# Used for CAP, Escort, and intercept if there is not a specialised aircraft available
CAP_CAPABLE = [
ESCORT_CAPABLE = [
Su_57,
F_22A,
F_15C,
F_15D,
F_14B,
F_14A_135_GR,
F_14A,
Su_33,
J_11A,
Su_30,
@@ -155,11 +174,19 @@ CAP_CAPABLE = [
Su_27,
MiG_29S,
F_16C_50,
F_16I,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18C_hornet,
JF_17,
JAS39Gripen,
F_16A_MLU,
F_16A,
F_4E,
VSN_F4B,
MiG_31,
MiG_25PD,
MiG_29G,
@@ -181,12 +208,14 @@ CAP_CAPABLE = [
F_5E_3,
VSN_F104S,
VSN_F104G,
VSN_F104C,
MiG_19P,
VSN_F100,
A_4E_C,
F_86F_Sabre,
MiG_15bis,
C_101CC,
L_39ZA,
VSN_F84G,
P_51D_30_NA,
P_51D,
SpitfireLFMkIXCW,
@@ -201,6 +230,11 @@ CAP_CAPABLE = [
I_16,
]
# Types to be skipped for escorts
CAP_CAPABLE = ESCORT_CAPABLE + [
L_39ZA,
]
# Used for CAS (Close air support) and BAI (Battlefield Interdiction)
CAS_CAPABLE = [
@@ -214,6 +248,12 @@ CAS_CAPABLE = [
F_15D,
F_15E,
F_16C_50,
F_16I,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18C_hornet,
Tornado_GR4,
Tornado_IDS,
@@ -221,6 +261,8 @@ CAS_CAPABLE = [
JF_17,
AV8BNA,
A_10A,
F_16A_MLU,
F_16A,
B_1B,
A_4E_C,
F_14B,
@@ -248,6 +290,7 @@ CAS_CAPABLE = [
SA342M,
SA342L,
Ka_50,
Ka_50_3,
Mi_28N,
Mi_24P,
Mi_24V,
@@ -268,9 +311,9 @@ CAS_CAPABLE = [
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
Bronco_OV_10A,
UH_1H,
VSN_F104S_AG,
VSN_F104G,
A_20G,
Ju_88A4,
P_47D_40,
@@ -288,21 +331,34 @@ CAS_CAPABLE = [
WingLoong_I,
MQ_9_Reaper,
RQ_1A_Predator,
VSN_A6A,
VSN_F100,
VSN_F105G,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F4B,
VSN_F84G,
]
# Aircraft used for SEAD and SEAD Escort tasks. Must be capable of the CAS DCS task.
SEAD_CAPABLE = [
SEAD_ESCORT_CAPABLE = [
JF_17,
F_16C_50,
F_16I,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18C_hornet,
Tornado_IDS,
Su_25T,
Su_25TM,
F_4E,
A_4E_C,
F_14B,
F_14A_135_GR,
JAS39Gripen_AG,
AV8BNA,
Su_24M,
@@ -315,14 +371,22 @@ SEAD_CAPABLE = [
Su_30SM,
MiG_27K,
Tornado_GR4,
VSN_F105G,
VSN_F100,
]
SEAD_CAPABLE = SEAD_ESCORT_CAPABLE + [
F_16A_MLU,
F_14B,
F_14A_135_GR,
]
# Aircraft used for DEAD tasks. Must be capable of the CAS DCS task.
DEAD_CAPABLE = SEAD_CAPABLE + [
AJS37,
F_14B,
F_14A_135_GR,
F_16A,
F_15E,
JAS39Gripen_AG,
B_1B,
B_52H,
@@ -331,19 +395,25 @@ DEAD_CAPABLE = SEAD_CAPABLE + [
H_6J,
A_20G,
Ju_88A4,
VSN_F104S_AG,
VSN_F104G,
P_47D_40,
P_47D_30bl1,
P_47D_30,
P_51D_30_NA,
P_51D,
Bronco_OV_10A,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
MosquitoFBMkVI,
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F100,
VSN_F84G,
]
@@ -361,9 +431,16 @@ STRIKE_CAPABLE = [
AJS37,
Tornado_GR4,
F_16C_50,
F_16I,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18C_hornet,
AV8BNA,
JF_17,
F_16A_MLU,
F_16A,
F_14B,
F_14A_135_GR,
@@ -390,6 +467,7 @@ STRIKE_CAPABLE = [
A_10C,
S_3B,
A_4E_C,
Bronco_OV_10A,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
@@ -406,11 +484,10 @@ STRIKE_CAPABLE = [
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
B_17G,
A_20G,
Ju_88A4,
VSN_F104S_AG,
VSN_F104G,
P_47D_40,
P_47D_30bl1,
P_47D_30,
@@ -422,6 +499,15 @@ STRIKE_CAPABLE = [
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F100,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F105G,
VSN_F105D,
VSN_F4B,
VSN_F84G,
]
@@ -432,6 +518,8 @@ ANTISHIP_CAPABLE = [
H_6J,
FA_18C_hornet,
JAS39Gripen_AG,
F_16A_MLU,
F_16A,
Su_24M,
Su_17M4,
JF_17,
@@ -460,6 +548,14 @@ RUNWAY_ATTACK_CAPABLE = [
JF_17,
Tornado_IDS,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1CT,
H_6J,
B_1B,
B_52H,
@@ -468,9 +564,16 @@ RUNWAY_ATTACK_CAPABLE = [
F_15E,
AJS37,
F_16C_50,
F_16I,
F_16D_Barak,
F_16D_50,
F_16D_50_NS,
F_16D_52,
F_16D_52_NS,
FA_18C_hornet,
AV8BNA,
JF_17,
F_16A_MLU,
F_16A,
F_14B,
F_14A_135_GR,
@@ -497,15 +600,8 @@ RUNWAY_ATTACK_CAPABLE = [
A_10C,
S_3B,
A_4E_C,
Bronco_OV_10A,
M_2000C,
Mirage_F1B,
Mirage_F1BE,
Mirage_F1CE,
Mirage_F1EE,
Mirage_F1EQ,
Mirage_F1M_CE,
Mirage_F1M_EE,
Mirage_F1CT,
MiG_27K,
MiG_21Bis,
MiG_15bis,
@@ -513,11 +609,10 @@ RUNWAY_ATTACK_CAPABLE = [
F_86F_Sabre,
C_101CC,
L_39ZA,
MB_339A,
B_17G,
A_20G,
Ju_88A4,
VSN_F104S_AG,
VSN_F104G,
P_47D_40,
P_47D_30bl1,
P_47D_30,
@@ -529,6 +624,14 @@ RUNWAY_ATTACK_CAPABLE = [
Bf_109K_4,
FW_190D9,
FW_190A8,
VSN_A6A,
VSN_F105G,
VSN_F105D,
VSN_F104S_AG,
VSN_F104G,
VSN_F104C,
VSN_F100,
VSN_F4B,
]
# For any aircraft that isn't necessarily directly involved in strike
@@ -605,7 +708,7 @@ def dcs_types_for_task(task: FlightType) -> Sequence[Type[FlyingType]]:
elif task == FlightType.SEAD:
return SEAD_CAPABLE
elif task == FlightType.SEAD_ESCORT:
return SEAD_CAPABLE
return SEAD_ESCORT_CAPABLE
elif task == FlightType.DEAD:
return DEAD_CAPABLE
elif task == FlightType.OCA_AIRCRAFT:
@@ -615,7 +718,7 @@ def dcs_types_for_task(task: FlightType) -> Sequence[Type[FlyingType]]:
elif task == FlightType.STRIKE:
return STRIKE_CAPABLE
elif task == FlightType.ESCORT:
return CAP_CAPABLE
return ESCORT_CAPABLE
elif task == FlightType.AEWC:
return AEWC_CAPABLE
elif task == FlightType.REFUELING:

View File

@@ -63,6 +63,7 @@ class Flight(SidcDescribable):
self.start_type = start_type
self.use_custom_loadout = False
self.custom_name = custom_name
self.group_id: int = 0
# Only used by transport missions.
self.cargo = cargo
@@ -90,6 +91,12 @@ class Flight(SidcDescribable):
def flight_plan(self) -> FlightPlan[Any]:
return self._flight_plan_builder.get_or_build()
def degrade_to_custom_flight_plan(self) -> None:
from .flightplans.custom import Builder as CustomBuilder
self._flight_plan_builder = CustomBuilder(self, self.flight_plan.waypoints[1:])
self.recreate_flight_plan()
def __getstate__(self) -> dict[str, Any]:
state = self.__dict__.copy()
# Avoid persisting the flight state since that's not (currently) used outside

View File

@@ -4,28 +4,39 @@ from dataclasses import dataclass
from datetime import timedelta
from typing import Iterator, TYPE_CHECKING, Type
from game.ato.flightplans.airlift import AirliftLayout
from game.ato.flightplans.standard import StandardFlightPlan
from game.ato.flightplans.standard import StandardFlightPlan, StandardLayout
from game.theater.controlpoint import ControlPointType
from game.theater.missiontarget import MissionTarget
from game.utils import Distance, feet, meters
from .ibuilder import IBuilder
from .planningerror import PlanningError
from .uizonedisplay import UiZone, UiZoneDisplay
from .waypointbuilder import WaypointBuilder
from ..flightwaypoint import FlightWaypointType
if TYPE_CHECKING:
from ..flightwaypoint import FlightWaypoint
@dataclass(frozen=True)
class AirAssaultLayout(AirliftLayout):
class AirAssaultLayout(StandardLayout):
# The pickup point is optional because we don't always need to load the cargo. When
# departing from a carrier, LHA, or off-map spawn, the cargo is pre-loaded.
pickup: FlightWaypoint | None
nav_to_ingress: list[FlightWaypoint]
ingress: FlightWaypoint
drop_off: FlightWaypoint
# This is an implementation detail used by CTLD. The aircraft will not go to this
# waypoint. It is used by CTLD as the destination for unloaded troops.
target: FlightWaypoint
nav_to_home: list[FlightWaypoint]
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
yield self.departure
yield from self.nav_to_pickup
if self.pickup:
if self.pickup is not None:
yield self.pickup
yield from self.nav_to_drop_off
yield from self.nav_to_ingress
yield self.ingress
yield self.drop_off
yield self.target
yield from self.nav_to_home
@@ -35,7 +46,7 @@ class AirAssaultLayout(AirliftLayout):
yield self.bullseye
class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]):
class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout], UiZoneDisplay):
@staticmethod
def builder_type() -> Type[Builder]:
return Builder
@@ -53,35 +64,48 @@ class AirAssaultFlightPlan(StandardFlightPlan[AirAssaultLayout]):
return None
@property
def engagement_distance(self) -> Distance:
# The radius of the WaypointZone created at the target location
def ctld_target_zone_radius(self) -> Distance:
return meters(2500)
@property
def mission_departure_time(self) -> timedelta:
return self.package.time_over_target
def ui_zone(self) -> UiZone:
return UiZone(
[self.layout.target.position],
self.ctld_target_zone_radius,
)
class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
def layout(self) -> AirAssaultLayout:
if not self.flight.is_helo:
raise PlanningError("Air assault is only usable by helicopters")
assert self.package.waypoints is not None
altitude = feet(1500) if self.flight.is_helo else self.doctrine.ingress_altitude
altitude_is_agl = self.flight.is_helo
builder = WaypointBuilder(self.flight, self.coalition)
if not self.flight.is_helo or self.flight.departure.cptype in [
if self.flight.departure.cptype in [
ControlPointType.AIRCRAFT_CARRIER_GROUP,
ControlPointType.LHA_GROUP,
ControlPointType.OFF_MAP,
]:
# Non-Helo flights or Off_Map will be preloaded
# Off_Map spawns will be preloaded
# Carrier operations load the logistics directly from the carrier
pickup = None
pickup_position = self.flight.departure.position
else:
# TODO The calculation of the Pickup LZ is currently randomized. This
# leads to the problem that we can not gurantee that the LZ is clear of
# obstacles. This has to be improved in the future so that the Mission can
# be autoplanned. In the current state the User has to check the created
# Waypoints for the Pickup and Dropoff LZs are free of obstacles.
# Create a special pickup zone for Helos from Airbase / FOB
pickup = builder.pickup(
pickup = builder.pickup_zone(
MissionTarget(
"Pickup Zone",
self.flight.departure.position.random_point_within(1200, 600),
@@ -90,6 +114,8 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
pickup_position = pickup.position
assault_area = builder.assault_area(self.package.target)
heading = self.package.target.position.heading_between_point(pickup_position)
# TODO we can not gurantee a safe LZ for DropOff. See comment above.
drop_off_zone = MissionTarget(
"Dropoff zone",
self.package.target.position.point_from_heading(heading, 1200),
@@ -97,21 +123,19 @@ class Builder(IBuilder[AirAssaultFlightPlan, AirAssaultLayout]):
return AirAssaultLayout(
departure=builder.takeoff(self.flight.departure),
nav_to_pickup=builder.nav_path(
self.flight.departure.position,
pickup_position,
altitude,
altitude_is_agl,
),
pickup=pickup,
nav_to_drop_off=builder.nav_path(
nav_to_ingress=builder.nav_path(
pickup_position,
drop_off_zone.position,
self.package.waypoints.ingress,
altitude,
altitude_is_agl,
),
drop_off=builder.drop_off(drop_off_zone),
stopover=None,
ingress=builder.ingress(
FlightWaypointType.INGRESS_AIR_ASSAULT,
self.package.waypoints.ingress,
self.package.target,
),
drop_off=builder.dropoff_zone(drop_off_zone),
target=assault_area,
nav_to_home=builder.nav_path(
drop_off_zone.position,

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,8 @@ from .formationattack import (
FormationAttackLayout,
)
from .waypointbuilder import WaypointBuilder
from .. import FlightType
from ...utils import Distance
class EscortFlightPlan(FormationAttackFlightPlan):
@@ -24,12 +26,17 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
ingress, target = builder.escort(
self.package.waypoints.ingress, self.package.target
)
ingress.only_for_player = True
target.only_for_player = True
hold = builder.hold(self._hold_point())
join = builder.join(self.package.waypoints.join)
split = builder.split(self.package.waypoints.split)
refuel = None
if self.package.waypoints.refuel is not None:
refuel = builder.refuel(self.package.waypoints.refuel)
refuel = builder.refuel(self.package.waypoints.refuel)
initial = None
if self.package.primary_task == FlightType.STRIKE:
initial = builder.escort_hold(
self.package.waypoints.initial, Distance.from_feet(20000)
)
return FormationAttackLayout(
departure=builder.takeoff(self.flight.departure),
@@ -39,11 +46,12 @@ class Builder(FormationAttackBuilder[EscortFlightPlan, FormationAttackLayout]):
),
join=join,
ingress=ingress,
initial=initial,
targets=[target],
split=split,
refuel=refuel,
nav_from=builder.nav_path(
split.position,
refuel.position,
self.flight.arrival.position,
self.doctrine.ingress_altitude,
),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -304,6 +304,13 @@ class WaypointBuilder:
return self._target_area(f"ATTACK {target.name}", target, flyover=True)
def assault_area(self, target: MissionTarget) -> FlightWaypoint:
"""A destination waypoint used by air-assault ground troops.
This waypoint is an implementation detail for CTLD and should not be followed by
aircraft.
"""
# TODO: Add a property that can hide this waypoint from the player's flight
# plan.
return self._target_area(f"ASSAULT {target.name}", target)
@staticmethod
@@ -415,6 +422,24 @@ class WaypointBuilder:
pretty_name="Orbit",
)
@staticmethod
def escort_hold(start: Point, altitude: Distance) -> FlightWaypoint:
"""Creates custom waypoint for escort flights that need to hold.
Args:
start: Position of the waypoint.
altitude: Altitude of the holding pattern.
"""
return FlightWaypoint(
"ESCORT HOLD",
FlightWaypointType.CUSTOM,
start,
altitude,
description="Anchor and hold at this point",
pretty_name="Escort Hold",
)
@staticmethod
def sweep_start(position: Point, altitude: Distance) -> FlightWaypoint:
"""Creates a sweep start waypoint.
@@ -494,58 +519,50 @@ class WaypointBuilder:
)
@staticmethod
def stopover(stopover: ControlPoint, name: str = "STOPOVER") -> FlightWaypoint:
"""Creates a stopover waypoint.
Args:
control_point: Pick up location.
def pickup_zone(pick_up: MissionTarget) -> FlightWaypoint:
"""Creates a pickup landing zone waypoint
This waypoint is used to generate the Trigger Zone used for AirAssault and
AirLift using the CTLD plugin (see LogisticsGenerator)
"""
return FlightWaypoint(
name,
FlightWaypointType.STOPOVER,
stopover.position,
meters(0),
"RADIO",
description=f"Stopover at {stopover}",
pretty_name="Stopover location",
control_point=stopover,
)
@staticmethod
def pickup(pick_up: MissionTarget) -> FlightWaypoint:
"""Creates a cargo pickup waypoint.
Args:
control_point: Pick up location.
"""
control_point = pick_up if isinstance(pick_up, ControlPoint) else None
return FlightWaypoint(
"PICKUP",
FlightWaypointType.PICKUP,
"PICKUPZONE",
FlightWaypointType.PICKUP_ZONE,
pick_up.position,
meters(0),
"RADIO",
description=f"Pick up cargo from {pick_up.name}",
pretty_name="Pick up location",
control_point=control_point,
pretty_name="Pick-up zone",
)
@staticmethod
def drop_off(drop_off: MissionTarget) -> FlightWaypoint:
"""Creates a cargo drop-off waypoint.
Args:
control_point: Drop-off location.
def dropoff_zone(drop_off: MissionTarget) -> FlightWaypoint:
"""Creates a dropoff landing zone waypoint
This waypoint is used to generate the Trigger Zone used for AirAssault and
AirLift using the CTLD plugin (see LogisticsGenerator)
"""
control_point = drop_off if isinstance(drop_off, ControlPoint) else None
return FlightWaypoint(
"DROP OFF",
FlightWaypointType.DROP_OFF,
"DROPOFFZONE",
FlightWaypointType.DROPOFF_ZONE,
drop_off.position,
meters(0),
"RADIO",
description=f"Drop off cargo at {drop_off.name}",
pretty_name="Drop off location",
pretty_name="Drop-off zone",
)
@staticmethod
def cargo_stop(control_point: ControlPoint) -> FlightWaypoint:
"""Creates a cargo stop waypoint.
This waypoint is used by AirLift as a landing and stopover waypoint
"""
return FlightWaypoint(
"CARGOSTOP",
FlightWaypointType.CARGO_STOP,
control_point.position,
meters(0),
"RADIO",
description=f"Stop for cargo at {control_point.name}",
pretty_name="Cargo stop",
control_point=control_point,
)

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
import datetime
import logging
from collections.abc import Iterable
from typing import Iterator, Mapping, Optional, TYPE_CHECKING, Type, Dict, Any
from typing import Iterator, Mapping, Optional, TYPE_CHECKING, Type, Dict
from dcs.unittype import FlyingType
@@ -151,10 +151,22 @@ class Loadout:
# names, so those have been included here too. The priority goes from first to
# last - the first element in the tuple will be tried first, then the second,
# etc.
loadout_names = {t: [f"Liberation {t.value}"] for t in FlightType}
loadout_names = {
t: [f"Retribution {t.value}", f"Liberation {t.value}"] for t in FlightType
}
legacy_names = {
FlightType.TARCAP: ("CAP HEAVY", "CAP", "Liberation BARCAP"),
FlightType.BARCAP: ("CAP HEAVY", "CAP", "Liberation TARCAP"),
FlightType.TARCAP: (
"CAP HEAVY",
"CAP",
"Retribution BARCAP",
"Liberation BARCAP",
),
FlightType.BARCAP: (
"CAP HEAVY",
"CAP",
"Retribution TARCAP",
"Liberation TARCAP",
),
FlightType.CAS: ("CAS MAVERICK F", "CAS"),
FlightType.STRIKE: ("STRIKE",),
FlightType.ANTISHIP: ("ANTISHIP",),

View File

@@ -129,6 +129,16 @@ class Package:
if not self.flights:
self.waypoints = None
@property
def primary_flight(self) -> Optional[Flight]:
task = self.primary_task
if not task:
return None
primaries = [x for x in self.flights if x.flight_type == task]
if len(primaries) > 0:
return primaries[0]
return None
@property
def primary_task(self) -> Optional[FlightType]:
if not self.flights:

View File

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

View File

@@ -3,8 +3,8 @@ import logging
import re
from typing import Any
from dcs.unitgroup import FlyingGroup
from dcs.flyingunit import FlyingUnit
from dcs.unitgroup import FlyingGroup
def callsign_for_support_unit(group: FlyingGroup[Any]) -> str:

View File

@@ -1,13 +1,12 @@
from __future__ import annotations
import dataclasses
import logging
from typing import Optional, TYPE_CHECKING, Dict, Union
from typing import Optional, TYPE_CHECKING
from game.squadrons import Squadron
from game.squadrons.squadrondef import SquadronDef
from ..ato.flighttype import FlightType
from .campaignairwingconfig import CampaignAirWingConfig, SquadronConfig
from ..ato.flighttype import FlightType
from ..dcs.aircrafttype import AircraftType
from ..theater import ControlPoint

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,11 +3,11 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.missionproposals import EscortType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater.theatergroundobject import NavalGroundObject
from game.ato.flighttype import FlightType
@dataclass

View File

@@ -3,10 +3,10 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.transfers import CargoShip
from game.ato.flighttype import FlightType
@dataclass

View File

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

View File

@@ -3,10 +3,10 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import ControlPoint
from game.ato.flighttype import FlightType
@dataclass

View File

@@ -3,10 +3,10 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import FrontLine
from game.ato.flighttype import FlightType
@dataclass

View File

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

View File

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

View File

@@ -3,10 +3,10 @@ from __future__ import annotations
from dataclasses import dataclass
from random import randint
from game.ato.flighttype import FlightType
from game.commander.tasks.packageplanningtask import PackagePlanningTask
from game.commander.theaterstate import TheaterState
from game.theater import ControlPoint
from game.ato.flighttype import FlightType
@dataclass

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ from dcs.countries import country_dict
from dcs.unittype import ShipType, StaticType
from dcs.unittype import UnitType as DcsUnitType
from game.armedforces.forcegroup import ForceGroup
from game.data.building_data import (
WW2_ALLIES_BUILDINGS,
DEFAULT_AVAILABLE_BUILDINGS,
@@ -25,13 +26,13 @@ from game.data.doctrine import (
COLDWAR_DOCTRINE,
WWII_DOCTRINE,
)
from game.data.units import UnitClass
from game.data.groups import GroupRole
from game.data.units import UnitClass
from game.dcs.aircrafttype import AircraftType
from game.dcs.groundunittype import GroundUnitType
from game.dcs.shipunittype import ShipUnitType
from game.armedforces.forcegroup import ForceGroup
from game.dcs.unittype import UnitType
from pydcs_extensions.f16i_idf.f16i_idf import inject_F16I
if TYPE_CHECKING:
from game.theater.start_generator import ModSettings
@@ -115,6 +116,9 @@ class Faction:
# List of default livery overrides
liveries_overrides: Dict[AircraftType, List[str]] = field(default_factory=dict)
# List of default livery overrides for ground vehicles
liveries_overrides_ground_forces: Dict[str, List[str]] = field(default_factory=dict)
#: Set to True if the faction should force the "Unrestricted satnav" option
#: for the mission. This option enables GPS for capable aircraft regardless
#: of the time period or operator. For example, the CJTF "countries" don't
@@ -124,6 +128,10 @@ class Faction:
#: both will use it.
unrestricted_satnav: bool = False
# Store mod settings so mod properties can be injected again on game load,
# in case mods like CJS F/A-18E/F/G or IDF F-16I are selected by the player
mod_settings: Optional[ModSettings] = field(default=None)
def has_access_to_dcs_type(self, unit_type: Type[DcsUnitType]) -> bool:
# Vehicle and Ship Units
if any(unit_type == u.dcs_unit_type for u in self.accessible_units):
@@ -279,6 +287,16 @@ class Faction:
aircraft = AircraftType.named(name)
faction.liveries_overrides[aircraft] = [s.lower() for s in livery]
# Load liveries override for ground forces
faction.liveries_overrides_ground_forces = {}
liveries_overrides_ground_forces = json.get(
"liveries_overrides_ground_forces", {}
)
for vehicle_type, livery in liveries_overrides_ground_forces.items():
faction.liveries_overrides_ground_forces[vehicle_type] = [
s.lower() for s in livery
]
faction.unrestricted_satnav = json.get("unrestricted_satnav", False)
return faction
@@ -294,7 +312,21 @@ class Faction:
if unit.unit_class is unit_class:
yield unit
def apply_mod_settings(self, mod_settings: ModSettings) -> None:
def apply_mod_settings(self, mod_settings: Optional[ModSettings] = None) -> None:
if mod_settings is None:
if self.mod_settings is None:
# No mod settings were provided and none were saved for this faction
# so stop here
return
elif self.mod_settings is not None:
# Saved mod settings were found for this faction,
# so load them for use
mod_settings = self.mod_settings
else:
# Update the mod settings of this faction
# so the settings can be applied again on load, if needed
self.mod_settings = mod_settings
# aircraft
if not mod_settings.a4_skyhawk:
self.remove_aircraft("A-4E-C")
@@ -303,14 +335,36 @@ class Faction:
if not mod_settings.uh_60l:
self.remove_aircraft("UH-60L")
self.remove_aircraft("KC130J")
if not mod_settings.f4b_phantom:
self.remove_aircraft("VSN_F4B")
if not mod_settings.f15d_baz:
self.remove_aircraft("F-15D")
if not mod_settings.f_16_idf:
self.remove_aircraft("F-16I")
self.remove_aircraft("F_16D_52")
self.remove_aircraft("F_16D_50")
self.remove_aircraft("F_16D_50_NS")
self.remove_aircraft("F_16D_52_NS")
else:
inject_F16I()
# Remove the stock Viper because that DCS ID is now used by the Sufa
self.remove_aircraft_by_name("F-16CM Fighting Falcon (Block 50)")
if not mod_settings.f22_raptor:
self.remove_aircraft("F-22A")
if not mod_settings.f84g_thunderjet:
self.remove_aircraft("VSN_F84G")
if not mod_settings.f100_supersabre:
self.remove_aircraft("VSN_F100")
if not mod_settings.f104_starfighter:
self.remove_aircraft("VSN_F104C")
self.remove_aircraft("VSN_F104G")
self.remove_aircraft("VSN_F104S")
self.remove_aircraft("VSN_F104S_AG")
if not mod_settings.f105_thunderchief:
self.remove_aircraft("VSN_F105D")
self.remove_aircraft("VSN_F105G")
if not mod_settings.a6a_intruder:
self.remove_aircraft("VSN_A6A")
if not mod_settings.jas39_gripen:
self.remove_aircraft("JAS39Gripen")
self.remove_aircraft("JAS39Gripen_AG")
@@ -321,6 +375,8 @@ class Faction:
self.remove_aircraft("Su-30SM")
if not mod_settings.su57_felon:
self.remove_aircraft("Su-57")
if not mod_settings.ov10a_bronco:
self.remove_aircraft("Bronco-OV-10A")
# frenchpack
if not mod_settings.frenchpack:
self.remove_vehicle("AMX10RCR")
@@ -380,6 +436,11 @@ class Faction:
if i.dcs_unit_type.id == name:
self.aircrafts.remove(i)
def remove_aircraft_by_name(self, name: str) -> None:
for i in self.aircrafts:
if i.name == name:
self.aircrafts.remove(i)
def remove_preset(self, name: str) -> None:
for pg in self.preset_groups:
if pg.name == name:

View File

@@ -1,4 +1,5 @@
from __future__ import annotations
import json
import logging
from pathlib import Path

View File

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

View File

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

View File

@@ -1,7 +1,5 @@
from __future__ import annotations
from collections import defaultdict
import logging
import random
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Iterator, Type, Optional
@@ -28,7 +26,6 @@ from game.theater.theatergroundobject import (
IadsGroundObject,
)
from game.theater.theatergroup import TheaterUnit
from game.utils import Heading
if TYPE_CHECKING:
from game.factions.faction import Faction

View File

@@ -1,9 +1,9 @@
from __future__ import annotations
from collections import defaultdict
import itertools
import logging
import pickle
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from typing import Iterator
@@ -31,7 +31,7 @@ from game.profiling import logged_duration
from game.version import VERSION
LAYOUT_DIR = "resources/layouts/"
LAYOUT_DUMP = "Liberation/layouts.p"
LAYOUT_DUMP = "Retribution/layouts.p"
LAYOUT_TYPES = {
GroupRole.AIR_DEFENSE: AntiAirLayout,

View File

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

44
game/migrator.py Normal file
View File

@@ -0,0 +1,44 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from game.ato.packagewaypoints import PackageWaypoints
from game.data.doctrine import MODERN_DOCTRINE, COLDWAR_DOCTRINE, WWII_DOCTRINE
from game.utils import knots, feet, nautical_miles
if TYPE_CHECKING:
from game import Game
class Migrator:
def __init__(self, game: Game):
self.game = game
self._migrate_game()
def _migrate_game(self) -> None:
self._update_doctrine()
self._update_packagewaypoints()
def _update_doctrine(self) -> None:
doctrines = [
MODERN_DOCTRINE,
COLDWAR_DOCTRINE,
WWII_DOCTRINE,
]
for c in self.game.coalitions:
if c.faction.doctrine.__dict__ in [d.__dict__ for d in doctrines]:
continue
found = False
for d in doctrines:
if c.faction.doctrine.rendezvous_altitude == d.rendezvous_altitude:
c.faction.doctrine = d
found = True
break
if not found:
c.faction.doctrine = MODERN_DOCTRINE
def _update_packagewaypoints(self) -> None:
for c in self.game.coalitions:
for p in c.ato.packages:
if not hasattr(p.waypoints, "initial"):
p.waypoints = PackageWaypoints.create(p, c)

View File

@@ -8,6 +8,7 @@ from dcs.task import (
CAP,
CAS,
EPLRS,
Escort,
FighterSweep,
GroundAttack,
Nothing,
@@ -19,6 +20,9 @@ from dcs.task import (
Refueling,
RunwayAttack,
Transport,
SEAD,
SwitchWaypoint,
OptJettisonEmptyTanks,
)
from dcs.unitgroup import FlyingGroup
@@ -83,6 +87,7 @@ class AircraftBehavior:
rtb_winchester: Optional[OptRTBOnOutOfAmmo.Values] = None,
restrict_jettison: Optional[bool] = None,
mission_uses_gun: bool = True,
rtb_on_bingo: bool = True,
) -> None:
group.points[0].tasks.clear()
group.points[0].tasks.append(OptReactOnThreat(react_on_threat))
@@ -104,7 +109,8 @@ class AircraftBehavior:
for unit in group.units:
unit.gun = 0
group.points[0].tasks.append(OptRTBOnBingoFuel(True))
group.points[0].tasks.append(OptRTBOnBingoFuel(rtb_on_bingo))
group.points[0].tasks.append(OptJettisonEmptyTanks())
# Do not restrict afterburner.
# https://forums.eagle.ru/forum/english/digital-combat-simulator/dcs-world-2-5/bugs-and-problems-ai/ai-ad/7121294-ai-stuck-at-high-aoa-after-making-sharp-turn-if-afterburner-is-restricted
@@ -265,7 +271,9 @@ class AircraftBehavior:
# Escort groups are actually given the CAP task so they can perform the
# Search Then Engage task, which we have to use instead of the Escort
# task for the reasons explained in JoinPointBuilder.
group.task = CAP.name
group.task = Escort.name
if flight.package.primary_task == FlightType.STRIKE:
group.add_trigger_action(SwitchWaypoint(None, 5))
self.configure_behavior(
flight, group, roe=OptROE.Values.OpenFire, restrict_jettison=True
)
@@ -274,7 +282,9 @@ class AircraftBehavior:
# CAS is able to perform all the same tasks as SEAD using a superset of the
# available aircraft, and F-14s are not able to be SEAD despite having TALDs.
# https://forums.eagle.ru/topic/272112-cannot-assign-f-14-to-sead/
group.task = CAS.name
group.task = SEAD.name
if flight.package.primary_task == FlightType.STRIKE:
group.add_trigger_action(SwitchWaypoint(None, 5))
self.configure_behavior(
flight,
group,
@@ -304,6 +314,7 @@ class AircraftBehavior:
react_on_threat=OptReactOnThreat.Values.EvadeFire,
roe=OptROE.Values.WeaponHold,
restrict_jettison=True,
rtb_on_bingo=False,
)
def configure_unknown_task(self, group: FlyingGroup[Any], flight: Flight) -> None:

View File

@@ -5,9 +5,12 @@ from datetime import datetime
from functools import cached_property
from typing import Any, Dict, List, TYPE_CHECKING
from dcs.action import AITaskPush
from dcs.condition import FlagIsTrue, GroupDead, Or, FlagIsFalse
from dcs.country import Country
from dcs.mission import Mission
from dcs.terrain.terrain import NoParkingSlotError
from dcs.triggers import TriggerOnce, Event
from dcs.unitgroup import FlyingGroup, StaticGroup
from game.ato.airtaaskingorder import AirTaskingOrder
@@ -17,8 +20,8 @@ from game.ato.flighttype import FlightType
from game.ato.package import Package
from game.ato.starttype import StartType
from game.factions.faction import Faction
from game.missiongenerator.missiondata import MissionData
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
from game.missiongenerator.missiondata import MissionData
from game.radio.radios import RadioRegistry
from game.radio.tacan import TacanRegistry
from game.runways import RunwayData
@@ -111,6 +114,19 @@ class AircraftGenerator:
flight, country, dynamic_runways
)
self.unit_map.add_aircraft(group, flight)
if (
package.primary_task == FlightType.STRIKE
and package.primary_flight is not None
):
splittrigger = TriggerOnce(Event.NoEvent, f"Split-{id(package)}")
splittrigger.add_condition(FlagIsTrue(flag=f"split-{id(package)}"))
splittrigger.add_condition(Or())
splittrigger.add_condition(FlagIsFalse(flag=f"split-{id(package)}"))
splittrigger.add_condition(GroupDead(package.primary_flight.group_id))
for flight in package.flights:
if flight is not package.primary_flight:
splittrigger.add_action(AITaskPush(flight.group_id, 1))
self.mission.triggerrules.triggers.append(splittrigger)
def spawn_unused_aircraft(
self, player_country: Country, enemy_country: Country
@@ -135,6 +151,9 @@ class AircraftGenerator:
def _spawn_unused_for(
self, squadron: Squadron, country: Country, faction: Faction
) -> None:
if self.game.settings.perf_disable_idle_aircraft:
return
assert isinstance(squadron.location, Airfield)
for _ in range(squadron.untasked_aircraft):
# Creating a flight even those this isn't a fragged mission lets us
@@ -152,7 +171,7 @@ class AircraftGenerator:
flight.state = Completed(flight, self.game.settings)
group = FlightGroupSpawner(
flight, country, self.mission, self.helipads
flight, country, self.mission, self.helipads, self.mission_data
).create_idle_aircraft()
AircraftPainter(flight, group).apply_livery()
self.unit_map.add_aircraft(group, flight)
@@ -162,7 +181,7 @@ class AircraftGenerator:
) -> FlyingGroup[Any]:
"""Creates and configures the flight group in the mission."""
group = FlightGroupSpawner(
flight, country, self.mission, self.helipads
flight, country, self.mission, self.helipads, self.mission_data
).create_flight_group()
self.flights.append(
FlightGroupConfigurator(

View File

@@ -12,9 +12,9 @@ from dcs.unitgroup import FlyingGroup
from game.ato import Flight, FlightType
from game.callsigns import callsign_for_support_unit
from game.data.weapons import Pylon, WeaponType as WeaponTypeEnum
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
from game.missiongenerator.lasercoderegistry import LaserCodeRegistry
from game.missiongenerator.logisticsgenerator import LogisticsGenerator
from game.missiongenerator.missiondata import MissionData, AwacsInfo, TankerInfo
from game.radio.radios import RadioFrequency, RadioRegistry
from game.radio.tacan import TacanBand, TacanRegistry, TacanUsage
from game.runways import RunwayData

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
from dcs.point import MovingPoint
from dcs.task import Land
from .pydcswaypointbuilder import PydcsWaypointBuilder
class LandingZoneBuilder(PydcsWaypointBuilder):
def build(self) -> MovingPoint:
waypoint = super().build()
# Create a landing task, currently only for Helos!
# Calculate a landing point with a small buffer to prevent AI from landing
# directly at the static ammo depot and exploding
landing_point = waypoint.position.random_point_within(15, 5)
# Use Land Task with 30s duration for helos
waypoint.add_task(Land(landing_point, duration=30))
return waypoint

View File

@@ -12,6 +12,7 @@ from dcs.task import (
from game.ato import FlightType
from game.ato.flightplans.patrolling import PatrollingFlightPlan
from ._helper import create_stop_orbit_trigger
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -57,7 +58,11 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
racetrack = ControlledTask(orbit)
self.set_waypoint_tot(waypoint, flight_plan.patrol_start_time)
loiter_duration = flight_plan.patrol_end_time - self.elapsed_mission_time
racetrack.stop_after_time(int(loiter_duration.total_seconds()))
elapsed = int(loiter_duration.total_seconds())
racetrack.stop_after_time(elapsed)
# What follows is some code to cope with the broken 'stop after time' condition
create_stop_orbit_trigger(racetrack, self.package, self.mission, elapsed)
# end of hotfix
waypoint.add_task(racetrack)
def configure_refueling_actions(self, waypoint: MovingPoint) -> None:

View File

@@ -1,9 +1,12 @@
from dcs.point import MovingPoint
from dcs.task import RefuelingTaskAction
from dcs.task import RefuelingTaskAction, ControlledTask
from .pydcswaypointbuilder import PydcsWaypointBuilder
class RefuelPointBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
waypoint.add_task(RefuelingTaskAction())
refuel = ControlledTask(RefuelingTaskAction())
refuel.start_probability(10)
waypoint.add_task(refuel)
return super().add_tasks(waypoint)

View File

@@ -8,8 +8,8 @@ from dcs.task import (
OptECMUsing,
WeaponType as DcsWeaponType,
)
from game.data.weapons import WeaponType
from game.data.weapons import WeaponType
from game.theater import TheaterGroundObject
from .pydcswaypointbuilder import PydcsWaypointBuilder

View File

@@ -1,5 +1,5 @@
from dcs.point import MovingPoint
from dcs.task import OptECMUsing, OptFormation
from dcs.task import OptECMUsing, OptFormation, RunScript
from .pydcswaypointbuilder import PydcsWaypointBuilder
@@ -16,4 +16,12 @@ class SplitPointBuilder(PydcsWaypointBuilder):
ecm_option = OptECMUsing(value=OptECMUsing.Values.UseIfOnlyLockByRadar)
waypoint.tasks.append(ecm_option)
waypoint.tasks.append(OptFormation.finger_four_close())
waypoint.tasks.append(OptFormation.finger_four_close())
waypoint.speed_locked = True
waypoint.speed = self.flight.coalition.doctrine.rtb_speed.meters_per_second
waypoint.ETA_locked = False
if self.flight is self.package.primary_flight:
script = RunScript(
f'trigger.action.setUserFlag("split-{id(self.package)}", true)'
)
waypoint.tasks.append(script)

View File

@@ -1,15 +0,0 @@
from dcs.point import MovingPoint, PointAction
from dcs.task import Land
from .pydcswaypointbuilder import PydcsWaypointBuilder
class StopoverBuilder(PydcsWaypointBuilder):
def build(self) -> MovingPoint:
waypoint = super().build()
waypoint.type = "LandingReFuAr"
waypoint.action = PointAction.LandingReFuAr
waypoint.landing_refuel_rearm_time = 2 # Minutes.
if (control_point := self.waypoint.control_point) is not None:
waypoint.airdrome_id = control_point.airdrome_id_for_landing
return waypoint

View File

@@ -1,15 +1,22 @@
import copy
from dcs.planes import B_17G, B_52H, Tu_22M3
from dcs import Point
from dcs.planes import B_17G, B_52H, Tu_22M3, B_1B
from dcs.point import MovingPoint
from dcs.task import Bombing, OptFormation, WeaponType, Expend
from dcs.task import Bombing, Expend, OptFormation, WeaponType
from game.utils import mach, Distance
from .pydcswaypointbuilder import PydcsWaypointBuilder
class StrikeIngressBuilder(PydcsWaypointBuilder):
def add_tasks(self, waypoint: MovingPoint) -> None:
if self.group.units[0].unit_type in [B_17G, B_52H, Tu_22M3]:
bomber = self.group.units[0].unit_type in [B_17G, Tu_22M3]
bomber_guided = self.group.units[0].unit_type in [B_1B, B_52H]
if bomber_guided:
self.add_strike_tasks(waypoint, WeaponType.Guided)
self.add_bombing_tasks(waypoint)
elif bomber:
self.add_bombing_tasks(waypoint)
else:
self.add_strike_tasks(waypoint)
@@ -21,24 +28,28 @@ class StrikeIngressBuilder(PydcsWaypointBuilder):
if not targets:
return
center = copy.copy(targets[0].position)
center: Point = copy.copy(targets[0].position)
for target in targets[1:]:
center += target.position
center /= len(targets)
ga = True if self.flight.count > 1 else False
bombing = Bombing(
center, weapon_type=WeaponType.Bombs, expend=Expend.All, group_attack=True
center, weapon_type=WeaponType.Bombs, expend=Expend.All, group_attack=ga
)
waypoint.tasks.append(bombing)
def add_strike_tasks(self, waypoint: MovingPoint) -> None:
def add_strike_tasks(
self, waypoint: MovingPoint, weapon_type: WeaponType = WeaponType.Auto
) -> None:
ga = True if self.flight.count > 1 else False
for target in self.waypoint.targets:
bombing = Bombing(
target.position, weapon_type=WeaponType.Auto, group_attack=True
)
bombing = Bombing(target.position, weapon_type=weapon_type, group_attack=ga)
# If there is only one target, drop all ordnance in one pass.
if len(self.waypoint.targets) == 1:
bombing.params["expend"] = Expend.All.value
waypoint.tasks.append(bombing)
waypoint.speed = mach(0.85, Distance.from_feet(20000)).meters_per_second
# Register special waypoints
self.register_special_waypoints(self.waypoint.targets)

View File

@@ -16,12 +16,12 @@ from game.ato import Flight, FlightWaypoint
from game.ato.flightstate import InFlight, WaitingForStart
from game.ato.flightwaypointtype import FlightWaypointType
from game.ato.starttype import StartType
from game.missiongenerator.aircraft.waypoints.stopover import StopoverBuilder
from game.missiongenerator.aircraft.waypoints.cargostop import CargoStopBuilder
from game.missiongenerator.missiondata import MissionData
from game.settings import Settings
from game.utils import pairwise
from .baiingress import BaiIngressBuilder
from .cargostop import CargoStopBuilder
from .landingzone import LandingZoneBuilder
from .casingress import CasIngressBuilder
from .deadingress import DeadIngressBuilder
from .default import DefaultWaypointBuilder
@@ -118,7 +118,6 @@ class WaypointGenerator:
def builder_for_waypoint(self, waypoint: FlightWaypoint) -> PydcsWaypointBuilder:
builders = {
FlightWaypointType.DROP_OFF: CargoStopBuilder,
FlightWaypointType.INGRESS_BAI: BaiIngressBuilder,
FlightWaypointType.INGRESS_CAS: CasIngressBuilder,
FlightWaypointType.INGRESS_DEAD: DeadIngressBuilder,
@@ -133,9 +132,10 @@ class WaypointGenerator:
FlightWaypointType.LOITER: HoldPointBuilder,
FlightWaypointType.PATROL: RaceTrackEndBuilder,
FlightWaypointType.PATROL_TRACK: RaceTrackBuilder,
FlightWaypointType.PICKUP: CargoStopBuilder,
FlightWaypointType.PICKUP_ZONE: LandingZoneBuilder,
FlightWaypointType.DROPOFF_ZONE: LandingZoneBuilder,
FlightWaypointType.REFUEL: RefuelPointBuilder,
FlightWaypointType.STOPOVER: StopoverBuilder,
FlightWaypointType.CARGO_STOP: CargoStopBuilder,
}
builder = builders.get(waypoint.waypoint_type, DefaultWaypointBuilder)
return builder(

View File

@@ -58,6 +58,7 @@ class MissionInfoGenerator:
self.awacs: List[AwacsInfo] = []
self.comms: List[CommInfo] = []
self.flights: List[FlightData] = []
self.player_flights: List[FlightData] = []
self.jtacs: List[JtacInfo] = []
self.tankers: List[TankerInfo] = []
self.frontlines: List[FrontLineInfo] = []
@@ -87,6 +88,8 @@ class MissionInfoGenerator:
flight: Flight information.
"""
self.flights.append(flight)
if len(flight.client_units) > 0:
self.player_flights.append(flight)
def add_jtac(self, jtac: JtacInfo) -> None:
"""Adds a JTAC to the mission.

View File

@@ -10,6 +10,7 @@ from dcs.unit import Vehicle
from dcs.unitgroup import VehicleGroup
from game.dcs.groundunittype import GroundUnitType
from game.missiongenerator.groundforcepainter import GroundForcePainter
from game.transfers import Convoy
from game.unitmap import UnitMap
from game.utils import kph
@@ -72,6 +73,7 @@ class ConvoyGenerator:
for_player: bool,
) -> VehicleGroup:
country = self.mission.country(self.game.coalition_for(for_player).country_name)
faction = self.game.faction_for(for_player)
unit_types = list(units.items())
main_unit_type, main_unit_count = unit_types[0]
@@ -97,6 +99,7 @@ class ConvoyGenerator:
v.position.x = position.x
v.position.y = next(y)
v.heading = 0
GroundForcePainter(faction, v).apply_livery()
group.add_unit(v)
return group

View File

@@ -17,6 +17,7 @@ class EnvironmentGenerator:
def set_atmospheric(self, atmospheric: AtmosphericConditions) -> None:
self.mission.weather.qnh = atmospheric.qnh.mm_hg
self.mission.weather.season_temperature = atmospheric.temperature_celsius
self.mission.weather.turbulence_at_ground = int(atmospheric.turbulence_per_10cm)
def set_clouds(self, clouds: Optional[Clouds]) -> None:
if clouds is None:

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import logging
import math
import random
from typing import List, Optional, TYPE_CHECKING, Tuple
@@ -43,6 +44,7 @@ from game.theater.controlpoint import ControlPoint
from game.unitmap import UnitMap
from game.utils import Heading
from .frontlineconflictdescription import FrontLineConflictDescription
from .groundforcepainter import GroundForcePainter
from .lasercoderegistry import LaserCodeRegistry
from .missiondata import JtacInfo, MissionData
@@ -221,7 +223,7 @@ class FlotGenerator:
u = random.choices(
manpads, weights=[m.spawn_weight for m in manpads]
)[0]
self.mission.vehicle_group(
vg = self.mission.vehicle_group(
side,
namegen.next_infantry_name(side, u),
u.dcs_unit_type,
@@ -230,6 +232,8 @@ class FlotGenerator:
heading=forward_heading.degrees,
move_formation=PointAction.OffRoad,
)
vehicle = vg.units[0]
GroundForcePainter(faction, vehicle).apply_livery()
return
possible_infantry_units = set(faction.infantry_with_class(UnitClass.INFANTRY))
@@ -246,7 +250,7 @@ class FlotGenerator:
weights=[u.spawn_weight for u in infantry_choices],
k=INFANTRY_GROUP_SIZE,
)
self.mission.vehicle_group(
vg = self.mission.vehicle_group(
side,
namegen.next_infantry_name(side, units[0]),
units[0].dcs_unit_type,
@@ -255,10 +259,12 @@ class FlotGenerator:
heading=forward_heading.degrees,
move_formation=PointAction.OffRoad,
)
vehicle = vg.units[0]
GroundForcePainter(faction, vehicle).apply_livery()
for unit in units[1:]:
position = infantry_position.random_point_within(55, 5)
self.mission.vehicle_group(
vg = self.mission.vehicle_group(
side,
namegen.next_infantry_name(side, unit),
unit.dcs_unit_type,
@@ -267,6 +273,8 @@ class FlotGenerator:
heading=forward_heading.degrees,
move_formation=PointAction.OffRoad,
)
vehicle = vg.units[0]
GroundForcePainter(faction, vehicle).apply_livery()
def _set_reform_waypoint(
self, dcs_group: VehicleGroup, forward_heading: Heading
@@ -769,6 +777,8 @@ class FlotGenerator:
heading: Heading,
) -> VehicleGroup:
cp = self.conflict.front_line.control_point_friendly_to(player)
faction = self.game.faction_for(player)
group = self.mission.vehicle_group(
side,
namegen.next_unit_name(side, unit_type),
@@ -783,5 +793,6 @@ class FlotGenerator:
for c in range(count):
vehicle: Vehicle = group.units[c]
vehicle.player_can_drive = True
GroundForcePainter(faction, vehicle).apply_livery()
return group

View File

@@ -0,0 +1,82 @@
from __future__ import annotations
import logging
import random
from typing import Any, Optional
from dcs.unit import Ship
from dcs.unitgroup import Vehicle
from game.factions.faction import Faction
class GroundForcePainter:
def __init__(self, faction: Faction, vehicle: Vehicle) -> None:
self.faction = faction
self.vehicle = vehicle
def livery_from_faction(self) -> Optional[str]:
faction = self.faction
try:
if (
choices := faction.liveries_overrides_ground_forces.get(
self.vehicle.type
)
) is not None:
return random.choice(choices)
except AttributeError:
logging.warning(
f"Faction {self.faction.name} is missing livery for ground unit {self.vehicle.type}"
)
return None
logging.warning(
f"Faction {self.faction.name} is missing livery for ground unit {self.vehicle.type}"
)
return None
def determine_livery(self) -> Optional[str]:
if (livery := self.livery_from_faction()) is not None:
return livery
return None
def apply_livery(self) -> None:
livery = self.determine_livery()
if livery is None:
return
self.vehicle.livery_id = livery
class NavalForcePainter:
def __init__(self, faction: Faction, vessel: Ship) -> None:
self.faction = faction
self.vessel = vessel
def livery_from_faction(self) -> Optional[str]:
faction = self.faction
try:
if (
choices := faction.liveries_overrides_ground_forces.get(
self.vessel.type
)
) is not None:
return random.choice(choices)
except AttributeError:
logging.warning(
f"Faction {self.faction.name} is missing livery for naval unit {self.vessel.type}"
)
return None
logging.warning(
f"Faction {self.faction.name} is missing livery for naval unit {self.vessel.type}"
)
return None
def determine_livery(self) -> Optional[str]:
if (livery := self.livery_from_faction()) is not None:
return livery
return None
def apply_livery(self) -> None:
livery = self.determine_livery()
if livery is None:
return
self.vessel.livery_id = livery

View File

@@ -400,6 +400,9 @@ class BriefingPage(KneeboardPage):
f"Temperature: {round(self.weather.atmospheric.temperature_celsius)} °C at sea level"
)
writer.text(f"QNH: {qnh_in_hg} inHg / {qnh_mm_hg} mmHg / {qnh_hpa} hPa")
writer.text(
f"Turbulence: {round(self.weather.atmospheric.turbulence_per_10cm)} per 10cm at ground level."
)
fl = self.flight
@@ -636,6 +639,8 @@ class SeadTaskPage(KneeboardPage):
class StrikeTaskPage(KneeboardPage):
"""A kneeboard page containing strike target information."""
WAYPOINT_DESC_MAX_LEN = 35
def __init__(self, flight: FlightData, dark_kneeboard: bool) -> None:
self.flight = flight
self.dark_kneeboard = dark_kneeboard
@@ -655,17 +660,21 @@ class StrikeTaskPage(KneeboardPage):
writer.title(f"{self.flight.callsign} Strike Task Info{custom_name_title}")
writer.table(
[self.target_info_row(t) for t in self.targets],
headers=["Steerpoint", "Description", "Location"],
[self.target_info_row(t, writer) for t in self.targets],
headers=["STPT", "Description", "Location"],
)
writer.write(path)
@staticmethod
def target_info_row(target: NumberedWaypoint) -> list[str]:
def target_info_row(
target: NumberedWaypoint, writer: KneeboardPageWriter
) -> list[str]:
return [
str(target.number),
target.waypoint.pretty_name,
writer.wrap_line(
target.waypoint.pretty_name, StrikeTaskPage.WAYPOINT_DESC_MAX_LEN
),
target.waypoint.position.latlng().format_dms(include_decimal_seconds=True),
]

View File

@@ -1,7 +1,9 @@
from typing import Any, Optional
from dcs import Mission
from dcs.unitgroup import FlyingGroup
from dcs.statics import Fortification
from dcs.unitgroup import FlyingGroup
from game.ato import Flight
from game.ato.flightplans.airassault import AirAssaultFlightPlan
from game.ato.flightwaypointtype import FlightWaypointType
@@ -9,7 +11,6 @@ from game.missiongenerator.missiondata import CargoInfo, LogisticsInfo
from game.settings.settings import Settings
from game.transfers import TransferOrder
ZONE_RADIUS = 300
CRATE_ZONE_RADIUS = 50
@@ -45,7 +46,7 @@ class LogisticsGenerator:
target_zone = f"{self.group.name}TARGET_ZONE"
self.mission.triggers.add_triggerzone(
self.flight.flight_plan.layout.target.position,
self.flight.flight_plan.engagement_distance.meters,
self.flight.flight_plan.ctld_target_zone_radius.meters,
False,
target_zone,
)
@@ -56,8 +57,8 @@ class LogisticsGenerator:
if (
waypoint.waypoint_type
not in [
FlightWaypointType.PICKUP,
FlightWaypointType.DROP_OFF,
FlightWaypointType.PICKUP_ZONE,
FlightWaypointType.DROPOFF_ZONE,
]
or waypoint.only_for_player
and not self.flight.client_count
@@ -68,7 +69,7 @@ class LogisticsGenerator:
self.mission.triggers.add_triggerzone(
waypoint.position, ZONE_RADIUS, False, zone_name
)
if waypoint.waypoint_type == FlightWaypointType.PICKUP:
if waypoint.waypoint_type == FlightWaypointType.PICKUP_ZONE:
pickup_point = waypoint.position
logistics_info.pickup_zone = zone_name
else:

View File

@@ -17,7 +17,6 @@ from game.plugins import LuaPluginManager
from game.theater import TheaterGroundObject
from game.theater.iadsnetwork.iadsrole import IadsRole
from game.utils import escape_string_for_lua
from .missiondata import MissionData
if TYPE_CHECKING:
@@ -41,7 +40,7 @@ class LuaGenerator:
self.inject_plugins()
def generate_plugin_data(self) -> None:
lua_data = LuaData("dcsLiberation")
lua_data = LuaData("dcsRetribution")
install_path = lua_data.add_item("installPath")
install_path.set_value(os.path.abspath("."))
@@ -202,7 +201,7 @@ class LuaGenerator:
for role, connections in node.connections.items():
iads_element.add_data_array(role, connections)
trigger = TriggerStart(comment="Set DCS Liberation data")
trigger = TriggerStart(comment="Set DCS Retribution data")
trigger.add_action(DoScript(String(lua_data.create_operations_lua())))
self.mission.triggerrules.triggers.append(trigger)
@@ -381,7 +380,7 @@ class LuaData(LuaItem):
"""crates the liberation lua script for the dcs mission"""
lua_prefix = """
-- setting configuration table
env.info("DCSLiberation|: setting configuration table")
env.info("DCSRetribution|: setting configuration table")
"""
return lua_prefix + self.serialize()

View File

@@ -3,14 +3,16 @@ from __future__ import annotations
from dataclasses import dataclass, field
from datetime import timedelta
from typing import Optional, TYPE_CHECKING
from game.dcs.aircrafttype import AircraftType
from game.missiongenerator.aircraft.flightdata import FlightData
from game.runways import RunwayData
if TYPE_CHECKING:
from game.radio.radios import RadioFrequency
from game.radio.tacan import TacanChannel
from game.utils import Distance
from uuid import UUID
@dataclass
@@ -94,3 +96,4 @@ class MissionData:
tankers: list[TankerInfo] = field(default_factory=list)
jtacs: list[JtacInfo] = field(default_factory=list)
logistics: list[LogisticsInfo] = field(default_factory=list)
cp_stack: dict[UUID, Distance] = field(default_factory=dict)

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