From a1886e37f82beb0f6ebab4c2dbe0d7a0d0cec21e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 4 Oct 2020 12:48:35 -0700 Subject: [PATCH 01/39] Fix save issues after aborting mission. When the mission is aborted the pending mission is still in the event list, which is part of the game option. That event has a reference to the operation, which in turn contains all the mission generator objects. Two of these objects are the radio/TACAN allocators, which use a generator to track the next free channel. Generators cannot be picked, so because these are transitively part of the game object the game cannot be saved. Aside from the briefing generator, none of those objects are actually needed outside the generation function itself, so just make them locals instead. This probably needs a larger refactor at some point. It doesn't look like we need so many calls into the operation type (it has an initialize, a prepare, and a generate, and it doesn't seem to need anything but the last one). The only reason breifinggen needs to remain a part of the class is because the briefing title and description are filled in from the derived class, where title and description should probably be overridden properties instead. I'm also not sure if we need to make the event list a part of game at all, and also don't think that the mission needs to be one of these events. --- game/operation/operation.py | 98 ++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/game/operation/operation.py b/game/operation/operation.py index c0ce5e67..b23c219e 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -68,26 +68,8 @@ class Operation: def initialize(self, mission: Mission, conflict: Conflict): self.current_mission = mission self.conflict = conflict - self.radio_registry = RadioRegistry() - self.tacan_registry = TacanRegistry() - self.airgen = AircraftConflictGenerator( - mission, conflict, self.game.settings, self.game, - self.radio_registry) - self.airsupportgen = AirSupportConflictGenerator( - mission, conflict, self.game, self.radio_registry, - self.tacan_registry) - self.triggersgen = TriggersGenerator(mission, conflict, self.game) - self.visualgen = VisualGenerator(mission, conflict, self.game) - self.envgen = EnviromentGenerator(mission, conflict, self.game) - self.forcedoptionsgen = ForcedOptionsGenerator(mission, conflict, self.game) - self.groundobjectgen = GroundObjectsGenerator( - mission, - conflict, - self.game, - self.radio_registry, - self.tacan_registry - ) - self.briefinggen = BriefingGenerator(mission, conflict, self.game) + self.briefinggen = BriefingGenerator(self.current_mission, + self.conflict, self.game) def prepare(self, terrain: Terrain, is_quick: bool): with open("resources/default_options.lua", "r") as f: @@ -127,6 +109,9 @@ class Operation: self.defenders_starting_position = self.to_cp.at def generate(self): + radio_registry = RadioRegistry() + tacan_registry = TacanRegistry() + # Dedup beacon/radio frequencies, since some maps have some frequencies # used multiple times. beacons = load_beacons_for_terrain(self.game.theater.terrain.name) @@ -138,7 +123,7 @@ class Operation: logging.error( f"TACAN beacon has no channel: {beacon.callsign}") else: - self.tacan_registry.reserve(beacon.tacan_channel) + tacan_registry.reserve(beacon.tacan_channel) for airfield, data in AIRFIELD_DATA.items(): if data.theater == self.game.theater.terrain.name: @@ -150,16 +135,26 @@ class Operation: # beacon list. for frequency in unique_map_frequencies: - self.radio_registry.reserve(frequency) + radio_registry.reserve(frequency) # Generate meteo + envgen = EnviromentGenerator(self.current_mission, self.conflict, + self.game) if self.environment_settings is None: - self.environment_settings = self.envgen.generate() + self.environment_settings = envgen.generate() else: - self.envgen.load(self.environment_settings) + envgen.load(self.environment_settings) # Generate ground object first - self.groundobjectgen.generate() + + groundobjectgen = GroundObjectsGenerator( + self.current_mission, + self.conflict, + self.game, + radio_registry, + tacan_registry + ) + groundobjectgen.generate() # Generate destroyed units for d in self.game.get_destroyed_units(): @@ -180,11 +175,16 @@ class Operation: dead=True, ) - # Air Support (Tanker & Awacs) - self.airsupportgen.generate(self.is_awacs_enabled) + airsupportgen = AirSupportConflictGenerator( + self.current_mission, self.conflict, self.game, radio_registry, + tacan_registry) + airsupportgen.generate(self.is_awacs_enabled) # Generate Activity on the map + airgen = AircraftConflictGenerator( + self.current_mission, self.conflict, self.game.settings, self.game, + radio_registry) for cp in self.game.theater.controlpoints: side = cp.captured if side: @@ -192,11 +192,11 @@ class Operation: else: country = self.current_mission.country(self.game.enemy_country) if cp.id in self.game.planners.keys(): - self.airgen.generate_flights( + airgen.generate_flights( cp, country, self.game.planners[cp.id], - self.groundobjectgen.runways + groundobjectgen.runways ) # Generate ground units on frontline everywhere @@ -221,18 +221,20 @@ class Operation: self.current_mission.groundControl.red_tactical_commander = self.ca_slots # Triggers - if self.game.is_player_attack(self.conflict.attackers_country): - cp = self.conflict.from_cp - else: - cp = self.conflict.to_cp - self.triggersgen.generate() + triggersgen = TriggersGenerator(self.current_mission, self.conflict, + self.game) + triggersgen.generate() # Options - self.forcedoptionsgen.generate() + forcedoptionsgen = ForcedOptionsGenerator(self.current_mission, + self.conflict, self.game) + forcedoptionsgen.generate() # Generate Visuals Smoke Effects + visualgen = VisualGenerator(self.current_mission, self.conflict, + self.game) if self.game.settings.perf_smoke_gen: - self.visualgen.generate() + visualgen.generate() # Inject Plugins Lua Scripts listOfPluginsScripts = [] @@ -327,19 +329,20 @@ class Operation: trigger.add_action(DoScript(String(lua))) self.current_mission.triggerrules.triggers.append(trigger) - self.assign_channels_to_flights() + self.assign_channels_to_flights(airgen.flights, + airsupportgen.air_support) kneeboard_generator = KneeboardGenerator(self.current_mission) - for dynamic_runway in self.groundobjectgen.runways.values(): + for dynamic_runway in groundobjectgen.runways.values(): self.briefinggen.add_dynamic_runway(dynamic_runway) - for tanker in self.airsupportgen.air_support.tankers: + for tanker in airsupportgen.air_support.tankers: self.briefinggen.add_tanker(tanker) kneeboard_generator.add_tanker(tanker) if self.is_awacs_enabled: - for awacs in self.airsupportgen.air_support.awacs: + for awacs in airsupportgen.air_support.awacs: self.briefinggen.add_awacs(awacs) kneeboard_generator.add_awacs(awacs) @@ -347,21 +350,23 @@ class Operation: self.briefinggen.add_jtac(jtac) kneeboard_generator.add_jtac(jtac) - for flight in self.airgen.flights: + for flight in airgen.flights: self.briefinggen.add_flight(flight) kneeboard_generator.add_flight(flight) self.briefinggen.generate() kneeboard_generator.generate() - def assign_channels_to_flights(self) -> None: + def assign_channels_to_flights(self, flights: List[FlightData], + air_support: AirSupport) -> None: """Assigns preset radio channels for client flights.""" - for flight in self.airgen.flights: + for flight in flights: if not flight.client_units: continue - self.assign_channels_to_flight(flight) + self.assign_channels_to_flight(flight, air_support) - def assign_channels_to_flight(self, flight: FlightData) -> None: + def assign_channels_to_flight(self, flight: FlightData, + air_support: AirSupport) -> None: """Assigns preset radio channels for a client flight.""" airframe = flight.aircraft_type @@ -372,4 +377,5 @@ class Operation: return aircraft_data.channel_allocator.assign_channels_for_flight( - flight, self.airsupportgen.air_support) + flight, air_support + ) From f5c32c6b98192829a354ee47ea7660d7e07a3a4e Mon Sep 17 00:00:00 2001 From: Khopa Date: Mon, 5 Oct 2020 19:26:07 +0200 Subject: [PATCH 02/39] Version 2.1.5 --- changelog.md | 5 +++++ qt_ui/uiconstants.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 59a20d76..11eef5d0 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +# 2.1.5 + +## Fixes : +* **[UI]** Fixed an issue that prevent saving after aborting a mission + # 2.1.4 ## Fixes : diff --git a/qt_ui/uiconstants.py b/qt_ui/uiconstants.py index 30f61ef1..1c6e84bc 100644 --- a/qt_ui/uiconstants.py +++ b/qt_ui/uiconstants.py @@ -8,7 +8,7 @@ from game.event import UnitsDeliveryEvent, FrontlineAttackEvent from theater.theatergroundobject import CATEGORY_MAP from userdata.liberation_theme import get_theme_icons -VERSION_STRING = "2.1.4" +VERSION_STRING = "2.1.5" URLS : Dict[str, str] = { "Manual": "https://github.com/khopa/dcs_liberation/wiki", From c41ecb6735118af57f5ac0277b450d0946f3a9cd Mon Sep 17 00:00:00 2001 From: Khopa Date: Sun, 4 Oct 2020 14:27:13 +0200 Subject: [PATCH 03/39] Fix aircrafts landing point --- gen/aircraft.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gen/aircraft.py b/gen/aircraft.py index ec5dec26..fb485e0a 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -952,6 +952,7 @@ class AircraftConflictGenerator: # pt.tasks.append(engagetgt) elif point.waypoint_type == FlightWaypointType.LANDING_POINT: pt.type = "Land" + pt.action = PointAction.Landing elif point.waypoint_type == FlightWaypointType.INGRESS_STRIKE: if group.units[0].unit_type == B_17G: From 262ba6c113c4e83e86a9b635a13b07f6cffb3b14 Mon Sep 17 00:00:00 2001 From: Khopa Date: Mon, 5 Oct 2020 19:28:55 +0200 Subject: [PATCH 04/39] Chery picked another fix for this release, so updated changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 11eef5d0..918abd4d 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,7 @@ ## Fixes : * **[UI]** Fixed an issue that prevent saving after aborting a mission +* **[Mission Generator]** Fixed aircraft landing point type being wrong # 2.1.4 From efbc6fe3ae0585e4766047782ffed4131cc373bc Mon Sep 17 00:00:00 2001 From: Khopa Date: Sat, 3 Oct 2020 16:18:12 +0200 Subject: [PATCH 05/39] Enable EPLRS for ground units that can use it. --- gen/armor.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gen/armor.py b/gen/armor.py index 5042149f..10a54b4b 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -183,6 +183,11 @@ class GroundConflictGenerator: return for dcs_group, group in ally_groups: + + if hasattr(group.unit_type, 'eplrs'): + if group.unit_type.eplrs: + group.points[0].tasks.append(EPLRS(group.id)) + if group.role == CombatGroupRole.ARTILLERY: # Fire on any ennemy in range if self.game.settings.perf_artillery: From 819d7752825f440bc87145fbc2bfadd2f7dc5e0e Mon Sep 17 00:00:00 2001 From: Khopa Date: Sun, 4 Oct 2020 14:11:28 +0200 Subject: [PATCH 06/39] EPLRS for 2.1.5 --- gen/groundobjectsgen.py | 4 +++ pydcs_extensions/frenchpack/frenchpack.py | 42 +++++++++++------------ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index d1cd5378..ddf2706e 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -80,6 +80,10 @@ class GroundObjectsGenerator: vehicle.heading = u.heading vehicle.player_can_drive = True vg.add_unit(vehicle) + + if hasattr(utype, 'eplrs'): + if utype.eplrs: + vg.points[0].tasks.append(EPLRS(vg.id)) else: vg = self.m.ship_group(side, g.name, utype, position=g.position, heading=g.units[0].heading) diff --git a/pydcs_extensions/frenchpack/frenchpack.py b/pydcs_extensions/frenchpack/frenchpack.py index 12d51827..2d5f5433 100644 --- a/pydcs_extensions/frenchpack/frenchpack.py +++ b/pydcs_extensions/frenchpack/frenchpack.py @@ -25,7 +25,7 @@ class ERC_90(unittype.VehicleType): detection_range = 0 threat_range = 4000 air_weapon_dist = 4000 - eprls = True + eplrs = True class VAB__50(unittype.VehicleType): @@ -34,7 +34,7 @@ class VAB__50(unittype.VehicleType): detection_range = 0 threat_range = 1200 air_weapon_dist = 1200 - eprls = True + eplrs = True class VAB_T20_13(unittype.VehicleType): @@ -43,7 +43,7 @@ class VAB_T20_13(unittype.VehicleType): detection_range = 0 threat_range = 2000 air_weapon_dist = 2000 - eprls = True + eplrs = True class VAB_MEPHISTO(unittype.VehicleType): @@ -52,7 +52,7 @@ class VAB_MEPHISTO(unittype.VehicleType): detection_range = 0 threat_range = 4000 air_weapon_dist = 4000 - eprls = True + eplrs = True class VBL__50(unittype.VehicleType): @@ -61,7 +61,7 @@ class VBL__50(unittype.VehicleType): detection_range = 0 threat_range = 1200 air_weapon_dist = 1200 - eprls = True + eplrs = True class VBL_AANF1(unittype.VehicleType): @@ -70,7 +70,7 @@ class VBL_AANF1(unittype.VehicleType): detection_range = 0 threat_range = 1000 air_weapon_dist = 1000 - eprls = True + eplrs = True class VBAE_CRAB(unittype.VehicleType): @@ -79,7 +79,7 @@ class VBAE_CRAB(unittype.VehicleType): detection_range = 0 threat_range = 3500 air_weapon_dist = 3500 - eprls = True + eplrs = True class VBAE_CRAB_MMP(unittype.VehicleType): @@ -88,7 +88,7 @@ class VBAE_CRAB_MMP(unittype.VehicleType): detection_range = 0 threat_range = 3500 air_weapon_dist = 3500 - eprls = True + eplrs = True class AMX_30B2(unittype.VehicleType): @@ -121,7 +121,7 @@ class DIM__TOYOTA_BLUE(unittype.VehicleType): detection_range = 0 threat_range = 1200 air_weapon_dist = 1200 - eprls = True + eplrs = True class DIM__TOYOTA_GREEN(unittype.VehicleType): @@ -130,7 +130,7 @@ class DIM__TOYOTA_GREEN(unittype.VehicleType): detection_range = 0 threat_range = 1200 air_weapon_dist = 1200 - eprls = True + eplrs = True class DIM__TOYOTA_DESERT(unittype.VehicleType): @@ -139,7 +139,7 @@ class DIM__TOYOTA_DESERT(unittype.VehicleType): detection_range = 0 threat_range = 1200 air_weapon_dist = 1200 - eprls = True + eplrs = True class DIM__KAMIKAZE(unittype.VehicleType): @@ -148,7 +148,7 @@ class DIM__KAMIKAZE(unittype.VehicleType): detection_range = 0 threat_range = 50 air_weapon_dist = 50 - eprls = True + eplrs = True ## FORTIFICATION @@ -187,7 +187,7 @@ class TRM_2000(unittype.VehicleType): detection_range = 3500 threat_range = 0 air_weapon_dist = 0 - eprls = True + eplrs = True class TRM_2000_Fuel(unittype.VehicleType): id = "TRM2000_Citerne" @@ -195,7 +195,7 @@ class TRM_2000_Fuel(unittype.VehicleType): detection_range = 3500 threat_range = 0 air_weapon_dist = 0 - eprls = True + eplrs = True class VAB_MEDICAL(unittype.VehicleType): id = "VABH" @@ -203,7 +203,7 @@ class VAB_MEDICAL(unittype.VehicleType): detection_range = 0 threat_range = 0 air_weapon_dist = 0 - eprls = True + eplrs = True class VAB(unittype.VehicleType): id = "VAB_RADIO" @@ -211,7 +211,7 @@ class VAB(unittype.VehicleType): detection_range = 0 threat_range = 0 air_weapon_dist = 0 - eprls = True + eplrs = True class VBL(unittype.VehicleType): id = "VBL-Radio" @@ -219,7 +219,7 @@ class VBL(unittype.VehicleType): detection_range = 0 threat_range = 0 air_weapon_dist = 0 - eprls = True + eplrs = True class Tracma_TD_1500(unittype.VehicleType): id = "Tracma" @@ -236,7 +236,7 @@ class SMOKE_SAM_IR(unittype.VehicleType): detection_range = 20000 threat_range = 20000 air_weapon_dist = 20000 - eprls = True + eplrs = True class _53T2(unittype.VehicleType): id = "AA20" @@ -251,7 +251,7 @@ class TRM_2000_53T2(unittype.VehicleType): detection_range = 6000 threat_range = 2000 air_weapon_dist = 2000 - eprls = True + eplrs = True class TRM_2000_PAMELA(unittype.VehicleType): id = "TRMMISTRAL" @@ -259,7 +259,7 @@ class TRM_2000_PAMELA(unittype.VehicleType): detection_range = 8000 threat_range = 10000 air_weapon_dist = 10000 - eprls = True + eplrs = True ## INFANTRY @@ -285,4 +285,4 @@ class VAB_MORTIER(unittype.VehicleType): detection_range = 0 threat_range = 15000 air_weapon_dist = 15000 - eprls = True \ No newline at end of file + eplrs = True \ No newline at end of file From 2a3bf9821b2c26c0c76e50c569e35e5a37aa34e4 Mon Sep 17 00:00:00 2001 From: Khopa Date: Mon, 5 Oct 2020 19:33:48 +0200 Subject: [PATCH 07/39] Fixed EPLRS cherry pick merge. --- changelog.md | 3 +++ gen/armor.py | 6 +++--- pydcs | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 918abd4d..e2c69706 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,8 @@ # 2.1.5 +## Features/Improvements : +* **[Units/Factions]** Enabled EPLRS for ground units that supports it (so they appear on A-10C II TAD and Helmet) + ## Fixes : * **[UI]** Fixed an issue that prevent saving after aborting a mission * **[Mission Generator]** Fixed aircraft landing point type being wrong diff --git a/gen/armor.py b/gen/armor.py index 10a54b4b..463d7571 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -184,9 +184,9 @@ class GroundConflictGenerator: for dcs_group, group in ally_groups: - if hasattr(group.unit_type, 'eplrs'): - if group.unit_type.eplrs: - group.points[0].tasks.append(EPLRS(group.id)) + if hasattr(group.units[0], 'eplrs'): + if group.units[0].eplrs: + dcs_group.points[0].tasks.append(EPLRS(dcs_group.id)) if group.role == CombatGroupRole.ARTILLERY: # Fire on any ennemy in range diff --git a/pydcs b/pydcs index ceea62a8..c203e5a1 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit ceea62a8e0731c21b3e1a3e90682aa0affc168f1 +Subproject commit c203e5a1b8d5eb42d559dab074e668bf37fa5158 From 73a97f9c2ac2ddb51f76b0c1f987399b49a6046f Mon Sep 17 00:00:00 2001 From: Khopa Date: Sat, 14 Nov 2020 20:16:13 +0100 Subject: [PATCH 08/39] Added High Digit Sams mods pydcs export extensions, maybe for later use. --- .../highdigitsams/highdigitsams.py | 169 ++++++++++++++++++ pydcs_extensions/mod_units.py | 24 ++- 2 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 pydcs_extensions/highdigitsams/highdigitsams.py diff --git a/pydcs_extensions/highdigitsams/highdigitsams.py b/pydcs_extensions/highdigitsams/highdigitsams.py new file mode 100644 index 00000000..3ff206d0 --- /dev/null +++ b/pydcs_extensions/highdigitsams/highdigitsams.py @@ -0,0 +1,169 @@ +from dcs import unittype + + +class SAM_SA_20_S_300PMU1_TR_30N6E(unittype.VehicleType): + id = "S-300PMU1 40B6M tr" + name = "SAM SA-20 S-300PMU1 TR 30N6E" + detection_range = 160000 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_20_S_300PMU1_TR_30N6E_truck(unittype.VehicleType): + id = "S-300PMU1 30N6E tr" + name = "SAM SA-20 S-300PMU1 TR 30N6E(truck)" + detection_range = 160000 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_20_S_300PMU1_SR_5N66E(unittype.VehicleType): + id = "S-300PMU1 40B6MD sr" + name = "SAM SA-20 S-300PMU1 SR 5N66E" + detection_range = 120000 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_20_S_300PMU1_SR_64N6E(unittype.VehicleType): + id = "S-300PMU1 64N6E sr" + name = "SAM SA-20 S-300PMU1 SR 64N6E" + detection_range = 300000 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_23_S_300VM_9S15M2_SR(unittype.VehicleType): + id = "S-300VM 9S15M2 sr" + name = "SAM SA-23 S-300VM 9S15M2 SR" + detection_range = 320000 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_23_S_300VM_9S19M2_SR(unittype.VehicleType): + id = "S-300VM 9S19M2 sr" + name = "SAM SA-23 S-300VM 9S19M2 SR" + detection_range = 310000 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_23_S_300VM_9S32ME_TR(unittype.VehicleType): + id = "S-300VM 9S32ME tr" + name = "SAM SA-23 S-300VM 9S32ME TR" + detection_range = 230000 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_20_S_300PMU1_LN_5P85CE(unittype.VehicleType): + id = "S-300PMU1 5P85CE ln" + name = "SAM SA-20 S-300PMU1 LN 5P85CE" + detection_range = 0 + threat_range = 150000 + air_weapon_dist = 150000 + + +class SAM_SA_20_S_300PMU1_LN_5P85DE(unittype.VehicleType): + id = "S-300PMU1 5P85DE ln" + name = "SAM SA-20 S-300PMU1 LN 5P85DE" + detection_range = 0 + threat_range = 150000 + air_weapon_dist = 150000 + + +class SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE(unittype.VehicleType): + id = "S-300PS 5P85CE ln" + name = "SAM SA-10 (5V55RUD) S-300PS LN 5P85CE" + detection_range = 0 + threat_range = 90000 + air_weapon_dist = 90000 + + +class SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE(unittype.VehicleType): + id = "S-300PS 5P85DE ln" + name = "SAM SA-10 (5V55RUD) S-300PS LN 5P85DE" + detection_range = 0 + threat_range = 90000 + air_weapon_dist = 90000 + + +class SAM_SA_23_S_300VM_9A83ME_LN(unittype.VehicleType): + id = "S-300VM 9A83ME ln" + name = "SAM SA-23 S-300VM 9A83ME LN" + detection_range = 0 + threat_range = 90000 + air_weapon_dist = 90000 + + +class SAM_SA_23_S_300VM_9A82ME_LN(unittype.VehicleType): + id = "S-300VM 9A82ME ln" + name = "SAM SA-23 S-300VM 9A82ME LN" + detection_range = 0 + threat_range = 200000 + air_weapon_dist = 200000 + + +class SAM_SA_17_Buk_M1_2_LN_9A310M1_2(unittype.VehicleType): + id = "SA-17 Buk M1-2 LN 9A310M1-2" + name = "SAM SA-17 Buk M1-2 LN 9A310M1-2" + detection_range = 120000 + threat_range = 50000 + air_weapon_dist = 50000 + + +class SAM_SA_2__V759__LN_SM_90(unittype.VehicleType): + id = "S_75M_Volhov_V759" + name = "SAM SA-2 (V759) LN SM-90" + detection_range = 0 + threat_range = 50000 + air_weapon_dist = 50000 + + +class SAM_HQ_2_LN_SM_90(unittype.VehicleType): + id = "HQ_2_Guideline_LN" + name = "SAM HQ-2 LN SM-90" + detection_range = 0 + threat_range = 50000 + air_weapon_dist = 50000 + + +class SAM_SA_3__V_601P__LN_5P73(unittype.VehicleType): + id = "5p73 V-601P ln" + name = "SAM SA-3 (V-601P) LN 5P73" + detection_range = 0 + threat_range = 18000 + air_weapon_dist = 18000 + + +class SAM_SA_20_S_300PMU1_CP_54K6(unittype.VehicleType): + id = "S-300PMU1 54K6 cp" + name = "SAM SA-20 S-300PMU1 CP 54K6" + detection_range = 0 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_23_S_300VM_9S457ME_CP(unittype.VehicleType): + id = "S-300VM 9S457ME cp" + name = "SAM SA-23 S-300VM 9S457ME CP" + detection_range = 0 + threat_range = 0 + air_weapon_dist = 0 + + +class SAM_SA_24_Igla_S_manpad(unittype.VehicleType): + id = "SA-24 Igla-S manpad" + name = "SAM SA-24 Igla-S manpad" + detection_range = 5000 + threat_range = 6000 + air_weapon_dist = 6000 + + +class SAM_SA_14_Strela_3_manpad(unittype.VehicleType): + id = "SA-14 Strela-3 manpad" + name = "SAM SA-14 Strela-3 manpad" + detection_range = 5000 + threat_range = 4500 + air_weapon_dist = 4500 diff --git a/pydcs_extensions/mod_units.py b/pydcs_extensions/mod_units.py index 132a7b2f..cfa1b321 100644 --- a/pydcs_extensions/mod_units.py +++ b/pydcs_extensions/mod_units.py @@ -1,4 +1,5 @@ from pydcs_extensions.a4ec.a4ec import A_4E_C +from pydcs_extensions.highdigitsams import highdigitsams from pydcs_extensions.mb339.mb339 import MB_339PAN from pydcs_extensions.rafale.rafale import Rafale_M, Rafale_A_S from pydcs_extensions.su57.su57 import Su_57 @@ -39,5 +40,26 @@ MODDED_VEHICLES = [ frenchpack.DIM__TOYOTA_BLUE, frenchpack.DIM__TOYOTA_GREEN, frenchpack.DIM__TOYOTA_DESERT, - frenchpack.DIM__KAMIKAZE + frenchpack.DIM__KAMIKAZE, + highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E, + highdigitsams.SAM_SA_20_S_300PMU1_TR_30N6E_truck, + highdigitsams.SAM_SA_20_S_300PMU1_SR_5N66E, + highdigitsams.SAM_SA_20_S_300PMU1_SR_64N6E, + highdigitsams.SAM_SA_23_S_300VM_9S15M2_SR, + highdigitsams.SAM_SA_23_S_300VM_9S19M2_SR, + highdigitsams.SAM_SA_23_S_300VM_9S32ME_TR, + highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85CE, + highdigitsams.SAM_SA_20_S_300PMU1_LN_5P85DE, + highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85CE, + highdigitsams.SAM_SA_10__5V55RUD__S_300PS_LN_5P85DE, + highdigitsams.SAM_SA_23_S_300VM_9A83ME_LN, + highdigitsams.SAM_SA_23_S_300VM_9A82ME_LN, + highdigitsams.SAM_SA_17_Buk_M1_2_LN_9A310M1_2, + highdigitsams.SAM_SA_2__V759__LN_SM_90, + highdigitsams.SAM_HQ_2_LN_SM_90, + highdigitsams.SAM_SA_3__V_601P__LN_5P73, + highdigitsams.SAM_SA_20_S_300PMU1_CP_54K6, + highdigitsams.SAM_SA_23_S_300VM_9S457ME_CP, + highdigitsams.SAM_SA_24_Igla_S_manpad, + highdigitsams.SAM_SA_14_Strela_3_manpad ] \ No newline at end of file From 3987274764f62d56abb6cd01a0dc8a97336a452b Mon Sep 17 00:00:00 2001 From: Khopa Date: Sat, 14 Nov 2020 21:24:15 +0100 Subject: [PATCH 09/39] Pulled latest pydcs version --- pydcs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydcs b/pydcs index 8e74bfb6..fa9195fb 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit 8e74bfb61b829477d35a80148fc295f2158902dd +Subproject commit fa9195fbccbf96775d108a22c13c3ee2375e4c0b From 16cfc4e945bf9825edd46c305c4aa77e8ef5d33b Mon Sep 17 00:00:00 2001 From: Khopa Date: Sat, 14 Nov 2020 21:29:03 +0100 Subject: [PATCH 10/39] Changelog update for 2.2.0 --- changelog.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index f3f99b7b..df66d42d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# 2.2.X +# 2.2.0 ## Features/Improvements : * **[Campaign Generator]** Added early warning radar generation @@ -13,10 +13,9 @@ * **[Map]** Highlight the selected flight path on the map * **[Map]** Improved SAM display settings * **[Map]** Improved flight plan display settings +* **[Map]** Caucasus and The Channel map use a new system to generate SAM and strike target location to reduce probability of targets generated in the middle of a forests * **[Misc]** Flexible Dedicated Hosting Options for Mission Files via environment variables * **[Moddability]** Custom campaigns can be designed through json files -* **[Moddability]** Custom campaigns can be designed through json files -* **[Moddability]** Custom factions can be designed through json files * **[Moddability]** LUA plugins can now be injected into Liberation missions. * **[Moddability]** Optional Skynet IADS lua plugin now included * **[New Game]** Starting budget can be freely selected @@ -25,8 +24,7 @@ * **[UI]** Add polygon drawing mode for map background * **[UI]** Added a warning if you press takeoff with no player enabled flights * **[UI]** Packages and flights now visible in the main window sidebar -* **[Units/Factions]** Added bombers to coalitions -* **[Units/Factions]** Added frenchpack mod units +* **[Units/Factions]** Added bombers to some coalitions * **[Units/Factions]** Added support for SU-57 mod by Cubanace * **[Units]** Added Freya EWR sites to german WW2 factions * **[Units]** Added support for many bombers (B-52H, B-1B, Tu-22, Tu-142) From f8735927bfd5cb4bfa8412802314d996f933b2d1 Mon Sep 17 00:00:00 2001 From: Khopa Date: Sat, 14 Nov 2020 21:32:27 +0100 Subject: [PATCH 11/39] Version string 2.2.0 --- game/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game/version.py b/game/version.py index e173526f..b9b97187 100644 --- a/game/version.py +++ b/game/version.py @@ -2,7 +2,7 @@ from pathlib import Path #: Current version of Liberation. -VERSION = "2.2.0-preview" +VERSION = "2.2.0" if Path("buildnumber").exists(): with open("buildnumber", "r") as file: VERSION += f"-{file.readline()}" From fad132dccac702698ce164029943e499e6260939 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 14 Nov 2020 12:37:22 -0800 Subject: [PATCH 12/39] Fix versioning for release builds. (cherry picked from commit 9019cbfd2b715c35da10ad7ed79d44ab81e57052) --- .github/workflows/release.yml | 4 ++++ game/version.py | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21806ed4..5580f694 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,10 @@ jobs: # For some reason the shiboken2.abi3.dll is not found properly, so I copy it instead Copy-Item .\venv\Lib\site-packages\shiboken2\shiboken2.abi3.dll .\venv\Lib\site-packages\PySide2\ -Force + - name: Finalize version + run: | + New-Item -ItemType file final + - name: mypy game run: | ./venv/scripts/activate diff --git a/game/version.py b/game/version.py index b9b97187..bdcbdded 100644 --- a/game/version.py +++ b/game/version.py @@ -1,8 +1,17 @@ from pathlib import Path +def _build_version_string() -> str: + components = ["2.2.0"] + if Path("buildnumber").exists(): + with open("buildnumber", "r") as file: + components.append(file.readline()) + + if not Path("final").exists(): + components.append("preview") + + return "-".join(components) + + #: Current version of Liberation. -VERSION = "2.2.0" -if Path("buildnumber").exists(): - with open("buildnumber", "r") as file: - VERSION += f"-{file.readline()}" +VERSION = _build_version_string() From 96c401e1b911ba6737623bca037ba2f892ce56ba Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sat, 14 Nov 2020 13:01:11 -0800 Subject: [PATCH 13/39] Fix pyinstaller spec for release. final and buildnumber are optional files. Move them into resources to avoid naming them explicitly. (cherry picked from commit fae9650f56854bb3f3508ee5b2625bba8e7ad2f8) --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- game/version.py | 9 +++++---- pyinstaller.spec | 1 - 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71d5cf3c..60cbf719 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,7 +44,7 @@ jobs: - name: update build number run: | - [IO.File]::WriteAllLines($pwd.path + "\buildnumber", $env:GITHUB_RUN_NUMBER) + [IO.File]::WriteAllLines($pwd.path + "\resources\buildnumber", $env:GITHUB_RUN_NUMBER) - name: Build binaries run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5580f694..ca8a238e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,7 +31,7 @@ jobs: - name: Finalize version run: | - New-Item -ItemType file final + New-Item -ItemType file resources\final - name: mypy game run: | diff --git a/game/version.py b/game/version.py index bdcbdded..f3b1d1f4 100644 --- a/game/version.py +++ b/game/version.py @@ -3,11 +3,12 @@ from pathlib import Path def _build_version_string() -> str: components = ["2.2.0"] - if Path("buildnumber").exists(): - with open("buildnumber", "r") as file: - components.append(file.readline()) + build_number_path = Path("resources/buildnumber") + if build_number_path.exists(): + with build_number_path.open("r") as build_number_file: + components.append(build_number_file.readline()) - if not Path("final").exists(): + if not Path("resources/final").exists(): components.append("preview") return "-".join(components) diff --git a/pyinstaller.spec b/pyinstaller.spec index e545d073..839c641f 100644 --- a/pyinstaller.spec +++ b/pyinstaller.spec @@ -11,7 +11,6 @@ analysis = Analysis( ('resources', 'resources'), ('resources/caucasus.p', 'dcs/terrain/'), ('resources/nevada.p', 'dcs/terrain/'), - ('buildnumber', './') ], hookspath=[], runtime_hooks=[], From 78cd60f3df1a6c026fe022a69aa3fa8c030febd5 Mon Sep 17 00:00:00 2001 From: Khopa Date: Sun, 15 Nov 2020 15:49:51 +0100 Subject: [PATCH 14/39] Added factions made by Discord user HerrTom --- .../factions/france_2005_frenchpack.json | 84 +++++++++++++++++++ resources/factions/georgia_2008.json | 46 ++++++++++ resources/factions/usn_1985.json | 73 ++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 resources/factions/france_2005_frenchpack.json create mode 100644 resources/factions/georgia_2008.json create mode 100644 resources/factions/usn_1985.json diff --git a/resources/factions/france_2005_frenchpack.json b/resources/factions/france_2005_frenchpack.json new file mode 100644 index 00000000..d909c08b --- /dev/null +++ b/resources/factions/france_2005_frenchpack.json @@ -0,0 +1,84 @@ +{ + "country": "France", + "name": "France 2005 (Frenchpack)", + "authors": "HerrTom", + "description": "

