mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Differentiate BARCAP and TARCAP.
Previously the only difference between these was the objective type: TARCAP was for front lines and BARCAP was for everything else. Now BARCAP is for friendly areas and TARCAP is for enemy areas. The practical difference between the two is that a TARCAP package is like the old front line CAP in that it will adjust its patrol time to match the package if it can, and it will also arrive two minutes ahead of the rest of the package to clear the area if needed.
This commit is contained in:
parent
8eef1eaa7c
commit
28e00055ab
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
# Features/Improvements
|
# Features/Improvements
|
||||||
* **[Flight Planner]** Added fighter sweep missions.
|
* **[Flight Planner]** Added fighter sweep missions.
|
||||||
|
* **[Flight Planner]** Differentiated BARCAP and TARCAP. TARCAP is now for hostile areas and will arrive before the package.
|
||||||
|
|
||||||
# 2.2.1
|
# 2.2.1
|
||||||
|
|
||||||
|
|||||||
@ -496,7 +496,11 @@ class CoalitionMissionPlanner:
|
|||||||
error = random.randint(-margin, margin)
|
error = random.randint(-margin, margin)
|
||||||
yield timedelta(minutes=max(0, time + error))
|
yield timedelta(minutes=max(0, time + error))
|
||||||
|
|
||||||
dca_types = (FlightType.BARCAP, FlightType.INTERCEPTION)
|
dca_types = {
|
||||||
|
FlightType.BARCAP,
|
||||||
|
FlightType.INTERCEPTION,
|
||||||
|
FlightType.TARCAP,
|
||||||
|
}
|
||||||
|
|
||||||
non_dca_packages = [p for p in self.ato.packages if
|
non_dca_packages = [p for p in self.ato.packages if
|
||||||
p.primary_task not in dca_types]
|
p.primary_task not in dca_types]
|
||||||
|
|||||||
@ -340,9 +340,10 @@ class CasFlightPlan(PatrollingFlightPlan):
|
|||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class FrontLineCapFlightPlan(PatrollingFlightPlan):
|
class TarCapFlightPlan(PatrollingFlightPlan):
|
||||||
takeoff: FlightWaypoint
|
takeoff: FlightWaypoint
|
||||||
land: FlightWaypoint
|
land: FlightWaypoint
|
||||||
|
lead_time: timedelta
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def waypoints(self) -> List[FlightWaypoint]:
|
def waypoints(self) -> List[FlightWaypoint]:
|
||||||
@ -353,6 +354,10 @@ class FrontLineCapFlightPlan(PatrollingFlightPlan):
|
|||||||
self.land,
|
self.land,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def tot_offset(self) -> timedelta:
|
||||||
|
return -self.lead_time
|
||||||
|
|
||||||
def depart_time_for_waypoint(
|
def depart_time_for_waypoint(
|
||||||
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
self, waypoint: FlightWaypoint) -> Optional[timedelta]:
|
||||||
if waypoint == self.patrol_end:
|
if waypoint == self.patrol_end:
|
||||||
@ -363,8 +368,8 @@ class FrontLineCapFlightPlan(PatrollingFlightPlan):
|
|||||||
def patrol_start_time(self) -> timedelta:
|
def patrol_start_time(self) -> timedelta:
|
||||||
start = self.package.escort_start_time
|
start = self.package.escort_start_time
|
||||||
if start is not None:
|
if start is not None:
|
||||||
return start
|
return start + self.tot_offset
|
||||||
return super().patrol_start_time
|
return super().patrol_start_time + self.tot_offset
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def patrol_end_time(self) -> timedelta:
|
def patrol_end_time(self) -> timedelta:
|
||||||
@ -374,6 +379,10 @@ class FrontLineCapFlightPlan(PatrollingFlightPlan):
|
|||||||
return super().patrol_end_time
|
return super().patrol_end_time
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Remove when breaking save compat.
|
||||||
|
FrontLineCapFlightPlan = TarCapFlightPlan
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class StrikeFlightPlan(FormationFlightPlan):
|
class StrikeFlightPlan(FormationFlightPlan):
|
||||||
takeoff: FlightWaypoint
|
takeoff: FlightWaypoint
|
||||||
@ -635,7 +644,7 @@ class FlightPlanBuilder:
|
|||||||
elif task == FlightType.SWEEP:
|
elif task == FlightType.SWEEP:
|
||||||
return self.generate_sweep(flight)
|
return self.generate_sweep(flight)
|
||||||
elif task == FlightType.TARCAP:
|
elif task == FlightType.TARCAP:
|
||||||
return self.generate_frontline_cap(flight)
|
return self.generate_tarcap(flight)
|
||||||
elif task == FlightType.TROOP_TRANSPORT:
|
elif task == FlightType.TROOP_TRANSPORT:
|
||||||
logging.error(
|
logging.error(
|
||||||
"Troop transport flight plan generation not implemented"
|
"Troop transport flight plan generation not implemented"
|
||||||
@ -704,47 +713,12 @@ class FlightPlanBuilder:
|
|||||||
if isinstance(location, FrontLine):
|
if isinstance(location, FrontLine):
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
raise InvalidObjectiveLocation(flight.flight_type, location)
|
||||||
|
|
||||||
|
start, end = self.racetrack_for_objective(location)
|
||||||
patrol_alt = random.randint(
|
patrol_alt = random.randint(
|
||||||
self.doctrine.min_patrol_altitude,
|
self.doctrine.min_patrol_altitude,
|
||||||
self.doctrine.max_patrol_altitude
|
self.doctrine.max_patrol_altitude
|
||||||
)
|
)
|
||||||
|
|
||||||
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
|
||||||
for airfield in closest_cache.closest_airfields:
|
|
||||||
# If the mission is a BARCAP of an enemy airfield, find the *next*
|
|
||||||
# closest enemy airfield.
|
|
||||||
if airfield == self.package.target:
|
|
||||||
continue
|
|
||||||
if airfield.captured != self.is_player:
|
|
||||||
closest_airfield = airfield
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise PlanningError("Could not find any enemy airfields")
|
|
||||||
|
|
||||||
heading = location.position.heading_between_point(
|
|
||||||
closest_airfield.position
|
|
||||||
)
|
|
||||||
|
|
||||||
min_distance_from_enemy = nm_to_meter(20)
|
|
||||||
distance_to_airfield = int(closest_airfield.position.distance_to_point(
|
|
||||||
self.package.target.position
|
|
||||||
))
|
|
||||||
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
|
||||||
min_cap_distance = min(self.doctrine.cap_min_distance_from_cp,
|
|
||||||
distance_to_no_fly)
|
|
||||||
max_cap_distance = min(self.doctrine.cap_max_distance_from_cp,
|
|
||||||
distance_to_no_fly)
|
|
||||||
|
|
||||||
end = location.position.point_from_heading(
|
|
||||||
heading,
|
|
||||||
random.randint(min_cap_distance, max_cap_distance)
|
|
||||||
)
|
|
||||||
diameter = random.randint(
|
|
||||||
self.doctrine.cap_min_track_length,
|
|
||||||
self.doctrine.cap_max_track_length
|
|
||||||
)
|
|
||||||
start = end.point_from_heading(heading - 180, diameter)
|
|
||||||
|
|
||||||
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
start, end = builder.race_track(start, end, patrol_alt)
|
start, end = builder.race_track(start, end, patrol_alt)
|
||||||
descent, land = builder.rtb(flight.from_cp)
|
descent, land = builder.rtb(flight.from_cp)
|
||||||
@ -788,20 +762,48 @@ class FlightPlanBuilder:
|
|||||||
land=land
|
land=land
|
||||||
)
|
)
|
||||||
|
|
||||||
def generate_frontline_cap(self, flight: Flight) -> FrontLineCapFlightPlan:
|
def racetrack_for_objective(self,
|
||||||
"""Generate a CAP flight plan for the given front line.
|
location: MissionTarget) -> Tuple[Point, Point]:
|
||||||
|
closest_cache = ObjectiveDistanceCache.get_closest_airfields(location)
|
||||||
|
for airfield in closest_cache.closest_airfields:
|
||||||
|
# If the mission is a BARCAP of an enemy airfield, find the *next*
|
||||||
|
# closest enemy airfield.
|
||||||
|
if airfield == self.package.target:
|
||||||
|
continue
|
||||||
|
if airfield.captured != self.is_player:
|
||||||
|
closest_airfield = airfield
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise PlanningError("Could not find any enemy airfields")
|
||||||
|
|
||||||
Args:
|
heading = location.position.heading_between_point(
|
||||||
flight: The flight to generate the flight plan for.
|
closest_airfield.position
|
||||||
"""
|
)
|
||||||
location = self.package.target
|
|
||||||
|
|
||||||
if not isinstance(location, FrontLine):
|
min_distance_from_enemy = nm_to_meter(20)
|
||||||
raise InvalidObjectiveLocation(flight.flight_type, location)
|
distance_to_airfield = int(closest_airfield.position.distance_to_point(
|
||||||
|
self.package.target.position
|
||||||
|
))
|
||||||
|
distance_to_no_fly = distance_to_airfield - min_distance_from_enemy
|
||||||
|
min_cap_distance = min(self.doctrine.cap_min_distance_from_cp,
|
||||||
|
distance_to_no_fly)
|
||||||
|
max_cap_distance = min(self.doctrine.cap_max_distance_from_cp,
|
||||||
|
distance_to_no_fly)
|
||||||
|
|
||||||
ally_cp, enemy_cp = location.control_points
|
end = location.position.point_from_heading(
|
||||||
patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
|
heading,
|
||||||
self.doctrine.max_patrol_altitude)
|
random.randint(min_cap_distance, max_cap_distance)
|
||||||
|
)
|
||||||
|
diameter = random.randint(
|
||||||
|
self.doctrine.cap_min_track_length,
|
||||||
|
self.doctrine.cap_max_track_length
|
||||||
|
)
|
||||||
|
start = end.point_from_heading(heading - 180, diameter)
|
||||||
|
return start, end
|
||||||
|
|
||||||
|
def racetrack_for_frontline(self,
|
||||||
|
front_line: FrontLine) -> Tuple[Point, Point]:
|
||||||
|
ally_cp, enemy_cp = front_line.control_points
|
||||||
|
|
||||||
# Find targets waypoints
|
# Find targets waypoints
|
||||||
ingress, heading, distance = Conflict.frontline_vector(
|
ingress, heading, distance = Conflict.frontline_vector(
|
||||||
@ -822,14 +824,33 @@ class FlightPlanBuilder:
|
|||||||
orbit0p = orbit_center.point_from_heading(heading, radius)
|
orbit0p = orbit_center.point_from_heading(heading, radius)
|
||||||
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
|
||||||
|
|
||||||
|
return orbit0p, orbit1p
|
||||||
|
|
||||||
|
def generate_tarcap(self, flight: Flight) -> TarCapFlightPlan:
|
||||||
|
"""Generate a CAP flight plan for the given front line.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
flight: The flight to generate the flight plan for.
|
||||||
|
"""
|
||||||
|
location = self.package.target
|
||||||
|
|
||||||
|
patrol_alt = random.randint(self.doctrine.min_patrol_altitude,
|
||||||
|
self.doctrine.max_patrol_altitude)
|
||||||
|
|
||||||
# Create points
|
# Create points
|
||||||
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
|
||||||
|
|
||||||
|
if isinstance(location, FrontLine):
|
||||||
|
orbit0p, orbit1p = self.racetrack_for_frontline(location)
|
||||||
|
else:
|
||||||
|
orbit0p, orbit1p = self.racetrack_for_objective(location)
|
||||||
|
|
||||||
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
start, end = builder.race_track(orbit0p, orbit1p, patrol_alt)
|
||||||
descent, land = builder.rtb(flight.from_cp)
|
descent, land = builder.rtb(flight.from_cp)
|
||||||
return FrontLineCapFlightPlan(
|
return TarCapFlightPlan(
|
||||||
package=self.package,
|
package=self.package,
|
||||||
flight=flight,
|
flight=flight,
|
||||||
|
lead_time=timedelta(minutes=2),
|
||||||
# Note that this duration only has an effect if there are no
|
# Note that this duration only has an effect if there are no
|
||||||
# flights in the package that have requested escort. If the package
|
# flights in the package that have requested escort. If the package
|
||||||
# requests an escort the CAP flight will remain on station for the
|
# requests an escort the CAP flight will remain on station for the
|
||||||
|
|||||||
@ -18,6 +18,7 @@ class QFlightTypeComboBox(QComboBox):
|
|||||||
"""Combo box for selecting a flight task type."""
|
"""Combo box for selecting a flight task type."""
|
||||||
|
|
||||||
COMMON_ENEMY_MISSIONS = [
|
COMMON_ENEMY_MISSIONS = [
|
||||||
|
FlightType.TARCAP,
|
||||||
FlightType.ESCORT,
|
FlightType.ESCORT,
|
||||||
FlightType.SEAD,
|
FlightType.SEAD,
|
||||||
FlightType.DEAD,
|
FlightType.DEAD,
|
||||||
@ -50,7 +51,6 @@ class QFlightTypeComboBox(QComboBox):
|
|||||||
]
|
]
|
||||||
|
|
||||||
ENEMY_AIRBASE_MISSIONS = [
|
ENEMY_AIRBASE_MISSIONS = [
|
||||||
FlightType.BARCAP,
|
|
||||||
# TODO: FlightType.STRIKE
|
# TODO: FlightType.STRIKE
|
||||||
] + COMMON_ENEMY_MISSIONS
|
] + COMMON_ENEMY_MISSIONS
|
||||||
|
|
||||||
@ -60,13 +60,11 @@ class QFlightTypeComboBox(QComboBox):
|
|||||||
] + COMMON_FRIENDLY_MISSIONS
|
] + COMMON_FRIENDLY_MISSIONS
|
||||||
|
|
||||||
ENEMY_GROUND_OBJECT_MISSIONS = [
|
ENEMY_GROUND_OBJECT_MISSIONS = [
|
||||||
FlightType.BARCAP,
|
|
||||||
FlightType.STRIKE,
|
FlightType.STRIKE,
|
||||||
] + COMMON_ENEMY_MISSIONS
|
] + COMMON_ENEMY_MISSIONS
|
||||||
|
|
||||||
FRONT_LINE_MISSIONS = [
|
FRONT_LINE_MISSIONS = [
|
||||||
FlightType.CAS,
|
FlightType.CAS,
|
||||||
FlightType.TARCAP,
|
|
||||||
# TODO: FlightType.TROOP_TRANSPORT
|
# TODO: FlightType.TROOP_TRANSPORT
|
||||||
# TODO: FlightType.EVAC
|
# TODO: FlightType.EVAC
|
||||||
] + COMMON_ENEMY_MISSIONS
|
] + COMMON_ENEMY_MISSIONS
|
||||||
|
|||||||
@ -149,10 +149,10 @@ class QFlightWaypointTab(QFrame):
|
|||||||
# departs, whereas BARCAP usually isn't part of a strike package and
|
# departs, whereas BARCAP usually isn't part of a strike package and
|
||||||
# has a fixed mission time.
|
# has a fixed mission time.
|
||||||
if task == FlightType.CAP:
|
if task == FlightType.CAP:
|
||||||
if isinstance(self.package.target, FrontLine):
|
if self.package.target.is_friendly(to_player=True):
|
||||||
task = FlightType.TARCAP
|
|
||||||
else:
|
|
||||||
task = FlightType.BARCAP
|
task = FlightType.BARCAP
|
||||||
|
else:
|
||||||
|
task = FlightType.TARCAP
|
||||||
self.flight.flight_type = task
|
self.flight.flight_type = task
|
||||||
self.planner.populate_flight_plan(self.flight)
|
self.planner.populate_flight_plan(self.flight)
|
||||||
self.flight_waypoint_list.update_list()
|
self.flight_waypoint_list.update_list()
|
||||||
|
|||||||
@ -42,3 +42,6 @@ class FrontLine(MissionTarget):
|
|||||||
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
def control_points(self) -> Tuple[ControlPoint, ControlPoint]:
|
||||||
"""Returns a tuple of the two control points."""
|
"""Returns a tuple of the two control points."""
|
||||||
return self.control_point_a, self.control_point_b
|
return self.control_point_a, self.control_point_b
|
||||||
|
|
||||||
|
def is_friendly(self, to_player: bool) -> bool:
|
||||||
|
return False
|
||||||
|
|||||||
@ -17,3 +17,7 @@ class MissionTarget:
|
|||||||
def distance_to(self, other: MissionTarget) -> int:
|
def distance_to(self, other: MissionTarget) -> int:
|
||||||
"""Computes the distance to the given mission target."""
|
"""Computes the distance to the given mission target."""
|
||||||
return self.position.distance_to_point(other.position)
|
return self.position.distance_to_point(other.position)
|
||||||
|
|
||||||
|
def is_friendly(self, to_player: bool) -> bool:
|
||||||
|
"""Returns True if the objective is in friendly territory."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|||||||
@ -113,6 +113,9 @@ class TheaterGroundObject(MissionTarget):
|
|||||||
def faction_color(self) -> str:
|
def faction_color(self) -> str:
|
||||||
return "BLUE" if self.control_point.captured else "RED"
|
return "BLUE" if self.control_point.captured else "RED"
|
||||||
|
|
||||||
|
def is_friendly(self, to_player: bool) -> bool:
|
||||||
|
return not self.control_point.is_friendly(to_player)
|
||||||
|
|
||||||
|
|
||||||
class BuildingGroundObject(TheaterGroundObject):
|
class BuildingGroundObject(TheaterGroundObject):
|
||||||
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
def __init__(self, name: str, category: str, group_id: int, object_id: int,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user