Compare commits

..

39 Commits

Author SHA1 Message Date
Khopa
a7824039e3 Added revenue for derrick building 2020-06-07 15:54:15 +02:00
Khopa
bb9247e821 Fix info panel messages. 2020-06-06 16:57:20 +02:00
Khopa
4bc04ec031 Auto redeploy frontline units. 2020-06-06 16:50:19 +02:00
Khopa
8fd91e6c5c Added possible ground target : Oil derrick 2020-06-06 15:45:15 +02:00
Khopa
ee137d086a Change power station template. (Some buildings could superpose in older version) 2020-06-06 15:10:07 +02:00
Khopa
fcd81850cb Debriefing and info on AA site and buildings destroyed. KC130 replace S3B 2020-06-06 04:10:22 +02:00
Khopa
aa4b07d024 Add more waypoints to generated flights 2020-06-05 21:24:23 +02:00
Khopa
16a096d288 Possible to setup whether AI should starts from parking or not. 2020-06-05 14:23:38 +02:00
Khopa
b219b2a71b Possible to setup custom saved game and installation directory. 2020-06-05 14:21:42 +02:00
Khopa
ce70242c35 Added 947 to allies and default payload for P47 2020-06-04 13:30:38 +02:00
Khopa
dec01e2611 Added P-47 to db 2020-06-04 13:25:14 +02:00
Khopa
4373d89661 WIP on CAP, and AI units can starts from ground uncontrolled instead of using late activation. 2020-06-03 00:45:44 +02:00
Khopa
c73290eebb Nav Target point for JF-17 and F-14.
Fix activation trigger using wrong coalition when playing REDFOR side.
2020-06-02 13:00:10 +02:00
Khopa
a022f9c2e1 Changelog update 2020-06-01 15:27:45 +02:00
Khopa
5adb25a695 Using late activation trigger. Planned flight will not spawn if their home base has been captured or is contested. 2020-06-01 15:11:38 +02:00
Khopa
08c2972cf9 Added map theater : North Caucasus 2020-06-01 14:20:43 +02:00
Khopa
8132c7e676 Fix : CAP aircraft will jettison fuel tanks. 2020-06-01 13:03:35 +02:00
Khopa
8c68c9f703 Fix for payload for release versions. 2020-06-01 12:51:59 +02:00
Khopa
000b6142fd Added Ka-50 to bluefor modern. 2020-06-01 12:42:02 +02:00
Khopa
c203ded1cd Improved Mission Planning flight selection behaviour. 2020-06-01 12:36:10 +02:00
Khopa
64c5c39b2a BARCAP renamed CAP for regular airbases. 2020-06-01 11:14:30 +02:00
Khopa
a8d2a1e371 Removed Ju-88 from CAS (only has torpedo in DCS) 2020-06-01 01:49:20 +02:00
Khopa
9e5846b24a Improved WW2 support. 2020-06-01 01:27:16 +02:00
Khopa
836ff9122c Fix base defense units not being generated when other ground objects were too close. 2020-06-01 00:35:26 +02:00
Khopa
75d836358b Revert an error on sam site generation. (Disabled the wrong code.) 2020-06-01 00:05:32 +02:00
Khopa
bb11e7f90c Removed FARP AA that does not respect the factions config. 2020-05-31 23:58:43 +02:00
Khopa
cf6a71ab86 Fix issue when "no night mission" is enabled. 2020-05-31 23:50:13 +02:00
Khopa
7f7288937d Handle error when there is no AWACS for a faction. 2020-05-31 23:26:03 +02:00
Khopa
a38f9c2183 Added performance settings. 2020-05-31 23:07:54 +02:00
Khopa
7ee880cadc Generate ICLS command for carriers 2020-05-31 20:00:07 +02:00
Khopa
9ae34d474b Added Bluefor modern faction 2020-05-31 19:44:53 +02:00
Khopa
2817e2f2c8 Update for new pyDCS version. Supercarrier support added. 2020-05-31 19:19:03 +02:00
Khopa
02886a09d3 AI Strike flight with user generated STRIKE, will now perform their bombing task correctly. 2020-05-31 18:02:00 +02:00
Khopa
0b9d827ad6 AI Strike flight will bomb all their targets correctly 2020-05-31 15:43:56 +02:00
Khopa
ab3ea84d70 AI flight planner now auto generate STRIKE flights.
Fix CAS point position in predefined wpt selector.
When an airbase is captured, base defenses are re-generated for the new base owner.
2020-05-30 02:32:45 +02:00
Khopa
03a1c44659 README update for QT UI branch 2020-05-30 00:03:42 +02:00
Khopa
53364444fd Possible fix for crash when loading old save files. 2020-05-30 00:03:11 +02:00
Khopa
94040e8551 Fix new campaign wizard crashing and made the campaign generator slightly faster. 2020-05-29 23:47:13 +02:00
Khopa
34d46ee28e Fix objective name error when no more objective name are availables. 2020-05-29 23:43:26 +02:00
86 changed files with 2523 additions and 1067 deletions

View File