French equipment using the Frenchpack, but without the Rafale mod.

", + "aircrafts": [ + "M_2000C", + "Mirage_2000_5", + "SA342M", + "SA342L", + "SA342Mistral" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "KC_135", + "KC130" + ], + "frontline_units": [ + "AMX_10RCR", + "AMX_10RCR_SEPAR", + "ERC_90", + "TRM_2000_PAMELA", + "VAB__50", + "VAB_MEPHISTO", + "VAB_T20_13", + "VAB_T20_13", + "VBL__50", + "VBL_AANF1", + "VBAE_CRAB", + "VBAE_CRAB_MMP", + "AMX_30B2", + "Leclerc_Serie_XXI" + ], + "artillery_units": [ + "MLRS_M270", + "SPH_M109_Paladin" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249", + "Stinger_MANPADS" + ], + "shorads": [ + "HQ7Generator", + "RolandGenerator" + ], + "sams": [ + "RolandGenerator", + "HawkGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "USS_Arleigh_Burke_IIa" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "requirements": { + "frenchpack V3.5": "https://forums.eagle.ru/showthread.php?t=279974" + }, + "carrier_names": [ + "L9013 Mistral", + "L9014 Tonerre", + "L9015 Dixmude" + ], + "helicopter_carrier_names": [ + "Jeanne d'Arc" + ], + "navy_generators": [ + "ArleighBurkeGroupGenerator" + ], + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/georgia_2008.json b/resources/factions/georgia_2008.json new file mode 100644 index 00000000..f575054c --- /dev/null +++ b/resources/factions/georgia_2008.json @@ -0,0 +1,46 @@ +{ + "country": "Georgia", + "name": "Georgia 2008", + "authors": "HerrTom", + "description": "

A faction that represents Georgia during the South Ossetian War. They will have a lot more aircraft than historically, and no real A2A capability.

", + "aircrafts": [ + "L_39ZA", + "Su_25", + "Mi_8MT", + "Mi_24V", + "UH_1H" + ], + "frontline_units": [ + "APC_BTR_80", + "APC_MTLB", + "APC_Cobra", + "IFV_BMP_1", + "IFV_BMP_2", + "MBT_T_72B", + "MBT_T_55" + ], + "artillery_units": [ + "MLRS_BM21_Grad", + "SPH_2S1_Gvozdika", + "SPH_2S3_Akatsia" + ], + "logistics_units": [ + "Transport_Ural_375", + "Transport_UAZ_469" + ], + "infantry_units": [ + "Paratrooper_AKS", + "Paratrooper_RPG_16" + ], + "shorads": [ + "SA13Generator", + "SA8Generator" + ], + "sams": [ + "SA6Generator", + "SA11Generator" + ], + "requirements": {}, + "has_jtac": true, + "jtac_unit": "MQ_9_Reaper" +} diff --git a/resources/factions/usn_1985.json b/resources/factions/usn_1985.json new file mode 100644 index 00000000..6ca2f2f1 --- /dev/null +++ b/resources/factions/usn_1985.json @@ -0,0 +1,73 @@ +{ + "country": "USA", + "name": "US Navy 1985", + "authors": "HerrTom", + "description": "

Highway to the Danger Zone! For Tomcat lovers.

", + "aircrafts": [ + "F_4E", + "F_14B", + "S_3B", + "UH_1H", + "AH_1W" + ], + "awacs": [ + "E_3A" + ], + "tankers": [ + "S_3B_Tanker" + ], + "frontline_units": [ + "MBT_M60A3_Patton", + "APC_M113", + "APC_M1025_HMMWV" + ], + "artillery_units": [ + "SPH_M109_Paladin", + "MLRS_M270" + ], + "logistics_units": [ + "Transport_M818" + ], + "infantry_units": [ + "Infantry_M4", + "Soldier_M249" + ], + "shorads": [ + "VulcanGenerator", + "ChaparralGenerator" + ], + "sams": [ + "HawkGenerator", + "ChaparralGenerator" + ], + "aircraft_carrier": [ + "CVN_74_John_C__Stennis" + ], + "helicopter_carrier": [ + "LHA_1_Tarawa" + ], + "destroyers": [ + "Oliver_Hazzard_Perry_class" + ], + "cruisers": [ + "Ticonderoga_class" + ], + "carrier_names": [ + "CVN-71 Theodore Roosevelt", + "CVN-72 Abraham Lincoln", + "CVN-73 George Washington", + "CVN-74 John C. Stennis" + ], + "helicopter_carrier_names": [ + "LHA-1 Tarawa", + "LHA-2 Saipan", + "LHA-3 Belleau Wood", + "LHA-4 Nassau", + "LHA-5 Peleliu" + ], + "navy_generators": [ + "OliverHazardPerryGroupGenerator" + ], + "requirements": {}, + "doctrine": "coldwar" +} \ No newline at end of file From 2e067aada6ccc313bffeeec046c585a25b30552d Mon Sep 17 00:00:00 2001 From: Khopa Date: Sun, 15 Nov 2020 15:57:36 +0100 Subject: [PATCH 15/39] Added full persian gulf map by Plob --- .../campaigns/persian_gulf_full_map.json | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 resources/campaigns/persian_gulf_full_map.json diff --git a/resources/campaigns/persian_gulf_full_map.json b/resources/campaigns/persian_gulf_full_map.json new file mode 100644 index 00000000..82818470 --- /dev/null +++ b/resources/campaigns/persian_gulf_full_map.json @@ -0,0 +1,137 @@ +{ + "name": "Persian Gulf - Full Map", + "theater": "Persian Gulf", + "authors": "Plob", + "description": "

In this scenario, you start at Liwa Airfield, and must work your way north through the whole map.

", + "player_points": [ + { + "type": "airbase", + "id": "Liwa Airbase", + "size": 1000, + "importance": 0.2 + }, + { + "type": "lha", + "id": 1002, + "x": -164000, + "y": -257000, + "captured_invert": true + }, + { + "type": "carrier", + "id": 1001, + "x": -124000, + "y": -303000, + "captured_invert": true + } + ], + "enemy_points": [ + { + "type": "airbase", + "id": "Al Ain International Airport", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Al Dhafra AB", + "size": 2000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Al Minhad AB", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Ras Al Khaimah", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Khasab", + "size": 1000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Bandar Abbas Intl", + "size": 2000, + "importance": 1 + }, + { + "type": "airbase", + "id": "Jiroft Airport", + "size": 2000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Kerman Airport", + "size": 2000, + "importance": 1.7, + "captured_invert": true + }, + { + "type": "airbase", + "id": "Lar Airbase", + "size": 1000, + "importance": 1.4 + }, + { + "type": "airbase", + "id": "Shiraz International Airport", + "size": 2000, + "importance": 1 + } + ], + "links": [ + [ + "Al Dhafra AB", + "Liwa Airbase" + ], + [ + "Al Dhafra AB", + "Al Ain International Airport" + ], + [ + "Al Ain International Airport", + "Al Minhad AB" + ], + [ + "Al Dhafra AB", + "Al Minhad AB" + ], + [ + "Al Minhad AB", + "Ras Al Khaimah" + ], + [ + "Khasab", + "Ras Al Khaimah" + ], + [ + "Bandar Abbas Intl", + "Lar Airbase" + ], + [ + "Shiraz International Airport", + "Lar Airbase" + ], + [ + "Shiraz International Airport", + "Kerman Airport" + ], + [ + "Jiroft Airport", + "Lar Airbase" + ], + [ + "Jiroft Airport", + "Kerman Airport" + ] + ] +} \ No newline at end of file From dfc31dfd5c8ad8a48372ebc443eb445dda123cdc Mon Sep 17 00:00:00 2001 From: Khopa Date: Sun, 15 Nov 2020 16:14:18 +0100 Subject: [PATCH 16/39] Changelog update (cherry picked from commit 8ffbf3267730c5651e4837bef09e48f68daa7158) --- changelog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/changelog.md b/changelog.md index df66d42d..a3570323 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,9 @@ +# 2.2.1 + +# Features/Improvements +* **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom +* **[Factions]** Added map Persian Gulf full by Plob + # 2.2.0 ## Features/Improvements : From 21cd764f6625db0784059a0b59a1e8a72a7a699d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Sun, 15 Nov 2020 18:28:40 -0800 Subject: [PATCH 17/39] Improve hold/split/join point positioning. This also removes ascend/descend waypoints. They don't seem to be helping at all. The AI already gets an implicit ascend waypoint (they won't go to waypoint one until they've climbed sufficiently), and forcing unnecessary sharp turns toward the possibly mispredicted ascent direction can mess with the AI. It's also yet another variable to contend with when planning hold points, and hold points do essentially the same thing. Fixes https://github.com/Khopa/dcs_liberation/issues/352. --- changelog.md | 3 ++ game/data/doctrine.py | 8 ++++ gen/flights/flightplan.py | 94 ++++++++++++++++++++++++++------------- 3 files changed, 74 insertions(+), 31 deletions(-) diff --git a/changelog.md b/changelog.md index a3570323..b5b938aa 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,9 @@ * **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom * **[Factions]** Added map Persian Gulf full by Plob +## Fixes : +* **[Flight Planner]** Hold, join, and split points are planned cautiously near enemy airfields. Ascend/descend points are no longer planned. + # 2.2.0 ## Features/Improvements : diff --git a/game/data/doctrine.py b/game/data/doctrine.py index 8d3e1a91..99bb254a 100644 --- a/game/data/doctrine.py +++ b/game/data/doctrine.py @@ -16,6 +16,8 @@ class Doctrine: sead_max_range: int rendezvous_altitude: int + hold_distance: int + push_distance: int join_distance: int split_distance: int ingress_egress_distance: int @@ -44,6 +46,8 @@ MODERN_DOCTRINE = Doctrine( strike_max_range=1500000, sead_max_range=1500000, rendezvous_altitude=feet_to_meter(25000), + hold_distance=nm_to_meter(15), + push_distance=nm_to_meter(20), join_distance=nm_to_meter(20), split_distance=nm_to_meter(20), ingress_egress_distance=nm_to_meter(45), @@ -69,6 +73,8 @@ COLDWAR_DOCTRINE = Doctrine( strike_max_range=1500000, sead_max_range=1500000, rendezvous_altitude=feet_to_meter(22000), + hold_distance=nm_to_meter(10), + push_distance=nm_to_meter(10), join_distance=nm_to_meter(10), split_distance=nm_to_meter(10), ingress_egress_distance=nm_to_meter(30), @@ -93,6 +99,8 @@ WWII_DOCTRINE = Doctrine( antiship=True, strike_max_range=1500000, sead_max_range=1500000, + hold_distance=nm_to_meter(5), + push_distance=nm_to_meter(5), join_distance=nm_to_meter(5), split_distance=nm_to_meter(5), rendezvous_altitude=feet_to_meter(10000), diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index e0df2b01..c73eb3ae 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -7,6 +7,7 @@ generating the waypoints for the mission. """ from __future__ import annotations +import math from datetime import timedelta from functools import cached_property import logging @@ -275,18 +276,14 @@ class PatrollingFlightPlan(FlightPlan): @dataclass(frozen=True) class BarCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint - ascent: FlightWaypoint - descent: FlightWaypoint land: FlightWaypoint @property def waypoints(self) -> List[FlightWaypoint]: return [ self.takeoff, - self.ascent, self.patrol_start, self.patrol_end, - self.descent, self.land, ] @@ -294,20 +291,16 @@ class BarCapFlightPlan(PatrollingFlightPlan): @dataclass(frozen=True) class CasFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint - ascent: FlightWaypoint target: FlightWaypoint - descent: FlightWaypoint land: FlightWaypoint @property def waypoints(self) -> List[FlightWaypoint]: return [ self.takeoff, - self.ascent, self.patrol_start, self.target, self.patrol_end, - self.descent, self.land, ] @@ -321,18 +314,14 @@ class CasFlightPlan(PatrollingFlightPlan): @dataclass(frozen=True) class FrontLineCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint - ascent: FlightWaypoint - descent: FlightWaypoint land: FlightWaypoint @property def waypoints(self) -> List[FlightWaypoint]: return [ self.takeoff, - self.ascent, self.patrol_start, self.patrol_end, - self.descent, self.land, ] @@ -360,28 +349,24 @@ class FrontLineCapFlightPlan(PatrollingFlightPlan): @dataclass(frozen=True) class StrikeFlightPlan(FormationFlightPlan): takeoff: FlightWaypoint - ascent: FlightWaypoint hold: FlightWaypoint join: FlightWaypoint ingress: FlightWaypoint targets: List[FlightWaypoint] egress: FlightWaypoint split: FlightWaypoint - descent: FlightWaypoint land: FlightWaypoint @property def waypoints(self) -> List[FlightWaypoint]: return [ self.takeoff, - self.ascent, self.hold, self.join, self.ingress ] + self.targets + [ self.egress, self.split, - self.descent, self.land, ] @@ -681,10 +666,8 @@ class FlightPlanBuilder: flight=flight, patrol_duration=self.doctrine.cap_duration, takeoff=builder.takeoff(flight.from_cp), - ascent=builder.ascent(flight.from_cp), patrol_start=start, patrol_end=end, - descent=descent, land=land ) @@ -736,10 +719,8 @@ class FlightPlanBuilder: # duration of the escorted mission, or until it is winchester/bingo. patrol_duration=self.doctrine.cap_duration, takeoff=builder.takeoff(flight.from_cp), - ascent=builder.ascent(flight.from_cp), patrol_start=start, patrol_end=end, - descent=descent, land=land ) @@ -805,14 +786,12 @@ class FlightPlanBuilder: package=self.package, flight=flight, takeoff=builder.takeoff(flight.from_cp), - ascent=builder.ascent(flight.from_cp), hold=builder.hold(self._hold_point(flight)), join=builder.join(self.package.waypoints.join), ingress=ingress, targets=[target], egress=egress, split=builder.split(self.package.waypoints.split), - descent=descent, land=land ) @@ -842,11 +821,9 @@ class FlightPlanBuilder: flight=flight, patrol_duration=self.doctrine.cas_duration, takeoff=builder.takeoff(flight.from_cp), - ascent=builder.ascent(flight.from_cp), patrol_start=builder.ingress_cas(ingress, location), target=builder.cas(center), patrol_end=builder.egress(egress, location), - descent=descent, land=land ) @@ -871,12 +848,50 @@ class FlightPlanBuilder: return builder.strike_area(location) def _hold_point(self, flight: Flight) -> Point: - heading = flight.from_cp.position.heading_between_point( - self.package.target.position - ) - return flight.from_cp.position.point_from_heading( - heading, nm_to_meter(15) + assert self.package.waypoints is not None + origin = flight.from_cp.position + target = self.package.target.position + join = self.package.waypoints.join + origin_to_target = origin.distance_to_point(target) + join_to_target = join.distance_to_point(target) + if origin_to_target < join_to_target: + # If the origin airfield is closer to the target than the join + # point, plan the hold point such that it retreats from the origin + # airfield. + return join.point_from_heading(target.heading_between_point(origin), + self.doctrine.push_distance) + + heading_to_join = origin.heading_between_point(join) + hold_point = origin.point_from_heading(heading_to_join, + self.doctrine.push_distance) + if hold_point.distance_to_point(join) >= self.doctrine.push_distance: + # Hold point is between the origin airfield and the join point and + # spaced sufficiently. + return hold_point + + # The hold point is between the origin airfield and the join point, but + # the distance between the hold point and the join point is too short. + # Bend the hold point out to extend the distance while maintaining the + # minimum distance from the origin airfield to keep the AI flying + # properly. + origin_to_join = origin.distance_to_point(join) + cos_theta = ( + (self.doctrine.hold_distance ** 2 + + origin_to_join ** 2 - + self.doctrine.join_distance ** 2) / + (2 * self.doctrine.hold_distance * origin_to_join) ) + try: + theta = math.acos(cos_theta) + except ValueError: + # No solution that maintains hold and join distances. Extend the + # hold point away from the target. + return origin.point_from_heading( + target.heading_between_point(origin), + self.doctrine.hold_distance) + + return origin.point_from_heading(heading_to_join - theta, + self.doctrine.hold_distance) # TODO: Make a model for the waypoint builder and use that in the UI. def generate_ascend_point(self, flight: Flight, @@ -944,23 +959,37 @@ class FlightPlanBuilder: package=self.package, flight=flight, takeoff=builder.takeoff(flight.from_cp), - ascent=builder.ascent(flight.from_cp), hold=builder.hold(self._hold_point(flight)), join=builder.join(self.package.waypoints.join), ingress=ingress, targets=target_waypoints, egress=builder.egress(self.package.waypoints.egress, location), split=builder.split(self.package.waypoints.split), - descent=descent, land=land ) def _join_point(self, ingress_point: Point) -> Point: + ingress_distance = self._distance_to_package_airfield(ingress_point) + if ingress_distance < self.doctrine.join_distance: + # If the ingress point is close to the origin, plan the join point + # farther back. + return ingress_point.point_from_heading( + self.package.target.position.heading_between_point( + self.package_airfield().position), + self.doctrine.join_distance) heading = self._heading_to_package_airfield(ingress_point) return ingress_point.point_from_heading(heading, -self.doctrine.join_distance) def _split_point(self, egress_point: Point) -> Point: + egress_distance = self._distance_to_package_airfield(egress_point) + if egress_distance < self.doctrine.split_distance: + # If the ingress point is close to the origin, plan the split point + # farther back. + return egress_point.point_from_heading( + self.package.target.position.heading_between_point( + self.package_airfield().position), + self.doctrine.split_distance) heading = self._heading_to_package_airfield(egress_point) return egress_point.point_from_heading(heading, -self.doctrine.split_distance) @@ -983,6 +1012,9 @@ class FlightPlanBuilder: def _heading_to_package_airfield(self, point: Point) -> int: return self.package_airfield().position.heading_between_point(point) + def _distance_to_package_airfield(self, point: Point) -> int: + return self.package_airfield().position.distance_to_point(point) + def package_airfield(self) -> ControlPoint: # We'll always have a package, but if this is being planned via the UI # it could be the first flight in the package. From 1d98432c5713bfdfdf88a90d1215f3f87b3b5351 Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Sun, 15 Nov 2020 22:06:05 -0600 Subject: [PATCH 18/39] Briefing tweak Fixes frontline info repeating when player has no vehicles. --- resources/briefing/templates/briefingtemplate_EN.j2 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/briefing/templates/briefingtemplate_EN.j2 b/resources/briefing/templates/briefingtemplate_EN.j2 index 0d32eeae..864fbabc 100644 --- a/resources/briefing/templates/briefingtemplate_EN.j2 +++ b/resources/briefing/templates/briefingtemplate_EN.j2 @@ -16,6 +16,7 @@ We do not have a single vehicle available to hold our position. The situation i {% if frontline.enemy_zero %} The enemy forces have been crushed, we will be able to make significant progress toward {{ frontline.enemy_base.name }} {% endif %} +{% if not frontline.player_zero %} {# Pick a random sentence to describe each frontline #} {% set fl_sent1 %}There are combats between {{ frontline.player_base.name }} and {{frontline.enemy_base.name}}. {%+ endset %} {% set fl_sent2 %}The war on the ground is still going on between {{frontline.player_base.name}} and {{frontline.enemy_base.name}}. {%+ endset %} @@ -57,8 +58,9 @@ On this location, our ground forces have been ordered to hold still, and defend {# TODO: Write a retreat sentence #} {% endif %} {% endif %} +{% endif %} -{% endfor %}{% endif %} +{%+ endfor %}{% endif %} Your flights: ==================== From d05897edcbd05b2b2b4cce3686c357c420975a59 Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Sun, 15 Nov 2020 22:50:14 -0600 Subject: [PATCH 19/39] Change default CAS loadout for Viggen Reported that AI can't hit the broad side of a barn with the rockets. --- resources/customized_payloads/AJS37.lua | 35 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/resources/customized_payloads/AJS37.lua b/resources/customized_payloads/AJS37.lua index c8ebdcf1..9fe5c251 100644 --- a/resources/customized_payloads/AJS37.lua +++ b/resources/customized_payloads/AJS37.lua @@ -5,28 +5,37 @@ local unitPayloads = { ["name"] = "CAS", ["pylons"] = { [1] = { - ["CLSID"] = "{ARAKM70BHE}", - ["num"] = 3, + ["CLSID"] = "{RB75}", + ["num"] = 5, }, [2] = { - ["CLSID"] = "{ARAKM70BHE}", - ["num"] = 2, + ["CLSID"] = "{RB75}", + ["num"] = 3, }, [3] = { + ["CLSID"] = "{RB75}", + ["num"] = 2, + }, + [4] = { + ["CLSID"] = "{RB75}", + ["num"] = 6, + }, + [5] = { + ["CLSID"] = "{Robot24J}", + ["num"] = 1, + }, + [6] = { + ["CLSID"] = "{Robot24J}", + ["num"] = 7, + }, + [7] = { ["CLSID"] = "{VIGGEN_X-TANK}", ["num"] = 4, }, - [4] = { - ["CLSID"] = "{ARAKM70BHE}", - ["num"] = 5, - }, - [5] = { - ["CLSID"] = "{ARAKM70BHE}", - ["num"] = 6, - }, }, ["tasks"] = { - [1] = 31, + [1] = 32, + [2] = 31, }, }, [2] = { From b7d160631a7bfc6998f576a118a1f898c2188f04 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Mon, 16 Nov 2020 19:15:11 -0800 Subject: [PATCH 20/39] Further improve split/join positioning. (cherry picked from commit dc235f36c8b8e7b7d7a9a0bc32c174e4c07d9883) --- gen/flights/flightplan.py | 62 +++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index c73eb3ae..7919ae9f 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -558,8 +558,8 @@ class FlightPlanBuilder: def regenerate_package_waypoints(self) -> None: ingress_point = self._ingress_point() egress_point = self._egress_point() - join_point = self._join_point(ingress_point) - split_point = self._split_point(egress_point) + join_point = self._rendezvous_point(ingress_point) + split_point = self._rendezvous_point(egress_point) from gen.ato import PackageWaypoints self.package.waypoints = PackageWaypoints( @@ -968,31 +968,41 @@ class FlightPlanBuilder: land=land ) - def _join_point(self, ingress_point: Point) -> Point: - ingress_distance = self._distance_to_package_airfield(ingress_point) - if ingress_distance < self.doctrine.join_distance: - # If the ingress point is close to the origin, plan the join point - # farther back. - return ingress_point.point_from_heading( - self.package.target.position.heading_between_point( - self.package_airfield().position), - self.doctrine.join_distance) - heading = self._heading_to_package_airfield(ingress_point) - return ingress_point.point_from_heading(heading, - -self.doctrine.join_distance) + def _retreating_rendezvous_point(self, attack_transition: Point) -> Point: + """Creates a rendezvous point that retreats from the origin airfield.""" + return attack_transition.point_from_heading( + self.package.target.position.heading_between_point( + self.package_airfield().position), + self.doctrine.join_distance) - def _split_point(self, egress_point: Point) -> Point: - egress_distance = self._distance_to_package_airfield(egress_point) - if egress_distance < self.doctrine.split_distance: - # If the ingress point is close to the origin, plan the split point - # farther back. - return egress_point.point_from_heading( - self.package.target.position.heading_between_point( - self.package_airfield().position), - self.doctrine.split_distance) - heading = self._heading_to_package_airfield(egress_point) - return egress_point.point_from_heading(heading, - -self.doctrine.split_distance) + def _advancing_rendezvous_point(self, attack_transition: Point) -> Point: + """Creates a rendezvous point that advances toward the target.""" + heading = self._heading_to_package_airfield(attack_transition) + return attack_transition.point_from_heading(heading, + -self.doctrine.join_distance) + + def _rendezvous_should_retreat(self, attack_transition: Point) -> bool: + transition_target_distance = attack_transition.distance_to_point( + self.package.target.position + ) + origin_target_distance = self._distance_to_package_airfield( + self.package.target.position + ) + + # If the origin point is closer to the target than the ingress point, + # the rendezvous point should be positioned in a position that retreats + # from the origin airfield. + return origin_target_distance < transition_target_distance + + def _rendezvous_point(self, attack_transition: Point) -> Point: + """Returns the position of the rendezvous point. + + Args: + attack_transition: The ingress or egress point for this rendezvous. + """ + if self._rendezvous_should_retreat(attack_transition): + return self._retreating_rendezvous_point(attack_transition) + return self._advancing_rendezvous_point(attack_transition) def _ingress_point(self) -> Point: heading = self._target_heading_to_package_airfield() From fa549fcf94e486d5a53749ff4f7024723115fe9d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 18 Nov 2020 19:27:03 -0800 Subject: [PATCH 21/39] Don't delay player flights with short delays. Not much point in delaying humans 8 seconds. Fixes https://github.com/Khopa/dcs_liberation/issues/397 --- gen/aircraft.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gen/aircraft.py b/gen/aircraft.py index 7c4eac80..a25d56f0 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1187,6 +1187,12 @@ class AircraftConflictGenerator: if not flight.client_count: return True + if start_time < timedelta(minutes=10): + # Don't bother delaying client flights with short start delays. Much + # more than ten minutes starts to eat into fuel a bit more + # (espeicially for something fuel limited like a Harrier). + return False + return not self.settings.never_delay_player_flights def set_takeoff_time(self, waypoint: FlightWaypoint, package: Package, From 8d485d5fa2aa81baf3f30ae66b5a666c0cd19f0e Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 18 Nov 2020 19:33:54 -0800 Subject: [PATCH 22/39] Delay player CV flight when their settings say so. Fixes https://github.com/Khopa/dcs_liberation/issues/375 This also fixes a problem where we're spawning non-cold start planes in an uncontrolled state. The ME won't let us do this, so we probably shouldn't be doing that. --- gen/aircraft.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/gen/aircraft.py b/gen/aircraft.py index a25d56f0..f17caea4 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1219,15 +1219,6 @@ class AircraftConflictGenerator: @staticmethod def should_activate_late(flight: Flight) -> bool: - if flight.client_count: - # Never delay players. Note that cold start player flights with - # AI members will still be marked as uncontrolled until the start - # trigger fires to postpone engine start. - # - # Player flights that start on the runway or in the air will start - # immediately, and AI flight members will not be delayed. - return False - if flight.start_type != "Cold": # Avoid spawning aircraft in the air or on the runway until it's # time for their mission. Also avoid burning through gas spawning From 216adcc35afbee89e8decda9b7f59617b2732d67 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 18 Nov 2020 21:26:14 -0800 Subject: [PATCH 23/39] Add infor about delayed flights to the start page. Fixes https://github.com/Khopa/dcs_liberation/issues/398 --- .../windows/QWaitingForMissionResultWindow.py | 37 +++++------- resources/ui/templates/mission_start_EN.j2 | 58 +++++++++++++++++++ 2 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 resources/ui/templates/mission_start_EN.j2 diff --git a/qt_ui/windows/QWaitingForMissionResultWindow.py b/qt_ui/windows/QWaitingForMissionResultWindow.py index c35a482e..cf186829 100644 --- a/qt_ui/windows/QWaitingForMissionResultWindow.py +++ b/qt_ui/windows/QWaitingForMissionResultWindow.py @@ -13,8 +13,9 @@ from PySide2.QtWidgets import ( QLabel, QMessageBox, QPushButton, - QTextEdit, + QTextBrowser, ) +from jinja2 import Environment, FileSystemLoader, select_autoescape from game.debriefing import Debriefing, wait_for_debriefing from game.game import Event, Game, logging @@ -65,27 +66,21 @@ class QWaitingForMissionResultWindow(QDialog): self.layout.addWidget(header, 0, 0) self.gridLayout = QGridLayout() - TEXT = "" + \ - "You are clear for takeoff" + \ - "" + \ - "

