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 ! diff --git a/changelog.md b/changelog.md index df66d42d..20b0555a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,21 @@ +# 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 +* **[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. +* **[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, 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 ## 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/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/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 7c4eac80..c0c5c370 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: @@ -1162,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( @@ -1187,6 +1199,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, @@ -1213,15 +1231,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 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/gen/flights/flightplan.py b/gen/flights/flightplan.py index e0df2b01..430a8c11 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, ] @@ -573,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( @@ -674,18 +659,15 @@ 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, 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 + land=builder.land(flight.from_cp) ) def generate_frontline_cap(self, flight: Flight) -> FrontLineCapFlightPlan: @@ -724,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, @@ -736,11 +717,9 @@ 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 + land=builder.land(flight.from_cp) ) def generate_dead(self, flight: Flight, @@ -799,21 +778,18 @@ 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, 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 + land=builder.land(flight.from_cp) ) def generate_cas(self, flight: Flight) -> CasFlightPlan: @@ -835,19 +811,16 @@ 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, 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 + land=builder.land(flight.from_cp) ) @staticmethod @@ -871,36 +844,52 @@ 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, - 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. @@ -939,31 +928,54 @@ 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, 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 + land=builder.land(flight.from_cp) ) - def _join_point(self, ingress_point: Point) -> Point: - 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: - 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() @@ -983,6 +995,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. diff --git a/gen/flights/traveltime.py b/gen/flights/traveltime.py index ee9a6c7e..0bddfcaf 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,15 @@ 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 " - f"estimate takeoff time takeoff time for {flight}") - # Takeoff immediately. - return timedelta() + from gen.flights.flightplan import CustomFlightPlan + if not isinstance(flight.flight_plan, CustomFlightPlan): + logging.warning( + "Found no rendezvous or target point. Cannot estimate " + f"takeoff time takeoff time for {flight}.") + return None from gen.flights.flightplan import FormationFlightPlan if isinstance(flight.flight_plan, FormationFlightPlan): @@ -126,7 +133,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/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/pydcs b/pydcs index fa9195fb..2883be31 160000 --- a/pydcs +++ b/pydcs @@ -1 +1 @@ -Subproject commit fa9195fbccbf96775d108a22c13c3ee2375e4c0b +Subproject commit 2883be31c2eb80834b93efd8d20ca17913986e9b 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/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/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): "
DCS Liberation was originally developed by shdwp, DCS Liberation 2.0 is a partial rewrite based on this work by Khopa." \ "
Then once the mission is loaded in ME, in menu \"Flight\",\n" + \ - "click on FLY Mission to launch.
\n" + \ - "" + \ - "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.)" + \ - "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/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) diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointTab.py index 43198d28..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 ( @@ -12,11 +12,15 @@ 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\ +from qt_ui.windows.mission.flight.waypoints \ .QPredefinedWaypointSelectionWindow import \ QPredefinedWaypointSelectionWindow from theater import FrontLine @@ -34,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 @@ -78,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) @@ -103,35 +97,51 @@ 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. + 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 + + 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/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: ==================== 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 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] = { 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/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/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/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_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/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 new file mode 100644 index 00000000..fabb207f --- /dev/null +++ b/resources/factions/usn_1985.json @@ -0,0 +1,88 @@ +{ + "country": "USA", + "name": "US Navy 1985", + "authors": "HerrTom", + "description": "Highway to the Danger Zone! For Tomcat lovers.
", + "aircrafts": [ + "F_4E", + "F_14A_135_GR", + "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", + "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 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. +
+ ++ 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. +
+ ++ 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.) + +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 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