@@ -1,26 +1,12 @@
![Logo](https://i.imgur.com/c2k18E1.png)
[DCS World](https://www.digitalcombatsimulator.com/en/products/world/) single-player dynamic campaign.
[DCS World](https://www.digitalcombatsimulator.com/en/products/world/) single-player semi dynamic campaign.
Uses [pydcs](http://github.com/pydcs/dcs) for mission generation.
DCS Liberation uses [pydcs](http://github.com/pydcs/dcs) for mission generation
and [Mist](https://github.com/mrSkortch/MissionScriptingTools) for mission scripting
## Tutorials
* [Manual](https://github.com/shdwp/dcs_liberation/wiki/Manual)
## Resources
You should start with the manual, it covers everything you need to know before playing the campaign.
* [Getting Started](https://github.com/Khopa/dcs_liberation/wiki/Getting-started)
* [Strike objectives reference images](https://imgur.com/a/vCSHa9f)
If you can't find the strike objective you can see here how it's supposed to look.
* [Troubleshooting](https://github.com/shdwp/dcs_liberation/wiki/Troubleshooting)
You could also briefly check the troubleshooting page to get familiar with the known issues that you could probably fix by yourself.
* [Modding tutorial](https://github.com/shdwp/dcs_liberation/wiki/Modding-tutorial)
Modding tutorial will cover how to change default loadouts, configure which planes are present in the campaign (or add new altogether) and more. Check this out if you find that something is not going for your liking, there could be a tutorial for changing that. Although be aware that it would require changing source files and could easily result in non functioning application.
* [Development guide](https://github.com/shdwp/dcs_liberation/wiki/Development-guide)
If you want to contribute to the project, this will give you a brief overview and on how to actually run it from source files.
* [Tutorials](https://github.com/Khopa/dcs_liberation/wiki/Tutorial-01-:-UI)

57
changelog.md Normal file
View File

@@ -0,0 +1,57 @@
#2.0 RC 7
##Features/Improvements :
* **[Units/Factions]** Added P-47D-30 for factions allies_1944
* **[Units/Factions]** Replaced S3-B Tanker by KC130 for most factions
* **[Mission Generator]** AI Flight generator has been reworked
* **[Mission Generator]** Add PP points for JF-17 on STRIKE missions
* **[Mission Generator]** Add ST point for F-14B on STRIKE missions
* **[Mission Generator]** Flights with client slots will never be delayed
* **[Mission Generator]** AI units can start from parking (Added a new setting)
* **[Mission Generator]** Tacan for carrier will only be in Mode X from now
* **[Mission Generator]** RTB waypoints for autogenerated flights
* **[Info Panel]** Added information about destroyed buildings in info panel
* **[Info Panel]** Added information about destroyed units at SAM site in info panel
* **[Info Panel]** Added information about units destroyed outside the frontline in the debriefing window
* **[Info Panel]** Added information about buildings destroyed in the debriefing window
* **[Map]** Tooltip now contains the list of building for Strike targets on the map
* **[Map]** Added "Oil derrick" building
* **[Misc]** Made it possible to setup DCS Saved Games directory and DCS installation directory manually
##Fixed issues :
* **[Mission Generator]** When playing as RED the activation trigger would not be properly generated
* **[Mission Generator]** Changed "strike" payload for Su-24M that was innefective
* **[Mission Generator]** Changed "strike" payload for JF-17 to use LS-6 bombs instead of GBU
* **[Mission Generator]** FW-190A8 is now properly considered as a flyable
* **[Maps/Campaign]** Now using Vasiani airport instead of Soganlung in North Caucasus campaign (More parking slots)
* **[Info Panel]** Message displayed on base capture event stated that the ennemy captured an airbase, while it was the player who captured it.
* **[Map]** Graphical glitch on map when one building of an objective was destroyed, but not the others
* **[Map]** Change power station template. (Buildings could end up superposed).
#2.0 RC 6
Saves file from RC5 are not compatible with the new version.
Sorry :(
##Features/Improvements :
* **[Units/Factions]** Supercarrier support (You have to go to settings to enable it, if you have the supercarrier module)
* **[Units/Factions]** Added 'Modern Bluefor' factions, containing all most popular DCS flyable units
* **[Units/Factions]** Factions US 2005 / 1990 will now sometimes have Arleigh Burke class ships instead of Perry as carrier escorts
* **[Units/Factions]** Added support for newest WW2 Units
* **[Campaign logic]** When a base is captured, refill the "base defenses" group with units for the new owner.
* **[Mission Generator]** Carrier ICLS channel will now be configured (check your briefing)
* **[Mission Generator]** SAM units will spawn on RED Alarm state
* **[Mission Generator]** AI Flight planner now creates its own STRIKE flights
* **[Mission Generator]** AI units assigned to Strike flight will now actually engage the buildings they have been assigned.
* **[Mission Generator]** Added performance settings to allow disabling : smoke, artillery strike, moving units, infantry, SAM Red alert mode.
* **[Mission Generator]** Using Late Activation & Trigger in attempt to improve performance & reduce stutter (Previously they were spawned through 'ETA' feature)
* **[UX]** : Improved flight selection behaviour in the Mission Planning Window
##Fixed issues :
* **[Mission Generator]** Payloads were not correctly assigned in the release version.
* **[Mission Generator]** Game generation does not work when "no night mission" settings was selected and the current time was "day"
* **[Mission Generator]** Game generation does not work when the player selected faction has no AWACS
* **[Mission Generator]** Planned flights will spawn even if their home base has been captured or is being contested by enemy ground units.
* **[Campaign Generator]** Base defenses would not be generated on Normandy map and in some rare cases on others maps as well
* **[Mission Planning]** CAS waypoints created from the "Predefined waypoint selector" would not be at the exact location of the frontline
* **[Naming]** CAP mission flown from airbase are not named BARCAP anymore (CAP from carrier is still named BARCAP)

View File

@@ -44,6 +44,7 @@ from game.factions.usa_1960 import USA_1960
from game.factions.usa_1965 import USA_1965
from game.factions.usa_1990 import USA_1990
from game.factions.usa_2005 import USA_2005
from game.factions.bluefor_modern import BLUEFOR_MODERN
"""
---------- BEGINNING OF CONFIGURATION SECTION
@@ -83,11 +84,14 @@ PRICES = {
J_11A: 26,
JF_17: 20,
Su_30: 24,
SpitfireLFMkIX:3,
SpitfireLFMkIXCW:3,
Bf_109K_4:3,
FW_190D9:3,
FW_190A8:3,
SpitfireLFMkIX: 8,
SpitfireLFMkIXCW: 8,
Bf_109K_4: 8,
FW_190D9: 8,
FW_190A8: 8,
A_20G: 12,
Ju_88A4: 12,
F_5E_3: 8,
MiG_15bis: 4,
@@ -146,18 +150,20 @@ PRICES = {
S_3B_Tanker: 13,
IL_78M: 13,
KC_135: 13,
KC130: 13,
A_50: 8,
E_3A: 8,
C_130: 8,
# WW2
P_51D_30_NA: 3,
P_51D: 3,
P_51D_30_NA: 6,
P_51D: 6,
P_47D_30: 6,
# armor
Armor.APC_MTLB: 4,
Armor.ARV_MTLB_U_BOMAN: 5,
Armor.FDDM_Grad: 5,
Armor.ARV_BRDM_2: 6,
Armor.ARV_BTR_RD: 8,
Armor.APC_BTR_80: 8,
@@ -227,22 +233,31 @@ PRICES = {
AirDefence.SAM_Chaparral_M48: 10,
# WW2
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G:7,
Armor.MT_Pz_Kpfw_IV_Ausf_H:4,
Armor.HT_Pz_Kpfw_VI_Tiger_I:10,
Armor.HT_Pz_Kpfw_VI_Ausf__B__Tiger_II:12,
Armor.APC_Sd_Kfz_251:3,
Armor.IFV_Sd_Kfz_234_2_Puma:4,
Armor.MT_M4_Sherman:4,
Armor.MT_M4A4_Sherman_Firefly:6,
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G:18,
Armor.MT_Pz_Kpfw_IV_Ausf_H:8,
Armor.HT_Pz_Kpfw_VI_Tiger_I:22,
Armor.HT_Pz_Kpfw_VI_Ausf__B__Tiger_II:26,
Armor.TD_Jagdpanther_G1: 16,
Armor.TD_Jagdpanzer_IV: 10,
Armor.Sd_Kfz_184_Elefant: 18,
Armor.APC_Sd_Kfz_251:2,
Armor.IFV_Sd_Kfz_234_2_Puma:6,
Armor.MT_M4_Sherman:5,
Armor.MT_M4A4_Sherman_Firefly:8,
Armor.CT_Cromwell_IV:8,
Armor.M30_Cargo_Carrier:2,
Armor.APC_M2A1:2,
AirDefence.AAA_Bofors_40mm:4,
AirDefence.AAA_Flak_36:6,
AirDefence.AAA_Flak_18:4,
Artillery.M12_GMC:2,
Artillery.Sturmpanzer_IV_Brummbär:2,
Armor.ST_Centaur_IV: 8,
Armor.HIT_Churchill_VII: 12,
Armor.LAC_M8_Greyhound: 4,
Armor.TD_M10_GMC: 8,
Armor.StuG_III_Ausf__G: 6,
AirDefence.AAA_Bofors_40mm: 4,
AirDefence.AAA_8_8cm_Flak_36: 6,
AirDefence.AAA_8_8cm_Flak_18: 4,
Artillery.M12_GMC: 2,
Artillery.Sturmpanzer_IV_Brummbär: 2,
# ship
CV_1143_5_Admiral_Kuznetsov: 100,
@@ -299,7 +314,7 @@ UNIT_BY_TASK = {
FW_190D9,
FW_190A8,
SpitfireLFMkIXCW,
SpitfireLFMkIX
SpitfireLFMkIX,
],
CAS: [
F_86F_Sabre,
@@ -330,6 +345,9 @@ UNIT_BY_TASK = {
Mi_28N,
Mi_24V,
MiG_27K,
A_20G,
P_47D_30,
Ju_88A4,
],
Transport: [
IL_76MD,
@@ -342,6 +360,7 @@ UNIT_BY_TASK = {
Refueling: [
IL_78M,
KC_135,
KC130,
S_3B_Tanker,
],
AWACS: [E_3A, A_50, ],
@@ -351,11 +370,11 @@ UNIT_BY_TASK = {
Armor.APC_MTLB,
Armor.APC_MTLB,
Armor.APC_MTLB,
Armor.ARV_MTLB_U_BOMAN,
Armor.ARV_MTLB_U_BOMAN,
Armor.ARV_MTLB_U_BOMAN,
Armor.ARV_MTLB_U_BOMAN,
Armor.ARV_MTLB_U_BOMAN,
Armor.FDDM_Grad,
Armor.FDDM_Grad,
Armor.FDDM_Grad,
Armor.FDDM_Grad,
Armor.FDDM_Grad,
Armor.ARV_BRDM_2,
Armor.ARV_BRDM_2,
Armor.ARV_BRDM_2,
@@ -450,6 +469,33 @@ UNIT_BY_TASK = {
Armor.APC_M2A1,
Armor.APC_M2A1,
Armor.APC_M2A1,
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.TD_Jagdpanther_G1,
Armor.TD_Jagdpanzer_IV,
Armor.Sd_Kfz_184_Elefant,
Armor.APC_Sd_Kfz_251,
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.CT_Cromwell_IV,
Armor.M30_Cargo_Carrier,
Armor.M30_Cargo_Carrier,
Armor.M30_Cargo_Carrier,
Armor.APC_M2A1,
Armor.APC_M2A1,
Armor.ST_Centaur_IV,
Armor.ST_Centaur_IV,
Armor.HIT_Churchill_VII,
Armor.LAC_M8_Greyhound,
Armor.LAC_M8_Greyhound,
Armor.TD_M10_GMC,
Armor.TD_M10_GMC,
Armor.StuG_III_Ausf__G,
Artillery.M12_GMC,
Artillery.Sturmpanzer_IV_Brummbär,
Artillery.MLRS_M270,
Artillery.SPH_M109_Paladin,
@@ -534,6 +580,7 @@ SAM_CONVERT = {
}
}
"""
Units that will always be spawned in the air
"""
@@ -568,9 +615,10 @@ FACTIONS = {
"USA 1965": USA_1965,
"USA 1960": USA_1960,
"USA 1955 (Require WW2 Pack)": USA_1955,
"USA 1944 (Require WW2 Pack)": USA_1944,
"France 1995": France_1995,
"Allies 1944 (Require WW2 Pack)": USA_1944,
"Bluefor Modern": BLUEFOR_MODERN,
"France 2005": France_2005,
"France 1995": France_1995,
"Germany 1990": Germany_1990,
"Netherlands 1990": Netherlands_1990,
"United Kingdown 1990": UnitedKingdom_1990,
@@ -637,7 +685,15 @@ PLANE_PAYLOAD_OVERRIDES = {
AntishipStrike: "ANTISHIP",
GroundAttack: "STRIKE"
},
F_A_18C: {
CAP: "CAP HEAVY",
Intercept: "CAP HEAVY",
CAS: "CAS MAVERICK F",
PinpointStrike: "STRIKE",
SEAD: "SEAD",
AntishipStrike: "ANTISHIP",
GroundAttack: "STRIKE"
},
A_10A: COMMON_OVERRIDE,
A_10C: COMMON_OVERRIDE,
AV8BNA: COMMON_OVERRIDE,
@@ -741,7 +797,8 @@ TIME_PERIODS = {
REWARDS = {
"power": 4, "warehouse": 2, "fuel": 2, "ammo": 2,
"farp": 1, "fob": 1, "factory": 10, "comms": 10, "oil": 10
"farp": 1, "fob": 1, "factory": 10, "comms": 10, "oil": 10,
"derrick": 8
}
# Base post-turn bonus value
@@ -797,6 +854,21 @@ TaskForceDict = typing.Dict[typing.Type[Task], AssignedUnitsDict]
StartingPosition = typing.Optional[typing.Union[ShipGroup, StaticGroup, Airport, Point]]
def upgrade_to_supercarrier(unit, name: str):
if unit == CVN_74_John_C__Stennis:
if name == "CVN-71 Theodore Roosevelt":
return CVN_71_Theodore_Roosevelt
elif name == "CVN-72 Abraham Lincoln":
return CVN_72_Abraham_Lincoln
elif name == "CVN-73 George Washington":
return CVN_73_George_Washington
else:
return CVN_71_Theodore_Roosevelt
elif unit == CV_1143_5_Admiral_Kuznetsov:
return CV_1143_5_Admiral_Kuznetsov_2017
else:
return unit
def unit_task(unit: UnitType) -> Task:
for task, units in UNIT_BY_TASK.items():
if unit in units:
@@ -962,14 +1034,6 @@ def _validate_db():
assert unit_type not in total_set, "{} is duplicate for task {}".format(unit_type, t)
total_set.add(unit_type)
# check country allegiance
for unit_type in total_set:
did_find = False
for country_units_list in FACTIONS.values():
if unit_type in country_units_list["units"]:
did_find = True
print("WARN : {} not in country list".format(unit_type))
# check prices
for unit_type in total_set:
assert unit_type in PRICES, "{} not in prices".format(unit_type)
@@ -982,4 +1046,5 @@ class DefaultLiveries:
OH_58D.Liveries = DefaultLiveries
F_16C_50.Liveries = DefaultLiveries
P_51D_30_NA.Liveries = DefaultLiveries
P_51D_30_NA.Liveries = DefaultLiveries
Ju_88A4.Liveries = DefaultLiveries

View File

@@ -13,6 +13,7 @@ from theater import *
from gen.environmentgen import EnvironmentSettings
from gen.conflictgen import Conflict
from game.db import assigned_units_from, unitdict_from
from theater.start_generator import generate_airbase_defense_group
from userdata.debriefing import Debriefing
from userdata import persistency
@@ -126,17 +127,6 @@ class Event:
self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn.miz"))
self.environment_settings = self.operation.environment_settings
def generate_quick(self):
pass
# TODO : This is not needed anymore. The player can start mission in flight from the flight planner if he want it to be quick.
# TODO : remove this method
#self.operation.is_awacs_enabled = self.is_awacs_enabled
#self.operation.environment_settings = self.environment_settings
#
#self.operation.prepare(self.game.theater.terrain, is_quick=True)
#self.operation.generate()
#self.operation.current_mission.save(persistency.mission_path_for("liberation_nextturn_quick.miz"))
def commit(self, debriefing: Debriefing):
logging.info("Commiting mission results")
@@ -189,16 +179,30 @@ class Event:
logging.info("cp {} killing ground object {}".format(cp, ground_object.string_identifier))
cp.ground_objects[i].is_dead = True
info = Information("Building destroyed",
ground_object.dcs_identifier + " has been destroyed at location " + ground_object.obj_name,
self.game.turn)
self.game.informations.append(info)
# -- AA Site groups
destroyed_units = 0
info = Information("Units destroyed at " + ground_object.obj_name,
"",
self.game.turn)
for i, ground_object in enumerate(cp.ground_objects):
if ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]:
for g in ground_object.groups:
for u in g.units:
if u.name == destroyed_ground_unit_name:
g.units.remove(u)
destroyed_units = destroyed_units + 1
info.text = u.type
ucount = sum([len(g.units) for g in ground_object.groups])
if ucount == 0:
ground_object.is_dead = True
if destroyed_units > 0:
self.game.informations.append(info)
# ------------------------------
# Captured bases
@@ -212,30 +216,40 @@ class Event:
id = int(captured.split("||")[0])
new_owner_coalition = int(captured.split("||")[1])
captured_cps = []
for cp in self.game.theater.controlpoints:
if cp.id == id:
if cp.captured and new_owner_coalition != coalition:
cp.captured = False
cp.base.aircraft = {}
cp.base.armor = {}
cp.base.aa = {}
for g in cp.ground_objects:
g.groups = []
info = Information(cp.name + " lost !",
"The ennemy took control of " + cp.name + "\nShame on us !",
self.game.turn)
info = Information(cp.name + " lost !", "The ennemy took control of " + cp.name + "\nShame on us !", self.game.turn)
self.game.informations.append(info)
pname = self.game.enemy_name
captured_cps.append(cp)
elif not(cp.captured) and new_owner_coalition == coalition:
cp.captured = True
cp.base.aircraft = {}
cp.base.armor = {}
cp.base.aa = {}
for g in cp.ground_objects:
g.groups = []
info = Information(cp.name + " captured !",
"The ennemy took control of " + cp.name + "\nShame on us !",
self.game.turn)
info = Information(cp.name + " captured !", "We took control of " + cp.name + "! Great job !", self.game.turn)
self.game.informations.append(info)
pname = self.game.player_name
captured_cps.append(cp)
else:
continue
cp.base.aircraft = {}
cp.base.armor = {}
airbase_def_id = 0
for g in cp.ground_objects:
g.groups = []
if g.airbase_group and pname != "":
generate_airbase_defense_group(airbase_def_id, g, pname, self.game, cp)
airbase_def_id = airbase_def_id + 1
for cp in captured_cps:
logging.info("Will run redeploy for " + cp.name)
self.redeploy_units(cp)
except Exception as e:
print(e)
@@ -313,6 +327,47 @@ class Event:
def skip(self):
pass
def redeploy_units(self, cp):
""""
Auto redeploy units to newly captured base
"""
ally_connected_cps = [ocp for ocp in cp.connected_points if cp.captured == ocp.captured]
enemy_connected_cps = [ocp for ocp in cp.connected_points if cp.captured != ocp.captured]
# If the newly captured cp does not have enemy connected cp,
# then it is not necessary to redeploy frontline units there.
if len(enemy_connected_cps) == 0:
return
else:
# From each ally cp, send reinforcements
for ally_cp in ally_connected_cps:
total_units_redeployed = 0
own_enemy_cp = [ocp for ocp in ally_cp.connected_points if ally_cp.captured != ocp.captured]
moved_units = {}
# If the connected base, does not have any more enemy cp connected.
# Or if it is not the opponent redeploying forces there (enemy AI will never redeploy all their forces at once)
if len(own_enemy_cp) > 0 or not cp.captured:
for frontline_unit, count in ally_cp.base.armor.items():
moved_units[frontline_unit] = int(count/2)
total_units_redeployed = total_units_redeployed + int(count/2)
else: # So if the old base, does not have any more enemy cp connected, or if it is an enemy base
for frontline_unit, count in ally_cp.base.armor.items():
moved_units[frontline_unit] = count
total_units_redeployed = total_units_redeployed + count
cp.base.commision_units(moved_units)
ally_cp.base.commit_losses(moved_units)
if total_units_redeployed > 0:
info = Information("Units redeployed", "", self.game.turn)
info.text = str(total_units_redeployed) + " units have been redeployed from " + ally_cp.name + " to " + cp.name
self.game.informations.append(info)
logging.info(info.text)
class UnitsDeliveryEvent(Event):
informational = True

View File

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

View File

@@ -11,7 +11,7 @@ France_1995 = {
Mirage_2000_5,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ France_2005 = {
FA_18C_hornet, # Standing as Rafale M
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -9,6 +9,7 @@ Germany_1944 = {
FW_190A8,
FW_190D9,
Bf_109K_4,
Ju_88A4,
Armor.MT_Pz_Kpfw_V_Panther_Ausf_G,
Armor.MT_Pz_Kpfw_IV_Ausf_H,
@@ -16,13 +17,20 @@ Germany_1944 = {
Armor.HT_Pz_Kpfw_VI_Ausf__B__Tiger_II,
Armor.APC_Sd_Kfz_251,
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.Sd_Kfz_184_Elefant,
Armor.TD_Jagdpanther_G1,
Armor.TD_Jagdpanzer_IV,
Artillery.Sturmpanzer_IV_Brummbär,
Unarmed.Sd_Kfz_2,
Unarmed.Sd_Kfz_7,
Unarmed.Kübelwagen_82,
Infantry.Infantry_Mauser_98,
AirDefence.AAA_Flak_36,
AirDefence.AAA_8_8cm_Flak_36,
],
"shorad":[
AirDefence.AAA_8_8cm_Flak_36,
]
}

View File

@@ -12,7 +12,7 @@ Germany_1990 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -15,7 +15,7 @@ India_2010 = {
Su_30,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ Israel_2000 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -28,7 +28,7 @@ Lybia_2011 = {
AirDefence.HQ_7_Self_Propelled_LN,
Armor.IFV_BMP_1,
Armor.ARV_MTLB_U_BOMAN,
Armor.FDDM_Grad,
Armor.ARV_BRDM_2,
Armor.MBT_T_55,
Armor.MBT_T_72B,

View File

@@ -11,7 +11,7 @@ Netherlands_1990 = {
F_5E_3,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -17,7 +17,7 @@ Russia_1955 = {
AirDefence.AAA_ZU_23_Closed,
AirDefence.AAA_ZU_23_on_Ural_375,
Armor.ARV_BRDM_2,
Armor.ARV_MTLB_U_BOMAN,
Armor.FDDM_Grad,
Armor.APC_MTLB,
Armor.MBT_T_55,
Artillery.MLRS_BM_21_Grad,

View File

@@ -12,7 +12,7 @@ Spain_1990 = {
C_101CC,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -11,7 +11,7 @@ Turkey_2005 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ UAE_2005 = {
F_16C_50,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ UnitedKingdom_1990 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -8,17 +8,25 @@ USA_1944 = {
"units": [
P_51D,
P_51D_30_NA,
P_47D_30,
SpitfireLFMkIX,
SpitfireLFMkIXCW,
A_20G,
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.CT_Cromwell_IV,
Armor.M30_Cargo_Carrier,
Armor.APC_M2A1,
Armor.CT_Cromwell_IV,
Armor.ST_Centaur_IV,
Armor.HIT_Churchill_VII,
Armor.LAC_M8_Greyhound,
Armor.TD_M10_GMC,
Artillery.M12_GMC,
Infantry.Infantry_M1_Garand,
Infantry.Infantry_SMLE_No_4_Mk_1,
LS_Samuel_Chase,
LST_Mk_II,
@@ -26,5 +34,7 @@ USA_1944 = {
Unarmed.CCKW_353,
AirDefence.AAA_Bofors_40mm,
], "shorad":[
AirDefence.AAA_Bofors_40mm,
]
}

View File

@@ -11,7 +11,7 @@ USA_1955 = {
P_51D,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -11,7 +11,7 @@ USA_1960 = {
P_51D,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -12,7 +12,7 @@ USA_1965 = {
F_4E,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,

View File

@@ -17,7 +17,7 @@ USA_1990 = {
B_1B,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,
@@ -46,6 +46,7 @@ USA_1990 = {
LHA_1_Tarawa,
], "destroyer": [
Oliver_Hazzard_Perry_class,
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [

View File

@@ -12,12 +12,11 @@ USA_2005 = {
FA_18C_hornet,
F_16C_50,
JF_17,
A_10C,
AV8BNA,
KC_135,
S_3B_Tanker,
KC130,
C_130,
E_3A,
@@ -53,11 +52,13 @@ USA_2005 = {
LHA_1_Tarawa,
], "destroyer": [
Oliver_Hazzard_Perry_class,
USS_Arleigh_Burke_IIa,
], "cruiser": [
Ticonderoga_class,
], "carrier_names": [
"CVN-71 Theodore Roosevelt",
"CVN-72 Abraham Lincoln",
"CVN-73 Georges Washington",
"CVN-73 George Washington",
"CVN-74 John C. Stennis",
], "lhanames": [
"LHA-1 Tarawa",

View File

@@ -1,24 +1,11 @@
import logging
import typing
import random
import math
from dcs.task import *
from dcs.vehicles import *
from datetime import datetime, timedelta
from game.db import REWARDS, PLAYER_BUDGET_BASE
from game.game_stats import GameStats
from game.infos.information import Information
from gen.conflictgen import Conflict
from gen.flights.ai_flight_planner import FlightPlanner
from gen.ground_forces.ai_ground_planner import GroundPlanner
from userdata.debriefing import Debriefing
from theater import *
from . import db
from .settings import Settings
from .event import *
from datetime import datetime, timedelta
from .settings import Settings
COMMISION_UNIT_VARIETY = 4
COMMISION_LIMITS_SCALE = 1.5
@@ -158,11 +145,8 @@ class Game:
def initiate_event(self, event: Event):
assert event in self.events
logging.info("Generating {} (regular)".format(event))
event.generate()
logging.info("Generating {} (quick)".format(event))
event.generate_quick()
def finish_event(self, event: Event, debriefing: Debriefing):
logging.info("Finishing event {}".format(event))
@@ -181,6 +165,18 @@ class Game:
else:
return event.name == self.player_name
def get_player_coalition_id(self):
if self.player_country in db.BLUEFOR_FACTIONS:
return 2
else:
return 1
def get_enemy_coalition_id(self):
if self.get_player_coalition_id() == 1:
return 2
else:
return 1
def pass_turn(self, no_action=False, ignored_cps: typing.Collection[ControlPoint] = None):
logging.info("Pass turn")
self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0))
@@ -230,6 +226,10 @@ class Game:
self.ground_planners[cp.id] = gplanner
def _enemy_reinforcement(self):
"""
Compute and commision reinforcement for enemy bases
"""
MAX_ARMOR = 30 * self.settings.multiplier
MAX_AIRCRAFT = 25 * self.settings.multiplier

View File

@@ -47,11 +47,9 @@ class GameStats:
for cp in game.theater.controlpoints:
if cp.captured:
turn_data.allied_units.aircraft_count += sum(cp.base.aircraft.values())
turn_data.allied_units.sam_count += sum(cp.base.aa.values())
turn_data.allied_units.vehicles_count += sum(cp.base.armor.values())
else:
turn_data.enemy_units.aircraft_count += sum(cp.base.aircraft.values())
turn_data.enemy_units.sam_count += sum(cp.base.aa.values())
turn_data.enemy_units.vehicles_count += sum(cp.base.armor.values())
self.data_per_turn.append(turn_data)

View File

@@ -64,7 +64,7 @@ class Operation:
def initialize(self, mission: Mission, conflict: Conflict):
self.current_mission = mission
self.conflict = conflict
self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings)
self.airgen = AircraftConflictGenerator(mission, conflict, self.game.settings, self.game)
self.shipgen = ShipGenerator(mission, conflict)
self.airsupportgen = AirSupportConflictGenerator(mission, conflict, self.game)
self.triggersgen = TriggersGenerator(mission, conflict, self.game)
@@ -172,7 +172,8 @@ class Operation:
self.forcedoptionsgen.generate()
# Generate Visuals Smoke Effects
self.visualgen.generate()
if self.game.settings.perf_smoke_gen:
self.visualgen.generate()
# Inject Lua Scripts
load_mist = TriggerStart(comment="Load Mist Lua Framework")
@@ -198,8 +199,6 @@ class Operation:
self.briefinggen.append_frequency("AWACS", "133 MHz AM")
self.briefinggen.append_frequency("Flight", "251 MHz AM")
if self.departure_cp.is_global or self.conflict.to_cp.is_global:
self.briefinggen.append_frequency("Carrier", "20X/ICLS CHAN1")
# Generate the briefing
self.briefinggen.generate()

View File

@@ -1,14 +1,26 @@
class Settings:
# Difficulty settings
player_skill = "Good"
enemy_skill = "Average"
enemy_vehicle_skill = "Average"
map_coalition_visibility = "All Units"
labels = "Full"
only_player_takeoff = True
only_player_takeoff = True # Legacy parameter do not use
night_disabled = False
supercarrier = False
multiplier = 1
sams = True
cold_start = False
sams = True # Legacy parameter do not use
cold_start = False # Legacy parameter do not use
version = None
# Performance oriented
perf_red_alert_state = True
perf_smoke_gen = True
perf_artillery = True
perf_moving_units = True
perf_infantry = True
perf_ai_parking_start = True

View File

@@ -1,21 +1,16 @@
import logging
from dcs.action import ActivateGroup, AITaskPush
from dcs.condition import TimeAfter, CoalitionHasAirdrome
from dcs.helicopters import UH_1H
from dcs.terrain.terrain import NoParkingSlotError
from dcs.triggers import TriggerOnce, Event
from game import db
from game.settings import Settings
from gen.flights.ai_flight_planner import FlightPlanner
from gen.flights.flight import Flight, FlightType
from gen.flights.ai_flight_planner import FlightPlanner, CAP_DEFAULT_ENGAGE_DISTANCE, nm_to_meter
from gen.flights.flight import Flight, FlightType, FlightWaypointType
from .conflictgen import *
from .naming import *
from .triggergen import TRIGGER_WAYPOINT_OFFSET
from dcs.mission import *
from dcs.unitgroup import *
from dcs.unittype import *
from dcs.task import *
from dcs.terrain.terrain import NoParkingSlotError, RunwayOccupiedError
SPREAD_DISTANCE_FACTOR = 1, 2
ESCORT_ENGAGEMENT_MAX_DIST = 100000
WORKAROUND_WAYP_DIST = 1000
@@ -46,8 +41,9 @@ INTERCEPT_MAX_DISTANCE = 200000
class AircraftConflictGenerator:
escort_targets = [] # type: typing.List[typing.Tuple[FlyingGroup, int]]
def __init__(self, mission: Mission, conflict: Conflict, settings: Settings):
def __init__(self, mission: Mission, conflict: Conflict, settings: Settings, game):
self.m = mission
self.game = game
self.settings = settings
self.conflict = conflict
self.escort_targets = []
@@ -55,33 +51,6 @@ class AircraftConflictGenerator:
def _start_type(self) -> StartType:
return self.settings.cold_start and StartType.Cold or StartType.Warm
def _group_point(self, point) -> Point:
distance = randint(
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[0]),
int(self.conflict.size * SPREAD_DISTANCE_FACTOR[1]),
)
return point.random_point_within(distance, self.conflict.size * SPREAD_DISTANCE_FACTOR[0])
def _split_to_groups(self, dict: db.PlaneDict, clients: db.PlaneDict = None) -> typing.Collection[typing.Tuple[FlyingType, int, int]]:
for flying_type, count in dict.items():
if clients:
client_count = clients.get(flying_type, 0)
else:
client_count = 0
if flying_type == F_14B:
# workaround since 2 and 3 tomcat collide on carrier
group_size = 2
else:
group_size = 4
while count > 0:
group_size = min(count, group_size)
client_size = max(min(client_count, group_size), 0)
yield (flying_type, group_size, client_size)
count -= group_size
client_count -= client_size
def _setup_group(self, group: FlyingGroup, for_task: typing.Type[Task], client_count: int):
did_load_loadout = False
@@ -255,37 +224,6 @@ class AircraftConflictGenerator:
else:
assert False
def _generate_escort(self, side: Country, units: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition, cp, is_quick=False, should_orbit=False):
groups = []
for flying_type, count, client_count in self._split_to_groups(units, clients):
group = self._generate_group(
name=namegen.next_unit_name(side, cp.id, flying_type),
side=side,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at)
group.task = Escort.name
self._setup_group(group, CAP, client_count)
for escorted_group, waypoint_index in self.escort_targets:
waypoint_index += 1
if not is_quick:
waypoint_index += TRIGGER_WAYPOINT_OFFSET
group.points[0].tasks.append(EscortTaskAction(escorted_group.id, engagement_max_dist=ESCORT_ENGAGEMENT_MAX_DIST, lastwpt=waypoint_index))
if should_orbit:
orbit_task = ControlledTask(OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle))
orbit_task.stop_after_duration(ATTACK_CIRCLE_DURATION * 60)
orbit_waypoint = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE)
orbit_waypoint.tasks.append(orbit_task)
orbit_waypoint.tasks.append(EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE))
groups.append(group)
return groups
def _setup_custom_payload(self, flight, group:FlyingGroup):
if flight.use_custom_loadout:
@@ -307,47 +245,56 @@ class AircraftConflictGenerator:
def generate_flights(self, cp, country, flight_planner:FlightPlanner):
for flight in flight_planner.interceptor_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_intercept_flight(group, flight)
self._setup_custom_payload(flight, group)
for flight in flight_planner.cap_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_cap_flight(group, flight)
self._setup_custom_payload(flight, group)
for flight in flight_planner.cas_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_cas_flight(group, flight)
self._setup_custom_payload(flight, group)
for flight in flight_planner.sead_flights:
group = self.generate_planned_flight(cp, country, flight)
self.setup_group_as_sead_flight(group, flight)
self._setup_custom_payload(flight, group)
for flight in flight_planner.custom_flights:
for flight in flight_planner.flights:
group = self.generate_planned_flight(cp, country, flight)
if flight.flight_type == FlightType.INTERCEPTION:
self.setup_group_as_intercept_flight(group, flight)
elif flight.flight_type in [FlightType.CAP, FlightType.TARCAP, FlightType.BARCAP]:
self.setup_group_as_cap_flight(group, flight)
elif flight.flight_type in [FlightType.CAS, FlightType.BAI]:
self.setup_group_as_cas_flight(group, flight)
elif flight.flight_type in [FlightType.STRIKE]:
self.setup_group_as_strike_flight(group, flight)
elif flight.flight_type in [FlightType.ANTISHIP]:
self.setup_group_as_antiship_flight(group, flight)
elif flight.flight_type in [FlightType.SEAD, FlightType.DEAD]:
self.setup_group_as_sead_flight(group, flight)
self._setup_custom_payload(flight, group)
else:
self.setup_group_as_cap_flight(group, flight)
self._setup_custom_payload(flight, group)
self.setup_flight_group(group, flight, flight.flight_type)
self.setup_group_activation_trigger(flight, group)
def setup_group_activation_trigger(self, flight, group):
if flight.scheduled_in > 0 and flight.client_count == 0:
if flight.start_type != "In Flight":
group.late_activation = False
group.uncontrolled = True
activation_trigger = TriggerOnce(Event.NoEvent, "LiberationControlTriggerForGroup" + str(group.id))
activation_trigger.add_condition(TimeAfter(seconds=flight.scheduled_in * 60))
if (flight.from_cp.cptype == ControlPointType.AIRBASE):
if flight.from_cp.captured:
activation_trigger.add_condition(
CoalitionHasAirdrome(self.game.get_player_coalition_id(), flight.from_cp.id))
else:
activation_trigger.add_condition(
CoalitionHasAirdrome(self.game.get_enemy_coalition_id(), flight.from_cp.id))
group.add_trigger_action(StartCommand())
activation_trigger.add_action(AITaskPush(group.id, len(group.tasks)))
self.m.triggerrules.triggers.append(activation_trigger)
else:
group.late_activation = True
activation_trigger = TriggerOnce(Event.NoEvent, "LiberationActivationTriggerForGroup" + str(group.id))
activation_trigger.add_condition(TimeAfter(seconds=flight.scheduled_in*60))
if(flight.from_cp.cptype == ControlPointType.AIRBASE):
if flight.from_cp.captured:
activation_trigger.add_condition(CoalitionHasAirdrome(self.game.get_player_coalition_id(), flight.from_cp.id))
else:
activation_trigger.add_condition(CoalitionHasAirdrome(self.game.get_enemy_coalition_id(), flight.from_cp.id))
activation_trigger.add_action(ActivateGroup(group.id))
self.m.triggerrules.triggers.append(activation_trigger)
def generate_planned_flight(self, cp, country, flight:Flight):
try:
if flight.start_type == "In Flight" or flight.client_count == 0:
if flight.client_count == 0 and self.game.settings.perf_ai_parking_start:
flight.start_type = "Warm"
if flight.start_type == "In Flight":
group = self._generate_group(
name=namegen.next_unit_name(country, cp.id, flight.unit_type),
side=country,
@@ -356,7 +303,6 @@ class AircraftConflictGenerator:
client_count=0,
at=cp.position)
else:
st = StartType.Runway
if flight.start_type == "Cold":
st = StartType.Cold
@@ -383,6 +329,7 @@ class AircraftConflictGenerator:
start_type=st)
except Exception:
# Generated when there is no place on Runway or on Parking Slots
flight.start_type = "In Flight"
group = self._generate_group(
name=namegen.next_unit_name(country, cp.id, flight.unit_type),
side=country,
@@ -391,7 +338,6 @@ class AircraftConflictGenerator:
client_count=0,
at=cp.position)
group.points[0].alt = 1500
group.points[0].ETA = flight.scheduled_in * 60
return group
@@ -403,53 +349,75 @@ class AircraftConflictGenerator:
group.add_waypoint(Point(point.x,point.y), point.alt)
def setup_group_as_cap_flight(self, group, flight):
self._setup_group(group, CAP, flight.client_count)
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
def setup_flight_group(self, group, flight, flight_type):
def setup_group_as_cas_flight(self, group, flight):
group.task = CAS.name
self._setup_group(group, CAS, flight.client_count)
if flight_type in [FlightType.CAP, FlightType.BARCAP, FlightType.TARCAP]:
group.task = CAP.name
self._setup_group(group, CAP, flight.client_count)
# group.points[0].tasks.clear()
# group.tasks.clear()
# group.tasks.append(EngageTargets(max_distance=40, targets=[Targets.All.Air]))
# group.tasks.append(EngageTargets(max_distance=nm_to_meter(120), targets=[Targets.All.Air]))
pass
elif flight_type in [FlightType.CAS, FlightType.BAI]:
group.task = CAS.name
self._setup_group(group, CAS, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(CASTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
elif flight_type in [FlightType.SEAD, FlightType.DEAD]:
group.task = SEAD.name
self._setup_group(group, SEAD, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(SEADTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
group.points[0].tasks.append(OptRestrictJettison(True))
elif flight_type in [FlightType.STRIKE]:
group.task = PinpointStrike.name
self._setup_group(group, GroundAttack, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
group.points[0].tasks.append(OptRestrictJettison(True))
elif flight_type in [FlightType.ANTISHIP]:
group.task = AntishipStrike.name
self._setup_group(group, AntishipStrike, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFire))
group.points[0].tasks.append(OptRestrictJettison(True))
group.points[0].tasks.clear()
group.points[0].tasks.append(CASTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
group.points[0].tasks.append(OptRestrictJettison(True))
for i, point in enumerate(flight.points):
if not point.only_for_player or (point.only_for_player and flight.client_count > 0):
pt = group.add_waypoint(Point(point.x, point.y), point.alt)
if point.waypoint_type == FlightWaypointType.PATROL_TRACK:
action = OrbitAction(altitude=pt.alt, pattern=OrbitAction.OrbitPattern.RaceTrack)
pt.tasks.append(action)
#for tgt in point.targets:
# if hasattr(tgt, "position"):
# engagetgt = EngageTargetsInZone(tgt.position, radius=CAP_DEFAULT_ENGAGE_DISTANCE, targets=[Targets.All.Air])
# pt.tasks.append(engagetgt)
elif point.waypoint_type == FlightWaypointType.LANDING_POINT:
pt.type = "Land"
elif point.waypoint_type == FlightWaypointType.INGRESS_STRIKE:
print("TGTS :")
print(point.targets)
for j, t in enumerate(point.targets):
print(t.position)
pt.tasks.append(Bombing(t.position))
if group.units[0].unit_type == JF_17 and j < 4:
group.add_nav_target_point(t.position, "PP" + str(j + 1))
if group.units[0].unit_type == F_14B and j == 0:
group.add_nav_target_point(t.position, "ST")
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
if pt is not None:
pt.alt_type = point.alt_type
pt.name = String(point.name)
def setup_group_as_sead_flight(self, group, flight):
group.task = SEAD.name
self._setup_group(group, SEAD, flight.client_count)
self._setup_custom_payload(flight, group)
group.points[0].tasks.clear()
group.points[0].tasks.append(SEADTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
group.points[0].tasks.append(OptRestrictJettison(True))
i = 1
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
group.points[i].tasks.clear()
group.points[i].tasks.append(SEADTaskAction())
i = i + 1
def setup_group_as_strike_flight(self, group, flight):
group.task = PinpointStrike.name
self._setup_group(group, GroundAttack, flight.client_count)
group.points[0].tasks.clear()
group.points[0].tasks.append(CASTaskAction())
group.points[0].tasks.append(OptReactOnThreat(OptReactOnThreat.Values.EvadeFire))
group.points[0].tasks.append(OptROE(OptROE.Values.OpenFireWeaponFree))
group.points[0].tasks.append(OptRestrictJettison(True))
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
def setup_group_as_antiship_flight(self, group, flight):
group.task = AntishipStrike.name
@@ -464,263 +432,4 @@ class AircraftConflictGenerator:
for point in flight.points:
group.add_waypoint(Point(point.x,point.y), point.alt)
def generate_cas_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(attackers, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, flying_type),
side=self.conflict.attackers_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
waypoint = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
self._add_radio_waypoint(group, self.conflict.tail, CAS_ALTITUDE, WARM_START_AIRSPEED)
group.task = CAS.name
self._setup_group(group, CAS, client_count)
if escort:
self.escort_targets.append((group, group.points.index(waypoint)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_ground_attack_strikegroup(self, strikegroup: db.PlaneDict, clients: db.PlaneDict, targets: typing.List[typing.Tuple[str, Point]], at: db.StartingPosition = None, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(strikegroup, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, flying_type),
side=self.conflict.attackers_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
escort_until_waypoint = None
for name, pos in targets:
waypoint = group.add_waypoint(pos, 0, WARM_START_AIRSPEED, self.m.translation.create_string(name))
waypoint.tasks.append(Bombing(pos, attack_qty=2))
if escort_until_waypoint is None:
escort_until_waypoint = waypoint
group.task = GroundAttack.name
self._setup_group(group, GroundAttack, client_count)
if escort:
self.escort_targets.append((group, group.points.index(escort_until_waypoint)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_sead_strikegroup(self, strikegroup: db.PlaneDict, clients: db.PlaneDict, targets: typing.List[typing.Tuple[str, Point]], at: db.StartingPosition, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(strikegroup, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, flying_type),
side=self.conflict.attackers_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
escort_until_waypoint = None
for name, pos in targets:
waypoint = group.add_waypoint(pos, 0, WARM_START_AIRSPEED, self.m.translation.create_string(name))
if escort_until_waypoint is None:
escort_until_waypoint = waypoint
group.task = SEAD.name
self._setup_group(group, SEAD, client_count)
if escort:
self.escort_targets.append((group, group.points.index(escort_until_waypoint)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_defenders_cas(self, defenders: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(defenders, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.defenders_country, self.conflict.to_cp.id, flying_type),
side=self.conflict.defenders_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_defenders_location))
location = self._group_point(self.conflict.air_defenders_location)
insertion_point = self.conflict.find_insertion_point(location)
waypoint = self._add_radio_waypoint(group, insertion_point, CAS_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
destination_tail = self.conflict.tail.distance_to_point(insertion_point) > self.conflict.position.distance_to_point(insertion_point)
self._add_radio_waypoint(group, destination_tail and self.conflict.tail or self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
group.task = CAS.name
self._setup_group(group, CAS, client_count)
if escort:
self.escort_targets.append((group, group.points.index(waypoint)))
self._rtb_for(group, self.conflict.to_cp, at)
def generate_ship_strikegroup(self, attackers: db.PlaneDict, clients: db.PlaneDict, target_groups: typing.Collection[ShipGroup], at: db.StartingPosition = None, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(attackers, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, flying_type),
side=self.conflict.attackers_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
wayp = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
for target_group in target_groups:
wayp.tasks.append(AttackGroup(target_group.id))
group.task = AntishipStrike.name
self._setup_group(group, AntishipStrike, client_count)
if escort:
self.escort_targets.append((group, group.points.index(wayp)))
self._rtb_for(group, self.conflict.from_cp, at)
def generate_attackers_escort(self, attackers: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for g in self._generate_escort(
side=self.conflict.attackers_country,
units=attackers,
clients=clients,
at=at and at or self._group_point(self.conflict.air_attackers_location),
is_quick=at is None,
cp=self.conflict.from_cp,
should_orbit=True):
self._rtb_for(g, self.conflict.from_cp, at)
def generate_defenders_escort(self, escort: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for g in self._generate_escort(
side=self.conflict.defenders_country,
units=escort,
clients=clients,
at=at and at or self._group_point(self.conflict.air_defenders_location),
is_quick=at is None,
cp=self.conflict.to_cp,
should_orbit=False):
self._rtb_for(g, self.conflict.to_cp, at)
def generate_defense(self, defenders: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for flying_type, count, client_count in self._split_to_groups(defenders, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.to_cp.id, flying_type),
side=self.conflict.defenders_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_defenders_location))
group.task = CAP.name
wayp = self._add_radio_waypoint(group, self.conflict.position, CAS_ALTITUDE, WARM_START_AIRSPEED)
wayp.tasks.append(dcs.task.EngageTargets(max_distance=DEFENCE_ENGAGEMENT_MAX_DISTANCE))
wayp.tasks.append(dcs.task.OrbitAction(ATTACK_CIRCLE_ALT, pattern=OrbitAction.OrbitPattern.Circle))
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.to_cp, at)
def generate_migcap(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, flying_type),
side=self.conflict.attackers_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
waypoint = self._add_radio_waypoint(group, self.conflict.position, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
self._add_radio_waypoint(group, self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
group.task = CAP.name
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.from_cp, at)
def generate_barcap(self, patrol: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for flying_type, count, client_count in self._split_to_groups(patrol, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, flying_type),
side=self.conflict.defenders_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_defenders_location))
waypoint = self._add_radio_waypoint(group, self.conflict.position, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
if self.conflict.is_vector:
self._add_radio_waypoint(group, self.conflict.tail, WARM_START_ALTITUDE, WARM_START_AIRSPEED)
else:
heading = group.position.heading_between_point(self.conflict.position)
waypoint = self._add_radio_waypoint(group, self.conflict.position.point_from_heading(heading, BARCAP_RACETRACK_DISTANCE),
WARM_START_ALTITUDE,
WARM_START_AIRSPEED)
waypoint.tasks.append(OrbitAction(WARM_START_ALTITUDE, WARM_START_AIRSPEED))
group.task = CAP.name
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.to_cp, at)
def generate_transport(self, transport: db.PlaneDict, destination: Airport, escort=True):
assert not escort or len(self.escort_targets) == 0
for flying_type, count, client_count in self._split_to_groups(transport):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, flying_type),
side=self.conflict.defenders_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=self._group_point(self.conflict.air_defenders_location))
waypoint = self._rtb_for(group, self.conflict.to_cp)
if escort:
self.escort_targets.append((group, group.points.index(waypoint)))
self._add_radio_waypoint(group, destination.position, RTB_ALTITUDE)
group.task = Transport.name
group.land_at(destination)
def generate_interception(self, interceptors: db.PlaneDict, clients: db.PlaneDict, at: db.StartingPosition = None):
for flying_type, count, client_count in self._split_to_groups(interceptors, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, flying_type),
side=self.conflict.attackers_country,
unit_type=flying_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location))
group.task = CAP.name
group.points[0].tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
wayp = self._add_radio_waypoint(group, self.conflict.position, WARM_START_ALTITUDE, INTERCEPTION_AIRSPEED)
wayp.tasks.append(EngageTargets(max_distance=INTERCEPT_MAX_DISTANCE))
if self.conflict.is_vector:
self._add_radio_waypoint(group, self.conflict.tail, CAS_ALTITUDE, WARM_START_ALTITUDE)
self._setup_group(group, CAP, client_count)
self._rtb_for(group, self.conflict.from_cp, at)
def generate_passenger_transport(self, helis: db.HeliDict, clients: db.HeliDict, at: db.StartingPosition):
for heli_type, count, client_count in self._split_to_groups(helis, clients):
group = self._generate_group(
name=namegen.next_unit_name(self.conflict.attackers_country, self.conflict.from_cp.id, heli_type),
side=self.conflict.attackers_country,
unit_type=heli_type,
count=count,
client_count=client_count,
at=at and at or self._group_point(self.conflict.air_attackers_location)
)
self._add_radio_waypoint(group, self.conflict.position, HELI_ALT)
self._setup_group(group, Transport, client_count)

View File

@@ -53,17 +53,20 @@ class AirSupportConflictGenerator:
tanker_group.points[0].tasks.append(SetImmortalCommand(True))
if is_awacs_enabled:
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side)[0]
awacs_flight = self.mission.awacs_flight(
country=self.mission.country(self.game.player_country),
name=namegen.next_awacs_name(self.mission.country(self.game.player_country)),
plane_type=awacs_unit,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
frequency=133,
start_type=StartType.Warm,
)
try:
awacs_unit = db.find_unittype(AWACS, self.conflict.attackers_side)[0]
awacs_flight = self.mission.awacs_flight(
country=self.mission.country(self.game.player_country),
name=namegen.next_awacs_name(self.mission.country(self.game.player_country)),
plane_type=awacs_unit,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.position.random_point_within(AWACS_DISTANCE, AWACS_DISTANCE),
frequency=133,
start_type=StartType.Warm,
)
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))
except:
print("No AWACS for faction")
awacs_flight.points[0].tasks.append(SetInvisibleCommand(True))
awacs_flight.points[0].tasks.append(SetImmortalCommand(True))

View File

@@ -96,6 +96,11 @@ class GroundConflictGenerator:
def gen_infantry_group_for_group(self, group, is_player, side:Country, forward_heading):
# Disable infantry unit gen if disabled
if not self.game.settings.perf_infantry:
return
infantry_position = group.points[0].position.random_point_within(250, 50)
if side == self.conflict.attackers_country:
@@ -135,12 +140,16 @@ class GroundConflictGenerator:
def plan_action_for_groups(self, stance, ally_groups, enemy_groups, forward_heading, from_cp, to_cp):
if not self.game.settings.perf_moving_units:
return
for dcs_group, group in ally_groups:
if group.role == CombatGroupRole.ARTILLERY:
# Fire on any ennemy in range
target = self.get_artillery_target_in_range(dcs_group, group, enemy_groups)
if target is not None:
dcs_group.points[0].tasks.append(FireAtPoint(target, len(group.units) * 10, 100))
if self.game.settings.perf_artillery:
target = self.get_artillery_target_in_range(dcs_group, group, enemy_groups)
if target is not None:
dcs_group.points[0].tasks.append(FireAtPoint(target, len(group.units) * 10, 100))
elif group.role in [CombatGroupRole.TANK, CombatGroupRole.IFV]:
if stance == CombatStance.AGGRESIVE:
# Attack nearest enemy if any

View File

@@ -57,11 +57,7 @@ class BriefingGenerator:
self.description += "-"*50 + "\n"
for planner in self.game.planners.values():
for flight in planner.cap_flights:
self.add_flight_description(flight)
for flight in planner.cas_flights:
self.add_flight_description(flight)
for flight in planner.sead_flights:
for flight in planner.flights:
self.add_flight_description(flight)
if self.freqs:
@@ -69,10 +65,9 @@ class BriefingGenerator:
self.description += "-" * 50 + "\n"
for name, freq in self.freqs:
self.description += "\n{}: {}".format(name, freq)
self.description += "\n" + ("-" * 50) + "\n"
for cp in self.game.theater.controlpoints:
if cp.captured and cp.cptype in [ControlPointType.LHA_GROUP, ControlPointType.AIRCRAFT_CARRIER_GROUP]:
self.description += "\n"
self.description += cp.name + " TACAN : "
self.description += str(cp.tacanN)
@@ -82,6 +77,9 @@ class BriefingGenerator:
self.description += "X"
self.description += " " + str(cp.tacanI) + "\n"
if cp.cptype == ControlPointType.AIRCRAFT_CARRIER_GROUP and hasattr(cp, "icls"):
self.description += "ICLS Channel : " + str(cp.icls) + "\n"
self.description += "-" * 50 + "\n"
self.m.set_description_text(self.description)

View File

@@ -172,13 +172,6 @@ class Conflict:
position = middle_point.point_from_heading(attack_heading, strength_delta * attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE)
return position, _opposite_heading(attack_heading)
ground_position = cls._find_ground_position(position, attack_distance / 2 - FRONTLINE_MIN_CP_DISTANCE, attack_heading, theater)
if ground_position:
return ground_position, _opposite_heading(attack_heading)
else:
logging.warning("Coudn't find frontline position between {} and {}!".format(from_cp, to_cp))
return position, _opposite_heading(attack_heading)
@classmethod
def frontline_vector(cls, from_cp: ControlPoint, to_cp: ControlPoint, theater: ConflictTheater) -> typing.Optional[typing.Tuple[Point, int, int]]:

View File

@@ -61,12 +61,14 @@ class EnviromentGenerator:
logging.info("Skip Night mission due to user settings")
if daytime == "dawn":
time_range = (8, 9)
elif daytime == "noon":
elif daytime == "day":
time_range = (10, 12)
elif daytime == "dusk":
time_range = (12, 14)
elif daytime == "night":
time_range = (14, 17)
else:
time_range = (10, 12)
else:
time_range = self.game.theater.daytime_map[daytime]

View File

@@ -5,8 +5,25 @@ import random
from game import db
from gen import Conflict
from gen.flights.ai_flight_planner_db import INTERCEPT_CAPABLE, CAP_CAPABLE, CAS_CAPABLE, SEAD_CAPABLE
from gen.flights.flight import Flight, FlightType, FlightWaypoint
from gen.flights.ai_flight_planner_db import INTERCEPT_CAPABLE, CAP_CAPABLE, CAS_CAPABLE, SEAD_CAPABLE, STRIKE_CAPABLE
from gen.flights.flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
def meter_to_feet(value_in_meter):
return int(3.28084 * value_in_meter)
def feet_to_meter(value_in_feet):
return int(float(value_in_feet)/3.048)
def meter_to_nm(value_in_meter):
return int(float(value_in_meter)*0.000539957)
def nm_to_meter(value_in_nm):
return int(float(value_in_nm)*1852)
# TODO : Ideally should be based on the aircraft type instead / Availability of fuel
STRIKE_MAX_RANGE = 1500000
@@ -17,6 +34,16 @@ MISSION_DURATION = 120 # in minutes
CAP_EVERY_X_MINUTES = 20
CAS_EVERY_X_MINUTES = 30
SEAD_EVERY_X_MINUTES = 40
STRIKE_EVERY_X_MINUTES = 40
INGRESS_EGRESS_DISTANCE = nm_to_meter(45)
INGRESS_ALT = feet_to_meter(20000)
EGRESS_ALT = feet_to_meter(20000)
PATROL_ALT_RANGE = (feet_to_meter(15000), feet_to_meter(33000))
NAV_ALT = 9144
PATTERN_ALTITUDE = feet_to_meter(5000)
CAP_DEFAULT_ENGAGE_DISTANCE = nm_to_meter(40)
class FlightPlanner:
@@ -53,7 +80,7 @@ class FlightPlanner:
#self.commision_interceptors()
# Then some CAP patrol for the next 2 hours
self.commision_barcap()
self.commision_cap()
# Then setup cas
self.commision_cas()
@@ -61,7 +88,9 @@ class FlightPlanner:
# Then prepare some sead flights if required
self.commision_sead()
# TODO : commision STRIKE / ANTISHIP
self.commision_strike()
# TODO : commision ANTISHIP
def remove_flight(self, index):
try:
@@ -107,7 +136,7 @@ class FlightPlanner:
for k, v in inventory.items():
self.aircraft_inventory[k] = v
def commision_barcap(self):
def commision_cap(self):
"""
Pick some aircraft to assign them to defensive CAP roles (BARCAP)
"""
@@ -124,32 +153,67 @@ class FlightPlanner:
break
inventory[unit] = inventory[unit] - 2
flight = Flight(unit, 2, self.from_cp, FlightType.BARCAP)
ftype = FlightType.BARCAP if self.from_cp.is_carrier else FlightType.CAP
flight = Flight(unit, 2, self.from_cp, ftype)
# Flight path : fly over each ground object (TODO : improve)
flight.points = []
flight.scheduled_in = offset + i*random.randint(CAP_EVERY_X_MINUTES-5, CAP_EVERY_X_MINUTES+5)
patrol_alt = random.randint(3600, 7000)
patrol_alt = random.randint(PATROL_ALT_RANGE[0], PATROL_ALT_RANGE[1])
patrolled = []
# Choose a location for CAP patrols (Either behind frontline if there is one, or to protect ground objects)
if len(self._get_cas_locations()) > 0:
loc = random.choice(self._get_cas_locations())
ingress, heading, distance = Conflict.frontline_vector(self.from_cp, loc, self.game.theater)
center = ingress.point_from_heading(heading, distance / 2)
orbit_center = center.point_from_heading(heading - 90, random.randint(nm_to_meter(6), nm_to_meter(15)))
radius = distance * 2
orbit0p = orbit_center.point_from_heading(heading, radius)
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
elif len(self.from_cp.ground_objects) > 0:
loc = random.choice(self.from_cp.ground_objects)
hdg = self.from_cp.position.heading_between_point(loc.position)
radius = random.randint(nm_to_meter(5), nm_to_meter(10))
orbit0p = loc.position.point_from_heading(hdg - 90, radius)
orbit1p = loc.position.point_from_heading(hdg + 90, radius)
else:
loc = self.from_cp.position.point_from_heading(random.randint(0, 360), random.randint(nm_to_meter(5), nm_to_meter(40)))
hdg = self.from_cp.position.heading_between_point(loc.position)
radius = random.randint(nm_to_meter(40), nm_to_meter(120))
orbit0p = loc.position.point_from_heading(hdg - 90, radius)
orbit1p = loc.position.point_from_heading(hdg + 90, radius)
# Create points
ascend = self.generate_ascend_point(self.from_cp)
flight.points.append(ascend)
orbit0 = FlightWaypoint(orbit0p.x, orbit0p.y, patrol_alt)
orbit0.name = "ORBIT 0"
orbit0.description = "Standby between this point and the next one"
orbit0.pretty_name = "Orbit race-track start"
orbit0.waypoint_type = FlightWaypointType.PATROL_TRACK
flight.points.append(orbit0)
orbit1 = FlightWaypoint(orbit1p.x, orbit1p.y, patrol_alt)
orbit1.name = "ORBIT 1"
orbit1.description = "Standby between this point and the previous one"
orbit1.pretty_name = "Orbit race-track end"
orbit1.waypoint_type = FlightWaypointType.PATROL
flight.points.append(orbit1)
orbit0.targets.append(self.from_cp)
obj_added = []
for ground_object in self.from_cp.ground_objects:
if ground_object.group_id not in patrolled and not ground_object.airbase_group:
point = FlightWaypoint(ground_object.position.x, ground_object.position.y, patrol_alt)
point.name = "Patrol point"
point.description = "Patrol #" + str(len(flight.points))
point.pretty_name = "Patrol #" + str(len(flight.points))
flight.points.append(point)
patrolled.append(ground_object.group_id)
if ground_object.obj_name not in obj_added and not ground_object.airbase_group:
orbit0.targets.append(ground_object)
obj_added.append(ground_object.obj_name)
if len(flight.points) == 0:
for i in range(3):
pos = self.from_cp.position.point_from_heading(random.randint(0,360), random.randint(30000, 80000))
point = FlightWaypoint(pos.x, pos.y, patrol_alt)
point.name = "Patrol point"
point.description = "Patrol #" + str(len(flight.points))
point.pretty_name = "Patrol #" + str(len(flight.points))
flight.points.append(point)
descend = self.generate_descend_point(self.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(self.from_cp)
flight.points.append(rtb)
self.cap_flights.append(flight)
self.flights.append(flight)
@@ -188,26 +252,39 @@ class FlightPlanner:
center = ingress.point_from_heading(heading, distance/2)
egress = ingress.point_from_heading(heading, distance)
flight.targets.append(center)
ascend = self.generate_ascend_point(self.from_cp)
flight.points.append(ascend)
ingress_point = FlightWaypoint(ingress.x, ingress.y, 1000)
ingress_point.alt_type = "RADIO"
ingress_point.name = "INGRESS"
ingress_point.pretty_name = "INGRESS"
ingress_point.description = "Ingress into CAS area"
ingress_point.waypoint_type = FlightWaypointType.INGRESS_CAS
flight.points.append(ingress_point)
center_point = FlightWaypoint(center.x, center.y, 1000)
center_point.alt_type = "RADIO"
center_point.description = "Provide CAS"
center_point.name = "CAS"
center_point.pretty_name = "INGRESS"
center_point.pretty_name = "CAS"
center_point.waypoint_type = FlightWaypointType.CAS
flight.points.append(center_point)
egress_point = FlightWaypoint(egress.x, egress.y, 1000)
egress_point.alt_type = "RADIO"
egress_point.description = "Egress from CAS area"
egress_point.name = "EGRESS"
egress_point.pretty_name = "EGRESS"
egress_point.waypoint_type = FlightWaypointType.EGRESS
flight.points.append(egress_point)
descend = self.generate_descend_point(self.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(self.from_cp)
flight.points.append(rtb)
self.cas_flights.append(flight)
self.flights.append(flight)
@@ -242,15 +319,50 @@ class FlightPlanner:
flight.points = []
flight.scheduled_in = offset + i*random.randint(SEAD_EVERY_X_MINUTES-5, SEAD_EVERY_X_MINUTES+5)
ascend = self.generate_ascend_point(self.from_cp)
flight.points.append(ascend)
location = self.potential_sead_targets[0][0]
self.potential_sead_targets.pop(0)
point = FlightWaypoint(location.position.x, location.position.y, 1000)
point.description = "SEAD"
point.pretty_name = "SEAD"
point.targets.append(location)
heading = self.from_cp.position.heading_between_point(location.position)
ingress_heading = heading - 180 + 25
egress_heading = heading - 180 - 25
ingress_pos = location.position.point_from_heading(ingress_heading, INGRESS_EGRESS_DISTANCE)
ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, INGRESS_ALT)
ingress_point.pretty_name = "INGRESS on " + location.obj_name
ingress_point.description = "INGRESS on " + location.obj_name
ingress_point.waypoint_type = FlightWaypointType.INGRESS_SEAD
flight.points.append(ingress_point)
point = FlightWaypoint(location.position.x, location.position.y, 0)
point.alt_type = "RADIO"
if flight.flight_type == FlightType.DEAD:
point.description = "SEAD on " + location.obj_name
point.pretty_name = "SEAD on " + location.obj_name
point.only_for_player = True
else:
point.description = "DEAD on " + location.obj_name
point.pretty_name = "DEAD on " + location.obj_name
point.only_for_player = True
ingress_point.targets.append(location)
flight.points.append(point)
egress_pos = location.position.point_from_heading(egress_heading, INGRESS_EGRESS_DISTANCE)
egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, EGRESS_ALT)
egress_point.pretty_name = "EGRESS from " + location.obj_name
egress_point.description = "EGRESS from " + location.obj_name
egress_point.waypoint_type = FlightWaypointType.EGRESS
flight.points.append(egress_point)
descend = self.generate_descend_point(self.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(self.from_cp)
flight.points.append(rtb)
self.sead_flights.append(flight)
self.flights.append(flight)
@@ -258,6 +370,110 @@ class FlightPlanner:
for k, v in inventory.items():
self.aircraft_inventory[k] = v
def commision_strike(self):
"""
Pick some aircraft to assign them to STRIKE tasks
"""
possible_aircraft = [k for k, v in self.aircraft_inventory.items() if k in STRIKE_CAPABLE and v >= 2]
inventory = dict({k: v for k, v in self.aircraft_inventory.items() if k in possible_aircraft})
if len(self.potential_strike_targets) > 0:
offset = random.randint(0,5)
for i in range(int(MISSION_DURATION/STRIKE_EVERY_X_MINUTES)):
if len(self.potential_strike_targets) <= 0:
break
try:
unit = random.choice([k for k, v in inventory.items() if v >= 2])
except IndexError:
break
inventory[unit] = inventory[unit] - 2
flight = Flight(unit, 2, self.from_cp, FlightType.STRIKE)
flight.points = []
flight.scheduled_in = offset + i*random.randint(SEAD_EVERY_X_MINUTES-5, SEAD_EVERY_X_MINUTES+5)
ascend = self.generate_ascend_point(self.from_cp)
flight.points.append(ascend)
location = self.potential_strike_targets[0][0]
self.potential_strike_targets.pop(0)
heading = self.from_cp.position.heading_between_point(location.position)
ingress_heading = heading - 180 + 25
egress_heading = heading - 180 - 25
ingress_pos = location.position.point_from_heading(ingress_heading, INGRESS_EGRESS_DISTANCE)
ingress_point = FlightWaypoint(ingress_pos.x, ingress_pos.y, INGRESS_ALT)
ingress_point.pretty_name = "INGRESS on " + location.obj_name
ingress_point.description = "INGRESS on " + location.obj_name
ingress_point.name = "INGRESS"
ingress_point.waypoint_type = FlightWaypointType.INGRESS_STRIKE
flight.points.append(ingress_point)
if len(location.groups) > 0 and location.dcs_identifier == "AA":
for g in location.groups:
for j, u in enumerate(g.units):
point = FlightWaypoint(u.position.x, u.position.y, 0)
point.description = "STRIKE " + "[" + str(location.obj_name) + "] : " + u.type + " #" + str(j)
point.pretty_name = "STRIKE " + "[" + str(location.obj_name) + "] : " + u.type + " #" + str(j)
point.name = location.obj_name + "#" + str(j)
point.only_for_player = True
ingress_point.targets.append(location)
flight.points.append(point)
else:
if hasattr(location, "obj_name"):
buildings = self.game.theater.find_ground_objects_by_obj_name(location.obj_name)
print(buildings)
for building in buildings:
print("BUILDING " + str(building.is_dead) + " " + str(building.dcs_identifier))
if building.is_dead:
continue
point = FlightWaypoint(building.position.x, building.position.y, 0)
point.description = "STRIKE on " + building.obj_name + " " + str(building.category)
point.pretty_name = "STRIKE on " + building.obj_name + " " + str(building.category)
point.name = building.obj_name
point.only_for_player = True
ingress_point.targets.append(building)
flight.points.append(point)
else:
point = FlightWaypoint(location.position.x, location.position.y, 0)
point.description = "STRIKE on " + location.obj_name + " " + str(location.category)
point.pretty_name = "STRIKE on " + location.obj_name + " " + str(location.category)
point.name = location.obj_name
point.only_for_player = True
ingress_point.targets.append(location)
flight.points.append(point)
egress_pos = location.position.point_from_heading(egress_heading, INGRESS_EGRESS_DISTANCE)
egress_point = FlightWaypoint(egress_pos.x, egress_pos.y, EGRESS_ALT)
egress_point.name = "EGRESS"
egress_point.pretty_name = "EGRESS from " + location.obj_name
egress_point.description = "EGRESS from " + location.obj_name
egress_point.waypoint_type = FlightWaypointType.EGRESS
flight.points.append(egress_point)
descend = self.generate_descend_point(self.from_cp)
flight.points.append(descend)
rtb = self.generate_rtb_waypoint(self.from_cp)
flight.points.append(rtb)
self.strike_flights.append(flight)
self.flights.append(flight)
# Update inventory
for k, v in inventory.items():
self.aircraft_inventory[k] = v
def _get_cas_locations(self):
cas_locations = []
for cp in self.from_cp.connected_points:
@@ -285,7 +501,7 @@ class FlightPlanner:
added_group = []
for g in cp.ground_objects:
if g.group_id in added_group: continue
if g.group_id in added_group or g.is_dead: continue
# Compute distance to current cp
distance = math.hypot(cp.position.x - self.from_cp.position.x,
@@ -347,3 +563,35 @@ class FlightPlanner:
del base_aircraft_inventory[f.unit_type]
return base_aircraft_inventory
def generate_ascend_point(self, from_cp):
ascend_heading = from_cp.heading
pos_ascend = from_cp.position.point_from_heading(ascend_heading, 10000)
ascend = FlightWaypoint(pos_ascend.x, pos_ascend.y, PATTERN_ALTITUDE)
ascend.name = "ASCEND"
ascend.alt_type = "RADIO"
ascend.description = "Ascend to alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL], then proceed to next waypoint"
ascend.pretty_name = "Ascend to alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL]"
ascend.waypoint_type = FlightWaypointType.ASCEND_POINT
return ascend
def generate_descend_point(self, from_cp):
ascend_heading = from_cp.heading
descend = from_cp.position.point_from_heading(ascend_heading - 180, 30000)
descend = FlightWaypoint(descend.x, descend.y, PATTERN_ALTITUDE)
descend.name = "DESCEND"
descend.alt_type = "RADIO"
descend.description = "Descend to pattern alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL], contact tower, and land"
descend.pretty_name = "Descend to pattern alt [" + str(meter_to_feet(PATTERN_ALTITUDE)) + " ft AGL]"
descend.waypoint_type = FlightWaypointType.DESCENT_POINT
return descend
def generate_rtb_waypoint(self, from_cp):
rtb = from_cp.position
rtb = FlightWaypoint(rtb.x, rtb.y, 0)
rtb.name = "LANDING"
rtb.alt_type = "RADIO"
rtb.description = "RTB"
rtb.pretty_name = "RTB"
rtb.waypoint_type = FlightWaypointType.LANDING_POINT
return rtb

View File

@@ -48,6 +48,7 @@ CAP_CAPABLE = [
P_51D_30_NA,
P_51D,
P_47D_30,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
@@ -106,6 +107,8 @@ CAS_CAPABLE = [
P_51D_30_NA,
P_51D,
P_47D_30,
A_20G,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
@@ -119,7 +122,7 @@ CAS_CAPABLE = [
SEAD_CAPABLE = [
F_4E,
FA_18C_hornet,
F_16C_50,
# F_16C_50, Not yet
AV8BNA,
JF_17,
@@ -164,10 +167,10 @@ STRIKE_CAPABLE = [
L_39ZA,
AJS37,
M_2000C,
P_51D_30_NA,
P_51D,
P_47D_30,
A_20G,
SpitfireLFMkIXCW,
SpitfireLFMkIX,
@@ -175,11 +178,18 @@ STRIKE_CAPABLE = [
Bf_109K_4,
FW_190D9,
FW_190A8,
]
ANTISHIP_CAPABLE = [
Su_24M,
Su_17M4,
F_A_18C,
AV8BNA,
JF_17
JF_17,
F_16C_50,
A_10C,
A_10A,
Ju_88A4,
]

View File

@@ -30,17 +30,38 @@ class FlightType(Enum):
EWAR = 16
class FlightWaypoint():
class FlightWaypointType(Enum):
TAKEOFF = 0 # Take off point
ASCEND_POINT = 1 # Ascension point after take off
PATROL = 2 # Patrol point
PATROL_TRACK = 3 # Patrol race track
NAV = 4 # Nav point
INGRESS_STRIKE = 5 # Ingress strike (For generator, means that this should have bombing on next TARGET_POINT points)
INGRESS_SEAD = 6 # Ingress sead (For generator, means that this should attack groups on TARGET_GROUP_LOC points)
INGRESS_CAS = 7 # Ingress cas (should start CAS task)
CAS = 8 # Should do CAS there
EGRESS = 9 # Should stop attack
DESCENT_POINT = 10 # Should start descending to pattern alt
LANDING_POINT = 11 # Should land there
TARGET_POINT = 12 # A target building or static object, position
TARGET_GROUP_LOC = 13 # A target group approximate location
TARGET_SHIP = 14 # A target ship known location
class FlightWaypoint:
def __init__(self, x: float, y: float, alt=0):
self.x = x
self.y = y
self.alt = alt
self.alt_type = "BARO"
self.name = ""
self.description = ""
self.targets = []
self.obj_name = ""
self.pretty_name = ""
self.waypoint_type = FlightWaypointType.TAKEOFF # type: FlightWaypointType
self.only_for_player = False
class Flight:

View File

@@ -19,6 +19,9 @@ TYPE_TANKS = [
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,
@@ -26,13 +29,21 @@ TYPE_TANKS = [
Armor.MT_M4_Sherman,
Armor.MT_M4A4_Sherman_Firefly,
Armor.StuG_IV,
Armor.ZTZ_96B
Armor.ST_Centaur_IV,
Armor.CT_Cromwell_IV,
Armor.HIT_Churchill_VII,
]
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,
]
TYPE_IFV = [
@@ -46,8 +57,10 @@ TYPE_IFV = [
Armor.IFV_M2A2_Bradley,
Armor.IFV_BMD_1,
Armor.ZBD_04A,
Armor.APC_Sd_Kfz_251,
# WW2
Armor.IFV_Sd_Kfz_234_2_Puma,
Armor.LAC_M8_Greyhound,
]
TYPE_APC = [
@@ -63,9 +76,11 @@ TYPE_APC = [
Armor.TPz_Fuchs,
Armor.ARV_BRDM_2,
Armor.ARV_BTR_RD,
Armor.ARV_MTLB_U_BOMAN,
Armor.M30_Cargo_Carrier,
Armor.FDDM_Grad,
# WW2
Armor.APC_M2A1,
Armor.APC_Sd_Kfz_251,
]
TYPE_ARTILLERY = [
@@ -79,9 +94,11 @@ TYPE_ARTILLERY = [
Artillery.SPH_2S9_Nona,
Artillery.SpGH_Dana,
Artillery.SPH_2S19_Msta,
Artillery.M12_GMC,
Artillery.MLRS_FDDM,
Artillery.Sturmpanzer_IV_Brummbär
# WW2
Artillery.Sturmpanzer_IV_Brummbär,
Artillery.M12_GMC
]
TYPE_LOGI = [

View File

@@ -77,14 +77,23 @@ class GroundObjectsGenerator:
vehicle.position.y = u.position.y
vehicle.heading = u.heading
vg.add_unit(vehicle)
if self.game.settings.perf_red_alert_state:
vg.points[0].tasks.append(OptAlarmState(2))
else:
vg.points[0].tasks.append(OptAlarmState(1))
elif ground_object.dcs_identifier in ["CARRIER", "LHA"]:
for g in ground_object.groups:
if len(g.units) > 0:
utype = unit_type_from_name(g.units[0].type)
sg = self.m.ship_group(side, g.name, utype, position=g.position, heading=g.units[0].heading)
if ground_object.dcs_identifier == "CARRIER" and self.game.settings.supercarrier == True:
utype = db.upgrade_to_supercarrier(utype, cp.name)
sg = self.m.ship_group(side, g.name, utype, position=g.position, heading=g.units[0].heading)
sg.units[0].name = self.m.string(g.units[0].name)
for i, u in enumerate(g.units):
if i > 0:
ship = Ship(self.m.next_unit_id(), self.m.string(u.name), unit_type_from_name(u.type))
@@ -100,6 +109,9 @@ class GroundObjectsGenerator:
modeChannel = "X" if not cp.tacanY else "Y"
sg.points[0].tasks.append(ActivateBeaconCommand(channel=cp.tacanN, modechannel=modeChannel, callsign=cp.tacanI, unit_id=sg.units[0].id))
if ground_object.dcs_identifier == "CARRIER" and hasattr(cp, "icls"):
sg.points[0].tasks.append(ActivateICLSCommand(cp.icls, unit_id=sg.units[0].id))
else:
if ground_object.dcs_identifier in warehouse_map:
static_type = warehouse_map[ground_object.dcs_identifier]
@@ -110,16 +122,6 @@ class GroundObjectsGenerator:
print("Didn't find {} in static _map(s)!".format(ground_object.dcs_identifier))
continue
if ground_object.group_id not in consumed_farps:
consumed_farps.add(ground_object.group_id)
if random.randint(0, 100) > 50:
farp_aa(
self.m,
side,
ground_object.string_identifier,
ground_object.position,
)
group = self.m.static_group(
country=side,
name=ground_object.string_identifier,

View File

@@ -68,7 +68,7 @@ class NameGenerator:
def random_objective_name(self):
if len(self.ANIMALS) == 0:
random.choice(ALPHA_MILITARY).upper() + " #" + str(random.randint(0, 100))
return random.choice(ALPHA_MILITARY).upper() + "#" + str(random.randint(0, 100))
else:
animal = random.choice(self.ANIMALS)
self.ANIMALS.remove(animal)

View File

@@ -4,7 +4,7 @@ from dcs.vehicles import AirDefence, Unarmed
from gen.sam.group_generator import GroupGenerator
GFLAK = [AirDefence.AAA_Flak_Vierling_38, AirDefence.AAA_Flak_Vierling_38, AirDefence.AAA_Flak_18, AirDefence.AAA_Flak_36, AirDefence.AAA_Flak_37, AirDefence.AAA_Flak_38]
GFLAK = [AirDefence.AAA_Flak_Vierling_38, 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_Flak_38]
class FlakGenerator(GroupGenerator):
"""
@@ -31,7 +31,7 @@ class FlakGenerator(GroupGenerator):
if(mixed):
unit_type = random.choice(GFLAK)
# Enough Opel truck to transport the guns
# Enough Opel Blitz truck to transport the guns
for i in range(grid_x):
for j in range(grid_y):
self.add_unit(Unarmed.Blitz_3_6_6700A, "AAA#" + str(index),

View File

@@ -50,7 +50,7 @@ SAM_MAP = {
AirDefence.SAM_Patriot_EPP_III: PatriotGenerator,
AirDefence.SAM_Chaparral_M48: ChaparralGenerator,
AirDefence.AAA_Bofors_40mm: BoforsGenerator,
AirDefence.AAA_Flak_36: FlakGenerator,
AirDefence.AAA_8_8cm_Flak_36: FlakGenerator,
AirDefence.SAM_SA_2_LN_SM_90: SA2Generator,
AirDefence.SAM_SA_3_S_125_LN_5P73: SA3Generator,
AirDefence.SAM_SA_6_Kub_LN_2P25: SA6Generator,

View File

@@ -4,6 +4,7 @@ import sys
from shutil import copyfile
import dcs
from PySide2 import QtWidgets
from PySide2.QtGui import QPixmap
from PySide2.QtWidgets import QApplication, QSplashScreen
from dcs import installation
@@ -11,21 +12,40 @@ from dcs import installation
from qt_ui import uiconstants
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
from qt_ui.windows.QLiberationWindow import QLiberationWindow
from userdata import persistency, logging as logging_module
from qt_ui.windows.preferences.QLiberationFirstStartWindow import QLiberationFirstStartWindow
from userdata import persistency, logging as logging_module, liberation_install
if __name__ == "__main__":
persistency.setup(installation.get_dcs_saved_games_directory())
app = QApplication(sys.argv)
css = ""
with open("./resources/stylesheets/style.css") as stylesheet:
app.setStyleSheet(stylesheet.read())
# Logging setup
VERSION_STRING = "2.0RC6"
logging_module.setup_version_string(VERSION_STRING)
# Inject custom payload in pydcs framework
custom_payloads = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..\\resources\\customized_payloads")
if os.path.exists(custom_payloads):
dcs.planes.FlyingType.payload_dirs.append(custom_payloads)
else:
# For release version the path is different.
custom_payloads = os.path.join(os.path.dirname(os.path.realpath(__file__)),
"resources\\customized_payloads")
if os.path.exists(custom_payloads):
dcs.planes.FlyingType.payload_dirs.append(custom_payloads)
VERSION_STRING = "2.0"
logging_module.setup_version_string(VERSION_STRING)
logging.info("Using {} as userdata folder".format(persistency.base_path()))
app = QApplication(sys.argv)
first_start = liberation_install.init()
if first_start:
window = QLiberationFirstStartWindow()
window.exec_()
logging.info("Using {} as 'Saved Game Folder'".format(persistency.base_path()))
logging.info("Using {} as 'DCS installation folder'".format(liberation_install.get_dcs_install_directory()))
# Splash screen setup
pixmap = QPixmap("./resources/ui/splash_screen.png")
@@ -38,24 +58,28 @@ if __name__ == "__main__":
uiconstants.load_aircraft_icons()
uiconstants.load_vehicle_icons()
css = ""
with open("./resources/stylesheets/style.css") as stylesheet:
css = stylesheet.read()
# Replace DCS Mission scripting file to allow DCS Liberation to work
print("Replace : " + installation.get_dcs_install_directory() + os.path.sep + "Scripts/MissionScripting.lua")
copyfile("./resources/scripts/MissionScripting.lua", installation.get_dcs_install_directory() + os.path.sep + "Scripts/MissionScripting.lua")
app.processEvents()
try:
liberation_install.replace_mission_scripting_file()
except:
error_dialog = QtWidgets.QErrorMessage()
error_dialog.setWindowTitle("Wrong DCS installation directory.")
error_dialog.showMessage("Unable to modify Mission Scripting file. Possible issues with rights. Try running as admin, or please perform the modification of the MissionScripting file manually.")
error_dialog.exec_()
# Apply CSS (need works)
app.setStyleSheet(css)
GameUpdateSignal()
# Start window
window = QLiberationWindow()
window.showMaximized()
splash.finish(window)
sys.exit(app.exec_())
qt_execution_code = app.exec_()
# Restore Mission Scripting file
logging.info("QT App terminated with status code : " + str(qt_execution_code))
logging.info("Attempt to restore original mission scripting file")
liberation_install.restore_original_mission_scripting()
sys.exit(qt_execution_code)

View File

@@ -8,12 +8,12 @@ from game.event import UnitsDeliveryEvent, FrontlineAttackEvent
from theater.theatergroundobject import CATEGORY_MAP
URLS : Dict[str, str] = {
"Manual": "https://github.com/shdwp/dcs_liberation/wiki/Manual",
"Manual": "https://github.com/khopa/dcs_liberation/wiki",
"Troubleshooting": "https://github.com/shdwp/dcs_liberation/wiki/Troubleshooting",
"Modding": "https://github.com/shdwp/dcs_liberation/wiki/Modding-tutorial",
"Repository": "https://github.com/shdwp/dcs_liberation",
"Repository": "https://github.com/khopa/dcs_liberation",
"ForumThread": "https://forums.eagle.ru/showthread.php?t=214834",
"Issues": "https://github.com/shdwp/dcs_liberation/issues"
"Issues": "https://github.com/khopa/dcs_liberation/issues"
}
LABELS_OPTIONS = ["Full", "Abbreviated", "Dot Only", "Off"]

View File

@@ -2,6 +2,7 @@ from PySide2.QtCore import QSortFilterProxyModel, Qt, QModelIndex
from PySide2.QtGui import QStandardItem, QStandardItemModel
from PySide2.QtWidgets import QComboBox, QCompleter
from game import Game
from gen import Conflict
from gen.flights.flight import FlightWaypoint
from theater import ControlPointType
@@ -89,20 +90,23 @@ class QPredefinedWaypointSelectionComboBox(QComboBox):
if cp.captured:
enemy_cp = [ecp for ecp in cp.connected_points if ecp.captured != cp.captured]
for ecp in enemy_cp:
wpt = FlightWaypoint((cp.position.x + ecp.position.x)/2, (cp.position.y + ecp.position.y)/2, 800)
pos = Conflict.frontline_position(self.game.theater, cp, ecp)[0]
wpt = FlightWaypoint(pos.x, pos.y, 800)
wpt.name = "Frontline " + cp.name + "/" + ecp.name + " [CAS]"
wpt.alt_type = "RADIO"
wpt.pretty_name = wpt.name
wpt.description = "Frontline"
i = add_model_item(i, model, wpt.pretty_name, wpt)
for cp in self.game.theater.controlpoints:
for ground_object in cp.ground_objects:
if not ground_object.is_dead and not ground_object.dcs_identifier == "AA":
wpt = FlightWaypoint(ground_object.position.x,ground_object.position.y, 0)
wpt.alt_type = "RADIO"
wpt.name = wpt.name = "[" + str(ground_object.obj_name) + "] : " + ground_object.category + " #" + str(ground_object.object_id)
wpt.pretty_name = wpt.name
wpt.obj_name = ground_object.obj_name
wpt.targets.append(ground_object)
if cp.captured:
wpt.description = "Friendly Building"
else:
@@ -116,8 +120,10 @@ class QPredefinedWaypointSelectionComboBox(QComboBox):
for g in ground_object.groups:
for j, u in enumerate(g.units):
wpt = FlightWaypoint(u.position.x, u.position.y, 0)
wpt.alt_type = "RADIO"
wpt.name = wpt.name = "[" + str(ground_object.obj_name) + "] : " + u.type + " #" + str(j)
wpt.pretty_name = wpt.name
wpt.targets.append(u)
wpt.obj_name = ground_object.obj_name
if cp.captured:
wpt.description = "Friendly unit : " + u.type
@@ -128,6 +134,7 @@ class QPredefinedWaypointSelectionComboBox(QComboBox):
for cp in self.game.theater.controlpoints:
wpt = FlightWaypoint(cp.position.x, cp.position.y, 0)
wpt.alt_type = "RADIO"
wpt.name = cp.name
if cp.captured:
wpt.description = "Position of " + cp.name + " [Friendly Airbase]"

View File

@@ -83,11 +83,16 @@ class QLiberationMap(QGraphicsView):
pen = QPen(brush=CONST.COLORS["red"])
brush = CONST.COLORS["red_transparent"]
added_objects = []
for ground_object in cp.ground_objects:
if ground_object.obj_name in added_objects:
continue
go_pos = self._transform_point(ground_object.position)
if not ground_object.airbase_group:
scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 12, 12, cp, ground_object))
buildings = self.game.theater.find_ground_objects_by_obj_name(ground_object.obj_name)
scene.addItem(QMapGroundObject(self, go_pos[0], go_pos[1], 12, 12, cp, ground_object, buildings))
if ground_object.category == "aa" and self.get_display_rule("sam"):
max_range = 0
@@ -99,6 +104,7 @@ class QLiberationMap(QGraphicsView):
max_range = unit.threat_range
if max_range >= 6000:
scene.addEllipse(go_pos[0] - max_range/300.0 + 8, go_pos[1] - max_range/300.0 + 8, max_range/150.0, max_range/150.0, pen, brush)
added_objects.append(ground_object.obj_name)
for cp in self.game.theater.enemy_points():
if self.get_display_rule("lines"):

View File

@@ -9,13 +9,14 @@ from theater import TheaterGroundObject, ControlPoint
class QMapGroundObject(QGraphicsRectItem):
def __init__(self, parent, x: float, y: float, w: float, h: float, cp: ControlPoint, model: TheaterGroundObject):
def __init__(self, parent, x: float, y: float, w: float, h: float, cp: ControlPoint, model: TheaterGroundObject, buildings=[]):
super(QMapGroundObject, self).__init__(x, y, w, h)
self.model = model
self.cp = cp
self.parent = parent
self.setAcceptHoverEvents(True)
self.setZValue(2)
self.buildings = buildings
#self.setFlag(QGraphicsItem.ItemIgnoresTransformations, True)
if len(self.model.groups) > 0:
@@ -32,7 +33,11 @@ class QMapGroundObject(QGraphicsRectItem):
tooltip = tooltip + str(unit) + "x" + str(units[unit]) + "\n"
self.setToolTip(tooltip[:-1])
else:
self.setToolTip("[" + self.model.obj_name + "] : " + self.model.category)
tooltip = "[" + self.model.obj_name + "]" + "\n"
for building in buildings:
if not building.is_dead:
tooltip = tooltip + str(building.dcs_identifier) + "\n"
self.setToolTip(tooltip[:-1])
def paint(self, painter, option, widget=None):

View File

@@ -57,6 +57,14 @@ class QDebriefingWindow(QDialog):
except:
print("Issue adding " + str(unit_type) + " to debriefing information")
for building, count in self.debriefing.player_dead_buildings_dict.items():
try:
lostUnitsLayout.addWidget(QLabel(building, row, 0))
lostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
row += 1
except:
print("Issue adding " + str(building) + " to debriefing information")
self.layout.addWidget(lostUnits)
# Enemy lost units
@@ -87,6 +95,14 @@ class QDebriefingWindow(QDialog):
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
row += 1
for building, count in self.debriefing.enemy_dead_buildings_dict.items():
try:
enemylostUnitsLayout.addWidget(QLabel(building), row, 0)
enemylostUnitsLayout.addWidget(QLabel("{}".format(count)), row, 1)
row += 1
except:
print("Issue adding " + str(building) + " to debriefing information")
self.layout.addWidget(enemylostUnits)
# confirm button

View File

@@ -1,10 +1,9 @@
import sys
import webbrowser
from PySide2 import QtGui
from PySide2.QtCore import Qt
from PySide2.QtGui import QIcon
from PySide2.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QMainWindow, QAction, QMessageBox, QDesktopWidget, \
from PySide2.QtWidgets import QWidget, QVBoxLayout, QMainWindow, QAction, QMessageBox, QDesktopWidget, \
QSplitter
import qt_ui.uiconstants as CONST
@@ -12,10 +11,12 @@ from game import Game
from qt_ui.uiconstants import URLS
from qt_ui.widgets.QTopPanel import QTopPanel
from qt_ui.widgets.map.QLiberationMap import QLiberationMap
from qt_ui.windows.preferences import QLiberationPreferences
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal, DebriefingSignal
from qt_ui.windows.QDebriefingWindow import QDebriefingWindow
from qt_ui.windows.QNewGameWizard import NewGameWizard
from qt_ui.windows.infos.QInfoPanel import QInfoPanel
from qt_ui.windows.preferences.QLiberationPreferencesWindow import QLiberationPreferencesWindow
from userdata import persistency
@@ -79,6 +80,10 @@ class QLiberationWindow(QMainWindow):
self.showAboutDialogAction.setIcon(QIcon.fromTheme("help-about"))
self.showAboutDialogAction.triggered.connect(self.showAboutDialog)
self.showLiberationPrefDialogAction = QAction("Preferences", self)
self.showLiberationPrefDialogAction.setIcon(QIcon.fromTheme("help-about"))
self.showLiberationPrefDialogAction.triggered.connect(self.showLiberationDialog)
def initToolbar(self):
self.tool_bar = self.addToolBar("File")
self.tool_bar.addAction(self.newGameAction)
@@ -92,17 +97,21 @@ class QLiberationWindow(QMainWindow):
file_menu.addAction(self.newGameAction)
#file_menu.addAction(QIcon(CONST.ICONS["Open"]), "Open") # TODO : implement
file_menu.addAction(self.saveGameAction)
file_menu.addSeparator()
file_menu.addAction(self.showLiberationPrefDialogAction)
file_menu.addSeparator()
#file_menu.addAction("Save As") # TODO : implement
#file_menu.addAction("Close Current Game", lambda: self.closeGame()) # Not working
file_menu.addAction("Exit" , lambda: self.exit())
help_menu = self.menu.addMenu("Help")
#help_menu.addAction("Online Manual", lambda: webbrowser.open_new_tab(URLS["Manual"]))
help_menu.addAction("Online Manual", lambda: webbrowser.open_new_tab(URLS["Manual"]))
help_menu.addAction("Discord", lambda: webbrowser.open_new_tab("https://" + "discord.gg" + "/" + "bKrt" + "rkJ"))
#help_menu.addAction("Troubleshooting Guide", lambda: webbrowser.open_new_tab(URLS["Troubleshooting"]))
#help_menu.addAction("Modding Guide", lambda: webbrowser.open_new_tab(URLS["Modding"]))
#help_menu.addSeparator() ----> Note from Khopa : I disable these links since it's not up to date for this branch
help_menu.addAction("Contribute", lambda: webbrowser.open_new_tab(URLS["Repository"]))
#help_menu.addAction("Contribute", lambda: webbrowser.open_new_tab(URLS["Repository"]))
help_menu.addAction("Forum Thread", lambda: webbrowser.open_new_tab(URLS["ForumThread"]))
help_menu.addAction("Report an issue", lambda: webbrowser.open_new_tab(URLS["Issues"]))
help_menu.addSeparator()
@@ -192,6 +201,10 @@ class QLiberationWindow(QMainWindow):
print(about.textFormat())
about.exec_()
def showLiberationDialog(self):
self.subwindow = QLiberationPreferencesWindow()
self.subwindow.show()
def onDebriefing(self, debrief: DebriefingSignal):
print("On Debriefing")
self.debriefing = QDebriefingWindow(debrief.debriefing, debrief.gameEvent, debrief.game)

View File

@@ -7,6 +7,7 @@ from dcs.task import CAP, CAS
import qt_ui.uiconstants as CONST
from game import db, Game
from gen import namegen
from theater import start_generator, persiangulf, nevada, caucasus, ConflictTheater, normandy
from userdata.logging import version_string
@@ -29,6 +30,7 @@ class NewGameWizard(QtWidgets.QWizard):
self.generatedGame = None
def accept(self):
blueFaction = [c for c in db.FACTIONS if db.FACTIONS[c]["side"] == "blue"][self.field("blueFaction")]
redFaction = [c for c in db.FACTIONS if db.FACTIONS[c]["side"] == "red"][self.field("redFaction")]
playerIsBlue = self.field("playerIsBlue")
@@ -36,11 +38,11 @@ class NewGameWizard(QtWidgets.QWizard):
isTerrainNttr = self.field("isTerrainNttr")
isTerrainCaucasusSmall = self.field("isTerrainCaucasusSmall")
isTerrainCaucasusSmallInverted = self.field("isTerrainCaucasusSmallInverted")
isTerrainCaucasusNorth= self.field("isTerrainCaucasusNorth")
isIranianCampaignTheater = self.field("isIranianCampaignTheater")
isTerrainNormandy = self.field("isTerrainNormandy")
isTerrainEmirates = self.field("isTerrainEmirates")
timePeriod = db.TIME_PERIODS[list(db.TIME_PERIODS.keys())[self.field("timePeriod")]]
sams = self.field("sams")
midGame = self.field("midGame")
multiplier = self.field("multiplier")
@@ -55,6 +57,8 @@ class NewGameWizard(QtWidgets.QWizard):
conflicttheater = caucasus.WesternGeorgia()
elif isTerrainCaucasusSmallInverted:
conflicttheater = caucasus.WesternGeorgiaInverted()
elif isTerrainCaucasusNorth:
conflicttheater = caucasus.NorthCaucasus()
elif isIranianCampaignTheater:
conflicttheater = persiangulf.IranianCampaign()
elif isTerrainEmirates:
@@ -64,27 +68,38 @@ class NewGameWizard(QtWidgets.QWizard):
else:
conflicttheater = caucasus.CaucasusTheater()
self.generatedGame = self.start_new_game(player_name, enemy_name, conflicttheater, sams, midGame, multiplier,
self.generatedGame = self.start_new_game(player_name, enemy_name, conflicttheater, midGame, multiplier,
timePeriod)
super(NewGameWizard, self).accept()
def start_new_game(self, player_name: str, enemy_name: str, conflicttheater: ConflictTheater, sams: bool,
def start_new_game(self, player_name: str, enemy_name: str, conflicttheater: ConflictTheater,
midgame: bool, multiplier: float, period: datetime):
if midgame:
for i in range(0, int(len(conflicttheater.controlpoints) / 2)):
conflicttheater.controlpoints[i].captured = True
start_generator.generate_inital_units(conflicttheater, enemy_name, sams, multiplier)
# Reset name generator
namegen.reset()
print("-- Starting New Game Generator")
print("Enemy name : " + enemy_name)
print("Player name : " + player_name)
print("Midgame : " + str(midgame))
start_generator.generate_inital_units(conflicttheater, enemy_name, True, multiplier)
print("-- Initial units generated")
game = Game(player_name=player_name,
enemy_name=enemy_name,
theater=conflicttheater,
start_date=period)
print("-- Game Object generated")
start_generator.generate_groundobjects(conflicttheater, game)
game.budget = int(game.budget * multiplier)
game.settings.multiplier = multiplier
game.settings.sams = sams
game.settings.sams = True
game.settings.version = version_string()
if midgame:
@@ -206,12 +221,14 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
# Terrain selection
terrainGroup = QtWidgets.QGroupBox("Terrain")
terrainCaucasusSmall = QtWidgets.QRadioButton("Caucasus - Western Georgia [RECOMMENDED]")
terrainCaucasusSmall = QtWidgets.QRadioButton("Caucasus - Western Georgia [RECOMMENDED - Early Cold War Era]")
terrainCaucasusSmall.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Caucasus"]))
terrainCaucasusSmallInverted = QtWidgets.QRadioButton("Caucasus - Western Georgia Inverted [RECOMMENDED]")
terrainCaucasusSmallInverted = QtWidgets.QRadioButton("Caucasus - Western Georgia Inverted [RECOMMENDED - Early Cold War Era]")
terrainCaucasusSmallInverted.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Caucasus"]))
terrainCaucasus = QtWidgets.QRadioButton("Caucasus - Full map [NOT TESTED]")
terrainCaucasus.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Caucasus"]))
terrainCaucasusNorth = QtWidgets.QRadioButton("Caucasus - North - [RECOMMENDED - Modern Era]")
terrainCaucasusNorth.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Caucasus"]))
terrainPg = QtWidgets.QRadioButton("Persian Gulf - Full Map [NOT TESTED]")
terrainPg.setIcon(QtGui.QIcon(CONST.ICONS["Terrain_Persian_Gulf"]))
@@ -238,6 +255,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
self.registerField('isTerrainCaucasus', terrainCaucasus)
self.registerField('isTerrainCaucasusSmall', terrainCaucasusSmall)
self.registerField('isTerrainCaucasusSmallInverted', terrainCaucasusSmallInverted)
self.registerField('isTerrainCaucasusNorth', terrainCaucasusNorth)
self.registerField('isTerrainPg', terrainPg)
self.registerField('isIranianCampaignTheater', terrainIran)
self.registerField('isTerrainEmirates', terrainEmirates)
@@ -249,6 +267,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
terrainGroupLayout = QtWidgets.QVBoxLayout()
terrainGroupLayout.addWidget(terrainCaucasusSmall)
terrainGroupLayout.addWidget(terrainCaucasusSmallInverted)
terrainGroupLayout.addWidget(terrainCaucasusNorth)
terrainGroupLayout.addWidget(terrainCaucasus)
terrainGroupLayout.addWidget(terrainIran)
terrainGroupLayout.addWidget(terrainEmirates)
@@ -278,21 +297,16 @@ class MiscOptions(QtWidgets.QWizardPage):
self.setPixmap(QtWidgets.QWizard.LogoPixmap,
QtGui.QPixmap('./resources/ui/wizard/logo1.png'))
sams = QtWidgets.QCheckBox()
sams.setChecked(True)
midGame = QtWidgets.QCheckBox()
multiplier = QtWidgets.QSpinBox()
multiplier.setEnabled(False)
multiplier.setMinimum(1)
multiplier.setMaximum(5)
self.registerField('sams', sams)
self.registerField('midGame', midGame)
self.registerField('multiplier', multiplier)
layout = QtWidgets.QGridLayout()
#layout.addWidget(QtWidgets.QLabel("With SAM Systems :"), 0, 0)
#layout.addWidget(sams, 0, 1)
layout.addWidget(QtWidgets.QLabel("Start at mid game"), 1, 0)
layout.addWidget(midGame, 1, 1)
layout.addWidget(QtWidgets.QLabel("Ennemy forces multiplier [Disabled for Now]"), 2, 0)

View File

@@ -38,7 +38,7 @@ class QMissionPlanning(QDialog):
self.planned_flight_view.set_flight_planner(self.planner)
self.selected_cp = self.captured_cp[0]
self.planned_flight_view.selectionModel().setCurrentIndex(self.planned_flight_view.indexAt(QPoint(1, 1)), QItemSelectionModel.Select)
self.planned_flight_view.selectionModel().setCurrentIndex(self.planned_flight_view.indexAt(QPoint(1, 1)), QItemSelectionModel.Rows)
self.planned_flight_view.selectionModel().selectionChanged.connect(self.on_flight_selection_change)
if len(self.planned_flight_view.flight_planner.flights) > 0:
@@ -83,15 +83,24 @@ class QMissionPlanning(QDialog):
else:
self.planned_flight_view.set_flight_planner(None)
print(self.selected_cp.id)
def on_flight_selection_change(self):
index = self.planned_flight_view.selectionModel().currentIndex().row()
flight = self.planner.flights[index]
print("On flight selection change")
index = self.planned_flight_view.selectionModel().currentIndex().row()
self.planned_flight_view.repaint();
if self.flight_planner is not None:
self.flight_planner.clearTabs()
try:
flight = self.planner.flights[index]
except IndexError:
flight = None
self.flight_planner = QFlightPlanner(flight, self.game, self.planner)
self.layout.addWidget(self.flight_planner, 0, 1)
def on_add_flight(self):
possible_aircraft_type = list(self.selected_cp.base.aircraft.keys())
@@ -110,7 +119,8 @@ class QMissionPlanning(QDialog):
def on_delete_flight(self):
index = self.planned_flight_view.selectionModel().currentIndex().row()
self.planner.remove_flight(index)
self.planned_flight_view.set_flight_planner(self.planner)
self.planned_flight_view.set_flight_planner(self.planner, index)
def on_start(self):

View File

@@ -1,6 +1,6 @@
from PySide2.QtCore import QSize
from PySide2.QtCore import QSize, QItemSelectionModel, QPoint
from PySide2.QtGui import QStandardItemModel
from PySide2.QtWidgets import QListView
from PySide2.QtWidgets import QListView, QAbstractItemView
from gen.flights.ai_flight_planner import FlightPlanner
from qt_ui.windows.mission.QFlightItem import QFlightItem
@@ -13,18 +13,29 @@ class QPlannedFlightsView(QListView):
self.model = QStandardItemModel(self)
self.setModel(self.model)
self.setIconSize(QSize(91, 24))
self.setSelectionBehavior(QAbstractItemView.SelectItems)
if flight_planner:
self.set_flight_planner(flight_planner)
def update_content(self):
def update_content(self, row=0):
for i, f in enumerate(self.flight_planner.flights):
self.model.appendRow(QFlightItem(f))
self.setSelectedFlight(row)
self.repaint()
def setSelectedFlight(self, row):
self.selectionModel().clearSelection()
index = self.model.index(row, 0)
if not index.isValid():
index = self.model.index(0, 0)
self.selectionModel().setCurrentIndex(index, QItemSelectionModel.Select)
self.repaint()
def clear_layout(self):
self.model.removeRows(0, self.model.rowCount())
def set_flight_planner(self, flight_planner: FlightPlanner):
def set_flight_planner(self, flight_planner: FlightPlanner, row=0):
self.clear_layout()
self.flight_planner = flight_planner
if self.flight_planner:
self.update_content()
self.update_content(row)

View File

@@ -111,6 +111,6 @@ class QFlightCreator(QDialog):
self.planner.flights.append(flight)
self.planner.custom_flights.append(flight)
if self.flight_view is not None:
self.flight_view.set_flight_planner(self.planner)
self.flight_view.set_flight_planner(self.planner, len(self.planner.flights)-1)
self.close()

View File

@@ -11,6 +11,7 @@ class QFlightPlanner(QTabWidget):
def __init__(self, flight: Flight, game: Game, planner):
super(QFlightPlanner, self).__init__()
self.tabCount = 0
if flight:
self.general_settings_tab = QGeneralFlightSettingsTab(flight, game, planner)
self.payload_tab = QFlightPayloadTab(flight, game)
@@ -18,9 +19,15 @@ class QFlightPlanner(QTabWidget):
self.addTab(self.general_settings_tab, "General Flight settings")
self.addTab(self.payload_tab, "Payload")
self.addTab(self.waypoint_tab, "Waypoints")
self.tabCount = 3
else:
tabError = QFrame()
l = QGridLayout()
l.addWidget(QLabel("No flight selected"))
tabError.setLayout(l)
self.addTab(tabError, "No flight")
self.addTab(tabError, "No flight")
self.tabCount = 1
def clearTabs(self):
for i in range(self.tabCount):
self.removeTab(i)

View File

@@ -0,0 +1,80 @@
from PySide2.QtGui import QIcon, Qt
from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout, QPlainTextEdit, QTextEdit
from qt_ui.windows.preferences.QLiberationPreferences import QLiberationPreferences
class QLiberationFirstStartWindow(QDialog):
def __init__(self):
super(QLiberationFirstStartWindow, self).__init__()
self.setModal(True)
self.setWindowTitle("First start configuration")
self.setMinimumSize(500, 200)
self.setWindowIcon(QIcon("./resources/icon.png"))
self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.Dialog | Qt.WindowTitleHint)
self.setWindowModality(Qt.WindowModal)
self.preferences = QLiberationPreferences()
WARN_TEXT = """
<strong>Welcome to DCS Liberation !</strong>
<br/><br>
<strong>Please take 30 seconds to read this :</strong>
<p>DCS Liberation will modify this file in your DCS installation directory :</p>
<br/>
<strong>&lt;dcs_installation_directory&gt;/Scripts/MissionScripting.lua</strong><br/>
<p>
This will disable some security limits of the DCS World Lua scripting environment, in order to allow communication between DCS World and DCS Liberation.
However, the modification of this file could potentially grant access to your filesystem to malicious DCS mission files.
</p>
<p>So, you should not join untrusted servers or open untrusted mission files within DCS world while DCS Liberation is running.</p>
<p>
DCS Liberation will restore your original MissionScripting file when it close.
</p>
<p>
However, should DCS Liberation encounter an unexpected crash (which should not happen), the MissionScripting file might not be restored.
If that occurs, you can use the backup file saved in the DCS Liberation directory there :
</p>
<br/>
<strong>./resources/scripts/MissionScripting.original.lua</strong><br/>
<p>Then copy it in your DCS installation directory to replace this file :</p>
<br/>
<strong>&lt;dcs_installation_directory&gt;/Scripts/MissionScripting.lua</strong><br/>
<p>As you click on the button below, the file will be replaced in your DCS installation directory.</p>
<br/><br/>
<strong>Thank you for reading !</strong>
"""
self.warning_text = QTextEdit(WARN_TEXT)
self.warning_text.setReadOnly(True)
self.apply_button = QPushButton("I have read everything and I Accept")
self.apply_button.clicked.connect(lambda : self.apply())
self.initUI()
def initUI(self):
layout = QVBoxLayout()
layout.addWidget(self.preferences)
layout.addWidget(self.warning_text)
layout.addStretch()
apply_btn_layout = QHBoxLayout()
apply_btn_layout.addStretch()
apply_btn_layout.addWidget(self.apply_button)
layout.addLayout(apply_btn_layout)
self.setLayout(layout)
def apply(self):
print("Applying changes")
if self.preferences.apply():
self.close()

View File

@@ -0,0 +1,93 @@
import os
from PySide2 import QtWidgets
from PySide2.QtGui import Qt
from PySide2.QtWidgets import QFrame, QLineEdit, QGridLayout, QVBoxLayout, QLabel, QPushButton, \
QFileDialog, QMessageBox, QDialog
from userdata import liberation_install
class QLiberationPreferences(QFrame):
def __init__(self):
super(QLiberationPreferences, self).__init__()
self.saved_game_dir = ""
self.dcs_install_dir = ""
self.dcs_install_dir = liberation_install.get_dcs_install_directory()
self.saved_game_dir = liberation_install.get_saved_game_dir()
self.edit_dcs_install_dir = QLineEdit(self.dcs_install_dir)
self.edit_saved_game_dir = QLineEdit(self.saved_game_dir)
self.edit_dcs_install_dir.setMinimumWidth(300)
self.edit_saved_game_dir.setMinimumWidth(300)
self.browse_saved_game = QPushButton("Browse...")
self.browse_saved_game.clicked.connect(self.on_browse_saved_games)
self.browse_install_dir = QPushButton("Browse...")
self.browse_install_dir.clicked.connect(self.on_browse_installation_dir)
self.initUi()
def initUi(self):
main_layout = QVBoxLayout()
layout = QGridLayout()
layout.addWidget(QLabel("<strong>DCS saved game directory:</strong>"), 0, 0, alignment=Qt.AlignLeft)
layout.addWidget(self.edit_saved_game_dir, 1, 0, alignment=Qt.AlignRight)
layout.addWidget(self.browse_saved_game, 1, 1, alignment=Qt.AlignRight)
layout.addWidget(QLabel("<strong>DCS installation directory:</strong>"), 2, 0, alignment=Qt.AlignLeft)
layout.addWidget(self.edit_dcs_install_dir, 3, 0, alignment=Qt.AlignRight)
layout.addWidget(self.browse_install_dir, 3, 1, alignment=Qt.AlignRight)
main_layout.addLayout(layout)
main_layout.addStretch()
self.setLayout(main_layout)
def on_browse_saved_games(self):
saved_game_dir = str(QFileDialog.getExistingDirectory(self, "Select DCS Saved Game Directory"))
if saved_game_dir:
self.saved_game_dir = saved_game_dir
self.edit_saved_game_dir.setText(saved_game_dir)
def on_browse_installation_dir(self):
install_dir = str(QFileDialog.getExistingDirectory(self, "Select DCS Installation Directory"))
if install_dir:
self.dcs_install_dir = install_dir
self.edit_dcs_install_dir.setText(install_dir)
def apply(self):
print("Applying changes")
self.saved_game_dir = self.edit_saved_game_dir.text()
self.dcs_install_dir = self.edit_dcs_install_dir.text()
if not os.path.isdir(self.saved_game_dir):
error_dialog = QMessageBox.critical(self, "Wrong DCS Saved Games directory.",
self.saved_game_dir + " is not a valid directory",
QMessageBox.StandardButton.Ok)
error_dialog.exec_()
return False
if not os.path.isdir(self.dcs_install_dir):
error_dialog = QMessageBox.critical(self, "Wrong DCS installation directory.",
self.dcs_install_dir + " is not a valid directory",
QMessageBox.StandardButton.Ok)
error_dialog.exec_()
return False
if not os.path.isdir(os.path.join(self.dcs_install_dir, "Scripts")) and os.path.isfile(os.path.join(self.dcs_install_dir, "bin", "DCS.exe")):
error_dialog = QMessageBox.critical(self, "Wrong DCS installation directory.",
self.dcs_install_dir + " is not a valid DCS installation directory",
QMessageBox.StandardButton.Ok)
error_dialog.exec_()
return False
liberation_install.setup(self.saved_game_dir, self.dcs_install_dir)
liberation_install.save_config()
return True

View File

@@ -0,0 +1,37 @@
from PySide2.QtGui import QIcon, Qt
from PySide2.QtWidgets import QDialog, QVBoxLayout, QPushButton, QHBoxLayout
from qt_ui.windows.preferences.QLiberationPreferences import QLiberationPreferences
class QLiberationPreferencesWindow(QDialog):
def __init__(self):
super(QLiberationPreferencesWindow, self).__init__()
self.setModal(True)
self.setWindowTitle("Preferences")
self.setMinimumSize(300, 200)
self.setWindowIcon(QIcon("./resources/icon.png"))
self.preferences = QLiberationPreferences()
self.apply_button = QPushButton("Apply")
self.apply_button.clicked.connect(lambda : self.apply())
self.initUI()
def initUI(self):
layout = QVBoxLayout()
layout.addWidget(self.preferences)
layout.addStretch()
apply_btn_layout = QHBoxLayout()
apply_btn_layout.addStretch()
apply_btn_layout.addWidget(self.apply_button)
layout.addLayout(apply_btn_layout)
self.setLayout(layout)
def apply(self):
if self.preferences.apply():
print("Closing")
self.close()
else:
print("Not Closing")

View File

@@ -1,7 +1,7 @@
from PySide2.QtCore import QSize, Qt, QItemSelectionModel, QPoint
from PySide2.QtGui import QStandardItemModel, QStandardItem
from PySide2.QtWidgets import QLabel, QDialog, QGridLayout, QListView, QStackedLayout, QComboBox, QWidget, \
QAbstractItemView, QPushButton, QGroupBox, QCheckBox
QAbstractItemView, QPushButton, QGroupBox, QCheckBox, QVBoxLayout
import qt_ui.uiconstants as CONST
from game.game import Game
@@ -98,11 +98,11 @@ class QSettingsWindow(QDialog):
self.enemyAASkill.currentIndexChanged.connect(self.applySettings)
self.difficultyLayout.addWidget(QLabel("Player coalition skill"), 0, 0)
self.difficultyLayout.addWidget(self.playerCoalitionSkill, 0, 1)
self.difficultyLayout.addWidget(self.playerCoalitionSkill, 0, 1, Qt.AlignRight)
self.difficultyLayout.addWidget(QLabel("Enemy skill"), 1, 0)
self.difficultyLayout.addWidget(self.enemyCoalitionSkill, 1, 1)
self.difficultyLayout.addWidget(self.enemyCoalitionSkill, 1, 1, Qt.AlignRight)
self.difficultyLayout.addWidget(QLabel("Enemy AA and vehicles skill"), 2, 0)
self.difficultyLayout.addWidget(self.enemyAASkill, 2, 1)
self.difficultyLayout.addWidget(self.enemyAASkill, 2, 1, Qt.AlignRight)
self.difficultyLabel = QComboBox()
[self.difficultyLabel.addItem(t) for t in CONST.LABELS_OPTIONS]
@@ -110,43 +110,78 @@ class QSettingsWindow(QDialog):
self.difficultyLabel.currentIndexChanged.connect(self.applySettings)
self.difficultyLayout.addWidget(QLabel("In Game Labels"), 3, 0)
self.difficultyLayout.addWidget(self.difficultyLabel, 3, 1)
self.difficultyLayout.addWidget(self.difficultyLabel, 3, 1, Qt.AlignRight)
self.noNightMission = QCheckBox()
self.noNightMission.setChecked(self.game.settings.night_disabled)
self.noNightMission.toggled.connect(self.applySettings)
self.difficultyLayout.addWidget(QLabel("No night missions"), 4, 0)
self.difficultyLayout.addWidget(self.noNightMission, 4, 1)
self.difficultyLayout.addWidget(self.noNightMission, 4, 1, Qt.AlignRight)
def initGeneratorLayout(self):
self.generatorPage = QWidget()
self.generatorLayout = QGridLayout()
self.generatorLayout = QVBoxLayout()
self.generatorLayout.setAlignment(Qt.AlignTop)
self.generatorPage.setLayout(self.generatorLayout)
self.coldStart = QCheckBox()
self.coldStart.setChecked(self.game.settings.cold_start)
self.coldStart.toggled.connect(self.applySettings)
self.takeOffOnlyForPlayerGroup = QCheckBox()
self.takeOffOnlyForPlayerGroup.setChecked(self.game.settings.only_player_takeoff)
self.takeOffOnlyForPlayerGroup.toggled.connect(self.applySettings)
self.coldStart = QCheckBox()
self.coldStart.setChecked(self.game.settings.cold_start)
self.coldStart.toggled.connect(self.applySettings)
self.gameplay = QGroupBox("Gameplay")
self.gameplayLayout = QGridLayout();
self.gameplayLayout.setAlignment(Qt.AlignTop)
self.gameplay.setLayout(self.gameplayLayout)
self.supercarrier = QCheckBox()
self.supercarrier.setChecked(self.game.settings.supercarrier)
self.supercarrier.toggled.connect(self.applySettings)
# Settings not used anymore
# self.generatorLayout.addWidget(QLabel("Aircraft cold start"), 0, 0)
# self.generatorLayout.addWidget(self.coldStart, 0, 1)
# self.generatorLayout.addWidget(QLabel("Takeoff only for player group"), 1, 0)
# self.generatorLayout.addWidget(self.takeOffOnlyForPlayerGroup, 1, 1)
self.generatorLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0)
self.generatorLayout.addWidget(self.supercarrier, 0, 1)
self.gameplayLayout.addWidget(QLabel("Use Supercarrier Module"), 0, 0)
self.gameplayLayout.addWidget(self.supercarrier, 0, 1, Qt.AlignRight)
self.performance = QGroupBox("Performance")
self.performanceLayout = QGridLayout();
self.performanceLayout.setAlignment(Qt.AlignTop)
self.performance.setLayout(self.performanceLayout)
self.smoke = QCheckBox()
self.smoke.setChecked(self.game.settings.perf_smoke_gen)
self.smoke.toggled.connect(self.applySettings)
self.red_alert = QCheckBox()
self.red_alert.setChecked(self.game.settings.perf_red_alert_state)
self.red_alert.toggled.connect(self.applySettings)
self.arti = QCheckBox()
self.arti.setChecked(self.game.settings.perf_artillery)
self.arti.toggled.connect(self.applySettings)
self.moving_units = QCheckBox()
self.moving_units.setChecked(self.game.settings.perf_moving_units)
self.moving_units.toggled.connect(self.applySettings)
self.infantry = QCheckBox()
self.infantry.setChecked(self.game.settings.perf_infantry)
self.infantry.toggled.connect(self.applySettings)
self.ai_parking_start = QCheckBox()
self.ai_parking_start.setChecked(self.game.settings.perf_ai_parking_start)
self.ai_parking_start.toggled.connect(self.applySettings)
self.performanceLayout.addWidget(QLabel("Smoke visual effect on frontline"), 0, 0)
self.performanceLayout.addWidget(self.smoke, 0, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("SAM starts in RED alert mode"), 1, 0)
self.performanceLayout.addWidget(self.red_alert, 1, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("Artillery strikes"), 2, 0)
self.performanceLayout.addWidget(self.arti, 2, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("Moving ground units"), 3, 0)
self.performanceLayout.addWidget(self.moving_units, 3, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("Generate infantry squads along vehicles"), 4, 0)
self.performanceLayout.addWidget(self.infantry, 4, 1, alignment=Qt.AlignRight)
self.performanceLayout.addWidget(QLabel("AI planes parking start (AI starts in flight if disabled)"), 5, 0)
self.performanceLayout.addWidget(self.ai_parking_start, 5, 1, alignment=Qt.AlignRight)
self.generatorLayout.addWidget(self.gameplay)
self.generatorLayout.addWidget(QLabel("Disabling settings below may improve performance, but will impact the overall quality of the experience."))
self.generatorLayout.addWidget(self.performance)
def initCheatLayout(self):
@@ -194,10 +229,15 @@ class QSettingsWindow(QDialog):
self.game.settings.enemy_vehicle_skill = CONST.SKILL_OPTIONS[self.enemyAASkill.currentIndex()]
self.game.settings.labels = CONST.LABELS_OPTIONS[self.difficultyLabel.currentIndex()]
self.game.settings.night_disabled = self.noNightMission.isChecked()
self.game.settings.only_player_takeoff = self.takeOffOnlyForPlayerGroup.isChecked()
self.game.settings.cold_start = self.coldStart.isChecked()
self.game.settings.supercarrier = self.supercarrier.isChecked()
self.game.settings.perf_red_alert_state = self.red_alert.isChecked()
self.game.settings.perf_smoke_gen = self.smoke.isChecked()
self.game.settings.perf_artillery = self.arti.isChecked()
self.game.settings.perf_moving_units = self.moving_units.isChecked()
self.game.settings.perf_infantry = self.infantry.isChecked()
self.game.settings.perf_ai_parking_start = self.ai_parking_start.isChecked()
GameUpdateSignal.get_instance().updateGame(self.game)
def onSelectionChanged(self):

View File

@@ -1,3 +1,4 @@
pydcs>=0.9.4
Pyside2>=5.13.0
pyinstaller==3.5
pyproj==2.6.1.post1

Binary file not shown.

View File

@@ -0,0 +1,62 @@
local unitPayloads = {
["name"] = "Bf-109K-4",
["payloads"] = {
[1] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "SC_501_SC500",
["num"] = 1,
},
},
["tasks"] = {
[1] = 34,
[2] = 31,
[3] = 30,
[4] = 32,
},
},
[2] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "SC_501_SC250",
["num"] = 1,
},
},
["tasks"] = {
[1] = 34,
[2] = 31,
[3] = 30,
[4] = 32,
},
},
[3] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 31,
},
},
[4] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "SC_501_SC500",
["num"] = 1,
},
},
["tasks"] = {
[1] = 34,
[2] = 31,
[3] = 30,
[4] = 32,
},
},
},
["tasks"] = {
},
["unitType"] = "Bf-109K-4",
}
return unitPayloads

View File

@@ -0,0 +1,61 @@
local unitPayloads = {
["name"] = "FW-190A8",
["payloads"] = {
[1] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{WGr21}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{WGr21}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{SC_250_T1_L2}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
},
},
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{SD_500_A}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
},
},
[3] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{SD_500_A}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
},
},
[4] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 31,
},
},
},
["tasks"] = {
},
["unitType"] = "FW-190A8",
}
return unitPayloads

