Compare commits

...

101 Commits
2.3.0 ... 2.3.3

Author SHA1 Message Date
C. Perreau
64c424b9a6 Merge pull request #649 from Khopa/develop_2_3_x
Release 2.3.3
2020-12-31 01:26:19 +01:00
Dan Albert
3716395453 Don't show FOB structure as a target.
This isn't perfect because the auto planner might still target it. We
need a larger refactoring for target iteration so we don't need to
remember all the special rules at each call site. For now, this restores
the 2.3.2 behavior.

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

(cherry picked from commit 69833f66e3)
2020-12-27 19:35:45 -08:00
Dan Albert
ec787b913c Use exact name matching when picking targets.
Inexact name matching targets the first group that partially matches the
given group name. aa|71 will match aa|7.

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

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

(cherry picked from commit 89f313295e)
2020-12-27 13:50:55 -08:00
Dan Albert
7bc7a44c72 Allow managing disbanded sites in CPs.
Fixes https://github.com/Khopa/dcs_liberation/issues/679

(cherry picked from commit 317a882386)
2020-12-27 13:09:15 -08:00
Dan Albert
7bbb1c0822 Don't show repaired TGOs as dead.
We were never resetting the dead state for repaired SAMs. Rather than
tracking that manually, determine liveness from the number of units left
alive.

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

(cherry picked from commit d3b1f6110f)
2020-12-26 16:21:31 -08:00
Dan Albert
6045f4dd91 Fix New Package for naval control points.
Also reordered the tasks so ship-specific tasks appear first.

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

(cherry picked from commit 8be2841bdf)
2020-12-26 14:30:34 -08:00
walterroach
0d0d582bd8 Add F-14A-135-GR Icon 2020-12-26 13:24:45 -06:00
Dan Albert
9f2fab78a1 Flesh out intel displays.
* Add enemy air and ground unit reports.
* Changes the summary to be a comparison of relative strengths rather
  than raw enemy numbers.

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

(cherry picked from commit 3bdf1377c0)
2020-12-25 16:08:10 -08:00
Dan Albert
8f24cf07be Improve layout of intel window.
(cherry picked from commit 1f4516b954)
2020-12-25 16:05:04 -08:00
Dan Albert
17c40234e9 Add basic intel window.
Currently only shows the enemy's economic information.

https://github.com/Khopa/dcs_liberation/issues/658
(cherry picked from commit 1d76ee4871)
2020-12-25 16:04:54 -08:00
Dan Albert
4cecddcdd0 Add basic enemy intel widget.
https://github.com/Khopa/dcs_liberation/issues/658
(cherry picked from commit b53cac4c7a)
2020-12-25 16:04:17 -08:00
Dan Albert
29a0644719 Never generate empty ship groups.
Fixes https://github.com/Khopa/dcs_liberation/issues/391

(cherry picked from commit c833078e71)
2020-12-25 01:35:24 -08:00
Khopa
dcac5b488a Changelog update 2020-12-24 16:11:12 +01:00
Khopa
e1009bdafa Fixed ships group that could be replaced by sam site. Removed the possibility to disband ship groups for now. 2020-12-24 16:09:13 +01:00
Khopa
38ce842ca8 Merge remote-tracking branch 'khopa/master' into develop_2_3_x
# Conflicts:
#	changelog.md
#	game/version.py
#	gen/ground_forces/ai_ground_planner.py
#	pydcs
#	resources/factions/iraq_1991.json
#	resources/factions/russia_2010.json
2020-12-24 13:32:14 +01:00
Khopa
368bf08ade Fixed mypy error 2020-12-24 03:18:53 +01:00
Khopa
c0fa135bf6 Artillery groups would retreat in the wrong direction - fixed (parameters of the find_retreat_point function are a bit confusing 😕 ) 2020-12-24 02:03:12 +01:00
Khopa
86394d8f19 Artillery groups would retreat in the wrong direction - fixed (parameters of the retreat point function are a bit confusing 😕 ) 2020-12-24 02:02:17 +01:00
Khopa
72c233cb0d Fixed possible assertion error when redeploying units which would lead to ground units not being redeployed. 2020-12-24 01:47:44 +01:00
Khopa
04e2c02eff SCUD missile sites will fire on nearest enemy airport by default 2020-12-24 01:26:00 +01:00
Khopa
7362744df2 Changelog update 2020-12-23 22:15:56 +01:00
Khopa
01951b5c32 Reworked emirates campaign 2020-12-23 21:58:39 +01:00
Khopa
f2f52771bd Removed "broken" midgame setting 2020-12-23 21:37:59 +01:00
Khopa
b59167d3ca Changelog update, WW2 factions can recruit AA/AT guns for frontlines. 2020-12-23 18:21:13 +01:00
Khopa
88e466562c Infantry squads can contain a mortar. 2020-12-23 17:53:52 +01:00
Khopa
1f85e5d7f8 Changelog update 2020-12-23 17:25:15 +01:00
Khopa
50471d510e Fixed and added many ground units icons 2020-12-23 17:24:20 +01:00
Khopa
8b7cf2f725 Changelog update 2020-12-23 01:35:19 +01:00
Khopa
282a5109ba Infantry group are always made of 5 units instead of a random amount. 2020-12-23 01:33:49 +01:00
Khopa
3d3b4738d9 Insurgent hard faction name fixed 2020-12-23 01:31:07 +01:00
Khopa
7c29ea836c Infantry is only generated for IFV and APC groups 2020-12-22 23:24:27 +01:00
Khopa
92e9e8c56a Merge remote-tracking branch 'khopa/develop_2_3_x' into develop_2_3_x 2020-12-22 23:23:52 +01:00
Khopa
12bf26223d Added shorad units on frontline 2020-12-22 23:23:32 +01:00
Dan Albert
fc6d4f0990 Add EWRS plugin.
Fixes https://github.com/Khopa/dcs_liberation/issues/323
2020-12-21 21:28:27 +01:00
C. Perreau
df948bde9d Update CONTRIBUTING.md 2020-12-21 14:14:42 +01:00
C. Perreau
203a720ae1 Create CONTRIBUTING.md 2020-12-21 14:14:39 +01:00
C. Perreau
3410f08cfb Create CODE_OF_CONDUCT.md 2020-12-21 14:14:37 +01:00
Khopa
21220141f2 pydcs submodule version update 2020-12-21 14:08:21 +01:00
Khopa
caf2d8436b Changelog update for 2.3.3 2020-12-21 13:56:00 +01:00
Khopa
4cc305fa81 Syrian civil war description updated 2020-12-21 13:55:33 +01:00
Khopa
60f837d0b9 Fixed : AI wouldn't buy artillery units 2020-12-21 13:34:56 +01:00
Emanuele Garofalo
e58ab34a15 Update NATO_Desert_Storm.json 2020-12-21 13:02:01 +01:00
Emanuele Garofalo
d960758ef3 Update iraq_1991.json 2020-12-21 13:01:58 +01:00
Emanuele Garofalo
a36ccdcc39 Update NATO_Desert_Storm.json 2020-12-21 13:01:57 +01:00
Khopa
2c475011a1 Syria terrain update + syrian civil war reworked in miz format 2020-12-21 03:19:17 +01:00
Khopa
bb04ce2abb Golan heights battle scenario fully migrated to miz format 2020-12-20 22:39:15 +01:00
Khopa
9850b22c0a Improved "Golan Heights Campaign Lite" for the Syria map. 2020-12-20 14:56:55 +01:00
Khopa
02ecfebb85 Merge remote-tracking branch 'khopa/develop_2_3_x' into develop_2_3_x 2020-12-19 23:54:28 +01:00
Khopa
a1fed62591 Added "Golan Heights Campaign Lite" for the Syria map. 2020-12-19 23:54:02 +01:00
Dan Albert
778ed6ad91 Update 2.3 branch to 2.3.3 preview. 2020-12-19 13:48:44 -08:00
Dan Albert
3260260dce Move more SAMs off runways in Syria Full.
(cherry picked from commit e5bca224e9)
2020-12-19 12:30:38 -08:00
Dan Albert
70c1290993 Note fixed SAM placement in changelog. 2020-12-19 12:30:38 -08:00
Dan Albert
6bae60c51e Note Iraq 1991 faction in changelog.
(cherry picked from commit 8447c563ea)
2020-12-19 12:30:38 -08:00
Emanuele Garofalo
a45adb6b3a New Faction Iraq 1991
(cherry picked from commit fd61a4b23a)
2020-12-19 12:30:38 -08:00
Dan Albert
476aaf5d3e Update changelog for #616.
(cherry picked from commit 3e4bb88089)
2020-12-19 12:30:38 -08:00
Dan Albert
58187b6969 Put back code to reserve beacon frequencies.
Fixes https://github.com/Khopa/dcs_liberation/issues/616

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

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

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

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

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

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

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

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

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

(cherry picked from commit b9ade2295e)
2020-12-19 12:30:38 -08:00
Dan Albert
8a03a9462b Move more SAMs off runways in Syria Full.
(cherry picked from commit e5bca224e9)
2020-12-19 12:22:27 -08:00
Dan Albert
078466241f Note fixed SAM placement in changelog. 2020-12-19 12:02:34 -08:00
Dan Albert
69a41879bb Note Iraq 1991 faction in changelog.
(cherry picked from commit 8447c563ea)
2020-12-19 11:53:49 -08:00
Emanuele Garofalo
e3524a506b New Faction Iraq 1991
(cherry picked from commit fd61a4b23a)
2020-12-19 11:53:46 -08:00
Dan Albert
9257311896 Update changelog for #616.
(cherry picked from commit 3e4bb88089)
2020-12-19 11:47:02 -08:00
Dan Albert
23e870e416 Put back code to reserve beacon frequencies.
Fixes https://github.com/Khopa/dcs_liberation/issues/616

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

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

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

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

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

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

(cherry picked from commit 745dfc71bc)
2020-12-19 11:46:58 -08:00
walterroach
afb0ac14c4 Fix bad air defense location #617 2020-12-19 13:31:31 -06:00
Dan Albert
5b44580061 Add missing 2.3.2 change to changelog.
(cherry picked from commit 4eac743812)
2020-12-17 16:28:17 -08:00
Dan Albert
ed8ab37bd5 Fix adding custom waypoints.
Fixes https://github.com/Khopa/dcs_liberation/issues/604

(cherry picked from commit 296e6e8e8f)
2020-12-17 16:26:19 -08:00
Khopa
334aab2755 Changelog update 2020-12-18 01:09:14 +01:00
Khopa
419f4f3156 Updated version string 2020-12-18 01:08:34 +01:00
Khopa
ec5a26e8dd Added ZSU-57 sites 2020-12-18 01:05:06 +01:00
Khopa
2b7cd36eea T72B3 and BTR-82A support 2020-12-18 00:42:44 +01:00
Khopa
2f11731052 pydcs version update to include new data export 2020-12-18 00:25:50 +01:00
walterroach
666858f8e2 Fix Ground units ... don't move forward #601 2020-12-17 02:03:02 -06:00
Dan Albert
2288b7f7b2 Fail gracefully when out of radio channels.
Fixes https://github.com/Khopa/dcs_liberation/issues/598

(cherry picked from commit 498af28efb)
2020-12-16 22:24:07 -08:00
Dan Albert
6bb0bdf66e Don't exclude BARCAP targets from culling.
Fixes https://github.com/Khopa/dcs_liberation/issues/597

(cherry picked from commit b9ade2295e)
2020-12-16 18:53:17 -08:00
C. Perreau
44b5f5a919 Merge pull request #596 from Khopa/develop_2_3_x
Release 2.3.1
2020-12-16 21:45:30 +01:00
Khopa
17d37494c2 2.3.1 changelog 2020-12-16 21:14:25 +01:00
Khopa
f0d81e98a0 About dialog update 2020-12-16 21:12:01 +01:00
Khopa
e3b13f7b4a UX : Display a warning message when attempting to buy more aircraft at an already full airfield. 2020-12-16 21:08:48 +01:00
Khopa
ba2686630a Updated version number 2020-12-16 20:51:36 +01:00
Emanuele Garofalo
e195cfa6a0 Replaced previous Syria full map by the new version by Hawkmoon 2020-12-16 20:49:03 +01:00
Emanuele Garofalo
b9fbd1906f Add files via upload
Fixed NATO DESERT STORM FACTIO
2020-12-16 20:47:04 +01:00
Emanuele Garofalo
1f611bafef Add files via upload
New Syria Full map Remastered
2020-12-16 20:46:54 +01:00
walterroach
69096b15ae Fix broken Full Caucasus Map frontline 2020-12-16 09:06:51 -06:00
Dan Albert
a075e62bad Fix easy going CAPs.
Fixes https://github.com/Khopa/dcs_liberation/issues/592

(cherry picked from commit 1ebe367e07)
2020-12-15 22:48:21 -08:00
Khopa
db229f25bf Changelog update 2020-12-16 00:28:59 +01:00
188 changed files with 2730 additions and 1253 deletions

76
CODE_OF_CONDUCT.md Normal file
View File

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

26
CONTRIBUTING.md Normal file
View File

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

View File