For Singleplayer :

\n" + \ - "In DCS, open the Mission Editor, and load the file : \n" + \ - "liberation_nextturn\n" + \ - "

Then once the mission is loaded in ME, in menu \"Flight\",\n" + \ - "click on FLY Mission to launch.

\n" + \ - "" + \ - "

For Multiplayer :

" + \ - "In DCS, open the Mission Editor, and load the file : " + \ - "liberation_nextturn" + \ - "

Click on File/Save. Then exit the mission editor, and go to Multiplayer.

" + \ - "

Then host a server with the mission, and tell your friends to join !

" + \ - "(The step in the mission editor is important, and fix a game breaking bug.)" + \ - "

Finishing

" + \ - "

Once you have played the mission, click on the \"Accept Results\" button.

" + \ - "

If DCS Liberation does not detect mission end, use the manually submit button, and choose the state.json file.

" - self.instructions_text = QTextEdit(TEXT) - self.instructions_text.setReadOnly(True) + jinja = Environment( + loader=FileSystemLoader("resources/ui/templates"), + autoescape=select_autoescape( + disabled_extensions=("",), + default_for_string=True, + default=True, + ), + trim_blocks=True, + lstrip_blocks=True, + ) + self.instructions_text = QTextBrowser() + self.instructions_text.setHtml( + jinja.get_template("mission_start_EN.j2").render()) + self.instructions_text.setOpenExternalLinks(True) self.gridLayout.addWidget(self.instructions_text, 1, 0) progress = QLabel("") diff --git a/resources/ui/templates/mission_start_EN.j2 b/resources/ui/templates/mission_start_EN.j2 new file mode 100644 index 00000000..a162635b --- /dev/null +++ b/resources/ui/templates/mission_start_EN.j2 @@ -0,0 +1,58 @@ +You are clear for takeoff + +