View File

@@ -0,0 +1,70 @@
local unitPayloads = {
["name"] = "FW-190D9",
["payloads"] = {
[1] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{WGr21}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{WGr21}",
["num"] = 2,
},
},
["tasks"] = {
[1] = 11,
[2] = 10,
[3] = 32,
[4] = 31,
},
},
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "SC_501_SC500",
["num"] = 1,
},
},
["tasks"] = {
[1] = 34,
[2] = 31,
[3] = 30,
[4] = 32,
},
},
[3] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{WGr21}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{WGr21}",
["num"] = 2,
},
},
["tasks"] = {
[1] = 11,
[2] = 10,
[3] = 32,
[4] = 31,
},
},
[4] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 31,
},
},
},
["tasks"] = {
},
["unitType"] = "FW-190D9",
}
return unitPayloads

View File

@@ -73,33 +73,29 @@ local unitPayloads = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "DIS_WMD7",
["num"] = 4,
["CLSID"] = "DIS_LS_6_500",
["num"] = 6,
},
[2] = {
["CLSID"] = "DIS_LS_6_500",
["num"] = 5,
},
[3] = {
["CLSID"] = "DIS_LS_6_500",
["num"] = 3,
},
[4] = {
["CLSID"] = "DIS_LS_6_500",
["num"] = 2,
},
[5] = {
["CLSID"] = "DIS_PL-5EII",
["num"] = 1,
},
[3] = {
[6] = {
["CLSID"] = "DIS_PL-5EII",
["num"] = 7,
},
[4] = {
["CLSID"] = "DIS_GBU_12_DUAL",
["num"] = 6,
},
[5] = {
["CLSID"] = "DIS_GBU_12_DUAL",
["num"] = 2,
},
[6] = {
["CLSID"] = "DIS_GBU_16",
["num"] = 3,
},
[7] = {
["CLSID"] = "DIS_GBU_16",
["num"] = 5,
},
},
["tasks"] = {
[1] = 10,

View File

@@ -0,0 +1,49 @@
local unitPayloads = {
["name"] = "Ju-88A4",
["payloads"] = {
[1] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{LTF_5B}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{LTF_5B}",
["num"] = 3,
},
},
["tasks"] = {
[1] = 32,
},
},
[2] = {
["name"] = "CAS",
["pylons"] = {
},
["tasks"] = {
[1] = 32,
},
},
[3] = {
["name"] = "STRIKE",
["pylons"] = {
},
["tasks"] = {
[1] = 32,
},
},
[4] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 32,
},
},
},
["tasks"] = {
},
["unitType"] = "Ju-88A4",
}
return unitPayloads