@@ -1,16 +1,78 @@
# 2.3.3
## Features/Improvements
* **[Campaigns]** Reworked Golan Heights campaign on Syria, (Added FOB and preset locations for SAMS)
* **[Campaigns]** Added a lite version of the Golan Heights campaign
* **[Campaigns]** Reworked Syrian Civil War campaign (Added FOB and preset locations for SAMS)
* **[Campaigns]** Reworked Emirates campaign
* **[Campaigns]** AA units added to frontlines and updated all factions to include some frontline AA units.
* **[Mission Generator]** Infantry will only be generated for APC and IFV groups
* **[Mission Generator]** Infantry squads size is not randomized anymore
* **[Mission Generator]** Infantry squads can have a mortar.
* **[Mission Generator]** SCUD missiles sites will now fire on enemy controls points in range when possible
* **[Factions]** Updated Nato Desert Storm to include F-14A
* **[Factions]** Updated Iraq 1991 factions to include Zsu-57 and Mig-29A
* **[Factions]** Germany 1944, added Stug III and Stug IV
* **[Factions]** Added factions Insurgents (Hard) with better and more weapons
* **[Plugins]** [The EWRS plugin](https://github.com/Bob7heBuilder/EWRS) is now included.
* **[UI]** Added enemy intelligence summary and details window.
## Fixes:
* **[Factions]** AI would never buy artillery units for the frontline - fixed
* **[Factions]** Removed the F-111 unit from the NATO desert storm faction. (Recruiting it would cause crashes in DCS, since it is not a valid unit)
* **[Campaign]** Automatic redeployment of ground units would sometimes fail - fixed
* **[Mission Generator]** Artillery groups would retreat in the wrong direction - fixed
* **[Units]** Fixed SPG_Stryker_M1128_MGS not being in db
* **[UI]** Fixed and added many missing ground units icons
* **[UI]** Ship groups could be replaced by SAM sites in the UI, which would lead to broken mission being generated - fixed
* **[New Game Wizard]** Removed the "mid game" campaign generator option which is currently broken
* **[Mission Generator]** Empty navy groups will no longer be generated
* **[Mission Generator]** Fixed BAI, SEAD, and DEAD flights ocassionally being assigned the wrong targets.
* **[Flight Planner]** Fixed not being able to plan packages against opfor carriers
* **[UI]** Repaired SAMs no longer show as dead.
* **[UI]** Fixed not being able to manage a disbanded site after disbanding and closing the base menu.
# 2.3.2
## Features/Improvements
* **[Units]** Support for newly added BTR-82A, T-72B3
* **[Units]** Added ZSU-57 AAA sites
* **[Culling]** BARCAP missions no longer create culling exclusion zones.
* **[Flight Planner]** Improved TOT planning. Negative start times no longer occur with TARCAPs and hold times no longer affect planning for flight plans without hold points.
* **[Factions]** Added Iraq 1991 faction (thanks again to Hawkmoon!)
## Fixes:
* **[Mission Generator]** Fix mission generation error when there are too many radio frequency to setup for the Mig-21
* **[Mission Generator]** Fix ground units not moving forward
* **[Mission Generator]** Fixed assigned radio channels overlapping with beacons.
* **[Flight Planner]** Fix creation of custom waypoints.
* **[Campaigns]** Fixed many cases of SAMs spawning on the runways/taxiways in Syria Full.
# 2.3.1
## Features/Improvements
* **[UX]** Added a warning message when the player is attempting to buy more planes at an already full airbase.
* **[Campaigns]** Migrated Syria full map to new format. (Thanks to Hawkmoon)
* **[Faction]** Added NATO desert Storm faction (Thanks to Hawkmoon)
## Fixes:
* **[AI]** CAP flights will engage enemies again.
* **[Campaigns]** Fixed a missing path on the Caucasus Full Map campaign
# 2.3.0
# Features/Improvements
## Features/Improvements
* **[Campaign Map]** Overhauled the campaign model
* **[Campaign Map]** Possible to add FOB as control points
* **[Campaign Map]** Added off-map spawn locations
* **[Campaign AI]** Overhauled AI recruiting behaviour
* **[Campaign AI]** Added AI proucurement for Blue
* **[Campaign AI]** Added AI procurement for Blue
* **[Campaign]** New Campaign: "Black Sea"
* **[Mission Planner]** Possible to move carrier and tarawa on the campaign map
* **[Mission Generator]** Infantry squads on frontline can have manpads
* **[Mission Generator]** Unused aircraft now spawned to allow for OCA strikes
* **[Mission Generator]** Opfor now obeys parking limits
* **[Mission Generator]** Support for Anubis C-130 Hercules mod
* **[Flight Planner]** Added fighter sweep missions.
* **[Flight Planner]** Added BAI missions.
* **[Flight Planner]** Added anti-ship missions.
@@ -21,6 +83,7 @@
* **[QOL]** On liberation startup, your latest save game is loaded automatically
* **[Units]** Reduced starting fuel load for C101
* **[UI]** Inform the user of the weather
* **[UI]** Added toolbar buttons to change map display settings
* **[Game]** Added new Economy options for adjusting income multipliers and starting budgets.
## Fixes :
@@ -34,7 +97,7 @@
# 2.2.1
# Features/Improvements
## Features/Improvements
* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom
* **[Factions]** Added map Persian Gulf full by Plob
* **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately.

View File

@@ -362,12 +362,14 @@ PRICES = {
# armor
Armor.APC_MTLB: 4,
Armor.FDDM_Grad: 5,
Armor.FDDM_Grad: 4,
Armor.ARV_BRDM_2: 6,
Armor.ARV_BTR_RD: 8,
Armor.ARV_BTR_RD: 6,
Armor.APC_BTR_80: 8,
Armor.APC_BTR_82A: 10,
Armor.MBT_T_55: 18,
Armor.MBT_T_72B: 22,
Armor.MBT_T_72B: 20,
Armor.MBT_T_72B3: 25,
Armor.MBT_T_80U: 25,
Armor.MBT_T_90: 30,
Armor.IFV_BMD_1: 8,
@@ -383,6 +385,7 @@ PRICES = {
Armor.ATGM_M1045_HMMWV_TOW: 8,
Armor.IFV_M2A2_Bradley: 12,
Armor.APC_M1126_Stryker_ICV: 10,
Armor.SPG_M1128_Stryker_MGS: 14,
Armor.ATGM_M1134_Stryker: 12,
Armor.MBT_M60A3_Patton: 16,
Armor.MBT_M1A2_Abrams: 25,
@@ -406,6 +409,7 @@ PRICES = {
Artillery.MLRS_BM_21_Grad: 15,
Artillery.MLRS_9K57_Uragan_BM_27: 50,
Artillery.MLRS_9A52_Smerch: 40,
Artillery._2B11_mortar: 4,
Unarmed.Transport_UAZ_469: 3,
Unarmed.Transport_Ural_375: 3,
@@ -434,6 +438,7 @@ PRICES = {
Armor.LAC_M8_Greyhound: 8,
Armor.TD_M10_GMC: 14,
Armor.StuG_III_Ausf__G: 12,
Armor.StuG_IV: 14,
Artillery.M12_GMC: 10,
Artillery.Sturmpanzer_IV_Brummbär: 10,
Armor.Daimler_Armoured_Car: 8,
@@ -481,11 +486,12 @@ PRICES = {
AirDefence.SAM_Stinger_comm_dsr: 4,
AirDefence.SAM_Stinger_comm: 4,
AirDefence.SPAAA_ZSU_23_4_Shilka: 10,
AirDefence.AAA_ZSU_57_2: 12,
AirDefence.AAA_ZU_23_Closed: 6,
AirDefence.AAA_ZU_23_Emplacement: 6,
AirDefence.AAA_ZU_23_on_Ural_375: 8,
AirDefence.AAA_ZU_23_on_Ural_375: 7,
AirDefence.AAA_ZU_23_Insurgent_Closed: 6,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: 8,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375: 7,
AirDefence.AAA_ZU_23_Insurgent: 6,
AirDefence.SAM_SA_18_Igla_MANPADS: 10,
AirDefence.SAM_SA_18_Igla_comm: 8,
@@ -704,6 +710,8 @@ UNIT_BY_TASK = {
Armor.APC_BTR_80,
Armor.APC_BTR_80,
Armor.APC_BTR_80,
Armor.APC_BTR_82A,
Armor.APC_BTR_82A,
Armor.IFV_BMP_1,
Armor.IFV_BMP_1,
Armor.IFV_BMP_1,
@@ -720,6 +728,8 @@ UNIT_BY_TASK = {
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_72B,
Armor.MBT_T_72B3,
Armor.MBT_T_72B3,
Armor.MBT_T_80U,
Armor.MBT_T_80U,
Armor.MBT_T_90,
@@ -748,6 +758,7 @@ UNIT_BY_TASK = {
Armor.APC_M1126_Stryker_ICV,
Armor.APC_M1126_Stryker_ICV,
Armor.APC_M1126_Stryker_ICV,
Armor.SPG_M1128_Stryker_MGS,
Armor.IFV_MCV_80,
Armor.IFV_MCV_80,
Armor.IFV_MCV_80,
@@ -812,6 +823,7 @@ UNIT_BY_TASK = {
Armor.TD_M10_GMC,
Armor.TD_M10_GMC,
Armor.StuG_III_Ausf__G,
Armor.StuG_IV,
Artillery.M12_GMC,
Artillery.Sturmpanzer_IV_Brummbär,
Armor.Daimler_Armoured_Car,
@@ -830,6 +842,30 @@ UNIT_BY_TASK = {
Artillery.M12_GMC,
Artillery.Sturmpanzer_IV_Brummbär,
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
AirDefence.AAA_ZSU_57_2,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.SAM_SA_15_Tor_9A331,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SPAAA_Gepard,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Linebacker_M6,
AirDefence.SAM_Chaparral_M48,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Roland_ADS,
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.AAA_8_8cm_Flak_18,
AirDefence.AAA_8_8cm_Flak_36,
AirDefence.AAA_8_8cm_Flak_37,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_Bofors_40mm,
AirDefence.AAA_M1_37mm,
AirDefence.AA_gun_QF_3_7,
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__TOYOTA_GREEN,
@@ -855,23 +891,6 @@ UNIT_BY_TASK = {
],
AirDefence: [
# those are listed multiple times here to balance prioritization more into lower tier AAs
AirDefence.AAA_Vulcan_M163,
AirDefence.AAA_Vulcan_M163,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Linebacker_M6,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.AAA_ZU_23_Closed,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_Hawk_PCP,
AirDefence.SAM_SA_2_LN_SM_90,
AirDefence.SAM_SA_11_Buk_LN_9A310M1,
],
Reconnaissance: [Unarmed.Transport_M818, Unarmed.Transport_Ural_375, Unarmed.Transport_UAZ_469],
Nothing: [Infantry.Infantry_M4, Infantry.Soldier_AK, ],
@@ -1255,6 +1274,7 @@ INFANTRY: List[VehicleType] = [
Infantry.Soldier_RPG,
Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4,
Infantry.Soldier_M249,
Artillery._2B11_mortar,
Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK,
Infantry.Paratrooper_RPG_16,
Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4,

View File

@@ -152,12 +152,10 @@ class Event:
loss.group.units.remove(loss.unit)
loss.group.units_losts.append(loss.unit)
if not loss.ground_object.alive_unit_count:
loss.ground_object.is_dead = True
def commit_building_losses(self, debriefing: Debriefing) -> None:
for loss in debriefing.building_losses:
loss.ground_object.is_dead = True
loss.ground_object.kill()
self.game.informations.append(Information(
"Building destroyed",
f"{loss.ground_object.dcs_identifier} has been destroyed at "

View File

@@ -1,3 +1,4 @@
import itertools
import logging
import random
import sys
@@ -11,7 +12,6 @@ from dcs.task import CAP, CAS, PinpointStrike
from dcs.vehicles import AirDefence
from game import db
from game.db import PLAYER_BUDGET_BASE, REWARDS
from game.inventory import GlobalAircraftInventory
from game.models.game_stats import GameStats
from game.plugins import LuaPluginManager
@@ -19,12 +19,14 @@ from gen.ato import AirTaskingOrder
from gen.conflictgen import Conflict
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from gen.flights.closestairfields import ObjectiveDistanceCache
from gen.flights.flight import FlightType
from gen.ground_forces.ai_ground_planner import GroundPlanner
from . import persistency
from .debriefing import Debriefing
from .event.event import Event, UnitsDeliveryEvent
from .event.frontlineattack import FrontlineAttackEvent
from .factions.faction import Faction
from .income import Income
from .infos.information import Information
from .procurement import ProcurementAi
from .settings import Settings
@@ -165,30 +167,14 @@ class Game:
front_line.control_point_a,
front_line.control_point_b)
@property
def budget_reward_amount(self) -> int:
reward = PLAYER_BUDGET_BASE * len(self.theater.player_points())
for cp in self.theater.player_points():
for g in cp.ground_objects:
if g.category in REWARDS.keys() and not g.is_dead:
reward += REWARDS[g.category]
return int(reward * self.settings.player_income_multiplier)
def process_player_income(self):
self.budget += self.budget_reward_amount
self.budget += Income(self, player=True).total
def process_enemy_income(self):
# TODO: Clean up save compat.
if not hasattr(self, "enemy_budget"):
self.enemy_budget = 0
production = 0.0
for enemy_point in self.theater.enemy_points():
for g in enemy_point.ground_objects:
if g.category in REWARDS.keys() and not g.is_dead:
production = production + REWARDS[g.category]
self.enemy_budget += production * self.settings.enemy_income_multiplier
self.enemy_budget += Income(self, player=False).total
def units_delivery_event(self, to_cp: ControlPoint) -> UnitsDeliveryEvent:
event = UnitsDeliveryEvent(attacker_name=self.player_name,
@@ -391,9 +377,16 @@ class Game:
if cpoint is not None:
points.append(cpoint)
for package in self.blue_ato.packages:
points.append(package.target.position)
for package in self.red_ato.packages:
packages = itertools.chain(self.blue_ato.packages,
self.red_ato.packages)
for package in packages:
if package.primary_task is FlightType.BARCAP:
# BARCAPs will be planned at most locations on smaller theaters,
# rendering culling fairly useless. BARCAP packages don't really
# need the ground detail since they're defensive. SAMs nearby
# are only interesting if there are enemies in the area, and if
# there are they won't be culled because of the enemy's mission.
continue
points.append(package.target.position)
# Else 0,0, since we need a default value

64
game/income.py Normal file
View File

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

View File

@@ -199,10 +199,14 @@ class Operation:
@classmethod
def create_radio_registries(cls) -> None:
unique_map_frequencies = set() # type: Set[RadioFrequency]
unique_map_frequencies: Set[RadioFrequency] = set()
cls._create_tacan_registry(unique_map_frequencies)
cls._create_radio_registry(unique_map_frequencies)
assert cls.radio_registry is not None
for frequency in unique_map_frequencies:
cls.radio_registry.reserve(frequency)
@classmethod
def assign_channels_to_flights(cls, flights: List[FlightData],
air_support: AirSupport) -> None:
@@ -256,8 +260,8 @@ class Operation:
unique_map_frequencies.add(data.atc.vhf_fm)
unique_map_frequencies.add(data.atc.vhf_am)
unique_map_frequencies.add(data.atc.uhf)
# No need to reserve ILS or TACAN because those are in the
# beacon list.
# No need to reserve ILS or TACAN because those are in the
# beacon list.
@classmethod
def _generate_ground_units(cls):

View File

@@ -1,16 +1,16 @@
from __future__ import annotations
from dataclasses import dataclass
import math
import random
from dataclasses import dataclass
from typing import Iterator, List, Optional, TYPE_CHECKING, Type
from dcs.task import CAP, CAS
from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.unittype import FlyingType, VehicleType
from game import db
from game.factions.faction import Faction
from game.theater import ControlPoint, MissionTarget
from game.theater import ControlPoint, MissionTarget, TYPE_SHORAD
from gen.flights.ai_flight_planner_db import (
capable_aircraft_for_task,
preferred_aircraft_for_task,
@@ -74,15 +74,25 @@ class ProcurementAi:
return budget
def random_affordable_ground_unit(
self, budget: int) -> Optional[Type[VehicleType]]:
affordable_units = [u for u in self.faction.frontline_units if
self, budget: int, cp: ControlPoint) -> Optional[Type[VehicleType]]:
affordable_units = [u for u in self.faction.frontline_units + self.faction.artillery_units if
db.PRICES[u] <= budget]
total_number_aa = cp.base.total_frontline_aa + cp.pending_frontline_aa_deliveries_count
total_non_aa = cp.base.total_armor + cp.pending_deliveries_count - total_number_aa
max_aa = math.ceil(total_non_aa/8)
# Limit the number of AA units the AI will buy
if not total_number_aa < max_aa:
for unit in [u for u in affordable_units if u in TYPE_SHORAD]:
affordable_units.remove(unit)
if not affordable_units:
return None
return random.choice(affordable_units)
def reinforce_front_line(self, budget: int) -> int:
if not self.faction.frontline_units:
if not self.faction.frontline_units and not self.faction.artillery_units:
return budget
while budget > 0:
@@ -91,7 +101,7 @@ class ProcurementAi:
break
cp = random.choice(candidates)
unit = self.random_affordable_ground_unit(budget)
unit = self.random_affordable_ground_unit(budget, cp)
if unit is None:
# Can't afford any more units.
break

View File

@@ -9,6 +9,7 @@ from dcs.unittype import FlyingType, UnitType, VehicleType
from dcs.vehicles import AirDefence, Armor
from game import db
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
STRENGTH_AA_ASSEMBLE_MIN = 0.2
PLANES_SCRAMBLE_MIN_BASE = 2
@@ -36,6 +37,10 @@ class Base:
def total_armor(self) -> int:
return sum(self.armor.values())
@property
def total_frontline_aa(self) -> int:
return sum([v for k, v in self.armor.items() if k in TYPE_SHORAD])
@property
def total_aa(self) -> int:
return sum(self.aa.values())
@@ -98,11 +103,11 @@ class Base:
self.armor = {k: v for k, v in self.armor.items() if k in applicable_units}
def commision_units(self, units: typing.Dict[typing.Any, int]):
for value in units.values():
assert value > 0
assert value == math.floor(value)
for unit_type, unit_count in units.items():
if unit_count <= 0:
continue
for_task = db.unit_task(unit_type)
target_dict = None

View File

@@ -487,11 +487,11 @@ class ConflictTheater:
for inclusion_zone in self.landmap[0]:
nearest_pair = ops.nearest_points(point, inclusion_zone)
nearest_points.append(nearest_pair[1])
min_distance = None # type: Optional[geometry.Point]
nearest_point = None # type: Optional[geometry.Point]
for pt in nearest_points:
min_distance = point.distance(nearest_points[0]) # type: geometry.Point
nearest_point = nearest_points[0] # type: geometry.Point
for pt in nearest_points[1:]:
distance = point.distance(pt)
if not min_distance or distance < min_distance:
if distance < min_distance:
min_distance = distance
nearest_point = pt
assert isinstance(nearest_point, geometry.Point)
@@ -503,8 +503,13 @@ class ConflictTheater:
)
return new_point
def control_points_for(self, player: bool) -> Iterator[ControlPoint]:
for point in self.controlpoints:
if point.captured == player:
yield point
def player_points(self) -> List[ControlPoint]:
return [point for point in self.controlpoints if point.captured]
return list(self.control_points_for(player=True))
def conflicts(self, from_player=True) -> Iterator[FrontLine]:
for cp in [x for x in self.controlpoints if x.captured == from_player]:
@@ -512,7 +517,7 @@ class ConflictTheater:
yield FrontLine(cp, connected_point, self)
def enemy_points(self) -> List[ControlPoint]:
return [point for point in self.controlpoints if not point.captured]
return list(self.control_points_for(player=False))
def closest_control_point(self, point: Point) -> ControlPoint:
closest = self.controlpoints[0]

View File

@@ -20,6 +20,7 @@ from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.unittype import FlyingType
from game import db
from gen.ground_forces.ai_ground_planner_db import TYPE_SHORAD
from gen.runways import RunwayAssigner, RunwayData
from gen.ground_forces.combat_stance import CombatStance
from .base import Base
@@ -457,6 +458,26 @@ class ControlPoint(MissionTarget, ABC):
u.position.x = u.position.x + delta.x
u.position.y = u.position.y + delta.y
@property
def pending_frontline_aa_deliveries_count(self):
"""
Get number of pending frontline aa units
"""
if self.pending_unit_deliveries:
return sum([v for k,v in self.pending_unit_deliveries.units.items() if k in TYPE_SHORAD])
else:
return 0
@property
def pending_deliveries_count(self):
"""
Get number of pending units
"""
if self.pending_unit_deliveries:
return sum([v for k, v in self.pending_unit_deliveries.units.items()])
else:
return 0
class Airfield(ControlPoint):
@@ -529,7 +550,6 @@ class NavalControlPoint(ControlPoint, ABC):
return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
yield from super().mission_types(for_player)
from gen.flights.flight import FlightType
if self.is_friendly(for_player):
yield from [
@@ -540,6 +560,7 @@ class NavalControlPoint(ControlPoint, ABC):
]
else:
yield FlightType.ANTISHIP
yield from super().mission_types(for_player)
@property
def heading(self) -> int:

View File

@@ -85,10 +85,13 @@ class TheaterGroundObject(MissionTarget):
self.dcs_identifier = dcs_identifier
self.airbase_group = airbase_group
self.sea_object = sea_object
self.is_dead = False
# TODO: There is never more than one group.
self.groups: List[Group] = []
@property
def is_dead(self) -> bool:
return self.alive_unit_count == 0
@property
def units(self) -> List[Unit]:
"""
@@ -161,6 +164,9 @@ class BuildingGroundObject(TheaterGroundObject):
sea_object=False
)
self.object_id = object_id
# Other TGOs track deadness based on the number of alive units, but
# buildings don't have groups assigned to the TGO.
self._dead = False
@property
def group_name(self) -> str:
@@ -171,6 +177,15 @@ class BuildingGroundObject(TheaterGroundObject):
def waypoint_name(self) -> str:
return f"{super().waypoint_name} #{self.object_id}"
@property
def is_dead(self) -> bool:
if not hasattr(self, "_dead"):
self._dead = False
return self._dead
def kill(self) -> None:
self._dead = True
class NavalGroundObject(TheaterGroundObject):
def mission_types(self, for_player: bool) -> Iterator[FlightType]:

View File

@@ -2,7 +2,7 @@ from pathlib import Path
def _build_version_string() -> str:
components = ["2.3.0"]
components = ["2.3.3"]
build_number_path = Path("resources/buildnumber")
if build_number_path.exists():
with build_number_path.open("r") as build_number_file:

View File

@@ -20,20 +20,20 @@ from dcs.planes import (
B_17G,
B_52H,
Bf_109K_4,
C_101EB,
C_101CC,
C_101EB,
FW_190A8,
FW_190D9,
F_14B,
I_16,
JF_17,
Ju_88A4,
PlaneType,
P_47D_30,
P_47D_30bl1,
P_47D_40,
P_51D,
P_51D_30_NA,
PlaneType,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
Su_33,
@@ -59,16 +59,15 @@ from dcs.task import (
OptReactOnThreat,
OptRestrictJettison,
OrbitAction,
PinpointStrike,
RunwayAttack,
SEAD,
StartCommand,
Targets,
Task,
WeaponType,
PinpointStrike,
)
from dcs.terrain.terrain import Airport, NoParkingSlotError
from dcs.translation import String
from dcs.triggers import Event, TriggerOnce, TriggerRule
from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup
from dcs.unittype import FlyingType, UnitType
@@ -99,7 +98,6 @@ from gen.flights.flight import (
)
from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio
from gen.runways import RunwayData
from .conflictgen import Conflict
from .flights.flightplan import (
CasFlightPlan,
LoiterFlightPlan,
@@ -108,7 +106,6 @@ from .flights.flightplan import (
)
from .flights.traveltime import GroundSpeed, TotEstimator
from .naming import namegen
from .runways import RunwayAssigner
if TYPE_CHECKING:
from game import Game
@@ -1349,7 +1346,7 @@ class AircraftConflictGenerator:
# And setting *our* waypoint TOT causes the takeoff time to show up in
# the player's kneeboard.
waypoint.tot = estimator.takeoff_time_for_flight(flight)
waypoint.tot = flight.flight_plan.takeoff_time()
# And finally assign it to the FlightData info so it shows correctly in
# the briefing.
self.flights[-1].departure_delay = start_time
@@ -1477,10 +1474,7 @@ class BaiIngressBuilder(PydcsWaypointBuilder):
target_group = self.package.target
if isinstance(target_group, TheaterGroundObject):
# Match search is used due to TheaterGroundObject.name not matching
# the Mission group name because of SkyNet prefixes.
tgroup = self.mission.find_group(target_group.group_name,
search="match")
tgroup = self.mission.find_group(target_group.group_name)
if tgroup is not None:
task = AttackGroup(tgroup.id, weapon_type=WeaponType.Auto)
task.params["attackQtyLimit"] = False
@@ -1530,10 +1524,7 @@ class DeadIngressBuilder(PydcsWaypointBuilder):
target_group = self.package.target
if isinstance(target_group, TheaterGroundObject):
# Match search is used due to TheaterGroundObject.name not matching
# the Mission group name because of SkyNet prefixes.
tgroup = self.mission.find_group(target_group.group_name,
search="match")
tgroup = self.mission.find_group(target_group.group_name)
if tgroup is not None:
task = AttackGroup(tgroup.id, weapon_type=WeaponType.Guided)
task.params["expend"] = "All"
@@ -1596,10 +1587,7 @@ class SeadIngressBuilder(PydcsWaypointBuilder):
target_group = self.package.target
if isinstance(target_group, TheaterGroundObject):
# Match search is used due to TheaterGroundObject.name not matching
# the Mission group name because of SkyNet prefixes.
tgroup = self.mission.find_group(target_group.group_name,
search="match")
tgroup = self.mission.find_group(target_group.group_name)
if tgroup is not None:
waypoint.add_task(EngageTargetsInZone(
position=tgroup.position,
@@ -1756,15 +1744,11 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
f"{flight_plan_type} does not define a patrol.")
return waypoint
racetrack = ControlledTask(OrbitAction(
altitude=waypoint.alt,
pattern=OrbitAction.OrbitPattern.RaceTrack
))
self.set_waypoint_tot(
waypoint, self.flight.flight_plan.patrol_start_time)
racetrack.stop_after_time(
int(self.flight.flight_plan.patrol_end_time.total_seconds()))
waypoint.add_task(racetrack)
# NB: It's important that the engage task comes before the orbit task.
# Though they're on the same waypoint, if the orbit task comes first it
# is their first priority and they will not engage any targets because
# they're fully focused on orbiting. If the STE task is first, they will
# engage targets if available and orbit if they find nothing to shoot.
# TODO: Move the properties of this task into the flight plan?
# CAP is the only current user of this so it's not a big deal, but might
@@ -1775,6 +1759,16 @@ class RaceTrackBuilder(PydcsWaypointBuilder):
waypoint.tasks.append(EngageTargets(max_distance=nm_to_meter(50),
targets=[Targets.All.Air]))
racetrack = ControlledTask(OrbitAction(
altitude=waypoint.alt,
pattern=OrbitAction.OrbitPattern.RaceTrack
))
self.set_waypoint_tot(
waypoint, self.flight.flight_plan.patrol_start_time)
racetrack.stop_after_time(
int(self.flight.flight_plan.patrol_end_time.total_seconds()))
waypoint.add_task(racetrack)
return waypoint

View File

@@ -50,6 +50,8 @@ FIGHT_DISTANCE = 3500
RANDOM_OFFSET_ATTACK = 250
INFANTRY_GROUP_SIZE = 5
@dataclass(frozen=True)
class JtacInfo:
@@ -226,7 +228,7 @@ class GroundConflictGenerator:
heading=forward_heading,
move_formation=PointAction.OffRoad)
for i in range(random.randint(3, 10)):
for i in range(INFANTRY_GROUP_SIZE):
u = random.choice(possible_infantry_units)
position = infantry_position.random_point_within(55, 5)
self.mission.vehicle_group(
@@ -281,7 +283,7 @@ class GroundConflictGenerator:
# Hold position
dcs_group.points[1].tasks.append(Hold())
retreat = self.find_retreat_point(dcs_group, heading_sum(forward_heading, 180), (int)(RETREAT_DISTANCE/3))
retreat = self.find_retreat_point(dcs_group, forward_heading, (int)(RETREAT_DISTANCE/3))
dcs_group.add_waypoint(dcs_group.position.point_from_heading(forward_heading, 1), PointAction.OffRoad)
dcs_group.points[2].tasks.append(Hold())
dcs_group.add_waypoint(retreat, PointAction.OffRoad)
@@ -675,12 +677,14 @@ class GroundConflictGenerator:
else:
g.set_skill(self.game.settings.enemy_vehicle_skill)
positioned_groups.append((g, group))
self.gen_infantry_group_for_group(
g,
is_player,
self.mission.country(country),
opposite_heading(spawn_heading)
)
if group.role in [CombatGroupRole.APC, CombatGroupRole.IFV]:
self.gen_infantry_group_for_group(
g,
is_player,
self.mission.country(country),
opposite_heading(spawn_heading)
)
else:
logging.warning(f"Unable to get valid position for {group}")

View File

@@ -32,6 +32,9 @@ class ChineseNavyGroupGenerator(ShipGroupGenerator):
else:
include_cc = False
if not any([include_frigate, include_dd, include_cc]):
include_frigate = True
if include_frigate:
self.add_unit(Type_054A_Frigate, "FF1", self.position.x + 1200, self.position.y + 900, self.heading)
self.add_unit(Type_054A_Frigate, "FF2", self.position.x + 1200, self.position.y - 900, self.heading)

View File

@@ -35,6 +35,9 @@ class RussianNavyGroupGenerator(ShipGroupGenerator):
else:
include_cc = False
if not any([include_frigate, include_dd, include_cc]):
include_frigate = True
if include_frigate:
frigate_type = random.choice([FFL_1124_4_Grisha, FSG_1241_1MP_Molniya])
self.add_unit(frigate_type, "FF1", self.position.x + 1200, self.position.y + 900, self.heading)

View File

@@ -73,10 +73,17 @@ class FlightPlan:
"""Iterates over all waypoints in the flight plan, in order."""
raise NotImplementedError
@property
def edges(self) -> Iterator[Tuple[FlightWaypoint, FlightWaypoint]]:
def edges(
self, until: Optional[FlightWaypoint] = None
) -> Iterator[Tuple[FlightWaypoint, FlightWaypoint]]:
"""A list of all paths between waypoints, in order."""
return zip(self.waypoints, self.waypoints[1:])
waypoints = self.waypoints
if until is None:
last_index = len(waypoints)
else:
last_index = waypoints.index(until) + 1
return zip(self.waypoints[:last_index], self.waypoints[1:last_index])
def best_speed_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> int:
@@ -137,7 +144,6 @@ class FlightPlan:
"""Joker fuel value for the FlightPlan
"""
return self.bingo_fuel + 1000
def max_distance_from(self, cp: ControlPoint) -> int:
"""Returns the farthest waypoint of the flight plan from a ControlPoint.
@@ -156,26 +162,18 @@ class FlightPlan:
"""
return timedelta()
# Not cached because changes to the package might alter the formation speed.
@property
def travel_time_to_target(self) -> Optional[timedelta]:
"""The estimated time between the first waypoint and the target."""
if self.tot_waypoint is None:
return None
return self._travel_time_to_waypoint(self.tot_waypoint)
def _travel_time_to_waypoint(
self, destination: FlightWaypoint) -> timedelta:
total = timedelta()
for previous_waypoint, waypoint in self.edges:
total += self.travel_time_between_waypoints(previous_waypoint,
waypoint)
if waypoint == destination:
break
else:
if destination not in self.waypoints:
raise PlanningError(
f"Did not find destination waypoint {destination} in "
f"waypoints for {self.flight}")
for previous_waypoint, waypoint in self.edges(until=destination):
total += self.travel_time_between_waypoints(previous_waypoint,
waypoint)
return total
def travel_time_between_waypoints(self, a: FlightWaypoint,
@@ -196,10 +194,59 @@ class FlightPlan:
def dismiss_escort_at(self) -> Optional[FlightWaypoint]:
return None
def takeoff_time(self) -> Optional[timedelta]:
tot_waypoint = self.tot_waypoint
if tot_waypoint is None:
return None
time = self.tot_for_waypoint(tot_waypoint)
if time is None:
return None
time += self.tot_offset
return time - self._travel_time_to_waypoint(tot_waypoint)
def startup_time(self) -> Optional[timedelta]:
takeoff_time = self.takeoff_time()
if takeoff_time is None:
return None
start_time = (takeoff_time - self.estimate_startup() -
self.estimate_ground_ops())
# In case FP math has given us some barely below zero time, round to
# zero.
if math.isclose(start_time.total_seconds(), 0):
return timedelta()
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
# and they're not interesting from a mission planning perspective so we
# don't want them in the UI.
#
# Round down so *barely* above zero start times are just zero.
return timedelta(seconds=math.floor(start_time.total_seconds()))
def estimate_startup(self) -> timedelta:
if self.flight.start_type == "Cold":
if self.flight.client_count:
return timedelta(minutes=10)
else:
# The AI doesn't seem to have a real startup procedure.
return timedelta(minutes=2)
return timedelta()
def estimate_ground_ops(self) -> timedelta:
if self.flight.start_type in ("Runway", "In Flight"):
return timedelta()
if self.flight.from_cp.is_fleet:
return timedelta(minutes=2)
else:
return timedelta(minutes=5)
@dataclass(frozen=True)
class LoiterFlightPlan(FlightPlan):
hold: FlightWaypoint
hold_duration: timedelta
def iter_waypoints(self) -> Iterator[FlightWaypoint]:
raise NotImplementedError
@@ -221,6 +268,17 @@ class LoiterFlightPlan(FlightPlan):
return self.push_time
return None
def travel_time_between_waypoints(self, a: FlightWaypoint,
b: FlightWaypoint) -> timedelta:
travel_time = super().travel_time_between_waypoints(a, b)
if a != self.hold:
return travel_time
try:
return travel_time + self.hold_duration
except AttributeError:
# Save compat for 2.3.
return travel_time + timedelta(minutes=5)
@dataclass(frozen=True)
class FormationFlightPlan(LoiterFlightPlan):
@@ -254,7 +312,7 @@ class FormationFlightPlan(LoiterFlightPlan):
all of its formation waypoints.
"""
speeds = []
for previous_waypoint, waypoint in self.edges:
for previous_waypoint, waypoint in self.edges():
if waypoint in self.package_speed_waypoints:
speeds.append(self.best_speed_between_waypoints(
previous_waypoint, waypoint))
@@ -486,7 +544,7 @@ class StrikeFlightPlan(FormationFlightPlan):
"""The estimated time between the first waypoint and the target."""
destination = self.tot_waypoint
total = timedelta()
for previous_waypoint, waypoint in self.edges:
for previous_waypoint, waypoint in self.edges():
if waypoint == self.tot_waypoint:
# For anything strike-like the TOT waypoint is the *flight's*
# mission target, but to synchronize with the rest of the
@@ -846,6 +904,7 @@ class FlightPlanBuilder:
lead_time=timedelta(minutes=5),
takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
hold_duration=timedelta(minutes=5),
sweep_start=start,
sweep_end=end,
land=builder.land(flight.arrival),
@@ -1050,6 +1109,7 @@ class FlightPlanBuilder:
flight=flight,
takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
hold_duration=timedelta(minutes=5),
join=builder.join(self.package.waypoints.join),
ingress=ingress,
targets=[target],
@@ -1196,6 +1256,7 @@ class FlightPlanBuilder:
flight=flight,
takeoff=builder.takeoff(flight.departure),
hold=builder.hold(self._hold_point(flight)),
hold_duration=timedelta(minutes=5),
join=builder.join(self.package.waypoints.join),
ingress=builder.ingress(ingress_type,
self.package.waypoints.ingress, location),

View File

@@ -89,68 +89,23 @@ class TravelTime:
# TODO: Most if not all of this should move into FlightPlan.
class TotEstimator:
# An extra five minutes given as wiggle room. Expected to be spent at the
# hold point performing any last minute configuration.
HOLD_TIME = timedelta(minutes=5)
def __init__(self, package: Package) -> None:
self.package = package
def mission_start_time(self, flight: Flight) -> timedelta:
takeoff_time = self.takeoff_time_for_flight(flight)
if takeoff_time is None:
@staticmethod
def mission_start_time(flight: Flight) -> timedelta:
startup_time = flight.flight_plan.startup_time()
if startup_time is None:
# Could not determine takeoff time, probably due to a custom flight
# plan. Start immediately.
return timedelta()
startup_time = self.estimate_startup(flight)
ground_ops_time = self.estimate_ground_ops(flight)
start_time = takeoff_time - startup_time - ground_ops_time
# In case FP math has given us some barely below zero time, round to
# zero.
if math.isclose(start_time.total_seconds(), 0):
return timedelta()
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
# and they're not interesting from a mission planning perspective so we
# don't want them in the UI.
#
# Round down so *barely* above zero start times are just zero.
return timedelta(seconds=math.floor(start_time.total_seconds()))
def takeoff_time_for_flight(self, flight: Flight) -> Optional[timedelta]:
travel_time = self.travel_time_to_rendezvous_or_target(flight)
if travel_time is None:
from gen.flights.flightplan import CustomFlightPlan
if not isinstance(flight.flight_plan, CustomFlightPlan):
logging.warning(
"Found no rendezvous or target point. Cannot estimate "
f"takeoff time takeoff time for {flight}.")
return None
from gen.flights.flightplan import FormationFlightPlan
if isinstance(flight.flight_plan, FormationFlightPlan):
tot = flight.flight_plan.tot_for_waypoint(
flight.flight_plan.join)
if tot is None:
logging.warning(
"Could not determine the TOT of the join point. Takeoff "
f"time for {flight} will be immediate.")
return None
else:
tot_waypoint = flight.flight_plan.tot_waypoint
if tot_waypoint is None:
tot = self.package.time_over_target
else:
tot = flight.flight_plan.tot_for_waypoint(tot_waypoint)
if tot is None:
logging.error(f"TOT waypoint for {flight} has no TOT")
tot = self.package.time_over_target
return tot - travel_time - self.HOLD_TIME
return startup_time
def earliest_tot(self) -> timedelta:
earliest_tot = max((
self.earliest_tot_for_flight(f) for f in self.package.flights
)) + self.HOLD_TIME
))
# Trim microseconds. DCS doesn't handle sub-second resolution for tasks,
# and they're not interesting from a mission planning perspective so we
@@ -159,7 +114,8 @@ class TotEstimator:
# Round up so we don't get negative start times.
return timedelta(seconds=math.ceil(earliest_tot.total_seconds()))
def earliest_tot_for_flight(self, flight: Flight) -> timedelta:
@staticmethod
def earliest_tot_for_flight(flight: Flight) -> timedelta:
"""Estimate fastest time from mission start to the target position.
For BARCAP flights, this is time to race track start. This ensures that
@@ -175,51 +131,18 @@ class TotEstimator:
The earliest possible TOT for the given flight in seconds. Returns 0
if an ingress point cannot be found.
"""
time_to_target = self.travel_time_to_target(flight)
if time_to_target is None:
# Clear the TOT, calculate the startup time. Negating the result gives
# the earliest possible start time.
orig_tot = flight.package.time_over_target
try:
flight.package.time_over_target = timedelta()
time = flight.flight_plan.startup_time()
finally:
flight.package.time_over_target = orig_tot
if time is None:
logging.warning(f"Cannot estimate TOT for {flight}")
# Return 0 so this flight's travel time does not affect the rest
# of the package.
return timedelta()
# Account for TOT offsets for the flight plan. An offset of -2 minutes
# means the flight's TOT is 2 minutes ahead of the package's so it needs
# an extra two minutes.
offset = -flight.flight_plan.tot_offset
startup = self.estimate_startup(flight)
ground_ops = self.estimate_ground_ops(flight)
return startup + ground_ops + time_to_target + offset
@staticmethod
def estimate_startup(flight: Flight) -> timedelta:
if flight.start_type == "Cold":
if flight.client_count:
return timedelta(minutes=10)
else:
# The AI doesn't seem to have a real startup procedure.
return timedelta(minutes=2)
return timedelta()
@staticmethod
def estimate_ground_ops(flight: Flight) -> timedelta:
if flight.start_type in ("Runway", "In Flight"):
return timedelta()
if flight.from_cp.is_fleet:
return timedelta(minutes=2)
else:
return timedelta(minutes=5)
@staticmethod
def travel_time_to_target(flight: Flight) -> Optional[timedelta]:
if flight.flight_plan is None:
return None
return flight.flight_plan.travel_time_to_target
@staticmethod
def travel_time_to_rendezvous_or_target(
flight: Flight) -> Optional[timedelta]:
if flight.flight_plan is None:
return None
from gen.flights.flightplan import FormationFlightPlan
if isinstance(flight.flight_plan, FormationFlightPlan):
return flight.flight_plan.travel_time_to_rendezvous
return flight.flight_plan.travel_time_to_target
return -time

View File

@@ -3,178 +3,14 @@ from enum import Enum
from typing import Dict, List
from dcs.unittype import VehicleType
from dcs.vehicles import Armor, Artillery, Infantry, Unarmed
import pydcs_extensions.frenchpack.frenchpack as frenchpack
from game.theater import ControlPoint
from gen.ground_forces.ai_ground_planner_db import *
from gen.ground_forces.combat_stance import CombatStance
TYPE_TANKS = [
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_80U,
Armor.MBT_T_90,
Armor.MBT_Leopard_2,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leclerc,
Armor.MBT_Challenger_II,
Armor.MBT_M1A2_Abrams,
Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_Mk__4,
Armor.ZTZ_96B,
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.StuG_IV,
Armor.CT_Centaur_IV,
Armor.CT_Cromwell_IV,
Armor.HIT_Churchill_VII,
Armor.LT_Mk_VII_Tetrarch,
# Mods
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__KAMIKAZE,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
]
TYPE_ATGM = [
Armor.ATGM_M1045_HMMWV_TOW,
Armor.ATGM_M1134_Stryker,
Armor.IFV_BMP_2,
# WW2 (Tank Destroyers)
Armor.M30_Cargo_Carrier,
Armor.TD_Jagdpanzer_IV,
Armor.TD_Jagdpanther_G1,
Armor.TD_M10_GMC,
# Mods
frenchpack.VBAE_CRAB_MMP,
frenchpack.VAB_MEPHISTO,
frenchpack.TRM_2000_PAMELA,
]
TYPE_IFV = [
Armor.IFV_BMP_3,
Armor.IFV_BMP_2,
Armor.IFV_BMP_1,
Armor.IFV_Marder,
Armor.IFV_MCV_80,
Armor.IFV_LAV_25,
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.IFV_M2A2_Bradley,
Armor.IFV_BMD_1,
Armor.ZBD_04A,
# WW2
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.LAC_M8_Greyhound,
Armor.Daimler_Armoured_Car,
# Mods
frenchpack.ERC_90,
frenchpack.VBAE_CRAB,
frenchpack.VAB_T20_13
]
TYPE_APC = [
Armor.APC_M1043_HMMWV_Armament,
Armor.APC_M1126_Stryker_ICV,
Armor.APC_M113,
Armor.APC_BTR_80,
Armor.APC_MTLB,
Armor.APC_M2A1,
Armor.APC_Cobra,
Armor.APC_Sd_Kfz_251,
Armor.APC_AAV_7,
Armor.TPz_Fuchs,
Armor.ARV_BRDM_2,
Armor.ARV_BTR_RD,
Armor.FDDM_Grad,
# WW2
Armor.APC_M2A1,
Armor.APC_Sd_Kfz_251,
# Mods
frenchpack.VAB__50,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
]
TYPE_ARTILLERY = [
Artillery.MLRS_9A52_Smerch,
Artillery.SPH_2S1_Gvozdika,
Artillery.SPH_2S3_Akatsia,
Artillery.MLRS_BM_21_Grad,
Artillery.MLRS_9K57_Uragan_BM_27,
Artillery.SPH_M109_Paladin,
Artillery.MLRS_M270,
Artillery.SPH_2S9_Nona,
Artillery.SpGH_Dana,
Artillery.SPH_2S19_Msta,
Artillery.MLRS_FDDM,
# WW2
Artillery.Sturmpanzer_IV_Brummbär,
Artillery.M12_GMC
]
TYPE_LOGI = [
Unarmed.Transport_M818,
Unarmed.Transport_KAMAZ_43101,
Unarmed.Transport_Ural_375,
Unarmed.Transport_GAZ_66,
Unarmed.Transport_GAZ_3307,
Unarmed.Transport_GAZ_3308,
Unarmed.Transport_Ural_4320_31_Armored,
Unarmed.Transport_Ural_4320T,
Unarmed.Blitz_3_6_6700A,
Unarmed.Kübelwagen_82,
Unarmed.Sd_Kfz_7,
Unarmed.Sd_Kfz_2,
Unarmed.Willys_MB,
Unarmed.Land_Rover_109_S3,
Unarmed.Land_Rover_101_FC,
# Mods
frenchpack.VBL,
frenchpack.VAB,
]
TYPE_INFANTRY = [
Infantry.Infantry_Soldier_Insurgents,
Infantry.Soldier_AK,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_Mauser_98,
Infantry.Infantry_SMLE_No_4_Mk_1,
Infantry.Georgian_soldier_with_M4,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_RPG_16,
Infantry.Soldier_M249,
Infantry.Infantry_M4,
Infantry.Soldier_RPG,
]
MAX_COMBAT_GROUP_PER_CP = 10
class CombatGroupRole(Enum):
TANK = 1
APC = 2
@@ -222,6 +58,7 @@ class CombatGroup:
s += "UNITS " + self.units[0].name + " * " + str(len(self.units))
return s
class GroundPlanner:
def __init__(self, cp:ControlPoint, game):
@@ -241,7 +78,6 @@ class GroundPlanner:
self.units_per_cp[cp.id] = []
self.reserve: List[CombatGroup] = []
def plan_groundwar(self):
if hasattr(self.cp, 'stance'):
@@ -273,6 +109,9 @@ class GroundPlanner:
elif key in TYPE_ATGM:
collection = self.atgm_group
role = CombatGroupRole.ATGM
elif key in TYPE_SHORAD:
collection = self.shorad_groups
role = CombatGroupRole.SHORAD
else:
print("Warning unit type not handled by ground generator")
print(key)
@@ -280,12 +119,16 @@ class GroundPlanner:
available = self.cp.base.armor[key]
while available > 0:
n = random.choice(group_size_choice)
if n > available:
if available >= 2:
n = 2
else:
n = 1
if role == CombatGroupRole.SHORAD:
n = 1
else:
n = random.choice(group_size_choice)
if n > available:
if available >= 2:
n = 2
else:
n = 1
available -= n
group = CombatGroup(role)

View File

@@ -0,0 +1,199 @@
from dcs.vehicles import AirDefence, Infantry, Unarmed, Artillery, Armor
from pydcs_extensions.frenchpack import frenchpack
TYPE_TANKS = [
Armor.MBT_T_55,
Armor.MBT_T_72B,
Armor.MBT_T_72B3,
Armor.MBT_T_80U,
Armor.MBT_T_90,
Armor.MBT_Leopard_2,
Armor.MBT_Leopard_1A3,
Armor.MBT_Leclerc,
Armor.MBT_Challenger_II,
Armor.MBT_M1A2_Abrams,
Armor.MBT_M60A3_Patton,
Armor.MBT_Merkava_Mk__4,
Armor.ZTZ_96B,
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
Armor.HT_Pz_Kpfw_VI_Tiger_I,
Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II,
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.StuG_IV,
Armor.CT_Centaur_IV,
Armor.CT_Cromwell_IV,
Armor.HIT_Churchill_VII,
Armor.LT_Mk_VII_Tetrarch,
# Mods
frenchpack.DIM__TOYOTA_BLUE,
frenchpack.DIM__TOYOTA_GREEN,
frenchpack.DIM__TOYOTA_DESERT,
frenchpack.DIM__KAMIKAZE,
frenchpack.AMX_10RCR,
frenchpack.AMX_10RCR_SEPAR,
frenchpack.AMX_30B2,
frenchpack.Leclerc_Serie_XXI,
]
TYPE_ATGM = [
Armor.ATGM_M1045_HMMWV_TOW,
Armor.ATGM_M1134_Stryker,
Armor.IFV_BMP_2,
# WW2 (Tank Destroyers)
Armor.M30_Cargo_Carrier,
Armor.TD_Jagdpanzer_IV,
Armor.TD_Jagdpanther_G1,
Armor.TD_M10_GMC,
# Mods
frenchpack.VBAE_CRAB_MMP,
frenchpack.VAB_MEPHISTO,
frenchpack.TRM_2000_PAMELA,
]
TYPE_IFV = [
Armor.IFV_BMP_3,
Armor.IFV_BMP_2,
Armor.IFV_BMP_1,
Armor.IFV_Marder,
Armor.IFV_MCV_80,
Armor.IFV_LAV_25,
Armor.SPG_M1128_Stryker_MGS,
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.IFV_M2A2_Bradley,
Armor.IFV_BMD_1,
Armor.ZBD_04A,
# WW2
Armor.AC_Sd_Kfz_234_2_Puma,
Armor.LAC_M8_Greyhound,
Armor.Daimler_Armoured_Car,
# Mods
frenchpack.ERC_90,
frenchpack.VBAE_CRAB,
frenchpack.VAB_T20_13
]
TYPE_APC = [
Armor.APC_M1043_HMMWV_Armament,
Armor.APC_M1126_Stryker_ICV,
Armor.APC_M113,
Armor.APC_BTR_80,
Armor.APC_BTR_82A,
Armor.APC_MTLB,
Armor.APC_M2A1,
Armor.APC_Cobra,
Armor.APC_Sd_Kfz_251,
Armor.APC_AAV_7,
Armor.TPz_Fuchs,
Armor.ARV_BRDM_2,
Armor.ARV_BTR_RD,
Armor.FDDM_Grad,
# WW2
Armor.APC_M2A1,
Armor.APC_Sd_Kfz_251,
# Mods
frenchpack.VAB__50,
frenchpack.VBL__50,
frenchpack.VBL_AANF1,
]
TYPE_ARTILLERY = [
Artillery.MLRS_9A52_Smerch,
Artillery.SPH_2S1_Gvozdika,
Artillery.SPH_2S3_Akatsia,
Artillery.MLRS_BM_21_Grad,
Artillery.MLRS_9K57_Uragan_BM_27,
Artillery.SPH_M109_Paladin,
Artillery.MLRS_M270,
Artillery.SPH_2S9_Nona,
Artillery.SpGH_Dana,
Artillery.SPH_2S19_Msta,
Artillery.MLRS_FDDM,
# WW2
Artillery.Sturmpanzer_IV_Brummbär,
Artillery.M12_GMC
]
TYPE_LOGI = [
Unarmed.Transport_M818,
Unarmed.Transport_KAMAZ_43101,
Unarmed.Transport_Ural_375,
Unarmed.Transport_GAZ_66,
Unarmed.Transport_GAZ_3307,
Unarmed.Transport_GAZ_3308,
Unarmed.Transport_Ural_4320_31_Armored,
Unarmed.Transport_Ural_4320T,
Unarmed.Blitz_3_6_6700A,
Unarmed.Kübelwagen_82,
Unarmed.Sd_Kfz_7,
Unarmed.Sd_Kfz_2,
Unarmed.Willys_MB,
Unarmed.Land_Rover_109_S3,
Unarmed.Land_Rover_101_FC,
# Mods
frenchpack.VBL,
frenchpack.VAB,
]
TYPE_INFANTRY = [
Infantry.Infantry_Soldier_Insurgents,
Infantry.Soldier_AK,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_Mauser_98,
Infantry.Infantry_SMLE_No_4_Mk_1,
Infantry.Georgian_soldier_with_M4,
Infantry.Infantry_Soldier_Rus,
Infantry.Paratrooper_AKS,
Infantry.Paratrooper_RPG_16,
Infantry.Soldier_M249,
Infantry.Infantry_M4,
Infantry.Soldier_RPG,
]
TYPE_SHORAD = [
AirDefence.AAA_ZU_23_on_Ural_375,
AirDefence.AAA_ZU_23_Insurgent_on_Ural_375,
AirDefence.AAA_ZSU_57_2,
AirDefence.SPAAA_ZSU_23_4_Shilka,
AirDefence.SAM_SA_8_Osa_9A33,
AirDefence.SAM_SA_9_Strela_1_9P31,
AirDefence.SAM_SA_13_Strela_10M3_9A35M3,
AirDefence.SAM_SA_15_Tor_9A331,
AirDefence.SAM_SA_19_Tunguska_2S6,
AirDefence.SPAAA_Gepard,
AirDefence.AAA_Vulcan_M163,
AirDefence.SAM_Linebacker_M6,
AirDefence.SAM_Chaparral_M48,
AirDefence.SAM_Avenger_M1097,
AirDefence.SAM_Roland_ADS,
AirDefence.HQ_7_Self_Propelled_LN,
AirDefence.AAA_8_8cm_Flak_18,
AirDefence.AAA_8_8cm_Flak_36,
AirDefence.AAA_8_8cm_Flak_37,
AirDefence.AAA_8_8cm_Flak_41,
AirDefence.AAA_Bofors_40mm,
AirDefence.AAA_M1_37mm,
AirDefence.AA_gun_QF_3_7,
]

View File

@@ -9,20 +9,21 @@ from __future__ import annotations
import logging
import random
from typing import Dict, Iterator, Optional, TYPE_CHECKING, Type
from typing import Dict, Iterator, Optional, TYPE_CHECKING, Type, List
from dcs import Mission
from dcs import Mission, Point
from dcs.country import Country
from dcs.statics import fortification_map, warehouse_map
from dcs.task import (
ActivateBeaconCommand,
ActivateICLSCommand,
EPLRS,
OptAlarmState,
OptAlarmState, FireAtPoint,
)
from dcs.unit import Ship, Unit, Vehicle
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup
from dcs.unittype import StaticType, UnitType
from dcs.vehicles import vehicle_map
from game import db
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
@@ -31,7 +32,7 @@ from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import (
BuildingGroundObject, CarrierGroundObject,
GenericCarrierGroundObject,
LhaGroundObject, ShipGroundObject,
LhaGroundObject, ShipGroundObject, MissileSiteGroundObject,
)
from game.unitmap import UnitMap
from game.utils import knots_to_kph, kph_to_mps, mps_to_kph
@@ -50,7 +51,7 @@ AA_CP_MIN_DISTANCE = 40000
class GenericGroundObjectGenerator:
"""An unspecialized ground object generator.
Currently used only for SAM and missile (V1/V2) sites.
Currently used only for SAM
"""
def __init__(self, ground_object: TheaterGroundObject, country: Country,
game: Game, mission: Mission, unit_map: UnitMap) -> None:
@@ -111,6 +112,55 @@ class GenericGroundObjectGenerator:
persistence_group, miz_group)
class MissileSiteGenerator(GenericGroundObjectGenerator):
def generate(self) -> None:
super(MissileSiteGenerator, self).generate()
# Note : Only the SCUD missiles group can fire (V1 site cannot fire in game right now)
# TODO : Should be pre-planned ?
# TODO : Add delay to task to spread fire task over mission duration ?
for group in self.ground_object.groups:
vg = self.m.find_group(group.name)
targets = self.possible_missile_targets(vg)
if vg is not None and targets:
target = random.choice(targets)
real_target = target.point_from_heading(random.randint(0, 360), random.randint(0, 2500))
vg.points[0].add_task(FireAtPoint(real_target))
logging.info("Set up fire task for missile group.")
else:
logging.info("Couldn't setup missile site to fire, group was not generated.")
def possible_missile_targets(self, vg: Group) -> List[Point]:
"""
Find enemy control points in range
:param vg: Vehicle group we are searching a target for (There is always only oe group right now)
:return: List of possible missile targets
"""
targets: List[Point] = []
for cp in self.game.theater.controlpoints:
if cp.captured != self.ground_object.control_point.captured:
distance = cp.position.distance_to_point(vg.position)
if distance < self.missile_site_range:
targets.append(cp.position)
return targets
@property
def missile_site_range(self) -> int:
"""
Get the missile site range
:return: Missile site range
"""
site_range = 0
for group in self.ground_object.groups:
vg = self.m.find_group(group.name)
if vg is not None:
for u in vg.units:
if u.type in vehicle_map:
if vehicle_map[u.type].threat_range > site_range:
site_range = vehicle_map[u.type].threat_range
return site_range
class BuildingSiteGenerator(GenericGroundObjectGenerator):
"""Generator for building sites.
@@ -421,8 +471,11 @@ class GroundObjectsGenerator:
generator = ShipObjectGenerator(
ground_object, country, self.game, self.m,
self.unit_map)
elif isinstance(ground_object, MissileSiteGroundObject):
generator = MissileSiteGenerator(
ground_object, country, self.game, self.m,
self.unit_map)
else:
generator = GenericGroundObjectGenerator(
ground_object, country, self.game, self.m,
self.unit_map)

View File

@@ -1,5 +1,6 @@
"""Radio frequency types and allocators."""
import itertools
import logging
from dataclasses import dataclass
from typing import Dict, Iterator, List, Set
@@ -71,12 +72,9 @@ class Radio:
self.minimum.hertz, self.maximum.hertz, self.step.hertz
))
class OutOfChannelsError(RuntimeError):
"""Raised when all channels usable by this radio have been allocated."""
def __init__(self, radio: Radio) -> None:
super().__init__(f"No available channels for {radio}")
@property
def last_channel(self) -> RadioFrequency:
return RadioFrequency(self.maximum.hertz - self.step.hertz)
class ChannelInUseError(RuntimeError):
@@ -215,7 +213,13 @@ class RadioRegistry:
self.reserve(channel)
return channel
except StopIteration:
raise OutOfChannelsError(radio)
# In the event of too many channel users, fail gracefully by reusing
# the last channel.
# https://github.com/Khopa/dcs_liberation/issues/598
channel = radio.last_channel
logging.warning(
f"No more free channels for {radio.name}. Reusing {channel}.")
return channel
def alloc_uhf(self) -> RadioFrequency:
"""Allocates a UHF radio channel suitable for inter-flight comms.

25
gen/sam/aaa_zsu57.py Normal file
View File

@@ -0,0 +1,25 @@
from dcs.vehicles import AirDefence
from gen.sam.airdefensegroupgenerator import (
AirDefenseRange,
AirDefenseGroupGenerator,
)
class ZSU57Generator(AirDefenseGroupGenerator):
"""
This generate a Zsu 57 group
"""
name = "ZSU-57-2 Group"
price = 60
def generate(self):
num_launchers = 5
positions = self.get_circular_position(num_launchers, launcher_distance=110, coverage=360)
for i, position in enumerate(positions):
self.add_unit(AirDefence.AAA_ZSU_57_2, "SPAA#" + str(i), position[0], position[1], position[2])
@classmethod
def range(cls) -> AirDefenseRange:
return AirDefenseRange.Short

View File

@@ -12,6 +12,7 @@ from gen.sam.aaa_bofors import BoforsGenerator
from gen.sam.aaa_flak import FlakGenerator
from gen.sam.aaa_flak18 import Flak18Generator
from gen.sam.aaa_ww2_ally_flak import AllyWW2FlakGenerator
from gen.sam.aaa_zsu57 import ZSU57Generator
from gen.sam.aaa_zu23_insurgent import ZU23InsurgentGenerator
from gen.sam.airdefensegroupgenerator import (
AirDefenseGroupGenerator,
@@ -98,7 +99,8 @@ SAM_MAP: Dict[str, Type[AirDefenseGroupGenerator]] = {
"ColdWarFlakGenerator": ColdWarFlakGenerator,
"EarlyColdWarFlakGenerator": EarlyColdWarFlakGenerator,
"FreyaGenerator": FreyaGenerator,
"AllyWW2FlakGenerator": AllyWW2FlakGenerator
"AllyWW2FlakGenerator": AllyWW2FlakGenerator,
"ZSU57Generator": ZSU57Generator
}

2
pydcs

Submodule pydcs updated: c9751f54e0...059c88c91b

View File

@@ -1,6 +1,7 @@
from PySide2.QtWidgets import QLabel, QHBoxLayout, QGroupBox, QPushButton
import qt_ui.uiconstants as CONST
from game.income import Income
from qt_ui.windows.finances.QFinancesMenu import QFinancesMenu
@@ -41,7 +42,7 @@ class QBudgetBox(QGroupBox):
return
self.game = game
self.setBudget(self.game.budget, self.game.budget_reward_amount)
self.setBudget(self.game.budget, Income(self.game, player=True).total)
self.finances.setEnabled(True)
def openFinances(self):

107
qt_ui/widgets/QIntelBox.py Normal file
View File

@@ -0,0 +1,107 @@
from typing import Optional
from PySide2.QtWidgets import (
QGridLayout,
QGroupBox,
QHBoxLayout,
QLabel,
QPushButton,
)
from game import Game
from game.income import Income
from qt_ui.windows.intel import IntelWindow
class QIntelBox(QGroupBox):
def __init__(self, game: Game) -> None:
super().__init__("Intel")
self.setProperty("style", "IntelSummary")
self.game = game
columns = QHBoxLayout()
self.setLayout(columns)
summary = QGridLayout()
columns.addLayout(summary)
summary.addWidget(QLabel("Air superiority:"), 0, 0)
self.air_strength = QLabel()
summary.addWidget(self.air_strength, 0, 1)
summary.addWidget(QLabel("Front line:"), 1, 0)
self.ground_strength = QLabel()
summary.addWidget(self.ground_strength, 1, 1)
summary.addWidget(QLabel("Economic strength:"), 2, 0)
self.economic_strength = QLabel()
summary.addWidget(self.economic_strength, 2, 1)
details = QPushButton("Details")
columns.addWidget(details)
details.clicked.connect(self.open_details_window)
self.update_summary()
self.details_window: Optional[IntelWindow] = None
def set_game(self, game: Optional[Game]) -> None:
self.game = game
self.update_summary()
@staticmethod
def forces_strength_text(own: int, enemy: int) -> str:
if not enemy:
return "enemy eliminated"
ratio = own / enemy
if ratio < 0.6:
return "outnumbered"
if ratio < 0.8:
return "slightly outnumbered"
if ratio < 1.2:
return "evenly matched"
if ratio < 1.4:
return "slight advantage"
return "strong advantage"
def economic_strength_text(self) -> str:
assert self.game is not None
own = Income(self.game, player=True).total
enemy = Income(self.game, player=False).total
if not enemy:
return "enemy economy ruined"
ratio = own / enemy
if ratio < 0.6:
return "strong disadvantage"
if ratio < 0.8:
return "slight disadvantage"
if ratio < 1.2:
return "evenly matched"
if ratio < 1.4:
return "slight advantage"
return "strong advantage"
def update_summary(self) -> None:
if self.game is None:
self.air_strength.setText("no data")
self.ground_strength.setText("no data")
self.economic_strength.setText("no data")
return
data = self.game.game_stats.data_per_turn[-1]
self.air_strength.setText(self.forces_strength_text(
data.allied_units.aircraft_count,
data.enemy_units.aircraft_count))
self.ground_strength.setText(self.forces_strength_text(
data.allied_units.vehicles_count,
data.enemy_units.vehicles_count))
self.economic_strength.setText(self.economic_strength_text())
def open_details_window(self) -> None:
self.details_window = IntelWindow(self.game)
self.details_window.show()

View File

@@ -16,6 +16,7 @@ from gen.flights.traveltime import TotEstimator
from qt_ui.models import GameModel
from qt_ui.widgets.QBudgetBox import QBudgetBox
from qt_ui.widgets.QFactionsInfos import QFactionsInfos
from qt_ui.widgets.QIntelBox import QIntelBox
from qt_ui.widgets.clientslots import MaxPlayerCount
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.QWaitingForMissionResultWindow import \
@@ -71,6 +72,8 @@ class QTopPanel(QFrame):
self.statistics.setProperty("style", "btn-primary")
self.statistics.clicked.connect(self.openStatisticsWindow)
self.intel_box = QIntelBox(self.game)
self.buttonBox = QGroupBox("Misc")
self.buttonBoxLayout = QHBoxLayout()
self.buttonBoxLayout.addWidget(self.settings)
@@ -90,6 +93,7 @@ class QTopPanel(QFrame):
self.layout.addWidget(self.factionsInfos)
self.layout.addWidget(self.conditionsWidget)
self.layout.addWidget(self.budgetBox)
self.layout.addWidget(self.intel_box)
self.layout.addWidget(self.buttonBox)
self.layout.addStretch(1)
self.layout.addWidget(self.proceedBox)
@@ -106,6 +110,7 @@ class QTopPanel(QFrame):
self.statistics.setEnabled(True)
self.conditionsWidget.setCurrentTurn(game.turn, game.conditions)
self.intel_box.set_game(game)
self.budgetBox.setGame(game)
self.factionsInfos.setGame(game)

View File

@@ -4,7 +4,7 @@ from PySide2.QtGui import QColor, QPainter
from PySide2.QtWidgets import QAction, QMenu
import qt_ui.uiconstants as const
from game.theater import ControlPoint
from game.theater import ControlPoint, NavalControlPoint
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2
from .QMapObject import QMapObject
@@ -108,7 +108,8 @@ class QMapControlPoint(QMapObject):
def open_new_package_dialog(self) -> None:
"""Extends the default packagedialog to redirect to base menu for red air base."""
if not self.control_point.captured:
self.on_click()
else:
super(QMapControlPoint, self).open_new_package_dialog()
is_navy = isinstance(self.control_point, NavalControlPoint)
if self.control_point.captured or is_navy:
super().open_new_package_dialog()
return
self.on_click()

View File

@@ -47,7 +47,7 @@ class QMapObject(QGraphicsRectItem):
object_details_action.triggered.connect(self.on_click)
menu.addAction(object_details_action)
# Not all locations have valid objetives. Off-map spawns, for example,
# Not all locations have valid objectives. Off-map spawns, for example,
# have no mission types.
if list(self.mission_target.mission_types(for_player=True)):
new_package_action = QAction(f"New package")

View File

@@ -284,7 +284,7 @@ class QLiberationWindow(QMainWindow):
"<h4>Authors</h4>" + \
"<p>DCS Liberation was originally developed by <b>shdwp</b>, DCS Liberation 2.0 is a partial rewrite based on this work by <b>Khopa</b>." \
"<h4>Contributors</h4>" + \
"shdwp, Khopa, ColonelPanic, Roach, Wrycu, calvinmorrow, JohanAberg, Deus, root0fall, Captain Cody, steveveepee, pedromagueija, parithon, bwRavencl, davidp57, Plob" + \
"shdwp, Khopa, ColonelPanic, Roach, Wrycu, calvinmorrow, JohanAberg, Deus, root0fall, Captain Cody, steveveepee, pedromagueija, parithon, bwRavencl, davidp57, Plob, Hawkmoon" + \
"<h4>Special Thanks :</h4>" \
"<b>rp-</b> <i>for the pydcs framework</i><br/>"\
"<b>Grimes (mrSkortch)</b> & <b>Speed</b> <i>for the MIST framework</i><br/>"\

View File

@@ -88,6 +88,9 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour):
if self.maximum_units > 0:
if self.cp.unclaimed_parking(self.game_model.game) <= 0:
logging.debug(f"No space for additional aircraft at {self.cp}.")
QMessageBox.warning(
self, "No space for additional aircraft",
f"There is no parking space left at {self.cp.name} to accommodate another plane.", QMessageBox.Ok)
return
super().buy(unit_type)

View File

@@ -7,7 +7,8 @@ from PySide2.QtWidgets import (
QWidget,
)
from game.theater import Airport, ControlPoint
from game.theater import Airport, ControlPoint, Fob
from game.theater.theatergroundobject import BuildingGroundObject
from qt_ui.windows.basemenu.base_defenses.QBaseDefenseGroupInfo import \
QBaseDefenseGroupInfo
@@ -30,9 +31,18 @@ class QBaseInformation(QFrame):
scroll_content.setLayout(task_box_layout)
for g in self.cp.ground_objects:
if g.airbase_group and len(g.groups) > 0:
group_info = QBaseDefenseGroupInfo(self.cp, g, self.game)
task_box_layout.addWidget(group_info)
# Airbase groups are the objects that are hidden on the map because
# they're shown in the base menu.
if not g.airbase_group:
continue
# Of these, we need to ignore the FOB structure itself since that's
# not supposed to be targetable.
if isinstance(self.cp, Fob) and isinstance(g, BuildingGroundObject):
continue
group_info = QBaseDefenseGroupInfo(self.cp, g, self.game)
task_box_layout.addWidget(group_info)
scroll_content.setLayout(task_box_layout)
scroll = QScrollArea()

View File

@@ -1,19 +1,69 @@
from PySide2.QtWidgets import QDialog, QGridLayout, QLabel, QFrame, QSizePolicy
from PySide2.QtWidgets import (
QDialog,
QFrame,
QGridLayout,
QLabel,
QSizePolicy,
)
import qt_ui.uiconstants as CONST
from game.db import REWARDS, PLAYER_BUDGET_BASE
from game.game import Game
from game.income import Income
class QHorizontalSeparationLine(QFrame):
def __init__(self):
super().__init__()
self.setMinimumWidth(1)
self.setFixedHeight(20)
self.setFrameShape(QFrame.HLine)
self.setFrameShadow(QFrame.Sunken)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
def __init__(self):
super().__init__()
self.setMinimumWidth(1)
self.setFixedHeight(20)
self.setFrameShape(QFrame.HLine)
self.setFrameShadow(QFrame.Sunken)
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
class FinancesLayout(QGridLayout):
def __init__(self, game: Game, player: bool) -> None:
super().__init__()
income = Income(game, player)
self.addWidget(QLabel("<b>Control Points</b>"), 0, 0)
self.addWidget(QLabel(
f"{len(income.control_points)} bases x {income.income_per_base}M"),
0, 1)
self.addWidget(QLabel(f"{income.from_bases}M"), 0, 2)
self.addWidget(QHorizontalSeparationLine(), 1, 0, 1, 3)
buildings = reversed(sorted(income.buildings, key=lambda b: b.income))
row = 2
for row, building in enumerate(buildings, row):
self.addWidget(
QLabel(f"<b>{building.category.upper()} [{building.name}]</b>"),
row, 0)
self.addWidget(QLabel(
f"{building.number} buildings x {building.income_per_building}M"),
row, 1)
rlabel = QLabel(f"{building.income}M")
rlabel.setProperty("style", "green")
self.addWidget(rlabel, row, 2)
self.addWidget(QHorizontalSeparationLine(), row + 1, 0, 1, 3)
self.addWidget(QLabel(
f"Income multiplier: {income.multiplier:.1f}"),
row + 2, 1
)
self.addWidget(QLabel(f"<b>{income.total}M</b>"), row + 2, 2)
if player:
budget = game.budget
else:
budget = game.enemy_budget
self.addWidget(QLabel(f"Balance"), row + 3, 1)
self.addWidget(QLabel(f"<b>{budget}M</b>"), row + 3, 2)
self.setRowStretch(row + 4, 1)
class QFinancesMenu(QDialog):
@@ -26,49 +76,4 @@ class QFinancesMenu(QDialog):
self.setWindowIcon(CONST.ICONS["Money"])
self.setMinimumSize(450, 200)
reward = PLAYER_BUDGET_BASE * len(self.game.theater.player_points())
layout = QGridLayout()
layout.addWidget(QLabel("<b>Control Points</b>"), 0, 0)
layout.addWidget(QLabel(str(len(self.game.theater.player_points())) + " bases x " + str(PLAYER_BUDGET_BASE) + "M"), 0, 1)
layout.addWidget(QLabel(str(reward) + "M"), 0, 2)
layout.addWidget(QHorizontalSeparationLine(), 1, 0, 1, 3)
i = 2
for cp in self.game.theater.player_points():
obj_names = []
[obj_names.append(ground_object.obj_name) for ground_object in cp.ground_objects if ground_object.obj_name not in obj_names]
for obj_name in obj_names:
reward = 0
g = None
cat = None
number = 0
for ground_object in cp.ground_objects:
if ground_object.obj_name != obj_name or ground_object.is_dead:
continue
else:
if g is None:
g = ground_object
cat = g.category
if cat in REWARDS.keys():
number = number + 1
reward += REWARDS[cat]
if g is not None and cat in REWARDS.keys():
layout.addWidget(QLabel("<b>" + g.category.upper() + " [" + obj_name + "]</b>"), i, 0)
layout.addWidget(QLabel(str(number) + " buildings x " + str(REWARDS[cat]) + "M"), i, 1)
rlabel = QLabel(str(reward) + "M")
rlabel.setProperty("style", "green")
layout.addWidget(rlabel, i, 2)
i = i + 1
self.setLayout(layout)
layout.addWidget(QHorizontalSeparationLine(), i + 1, 0, 1, 3)
layout.addWidget(QLabel(
f"Income multiplier: {game.settings.player_income_multiplier:.1f}"),
i + 2, 1
)
layout.addWidget(
QLabel("<b>" + str(self.game.budget_reward_amount) + "M </b>"),
i + 2, 2)
self.setLayout(FinancesLayout(game, player=True))

View File

@@ -21,6 +21,7 @@ from game import Game, db
from game.data.building_data import FORTIFICATION_BUILDINGS
from game.db import PRICES, PinpointStrike, REWARDS, unit_type_of
from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import NavalGroundObject
from gen.defenses.armor_group_generator import \
generate_armor_group_of_type_and_size
from gen.sam.sam_group_generator import get_faction_possible_sams_generator
@@ -81,9 +82,10 @@ class QGroundObjectMenu(QDialog):
self.buy_replace.clicked.connect(self.buy_group)
self.buy_replace.setProperty("style", "btn-success")
if self.total_value > 0:
self.actionLayout.addWidget(self.sell_all_button)
self.actionLayout.addWidget(self.buy_replace)
if not isinstance(self.ground_object, NavalGroundObject):
if self.total_value > 0:
self.actionLayout.addWidget(self.sell_all_button)
self.actionLayout.addWidget(self.buy_replace)
if self.cp.captured and self.ground_object.dcs_identifier == "AA":
self.mainLayout.addLayout(self.actionLayout)

147
qt_ui/windows/intel.py Normal file
View File

@@ -0,0 +1,147 @@
import itertools
from PySide2.QtWidgets import (
QDialog,
QFrame,
QGridLayout,
QLabel,
QLayout,
QScrollArea,
QSizePolicy,
QSpacerItem,
QTabWidget,
QVBoxLayout,
QWidget,
)
from game.game import Game
from qt_ui.uiconstants import ICONS
from qt_ui.windows.finances.QFinancesMenu import FinancesLayout
class ScrollingFrame(QFrame):
def __init__(self) -> None:
super().__init__()
widget = QWidget()
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(widget)
self.scrolling_layout = QVBoxLayout()
widget.setLayout(self.scrolling_layout)
self.setLayout(QVBoxLayout())
self.layout().addWidget(scroll_area)
def addWidget(self, widget: QWidget, *args, **kwargs) -> None:
self.scrolling_layout.addWidget(widget, *args, **kwargs)
def addLayout(self, layout: QLayout, *args, **kwargs) -> None:
self.scrolling_layout.addLayout(layout, *args, **kwargs)
class EconomyIntelTab(ScrollingFrame):
def __init__(self, game: Game) -> None:
super().__init__()
self.addLayout(FinancesLayout(game, player=False))
class IntelTableLayout(QGridLayout):
def __init__(self) -> None:
super().__init__()
self.row = itertools.count(0)
def add_header(self, text: str) -> None:
self.addWidget(QLabel(f"<b>{text}</b>"), next(self.row), 0)
def add_spacer(self) -> None:
self.addItem(
QSpacerItem(0, 0, QSizePolicy.Preferred, QSizePolicy.Expanding),
next(self.row), 0)
def add_row(self, text: str, count: int) -> None:
row = next(self.row)
self.addWidget(QLabel(text), row, 0)
self.addWidget(QLabel(str(count)), row, 1)
class AircraftIntelLayout(IntelTableLayout):
def __init__(self, game: Game, player: bool) -> None:
super().__init__()
total = 0
for control_point in game.theater.control_points_for(player):
base = control_point.base
total += base.total_aircraft
if not base.total_aircraft:
continue
self.add_header(control_point.name)
for airframe, count in base.aircraft.items():
if not count:
continue
self.add_row(airframe.id, count)
self.add_spacer()
self.add_row("<b>Total</b>", total)
class AircraftIntelTab(ScrollingFrame):
def __init__(self, game: Game) -> None:
super().__init__()
self.addLayout(AircraftIntelLayout(game, player=False))
class ArmyIntelLayout(IntelTableLayout):
def __init__(self, game: Game, player: bool) -> None:
super().__init__()
total = 0
for control_point in game.theater.control_points_for(player):
base = control_point.base
total += base.total_armor
if not base.total_armor:
continue
self.add_header(control_point.name)
for vehicle, count in base.armor.items():
if not count:
continue
self.add_row(vehicle.id, count)
self.add_spacer()
self.add_row("<b>Total</b>", total)
class ArmyIntelTab(ScrollingFrame):
def __init__(self, game: Game) -> None:
super().__init__()
self.addLayout(ArmyIntelLayout(game, player=False))
class IntelTabs(QTabWidget):
def __init__(self, game: Game):
super().__init__()
self.addTab(EconomyIntelTab(game), "Economy")
self.addTab(AircraftIntelTab(game), "Air forces")
self.addTab(ArmyIntelTab(game), "Ground forces")
class IntelWindow(QDialog):
def __init__(self, game: Game):
super().__init__()
self.game = game
self.setModal(True)
self.setWindowTitle("Intelligence")
self.setWindowIcon(ICONS["Statistics"])
self.setMinimumSize(600, 500)
layout = QVBoxLayout()
self.setLayout(layout)
layout.addWidget(IntelTabs(game), stretch=1)

View File

@@ -7,7 +7,6 @@ from PySide2.QtWidgets import QHeaderView, QTableView
from game.utils import meter_to_feet
from gen.ato import Package
from gen.flights.flight import Flight, FlightWaypoint, FlightWaypointType
from gen.flights.traveltime import TotEstimator
from qt_ui.windows.mission.flight.waypoints.QFlightWaypointItem import \
QWaypointItem
@@ -74,9 +73,9 @@ class QFlightWaypointList(QTableView):
time = timedelta(seconds=int(time.total_seconds()))
return f"{prefix}T+{time}"
def takeoff_text(self, flight: Flight) -> str:
estimator = TotEstimator(self.package)
takeoff_time = estimator.takeoff_time_for_flight(flight)
@staticmethod
def takeoff_text(flight: Flight) -> str:
takeoff_time = flight.flight_plan.takeoff_time()
# Handle custom flight plans where we can't estimate the takeoff time.
if takeoff_time is None:
takeoff_time = timedelta()

View File

@@ -116,7 +116,8 @@ class QFlightWaypointTab(QFrame):
if not waypoints:
return
self.degrade_to_custom_flight_plan()
self.flight.flight_plan.waypoints.extend(waypoints)
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
self.flight.flight_plan.custom_waypoints.extend(waypoints)
self.flight_waypoint_list.update_list()
self.on_change()
@@ -124,7 +125,8 @@ class QFlightWaypointTab(QFrame):
rtb = self.planner.generate_rtb_waypoint(self.flight,
self.flight.from_cp)
self.degrade_to_custom_flight_plan()
self.flight.flight_plan.waypoints.append(rtb)
assert isinstance(self.flight.flight_plan, CustomFlightPlan)
self.flight.flight_plan.custom_waypoints.append(rtb)
self.flight_waypoint_list.update_list()
self.on_change()

View File

@@ -81,7 +81,7 @@ class NewGameWizard(QtWidgets.QWizard):
enemy_budget=int(self.field("enemy_starting_money")),
# QSlider forces integers, so we use 1 to 50 and divide by 10 to
# give 0.1 to 5.0.
midgame=self.field("midGame"),
midgame=False,
inverted=self.field("invertMap"),
no_carrier=self.field("no_carrier"),
no_lha=self.field("no_lha"),
@@ -271,10 +271,10 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
mapSettingsLayout.addWidget(QtWidgets.QLabel("Invert Map"), 0, 0)
mapSettingsLayout.addWidget(invertMap, 0, 1)
mapSettingsLayout.addWidget(QtWidgets.QLabel("Start at mid game"), 1, 0)
midgame = QtWidgets.QCheckBox()
self.registerField('midGame', midgame)
mapSettingsLayout.addWidget(midgame, 1, 1)
#mapSettingsLayout.addWidget(QtWidgets.QLabel("Start at mid game"), 1, 0)
#midgame = QtWidgets.QCheckBox()
#self.registerField('midGame', midgame)
#mapSettingsLayout.addWidget(midgame, 1, 1)
mapSettingsGroup.setLayout(mapSettingsLayout)
# Time Period

View File

@@ -3,106 +3,5 @@
"theater": "Persian Gulf",
"authors": "Khopa",
"description": "<p>In this scenario, you can play an invasion of the Emirates and Oman, where your forces starts in Fujairah.</p><p><strong>Note:</strong> Fujairah airfield has very few slots for aircrafts, so it recommended to operate from carriers at the start of the campaign. Thus, a carrier-capable faction is recommended.</p>",
"player_points": [
{
"type": "airbase",
"id": "Fujairah Intl",
"radials": [
180,
225,
270,
315,
0
],
"size": 1000,
"importance": 1,
"captured_invert": true
},
{
"type": "lha",
"id": 1002,
"x": -79770,
"y": 49430,
"captured_invert": true
},
{
"type": "carrier",
"id": 1001,
"x": -61770,
"y": 69039,
"captured_invert": true
}
],
"enemy_points": [
{
"type": "airbase",
"id": "Al Dhafra AB",
"size": 2000,
"importance": 1.2
},
{
"type": "airbase",
"id": "Al Ain International Airport",
"size": 2000,
"importance": 1
},
{
"type": "airbase",
"id": "Al Maktoum Intl",
"size": 2000,
"importance": 1
},
{
"type": "airbase",
"id": "Al Minhad AB",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Sharjah Intl",
"size": 2000,
"importance": 1
},
{
"type": "airbase",
"id": "Ras Al Khaimah",
"size": 1000,
"importance": 1
}
],
"links": [
[
"Al Ain International Airport",
"Al Dhafra AB"
],
[
"Al Dhafra AB",
"Al Maktoum Intl"
],
[
"Al Ain International Airport",
"Fujairah Intl"
],
[
"Al Ain International Airport",
"Al Maktoum Intl"
],
[
"Al Maktoum Intl",
"Al Minhad AB"
],
[
"Al Minhad AB",
"Sharjah Intl"
],
[
"Ras Al Khaimah",
"Sharjah Intl"
],
[
"Fujairah Intl",
"Sharjah Intl"
]
]
"miz": "emirates.miz"
}

Binary file not shown.

View File

@@ -0,0 +1,7 @@
{
"name": "Syria - Battle for Golan Heights",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.<br/><br/>You can use the inverted configuration to start on the Syrian side.<br/><br/>If this scenario is too heavy, try the lite version.</p>",
"miz": "golan_heights.miz"
}

Binary file not shown.

View File

@@ -1,83 +0,0 @@
{
"name": "Syria - Golan heights battle",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.</p>",
"player_points": [
{
"type": "airbase",
"id": "Ramat David",
"size": 1000,
"importance": 1.4
},
{
"type": "carrier",
"id": 1001,
"x": -280000,
"y": -238000,
"captured_invert": true
},
{
"type": "lha",
"id": 1002,
"x": -237000,
"y": -89800,
"captured_invert": true
}
],
"enemy_points": [
{
"type": "airbase",
"id": "Khalkhalah",
"size": 1000,
"importance": 1.2
},
{
"type": "airbase",
"id": "King Hussein Air College",
"size": 1000,
"importance": 1.4
},
{
"type": "airbase",
"id": "Marj Ruhayyil",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Mezzeh",
"size": 1000,
"importance": 1.2
},
{
"type": "airbase",
"id": "Al-Dumayr",
"size": 1000,
"importance": 1.2,
"captured_invert": true
}
],
"links": [
[
"Khalkhalah",
"Ramat David"
],
[
"Khalkhalah",
"King Hussein Air College"
],
[
"Khalkhalah",
"Marj Ruhayyil"
],
[
"Marj Ruhayyil",
"Mezzeh"
],
[
"Al-Dumayr",
"Marj Ruhayyil"
]
]
}

View File

@@ -0,0 +1,7 @@
{
"name": "Syria - Battle for Golan Heights - Lite",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>In this scenario, you start in Israel and the conflict is focused around the golan heights, an historically disputed territory.<br/><br/>This scenario is designed to be performance friendly.</p>",
"miz": "golan_heights_lite.miz"
}

Binary file not shown.

View File

@@ -1,185 +0,0 @@
{
"name": "Syria - Full Map",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>Full map of Syria</p><p><strong>Note:</strong> This scenario is heavy on performance, enabling \"culling\" in settings is highly recommended.</p>",
"player_points": [
{
"type": "airbase",
"id": "Ramat David",
"size": 1000,
"importance": 1.4
},
{
"type": "carrier",
"id": 1001,
"x": -151000,
"y": -106000,
"captured_invert": true
},
{
"type": "lha",
"id": 1002,
"x": -131000,
"y": -161000,
"captured_invert": true
}
],
"enemy_points": [
{
"type": "airbase",
"id": "King Hussein Air College",
"size": 1000,
"importance": 1.4
},
{
"type": "airbase",
"id": "Khalkhalah",
"size": 1000,
"importance": 1.2
},
{
"type": "airbase",
"id": "Al-Dumayr",
"size": 1000,
"importance": 1.2
},
{
"type": "airbase",
"id": "Al Qusayr",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Rene Mouawad",
"size": 1000,
"importance": 1.4
},
{
"type": "airbase",
"id": "Hama",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Bassel Al-Assad",
"size": 1000,
"importance": 1.4
},
{
"type": "airbase",
"id": "Palmyra",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Tabqa",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Jirah",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Aleppo",
"size": 1000,
"importance": 1.2
},
{
"type": "airbase",
"id": "Minakh",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Hatay",
"size": 1000,
"importance": 1.4
},
{
"type": "airbase",
"id": "Incirlik",
"size": 1000,
"importance": 1.4,
"captured_invert": true
}
],
"links": [
[
"King Hussein Air College",
"Ramat David"
],
[
"Khalkhalah",
"King Hussein Air College"
],
[
"Al-Dumayr",
"Khalkhalah"
],
[
"Al Qusayr",
"Al-Dumayr"
],
[
"Al Qusayr",
"Hama"
],
[
"Al Qusayr",
"Palmyra"
],
[
"Al Qusayr",
"Rene Mouawad"
],
[
"Bassel Al-Assad",
"Rene Mouawad"
],
[
"Aleppo",
"Hama"
],
[
"Bassel Al-Assad",
"Hama"
],
[
"Bassel Al-Assad",
"Hatay"
],
[
"Palmyra",
"Tabqa"
],
[
"Jirah",
"Tabqa"
],
[
"Aleppo",
"Jirah"
],
[
"Aleppo",
"Minakh"
],
[
"Hatay",
"Minakh"
],
[
"Incirlik",
"Minakh"
]
]
}

View File

@@ -0,0 +1,7 @@
{
"name": "Syria - Full Map",
"theater": "Syria",
"authors": "Hawkmoon",
"description": "<p>Full map of Syria</p><p><strong>Note:</strong>For a better early game experience is suggested to give the AI an high amount of starting money This scenario is heavy on performance, enabling \"culling\" in settings is highly recommended.</p>",
"miz": "syria_full_map_remastered.miz"
}

Binary file not shown.

View File

@@ -2,92 +2,6 @@
"name": "Syria - Syrian Civil War",
"theater": "Syria",
"authors": "Khopa",
"description": "<p>This scenario can be used to simulate parts of the Syrian Civil War.</p>",
"player_points": [
{
"type": "airbase",
"id": "Bassel Al-Assad",
"size": 1000,
"importance": 1.4
},
{
"type": "airbase",
"id": "Marj Ruhayyil",
"size": 1000,
"importance": 1
},
{
"type": "carrier",
"id": 1001,
"x": 18537,
"y": -52000,
"captured_invert": true
},
{
"type": "lha",
"id": 1002,
"x": 116000,
"y": -30000,
"captured_invert": true
}
],
"enemy_points": [
{
"type": "airbase",
"id": "Hama",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Aleppo",
"size": 1000,
"importance": 1.2,
"captured_invert": true
},
{
"type": "airbase",
"id": "Al Qusayr",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Palmyra",
"size": 1000,
"importance": 1
},
{
"type": "airbase",
"id": "Al-Dumayr",
"size": 1000,
"importance": 1.2
}
],
"links": [
[
"Bassel Al-Assad",
"Hama"
],
[
"Al-Dumayr",
"Marj Ruhayyil"
],
[
"Aleppo",
"Hama"
],
[
"Al Qusayr",
"Hama"
],
[
"Al Qusayr",
"Al-Dumayr"
],
[
"Al Qusayr",
"Palmyra"
]
]
"description": "<p>This scenario can be used to simulate parts of the Syrian Civil War.<br/><br/>You start on the coast with an airbase in Latakia, and ground forces in Tartus.<br/><br/>This scenario can also be used to simulate a western invasion of Syria.<br/><br/>In inverted configuration you start in Aleppo.</p>",
"miz": "syrian_civil_war.miz"
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -52,14 +52,14 @@
"name": "",
"callsign": "IBND",
"beacon_type": 14,
"hertz": 333800000,
"hertz": 109900000,
"channel": null
},
{
"name": "",
"callsign": "IBND",
"beacon_type": 15,
"hertz": 333800000,
"hertz": 109900000,
"channel": null
},
{
@@ -77,12 +77,19 @@
"channel": null
},
{
"name": "BandarEJask",
"name": "JASK",
"callsign": "JSK",
"beacon_type": 9,
"hertz": 349000000,
"hertz": 349000,
"channel": null
},
{
"name": "",
"callsign": "JSK",
"beacon_type": 5,
"hertz": null,
"channel": 110
},
{
"name": "BandarLengeh",
"callsign": "LEN",
@@ -101,8 +108,8 @@
"name": "",
"callsign": "MMA",
"beacon_type": 15,
"hertz": 111100000,
"channel": 48
"hertz": 109100000,
"channel": 28
},
{
"name": "",
@@ -115,28 +122,28 @@
"name": "",
"callsign": "IMA",
"beacon_type": 15,
"hertz": 109100000,
"channel": 28
"hertz": 111100000,
"channel": 48
},
{
"name": "",
"callsign": "RMA",
"beacon_type": 15,
"hertz": 114900000,
"hertz": 108700000,
"channel": 24
},
{
"name": "",
"callsign": "MMA",
"beacon_type": 14,
"hertz": 111100000,
"channel": 48
"hertz": 109100000,
"channel": 28
},
{
"name": "",
"callsign": "RMA",
"beacon_type": 14,
"hertz": 114900000,
"hertz": 108700000,
"channel": 24
},
{
@@ -150,8 +157,8 @@
"name": "",
"callsign": "IMA",
"beacon_type": 14,
"hertz": 109100000,
"channel": 28
"hertz": 111100000,
"channel": 48
},
{
"name": "AlDhafra",
@@ -332,7 +339,7 @@
"name": "KishIsland",
"callsign": "KIH",
"beacon_type": 9,
"hertz": 201000000,
"hertz": 201000,
"channel": null
},
{
@@ -367,14 +374,14 @@
"name": "LavanIsland",
"callsign": "LVA",
"beacon_type": 9,
"hertz": 310000000,
"hertz": 310000,
"channel": 0
},
{
"name": "LiwaAirbase",
"callsign": "\u00c4\u00bc",
"beacon_type": 7,
"hertz": null,
"callsign": "OMLW",
"beacon_type": 6,
"hertz": 117400000,
"channel": 121
},
{
@@ -433,13 +440,6 @@
"hertz": 113600000,
"channel": 83
},
{
"name": "SasAlNakheelAirport",
"callsign": "SAS",
"beacon_type": 10,
"hertz": 128925,
"channel": null
},
{
"name": "SasAlNakheel",
"callsign": "SAS",
@@ -500,14 +500,14 @@
"name": "",
"callsign": "ISYZ",
"beacon_type": 15,
"hertz": 109900000,
"hertz": 108500000,
"channel": null
},
{
"name": "",
"callsign": "ISYZ",
"beacon_type": 14,
"hertz": 109900000,
"hertz": 108500000,
"channel": null
},
{
@@ -556,7 +556,7 @@
"name": "DezfulAirport",
"callsign": "DZF",
"beacon_type": 9,
"hertz": 293000000,
"hertz": 293000,
"channel": null
},
{

View File

@@ -0,0 +1,101 @@
{
"country": "Combined Joint Task Forces Blue",
"name": "NATO Desert Storm",
"authors": "Hawkmoon",
"description": "<p>A faction to recreate the actual unit lineup during Desert Storm as closely as possible</p>",
"aircrafts": [
"F_15C",
"F_14A_135_GR",
"F_14B",
"F_15E",
"F_16C_50",
"FA_18C_hornet",
"A_10A",
"AV8BNA",
"UH_1H",
"AH_64A",
"B_52H",
"B_1B",
"Tornado_IDS",
"F_4E",
"F_117A",
"M_2000C",
"S_3B",
"SA342M",
"SA342L",
"SA342Mistral",
"OH_58D"
],
"awacs": [
"E_3A",
"E_2C"
],
"tankers": [
"KC_135",
"KC130"
],
"frontline_units": [
"MBT_M1A2_Abrams",
"ATGM_M1134_Stryker",
"IFV_M2A2_Bradley",
"APC_M1126_Stryker_ICV",
"IFV_LAV_25",
"APC_M1043_HMMWV_Armament",
"ATGM_M1045_HMMWV_TOW",
"TPz_Fuchs",
"IFV_MCV_80",
"MBT_Challenger_II",
"MBT_M60A3_Patton",
"SPG_M1128_Stryker_MGS",
"SAM_Avenger_M1097"
],
"artillery_units": [
"MLRS_M270",
"SPH_M109_Paladin"
],
"logistics_units": [
"Transport_M818"
],
"infantry_units": [
"Infantry_M4",
"Soldier_M249",
"Stinger_MANPADS"
],
"air_defenses": [
"AvengerGenerator",
"ChaparralGenerator",
"VulcanGenerator",
"RolandGenerator",
"HawkGenerator",
"PatriotGenerator",
"RapierGenerator"
],
"ewrs": [
"PatriotEwrGenerator"
],
"aircraft_carrier": [
"CVN_74_John_C__Stennis"
],
"helicopter_carrier": [
"LHA_1_Tarawa"
],
"destroyers": [
"OliverHazardPerryGroupGenerator"
],
"cruisers": [
"Ticonderoga_class"
],
"requirements": {},
"carrier_names": [
"CVN-71 Theodore Roosevelt"
],
"helicopter_carrier_names": [
"LHA-1 Tarawa",
"LHA-4 Nassau"
],
"navy_generators": [
"OliverHazardPerryGroupGenerator"
],
"has_jtac": true,
"jtac_unit": "MQ_9_Reaper"
}

View File

@@ -25,7 +25,9 @@
"LAC_M8_Greyhound",
"TD_M10_GMC",
"Daimler_Armoured_Car",
"LT_Mk_VII_Tetrarch"
"LT_Mk_VII_Tetrarch",
"AA_gun_QF_3_7",
"AAA_Bofors_40mm"
],
"artillery_units": [
"M12_GMC"

View File

@@ -15,7 +15,8 @@
],
"frontline_units": [
"MT_M4_Sherman",
"APC_M2A1"
"APC_M2A1",
"AAA_Bofors_40mm"
],
"artillery_units": [
],

View File

@@ -25,7 +25,8 @@
],
"frontline_units": [
"MBT_M60A3_Patton",
"APC_M113"
"APC_M113",
"SAM_Chaparral_M48"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@@ -26,7 +26,8 @@
],
"frontline_units": [
"MBT_M60A3_Patton",
"APC_M113"
"APC_M113",
"SAM_Chaparral_M48"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@@ -27,7 +27,8 @@
],
"frontline_units": [
"MBT_M60A3_Patton",
"APC_M113"
"APC_M113",
"SAM_Chaparral_M48"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@@ -43,7 +43,9 @@
"IFV_Marder",
"IFV_LAV_25",
"APC_M1043_HMMWV_Armament",
"ATGM_M1045_HMMWV_TOW"
"ATGM_M1045_HMMWV_TOW",
"SAM_Linebacker_M6",
"SAM_Avenger_M1097"
],
"artillery_units": [
"MLRS_M270",

View File

@@ -20,7 +20,8 @@
"MBT_Leopard_2",
"IFV_LAV_25",
"APC_M113",
"IFV_MCV_80"
"IFV_MCV_80",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@@ -22,7 +22,8 @@
"ZTZ_96B",
"MBT_T_55",
"ZBD_04A",
"IFV_BMP_1"
"IFV_BMP_1",
"HQ_7_Self_Propelled_LN"
],
"artillery_units": [
"MLRS_9A52_Smerch",
@@ -48,6 +49,7 @@
"SA13Generator",
"Tier2SA10Generator",
"ZSU23Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralGenerator"
],

View File

@@ -24,7 +24,8 @@
"ATGM_M1134_Stryker",
"IFV_LAV_25",
"APC_M1043_HMMWV_Armament",
"ATGM_M1045_HMMWV_TOW"
"ATGM_M1045_HMMWV_TOW",
"SAM_Roland_ADS"
],
"artillery_units": [
"MLRS_M270",
@@ -36,7 +37,8 @@
"infantry_units": [
"Infantry_M4",
"Soldier_M249",
"Stinger_MANPADS"
"Stinger_MANPADS",
"_2B11_mortar"
],
"air_defenses": [
"RolandGenerator",

View File

@@ -31,7 +31,8 @@
"VBAE_CRAB",
"VBAE_CRAB_MMP",
"AMX_30B2",
"Leclerc_Serie_XXI"
"Leclerc_Serie_XXI",
"SAM_Roland_ADS"
],
"artillery_units": [
"MLRS_M270",

View File

@@ -34,7 +34,8 @@
"VBAE_CRAB",
"VBAE_CRAB_MMP",
"AMX_30B2",
"Leclerc_Serie_XXI"
"Leclerc_Serie_XXI",
"SAM_Roland_ADS"
],
"artillery_units": [
"MLRS_M270",

View File

@@ -17,7 +17,8 @@
"IFV_BMP_1",
"IFV_BMP_2",
"MBT_T_72B",
"MBT_T_55"
"MBT_T_55",
"SAM_SA_13_Strela_10M3_9A35M3"
],
"artillery_units": [
"MLRS_BM_21_Grad",

View File

@@ -13,7 +13,8 @@
"MT_Pz_Kpfw_IV_Ausf_H",
"APC_Sd_Kfz_251",
"AC_Sd_Kfz_234_2_Puma",
"TD_Jagdpanzer_IV"
"TD_Jagdpanzer_IV",
"AAA_8_8cm_Flak_18"
],
"artillery_units": [
"Sturmpanzer_IV_Brummbär"

View File

@@ -18,7 +18,11 @@
"AC_Sd_Kfz_234_2_Puma",
"Sd_Kfz_184_Elefant",
"TD_Jagdpanther_G1",
"TD_Jagdpanzer_IV"
"TD_Jagdpanzer_IV",
"StuG_III_Ausf__G",
"StuG_IV",
"AAA_8_8cm_Flak_18",
"AAA_8_8cm_Flak_41"
],
"artillery_units": [
"Sturmpanzer_IV_Brummbär"

View File

@@ -10,7 +10,8 @@
],
"frontline_units": [
"MT_Pz_Kpfw_IV_Ausf_H",
"APC_Sd_Kfz_251"
"APC_Sd_Kfz_251",
"AAA_8_8cm_Flak_18"
],
"artillery_units": [
],

View File

@@ -22,7 +22,8 @@
"TPz_Fuchs",
"MBT_Leopard_1A3",
"MBT_Leopard_2",
"IFV_Marder"
"IFV_Marder",
"SPAAA_Gepard"
],
"artillery_units": [
],
@@ -32,6 +33,7 @@
"infantry_units": [
"Infantry_M4",
"Soldier_M249",
"_2B11_mortar",
"Stinger_MANPADS"
],
"air_defenses": [

View File

@@ -22,7 +22,8 @@
"frontline_units": [
"MBT_T_90",
"MBT_T_72B",
"IFV_BMP_2"
"IFV_BMP_2",
"SAM_SA_19_Tunguska_2S6"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",

View File

@@ -22,6 +22,7 @@
"infantry_units": [
"Infantry_Soldier_Insurgents",
"Soldier_RPG",
"_2B11_mortar",
"SAM_SA_18_Igla_MANPADS"
],
"air_defenses": [

View File

@@ -0,0 +1,39 @@
{
"country": "Insurgents",
"name": "Insurgents (Hard)",
"authors": "Khopa",
"description": "<p>Insurgents faction.</p>",
"aircrafts": [
],
"frontline_units": [
"ATGM_M1045_HMMWV_TOW",
"APC_M1043_HMMWV_Armament",
"ARV_BRDM_2",
"APC_BTR_80",
"ARV_BTR_RD",
"IFV_BMP_1",
"MBT_T_55",
"AAA_ZU_23_Insurgent_on_Ural_375",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad",
"SPH_2S19_Msta"
],
"logistics_units": [
"Transport_Ural_375",
"Transport_UAZ_469"
],
"infantry_units": [
"Infantry_Soldier_Insurgents",
"Soldier_RPG",
"_2B11_mortar",
"SAM_SA_18_Igla_MANPADS"
],
"air_defenses": [
"SA9Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralInsurgentGenerator"
]
}

View File

@@ -27,7 +27,9 @@
"APC_BTR_80",
"MBT_M60A3_Patton",
"IFV_BMP_1",
"MBT_T_72B"
"MBT_T_72B",
"SPAAA_ZSU_23_4_Shilka",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad",

View File

@@ -0,0 +1,88 @@
{
"country": "Iraq",
"name": "Iraq 1991",
"authors": "Hawkmoon",
"description": "<p>Iraq forces during desert Storm</p>",
"aircrafts": [
"MiG_19P",
"MiG_21Bis",
"MiG_23MLD",
"MiG_25PD",
"Su_17M4",
"Mi_8MT",
"Su-25",
"Su-24M",
"MiG_25PD",
"Tu_22M3",
"L_39C",
"L_39ZA",
"Mi_24V",
"MiG_29A"
],
"awacs": [
"A_50"
],
"tankers": [
"IL_78M"
],
"frontline_units": [
"IFV_BMP_1",
"APC_MTLB",
"MBT_T_55",
"MBT_T_72B",
"APC_BTR_80",
"ARV_BRDM_2",
"SPH_2S1_Gvozdika",
"AAA_ZSU_57_2",
"SPAAA_ZSU_23_4_Shilka"
],
"artillery_units": [
"MLRS_BM_21_Grad"
],
"logistics_units": [
"Transport_Ural_375",
"Transport_UAZ_469"
],
"infantry_units": [
"Paratrooper_AKS",
"Infantry_Soldier_Rus",
"Paratrooper_RPG_16",
"SAM_SA_18_Igla_MANPADS"
],
"air_defenses": [
"ColdWarFlakGenerator",
"EarlyColdWarFlakGenerator",
"SA2Generator",
"SA3Generator",
"SA6Generator",
"SA8Generator",
"SA9Generator",
"SA13Generator",
"ZSU23Generator",
"ZU23Generator",
"ZU23UralGenerator"
],
"ewrs": [
"BoxSpringGenerator"
],
"missiles": [
"ScudGenerator"
],
"missiles_group_count": 1,
"aircraft_carrier": [
],
"helicopter_carrier": [
],
"helicopter_carrier_names": [
],
"destroyers": [
],
"cruisers": [
],
"requirements": {},
"carrier_names": [
],
"navy_generators": [
"GrishaGroupGenerator"
]
}

View File

@@ -15,7 +15,8 @@
"MT_M4A4_Sherman_Firefly",
"APC_M2A1",
"MT_M4_Sherman",
"LAC_M8_Greyhound"
"LAC_M8_Greyhound",
"AAA_Bofors_40mm"
],
"artillery_units": [
],

View File

@@ -19,7 +19,8 @@
"MT_M4_Sherman",
"APC_M2A1",
"MBT_M60A3_Patton",
"APC_M113"
"APC_M113",
"SAM_Chaparral_M48"
],
"artillery_units": [
],

View File

@@ -22,7 +22,8 @@
"frontline_units": [
"APC_M113",
"MBT_M60A3_Patton",
"MBT_Merkava_Mk__4"
"MBT_Merkava_Mk__4",
"AAA_Vulcan_M163"
],
"artillery_units": [
],

View File

@@ -23,7 +23,8 @@
"APC_M113",
"APC_M1043_HMMWV_Armament",
"ATGM_M1045_HMMWV_TOW",
"MBT_Merkava_Mk__4"
"MBT_Merkava_Mk__4",
"AAA_Vulcan_M163"
],
"artillery_units": [
"SPH_M109_Paladin",

View File

@@ -18,7 +18,8 @@
],
"frontline_units": [
"MBT_Leopard_1A3",
"APC_M113"
"APC_M113",
"SAM_Avenger_M1097"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@@ -19,7 +19,8 @@
],
"frontline_units": [
"MBT_Leopard_1A3",
"APC_M113"
"APC_M113",
"SAM_Avenger_M1097"
],
"artillery_units": [
"SPH_M109_Paladin"

View File

@@ -23,7 +23,8 @@
"IFV_Marder",
"TPz_Fuchs",
"IFV_LAV_25",
"APC_M1043_HMMWV_Armament"
"APC_M1043_HMMWV_Armament",
"SPAAA_Gepard"
],
"artillery_units": [
"SPH_M109_Paladin",

View File

@@ -20,7 +20,9 @@
"IFV_BMP_1",
"ARV_BRDM_2",
"MBT_T_72B",
"MBT_T_55"
"MBT_T_55",
"SPAAA_ZSU_23_4_Shilka",
"SAM_SA_8_Osa_9A33"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@@ -17,7 +17,8 @@
],
"frontline_units": [
"APC_M113",
"MBT_Leopard_1A3"
"MBT_Leopard_1A3",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@@ -24,7 +24,9 @@
"IFV_BMP_1",
"MBT_T_55",
"MBT_T_72B",
"MBT_T_80U"
"MBT_T_80U",
"AAA_ZSU_57_2",
"SAM_SA_9_Strela_1_9P31"
],
"artillery_units": [
"MLRS_BM_21_Grad",
@@ -48,7 +50,8 @@
"SA9Generator",
"SA13Generator",
"ZU23Generator",
"ZSU23Generator"
"ZSU23Generator",
"ZSU57Generator"
],
"ewrs": [
"BoxSpringGenerator",

View File

@@ -23,7 +23,8 @@
"MBT_T_55",
"ZBD_04A",
"APC_BTR_80",
"APC_M113"
"APC_M113",
"HQ_7_Self_Propelled_LN"
],
"artillery_units": [
"MLRS_9A52_Smerch",

View File

@@ -13,7 +13,8 @@
"frontline_units": [
"APC_Cobra",
"APC_BTR_80",
"ARV_BRDM_2"
"ARV_BRDM_2",
"SAM_SA_13_Strela_10M3_9A35M3"
],
"artillery_units": [
"SPH_2S19_Msta"

View File

@@ -11,7 +11,9 @@
],
"frontline_units": [
"APC_M1043_HMMWV_Armament",
"IFV_MCV_80"
"IFV_MCV_80",
"IFV_LAV_25",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@@ -12,7 +12,9 @@
],
"frontline_units": [
"APC_M1043_HMMWV_Armament",
"IFV_MCV_80"
"IFV_MCV_80",
"IFV_LAV_25",
"SAM_Avenger_M1097"
],
"artillery_units": [
],

View File

@@ -17,7 +17,8 @@
"FDDM_Grad",
"APC_MTLB",
"MBT_T_55",
"AAA_ZU_23_on_Ural_375"
"AAA_ZU_23_on_Ural_375",
"AAA_8_8cm_Flak_18"
],
"artillery_units": [
"MLRS_BM_21_Grad"

View File

@@ -21,7 +21,9 @@
"ARV_BTR_RD",
"IFV_BMD_1",
"IFV_BMP_1",
"MBT_T_55"
"MBT_T_55",
"AAA_ZU_23_on_Ural_375",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad"
@@ -39,6 +41,7 @@
"SA2Generator",
"SA3Generator",
"ZSU23Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralGenerator"
],

View File

@@ -14,7 +14,8 @@
"APC_BTR_80",
"IFV_BMD_1",
"IFV_BMP_1",
"MBT_T_55"
"MBT_T_55",
"AAA_ZSU_57_2"
],
"artillery_units": [
"MLRS_BM_21_Grad",
@@ -38,6 +39,7 @@
"SA11Generator",
"ColdWarFlakGenerator",
"ZSU23Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralGenerator"
],

View File

@@ -25,7 +25,8 @@
"APC_BTR_80",
"IFV_BMD_1",
"IFV_BMP_1",
"MBT_T_55"
"MBT_T_55",
"SAM_SA_8_Osa_9A33"
],
"artillery_units": [
"MLRS_BM_21_Grad",
@@ -38,7 +39,8 @@
],
"infantry_units": [
"Infantry_Soldier_Rus",
"Soldier_RPG"
"Soldier_RPG",
"_2B11_mortar"
],
"air_defenses": [
"ColdWarFlakGenerator",
@@ -49,6 +51,7 @@
"SA9Generator",
"SA13Generator",
"ZSU23Generator",
"ZSU57Generator",
"ZU23Generator",
"ZU23UralGenerator"
],

View File

@@ -29,7 +29,8 @@
"IFV_BMP_1",
"IFV_BMP_2",
"MBT_T_72B",
"MBT_T_80U"
"MBT_T_80U",
"SAM_SA_13_Strela_10M3_9A35M3"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",
@@ -43,6 +44,7 @@
"Paratrooper_AKS",
"Infantry_Soldier_Rus",
"Paratrooper_RPG_16",
"_2B11_mortar",
"SAM_SA_18_Igla_S_MANPADS"
],
"air_defenses": [

View File

@@ -31,9 +31,11 @@
"IFV_BMP_2",
"IFV_BMP_3",
"APC_BTR_80",
"APC_BTR_82A",
"MBT_T_90",
"MBT_T_80U",
"MBT_T_72B"
"MBT_T_72B3",
"SAM_SA_19_Tunguska_2S6"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",
@@ -47,6 +49,7 @@
"Paratrooper_AKS",
"Infantry_Soldier_Rus",
"Paratrooper_RPG_16",
"_2B11_mortar",
"SAM_SA_18_Igla_MANPADS"
],
"air_defenses": [

View File

@@ -32,7 +32,8 @@
"IFV_BMP_3",
"MBT_T_90",
"MBT_T_80U",
"MBT_T_72B"
"MBT_T_72B",
"SAM_SA_19_Tunguska_2S6"
],
"artillery_units": [
"MLRS_9K57_Uragan_BM_27",

View File

@@ -11,7 +11,8 @@
"MT_M4_Sherman",
"APC_M2A1",
"Daimler_Armoured_Car",
"LT_Mk_VII_Tetrarch"
"LT_Mk_VII_Tetrarch",
"AAA_Bofors_40mm"
],
"artillery_units": [
"MLRS_BM_21_Grad"

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