From dcaa390d24b685d566501ef07404658df9a679c2 Mon Sep 17 00:00:00 2001 From: Khopa Date: Wed, 21 Oct 2020 21:40:31 +0200 Subject: [PATCH 1/9] Added support for Su-57 mod by Cubanace --- game/db.py | 7 + game/factions/russia_2020.py | 117 ++ gen/flights/ai_flight_planner_db.py | 3 + pydcs_extensions/su57/su57.py | 1736 +++++++++++++++++++++ resources/customized_payloads/Su-57.lua | 289 ++++ resources/ui/units/aircrafts/Su-57_24.jpg | Bin 0 -> 1201 bytes 6 files changed, 2152 insertions(+) create mode 100644 game/factions/russia_2020.py create mode 100644 pydcs_extensions/su57/su57.py create mode 100644 resources/customized_payloads/Su-57.lua create mode 100644 resources/ui/units/aircrafts/Su-57_24.jpg diff --git a/game/db.py b/game/db.py index 97c3c4fc..432d23dc 100644 --- a/game/db.py +++ b/game/db.py @@ -190,6 +190,7 @@ from game.factions.russia_1965 import Russia_1965 from game.factions.russia_1975 import Russia_1975 from game.factions.russia_1990 import Russia_1990 from game.factions.russia_2010 import Russia_2010 +from game.factions.russia_2020 import Russia_2020 from game.factions.spain_1990 import Spain_1990 from game.factions.sweden_1990 import Sweden_1990 from game.factions.syria import ( @@ -216,11 +217,13 @@ from game.factions.usa_2005 import USA_2005 from pydcs_extensions.a4ec.a4ec import A_4E_C from pydcs_extensions.mb339.mb339 import MB_339PAN from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M +from pydcs_extensions.su57.su57 import Su_57 plane_map["A-4E-C"] = A_4E_C plane_map["MB-339PAN"] = MB_339PAN plane_map["Rafale_M"] = Rafale_M plane_map["Rafale_A_S"] = Rafale_A_S +plane_map["Su-57"] = Su_57 vehicle_map["FieldHL"] = frenchpack._FIELD_HIDE vehicle_map["HARRIERH"] = frenchpack._FIELD_HIDE_SMALL @@ -297,6 +300,7 @@ PRICES = { J_11A: 26, JF_17: 20, Su_30: 24, + Su_57: 40, SpitfireLFMkIX: 14, SpitfireLFMkIXCW: 14, @@ -608,6 +612,7 @@ UNIT_BY_TASK = { F_5E_3, Su_27, Su_33, + Su_57, MiG_19P, MiG_21Bis, MiG_23MLD, @@ -975,6 +980,7 @@ FACTIONS: Dict[str, Dict[str, Any]] = { "Russia 1975": Russia_1975, "Russia 1990": Russia_1990, "Russia 2010": Russia_2010, + "Russia 2020 (Modded)": Russia_2020, "France 1995": France_1995, "France 2005": France_2005, @@ -1135,6 +1141,7 @@ PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = { Su_24M:COMMON_OVERRIDE, Su_30: COMMON_OVERRIDE, Su_34: COMMON_OVERRIDE, + Su_57: COMMON_OVERRIDE, MiG_23MLD: COMMON_OVERRIDE, MiG_27K: COMMON_OVERRIDE, Tornado_GR4: COMMON_OVERRIDE, diff --git a/game/factions/russia_2020.py b/game/factions/russia_2020.py new file mode 100644 index 00000000..6cc60bc1 --- /dev/null +++ b/game/factions/russia_2020.py @@ -0,0 +1,117 @@ +from dcs.helicopters import ( + Ka_50, + Mi_24V, + Mi_28N, + Mi_8MT, +) +from dcs.planes import ( + A_50, + An_26B, + An_30M, + IL_76MD, + IL_78M, + L_39ZA, + MiG_29S, + MiG_31, + Su_24M, + Su_25, + Su_25T, + Su_27, + Su_30, + Su_33, + Su_34, + Yak_40, +) +from dcs.ships import ( + Bulk_cargo_ship_Yakushev, + CV_1143_5_Admiral_Kuznetsov, + Dry_cargo_ship_Ivanov, + FF_1135M_Rezky, + FSG_1241_1MP_Molniya, + Tanker_Elnya_160, +) +from dcs.vehicles import ( + AirDefence, + Armor, + Artillery, + Infantry, + Unarmed, +) + +from pydcs_extensions.su57.su57 import Su_57 + +Russia_2020 = { + "country": "Russia", + "side": "red", + "units": [ + + Su_27, + Su_30, + Su_33, + MiG_29S, + MiG_31, + Su_57, + + Su_25, + Su_25T, + Su_34, + Su_24M, + L_39ZA, + + IL_76MD, + IL_78M, + An_26B, + An_30M, + Yak_40, + A_50, + + Ka_50, + Mi_8MT, + Mi_24V, + Mi_28N, + + AirDefence.SAM_SA_19_Tunguska_2S6, + AirDefence.SAM_SA_11_Buk_LN_9A310M1, + AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + + Armor.APC_BTR_80, + Armor.MBT_T_90, + Armor.MBT_T_80U, + Armor.MBT_T_72B, + Armor.IFV_BMP_1, + Armor.IFV_BMP_2, + Armor.IFV_BMP_3, + + Artillery.MLRS_9K57_Uragan_BM_27, + Artillery.SPH_2S19_Msta, + + Unarmed.Transport_Ural_375, + Unarmed.Transport_UAZ_469, + + CV_1143_5_Admiral_Kuznetsov, + Bulk_cargo_ship_Yakushev, + Dry_cargo_ship_Ivanov, + Tanker_Elnya_160, + + # Infantry squad + Infantry.Paratrooper_AKS, + Infantry.Infantry_Soldier_Rus, + Infantry.Paratrooper_RPG_16, + ], + "shorad":[ + AirDefence.SAM_SA_19_Tunguska_2S6, + AirDefence.SAM_SA_13_Strela_10M3_9A35M3 + ], "aircraft_carrier": [ + CV_1143_5_Admiral_Kuznetsov, + ], "carrier_names": [ + "Admiral Kuznetov" + ], "destroyer": [ + FF_1135M_Rezky, + ], "cruiser": [ + FSG_1241_1MP_Molniya, + ], "boat": [ + "RussianNavyGroupGenerator", "KiloSubGroupGenerator" + ], "requirements": { + "SU-57 Felon By CubanAce Simulations": "https://www.digitalcombatsimulator.com/fr/files/2539621/" + } +} diff --git a/gen/flights/ai_flight_planner_db.py b/gen/flights/ai_flight_planner_db.py index 2baadf7d..02b21f40 100644 --- a/gen/flights/ai_flight_planner_db.py +++ b/gen/flights/ai_flight_planner_db.py @@ -83,6 +83,7 @@ from pydcs_extensions.rafale.rafale import Rafale_A_S, Rafale_M # TODO: These lists really ought to be era (faction) dependent. # Factions which have F-5s, F-86s, and A-4s will should prefer F-5s for CAP, but # factions that also have F-4s should not. +from pydcs_extensions.su57.su57 import Su_57 INTERCEPT_CAPABLE = [ MiG_21Bis, @@ -120,6 +121,7 @@ CAP_CAPABLE = [ JF_17, Su_30, Su_33, + Su_57, M_2000C, Mirage_2000_5, @@ -169,6 +171,7 @@ CAP_PREFERRED = [ J_11A, Su_30, Su_33, + Su_57, M_2000C, Mirage_2000_5, diff --git a/pydcs_extensions/su57/su57.py b/pydcs_extensions/su57/su57.py new file mode 100644 index 00000000..a586222e --- /dev/null +++ b/pydcs_extensions/su57/su57.py @@ -0,0 +1,1736 @@ +from enum import Enum + +from dcs import task +from dcs.planes import PlaneType +from dcs.weapons_data import Weapons + + +class Su57Weapons: + Kh_59MK2 = {"clsid": "{KH_59MK2}", "name": "Kh-59MK2", "weight": None} + RVV_AE = {"clsid": "{RVV-AE}", "name": "RVV-AE", "weight": 250} + RVV_BD = {"clsid": "{RVV-BD}", "name": "RVV-BD", "weight": 600} + RVV_L = {"clsid": "{RVV-L}", "name": "RVV-L", "weight": 748} + RVV_M = {"clsid": "{RVV-M}", "name": "RVV-M", "weight": 190} + Su_57_Fuel_Tank = {"clsid": "{SU_57Tank}", "name": "Su-57 Fuel Tank", "weight": 1561.421} + + +class Su_57(PlaneType): + id = "Su-57" + flyable = True + height = 4.074 + width = 13.95 + length = 19.008 + fuel_max = 10300 + max_speed = 2499.984 + chaff = 100 + flare = 96 + charge_total = 200 + chaff_charge_size = 1 + flare_charge_size = 1 + category = "Interceptor" #{78EFB7A2-FD52-4b57-A6A6-3BF0E1D6555F} + + class Liveries: + + class USSR(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Georgia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Venezuela(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Australia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Israel(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Combined_Joint_Task_Forces_Blue(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Sudan(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Norway(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Romania(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Iran(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Ukraine(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Libya(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Belgium(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Slovakia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Greece(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class UK(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Third_Reich(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Hungary(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Abkhazia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Morocco(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class United_Nations_Peacekeepers(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Switzerland(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class SouthOssetia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Vietnam(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class China(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Yemen(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Kuwait(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Serbia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Oman(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class India(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Egypt(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class TheNetherlands(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Poland(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Syria(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Finland(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Kazakhstan(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Denmark(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Sweden(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Croatia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class CzechRepublic(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class GDR(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Yugoslavia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Bulgaria(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class SouthKorea(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Tunisia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Combined_Joint_Task_Forces_Red(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Lebanon(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Portugal(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Cuba(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Insurgents(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class SaudiArabia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class France(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class USA(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Honduras(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Qatar(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Russia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class United_Arab_Emirates(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Italian_Social_Republi(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Austria(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Bahrain(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Italy(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Chile(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Turkey(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Philippines(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Algeria(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Pakistan(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Malaysia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Indonesia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Iraq(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Germany(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class South_Africa(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Jordan(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Mexico(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class USAFAggressors(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Brazil(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Spain(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Belarus(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Canada(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class NorthKorea(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Ethiopia(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Japan(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Thailand(Enum): + _22 = "22" + _20 = "20" + _24 = "24" + _23 = "23" + _25 = "25" + _26 = "26" + _21 = "21" + _13 = "13" + _3 = "3" + _14 = "14" + _10 = "10" + _11 = "11" + _6 = "6" + _12 = "12" + _8 = "8" + _15 = "15" + + class Pylon1: + R_73 = (1, Weapons.R_73) + RVV_AE = (1, Su57Weapons.RVV_AE) + RVV_M = (1, Su57Weapons.RVV_M) + Smoke_Generator___red = (1, Weapons.Smoke_Generator___red) + Smoke_Generator___green = (1, Weapons.Smoke_Generator___green) + Smoke_Generator___blue = (1, Weapons.Smoke_Generator___blue) + Smoke_Generator___white = (1, Weapons.Smoke_Generator___white) + Smoke_Generator___yellow = (1, Weapons.Smoke_Generator___yellow) + Smoke_Generator___orange = (1, Weapons.Smoke_Generator___orange) + + class Pylon2: + R_27R = (2, Weapons.R_27R) + R_27ER = (2, Weapons.R_27ER) + R_27T = (2, Weapons.R_27T) + R_27ET = (2, Weapons.R_27ET) + R_77 = (2, Weapons.R_77) + R_73 = (2, Weapons.R_73) + Kh_31P = (2, Weapons.Kh_31P) + Kh_31A = (2, Weapons.Kh_31A) + Kh_29L = (2, Weapons.Kh_29L) + Kh_29T = (2, Weapons.Kh_29T) + Kh_59M = (2, Weapons.Kh_59M) + MER_6_FAB_100 = (2, Weapons.MER_6_FAB_100) + B_8M1___20_S_8KOM = (2, Weapons.B_8M1___20_S_8KOM) + B_13L___5_S_13_OF = (2, Weapons.B_13L___5_S_13_OF) + S_25_OFM = (2, Weapons.S_25_OFM) + BetAB_500 = (2, Weapons.BetAB_500) + KMGU_2___96_AO_2_5RT = (2, Weapons.KMGU_2___96_AO_2_5RT) + KMGU_2___96_PTAB_2_5KO = (2, Weapons.KMGU_2___96_PTAB_2_5KO) + FAB_250 = (2, Weapons.FAB_250) + RBK_250_PTAB_2_5M = (2, Weapons.RBK_250_PTAB_2_5M) + FAB_500_M62 = (2, Weapons.FAB_500_M62) + RBK_500_255_PTAB_10_5 = (2, Weapons.RBK_500_255_PTAB_10_5) + KAB_500L = (2, Weapons.KAB_500L) + KAB_500kr = (2, Weapons.KAB_500kr) + FAB_1500_M54 = (2, Weapons.FAB_1500_M54) + KAB_1500L = (2, Weapons.KAB_1500L) + MER_6_FAB_250 = (2, Weapons.MER_6_FAB_250) + RVV_BD = (2, Su57Weapons.RVV_BD) + RVV_AE = (2, Su57Weapons.RVV_AE) + RVV_M = (2, Su57Weapons.RVV_M) + RVV_L = (2, Su57Weapons.RVV_L) + Fuel_tank_800L_Wing = (2, Weapons.Fuel_tank_800L_Wing) + RN_28 = (2, Weapons.RN_28) + Su_57_Fuel_Tank = (2, Su57Weapons.Su_57_Fuel_Tank) + Kh_59MK2 = (2, Su57Weapons.Kh_59MK2) + Smoke_Generator___red = (2, Weapons.Smoke_Generator___red) + Smoke_Generator___green = (2, Weapons.Smoke_Generator___green) + Smoke_Generator___blue = (2, Weapons.Smoke_Generator___blue) + Smoke_Generator___white = (2, Weapons.Smoke_Generator___white) + Smoke_Generator___yellow = (2, Weapons.Smoke_Generator___yellow) + Smoke_Generator___orange = (2, Weapons.Smoke_Generator___orange) + + class Pylon3: + R_73 = (3, Weapons.R_73) + Smoke_Generator___red = (3, Weapons.Smoke_Generator___red) + Smoke_Generator___green = (3, Weapons.Smoke_Generator___green) + Smoke_Generator___blue = (3, Weapons.Smoke_Generator___blue) + Smoke_Generator___white = (3, Weapons.Smoke_Generator___white) + Smoke_Generator___yellow = (3, Weapons.Smoke_Generator___yellow) + Smoke_Generator___orange = (3, Weapons.Smoke_Generator___orange) + + class Pylon4: + R_27R = (4, Weapons.R_27R) + R_27ER = (4, Weapons.R_27ER) + R_27T = (4, Weapons.R_27T) + R_27ET = (4, Weapons.R_27ET) + R_77 = (4, Weapons.R_77) + R_73 = (4, Weapons.R_73) + Kh_31P = (4, Weapons.Kh_31P) + Kh_31A = (4, Weapons.Kh_31A) + Kh_29L = (4, Weapons.Kh_29L) + Kh_29T = (4, Weapons.Kh_29T) + Kh_59M = (4, Weapons.Kh_59M) + MER_6_FAB_100 = (4, Weapons.MER_6_FAB_100) + B_8M1___20_S_8KOM = (4, Weapons.B_8M1___20_S_8KOM) + B_13L___5_S_13_OF = (4, Weapons.B_13L___5_S_13_OF) + S_25_OFM = (4, Weapons.S_25_OFM) + BetAB_500 = (4, Weapons.BetAB_500) + KMGU_2___96_AO_2_5RT = (4, Weapons.KMGU_2___96_AO_2_5RT) + KMGU_2___96_PTAB_2_5KO = (4, Weapons.KMGU_2___96_PTAB_2_5KO) + FAB_250 = (4, Weapons.FAB_250) + RBK_250_PTAB_2_5M = (4, Weapons.RBK_250_PTAB_2_5M) + FAB_500_M62 = (4, Weapons.FAB_500_M62) + RBK_500_255_PTAB_10_5 = (4, Weapons.RBK_500_255_PTAB_10_5) + KAB_500L = (4, Weapons.KAB_500L) + KAB_500kr = (4, Weapons.KAB_500kr) + FAB_1500_M54 = (4, Weapons.FAB_1500_M54) + KAB_1500L = (4, Weapons.KAB_1500L) + MER_6_FAB_250 = (4, Weapons.MER_6_FAB_250) + RVV_BD = (4, Su57Weapons.RVV_BD) + RVV_AE = (4, Su57Weapons.RVV_AE) + RVV_M = (4, Su57Weapons.RVV_M) + RVV_L = (4, Su57Weapons.RVV_L) + RN_28 = (4, Weapons.RN_28) + Su_57_Fuel_Tank = (4, Su57Weapons.Su_57_Fuel_Tank) + Kh_59MK2 = (4, Su57Weapons.Kh_59MK2) + + class Pylon5: + R_77 = (5, Weapons.R_77) + RVV_AE = (5, Su57Weapons.RVV_AE) + RVV_M = (5, Su57Weapons.RVV_M) + Kh_59MK2 = (5, Su57Weapons.Kh_59MK2) + + class Pylon6: + R_77 = (6, Weapons.R_77) + RVV_AE = (6, Su57Weapons.RVV_AE) + RVV_M = (6, Su57Weapons.RVV_M) + Kh_59MK2 = (6, Su57Weapons.Kh_59MK2) + + class Pylon7: + R_77 = (7, Weapons.R_77) + RVV_AE = (7, Su57Weapons.RVV_AE) + RVV_M = (7, Su57Weapons.RVV_M) + Kh_59MK2 = (7, Su57Weapons.Kh_59MK2) + + class Pylon8: + R_77 = (8, Weapons.R_77) + RVV_AE = (8, Su57Weapons.RVV_AE) + RVV_M = (8, Su57Weapons.RVV_M) + Kh_59MK2 = (8, Su57Weapons.Kh_59MK2) + + class Pylon9: + R_27R = (9, Weapons.R_27R) + R_27ER = (9, Weapons.R_27ER) + R_27T = (9, Weapons.R_27T) + R_27ET = (9, Weapons.R_27ET) + R_77 = (9, Weapons.R_77) + R_73 = (9, Weapons.R_73) + Kh_31P = (9, Weapons.Kh_31P) + Kh_31A = (9, Weapons.Kh_31A) + Kh_29L = (9, Weapons.Kh_29L) + Kh_29T = (9, Weapons.Kh_29T) + Kh_59M = (9, Weapons.Kh_59M) + MER_6_FAB_100 = (9, Weapons.MER_6_FAB_100) + B_8M1___20_S_8KOM = (9, Weapons.B_8M1___20_S_8KOM) + B_13L___5_S_13_OF = (9, Weapons.B_13L___5_S_13_OF) + S_25_OFM = (9, Weapons.S_25_OFM) + BetAB_500 = (9, Weapons.BetAB_500) + KMGU_2___96_AO_2_5RT = (9, Weapons.KMGU_2___96_AO_2_5RT) + KMGU_2___96_PTAB_2_5KO = (9, Weapons.KMGU_2___96_PTAB_2_5KO) + FAB_250 = (9, Weapons.FAB_250) + RBK_250_PTAB_2_5M = (9, Weapons.RBK_250_PTAB_2_5M) + FAB_500_M62 = (9, Weapons.FAB_500_M62) + RBK_500_255_PTAB_10_5 = (9, Weapons.RBK_500_255_PTAB_10_5) + KAB_500L = (9, Weapons.KAB_500L) + KAB_500kr = (9, Weapons.KAB_500kr) + FAB_1500_M54 = (9, Weapons.FAB_1500_M54) + KAB_1500L = (9, Weapons.KAB_1500L) + MER_6_FAB_250 = (9, Weapons.MER_6_FAB_250) + RVV_BD = (9, Su57Weapons.RVV_BD) + RVV_AE = (9, Su57Weapons.RVV_AE) + RVV_M = (9, Su57Weapons.RVV_M) + RVV_L = (9, Su57Weapons.RVV_L) + RN_28 = (9, Weapons.RN_28) + Su_57_Fuel_Tank = (9, Su57Weapons.Su_57_Fuel_Tank) + Kh_59MK2 = (9, Su57Weapons.Kh_59MK2) + + class Pylon10: + R_73 = (10, Weapons.R_73) + Smoke_Generator___red = (10, Weapons.Smoke_Generator___red) + Smoke_Generator___green = (10, Weapons.Smoke_Generator___green) + Smoke_Generator___blue = (10, Weapons.Smoke_Generator___blue) + Smoke_Generator___white = (10, Weapons.Smoke_Generator___white) + Smoke_Generator___yellow = (10, Weapons.Smoke_Generator___yellow) + Smoke_Generator___orange = (10, Weapons.Smoke_Generator___orange) + + class Pylon11: + R_27R = (11, Weapons.R_27R) + R_27ER = (11, Weapons.R_27ER) + R_27T = (11, Weapons.R_27T) + R_27ET = (11, Weapons.R_27ET) + R_77 = (11, Weapons.R_77) + R_73 = (11, Weapons.R_73) + Kh_31P = (11, Weapons.Kh_31P) + Kh_31A = (11, Weapons.Kh_31A) + Kh_29L = (11, Weapons.Kh_29L) + Kh_29T = (11, Weapons.Kh_29T) + Kh_59M = (11, Weapons.Kh_59M) + MER_6_FAB_100 = (11, Weapons.MER_6_FAB_100) + B_8M1___20_S_8KOM = (11, Weapons.B_8M1___20_S_8KOM) + B_13L___5_S_13_OF = (11, Weapons.B_13L___5_S_13_OF) + S_25_OFM = (11, Weapons.S_25_OFM) + BetAB_500 = (11, Weapons.BetAB_500) + KMGU_2___96_AO_2_5RT = (11, Weapons.KMGU_2___96_AO_2_5RT) + KMGU_2___96_PTAB_2_5KO = (11, Weapons.KMGU_2___96_PTAB_2_5KO) + FAB_250 = (11, Weapons.FAB_250) + RBK_250_PTAB_2_5M = (11, Weapons.RBK_250_PTAB_2_5M) + FAB_500_M62 = (11, Weapons.FAB_500_M62) + RBK_500_255_PTAB_10_5 = (11, Weapons.RBK_500_255_PTAB_10_5) + KAB_500L = (11, Weapons.KAB_500L) + KAB_500kr = (11, Weapons.KAB_500kr) + FAB_1500_M54 = (11, Weapons.FAB_1500_M54) + KAB_1500L = (11, Weapons.KAB_1500L) + MER_6_FAB_250 = (11, Weapons.MER_6_FAB_250) +#ERRR {R-33} + RVV_BD = (11,Su57Weapons.RVV_BD) + RVV_AE = (11, Su57Weapons.RVV_AE) + RVV_M = (11, Su57Weapons.RVV_M) + RVV_L = (11, Su57Weapons.RVV_L) + Fuel_tank_800L_Wing = (11, Weapons.Fuel_tank_800L_Wing) + Su_57_Fuel_Tank = (11, Su57Weapons.Su_57_Fuel_Tank) + RN_28 = (11, Weapons.RN_28) + Smoke_Generator___red = (11, Weapons.Smoke_Generator___red) + Smoke_Generator___green = (11, Weapons.Smoke_Generator___green) + Smoke_Generator___blue = (11, Weapons.Smoke_Generator___blue) + Smoke_Generator___white = (11, Weapons.Smoke_Generator___white) + Smoke_Generator___yellow = (11, Weapons.Smoke_Generator___yellow) + Smoke_Generator___orange = (11, Weapons.Smoke_Generator___orange) + Kh_59MK2 = (11, Su57Weapons.Kh_59MK2) + + class Pylon12: + R_73 = (12, Weapons.R_73) + RVV_AE = (12, Su57Weapons.RVV_AE) + RVV_M = (12, Su57Weapons.RVV_M) + Smoke_Generator___red = (12, Weapons.Smoke_Generator___red) + Smoke_Generator___green = (12, Weapons.Smoke_Generator___green) + Smoke_Generator___blue = (12, Weapons.Smoke_Generator___blue) + Smoke_Generator___white = (12, Weapons.Smoke_Generator___white) + Smoke_Generator___yellow = (12, Weapons.Smoke_Generator___yellow) + Smoke_Generator___orange = (12, Weapons.Smoke_Generator___orange) + + pylons = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + + tasks = [task.CAP, task.Intercept, task.Escort, task.FighterSweep, task.AFAC, task.GroundAttack, task.RunwayAttack, task.AntishipStrike, task.CAS] + task_default = task.CAP diff --git a/resources/customized_payloads/Su-57.lua b/resources/customized_payloads/Su-57.lua new file mode 100644 index 00000000..fd4f7479 --- /dev/null +++ b/resources/customized_payloads/Su-57.lua @@ -0,0 +1,289 @@ +local unitPayloads = { + ["name"] = "Su-57", + ["payloads"] = { + [1] = { + ["name"] = "CAP", + ["pylons"] = { + [1] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 12, + }, + [2] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 11, + }, + [4] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 2, + }, + [5] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 7, + }, + [6] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 6, + }, + [7] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 8, + }, + [8] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 5, + }, + [9] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 4, + }, + [10] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 3, + }, + [11] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 9, + }, + [12] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 10, + }, + }, + ["tasks"] = { + [1] = 11, + }, + }, + [2] = { + ["name"] = "SEAD", + ["pylons"] = { + [1] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 12, + }, + [2] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{40AB87E8-BEFB-4D85-90D9-B2753ACF9514}", + ["num"] = 2, + }, + [4] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 3, + }, + [5] = { + ["CLSID"] = "{40AB87E8-BEFB-4D85-90D9-B2753ACF9514}", + ["num"] = 11, + }, + [6] = { + ["CLSID"] = "{40AB87E8-BEFB-4D85-90D9-B2753ACF9514}", + ["num"] = 9, + }, + [7] = { + ["CLSID"] = "{40AB87E8-BEFB-4D85-90D9-B2753ACF9514}", + ["num"] = 4, + }, + [8] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 8, + }, + [9] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 5, + }, + [10] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 6, + }, + [11] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 7, + }, + [12] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 10, + }, + }, + ["tasks"] = { + [1] = 11, + }, + }, + [3] = { + ["name"] = "ANTISHIP", + ["pylons"] = { + [1] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 12, + }, + [2] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{KH_59MK2}", + ["num"] = 2, + }, + [4] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 3, + }, + [5] = { + ["CLSID"] = "{KH_59MK2}", + ["num"] = 11, + }, + [6] = { + ["CLSID"] = "{KH_59MK2}", + ["num"] = 9, + }, + [7] = { + ["CLSID"] = "{KH_59MK2}", + ["num"] = 4, + }, + [8] = { + ["CLSID"] = "{KH_59MK2}", + ["num"] = 8, + }, + [9] = { + ["CLSID"] = "{KH_59MK2}", + ["num"] = 5, + }, + [10] = { + ["CLSID"] = "{KH_59MK2}", + ["num"] = 6, + }, + [11] = { + ["CLSID"] = "{KH_59MK2}", + ["num"] = 7, + }, + [12] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 10, + }, + }, + ["tasks"] = { + [1] = 11, + }, + }, + [4] = { + ["name"] = "CAS", + ["pylons"] = { + [1] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 12, + }, + [2] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{B4FC81C9-B861-4E87-BBDC-A1158E648EBF}", + ["num"] = 2, + }, + [4] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 3, + }, + [5] = { + ["CLSID"] = "{B4FC81C9-B861-4E87-BBDC-A1158E648EBF}", + ["num"] = 11, + }, + [6] = { + ["CLSID"] = "{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}", + ["num"] = 9, + }, + [7] = { + ["CLSID"] = "{F72F47E5-C83A-4B85-96ED-D3E46671EE9A}", + ["num"] = 4, + }, + [8] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 8, + }, + [9] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 5, + }, + [10] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 6, + }, + [11] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 7, + }, + [12] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 10, + }, + }, + ["tasks"] = { + [1] = 11, + }, + }, + [5] = { + ["name"] = "STRIKE", + ["pylons"] = { + [1] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 12, + }, + [2] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}", + ["num"] = 2, + }, + [4] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 3, + }, + [5] = { + ["CLSID"] = "{3C612111-C7AD-476E-8A8E-2485812F4E5C}", + ["num"] = 11, + }, + [6] = { + ["CLSID"] = "{53BE25A4-C86C-4571-9BC0-47D668349595}", + ["num"] = 9, + }, + [7] = { + ["CLSID"] = "{53BE25A4-C86C-4571-9BC0-47D668349595}", + ["num"] = 4, + }, + [8] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 8, + }, + [9] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 5, + }, + [10] = { + ["CLSID"] = "{FBC29BFE-3D24-4C64-B81D-941239D12249}", + ["num"] = 10, + }, + [11] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 7, + }, + [12] = { + ["CLSID"] = "{RVV-AE}", + ["num"] = 6, + }, + }, + ["tasks"] = { + [1] = 11, + }, + }, + }, + ["tasks"] = { + }, + ["unitType"] = "Su-57", +} +return unitPayloads diff --git a/resources/ui/units/aircrafts/Su-57_24.jpg b/resources/ui/units/aircrafts/Su-57_24.jpg new file mode 100644 index 0000000000000000000000000000000000000000..adf32207a675ba5bb76be8b62585dd89fceb0451 GIT binary patch literal 1201 zcmex=^(PF6}rMnOeST|r4lSw=>~TvNxu(8R<c1}I=;VrF4wW9Q)H;sz?% zD!{d!pzFb!U9xX3zTPI5o8roG<0MW4oqZMDikqloVbuf*=gfJ(V&YTRE(2~ znmD<{#3dx9RMpfqG__1j&CD$#!xx+I*%kd^JG+lwS2`a!&%FIq|H*!x1%2hB z=Y8f#z1P#%o6@rB(!2F4>MRY)y|t&e%TLRmex-)7xN?Q>iu}&J)Cs5C$_}V+_p4a6 z=G~f}MBY-XT)E%BgHE46#P&}$?)Z+?H`Y}jzNNNy@m=j{+cTm&nNI)e>AUiuLFmWa z63vT?wupzFlP*>{=;OnGS9#VdQ|Ykkt^n`g>RjpUw*E&xLj04xc9wndWUf~Xd-$ z=L-v0e^%GGoYmWUx5^$7FZSq9I-(K0PgA7G<1z0mj`y!a9`3y&WLbA}(JQTs>Cd|4 z4J>4jU8>u$Y?T_{++N--xyyde*P66ucY{S@^G_*%lb~-MG3}abUPk5Z<;kv53-f8I zNXxice0@3lN48BS_t}pwjoo@}{+j!Ww?bFPd&2 z@sX_iFv+m(`-_5X#kYI1I5JG5!%`m?rn2-Ov(i|r9OW4iu64fhqt?wUhbxu!eK&eb zGrqr&wEpIg=HqTb*9GF-&J-+MZf*Fga9(wwt9jtojh=H=d;Y1+J+gh)oox%4Sog4a zNdH}Qq^@cCj(=BYddV3kN1a0i}S<~iIbIeq+_zkD} O*F{~+Ee-DczX Date: Wed, 21 Oct 2020 23:44:36 +0200 Subject: [PATCH 2/9] changes to the export of state.json : - dcsLiberation.installPath does not include "state.json" anymore - corrected behavior : - try LIBERATION_EXPORT_DIR - then try dcsLiberation.installPath - then try TEMP - then try working directory - corrected multiple bugs in dcs_liberation.lua - corrected bad string.format causing DCS crashes in jtacautolase-config.lua --- game/operation/operation.py | 2 +- resources/plugins/base/dcs_liberation.lua | 145 +++++++++++------- .../jtacautolase/jtacautolase-config.lua | 8 +- 3 files changed, 98 insertions(+), 57 deletions(-) diff --git a/game/operation/operation.py b/game/operation/operation.py index c83f903f..98feb740 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -358,7 +358,7 @@ class Operation: # set a LUA table with data from Liberation that we want to set # at the moment it contains Liberation's install path, and an overridable definition for the JTACAutoLase function # later, we'll add data about the units and points having been generated, in order to facilitate the configuration of the plugin lua scripts - state_location = "[[" + os.path.abspath("state.json") + "]]" + state_location = "[[" + os.path.abspath(".") + "]]" lua = """ -- setting configuration table env.info("DCSLiberation|: setting configuration table") diff --git a/resources/plugins/base/dcs_liberation.lua b/resources/plugins/base/dcs_liberation.lua index 8017b1f5..722440a6 100644 --- a/resources/plugins/base/dcs_liberation.lua +++ b/resources/plugins/base/dcs_liberation.lua @@ -1,3 +1,6 @@ +-- the state.json file will be updated according to this schedule, and also on each destruction or capture event +local WRITESTATE_SCHEDULE_IN_SECONDS = 60 + logger = mist.Logger:new("DCSLiberation", "info") logger:info("Check that json.lua is loaded : json = "..tostring(json)) @@ -8,6 +11,10 @@ base_capture_events = {} destroyed_objects_positions = {} mission_ended = false +local function ends_with(str, ending) + return ending == "" or str:sub(-#ending) == ending +end + local function messageAll(message) local msg = {} msg.text = message @@ -16,10 +23,13 @@ local function messageAll(message) mist.message.add(msg) end -write_state = function() - --messageAll("Writing DCS Liberation State...") - --logger.info("Writing DCS LIBERATION state") - local fp = io.open(debriefing_file_location, 'w') +function write_state() + local _debriefing_file_location = debriefing_file_location + if not debriefing_file_location then + _debriefing_file_location = "[nil]" + end + + local fp = io.open(_debriefing_file_location, 'w') local game_state = { ["killed_aircrafts"] = killed_aircrafts, ["killed_ground_units"] = killed_ground_units, @@ -29,83 +39,114 @@ write_state = function() ["destroyed_objects_positions"] = destroyed_objects_positions, } if not json then - local message = string.format("Unable to save DCS Liberation state to %s, JSON library is not loaded !",debriefing_file_location) + local message = string.format("Unable to save DCS Liberation state to %s, JSON library is not loaded !", _debriefing_file_location) logger:error(message) messageAll(message) end fp:write(json:encode(game_state)) fp:close() - -- logger.info("Done writing DCS Liberation state") - -- messageAll("Done writing DCS Liberation state.") end - -local function discoverDebriefingFilePath() - local function insertFileName(directoryOrFilePath, overrideFileName) - if overrideFileName then - logger:info("Using LIBERATION_EXPORT_STAMPED_STATE to locate the state.json") - return directoryOrFilePath .. os.time() .. "-state.json" - end - - local filename = "state.json" - if not (directoryOrFilePath:sub(-#filename) == filename) then - return directoryOrFilePath .. filename - end - - return directoryOrFilePath +local function canWrite(name) + local f = io.open(name, "w") + if f then + f:close() + return true end + return false +end +local function testDebriefingFilePath(folderPath, folderName, useCurrentStamping) + if folderPath then + local filePath = nil + if not ends_with(folderPath, "\\") then + folderPath = folderPath .. "\\" + end + if useCurrentStamping then + filePath = string.format("%sstate-%s.json",folderPath, tostring(os.time())) + else + filePath = string.format("%sstate.json",folderPath) + end + local isOk = canWrite(filePath) + if isOk then + logger:info(string.format("The state.json file will be created in %s : (%s)",folderName, filePath)) + return filePath + end + end + return nil +end + +local function discoverDebriefingFilePath() -- establish a search pattern into the following modes - -- 1. Environment variable mode, to support dedicated server hosting - -- 2. Embedded DCS Liberation Generation, to support locally hosted single player - -- 3. Retain the classic TEMP directory logic + -- 1. Environment variable LIBERATION_EXPORT_DIR, to support dedicated server hosting + -- 2. Embedded DCS Liberation dcsLiberation.installPath (set by the app to its install path), to support locally hosted single player + -- 3. System temporary folder, as set in the TEMP environment variable + -- 4. Working directory. + + local useCurrentStamping = nil + if os then + useCurrentStamping = os.getenv("LIBERATION_EXPORT_STAMPED_STATE") + end + local installPath = nil + if dcsLiberation then + installPath = dcsLiberation.installPath + end + if os then - local exportDirectory = os.getenv("LIBERATION_EXPORT_DIR") - - if exportDirectory then - logger:info("Using LIBERATION_EXPORT_DIR to locate the state.json") - local useCurrentStamping = os.getenv("LIBERATION_EXPORT_STAMPED_STATE") - exportDirectory = exportDirectory .. "\\" - return insertFileName(exportDirectory, useCurrentStamping) + local result = nil + -- try using the LIBERATION_EXPORT_DIR environment variable + result = testDebriefingFilePath(os.getenv("LIBERATION_EXPORT_DIR"), "LIBERATION_EXPORT_DIR", useCurrentStamping) + if result then + return result + end + -- no joy ? maybe there is a valid path in the mission ? + result = testDebriefingFilePath(installPath, "the DCS Liberation install folder", useCurrentStamping) + if result then + return result + end + -- there's always the possibility of using the system temporary folder + result = testDebriefingFilePath(os.getenv("TEMP"), "TEMP", useCurrentStamping) + if result then + return result end end - if dcsLiberation then - logger:info("Using DCS Liberation install folder for state.json") - return insertFileName(dcsLiberation.installPath) - end - + -- nothing worked, let's try the last resort folder : current directory. if lfs then - logger:info("Using DCS working directory for state.json") - return insertFileName(lfs.writedir()) + return testDebriefingFilePath(lfs.writedir(), "the working directory", useCurrentStamping) end + + return nil end - debriefing_file_location = discoverDebriefingFilePath() -logger:info(string.format("DCS Liberation state will be written as json to [[%s]]",debriefing_file_location)) - write_state_error_handling = function() + local _debriefing_file_location = debriefing_file_location + if not debriefing_file_location then + _debriefing_file_location = "[nil]" + logger:error("Unable to find where to write DCS Liberation state") + end + if pcall(write_state) then - -- messageAll("Written DCS Liberation state to "..debriefing_file_location) else - messageAll("Unable to write DCS Liberation state to "..debriefing_file_location.. + messageAll("Unable to write DCS Liberation state to ".._debriefing_file_location.. "\nYou can abort the mission in DCS Liberation.\n".. "\n\nPlease fix your setup in DCS Liberation, make sure you are pointing to the right installation directory from the File/Preferences menu. Then after fixing the path restart DCS Liberation, and then restart DCS.".. "\n\nYou can also try to fix the issue manually by replacing the file /Scripts/MissionScripting.lua by the one provided there : /resources/scripts/MissionScripting.lua. And then restart DCS. (This will also have to be done again after each DCS update)".. "\n\nIt's not worth playing, the state of the mission will not be recorded.") end -end -mist.scheduleFunction(write_state_error_handling, {}, timer.getTime() + 10, 60, timer.getTime() + 3600) + -- reschedule + mist.scheduleFunction(write_state_error_handling, {}, timer.getTime() + WRITESTATE_SCHEDULE_IN_SECONDS) +end activeWeapons = {} local function onEvent(event) if event.id == world.event.S_EVENT_CRASH and event.initiator then - --messageAll("Destroyed :" .. event.initiator.getName(event.initiator)) killed_aircrafts[#killed_aircrafts + 1] = event.initiator.getName(event.initiator) + write_state() end if event.id == world.event.S_EVENT_DEAD and event.initiator then @@ -118,15 +159,12 @@ local function onEvent(event) destruction.type = event.initiator:getTypeName() destruction.orientation = mist.getHeading(event.initiator) * 57.3 destroyed_objects_positions[#destroyed_objects_positions + 1] = destruction + write_state() end - --if event.id == world.event.S_EVENT_SHOT and event.weapon then - -- weapons_fired[#weapons_fired + 1] = event.weapon.getTypeName(event.weapon) - --end - if event.id == world.event.S_EVENT_BASE_CAPTURED and event.place then - --messageAll("Base captured :" .. event.place.getName(event.place)) base_capture_events[#base_capture_events + 1] = event.place.getID(event.place) .. "||" .. event.place.getCoalition(event.place) .. "||" .. event.place.getName(event.place) + write_state() end if event.id == world.event.S_EVENT_MISSION_END then @@ -136,4 +174,7 @@ local function onEvent(event) end -mist.addEventHandler(onEvent) \ No newline at end of file +mist.addEventHandler(onEvent) + +-- create the state.json file and start the scheduling +write_state_error_handling() \ No newline at end of file diff --git a/resources/plugins/jtacautolase/jtacautolase-config.lua b/resources/plugins/jtacautolase/jtacautolase-config.lua index 47a88edf..38d02911 100644 --- a/resources/plugins/jtacautolase/jtacautolase-config.lua +++ b/resources/plugins/jtacautolase/jtacautolase-config.lua @@ -9,17 +9,17 @@ env.info("DCSLiberation|JTACAutolase plugin - configuration") if dcsLiberation then - env.info(string.format("DCSLiberation|JTACAutolase plugin - dcsLiberation")) + env.info("DCSLiberation|JTACAutolase plugin - dcsLiberation") -- specific options local smoke = false -- retrieve specific options values if dcsLiberation.plugins then - env.info(string.format("DCSLiberation|JTACAutolase plugin - dcsLiberation.plugins")) + env.info("DCSLiberation|JTACAutolase plugin - dcsLiberation.plugins") if dcsLiberation.plugins.jtacautolase then - env.info(string.format("DCSLiberation|JTACAutolase plugin - dcsLiberation.plugins.jtacautolase")) + env.info("DCSLiberation|JTACAutolase plugin - dcsLiberation.plugins.jtacautolase") smoke = dcsLiberation.plugins.jtacautolase.smoke env.info(string.format("DCSLiberation|JTACAutolase plugin - smoke = %s",tostring(smoke))) end @@ -29,7 +29,7 @@ if dcsLiberation then for _, jtac in pairs(dcsLiberation.JTACs) do env.info(string.format("DCSLiberation|JTACAutolase plugin - setting up %s",jtac.dcsUnit)) if JTACAutoLase then - env.info(string.format("DCSLiberation|JTACAutolase plugin - calling dcsLiberation.JTACAutoLase")) + env.info("DCSLiberation|JTACAutolase plugin - calling JTACAutoLase") JTACAutoLase(jtac.dcsUnit, jtac.laserCode, smoke, 'vehicle') end end From 8ac5dbe22a504e2278e25908e57f58d785a1a88f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 21 Oct 2020 17:52:03 -0700 Subject: [PATCH 3/9] Add double- and right-click actions for ATO lists. Fixes https://github.com/Khopa/dcs_liberation/issues/230 --- qt_ui/widgets/ato.py | 108 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/qt_ui/widgets/ato.py b/qt_ui/widgets/ato.py index bc45fac9..5f22b961 100644 --- a/qt_ui/widgets/ato.py +++ b/qt_ui/widgets/ato.py @@ -10,15 +10,26 @@ from PySide2.QtCore import ( QSize, Qt, ) -from PySide2.QtGui import QFont, QFontMetrics, QIcon, QPainter +from PySide2.QtGui import ( + QContextMenuEvent, + QFont, + QFontMetrics, + QIcon, + QPainter, +) from PySide2.QtWidgets import ( QAbstractItemView, + QAction, QGroupBox, QHBoxLayout, QListView, + QMenu, QPushButton, QSplitter, - QStyle, QStyleOptionViewItem, QStyledItemDelegate, QVBoxLayout, + QStyle, + QStyleOptionViewItem, + QStyledItemDelegate, + QVBoxLayout, ) from game import db @@ -130,14 +141,17 @@ class FlightDelegate(QStyledItemDelegate): class QFlightList(QListView): """List view for displaying the flights of a package.""" - def __init__(self, model: Optional[PackageModel]) -> None: + def __init__(self, game_model: GameModel, + package_model: Optional[PackageModel]) -> None: super().__init__() - self.package_model = model - self.set_package(model) - if model is not None: - self.setItemDelegate(FlightDelegate(model.package)) + self.game_model = game_model + self.package_model = package_model + self.set_package(package_model) + if package_model is not None: + self.setItemDelegate(FlightDelegate(package_model.package)) self.setIconSize(QSize(91, 24)) self.setSelectionBehavior(QAbstractItemView.SelectItems) + self.doubleClicked.connect(self.on_double_click) def set_package(self, model: Optional[PackageModel]) -> None: """Sets the package model to display.""" @@ -172,6 +186,38 @@ class QFlightList(QListView): return None return self.package_model.flight_at_index(index) + def on_double_click(self, index: QModelIndex) -> None: + if not index.isValid(): + return + self.edit_flight(index) + + def edit_flight(self, index: QModelIndex) -> None: + from qt_ui.dialogs import Dialog + Dialog.open_edit_flight_dialog( + self.package_model, self.package_model.flight_at_index(index) + ) + + def delete_flight(self, index: QModelIndex) -> None: + self.game_model.game.aircraft_inventory.return_from_flight( + self.selected_item) + self.package_model.delete_flight_at_index(index) + GameUpdateSignal.get_instance().redraw_flight_paths() + + def contextMenuEvent(self, event: QContextMenuEvent) -> None: + index = self.indexAt(event.pos()) + + menu = QMenu("Menu") + + edit_action = QAction("Edit") + edit_action.triggered.connect(lambda: self.edit_flight(index)) + menu.addAction(edit_action) + + delete_action = QAction(f"Delete") + delete_action.triggered.connect(lambda: self.delete_flight(index)) + menu.addAction(delete_action) + + menu.exec_(event.globalPos()) + class QFlightPanel(QGroupBox): """The flight display portion of the ATO panel. @@ -189,7 +235,7 @@ class QFlightPanel(QGroupBox): self.vbox = QVBoxLayout() self.setLayout(self.vbox) - self.flight_list = QFlightList(package_model) + self.flight_list = QFlightList(game_model, package_model) self.vbox.addWidget(self.flight_list) self.button_row = QHBoxLayout() @@ -242,10 +288,7 @@ class QFlightPanel(QGroupBox): if not index.isValid(): logging.error(f"Cannot edit flight when no flight is selected.") return - from qt_ui.dialogs import Dialog - Dialog.open_edit_flight_dialog( - self.package_model, self.package_model.flight_at_index(index) - ) + self.flight_list.edit_flight(index) def on_delete(self) -> None: """Removes the selected flight from the package.""" @@ -253,10 +296,7 @@ class QFlightPanel(QGroupBox): if not index.isValid(): logging.error(f"Cannot delete flight when no flight is selected.") return - self.game_model.game.aircraft_inventory.return_from_flight( - self.flight_list.selected_item) - self.package_model.delete_flight_at_index(index) - GameUpdateSignal.get_instance().redraw_flight_paths() + self.flight_list.delete_flight(index) @contextmanager @@ -338,6 +378,7 @@ class QPackageList(QListView): self.setIconSize(QSize(91, 24)) self.setSelectionBehavior(QAbstractItemView.SelectItems) self.model().rowsInserted.connect(self.on_new_packages) + self.doubleClicked.connect(self.on_double_click) @property def selected_item(self) -> Optional[Package]: @@ -347,6 +388,14 @@ class QPackageList(QListView): return None return self.ato_model.package_at_index(index) + def edit_package(self, index: QModelIndex) -> None: + from qt_ui.dialogs import Dialog + Dialog.open_edit_package_dialog(self.ato_model.get_package_model(index)) + + def delete_package(self, index: QModelIndex) -> None: + self.ato_model.delete_package_at_index(index) + GameUpdateSignal.get_instance().redraw_flight_paths() + def on_new_packages(self, _parent: QModelIndex, first: int, _last: int) -> None: # Select the newly created pacakges. This should only ever happen due to @@ -355,6 +404,26 @@ class QPackageList(QListView): self.selectionModel().setCurrentIndex(self.model().index(first, 0), QItemSelectionModel.Select) + def on_double_click(self, index: QModelIndex) -> None: + if not index.isValid(): + return + self.edit_package(index) + + def contextMenuEvent(self, event: QContextMenuEvent) -> None: + index = self.indexAt(event.pos()) + + menu = QMenu("Menu") + + edit_action = QAction("Edit") + edit_action.triggered.connect(lambda: self.edit_package(index)) + menu.addAction(edit_action) + + delete_action = QAction(f"Delete") + delete_action.triggered.connect(lambda: self.delete_package(index)) + menu.addAction(delete_action) + + menu.exec_(event.globalPos()) + class QPackagePanel(QGroupBox): """The package display portion of the ATO panel. @@ -420,8 +489,7 @@ class QPackagePanel(QGroupBox): if not index.isValid(): logging.error(f"Cannot edit package when no package is selected.") return - from qt_ui.dialogs import Dialog - Dialog.open_edit_package_dialog(self.ato_model.get_package_model(index)) + self.package_list.edit_package(index) def on_delete(self) -> None: """Removes the package from the ATO.""" @@ -429,8 +497,7 @@ class QPackagePanel(QGroupBox): if not index.isValid(): logging.error(f"Cannot delete package when no package is selected.") return - self.ato_model.delete_package_at_index(index) - GameUpdateSignal.get_instance().redraw_flight_paths() + self.package_list.delete_package(index) class QAirTaskingOrderPanel(QSplitter): @@ -440,6 +507,7 @@ class QAirTaskingOrderPanel(QSplitter): packages of the player's ATO, and the bottom half displays the flights of the selected package. """ + def __init__(self, game_model: GameModel) -> None: super().__init__(Qt.Vertical) self.ato_model = game_model.ato_model From 177b505cb71501e9435d15d3ea3412edc68d7e93 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 21 Oct 2020 17:54:58 -0700 Subject: [PATCH 4/9] Update client slots UI on end turn. Fixes https://github.com/Khopa/dcs_liberation/issues/222 --- qt_ui/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qt_ui/models.py b/qt_ui/models.py index 6480d933..e13ac0ea 100644 --- a/qt_ui/models.py +++ b/qt_ui/models.py @@ -252,6 +252,8 @@ class AtoModel(QAbstractListModel): else: self.ato = AirTaskingOrder() self.endResetModel() + # noinspection PyUnresolvedReferences + self.client_slots_changed.emit() def get_package_model(self, index: QModelIndex) -> PackageModel: """Returns a model for the package at the given index.""" From 58fd651a0bfab97a71b66ab837d9e595d4c9536b Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 21 Oct 2020 18:35:14 -0700 Subject: [PATCH 5/9] Confirm exit to avoid losing progress. Fixes https://github.com/Khopa/dcs_liberation/issues/218 --- qt_ui/windows/QLiberationWindow.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 99783d11..e5bb8889 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -4,7 +4,7 @@ import webbrowser from typing import Optional, Union from PySide2.QtCore import Qt -from PySide2.QtGui import QIcon +from PySide2.QtGui import QCloseEvent, QIcon from PySide2.QtWidgets import ( QAction, QActionGroup, QDesktopWidget, @@ -136,7 +136,7 @@ class QLiberationWindow(QMainWindow): file_menu.addSeparator() file_menu.addAction(self.showLiberationPrefDialogAction) file_menu.addSeparator() - file_menu.addAction("E&xit" , lambda: self.exit()) + file_menu.addAction("E&xit", self.close) displayMenu = self.menu.addMenu("&Display") @@ -214,13 +214,6 @@ class QLiberationWindow(QMainWindow): self.game = game GameUpdateSignal.get_instance().updateGame(self.game) - def closeGame(self): - self.game = None - GameUpdateSignal.get_instance().updateGame(self.game) - - def exit(self): - sys.exit(0) - def setGame(self, game: Optional[Game]): if game is not None: game.on_load() @@ -257,3 +250,14 @@ class QLiberationWindow(QMainWindow): logging.info("On Debriefing") self.debriefing = QDebriefingWindow(debrief.debriefing, debrief.gameEvent, debrief.game) self.debriefing.show() + + def closeEvent(self, event: QCloseEvent) -> None: + result = QMessageBox.question( + self, "Quit Liberation?", + "Are you sure you want to quit? All unsaved progress will be lost.", + QMessageBox.Yes | QMessageBox.No + ) + if result == QMessageBox.Yes: + super().closeEvent(event) + else: + event.ignore() From 8c70d1ab791b736e8250c6b1913d3da3f549ec96 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 21 Oct 2020 23:53:38 -0700 Subject: [PATCH 6/9] Fix foot to meter conversion. Somehow this constant was wrong so all of our foot-to-meter conversions were coming out ~7% too large. We're still introducing some error because we're rounding early rather than only when we need an integer, but it's much more accurate now. --- game/utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/game/utils.py b/game/utils.py index 34b82aa4..44652472 100644 --- a/game/utils.py +++ b/game/utils.py @@ -1,14 +1,14 @@ -def meter_to_feet(value_in_meter): +def meter_to_feet(value_in_meter: float) -> int: return int(3.28084 * value_in_meter) -def feet_to_meter(value_in_feet): - return int(float(value_in_feet)/3.048) +def feet_to_meter(value_in_feet: float) -> int: + return int(value_in_feet / 3.28084) -def meter_to_nm(value_in_meter): - return int(float(value_in_meter)*0.000539957) +def meter_to_nm(value_in_meter: float) -> int: + return int(value_in_meter / 1852) -def nm_to_meter(value_in_nm): - return int(float(value_in_nm)*1852) \ No newline at end of file +def nm_to_meter(value_in_nm: float) -> int: + return int(value_in_nm * 1852) From 69f15824ca79a680d0c8b56224b75a0b6a9f847f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Oct 2020 00:44:54 -0700 Subject: [PATCH 7/9] Fix breakage in package list UI. --- qt_ui/windows/mission/QPackageDialog.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/qt_ui/windows/mission/QPackageDialog.py b/qt_ui/windows/mission/QPackageDialog.py index 4ee1a0a1..b2733a40 100644 --- a/qt_ui/windows/mission/QPackageDialog.py +++ b/qt_ui/windows/mission/QPackageDialog.py @@ -17,7 +17,7 @@ from gen.ato import Package from gen.flights.flight import Flight from gen.flights.flightplan import FlightPlanBuilder from gen.flights.traveltime import TotEstimator -from qt_ui.models import AtoModel, PackageModel +from qt_ui.models import AtoModel, GameModel, PackageModel from qt_ui.uiconstants import EVENT_ICONS from qt_ui.widgets.ato import QFlightList from qt_ui.windows.GameUpdateSignal import GameUpdateSignal @@ -35,9 +35,9 @@ class QPackageDialog(QDialog): #: Emitted when a change is made to the package. package_changed = Signal() - def __init__(self, game: Game, model: PackageModel) -> None: + def __init__(self, game_model: GameModel, model: PackageModel) -> None: super().__init__() - self.game = game + self.game_model = game_model self.package_model = model self.add_flight_dialog: Optional[QFlightCreator] = None @@ -86,7 +86,7 @@ class QPackageDialog(QDialog): self.reset_tot_button.clicked.connect(self.reset_tot) self.tot_column.addWidget(self.reset_tot_button) - self.package_view = QFlightList(self.package_model) + self.package_view = QFlightList(self.game_model, self.package_model) self.package_view.selectionModel().selectionChanged.connect( self.on_selection_changed ) @@ -113,6 +113,10 @@ class QPackageDialog(QDialog): self.finished.connect(self.on_close) self.rejected.connect(self.on_cancel) + @property + def game(self) -> Game: + return self.game_model.game + def tot_qtime(self) -> QTime: delay = self.package_model.package.time_over_target hours = delay // 3600 From 95f486870de9e6fef1dc85b888ef52e6036c1133 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 22 Oct 2020 00:45:57 -0700 Subject: [PATCH 8/9] Correct AI startup time. It doesn't look like the AI is subject to much startup time. I see B-1, F-15, F-16, and M-2000 all start up in about 2 minutes. --- gen/flights/traveltime.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gen/flights/traveltime.py b/gen/flights/traveltime.py index a4690f1a..1b1c5622 100644 --- a/gen/flights/traveltime.py +++ b/gen/flights/traveltime.py @@ -202,7 +202,11 @@ class TotEstimator: @staticmethod def estimate_startup(flight: Flight) -> int: if flight.start_type == "Cold": - return 10 * 60 + if flight.client_count: + return 10 * 60 + else: + # The AI doesn't seem to have a real startup procedure. + return 2 * 60 return 0 @staticmethod From fd969020af5e8e7cf9e124c12c4c7b70ea480353 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 18 Oct 2020 15:05:26 -0700 Subject: [PATCH 9/9] Add escort tasks for the AI. --- gen/aircraft.py | 79 +++++++++++++++---- gen/flights/flight.py | 1 + gen/flights/flightplan.py | 39 ++++----- gen/flights/traveltime.py | 2 + gen/flights/waypointbuilder.py | 44 ++++++++++- .../flight/waypoints/QFlightWaypointTab.py | 9 ++- 6 files changed, 133 insertions(+), 41 deletions(-) diff --git a/gen/aircraft.py b/gen/aircraft.py index 5076ee8e..0c4aa97c 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -3,11 +3,11 @@ from __future__ import annotations import logging import random from dataclasses import dataclass -from typing import Dict, List, Optional, Tuple, Type, Union +from typing import Dict, List, Optional, Type, Union from dcs import helicopters -from dcs.action import AITaskPush, ActivateGroup, MessageToAll -from dcs.condition import CoalitionHasAirdrome, PartOfCoalitionInZone, TimeAfter +from dcs.action import AITaskPush, ActivateGroup +from dcs.condition import CoalitionHasAirdrome, TimeAfter from dcs.country import Country from dcs.flyingunit import FlyingUnit from dcs.helicopters import UH_1H, helicopter_map @@ -40,7 +40,6 @@ from dcs.task import ( ControlledTask, EPLRS, EngageTargets, - Escort, GroundAttack, OptROE, OptRTBOnBingoFuel, @@ -55,10 +54,10 @@ from dcs.task import ( Targets, Task, ) -from dcs.terrain.terrain import Airport, NoParkingSlotError +from dcs.terrain.terrain import Airport from dcs.translation import String from dcs.triggers import Event, TriggerOnce, TriggerRule -from dcs.unitgroup import FlyingGroup, Group, ShipGroup, StaticGroup +from dcs.unitgroup import FlyingGroup, ShipGroup, StaticGroup from dcs.unittype import FlyingType, UnitType from game import db @@ -548,7 +547,6 @@ class AircraftConflictGenerator: self.settings = settings self.conflict = conflict self.radio_registry = radio_registry - self.escort_targets: List[Tuple[FlyingGroup, int]] = [] self.flights: List[FlightData] = [] def get_intra_flight_channel(self, airframe: UnitType) -> RadioFrequency: @@ -633,10 +631,6 @@ class AircraftConflictGenerator: logging.warning(f"Unhandled departure control point: {cp.cptype}") departure_runway = fallback_runway - # The first waypoint is set automatically by pydcs, so it's not in our - # list. Convert the pydcs MovingPoint to a FlightWaypoint so it shows up - # in our FlightData. - first_point = FlightWaypoint.from_pydcs(group.points[0], flight.from_cp) self.flights.append(FlightData( flight_type=flight.flight_type, units=group.units, @@ -808,8 +802,8 @@ class AircraftConflictGenerator: logging.info(f"Generating flight: {flight.unit_type}") group = self.generate_planned_flight(flight.from_cp, country, flight) - self.setup_flight_group(group, package, flight, timing, - dynamic_runways) + self.setup_flight_group(group, flight, dynamic_runways) + self.create_waypoints(group, package, flight, timing) def set_activation_time(self, flight: Flight, group: FlyingGroup, delay: int) -> None: @@ -988,8 +982,11 @@ class AircraftConflictGenerator: def configure_escort(self, group: FlyingGroup, flight: Flight, dynamic_runways: Dict[str, RunwayData]) -> None: - group.task = Escort.name - self._setup_group(group, Escort, flight, dynamic_runways) + # Escort groups are actually given the CAP task so they can perform the + # Search Then Engage task, which we have to use instead of the Escort + # task for the reasons explained in JoinPointBuilder. + group.task = CAP.name + self._setup_group(group, CAP, flight, dynamic_runways) self.configure_behavior(group, roe=OptROE.Values.OpenFire, restrict_jettison=True) @@ -998,8 +995,7 @@ class AircraftConflictGenerator: logging.error(f"Unhandled flight type: {flight.flight_type.name}") self.configure_behavior(group) - def setup_flight_group(self, group: FlyingGroup, package: Package, - flight: Flight, timing: PackageWaypointTiming, + def setup_flight_group(self, group: FlyingGroup, flight: Flight, dynamic_runways: Dict[str, RunwayData]) -> None: flight_type = flight.flight_type if flight_type in [FlightType.BARCAP, FlightType.TARCAP, @@ -1020,16 +1016,23 @@ class AircraftConflictGenerator: self.configure_eplrs(group, flight) + def create_waypoints(self, group: FlyingGroup, package: Package, + flight: Flight, timing: PackageWaypointTiming) -> None: + for waypoint in flight.points: waypoint.tot = None takeoff_point = FlightWaypoint.from_pydcs(group.points[0], flight.from_cp) self.set_takeoff_time(takeoff_point, package, flight, group) + + filtered_points = [] for point in flight.points: if point.only_for_player and not flight.client_count: continue + filtered_points.append(point) + for idx, point in enumerate(filtered_points): PydcsWaypointBuilder.for_waypoint( point, group, flight, timing, self.m ).build() @@ -1114,6 +1117,8 @@ class PydcsWaypointBuilder: mission: Mission) -> PydcsWaypointBuilder: builders = { FlightWaypointType.EGRESS: EgressPointBuilder, + FlightWaypointType.INGRESS_CAS: IngressBuilder, + FlightWaypointType.INGRESS_ESCORT: IngressBuilder, FlightWaypointType.INGRESS_SEAD: SeadIngressBuilder, FlightWaypointType.INGRESS_STRIKE: StrikeIngressBuilder, FlightWaypointType.JOIN: JoinPointBuilder, @@ -1236,8 +1241,48 @@ class JoinPointBuilder(PydcsWaypointBuilder): def build(self) -> MovingPoint: waypoint = super().build() self.set_waypoint_tot(waypoint, self.timing.join) + if self.flight.flight_type == FlightType.ESCORT: + self.configure_escort_tasks(waypoint) return waypoint + @staticmethod + def configure_escort_tasks(waypoint: MovingPoint) -> None: + # Ideally we would use the escort mission type and escort task to have + # the AI automatically but the AI only escorts AI flights while they are + # traveling between waypoints. When an AI flight performs an attack + # (such as attacking the mission target), AI escorts wander aimlessly + # until the escorted group resumes its flight plan. + # + # As such, we instead use the Search Then Engage task, which is an + # enroute task that causes the AI to follow their flight plan and engage + # enemies of the set type within a certain distance. The downside to + # this approach is that AI escorts are no longer related to the group + # they are escorting, aside from the fact that they fly a similar flight + # plan at the same time. With Escort, the escorts will follow the + # escorted group out of the area. The strike element may or may not fly + # directly over the target, and they may or may not require multiple + # attack runs. For the escort flight we must just assume a flight plan + # for the escort to fly. If the strike flight doesn't need to overfly + # the target, the escorts are needlessly going in harms way. If the + # strike flight needs multiple passes, the escorts may leave before the + # escorted aircraft do. + # + # Another possible option would be to use Search Then Engage for join -> + # ingress and egress -> split, but use a Search Then Engage in Zone task + # for the target area that is set to end on a flag flip that occurs when + # the strike aircraft finish their attack task. + # + # https://forums.eagle.ru/forum/english/digital-combat-simulator/dcs-world-2-5/bugs-and-problems-ai/ai-ad/250183-task-follow-and-escort-temporarily-aborted + waypoint.add_task(ControlledTask(EngageTargets( + # TODO: From doctrine. + max_distance=nm_to_meter(30), + targets=[Targets.All.Air.Planes.Fighters] + ))) + + # We could set this task to end at the split point. pydcs doesn't + # currently support that task end condition though, and we don't really + # need it. + class LandingPointBuilder(PydcsWaypointBuilder): def build(self) -> MovingPoint: diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 82a25226..f47a8489 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -52,6 +52,7 @@ class FlightWaypointType(Enum): JOIN = 16 SPLIT = 17 LOITER = 18 + INGRESS_ESCORT = 19 class PredefinedWaypointCategory(Enum): diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index ed2b7bb0..e89d2f53 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -132,7 +132,7 @@ class FlightPlanBuilder: if not isinstance(location, TheaterGroundObject): raise InvalidObjectiveLocation(flight.flight_type, location) - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.ascent(flight.from_cp) builder.hold(self._hold_point(flight)) builder.join(self.package.waypoints.join) @@ -222,7 +222,7 @@ class FlightPlanBuilder: ) start = end.point_from_heading(heading - 180, diameter) - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.ascent(flight.from_cp) builder.race_track(start, end, patrol_alt) builder.rtb(flight.from_cp) @@ -264,7 +264,7 @@ class FlightPlanBuilder: orbit1p = orbit_center.point_from_heading(heading + 180, radius) # Create points - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.ascent(flight.from_cp) builder.hold(self._hold_point(flight)) builder.join(self.package.waypoints.join) @@ -290,7 +290,7 @@ class FlightPlanBuilder: if custom_targets is None: custom_targets = [] - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.ascent(flight.from_cp) builder.hold(self._hold_point(flight)) builder.join(self.package.waypoints.join) @@ -328,17 +328,12 @@ class FlightPlanBuilder: def generate_escort(self, flight: Flight) -> None: assert self.package.waypoints is not None - patrol_alt = random.randint( - self.doctrine.min_patrol_altitude, - self.doctrine.max_patrol_altitude - ) - - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.ascent(flight.from_cp) builder.hold(self._hold_point(flight)) builder.join(self.package.waypoints.join) - builder.race_track(self.package.waypoints.ingress, - self.package.waypoints.egress, patrol_alt) + builder.escort(self.package.waypoints.ingress, + self.package.target, self.package.waypoints.egress) builder.split(self.package.waypoints.split) builder.rtb(flight.from_cp) @@ -366,7 +361,7 @@ class FlightPlanBuilder: center = ingress.point_from_heading(heading, distance / 2) egress = ingress.point_from_heading(heading, distance) - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.ascent(flight.from_cp, is_helo) builder.hold(self._hold_point(flight)) builder.join(self.package.waypoints.join) @@ -379,33 +374,39 @@ class FlightPlanBuilder: flight.points = builder.build() # TODO: Make a model for the waypoint builder and use that in the UI. - def generate_ascend_point(self, departure: ControlPoint) -> FlightWaypoint: + def generate_ascend_point(self, flight: Flight, + departure: ControlPoint) -> FlightWaypoint: """Generate ascend point. Args: + flight: The flight to generate the descend point for. departure: Departure airfield or carrier. """ - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.ascent(departure) return builder.build()[0] - def generate_descend_point(self, arrival: ControlPoint) -> FlightWaypoint: + def generate_descend_point(self, flight: Flight, + arrival: ControlPoint) -> FlightWaypoint: """Generate approach/descend point. Args: + flight: The flight to generate the descend point for. arrival: Arrival airfield or carrier. """ - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.descent(arrival) return builder.build()[0] - def generate_rtb_waypoint(self, arrival: ControlPoint) -> FlightWaypoint: + def generate_rtb_waypoint(self, flight: Flight, + arrival: ControlPoint) -> FlightWaypoint: """Generate RTB landing point. Args: + flight: The flight to generate the landing waypoint for. arrival: Arrival airfield or carrier. """ - builder = WaypointBuilder(self.doctrine) + builder = WaypointBuilder(flight, self.doctrine) builder.land(arrival) return builder.build()[0] diff --git a/gen/flights/traveltime.py b/gen/flights/traveltime.py index 1b1c5622..8fb8b7af 100644 --- a/gen/flights/traveltime.py +++ b/gen/flights/traveltime.py @@ -22,12 +22,14 @@ CAP_DURATION = 30 # Minutes INGRESS_TYPES = { FlightWaypointType.INGRESS_CAS, + FlightWaypointType.INGRESS_ESCORT, FlightWaypointType.INGRESS_SEAD, FlightWaypointType.INGRESS_STRIKE, } IP_TYPES = { FlightWaypointType.INGRESS_CAS, + FlightWaypointType.INGRESS_ESCORT, FlightWaypointType.INGRESS_SEAD, FlightWaypointType.INGRESS_STRIKE, FlightWaypointType.PATROL_TRACK, diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index fd4b5aed..5ee8820c 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -8,11 +8,12 @@ from dcs.unit import Unit from game.data.doctrine import Doctrine from game.utils import nm_to_meter from theater import ControlPoint, MissionTarget, TheaterGroundObject -from .flight import FlightWaypoint, FlightWaypointType +from .flight import Flight, FlightWaypoint, FlightWaypointType class WaypointBuilder: - def __init__(self, doctrine: Doctrine) -> None: + def __init__(self, flight: Flight, doctrine: Doctrine) -> None: + self.flight = flight self.doctrine = doctrine self.waypoints: List[FlightWaypoint] = [] self.ingress_point: Optional[FlightWaypoint] = None @@ -127,6 +128,9 @@ class WaypointBuilder: def ingress_cas(self, position: Point, objective: MissionTarget) -> None: self._ingress(FlightWaypointType.INGRESS_CAS, position, objective) + def ingress_escort(self, position: Point, objective: MissionTarget) -> None: + self._ingress(FlightWaypointType.INGRESS_ESCORT, position, objective) + def ingress_sead(self, position: Point, objective: MissionTarget) -> None: self._ingress(FlightWaypointType.INGRESS_SEAD, position, objective) @@ -199,6 +203,9 @@ class WaypointBuilder: waypoint.description = description waypoint.pretty_name = description waypoint.name = name + # The target waypoints are only for the player's benefit. AI tasks for + # the target are set on the ingress point so they begin their attack + # *before* reaching the target. waypoint.only_for_player = True self.waypoints.append(waypoint) # TODO: This seems wrong, but it's what was there before. @@ -231,6 +238,9 @@ class WaypointBuilder: waypoint.description = name waypoint.pretty_name = name waypoint.name = name + # The target waypoints are only for the player's benefit. AI tasks for + # the target are set on the ingress point so they begin their attack + # *before* reaching the target. waypoint.only_for_player = True self.waypoints.append(waypoint) # TODO: This seems wrong, but it's what was there before. @@ -305,3 +315,33 @@ class WaypointBuilder: """ self.descent(arrival, is_helo) self.land(arrival) + + def escort(self, ingress: Point, target: MissionTarget, + egress: Point) -> None: + """Creates the waypoints needed to escort the package. + + Args: + ingress: The package ingress point. + target: The mission target. + egress: The package egress point. + """ + # This would preferably be no points at all, and instead the Escort task + # would begin on the join point and end on the split point, however the + # escort task does not appear to work properly (see the longer + # description in gen.aircraft.JoinPointBuilder), so instead we give + # the escort flights a flight plan including the ingress point, target + # area, and egress point. + self._ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target) + + waypoint = FlightWaypoint( + FlightWaypointType.TARGET_GROUP_LOC, + target.position.x, + target.position.y, + self.doctrine.ingress_altitude + ) + waypoint.name = "TARGET" + waypoint.description = "Escort the package" + waypoint.pretty_name = "Target area" + self.waypoints.append(waypoint) + + self.egress(egress, target) diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py index 21a85a84..fc311120 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py @@ -111,19 +111,22 @@ class QFlightWaypointTab(QFrame): self.subwindow.show() def on_ascend_waypoint(self): - ascend = self.planner.generate_ascend_point(self.flight.from_cp) + ascend = self.planner.generate_ascend_point(self.flight, + self.flight.from_cp) self.flight.points.append(ascend) self.flight_waypoint_list.update_list() self.on_change() def on_rtb_waypoint(self): - rtb = self.planner.generate_rtb_waypoint(self.flight.from_cp) + rtb = self.planner.generate_rtb_waypoint(self.flight, + self.flight.from_cp) self.flight.points.append(rtb) self.flight_waypoint_list.update_list() self.on_change() def on_descend_waypoint(self): - descend = self.planner.generate_descend_point(self.flight.from_cp) + descend = self.planner.generate_descend_point(self.flight, + self.flight.from_cp) self.flight.points.append(descend) self.flight_waypoint_list.update_list() self.on_change()