View File

@@ -0,0 +1,85 @@
local unitPayloads = {
["name"] = "P-47D-30",
["payloads"] = {
[1] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{AN-M64}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{AN-M64}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{AN-M64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 11,
},
},
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{AN_M65}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{AN_M65}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{AN-M64}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 11,
},
},
[3] = {
["name"] = "ANTISTRIKE",
["pylons"] = {
},
["tasks"] = {
[1] = 11,
},
},
[4] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 11,
},
},
[5] = {
["name"] = "SEAD",
["pylons"] = {
[1] = {
["CLSID"] = "{AN_M57}",
["num"] = 3,
},
[2] = {
["CLSID"] = "{AN_M57}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{AN_M57}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 11,
},
},
},
["tasks"] = {
},
["unitType"] = "P-47D-30",
}
return unitPayloads

View File

@@ -0,0 +1,141 @@
local unitPayloads = {
["name"] = "P-51D-30-NA",
["payloads"] = {
[1] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{HVAR}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{HVAR}",
["num"] = 9,
},
[3] = {
["CLSID"] = "{HVAR}",
["num"] = 8,
},
[4] = {
["CLSID"] = "{AN-M64}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{AN-M64}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{HVAR}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{HVAR}",
["num"] = 2,
},
[8] = {
["CLSID"] = "{HVAR}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
[2] = 32,
[3] = 30,
},
},
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{HVAR}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{HVAR}",
["num"] = 9,
},
[3] = {
["CLSID"] = "{HVAR}",
["num"] = 8,
},
[4] = {
["CLSID"] = "{AN-M64}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{AN-M64}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{HVAR}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{HVAR}",
["num"] = 2,
},
[8] = {
["CLSID"] = "{HVAR}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
[2] = 32,
[3] = 30,
},
},
[3] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 31,
},
},
[4] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{HVAR}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{HVAR}",
["num"] = 9,
},
[3] = {
["CLSID"] = "{HVAR}",
["num"] = 8,
},
[4] = {
["CLSID"] = "{AN-M64}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{AN-M64}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{HVAR}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{HVAR}",
["num"] = 2,
},
[8] = {
["CLSID"] = "{HVAR}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
[2] = 32,
[3] = 30,
},
},
},
["unitType"] = "P-51D-30-NA",
}
return unitPayloads