+ Some player flights may be delayed to start. For such flights, it will not be + possible to enter the cockpit for a delayed flight until its mission start + time, shown in the flight information window. +

+ +

+ To reduce delays, schedule packages with player flights with an earlier TOT. + Note that if some flights within the package will take a long time to reach the + target, a player flight may still be delayed. +

+ +

+ To avoid delays entirely, use the "Never delay player flights" option in the + mission generation settings. Note that this will not adjust + the timing of your mission; this option only allows you to wait in the + cockpit. +

+ +

+ For more information, see the mission planning documentation on + + the wiki. +

+ +

For Singleplayer:

+ +

+ In DCS, open the Mission Editor and load the file: liberation_nextturn. +

+ +

+ Once the mission is loaded in the ME, use the "FLY" option in the "Flight" + menu to launch. +

+ +

For Multiplayer:

+ +

+ In DCS, open the Mission Editor, and load the file: liberation_nextturn +

+ +

Select File/Save, exit the mission editor, and then select Multiplayer.

+ +

Then host a server with the mission, and tell your friends to join!

+ +(The step in the mission editor is important, and fix a game breaking bug.) + +

Finishing

+ +

Once you have played the mission, click on the \"Accept Results\" button.

+ +

+ If DCS Liberation does not detect mission end, use the manually submit button, + and choose the state.json file. +