View File

@@ -0,0 +1,133 @@
local unitPayloads = {
["name"] = "P-51D",
["payloads"] = {
[1] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{HVAR}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{HVAR}",
["num"] = 9,
},
[3] = {
["CLSID"] = "{HVAR}",
["num"] = 8,
},
[4] = {
["CLSID"] = "{AN-M64}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{AN-M64}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{HVAR}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{HVAR}",
["num"] = 2,
},
[8] = {
["CLSID"] = "{HVAR}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
[2] = 32,
[3] = 30,
},
},
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{HVAR}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{HVAR}",
["num"] = 9,
},
[3] = {
["CLSID"] = "{HVAR}",
["num"] = 8,
},
[4] = {
["CLSID"] = "{AN-M64}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{AN-M64}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{HVAR}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{HVAR}",
["num"] = 2,
},
[8] = {
["CLSID"] = "{HVAR}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
[2] = 32,
[3] = 30,
},
},
[3] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{HVAR}",
["num"] = 10,
},
[2] = {
["CLSID"] = "{HVAR}",
["num"] = 9,
},
[3] = {
["CLSID"] = "{HVAR}",
["num"] = 8,
},
[4] = {
["CLSID"] = "{AN-M64}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{AN-M64}",
["num"] = 4,
},
[6] = {
["CLSID"] = "{HVAR}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{HVAR}",
["num"] = 2,
},
[8] = {
["CLSID"] = "{HVAR}",
["num"] = 1,
},
},
["tasks"] = {
[1] = 31,
[2] = 32,
[3] = 30,
},
},
},
["unitType"] = "P-51D",
}
return unitPayloads

View File

@@ -0,0 +1,77 @@
local unitPayloads = {
["name"] = "SpitfireLFMkIX",
["payloads"] = {
[1] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 31,
},
},
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "British_GP_500LBS_Bomb_MK4_on_British_UniversalBC_MK3",
["num"] = 2,
},
[2] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_LH_Spitfire_Wing_Carrier",
["num"] = 1,
},
[3] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_RH_Spitfire_Wing_Carrier",
["num"] = 3,
},
},
["tasks"] = {
[1] = 31,
},
},
[3] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "British_GP_500LBS_Bomb_MK4_on_British_UniversalBC_MK3",
["num"] = 2,
},
[2] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_LH_Spitfire_Wing_Carrier",
["num"] = 1,
},
[3] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_RH_Spitfire_Wing_Carrier",
["num"] = 3,
},
},
["tasks"] = {
[1] = 31,
},
},
[4] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "British_GP_500LBS_Bomb_MK4_on_British_UniversalBC_MK3",
["num"] = 2,
},
[2] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_LH_Spitfire_Wing_Carrier",
["num"] = 1,
},
[3] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_RH_Spitfire_Wing_Carrier",
["num"] = 3,
},
},
["tasks"] = {
[1] = 31,
},
},
},
["tasks"] = {
},
["unitType"] = "SpitfireLFMkIX",
}
return unitPayloads