\ No newline at end of file From 2d56ae1cb6d83f686e9f0a811ceabeead739c428 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 18 Nov 2020 22:03:33 -0800 Subject: [PATCH 24/39] Avoid cases where empty flights could be created. Fixes https://github.com/Khopa/dcs_liberation/issues/373 --- qt_ui/widgets/combos/QOriginAirfieldSelector.py | 13 ++++++++++++- qt_ui/windows/mission/flight/QFlightCreator.py | 15 +++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/qt_ui/widgets/combos/QOriginAirfieldSelector.py b/qt_ui/widgets/combos/QOriginAirfieldSelector.py index b0530efc..eab48e3a 100644 --- a/qt_ui/widgets/combos/QOriginAirfieldSelector.py +++ b/qt_ui/widgets/combos/QOriginAirfieldSelector.py @@ -1,9 +1,10 @@ """Combo box for selecting a departure airfield.""" from typing import Iterable +from PySide2.QtCore import Signal from PySide2.QtWidgets import QComboBox - from dcs.planes import PlaneType + from game.inventory import GlobalAircraftInventory from theater.controlpoint import ControlPoint @@ -15,6 +16,8 @@ class QOriginAirfieldSelector(QComboBox): that have unassigned inventory of the given aircraft type. """ + availability_changed = Signal(int) + def __init__(self, global_inventory: GlobalAircraftInventory, origins: Iterable[ControlPoint], aircraft: PlaneType) -> None: @@ -23,6 +26,7 @@ class QOriginAirfieldSelector(QComboBox): self.origins = list(origins) self.aircraft = aircraft self.rebuild_selector() + self.currentIndexChanged.connect(self.index_changed) def change_aircraft(self, aircraft: PlaneType) -> None: if self.aircraft == aircraft: @@ -47,3 +51,10 @@ class QOriginAirfieldSelector(QComboBox): return 0 inventory = self.global_inventory.for_control_point(origin) return inventory.available(self.aircraft) + + def index_changed(self, index: int) -> None: + origin = self.itemData(index) + if origin is None: + return + inventory = self.global_inventory.for_control_point(origin) + self.availability_changed.emit(inventory.available(self.aircraft)) diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index ee4b3354..f4fe6041 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -54,11 +54,11 @@ class QFlightCreator(QDialog): [cp for cp in game.theater.controlpoints if cp.captured], self.aircraft_selector.currentData() ) - self.airfield_selector.currentIndexChanged.connect(self.update_max_size) + self.airfield_selector.availability_changed.connect(self.update_max_size) layout.addLayout(QLabeledWidget("Airfield:", self.airfield_selector)) self.flight_size_spinner = QFlightSizeSpinner() - self.update_max_size() + self.update_max_size(self.airfield_selector.available) layout.addLayout(QLabeledWidget("Size:", self.flight_size_spinner)) self.client_slots_spinner = QFlightSizeSpinner( @@ -91,6 +91,8 @@ class QFlightCreator(QDialog): return f"{origin.name} has no {aircraft.id} available." if size > available: return f"{origin.name} has only {available} {aircraft.id} available." + if size <= 0: + return f"Flight must have at least one aircraft." return None def create_flight(self) -> None: @@ -120,7 +122,8 @@ class QFlightCreator(QDialog): new_aircraft = self.aircraft_selector.itemData(index) self.airfield_selector.change_aircraft(new_aircraft) - def update_max_size(self) -> None: - self.flight_size_spinner.setMaximum( - min(self.airfield_selector.available, 4) - ) + def update_max_size(self, available: int) -> None: + self.flight_size_spinner.setMaximum(min(available, 4)) + if self.flight_size_spinner.maximum() >= 2: + if self.flight_size_spinner.value() < 2: + self.flight_size_spinner.setValue(2) From a816877d08573b8362faa3355ea6f459a010ea88 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 18 Nov 2020 23:40:18 -0800 Subject: [PATCH 25/39] Allow deleting waypoints. In almost every case this leaves us with a flight plan we can't reason about, so it gets degraded to `CustomFlightPlan`. The exception is when deleting a target point when there are other target points remaining. This probably gets people using this feature back to what they want though, which is essentially the 2.1 behavior. Fixes https://github.com/Khopa/dcs_liberation/issues/393 --- gen/flights/traveltime.py | 14 +++++---- .../flight/waypoints/QFlightWaypointTab.py | 30 +++++++++++++++++-- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/gen/flights/traveltime.py b/gen/flights/traveltime.py index ee9a6c7e..17f8ec36 100644 --- a/gen/flights/traveltime.py +++ b/gen/flights/traveltime.py @@ -96,6 +96,11 @@ class TotEstimator: def mission_start_time(self, flight: Flight) -> timedelta: takeoff_time = self.takeoff_time_for_flight(flight) + if takeoff_time is None: + # Could not determine takeoff time, probably due to a custom flight + # plan. Start immediately. + return timedelta() + startup_time = self.estimate_startup(flight) ground_ops_time = self.estimate_ground_ops(flight) start_time = takeoff_time - startup_time - ground_ops_time @@ -110,13 +115,12 @@ class TotEstimator: # Round down so *barely* above zero start times are just zero. return timedelta(seconds=math.floor(start_time.total_seconds())) - def takeoff_time_for_flight(self, flight: Flight) -> timedelta: + def takeoff_time_for_flight(self, flight: Flight) -> Optional[timedelta]: travel_time = self.travel_time_to_rendezvous_or_target(flight) if travel_time is None: - logging.warning("Found no join point or patrol point. Cannot " + logging.warning("Found no rendezvous or target point. Cannot " f"estimate takeoff time takeoff time for {flight}") - # Takeoff immediately. - return timedelta() + return None from gen.flights.flightplan import FormationFlightPlan if isinstance(flight.flight_plan, FormationFlightPlan): @@ -126,7 +130,7 @@ class TotEstimator: logging.warning( "Could not determine the TOT of the join point. Takeoff " f"time for {flight} will be immediate.") - return timedelta() + return None else: tot = self.package.time_over_target return tot - travel_time - self.HOLD_TIME diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py index 43198d28..4352ac47 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py @@ -12,8 +12,12 @@ from PySide2.QtWidgets import ( from game import Game from gen.ato import Package -from gen.flights.flight import Flight, FlightType -from gen.flights.flightplan import FlightPlanBuilder +from gen.flights.flight import Flight, FlightType, FlightWaypoint +from gen.flights.flightplan import ( + CustomFlightPlan, + FlightPlanBuilder, + StrikeFlightPlan, +) from qt_ui.windows.mission.flight.waypoints.QFlightWaypointList import \ QFlightWaypointList from qt_ui.windows.mission.flight.waypoints\ @@ -103,10 +107,30 @@ class QFlightWaypointTab(QFrame): def on_delete_waypoint(self): wpt = self.flight_waypoint_list.selectionModel().currentIndex().row() if wpt > 0: - del self.flight.points[wpt-1] + self.delete_waypoint(self.flight.flight_plan.waypoints[wpt]) self.flight_waypoint_list.update_list() self.on_change() + def delete_waypoint(self, waypoint: FlightWaypoint) -> None: + # Need to degrade to a custom flight plan and remove the waypoint. + # If the waypoint is a target waypoint and is not the last target + # waypoint, we don't need to degrade. + flight_plan = self.flight.flight_plan + if isinstance(flight_plan, StrikeFlightPlan): + if waypoint in flight_plan.targets and len(flight_plan.targets) > 1: + flight_plan.targets.remove(waypoint) + return + + if not isinstance(flight_plan, CustomFlightPlan): + flight_plan = CustomFlightPlan( + package=self.flight.package, + flight=self.flight, + custom_waypoints=flight_plan.waypoints + ) + + flight_plan.waypoints.remove(waypoint) + self.flight.flight_plan = flight_plan + def on_fast_waypoint(self): self.subwindow = QPredefinedWaypointSelectionWindow(self.game, self.flight, self.flight_waypoint_list) self.subwindow.finished.connect(self.on_change) From fd473f0a46d140f54b9610485d0ebbdad8e13f7c Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 00:29:05 -0800 Subject: [PATCH 26/39] Fix custom waypoints. Like with deleting waypoints, these will degrade the flight plan to the 2.1 behavior. Ascend/descend points aren't in use any more, so I removed those. --- gen/flights/flightplan.py | 39 ++---------- gen/flights/traveltime.py | 7 ++- gen/flights/waypointbuilder.py | 57 ------------------ .../QPredefinedWaypointSelectionComboBox.py | 3 +- .../flight/waypoints/QFlightWaypointTab.py | 60 +++++++------------ .../QPredefinedWaypointSelectionWindow.py | 29 +++++---- theater/theatergroundobject.py | 8 +++ 7 files changed, 59 insertions(+), 144 deletions(-) diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 7919ae9f..430a8c11 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -659,7 +659,6 @@ class FlightPlanBuilder: builder = WaypointBuilder(self.game.conditions, flight, self.doctrine) start, end = builder.race_track(start, end, patrol_alt) - descent, land = builder.rtb(flight.from_cp) return BarCapFlightPlan( package=self.package, @@ -668,7 +667,7 @@ class FlightPlanBuilder: takeoff=builder.takeoff(flight.from_cp), patrol_start=start, patrol_end=end, - land=land + land=builder.land(flight.from_cp) ) def generate_frontline_cap(self, flight: Flight) -> FrontLineCapFlightPlan: @@ -707,9 +706,8 @@ class FlightPlanBuilder: # Create points builder = WaypointBuilder(self.game.conditions, flight, self.doctrine) - start, end = builder.race_track(orbit0p, orbit1p, patrol_alt) - descent, land = builder.rtb(flight.from_cp) + return FrontLineCapFlightPlan( package=self.package, flight=flight, @@ -721,7 +719,7 @@ class FlightPlanBuilder: takeoff=builder.takeoff(flight.from_cp), patrol_start=start, patrol_end=end, - land=land + land=builder.land(flight.from_cp) ) def generate_dead(self, flight: Flight, @@ -780,7 +778,6 @@ class FlightPlanBuilder: ingress, target, egress = builder.escort( self.package.waypoints.ingress, self.package.target, self.package.waypoints.egress) - descent, land = builder.rtb(flight.from_cp) return StrikeFlightPlan( package=self.package, @@ -792,7 +789,7 @@ class FlightPlanBuilder: targets=[target], egress=egress, split=builder.split(self.package.waypoints.split), - land=land + land=builder.land(flight.from_cp) ) def generate_cas(self, flight: Flight) -> CasFlightPlan: @@ -814,7 +811,6 @@ class FlightPlanBuilder: egress = ingress.point_from_heading(heading, distance) builder = WaypointBuilder(self.game.conditions, flight, self.doctrine) - descent, land = builder.rtb(flight.from_cp) return CasFlightPlan( package=self.package, @@ -824,7 +820,7 @@ class FlightPlanBuilder: patrol_start=builder.ingress_cas(ingress, location), target=builder.cas(center), patrol_end=builder.egress(egress, location), - land=land + land=builder.land(flight.from_cp) ) @staticmethod @@ -894,28 +890,6 @@ class FlightPlanBuilder: self.doctrine.hold_distance) # TODO: Make a model for the waypoint builder and use that in the UI. - 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.game.conditions, flight, self.doctrine) - return builder.ascent(departure) - - 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.game.conditions, flight, self.doctrine) - return builder.descent(arrival) - def generate_rtb_waypoint(self, flight: Flight, arrival: ControlPoint) -> FlightWaypoint: """Generate RTB landing point. @@ -954,7 +928,6 @@ class FlightPlanBuilder: target_waypoints.append( self.target_area_waypoint(flight, location, builder)) - descent, land = builder.rtb(flight.from_cp) return StrikeFlightPlan( package=self.package, flight=flight, @@ -965,7 +938,7 @@ class FlightPlanBuilder: targets=target_waypoints, egress=builder.egress(self.package.waypoints.egress, location), split=builder.split(self.package.waypoints.split), - land=land + land=builder.land(flight.from_cp) ) def _retreating_rendezvous_point(self, attack_transition: Point) -> Point: diff --git a/gen/flights/traveltime.py b/gen/flights/traveltime.py index 17f8ec36..0bddfcaf 100644 --- a/gen/flights/traveltime.py +++ b/gen/flights/traveltime.py @@ -118,8 +118,11 @@ class TotEstimator: def takeoff_time_for_flight(self, flight: Flight) -> Optional[timedelta]: travel_time = self.travel_time_to_rendezvous_or_target(flight) if travel_time is None: - logging.warning("Found no rendezvous or target point. Cannot " - f"estimate takeoff time takeoff time for {flight}") + from gen.flights.flightplan import CustomFlightPlan + if not isinstance(flight.flight_plan, CustomFlightPlan): + logging.warning( + "Found no rendezvous or target point. Cannot estimate " + f"takeoff time takeoff time for {flight}.") return None from gen.flights.flightplan import FormationFlightPlan diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index ddc76b5f..3b67e8e0 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -7,11 +7,9 @@ from dcs.mapping import Point from dcs.unit import Unit from game.data.doctrine import Doctrine -from game.utils import nm_to_meter from game.weather import Conditions from theater import ControlPoint, MissionTarget, TheaterGroundObject from .flight import Flight, FlightWaypoint, FlightWaypointType -from ..runways import RunwayAssigner @dataclass(frozen=True) @@ -57,52 +55,6 @@ class WaypointBuilder: waypoint.pretty_name = "Takeoff" return waypoint - def ascent(self, departure: ControlPoint) -> FlightWaypoint: - """Create ascent waypoint for the given departure airfield or carrier. - - Args: - departure: Departure airfield or carrier. - """ - heading = RunwayAssigner(self.conditions).takeoff_heading(departure) - position = departure.position.point_from_heading( - heading, nm_to_meter(5) - ) - waypoint = FlightWaypoint( - FlightWaypointType.ASCEND_POINT, - position.x, - position.y, - 500 if self.is_helo else self.doctrine.pattern_altitude - ) - waypoint.name = "ASCEND" - waypoint.alt_type = "RADIO" - waypoint.description = "Ascend" - waypoint.pretty_name = "Ascend" - return waypoint - - def descent(self, arrival: ControlPoint) -> FlightWaypoint: - """Create descent waypoint for the given arrival airfield or carrier. - - Args: - arrival: Arrival airfield or carrier. - """ - landing_heading = RunwayAssigner(self.conditions).landing_heading( - arrival) - heading = (landing_heading + 180) % 360 - position = arrival.position.point_from_heading( - heading, nm_to_meter(5) - ) - waypoint = FlightWaypoint( - FlightWaypointType.DESCENT_POINT, - position.x, - position.y, - 300 if self.is_helo else self.doctrine.pattern_altitude - ) - waypoint.name = "DESCEND" - waypoint.alt_type = "RADIO" - waypoint.description = "Descend to pattern altitude" - waypoint.pretty_name = "Descend" - return waypoint - @staticmethod def land(arrival: ControlPoint) -> FlightWaypoint: """Create descent waypoint for the given arrival airfield or carrier. @@ -326,15 +278,6 @@ class WaypointBuilder: return (self.race_track_start(start, altitude), self.race_track_end(end, altitude)) - def rtb(self, - arrival: ControlPoint) -> Tuple[FlightWaypoint, FlightWaypoint]: - """Creates descent ant landing waypoints for the given control point. - - Args: - arrival: Arrival airfield or carrier. - """ - return self.descent(arrival), self.land(arrival) - def escort(self, ingress: Point, target: MissionTarget, egress: Point) -> \ Tuple[FlightWaypoint, FlightWaypoint, FlightWaypoint]: """Creates the waypoints needed to escort the package. diff --git a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py index 72ece41e..2a9f8bc9 100644 --- a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py +++ b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py @@ -44,7 +44,6 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): i = 0 def add_model_item(i, model, name, wpt): - print(name) item = QStandardItem(name) model.setItem(i, 0, item) self.wpts.append(wpt) @@ -79,7 +78,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): 0 ) wpt.alt_type = "RADIO" - wpt.name = wpt.name = "[" + str(ground_object.obj_name) + "] : " + ground_object.category + " #" + str(ground_object.object_id) + wpt.name = ground_object.waypoint_name wpt.pretty_name = wpt.name wpt.obj_name = ground_object.obj_name wpt.targets.append(ground_object) diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py index 4352ac47..c480da2a 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Iterable, List, Optional from PySide2.QtCore import Signal from PySide2.QtWidgets import ( @@ -20,7 +20,7 @@ from gen.flights.flightplan import ( ) from qt_ui.windows.mission.flight.waypoints.QFlightWaypointList import \ QFlightWaypointList -from qt_ui.windows.mission.flight.waypoints\ +from qt_ui.windows.mission.flight.waypoints \ .QPredefinedWaypointSelectionWindow import \ QPredefinedWaypointSelectionWindow from theater import FrontLine @@ -38,8 +38,6 @@ class QFlightWaypointTab(QFrame): self.planner = FlightPlanBuilder(self.game, package, is_player=True) self.flight_waypoint_list: Optional[QFlightWaypointList] = None - self.ascend_waypoint: Optional[QPushButton] = None - self.descend_waypoint: Optional[QPushButton] = None self.rtb_waypoint: Optional[QPushButton] = None self.delete_selected: Optional[QPushButton] = None self.open_fast_waypoint_button: Optional[QPushButton] = None @@ -82,14 +80,6 @@ class QFlightWaypointTab(QFrame): rlayout.addWidget(QLabel("Advanced : ")) rlayout.addWidget(QLabel("Do not use for AI flights")) - self.ascend_waypoint = QPushButton("Add Ascend Waypoint") - self.ascend_waypoint.clicked.connect(self.on_ascend_waypoint) - rlayout.addWidget(self.ascend_waypoint) - - self.descend_waypoint = QPushButton("Add Descend Waypoint") - self.descend_waypoint.clicked.connect(self.on_descend_waypoint) - rlayout.addWidget(self.descend_waypoint) - self.rtb_waypoint = QPushButton("Add RTB Waypoint") self.rtb_waypoint.clicked.connect(self.on_rtb_waypoint) rlayout.addWidget(self.rtb_waypoint) @@ -115,47 +105,43 @@ class QFlightWaypointTab(QFrame): # Need to degrade to a custom flight plan and remove the waypoint. # If the waypoint is a target waypoint and is not the last target # waypoint, we don't need to degrade. - flight_plan = self.flight.flight_plan - if isinstance(flight_plan, StrikeFlightPlan): - if waypoint in flight_plan.targets and len(flight_plan.targets) > 1: - flight_plan.targets.remove(waypoint) + if isinstance(self.flight.flight_plan, StrikeFlightPlan): + is_target = waypoint in self.flight.flight_plan.targets + if is_target and len(self.flight.flight_plan.targets) > 1: + self.flight.flight_plan.targets.remove(waypoint) return - if not isinstance(flight_plan, CustomFlightPlan): - flight_plan = CustomFlightPlan( - package=self.flight.package, - flight=self.flight, - custom_waypoints=flight_plan.waypoints - ) - - flight_plan.waypoints.remove(waypoint) - self.flight.flight_plan = flight_plan + self.degrade_to_custom_flight_plan() + self.flight.flight_plan.waypoints.remove(waypoint) def on_fast_waypoint(self): self.subwindow = QPredefinedWaypointSelectionWindow(self.game, self.flight, self.flight_waypoint_list) - self.subwindow.finished.connect(self.on_change) + self.subwindow.waypoints_added.connect(self.on_waypoints_added) self.subwindow.show() - def on_ascend_waypoint(self): - ascend = self.planner.generate_ascend_point(self.flight, - self.flight.from_cp) - self.flight.points.append(ascend) + def on_waypoints_added(self, waypoints: Iterable[FlightWaypoint]) -> None: + if not waypoints: + return + self.degrade_to_custom_flight_plan() + self.flight.flight_plan.waypoints.extend(waypoints) self.flight_waypoint_list.update_list() self.on_change() def on_rtb_waypoint(self): rtb = self.planner.generate_rtb_waypoint(self.flight, self.flight.from_cp) - self.flight.points.append(rtb) + self.degrade_to_custom_flight_plan() + self.flight.flight_plan.waypoints.append(rtb) self.flight_waypoint_list.update_list() self.on_change() - def on_descend_waypoint(self): - 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() + def degrade_to_custom_flight_plan(self) -> None: + if not isinstance(self.flight.flight_plan, CustomFlightPlan): + self.flight.flight_plan = CustomFlightPlan( + package=self.flight.package, + flight=self.flight, + custom_waypoints=self.flight.flight_plan.waypoints + ) def confirm_recreate(self, task: FlightType) -> None: result = QMessageBox.question( diff --git a/qt_ui/windows/mission/flight/waypoints/QPredefinedWaypointSelectionWindow.py b/qt_ui/windows/mission/flight/waypoints/QPredefinedWaypointSelectionWindow.py index e7e2a90c..ccec5034 100644 --- a/qt_ui/windows/mission/flight/waypoints/QPredefinedWaypointSelectionWindow.py +++ b/qt_ui/windows/mission/flight/waypoints/QPredefinedWaypointSelectionWindow.py @@ -1,11 +1,20 @@ -from PySide2.QtCore import Qt -from PySide2.QtWidgets import QDialog, QLabel, QHBoxLayout, QVBoxLayout, QPushButton, QCheckBox +from PySide2.QtCore import Qt, Signal +from PySide2.QtWidgets import ( + QCheckBox, + QDialog, + QHBoxLayout, + QLabel, + QPushButton, + QVBoxLayout, +) from game import Game from gen.flights.flight import Flight from qt_ui.uiconstants import EVENT_ICONS -from qt_ui.widgets.combos.QPredefinedWaypointSelectionComboBox import QPredefinedWaypointSelectionComboBox -from qt_ui.windows.mission.flight.waypoints.QFlightWaypointInfoBox import QFlightWaypointInfoBox +from qt_ui.widgets.combos.QPredefinedWaypointSelectionComboBox import \ + QPredefinedWaypointSelectionComboBox +from qt_ui.windows.mission.flight.waypoints.QFlightWaypointInfoBox import \ + QFlightWaypointInfoBox PREDEFINED_WAYPOINT_CATEGORIES = [ "Frontline (CAS AREA)", @@ -17,6 +26,8 @@ PREDEFINED_WAYPOINT_CATEGORIES = [ class QPredefinedWaypointSelectionWindow(QDialog): + # List of FlightWaypoint + waypoints_added = Signal(list) def __init__(self, game: Game, flight: Flight, flight_waypoint_list): super(QPredefinedWaypointSelectionWindow, self).__init__() @@ -44,7 +55,6 @@ class QPredefinedWaypointSelectionWindow(QDialog): self.init_ui() self.on_select_wpt_changed() - print("DONE") def init_ui(self): @@ -77,12 +87,5 @@ class QPredefinedWaypointSelectionWindow(QDialog): self.add_button.setDisabled(False) def add_waypoint(self): - - for wpt in self.selected_waypoints: - self.flight.points.append(wpt) - - self.flight_waypoint_list.update_list() + self.waypoints_added.emit(self.selected_waypoints) self.close() - - - diff --git a/theater/theatergroundobject.py b/theater/theatergroundobject.py index 0e8b3c87..a0694974 100644 --- a/theater/theatergroundobject.py +++ b/theater/theatergroundobject.py @@ -99,6 +99,10 @@ class TheaterGroundObject(MissionTarget): """The name of the unit group.""" return f"{self.category}|{self.group_id}" + @property + def waypoint_name(self) -> str: + return f"[{self.name}] {self.category}" + def __str__(self) -> str: return NAME_BY_CATEGORY[self.category] @@ -136,6 +140,10 @@ class BuildingGroundObject(TheaterGroundObject): """The name of the unit group.""" return f"{self.category}|{self.group_id}|{self.object_id}" + @property + def waypoint_name(self) -> str: + return f"{super().waypoint_name} #{self.object_id}" + class GenericCarrierGroundObject(TheaterGroundObject): pass From 365b379798ed9e9cbc62f3e186d35138d4c84642 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 00:42:54 -0800 Subject: [PATCH 27/39] Update changelog for 2.2.1. --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index b5b938aa..5990f984 100644 --- a/changelog.md +++ b/changelog.md @@ -3,9 +3,14 @@ # Features/Improvements * **[Factions]** Added factions : Georgia 2008, USN 1985, France 2005 Frenchpack by HerrTom * **[Factions]** Added map Persian Gulf full by Plob +* **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately. +* **[UI]** Mission start screen now informs players about delayed flights. ## Fixes : * **[Flight Planner]** Hold, join, and split points are planned cautiously near enemy airfields. Ascend/descend points are no longer planned. +* **[Flight Planner]** Custom waypoints are usable again. Not that in most cases custom flight plans will revert to the 2.1 flight planning behavior. +* **[Flight Planner]** Fixed UI bug that made it possible to create empty flights which would throw an error. +* **[Flight Planner]** Player flights from carriers will now be delayed correctly according to the player's settings. # 2.2.0 From 63d58623194e009961eb301b9408f7f6466a2660 Mon Sep 17 00:00:00 2001 From: Khopa Date: Wed, 18 Nov 2020 21:51:47 +0100 Subject: [PATCH 28/39] Pydcs module update --- pydcs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydcs b/pydcs index fa9195fb..2883be31 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit fa9195fbccbf96775d108a22c13c3ee2375e4c0b +Subproject commit 2883be31c2eb80834b93efd8d20ca17913986e9b From 483db564f902a017666641473d36d1ed0ea29b6a Mon Sep 17 00:00:00 2001 From: Khopa Date: Thu, 19 Nov 2020 00:34:01 +0100 Subject: [PATCH 29/39] Added F-14A support --- changelog.md | 1 + game/db.py | 98 ++--- gen/flights/ai_flight_planner_db.py | 6 + .../customized_payloads/F-14A-135-GR.lua | 343 ++++++++++++++++++ resources/factions/bluefor_coldwar.json | 1 + resources/factions/bluefor_coldwar_a4.json | 1 + .../factions/bluefor_coldwar_a4_mb339.json | 1 + resources/factions/iran_2015.json | 2 +- resources/factions/usa_1975.json | 2 +- resources/factions/usa_1990.json | 23 +- resources/factions/usn_1985.json | 1 + 11 files changed, 430 insertions(+), 49 deletions(-) create mode 100644 resources/customized_payloads/F-14A-135-GR.lua diff --git a/changelog.md b/changelog.md index 5990f984..2ae74709 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ * **[Factions]** Added map Persian Gulf full by Plob * **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately. * **[UI]** Mission start screen now informs players about delayed flights. +* **[Units]** Added support for F-14A-135-GR ## Fixes : * **[Flight Planner]** Hold, join, and split points are planned cautiously near enemy airfields. Ascend/descend points are no longer planned. diff --git a/game/db.py b/game/db.py index a1794764..c73f7dbf 100644 --- a/game/db.py +++ b/game/db.py @@ -2,9 +2,7 @@ from datetime import datetime from enum import Enum from typing import Dict, List, Optional, Tuple, Type, Union -from dcs import Mission from dcs.countries import country_dict -from dcs.country import Country from dcs.helicopters import ( AH_1W, AH_64A, @@ -46,6 +44,7 @@ from dcs.planes import ( FW_190A8, FW_190D9, F_117A, + F_14A_135_GR, F_14B, F_15C, F_15E, @@ -105,7 +104,7 @@ from dcs.planes import ( Tu_95MS, WingLoong_I, Yak_40, - plane_map, + plane_map ) from dcs.ships import ( Armed_speedboat, @@ -155,7 +154,6 @@ from dcs.vehicles import ( ) import pydcs_extensions.frenchpack.frenchpack as frenchpack -from game.factions.faction import Faction # PATCH pydcs data with MODS from game.factions.faction_loader import FactionLoader from pydcs_extensions.a4ec.a4ec import A_4E_C @@ -204,7 +202,6 @@ vehicle_map["Toyota_vert"] = frenchpack.DIM__TOYOTA_GREEN vehicle_map["Toyota_desert"] = frenchpack.DIM__TOYOTA_DESERT vehicle_map["Kamikaze"] = frenchpack.DIM__KAMIKAZE - """ ---------- BEGINNING OF CONFIGURATION SECTION """ @@ -273,6 +270,7 @@ PRICES = { F_15E: 24, F_16C_50: 20, F_16A: 14, + F_14A_135_GR: 20, F_14B: 24, Tornado_IDS: 20, Tornado_GR4: 20, @@ -401,20 +399,20 @@ PRICES = { Unarmed.Transport_M818: 3, # WW2 - Armor.MT_Pz_Kpfw_V_Panther_Ausf_G:24, - Armor.MT_Pz_Kpfw_IV_Ausf_H:16, - Armor.HT_Pz_Kpfw_VI_Tiger_I:24, - Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II:26, + Armor.MT_Pz_Kpfw_V_Panther_Ausf_G: 24, + Armor.MT_Pz_Kpfw_IV_Ausf_H: 16, + Armor.HT_Pz_Kpfw_VI_Tiger_I: 24, + Armor.HT_Pz_Kpfw_VI_Ausf__B_Tiger_II: 26, Armor.TD_Jagdpanther_G1: 18, Armor.TD_Jagdpanzer_IV: 11, Armor.Sd_Kfz_184_Elefant: 18, - Armor.APC_Sd_Kfz_251:4, - Armor.AC_Sd_Kfz_234_2_Puma:8, - Armor.MT_M4_Sherman:12, - Armor.MT_M4A4_Sherman_Firefly:16, - Armor.CT_Cromwell_IV:12, - Armor.M30_Cargo_Carrier:2, - Armor.APC_M2A1:4, + Armor.APC_Sd_Kfz_251: 4, + Armor.AC_Sd_Kfz_234_2_Puma: 8, + Armor.MT_M4_Sherman: 12, + Armor.MT_M4A4_Sherman_Firefly: 16, + Armor.CT_Cromwell_IV: 12, + Armor.M30_Cargo_Carrier: 2, + Armor.APC_M2A1: 4, Armor.CT_Centaur_IV: 10, Armor.HIT_Churchill_VII: 16, Armor.LAC_M8_Greyhound: 8, @@ -577,6 +575,7 @@ UNIT_BY_TASK = { MiG_31, FA_18C_hornet, F_15C, + F_14A_135_GR, F_14B, F_16A, F_16C_50, @@ -902,7 +901,6 @@ SAM_CONVERT = { } } - """ Units that will always be spawned in the air """ @@ -913,7 +911,7 @@ TAKEOFF_BAN: List[Type[FlyingType]] = [ Units that will be always spawned in the air if launched from the carrier """ CARRIER_TAKEOFF_BAN: List[Type[FlyingType]] = [ - Su_33, # Kuznecow is bugged in a way that only 2 aircraft could be spawned + Su_33, # Kuznecow is bugged in a way that only 2 aircraft could be spawned ] """ @@ -924,6 +922,7 @@ FACTIONS = FactionLoader() CARRIER_TYPE_BY_PLANE = { FA_18C_hornet: CVN_74_John_C__Stennis, + F_14A_135_GR: CVN_74_John_C__Stennis, F_14B: CVN_74_John_C__Stennis, Ka_50: LHA_1_Tarawa, SA342M: LHA_1_Tarawa, @@ -997,6 +996,7 @@ PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = { AV8BNA: COMMON_OVERRIDE, C_101CC: COMMON_OVERRIDE, F_5E_3: COMMON_OVERRIDE, + F_14A_135_GR: COMMON_OVERRIDE, F_14B: COMMON_OVERRIDE, F_15C: COMMON_OVERRIDE, F_16C_50: COMMON_OVERRIDE, @@ -1006,14 +1006,14 @@ PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = { MiG_19P: COMMON_OVERRIDE, MiG_21Bis: COMMON_OVERRIDE, AJS37: COMMON_OVERRIDE, - Su_25T:COMMON_OVERRIDE, - Su_25:COMMON_OVERRIDE, - Su_27:COMMON_OVERRIDE, - Su_33:COMMON_OVERRIDE, - MiG_29A:COMMON_OVERRIDE, - MiG_29G:COMMON_OVERRIDE, - MiG_29S:COMMON_OVERRIDE, - Su_24M:COMMON_OVERRIDE, + Su_25T: COMMON_OVERRIDE, + Su_25: COMMON_OVERRIDE, + Su_27: COMMON_OVERRIDE, + Su_33: COMMON_OVERRIDE, + MiG_29A: COMMON_OVERRIDE, + MiG_29G: COMMON_OVERRIDE, + MiG_29S: COMMON_OVERRIDE, + Su_24M: COMMON_OVERRIDE, Su_30: COMMON_OVERRIDE, Su_34: COMMON_OVERRIDE, Su_57: COMMON_OVERRIDE, @@ -1022,21 +1022,21 @@ PLANE_PAYLOAD_OVERRIDES: Dict[Type[PlaneType], Dict[Type[Task], str]] = { Tornado_GR4: COMMON_OVERRIDE, Tornado_IDS: COMMON_OVERRIDE, Mirage_2000_5: COMMON_OVERRIDE, - MiG_31:COMMON_OVERRIDE, - SA342M:COMMON_OVERRIDE, - SA342L:COMMON_OVERRIDE, - SA342Mistral:COMMON_OVERRIDE, - Mi_8MT:COMMON_OVERRIDE, - Mi_24V:COMMON_OVERRIDE, - Mi_28N:COMMON_OVERRIDE, - Ka_50:COMMON_OVERRIDE, - L_39ZA:COMMON_OVERRIDE, - L_39C:COMMON_OVERRIDE, + MiG_31: COMMON_OVERRIDE, + SA342M: COMMON_OVERRIDE, + SA342L: COMMON_OVERRIDE, + SA342Mistral: COMMON_OVERRIDE, + Mi_8MT: COMMON_OVERRIDE, + Mi_24V: COMMON_OVERRIDE, + Mi_28N: COMMON_OVERRIDE, + Ka_50: COMMON_OVERRIDE, + L_39ZA: COMMON_OVERRIDE, + L_39C: COMMON_OVERRIDE, Su_17M4: COMMON_OVERRIDE, F_4E: COMMON_OVERRIDE, - P_47D_30:COMMON_OVERRIDE, - P_47D_30bl1:COMMON_OVERRIDE, - P_47D_40:COMMON_OVERRIDE, + P_47D_30: COMMON_OVERRIDE, + P_47D_30bl1: COMMON_OVERRIDE, + P_47D_40: COMMON_OVERRIDE, B_17G: COMMON_OVERRIDE, P_51D: COMMON_OVERRIDE, P_51D_30_NA: COMMON_OVERRIDE, @@ -1129,6 +1129,7 @@ PLAYER_BUDGET_BASE = 20 CARRIER_CAPABLE = [ FA_18C_hornet, + F_14A_135_GR, F_14B, AV8BNA, Su_33, @@ -1164,7 +1165,6 @@ LHA_CAPABLE = [ SA342Mistral ] - """ ---------- END OF CONFIGURATION SECTION """ @@ -1216,16 +1216,20 @@ def find_unittype(for_task: Task, country_name: str) -> List[UnitType]: def find_infantry(country_name: str) -> List[UnitType]: inf = [ - Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, + Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, Infantry.Paratrooper_AKS, + Infantry.Paratrooper_AKS, Infantry.Soldier_RPG, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Infantry_M4, Infantry.Soldier_M249, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Soldier_AK, Infantry.Paratrooper_RPG_16, - Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, - Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, + Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, Infantry.Georgian_soldier_with_M4, + Infantry.Georgian_soldier_with_M4, + Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, Infantry.Infantry_Soldier_Rus, + Infantry.Infantry_Soldier_Rus, Infantry.Infantry_SMLE_No_4_Mk_1, Infantry.Infantry_SMLE_No_4_Mk_1, Infantry.Infantry_SMLE_No_4_Mk_1, - Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, + Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, Infantry.Infantry_Mauser_98, + Infantry.Infantry_Mauser_98, Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand, Infantry.Infantry_M1_Garand, Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents, Infantry.Infantry_Soldier_Insurgents ] @@ -1356,7 +1360,7 @@ def unitdict_from(fd: AssignedUnitsDict) -> Dict: def country_id_from_name(name): - for k,v in country_dict.items(): + for k, v in country_dict.items(): if v.name == name: return k return -1 @@ -1374,8 +1378,10 @@ def _validate_db(): for unit_type in total_set: assert unit_type in PRICES, "{} not in prices".format(unit_type) + _validate_db() + class DefaultLiveries: class Default(Enum): af_standard = "" @@ -1385,4 +1391,4 @@ OH_58D.Liveries = DefaultLiveries F_16C_50.Liveries = DefaultLiveries P_51D_30_NA.Liveries = DefaultLiveries Ju_88A4.Liveries = DefaultLiveries -B_17G.Liveries = DefaultLiveries \ No newline at end of file +B_17G.Liveries = DefaultLiveries diff --git a/gen/flights/ai_flight_planner_db.py b/gen/flights/ai_flight_planner_db.py index fbc2b257..30749e33 100644 --- a/gen/flights/ai_flight_planner_db.py +++ b/gen/flights/ai_flight_planner_db.py @@ -27,6 +27,7 @@ from dcs.planes import ( FW_190A8, FW_190D9, F_117A, + F_14A_135_GR, F_14B, F_15C, F_15E, @@ -104,6 +105,7 @@ INTERCEPT_CAPABLE = [ Mirage_2000_5, Rafale_M, + F_14A_135_GR, F_14B, F_15C, @@ -135,6 +137,7 @@ CAP_CAPABLE = [ F_86F_Sabre, F_4E, F_5E_3, + F_14A_135_GR, F_14B, F_15C, F_15E, @@ -183,6 +186,7 @@ CAP_PREFERRED = [ Mirage_2000_5, F_86F_Sabre, + F_14A_135_GR, F_14B, F_15C, @@ -226,6 +230,7 @@ CAS_CAPABLE = [ F_86F_Sabre, F_5E_3, + F_14A_135_GR, F_14B, F_15E, F_16A, @@ -390,6 +395,7 @@ STRIKE_CAPABLE = [ F_86F_Sabre, F_5E_3, + F_14A_135_GR, F_14B, F_15E, F_16A, diff --git a/resources/customized_payloads/F-14A-135-GR.lua b/resources/customized_payloads/F-14A-135-GR.lua new file mode 100644 index 00000000..9926af41 --- /dev/null +++ b/resources/customized_payloads/F-14A-135-GR.lua @@ -0,0 +1,343 @@ +local unitPayloads = { + ["name"] = "F-14A", + ["payloads"] = { + [1] = { + ["name"] = "CAP", + ["pylons"] = { + [1] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 10, + }, + [2] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{SHOULDER AIM_54C_Mk47 L}", + ["num"] = 2, + }, + [4] = { + ["CLSID"] = "{SHOULDER AIM_54C_Mk47 R}", + ["num"] = 9, + }, + [5] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 8, + }, + [6] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 3, + }, + [7] = { + ["CLSID"] = "{AIM_54C_Mk47}", + ["num"] = 7, + }, + [8] = { + ["CLSID"] = "{AIM_54C_Mk47}", + ["num"] = 4, + }, + [9] = { + ["CLSID"] = "{AIM_54C_Mk47}", + ["num"] = 6, + }, + [10] = { + ["CLSID"] = "{AIM_54C_Mk47}", + ["num"] = 5, + }, + }, + ["tasks"] = { + [1] = 10, + }, + }, + [2] = { + ["name"] = "CAS", + ["pylons"] = { + [1] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 10, + }, + [2] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{F14-LANTIRN-TP}", + ["num"] = 9, + }, + [4] = { + ["CLSID"] = "{PHXBRU3242_2*LAU10 LS}", + ["num"] = 2, + }, + [5] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 8, + }, + [6] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 3, + }, + [7] = { + ["CLSID"] = "{BRU-32 MK-82}", + ["num"] = 7, + }, + [8] = { + ["CLSID"] = "{BRU-32 MK-82}", + ["num"] = 4, + }, + [9] = { + ["CLSID"] = "{BRU-32 MK-82}", + ["num"] = 6, + }, + [10] = { + ["CLSID"] = "{BRU-32 MK-82}", + ["num"] = 5, + }, + }, + ["tasks"] = { + [1] = 10, + }, + }, + [3] = { + ["name"] = "SEAD", + ["pylons"] = { + [1] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 10, + }, + [2] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{SHOULDER AIM_54C_Mk47 R}", + ["num"] = 9, + }, + [4] = { + ["CLSID"] = "{SHOULDER AIM_54C_Mk47 L}", + ["num"] = 2, + }, + [5] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 8, + }, + [6] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 3, + }, + [7] = { + ["CLSID"] = "{BRU3242_ADM141}", + ["num"] = 7, + }, + [8] = { + ["CLSID"] = "{BRU3242_ADM141}", + ["num"] = 4, + }, + [9] = { + ["CLSID"] = "{BRU3242_ADM141}", + ["num"] = 6, + }, + [10] = { + ["CLSID"] = "{BRU3242_ADM141}", + ["num"] = 5, + }, + }, + ["tasks"] = { + [1] = 10, + }, + }, + [4] = { + ["name"] = "DEAD", + ["pylons"] = { + [1] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 10, + }, + [2] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{F14-LANTIRN-TP}", + ["num"] = 9, + }, + [4] = { + ["CLSID"] = "{PHXBRU3242_2*LAU10 LS}", + ["num"] = 2, + }, + [5] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 8, + }, + [6] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 3, + }, + [7] = { + ["CLSID"] = "{BRU-32 GBU-12}", + ["num"] = 7, + }, + [8] = { + ["CLSID"] = "{BRU-32 GBU-12}", + ["num"] = 4, + }, + [9] = { + ["CLSID"] = "{BRU-32 GBU-12}", + ["num"] = 6, + }, + [10] = { + ["CLSID"] = "{BRU-32 GBU-12}", + ["num"] = 5, + }, + }, + ["tasks"] = { + [1] = 10, + }, + }, + [5] = { + ["name"] = "STRIKE", + ["pylons"] = { + [1] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 10, + }, + [2] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{F14-LANTIRN-TP}", + ["num"] = 9, + }, + [4] = { + ["CLSID"] = "{SHOULDER AIM-7MH}", + ["num"] = 2, + }, + [5] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 8, + }, + [6] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 3, + }, + [7] = { + ["CLSID"] = "{BRU-32 GBU-16}", + ["num"] = 7, + }, + [8] = { + ["CLSID"] = "{BRU-32 GBU-16}", + ["num"] = 4, + }, + [9] = { + ["CLSID"] = "{BRU-32 GBU-16}", + ["num"] = 6, + }, + [10] = { + ["CLSID"] = "{BRU-32 GBU-16}", + ["num"] = 5, + }, + }, + ["tasks"] = { + [1] = 10, + }, + }, + [6] = { + ["name"] = "BAI", + ["pylons"] = { + [1] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 10, + }, + [2] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 1, + }, + [3] = { + ["CLSID"] = "{F14-LANTIRN-TP}", + ["num"] = 9, + }, + [4] = { + ["CLSID"] = "{PHXBRU3242_2*LAU10 LS}", + ["num"] = 2, + }, + [5] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 8, + }, + [6] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 3, + }, + [7] = { + ["CLSID"] = "{BRU-32 MK-82}", + ["num"] = 7, + }, + [8] = { + ["CLSID"] = "{BRU-32 MK-82}", + ["num"] = 4, + }, + [9] = { + ["CLSID"] = "{BRU-32 MK-20}", + ["num"] = 6, + }, + [10] = { + ["CLSID"] = "{BRU-32 MK-20}", + ["num"] = 5, + }, + }, + ["tasks"] = { + [1] = 10, + }, + }, + [7] = { + ["name"] = "ANTISHIP", + ["pylons"] = { + [1] = { + ["CLSID"] = "{F14-LANTIRN-TP}", + ["num"] = 9, + }, + [2] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 10, + }, + [3] = { + ["CLSID"] = "{LAU-138 wtip - AIM-9M}", + ["num"] = 1, + }, + [4] = { + ["CLSID"] = "{PHXBRU3242_2*LAU10 LS}", + ["num"] = 2, + }, + [5] = { + ["CLSID"] = "{BRU-32 GBU-16}", + ["num"] = 7, + }, + [6] = { + ["CLSID"] = "{BRU-32 GBU-16}", + ["num"] = 4, + }, + [7] = { + ["CLSID"] = "{BRU3242_ADM141}", + ["num"] = 6, + }, + [8] = { + ["CLSID"] = "{BRU3242_ADM141}", + ["num"] = 5, + }, + [9] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 3, + }, + [10] = { + ["CLSID"] = "{F14-300gal}", + ["num"] = 8, + }, + }, + ["tasks"] = { + [1] = 10, + }, + }, + }, + ["unitType"] = "F-14A-135-GR", +} +return unitPayloads diff --git a/resources/factions/bluefor_coldwar.json b/resources/factions/bluefor_coldwar.json index dd711747..b06ad1fb 100644 --- a/resources/factions/bluefor_coldwar.json +++ b/resources/factions/bluefor_coldwar.json @@ -4,6 +4,7 @@ "authors": "Khopa", "description": "

A generic bluefor coldwar faction.

", "aircrafts": [ + "F_14A_135_GR", "F_14B", "F_4E", "F_5E_3", diff --git a/resources/factions/bluefor_coldwar_a4.json b/resources/factions/bluefor_coldwar_a4.json index 12fe48bf..78cc28fb 100644 --- a/resources/factions/bluefor_coldwar_a4.json +++ b/resources/factions/bluefor_coldwar_a4.json @@ -4,6 +4,7 @@ "authors": "Khopa", "description": "

A generic bluefor coldwar faction. (With the A-4E-C mod)

", "aircrafts": [ + "F_14A_135_GR", "F_14B", "F_4E", "F_5E_3", diff --git a/resources/factions/bluefor_coldwar_a4_mb339.json b/resources/factions/bluefor_coldwar_a4_mb339.json index d106c693..a0c31139 100644 --- a/resources/factions/bluefor_coldwar_a4_mb339.json +++ b/resources/factions/bluefor_coldwar_a4_mb339.json @@ -4,6 +4,7 @@ "authors": "Khopa", "description": "

A generic bluefor coldwar faction. (With the A-4E-C and the MB-339 mods)

", "aircrafts": [ + "F_14A_135_GR", "F_14B", "F_4E", "F_5E_3", diff --git a/resources/factions/iran_2015.json b/resources/factions/iran_2015.json index 6b028767..fee95e9e 100644 --- a/resources/factions/iran_2015.json +++ b/resources/factions/iran_2015.json @@ -8,7 +8,7 @@ "MiG_29A", "F_4E", "F_5E_3", - "F_14B", + "F_14A_135_GR", "Su_17M4", "Su_24M", "Su_25", diff --git a/resources/factions/usa_1975.json b/resources/factions/usa_1975.json index 77871754..c1d8906f 100644 --- a/resources/factions/usa_1975.json +++ b/resources/factions/usa_1975.json @@ -6,7 +6,7 @@ "aircrafts": [ "F_5E_3", "F_4E", - "F_14B", + "F_14A_135_GR", "B_52H", "UH_1H" ], diff --git a/resources/factions/usa_1990.json b/resources/factions/usa_1990.json index d1f1a270..c09f23cf 100644 --- a/resources/factions/usa_1990.json +++ b/resources/factions/usa_1990.json @@ -6,6 +6,7 @@ "aircrafts": [ "F_15C", "F_15E", + "F_14A_135_GR", "F_14B", "FA_18C_hornet", "F_16C_50", @@ -85,5 +86,25 @@ "ArleighBurkeGroupGenerator" ], "has_jtac": true, - "jtac_unit": "MQ_9_Reaper" + "jtac_unit": "MQ_9_Reaper", + "liveries_overrides": { + "FA_18C_hornet": [ + "VFA-37", + "VFA-106", + "VFA-113", + "VFA-122", + "VFA-131", + "VFA-192", + "VFA-34", + "VFA-83", + "VFA-87", + "VFA-97", + "VMFA-122", + "VMFA-132", + "VMFA-251", + "VMFA-312", + "VMFA-314", + "VMFA-323" + ] + } } \ No newline at end of file diff --git a/resources/factions/usn_1985.json b/resources/factions/usn_1985.json index 6ca2f2f1..b7f8d07f 100644 --- a/resources/factions/usn_1985.json +++ b/resources/factions/usn_1985.json @@ -5,6 +5,7 @@ "description": "