View File

@@ -0,0 +1,77 @@
local unitPayloads = {
["name"] = "SpitfireLFMkIXCW",
["payloads"] = {
[1] = {
["name"] = "CAP",
["pylons"] = {
},
["tasks"] = {
[1] = 31,
},
},
[2] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_RH_Spitfire_Wing_Carrier",
["num"] = 3,
},
[2] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_LH_Spitfire_Wing_Carrier",
["num"] = 1,
},
[3] = {
["CLSID"] = "British_GP_500LBS_Bomb_MK4_on_British_UniversalBC_MK3",
["num"] = 2,
},
},
["tasks"] = {
[1] = 31,
},
},
[3] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_RH_Spitfire_Wing_Carrier",
["num"] = 3,
},
[2] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_LH_Spitfire_Wing_Carrier",
["num"] = 1,
},
[3] = {
["CLSID"] = "British_GP_500LBS_Bomb_MK4_on_British_UniversalBC_MK3",
["num"] = 2,
},
},
["tasks"] = {
[1] = 31,
},
},
[4] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_RH_Spitfire_Wing_Carrier",
["num"] = 3,
},
[2] = {
["CLSID"] = "British_GP_250LBS_Bomb_MK4_on_LH_Spitfire_Wing_Carrier",
["num"] = 1,
},
[3] = {
["CLSID"] = "British_GP_500LBS_Bomb_MK4_on_British_UniversalBC_MK3",
["num"] = 2,
},
},
["tasks"] = {
[1] = 31,
},
},
},
["tasks"] = {
},
["unitType"] = "SpitfireLFMkIXCW",
}
return unitPayloads

View File

@@ -2,156 +2,150 @@ local unitPayloads = {
["name"] = "Su-24M",
["payloads"] = {
[1] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{D8F2C90B-887B-4B9E-9FE2-996BC9E9AF03}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{0519A264-0AB6-11d6-9193-00A0249B6F00}",
["num"] = 5,
},
[4] = {
["CLSID"] = "{D8F2C90B-887B-4B9E-9FE2-996BC9E9AF03}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 8,
},
},
["tasks"] = {
[1] = 29,
[2] = 30,
},
},
[2] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{292960BB-6518-41AC-BADA-210D65D5073C}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{292960BB-6518-41AC-BADA-210D65D5073C}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{292960BB-6518-41AC-BADA-210D65D5073C}",
["num"] = 7,
},
[4] = {
["CLSID"] = "{292960BB-6518-41AC-BADA-210D65D5073C}",
["num"] = 8,
},
[5] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 6,
},
[6] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 4,
},
[8] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 5,
},
},
["tasks"] = {
[1] = 31,
},
},
[3] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 3,
},
[4] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 4,
},
[5] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 5,
},
[6] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 6,
},
[7] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 7,
},
[8] = {
["CLSID"] = "{4203753F-8198-4E85-9924-6F8FF679F9FF}",
["num"] = 8,
},
},
["tasks"] = {
[1] = 31,
},
},
[4] = {
["name"] = "SEAD",
["pylons"] = {
[1] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 1,
},
[2] = {
["CLSID"] = "{FE382A68-8620-4AC0-BDF5-709BFE3977D7}",
["num"] = 2,
},
[3] = {
["CLSID"] = "{0519A264-0AB6-11d6-9193-00A0249B6F00}",
["num"] = 5,
},
[4] = {
["CLSID"] = "{FE382A68-8620-4AC0-BDF5-709BFE3977D7}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
[2] = {
["CLSID"] = "{E86C5AA5-6D49-4F00-AD2E-79A62D6DDE26}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{E86C5AA5-6D49-4F00-AD2E-79A62D6DDE26}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{FE382A68-8620-4AC0-BDF5-709BFE3977D7}",
["num"] = 2,
},
},
["tasks"] = {
[1] = 29,
},
},
[5] = {
[2] = {
["name"] = "STRIKE",
["pylons"] = {
[1] = {
["CLSID"] = "{KAB_1500LG_LOADOUT}",
["num"] = 7,
},
[2] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{KAB_1500LG_LOADOUT}",
["num"] = 2,
},
[5] = {
["CLSID"] = "{E2C426E3-8B10-4E09-B733-9CDC26520F48}",
["num"] = 3,
},
[6] = {
["CLSID"] = "{E2C426E3-8B10-4E09-B733-9CDC26520F48}",
["num"] = 6,
},
[7] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 4,
},
[8] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 5,
},
},
["tasks"] = {
},
},
[3] = {
["name"] = "CAP",
["pylons"] = {
[1] = {
["CLSID"] = "{B0DBC591-0F52-4F7D-AD7B-51E67725FB81}",
["num"] = 1,
["CLSID"] = "{7D7EC917-05F6-49D4-8045-61FC587DD019}",
["num"] = 7,
},
[2] = {
["CLSID"] = "{275A2855-4A79-4B2D-B082-91EA2ADF4691}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{B0DBC591-0F52-4F7D-AD7B-51E67725FB81}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{7D7EC917-05F6-49D4-8045-61FC587DD019}",
["num"] = 2,
},
},
["tasks"] = {
},
},
[4] = {
["name"] = "ANTISHIP",
["pylons"] = {
[1] = {
["CLSID"] = "{D8F2C90B-887B-4B9E-9FE2-996BC9E9AF03}",
["num"] = 7,
},
[2] = {
["CLSID"] = "{E86C5AA5-6D49-4F00-AD2E-79A62D6DDE26}",
["num"] = 8,
},
[3] = {
["CLSID"] = "{E86C5AA5-6D49-4F00-AD2E-79A62D6DDE26}",
["num"] = 1,
},
[4] = {
["CLSID"] = "{D8F2C90B-887B-4B9E-9FE2-996BC9E9AF03}",
["num"] = 2,
},
},
["tasks"] = {
},
},
[5] = {
["name"] = "CAS",
["pylons"] = {
[1] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 8,
},
[2] = {
["CLSID"] = "{6DADF342-D4BA-4D8A-B081-BA928C4AF86D}",
["num"] = 1,
},
[3] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 2,
},
[4] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 7,
},
[5] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 6,
},
[6] = {
["CLSID"] = "{3858707D-F5D5-4bbb-BDD8-ABB0530EBC7C}",
["num"] = 3,
},
[7] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 5,
},
[8] = {
["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}",
["num"] = 4,
},
},
["tasks"] = {
[1] = 32,
},
},
},

Binary file not shown.

Binary file not shown.

View File