Highway to the Danger Zone! For Tomcat lovers.

", "aircrafts": [ "F_4E", + "F_14A_135_GR", "F_14B", "S_3B", "UH_1H", From 8d57bbc7773ccde128594073e5df48f368bec94d Mon Sep 17 00:00:00 2001 From: Khopa Date: Thu, 19 Nov 2020 20:17:11 +0100 Subject: [PATCH 30/39] Changelog update 2.2.1 --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 2ae74709..9e87ba2e 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,8 @@ * **[Flight Planner]** Custom waypoints are usable again. Not that in most cases custom flight plans will revert to the 2.1 flight planning behavior. * **[Flight Planner]** Fixed UI bug that made it possible to create empty flights which would throw an error. * **[Flight Planner]** Player flights from carriers will now be delayed correctly according to the player's settings. +* **[Misc]** Spitfire variant with clipped wings was not seen as flyable by DCS Liberation (hence could not be setup as client/player slot) +* **[Misc]** Updated Syria terrain parking slots database # 2.2.0 From e6e4cca0769688081bb239da8b3c6db68acba91e Mon Sep 17 00:00:00 2001 From: Khopa Date: Mon, 16 Nov 2020 23:57:12 +0100 Subject: [PATCH 31/39] Made it possible to setup custom liveries in faction files. --- game/factions/faction.py | 11 +++++++++++ gen/aircraft.py | 11 +++++++++++ resources/factions/us_aggressors.json | 21 ++++++++++++++++++++- resources/factions/usa_2005.json | 22 +++++++++++++++++++++- resources/factions/usn_1985.json | 16 +++++++++++++++- 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/game/factions/faction.py b/game/factions/faction.py index 5a056bf1..b0caf4bb 100644 --- a/game/factions/faction.py +++ b/game/factions/faction.py @@ -105,6 +105,9 @@ class Faction: # List of available buildings for this faction building_set: List[str] = field(default_factory=list) + # List of default livery overrides + liveries_overrides: Dict[UnitType, List[str]] = field(default_factory=dict) + @classmethod def from_json(cls: Type[Faction], json: Dict[str, Any]) -> Faction: @@ -183,6 +186,14 @@ class Faction: else: faction.building_set = DEFAULT_AVAILABLE_BUILDINGS + # Load liveries override + faction.liveries_overrides = {} + liveries_overrides = json.get("liveries_overrides", {}) + for k, v in liveries_overrides.items(): + k = load_aircraft(k) + if k is not None: + faction.liveries_overrides[k] = [s.lower() for s in v] + return faction @property diff --git a/gen/aircraft.py b/gen/aircraft.py index f17caea4..85e827ff 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -713,6 +713,17 @@ class AircraftConflictGenerator: for unit_instance in group.units: unit_instance.livery_id = db.PLANE_LIVERY_OVERRIDES[unit_type] + # Override livery by faction file data + if flight.from_cp.captured: + faction = self.game.player_faction + else: + faction = self.game.enemy_faction + + if unit_type in faction.liveries_overrides: + livery = random.choice(faction.liveries_overrides[unit_type]) + for unit_instance in group.units: + unit_instance.livery_id = livery + for idx in range(0, min(len(group.units), flight.client_count)): unit = group.units[idx] if self.use_client: diff --git a/resources/factions/us_aggressors.json b/resources/factions/us_aggressors.json index 9e2b41a2..e3bf8108 100644 --- a/resources/factions/us_aggressors.json +++ b/resources/factions/us_aggressors.json @@ -61,5 +61,24 @@ "OliverHazardPerryGroupGenerator" ], "has_jtac": true, - "jtac_unit": "MQ_9_Reaper" + "jtac_unit": "MQ_9_Reaper", + "liveries_overrides": { + "FA_18C_hornet": [ + "NSAWC brown splinter", + "NAWDC black", + "VFC-12" + ], + "F_15C": [ + "65th Aggressor SQN (WA) MiG", + "65th Aggressor SQN (WA) MiG", + "65th Aggressor SQN (WA) SUPER_Flanker" + ], + "F_16C_50": [ + "usaf 64th aggressor sqn - shark", + "usaf 64th aggressor sqn-splinter", + "64th_aggressor_squadron_ghost" + ], "F_14B": [ + "vf-74 adversary" + ] + } } \ No newline at end of file diff --git a/resources/factions/usa_2005.json b/resources/factions/usa_2005.json index 59c3a4f2..f10e36bb 100644 --- a/resources/factions/usa_2005.json +++ b/resources/factions/usa_2005.json @@ -86,5 +86,25 @@ "OliverHazardPerryGroupGenerator" ], "has_jtac": true, - "jtac_unit": "MQ_9_Reaper" + "jtac_unit": "MQ_9_Reaper", + "liveries_overrides": { + "FA_18C_hornet": [ + "VFA-37", + "VFA-106", + "VFA-113", + "VFA-122", + "VFA-131", + "VFA-192", + "VFA-34", + "VFA-83", + "VFA-87", + "VFA-97", + "VMFA-122", + "VMFA-132", + "VMFA-251", + "VMFA-312", + "VMFA-314", + "VMFA-323" + ] + } } \ No newline at end of file diff --git a/resources/factions/usn_1985.json b/resources/factions/usn_1985.json index b7f8d07f..fabb207f 100644 --- a/resources/factions/usn_1985.json +++ b/resources/factions/usn_1985.json @@ -70,5 +70,19 @@ "OliverHazardPerryGroupGenerator" ], "requirements": {}, - "doctrine": "coldwar" + "doctrine": "coldwar", + "liveries_overrides": { + "FA_18C_hornet": [ + "VFA-37", + "VFA-106", + "VFA-113", + "VFA-122", + "VFA-131", + "VFA-192", + "VFA-34", + "VFA-83", + "VFA-87", + "VFA-97" + ] + } } \ No newline at end of file From 3eb2529b0bf452557ace9fc961a01f0f38ee3314 Mon Sep 17 00:00:00 2001 From: walterroach Date: Thu, 19 Nov 2020 13:20:05 -0600 Subject: [PATCH 32/39] Fix #402 --- gen/aircraft.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/gen/aircraft.py b/gen/aircraft.py index 85e827ff..c0c5c370 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -1173,12 +1173,13 @@ class AircraftConflictGenerator: viggen_target_points = [ (idx, point) for idx, point in enumerate(filtered_points) if point.waypoint_type in TARGET_WAYPOINTS ] - keep_target = viggen_target_points[random.randint(0, len(viggen_target_points) - 1)] - filtered_points = [ - point for idx, point in enumerate(filtered_points) if ( - point.waypoint_type not in TARGET_WAYPOINTS or idx == keep_target[0] - ) - ] + if viggen_target_points: + keep_target = viggen_target_points[random.randint(0, len(viggen_target_points) - 1)] + filtered_points = [ + point for idx, point in enumerate(filtered_points) if ( + point.waypoint_type not in TARGET_WAYPOINTS or idx == keep_target[0] + ) + ] for idx, point in enumerate(filtered_points): PydcsWaypointBuilder.for_waypoint( From 434755a6201572259ff49b8a04afc2312e9b236c Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Thu, 19 Nov 2020 14:23:43 -0600 Subject: [PATCH 33/39] Update README.md --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d51bd19..82e950b4 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ First, a big thanks to shdwp, for starting the original DCS Liberation project. Then, DCS Liberation uses [pydcs](http://github.com/pydcs/dcs) for mission generation, and nothing would be possible without this. It also uses the popular [Mist](https://github.com/mrSkortch/MissionScriptingTools) lua framework for mission scripting. -And for the JTAC feature, DCS Liberation embed Ciribob's JTAC Autolase [script](https://github.com/ciribob/DCS-JTACAutoLaze). + +Excellent lua scripts DCS Liberation uses as plugins: + +* For the JTAC feature, DCS Liberation embeds Ciribob's JTAC Autolase [script](https://github.com/ciribob/DCS-JTACAutoLaze). +* Walder's [Skynet-IADS](https://github.com/walder/Skynet-IADS) is used for Integrated Air Defense System. Please also show some support to these projects ! From b7634a8ac32e014644754423b1cbbe86e2fe692f Mon Sep 17 00:00:00 2001 From: Khopa Date: Thu, 19 Nov 2020 21:54:33 +0100 Subject: [PATCH 34/39] About dialog update --- qt_ui/windows/QLiberationWindow.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qt_ui/windows/QLiberationWindow.py b/qt_ui/windows/QLiberationWindow.py index 67dba6f8..1560fb16 100644 --- a/qt_ui/windows/QLiberationWindow.py +++ b/qt_ui/windows/QLiberationWindow.py @@ -241,11 +241,12 @@ class QLiberationWindow(QMainWindow): "

Authors

" + \ "

DCS Liberation was originally developed by shdwp, DCS Liberation 2.0 is a partial rewrite based on this work by Khopa." \ "

Contributors

" + \ - "shdwp, Khopa, ColonelPanic, Wrycu, calvinmorrow, JohanAberg, Deus, root0fall, Captain Cody, steveveepee, pedromagueija, parithon, bwRavencl, davidp57" + \ + "shdwp, Khopa, ColonelPanic, Roach, Wrycu, calvinmorrow, JohanAberg, Deus, root0fall, Captain Cody, steveveepee, pedromagueija, parithon, bwRavencl, davidp57" + \ "

Special Thanks :

" \ "rp- for the pydcs framework
"\ "Grimes (mrSkortch) & Speed for the MIST framework
"\ - "Ciribob for the JTACAutoLase.lua script
" + "Ciribob for the JTACAutoLase.lua script
"\ + "Walder for the Skynet-IADS script
" about = QMessageBox() about.setWindowTitle("About DCS Liberation") about.setIcon(QMessageBox.Icon.Information) From 87248fec53adc718c691c96a8d9c644bc05e46b1 Mon Sep 17 00:00:00 2001 From: Khopa Date: Thu, 19 Nov 2020 21:55:11 +0100 Subject: [PATCH 35/39] Changelog update --- changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 9e87ba2e..20b0555a 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ * **[Flight Planner]** Player flights with start delays under ten minutes will spawn immediately. * **[UI]** Mission start screen now informs players about delayed flights. * **[Units]** Added support for F-14A-135-GR +* **[Modding]** Possible to setup liveries overrides in factions definition files ## Fixes : * **[Flight Planner]** Hold, join, and split points are planned cautiously near enemy airfields. Ascend/descend points are no longer planned. @@ -13,7 +14,7 @@ * **[Flight Planner]** Fixed UI bug that made it possible to create empty flights which would throw an error. * **[Flight Planner]** Player flights from carriers will now be delayed correctly according to the player's settings. * **[Misc]** Spitfire variant with clipped wings was not seen as flyable by DCS Liberation (hence could not be setup as client/player slot) -* **[Misc]** Updated Syria terrain parking slots database +* **[Misc]** Updated Syria terrain parking slots database, the out-of-date database could end up generating aircraft in wrong slots (We are still experiencing issues with somes airbases, such as Khalkhalah though) # 2.2.0 From 206d09f7f836a92563ffaf1dede19655ad31f6ee Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 00:18:00 -0800 Subject: [PATCH 36/39] Maybe correct fishbed radios. Maybe fixes https://github.com/Khopa/dcs_liberation/issues/377 --- gen/radios.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/radios.py b/gen/radios.py index c2180fe3..87b8661f 100644 --- a/gen/radios.py +++ b/gen/radios.py @@ -134,7 +134,7 @@ RADIOS: List[Radio] = [ Radio("RSIU-4V", MHz(100), MHz(150), step=MHz(1)), # MiG-21bis - Radio("RSIU-5V", MHz(100), MHz(150), step=MHz(1)), + Radio("RSIU-5V", MHz(118), MHz(140), step=MHz(1)), # Ka-50 # Note: Also capable of 100MHz-150MHz, but we can't model gaps. From c3b028ef4ba42f3f4a980654616afe0f77a45fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Mu=C3=B1oz=20Fernandez?= Date: Fri, 20 Nov 2020 12:47:35 +0100 Subject: [PATCH 37/39] fix: fixes #325 for version 2.2.x --- game/game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game/game.py b/game/game.py index 7308a128..8895d405 100644 --- a/game/game.py +++ b/game/game.py @@ -154,7 +154,7 @@ class Game: reward = PLAYER_BUDGET_BASE * len(self.theater.player_points()) for cp in self.theater.player_points(): for g in cp.ground_objects: - if g.category in REWARDS.keys(): + if g.category in REWARDS.keys() and not g.is_dead: reward = reward + REWARDS[g.category] return reward else: @@ -277,7 +277,7 @@ class Game: production = 0.0 for enemy_point in self.theater.enemy_points(): for g in enemy_point.ground_objects: - if g.category in REWARDS.keys(): + if g.category in REWARDS.keys() and not g.is_dead: production = production + REWARDS[g.category] production = production * 0.75 From 70d982b0eda47ea6f795bf294e57ca7b23903f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Mu=C3=B1oz=20Fernandez?= Date: Fri, 20 Nov 2020 15:10:08 +0100 Subject: [PATCH 38/39] fix: Pass Turn and Take Off Buttons when no game is loaded --- qt_ui/widgets/QTopPanel.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index 79f4e77e..57565208 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -48,12 +48,14 @@ class QTopPanel(QFrame): self.passTurnButton.setIcon(CONST.ICONS["PassTurn"]) self.passTurnButton.setProperty("style", "btn-primary") self.passTurnButton.clicked.connect(self.passTurn) + if not self.game or self.game.turn == 0: + self.passTurnButton.setEnabled(False) self.proceedButton = QPushButton("Take off") self.proceedButton.setIcon(CONST.ICONS["Proceed"]) self.proceedButton.setProperty("style", "start-button") self.proceedButton.clicked.connect(self.launch_mission) - if self.game and self.game.turn == 0: + if not self.game or self.game.turn == 0: self.proceedButton.setEnabled(False) self.factionsInfos = QFactionsInfos(self.game) @@ -101,6 +103,8 @@ class QTopPanel(QFrame): self.budgetBox.setGame(game) self.factionsInfos.setGame(game) + self.passTurnButton.setEnabled(True) + if game and game.turn == 0: self.proceedButton.setEnabled(False) else: From d394d01ea830834bde340c5034f3e0d1a94dd06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Mu=C3=B1oz=20Fernandez?= Date: Fri, 20 Nov 2020 15:11:12 +0100 Subject: [PATCH 39/39] fix: conditional value at passTurn on QTopPanel --- qt_ui/widgets/QTopPanel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/widgets/QTopPanel.py b/qt_ui/widgets/QTopPanel.py index 57565208..a18e2bbe 100644 --- a/qt_ui/widgets/QTopPanel.py +++ b/qt_ui/widgets/QTopPanel.py @@ -48,7 +48,7 @@ class QTopPanel(QFrame): self.passTurnButton.setIcon(CONST.ICONS["PassTurn"]) self.passTurnButton.setProperty("style", "btn-primary") self.passTurnButton.clicked.connect(self.passTurn) - if not self.game or self.game.turn == 0: + if not self.game: self.passTurnButton.setEnabled(False) self.proceedButton = QPushButton("Take off")