@@ -9,7 +9,7 @@ dcs.planes.FlyingType.payload_dirs = [os.path.join(os.path.dirname(os.path.realp
mis = dcs.Mission(dcs.terrain.PersianGulf())
pos = dcs.terrain.PersianGulf().khasab().position
airgen = AircraftConflictGenerator(mis, None, None)
airgen = AircraftConflictGenerator(mis, None, None, None)
for t, uts in db.UNIT_BY_TASK.items():
if t != dcs.task.CAP and t != dcs.task.CAS:

View File

@@ -120,34 +120,13 @@ class Base:
target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count
def commit_losses(self, units_lost: typing.Dict[typing.Any, int]):
# advanced SAM sites have multiple units - this code was not at all set up to handle that
# to avoid having to restructure a bunch of upstream code, we track total destroyed units and
# use that to determine if a site was destroyed
# this can be thought of as the enemy re-distributing parts of SAM sites to keep as many
# operational as possible (pulling specific units from ...storage... to bring them back online
# if non-letal damage was done)
# in the future, I may add more depth to this (e.g. a base having a certain number of spares and tracking
# the number of pieces of each site), but for now this is what we get
sams_destroyed = {}
# we count complex SAM sites at the end - don't double count
aa_skip = [
AirDefence.SAM_SA_6_Kub_LN_2P25,
AirDefence.SAM_SA_3_S_125_LN_5P73,
AirDefence.SAM_SA_11_Buk_LN_9A310M1
]
for unit_type, count in units_lost.items():
if unit_type in db.SAM_CONVERT or unit_type in db.SAM_CONVERT['except']:
# unit is part of an advanced SAM site, which means it will fail the below check
try:
sams_destroyed[unit_type] += 1
except KeyError:
sams_destroyed[unit_type] = 1
if unit_type in self.aircraft:
target_array = self.aircraft
elif unit_type in self.armor:
target_array = self.armor
elif unit_type in self.aa and unit_type not in aa_skip:
target_array = self.aa
else:
print("Base didn't find event type {}".format(unit_type))
continue
@@ -160,22 +139,6 @@ class Base:
if target_array[unit_type] == 0:
del target_array[unit_type]
# now that we have a complete picture of the SAM sites destroyed, determine if any were destroyed
for sam_site, count in sams_destroyed.items():
dead_count = aaa.num_sam_dead(sam_site, count)
try:
modified_sam_site = db.SAM_CONVERT[sam_site]
except KeyError:
modified_sam_site = db.SAM_CONVERT[sam_site]['except']
if modified_sam_site in self.aa:
self.aa[modified_sam_site] = max(
self.aa[modified_sam_site] - dead_count,
0
)
if self.aa[modified_sam_site] == 0:
del self.aa[modified_sam_site]
def affect_strength(self, amount):
self.strength += amount
if self.strength > BASE_MAX_STRENGTH:

View File

@@ -73,11 +73,6 @@ class CaucasusTheater(ConflictTheater):
self.carrier_1.captured = True
self.batumi.captured = True
def add_controlpoint(self, point: ControlPoint, connected_to: typing.Collection[ControlPoint] = []):
point.name = " ".join(re.split(r"[ -]", point.name)[:1])
super(CaucasusTheater, self).add_controlpoint(point, connected_to=connected_to)
"""
A smaller version of the caucasus map in western georgia.
@@ -160,4 +155,51 @@ class WesternGeorgiaInverted(ConflictTheater):
self.add_controlpoint(self.carrier_1)
self.carrier_1.captured = True
self.sochi.captured = True
self.sochi.captured = True
class NorthCaucasus(ConflictTheater):
terrain = caucasus.Caucasus()
overview_image = "caumap.gif"
reference_points = {(-317948.32727306, 635639.37385346): (278.5*4, 319*4),
(-355692.3067714, 617269.96285781): (263*4, 352*4), }
landmap = load_landmap("resources\\caulandmap.p")
daytime_map = {
"dawn": (6, 9),
"day": (9, 18),
"dusk": (18, 20),
"night": (0, 5),
}
carrier_1 = ControlPoint.carrier("Carrier", mapping.Point(-305810.6875, 406399.1875))
def __init__(self, load_ground_objects=True):
super(NorthCaucasus, self).__init__()
self.kutaisi = ControlPoint.from_airport(caucasus.Kutaisi, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.vaziani = ControlPoint.from_airport(caucasus.Vaziani, LAND, SIZE_SMALL, IMPORTANCE_LOW)
self.maykop = ControlPoint.from_airport(caucasus.Maykop_Khanskaya, LAND, SIZE_LARGE, IMPORTANCE_HIGH)
self.beslan = ControlPoint.from_airport(caucasus.Beslan, LAND, SIZE_REGULAR, IMPORTANCE_LOW)
self.nalchik = ControlPoint.from_airport(caucasus.Nalchik, LAND, SIZE_REGULAR, 1.1)
self.mineralnye = ControlPoint.from_airport(caucasus.Mineralnye_Vody, LAND, SIZE_BIG, 1.3)
self.mozdok = ControlPoint.from_airport(caucasus.Mozdok, LAND, SIZE_BIG, 1.1)
self.carrier_1 = ControlPoint.carrier("Carrier", mapping.Point(-285810.6875, 496399.1875))
self.vaziani.frontline_offset = 0.5
self.vaziani.base.strength = 1
self.add_controlpoint(self.kutaisi, connected_to=[self.vaziani])
self.add_controlpoint(self.vaziani, connected_to=[self.beslan, self.kutaisi])
self.add_controlpoint(self.beslan, connected_to=[self.vaziani, self.mozdok, self.nalchik])
self.add_controlpoint(self.nalchik, connected_to=[self.beslan, self.mozdok, self.mineralnye])
self.add_controlpoint(self.mozdok, connected_to=[self.nalchik, self.beslan, self.mineralnye])
self.add_controlpoint(self.mineralnye, connected_to=[self.nalchik, self.mozdok, self.maykop])
self.add_controlpoint(self.maykop, connected_to=[self.mineralnye])
self.add_controlpoint(self.carrier_1, connected_to=[])
self.carrier_1.captured = True
self.vaziani.captured = True
self.kutaisi.captured = True

View File

@@ -72,6 +72,14 @@ class ConflictTheater:
self.controlpoints.append(point)
def find_ground_objects_by_obj_name(self, obj_name):
found = []
for cp in self.controlpoints:
for g in cp.ground_objects:
if g.obj_name == obj_name:
found.append(g)
return found
def is_in_sea(self, point: Point) -> bool:
if not self.landmap:
return False

View File

@@ -27,6 +27,7 @@ class ControlPoint:
full_name = None # type: str
base = None # type: theater.base.Base
at = None # type: db.StartPosition
icls = 1
connected_points = None # type: typing.List[ControlPoint]
ground_objects = None # type: typing.List[TheaterGroundObject]
@@ -36,6 +37,9 @@ class ControlPoint:
frontline_offset = 0.0
cptype: ControlPointType = None
ICLS_counter = 1
alt = 0
def __init__(self, id: int, name: str, position: Point, at, radials: typing.Collection[int], size: int, importance: float,
has_frontline=True, cptype=ControlPointType.AIRBASE):
import theater.base
@@ -60,20 +64,26 @@ class ControlPoint:
self.tacanY = False
self.tacanN = None
self.tacanI = "TAC"
self.icls = 0
self.airport = None
@classmethod
def from_airport(cls, airport: Airport, radials: typing.Collection[int], size: int, importance: float, has_frontline=True):
assert airport
return cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline, cptype=ControlPointType.AIRBASE)
obj = cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline, cptype=ControlPointType.AIRBASE)
obj.airport = airport()
return obj
@classmethod
def carrier(cls, name: str, at: Point, id: int = 1001):
import theater.conflicttheater
cp = cls(id, name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1,
has_frontline=False, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP)
cp.tacanY = random.choice([True, False])
cp.tacanY = False
cp.tacanN = random.randint(26, 49)
cp.tacanI = random.choice(["STE", "CVN", "CVH", "CCV", "ACC", "ARC", "GER", "ABR", "LIN", "TRU"])
ControlPoint.ICLS_counter = ControlPoint.ICLS_counter + 1
cp.icls = ControlPoint.ICLS_counter
return cp
@classmethod
@@ -81,11 +91,20 @@ class ControlPoint:
import theater.conflicttheater
cp = cls(id, name, at, at, theater.conflicttheater.LAND, theater.conflicttheater.SIZE_SMALL, 1,
has_frontline=False, cptype=ControlPointType.LHA_GROUP)
cp.tacanY = random.choice([True, False])
cp.tacanY = False
cp.tacanN = random.randint(1,25)
cp.tacanI = random.choice(["LHD", "LHA", "LHB", "LHC", "LHD", "LDS"])
return cp
@property
def heading(self):
if self.cptype == ControlPointType.AIRBASE:
return self.airport.runways[0].heading
elif self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP]:
return 0 # TODO compute heading
else:
return 0
def __str__(self):
return self.name
@@ -170,3 +189,10 @@ class ControlPoint:
return closest_radial
def find_ground_objects_by_obj_name(self, obj_name):
found = []
for g in self.ground_objects:
if g.obj_name == obj_name:
found.append(g)
return found

View File

@@ -112,11 +112,15 @@ def generate_groundobjects(theater: ConflictTheater, game):
if "lhanames" in db.FACTIONS[faction]:
cp.name = random.choice(db.FACTIONS[faction]["lhanames"])
else:
for i in range(random.randint(2,6)):
point = find_location(True, cp.position, theater, 1000, 2800, [])
print("GENERATE BASE DEFENSE")
point = find_location(True, cp.position, theater, 1000, 2800, [], True)
print(point)
if point is None:
print("Couldn't find point for {}".format(cp))
print("Couldn't find point for {} base defense".format(cp))
continue
group_id = group_id + 1
@@ -131,18 +135,7 @@ def generate_groundobjects(theater: ConflictTheater, game):
g.heading = 0
g.position = Point(point.x, point.y)
if i == 0:
group = generate_armor_group(faction, game, g)
elif i == 1 and random.randint(0,1) == 0:
group = generate_anti_air_group(game, cp, g, faction)
elif random.randint(0, 2) == 1:
group = generate_shorad_group(game, cp, g, faction)
else:
group = generate_armor_group(faction, game, g)
g.groups = []
if group is not None:
g.groups.append(group)
generate_airbase_defense_group(i, g, faction, game, cp)
cp.ground_objects.append(g)
print("---------------------------")
@@ -151,7 +144,27 @@ def generate_groundobjects(theater: ConflictTheater, game):
print(ground_object.groups)
def find_location(on_ground, near, theater, min, max, others) -> typing.Optional[Point]:
def generate_airbase_defense_group(airbase_defense_group_id, ground_obj:TheaterGroundObject, faction, game, cp):
print("GENERATE AIR DEFENSE GROUP")
print(faction)
print(airbase_defense_group_id)
if airbase_defense_group_id == 0:
group = generate_armor_group(faction, game, ground_obj)
elif airbase_defense_group_id == 1 and random.randint(0, 1) == 0:
group = generate_anti_air_group(game, cp, ground_obj, faction)
elif random.randint(0, 2) == 1:
group = generate_shorad_group(game, cp, ground_obj, faction)
else:
group = generate_armor_group(faction, game, ground_obj)
ground_obj.groups = []
if group is not None:
ground_obj.groups.append(group)
def find_location(on_ground, near, theater, min, max, others, is_base_defense=False) -> typing.Optional[Point]:
"""
Find a valid ground object location
:param on_ground: Whether it should be on ground or on sea (True = on ground)
@@ -163,7 +176,7 @@ def find_location(on_ground, near, theater, min, max, others) -> typing.Optional
:return:
"""
point = None
for _ in range(1000):
for _ in range(300):
# Check if on land or sea
p = near.random_point_within(max, min)
@@ -189,6 +202,7 @@ def find_location(on_ground, near, theater, min, max, others) -> typing.Optional
if point:
for other in theater.controlpoints:
if is_base_defense: break
if other.position != near:
if point is None:
break
@@ -254,7 +268,7 @@ def generate_cp_ground_points(cp: ControlPoint, theater, game, group_id, templat
g.group_id = group_id
g.object_id = object_id
g.cp_id = cp.id
g.airbase_gorup = False
g.airbase_group = False
g.obj_name = obj_name
g.dcs_identifier = object["type"]

View File

@@ -30,10 +30,12 @@ ABBREV_NAME = {
}
CATEGORY_MAP = {
# Special cases
"CARRIER": ["CARRIER"],
"LHA": ["LHA"],
"aa": ["AA"],
"power": ["Workshop A", "Electric power box", "Garage small A"],
# Buildings
"power": ["Workshop A", "Electric power box", "Garage small A", "Farm B", "Repair workshop", "Garage B"],
"warehouse": ["Warehouse", "Hangar A"],
"fuel": ["Tank", "Tank 2", "Tank 3", "Fuel tank"],
"ammo": [".Ammunition depot", "Hangar B"],
@@ -42,6 +44,7 @@ CATEGORY_MAP = {
"factory": ["Tech combine", "Tech hangar A"],
"comms": ["TV tower", "Comms tower M"],
"oil": ["Oil platform"],
"derrick": ["Oil derrick", "Pump station", "Subsidiary structure 2"],
}

View File

@@ -1,132 +0,0 @@
"""
This utility classes provides methods to check players installed DCS environment.
TODO : add method 'is_using_open_beta', 'is_using_stable'
TODO : [NICE to have] add method to check list of installed DCS modules (could be done either through window registry, or through filesystem analysis)
"""
import winreg
import os
def is_using_dcs_steam_edition():
"""
Check if DCS World : Steam Edition version is installed on this computer
:return True if DCS Steam edition is installed,
-1 if DCS Steam Edition is registered in Steam apps but not installed,
False if never installed in Steam
"""
try:
# Note : Steam App ID for DCS World is 223750
dcs_steam_app_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Valve\\Steam\\Apps\\223750")
installed = winreg.QueryValueEx(dcs_steam_app_key, "Installed")
winreg.CloseKey(dcs_steam_app_key)
if installed[0] == 1:
return True
else:
return False
except FileNotFoundError as fnfe:
return False
def is_using_dcs_standalone_edition():
"""
Check if DCS World standalone edition is installed on this computer
:return True if Standalone is installed, False if it is not
"""
try:
dcs_path_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Eagle Dynamics\\DCS World")
winreg.CloseKey(dcs_path_key)
return True
except FileNotFoundError as fnfe:
return False
def _find_steam_directory():
"""
Get the Steam install directory for this computer from registry
:return Steam installation path
"""
try:
steam_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Valve\\Steam")
path = winreg.QueryValueEx(steam_key, "SteamPath")[0]
winreg.CloseKey(steam_key)
return path
except FileNotFoundError as fnfe:
print(fnfe)
return ""
def _get_steam_library_folders():
"""
Get the installation directory for Steam games
:return List of Steam library folders where games can be installed
"""
try:
steam_dir = _find_steam_directory()
"""
For reference here is what the vdf file is supposed to look like :
"LibraryFolders"
{
"TimeNextStatsReport" "1561832478"
"ContentStatsID" "-158337411110787451"
"1" "D:\\Games\\Steam"
"2" "E:\\Steam"
}
"""
vdf_file_location = steam_dir + os.path.sep + "steamapps" + os.path.sep + "libraryfolders.vdf"
with open(vdf_file_location) as adf_file:
paths = [l.split("\"")[3] for l in adf_file.readlines()[1:] if ':\\\\' in l]
return paths
except Exception as e:
print(e)
return []
def _find_steam_dcs_directory():
"""
Find the DCS install directory for DCS World Steam Edition
:return: Install directory as string, empty string if not found
"""
for library_folder in _get_steam_library_folders():
folder = library_folder + os.path.sep + "steamapps" + os.path.sep + "common" + os.path.sep + "DCSWorld"
if os.path.isdir(folder):
return folder + os.path.sep
return ""
def get_dcs_install_directory():
"""
Get the DCS World install directory for this computer
:return DCS World install directory
"""
if is_using_dcs_standalone_edition():
try:
dcs_path_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Eagle Dynamics\\DCS World")
path = winreg.QueryValueEx(dcs_path_key, "Path")
dcs_dir = path[0] + os.path.sep
winreg.CloseKey(dcs_path_key)
return dcs_dir
except Exception as e:
print("Couldn't detect DCS World installation folder")
return ""
elif is_using_dcs_steam_edition():
return _find_steam_dcs_directory()
else:
print("Couldn't detect any installed DCS World version")
def get_dcs_saved_games_directory():
"""
Get the save game directory for DCS World
:return: Save game directory as string
"""
return os.path.join(os.path.expanduser("~"), "Saved Games", "DCS")
if __name__ == "__main__":
print("Using STEAM Edition : " + str(is_using_dcs_steam_edition()))
print("Using Standalone Edition : " + str(is_using_dcs_standalone_edition()))
print("DCS Installation directory : " + get_dcs_install_directory())
print("DCS saved games directory : " + get_dcs_saved_games_directory())

View File

@@ -47,6 +47,8 @@ class Debriefing:
self.dead_aircraft = []
self.dead_units = []
self.dead_aaa_groups = []
self.dead_buildings = []
for aircraft in self.killed_aircrafts:
try:
@@ -70,10 +72,36 @@ class Debriefing:
except Exception as e:
print(e)
for unit in self.killed_ground_units:
for cp in game.theater.controlpoints:
print(cp.name)
print(cp.captured)
if cp.captured:
country = self.player_country_id
else:
country = self.enemy_country_id
player_unit = (country == self.player_country_id)
for i, ground_object in enumerate(cp.ground_objects):
print(unit)
print(ground_object.string_identifier)
if ground_object.matches_string_identifier(unit):
unit = DebriefingDeadUnitInfo(country, player_unit, ground_object.dcs_identifier)
self.dead_buildings.append(unit)
elif ground_object.dcs_identifier in ["AA", "CARRIER", "LHA"]:
for g in ground_object.groups:
for u in g.units:
if u.name == unit:
unit = DebriefingDeadUnitInfo(country, player_unit, db.unit_type_from_name(u.type))
self.dead_units.append(unit)
self.player_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.player_country_id]
self.enemy_dead_aircraft = [a for a in self.dead_aircraft if a.country_id == self.enemy_country_id]
self.player_dead_units = [a for a in self.dead_units if a.country_id == self.player_country_id]
self.enemy_dead_units = [a for a in self.dead_units if a.country_id == self.enemy_country_id]
self.player_dead_buildings = [a for a in self.dead_buildings if a.country_id == self.player_country_id]
self.enemy_dead_buildings = [a for a in self.dead_buildings if a.country_id == self.enemy_country_id]
print(self.player_dead_aircraft)
print(self.enemy_dead_aircraft)
@@ -108,10 +136,27 @@ class Debriefing:
else:
self.enemy_dead_units_dict[a.type] = 1
self.player_dead_buildings_dict = {}
for a in self.player_dead_buildings:
if a.type in self.player_dead_buildings_dict.keys():
self.player_dead_buildings_dict[a.type] = self.player_dead_buildings_dict[a.type] + 1
else:
self.player_dead_buildings_dict[a.type] = 1
self.enemy_dead_buildings_dict = {}
for a in self.enemy_dead_buildings:
if a.type in self.enemy_dead_buildings_dict.keys():
self.enemy_dead_buildings_dict[a.type] = self.enemy_dead_buildings_dict[a.type] + 1
else:
self.enemy_dead_buildings_dict[a.type] = 1
print("DEBRIEFING PRE PROCESS")
print(self.player_dead_aircraft_dict)
print(self.enemy_dead_aircraft_dict)
print(self.player_dead_units_dict)
print(self.enemy_dead_units_dict)
print(self.player_dead_buildings_dict)
print(self.enemy_dead_buildings_dict)
def _poll_new_debriefing_log(callback: typing.Callable, game):

View File

@@ -0,0 +1,99 @@
import json
import os
from shutil import copyfile
import dcs
from userdata import persistency
global __dcs_saved_game_directory
global __dcs_installation_directory
PREFERENCES_FILE_PATH = "liberation_preferences.json"
def init():
global __dcs_saved_game_directory
global __dcs_installation_directory
if os.path.isfile(PREFERENCES_FILE_PATH):
try:
with(open(PREFERENCES_FILE_PATH)) as prefs:
pref_data = json.loads(prefs.read())
__dcs_saved_game_directory = pref_data["saved_game_dir"]
__dcs_installation_directory = pref_data["dcs_install_dir"]
is_first_start = False
except:
__dcs_saved_game_directory = ""
__dcs_installation_directory = ""
is_first_start = True
else:
try:
__dcs_saved_game_directory = dcs.installation.get_dcs_saved_games_directory()
if os.path.exists(__dcs_saved_game_directory + ".openbeta"):
__dcs_saved_game_directory = dcs.installation.get_dcs_saved_games_directory() + ".openbeta"
except:
__dcs_saved_game_directory = ""
try:
__dcs_installation_directory = dcs.installation.get_dcs_install_directory()
except:
__dcs_installation_directory = ""
is_first_start = True
persistency.setup(__dcs_saved_game_directory)
return is_first_start
def setup(saved_game_dir, install_dir):
global __dcs_saved_game_directory
global __dcs_installation_directory
__dcs_saved_game_directory = saved_game_dir
__dcs_installation_directory = install_dir
persistency.setup(__dcs_saved_game_directory)
def save_config():
global __dcs_saved_game_directory
global __dcs_installation_directory
pref_data = {"saved_game_dir": __dcs_saved_game_directory,
"dcs_install_dir": __dcs_installation_directory}
with(open(PREFERENCES_FILE_PATH, "w")) as prefs:
prefs.write(json.dumps(pref_data))
def get_dcs_install_directory():
global __dcs_installation_directory
return __dcs_installation_directory
def get_saved_game_dir():
global __dcs_saved_game_directory
return __dcs_saved_game_directory
def replace_mission_scripting_file():
install_dir = get_dcs_install_directory()
mission_scripting_path = os.path.join(install_dir, "Scripts", "MissionScripting.lua")
liberation_scripting_path = "./resources/scripts/MissionScripting.lua"
backup_scripting_path = "./resources/scripts/MissionScripting.original.lua"
if os.path.isfile(mission_scripting_path):
with open(mission_scripting_path, "r") as ms:
current_file_content = ms.read()
with open(liberation_scripting_path, "r") as libe_ms:
liberation_file_content = libe_ms.read()
# Save original file
if current_file_content != liberation_file_content:
copyfile(mission_scripting_path, backup_scripting_path)
# Replace DCS file
copyfile(liberation_scripting_path, mission_scripting_path)
def restore_original_mission_scripting():
install_dir = get_dcs_install_directory()
mission_scripting_path = os.path.join(install_dir, "Scripts", "MissionScripting.lua")
backup_scripting_path = "./resources/scripts/MissionScripting.original.lua"
if os.path.isfile(backup_scripting_path) and os.path.isfile(mission_scripting_path):
copyfile(backup_scripting_path, mission_scripting_path)

View File

@@ -46,4 +46,4 @@ else:
logging.basicConfig(stream=log_stream, level=logging.INFO)
Tk.report_callback_exception = _handle_exception
logging.info("DCS Libration {}".format(_version_string))
logging.info("DCS Liberation {}".format(_version_string))

View File

@@ -2,9 +2,6 @@ import logging
import os
import pickle
import shutil
import sys
from dcs import installation
_dcs_saved_game_folder = None # type: str
@@ -17,12 +14,7 @@ def setup(user_folder: str):
def base_path() -> str:
global _dcs_saved_game_folder
assert _dcs_saved_game_folder
openbeta_path = _dcs_saved_game_folder + ".openbeta"
if os.path.exists(openbeta_path):
return openbeta_path # For standalone openbeta users
else:
return _dcs_saved_game_folder # For standalone stable users & steam users (any branch)
return _dcs_saved_game_folder
def _save_file() -> str:
@@ -46,7 +38,12 @@ def restore_game():
return None
with open(_save_file(), "rb") as f:
return pickle.load(f)
try:
save = pickle.load(f)
return save
except:
print("Invalid Save game")
return None
def save_game(game) -> bool: