Merge branch 'develop' into faction_refactor

# Conflicts:
#	game/factions/bluefor_coldwar.py
#	game/factions/bluefor_coldwar_a4.py
#	game/factions/bluefor_coldwar_mods.py
#	game/factions/bluefor_modern.py
This commit is contained in:
Khopa
2020-10-24 16:57:28 +02:00
36 changed files with 571 additions and 335 deletions

View File

@@ -206,10 +206,9 @@ class PackageBuilder:
if assignment is None:
return False
airfield, aircraft = assignment
flight = Flight(aircraft, plan.num_aircraft, airfield, plan.task,
self.start_type)
flight = Flight(self.package, aircraft, plan.num_aircraft, airfield,
plan.task, self.start_type)
self.package.add_flight(flight)
flight.targetPoint = self.package.target
return True
def build(self) -> Package:
@@ -222,7 +221,7 @@ class PackageBuilder:
for flight in flights:
self.global_inventory.return_from_flight(flight)
self.package.remove_flight(flight)
flight.targetPoint = None
class ObjectiveFinder:
"""Identifies potential objectives for the mission planner."""

View File

@@ -19,11 +19,14 @@ from dcs.planes import (
A_10C_2,
A_20G,
B_17G,
B_1B,
B_52H,
Bf_109K_4,
C_101CC,
FA_18C_hornet,
FW_190A8,
FW_190D9,
F_117A,
F_14B,
F_15C,
F_15E,
@@ -71,6 +74,9 @@ from dcs.planes import (
Su_34,
Tornado_GR4,
Tornado_IDS,
Tu_160,
Tu_22M3,
Tu_95MS,
WingLoong_I,
)
@@ -226,6 +232,8 @@ CAS_CAPABLE = [
F_16C_50,
FA_18C_hornet,
B_1B,
Tornado_IDS,
Tornado_GR4,
@@ -367,6 +375,10 @@ STRIKE_CAPABLE = [
Su_25T,
Su_34,
Tu_160,
Tu_22M3,
Tu_95MS,
JF_17,
M_2000C,
@@ -384,6 +396,10 @@ STRIKE_CAPABLE = [
F_16C_50,
FA_18C_hornet,
B_1B,
B_52H,
F_117A,
Tornado_IDS,
Tornado_GR4,
@@ -413,11 +429,16 @@ STRIKE_CAPABLE = [
STRIKE_PREFERRED = [
AJS37,
F_15E,
Tornado_GR4,
A_20G,
B_17G,
B_1B,
B_52H,
F_117A,
F_15E,
Tornado_GR4,
Tu_160,
Tu_22M3,
Tu_95MS,
]
ANTISHIP_CAPABLE = [

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
from enum import Enum
from typing import Dict, Iterable, List, Optional
from typing import Dict, Iterable, List, Optional, TYPE_CHECKING
from dcs.mapping import Point
from dcs.point import MovingPoint, PointAction
@@ -8,6 +10,9 @@ from dcs.unittype import UnitType
from game import db
from theater.controlpoint import ControlPoint, MissionTarget
if TYPE_CHECKING:
from gen.ato import Package
class FlightType(Enum):
CAP = 0 # Do not use. Use BARCAP or TARCAP.
@@ -138,10 +143,11 @@ class Flight:
use_custom_loadout = False
preset_loadout_name = ""
group = False # Contains DCS Mission group data after mission has been generated
targetPoint = None # Contains either None or a Strike/SEAD target point location
def __init__(self, unit_type: UnitType, count: int, from_cp: ControlPoint,
flight_type: FlightType, start_type: str) -> None:
def __init__(self, package: Package, unit_type: UnitType, count: int,
from_cp: ControlPoint, flight_type: FlightType,
start_type: str) -> None:
self.package = package
self.unit_type = unit_type
self.count = count
self.from_cp = from_cp

View File

@@ -132,7 +132,7 @@ class FlightPlanBuilder:
if not isinstance(location, TheaterGroundObject):
raise InvalidObjectiveLocation(flight.flight_type, location)
builder = WaypointBuilder(flight, self.doctrine)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.ascent(flight.from_cp)
builder.hold(self._hold_point(flight))
builder.join(self.package.waypoints.join)
@@ -157,11 +157,7 @@ class FlightPlanBuilder:
if building.is_dead:
continue
builder.strike_point(
building,
f"{building.obj_name} {building.category}",
location
)
builder.strike_point(building, building.category, location)
builder.egress(self.package.waypoints.egress, location)
builder.split(self.package.waypoints.split)
@@ -222,7 +218,7 @@ class FlightPlanBuilder:
)
start = end.point_from_heading(heading - 180, diameter)
builder = WaypointBuilder(flight, self.doctrine)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.ascent(flight.from_cp)
builder.race_track(start, end, patrol_alt)
builder.rtb(flight.from_cp)
@@ -264,7 +260,7 @@ class FlightPlanBuilder:
orbit1p = orbit_center.point_from_heading(heading + 180, radius)
# Create points
builder = WaypointBuilder(flight, self.doctrine)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.ascent(flight.from_cp)
builder.hold(self._hold_point(flight))
builder.join(self.package.waypoints.join)
@@ -290,7 +286,7 @@ class FlightPlanBuilder:
if custom_targets is None:
custom_targets = []
builder = WaypointBuilder(flight, self.doctrine)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.ascent(flight.from_cp)
builder.hold(self._hold_point(flight))
builder.join(self.package.waypoints.join)
@@ -328,7 +324,7 @@ class FlightPlanBuilder:
def generate_escort(self, flight: Flight) -> None:
assert self.package.waypoints is not None
builder = WaypointBuilder(flight, self.doctrine)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.ascent(flight.from_cp)
builder.hold(self._hold_point(flight))
builder.join(self.package.waypoints.join)
@@ -351,9 +347,6 @@ class FlightPlanBuilder:
if not isinstance(location, FrontLine):
raise InvalidObjectiveLocation(flight.flight_type, location)
is_helo = getattr(flight.unit_type, "helicopter", False)
cap_alt = 500 if is_helo else 1000
ingress, heading, distance = Conflict.frontline_vector(
location.control_points[0], location.control_points[1],
self.game.theater
@@ -361,15 +354,15 @@ class FlightPlanBuilder:
center = ingress.point_from_heading(heading, distance / 2)
egress = ingress.point_from_heading(heading, distance)
builder = WaypointBuilder(flight, self.doctrine)
builder.ascent(flight.from_cp, is_helo)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.ascent(flight.from_cp)
builder.hold(self._hold_point(flight))
builder.join(self.package.waypoints.join)
builder.ingress_cas(ingress, location)
builder.cas(center, cap_alt)
builder.cas(center)
builder.egress(egress, location)
builder.split(self.package.waypoints.split)
builder.rtb(flight.from_cp, is_helo)
builder.rtb(flight.from_cp)
flight.points = builder.build()
@@ -382,7 +375,7 @@ class FlightPlanBuilder:
flight: The flight to generate the descend point for.
departure: Departure airfield or carrier.
"""
builder = WaypointBuilder(flight, self.doctrine)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.ascent(departure)
return builder.build()[0]
@@ -394,7 +387,7 @@ class FlightPlanBuilder:
flight: The flight to generate the descend point for.
arrival: Arrival airfield or carrier.
"""
builder = WaypointBuilder(flight, self.doctrine)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.descent(arrival)
return builder.build()[0]
@@ -406,7 +399,7 @@ class FlightPlanBuilder:
flight: The flight to generate the landing waypoint for.
arrival: Arrival airfield or carrier.
"""
builder = WaypointBuilder(flight, self.doctrine)
builder = WaypointBuilder(self.game.conditions, flight, self.doctrine)
builder.land(arrival)
return builder.build()[0]

View File

@@ -27,21 +27,22 @@ INGRESS_TYPES = {
FlightWaypointType.INGRESS_STRIKE,
}
IP_TYPES = {
FlightWaypointType.INGRESS_CAS,
FlightWaypointType.INGRESS_ESCORT,
FlightWaypointType.INGRESS_SEAD,
FlightWaypointType.INGRESS_STRIKE,
FlightWaypointType.PATROL_TRACK,
}
class GroundSpeed:
@staticmethod
def mission_speed(package: Package) -> int:
speeds = set()
for flight in package.flights:
waypoint = flight.waypoint_with_type(IP_TYPES)
# Find a waypoint that matches the mission start waypoint and use
# that for the altitude of the mission. That may not be true for the
# whole mission, but it's probably good enough for now.
waypoint = flight.waypoint_with_type({
FlightWaypointType.INGRESS_CAS,
FlightWaypointType.INGRESS_ESCORT,
FlightWaypointType.INGRESS_SEAD,
FlightWaypointType.INGRESS_STRIKE,
FlightWaypointType.PATROL_TRACK,
})
if waypoint is None:
logging.error(f"Could not find ingress point for {flight}.")
if flight.points:
@@ -152,8 +153,10 @@ class TotEstimator:
# Takeoff immediately.
return 0
if self.package.primary_task == FlightType.BARCAP:
start_time = self.timing.race_track_start
# BARCAP flights do not coordinate with the rest of the package on join
# or ingress points.
if flight.flight_type == FlightType.BARCAP:
start_time = self.timing.race_track_start(flight)
else:
start_time = self.timing.join
return start_time - travel_time - self.HOLD_TIME
@@ -166,7 +169,9 @@ class TotEstimator:
def earliest_tot_for_flight(self, flight: Flight) -> int:
"""Estimate fastest time from mission start to the target position.
For CAP missions, this is time to race track start.
For BARCAP flights, this is time to race track start. This ensures that
they are on station at the same time any other package members reach
their ingress point.
For other mission types this is the time to the mission target.
@@ -177,27 +182,34 @@ class TotEstimator:
The earliest possible TOT for the given flight in seconds. Returns 0
if an ingress point cannot be found.
"""
time_to_ingress = self.estimate_waypoints_to_target(flight, IP_TYPES)
if time_to_ingress is None:
logging.warning(
f"Found no ingress types. Cannot estimate TOT for {flight}")
# Return 0 so this flight's travel time does not affect the rest of
# the package.
return 0
if self.package.primary_task == FlightType.BARCAP:
# The racetrack start *is* the target. The package target is the
# protected objective.
time_to_target = 0
if flight.flight_type == FlightType.BARCAP:
time_to_target = self.estimate_waypoints_to_target(flight, {
FlightWaypointType.PATROL_TRACK
})
if time_to_target is None:
logging.warning(
f"Found no race track. Cannot estimate TOT for {flight}")
# Return 0 so this flight's travel time does not affect the rest
# of the package.
return 0
else:
time_to_ingress = self.estimate_waypoints_to_target(
flight, INGRESS_TYPES
)
if time_to_ingress is None:
logging.warning(
f"Found no ingress types. Cannot estimate TOT for {flight}")
# Return 0 so this flight's travel time does not affect the rest
# of the package.
return 0
assert self.package.waypoints is not None
time_to_target = TravelTime.between_points(
time_to_target = time_to_ingress + TravelTime.between_points(
self.package.waypoints.ingress, self.package.target.position,
GroundSpeed.mission_speed(self.package))
return sum([
self.estimate_startup(flight),
self.estimate_ground_ops(flight),
time_to_ingress,
time_to_target,
])
@@ -281,18 +293,22 @@ class PackageWaypointTiming:
assert self.package.time_over_target is not None
return self.package.time_over_target
@property
def race_track_start(self) -> int:
if self.package.primary_task == FlightType.BARCAP:
return self.package.time_over_target
def race_track_start(self, flight: Flight) -> int:
if flight.flight_type == FlightType.BARCAP:
return self.target
else:
# The only other type that (currently) uses race tracks is TARCAP,
# which is sort of in need of cleanup. TARCAP is only valid on front
# lines and they participate in join points and patrol between the
# ingress and egress points rather than on a race track actually
# pointed at the enemy.
return self.ingress
@property
def race_track_end(self) -> int:
if self.package.primary_task == FlightType.BARCAP:
def race_track_end(self, flight: Flight) -> int:
if flight.flight_type == FlightType.BARCAP:
return self.target + CAP_DURATION * 60
else:
# For TARCAP. See the explanation in race_track_start.
return self.egress
def push_time(self, flight: Flight, hold_point: FlightWaypoint) -> int:
@@ -303,7 +319,8 @@ class PackageWaypointTiming:
GroundSpeed.for_flight(flight, hold_point.alt)
)
def tot_for_waypoint(self, waypoint: FlightWaypoint) -> Optional[int]:
def tot_for_waypoint(self, flight: Flight,
waypoint: FlightWaypoint) -> Optional[int]:
target_types = (
FlightWaypointType.TARGET_GROUP_LOC,
FlightWaypointType.TARGET_POINT,
@@ -321,7 +338,7 @@ class PackageWaypointTiming:
elif waypoint.waypoint_type == FlightWaypointType.SPLIT:
return self.split
elif waypoint.waypoint_type == FlightWaypointType.PATROL_TRACK:
return self.race_track_start
return self.race_track_start(flight)
return None
def depart_time_for_waypoint(self, waypoint: FlightWaypoint,
@@ -329,7 +346,7 @@ class PackageWaypointTiming:
if waypoint.waypoint_type == FlightWaypointType.LOITER:
return self.push_time(flight, waypoint)
elif waypoint.waypoint_type == FlightWaypointType.PATROL:
return self.race_track_end
return self.race_track_end(flight)
return None
@classmethod

View File

@@ -7,29 +7,35 @@ 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
class WaypointBuilder:
def __init__(self, flight: Flight, doctrine: Doctrine) -> None:
def __init__(self, conditions: Conditions, flight: Flight,
doctrine: Doctrine) -> None:
self.conditions = conditions
self.flight = flight
self.doctrine = doctrine
self.waypoints: List[FlightWaypoint] = []
self.ingress_point: Optional[FlightWaypoint] = None
@property
def is_helo(self) -> bool:
return getattr(self.flight.unit_type, "helicopter", False)
def build(self) -> List[FlightWaypoint]:
return self.waypoints
def ascent(self, departure: ControlPoint, is_helo: bool = False) -> None:
def ascent(self, departure: ControlPoint) -> None:
"""Create ascent waypoint for the given departure airfield or carrier.
Args:
departure: Departure airfield or carrier.
is_helo: True if the flight is a helicopter.
"""
# TODO: Pick runway based on wind direction.
heading = departure.heading
heading = RunwayAssigner(self.conditions).takeoff_heading(departure)
position = departure.position.point_from_heading(
heading, nm_to_meter(5)
)
@@ -37,7 +43,7 @@ class WaypointBuilder:
FlightWaypointType.ASCEND_POINT,
position.x,
position.y,
500 if is_helo else self.doctrine.pattern_altitude
500 if self.is_helo else self.doctrine.pattern_altitude
)
waypoint.name = "ASCEND"
waypoint.alt_type = "RADIO"
@@ -45,16 +51,15 @@ class WaypointBuilder:
waypoint.pretty_name = "Ascend"
self.waypoints.append(waypoint)
def descent(self, arrival: ControlPoint, is_helo: bool = False) -> None:
def descent(self, arrival: ControlPoint) -> None:
"""Create descent waypoint for the given arrival airfield or carrier.
Args:
arrival: Arrival airfield or carrier.
is_helo: True if the flight is a helicopter.
"""
# TODO: Pick runway based on wind direction.
# ControlPoint.heading is the departure heading.
heading = (arrival.heading + 180) % 360
landing_heading = RunwayAssigner(self.conditions).landing_heading(
arrival)
heading = (landing_heading + 180) % 360
position = arrival.position.point_from_heading(
heading, nm_to_meter(5)
)
@@ -62,7 +67,7 @@ class WaypointBuilder:
FlightWaypointType.DESCENT_POINT,
position.x,
position.y,
300 if is_helo else self.doctrine.pattern_altitude
300 if self.is_helo else self.doctrine.pattern_altitude
)
waypoint.name = "DESCEND"
waypoint.alt_type = "RADIO"
@@ -94,7 +99,7 @@ class WaypointBuilder:
FlightWaypointType.LOITER,
position.x,
position.y,
self.doctrine.rendezvous_altitude
500 if self.is_helo else self.doctrine.rendezvous_altitude
)
waypoint.pretty_name = "Hold"
waypoint.description = "Wait until push time"
@@ -106,7 +111,7 @@ class WaypointBuilder:
FlightWaypointType.JOIN,
position.x,
position.y,
self.doctrine.ingress_altitude
500 if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.pretty_name = "Join"
waypoint.description = "Rendezvous with package"
@@ -118,7 +123,7 @@ class WaypointBuilder:
FlightWaypointType.SPLIT,
position.x,
position.y,
self.doctrine.ingress_altitude
500 if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.pretty_name = "Split"
waypoint.description = "Depart from package"
@@ -146,7 +151,7 @@ class WaypointBuilder:
ingress_type,
position.x,
position.y,
self.doctrine.ingress_altitude
500 if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.pretty_name = "INGRESS on " + objective.name
waypoint.description = "INGRESS on " + objective.name
@@ -159,7 +164,7 @@ class WaypointBuilder:
FlightWaypointType.EGRESS,
position.x,
position.y,
self.doctrine.ingress_altitude
500 if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.pretty_name = "EGRESS from " + target.name
waypoint.description = "EGRESS from " + target.name
@@ -168,24 +173,21 @@ class WaypointBuilder:
def dead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
location: MissionTarget) -> None:
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
location)
self._target_point(target, name, f"STRIKE {name}", location)
# TODO: Seems fishy.
if self.ingress_point is not None:
self.ingress_point.targetGroup = location
def sead_point(self, target: Union[TheaterGroundObject, Unit], name: str,
location: MissionTarget) -> None:
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
location)
self._target_point(target, name, f"STRIKE {name}", location)
# TODO: Seems fishy.
if self.ingress_point is not None:
self.ingress_point.targetGroup = location
def strike_point(self, target: Union[TheaterGroundObject, Unit], name: str,
location: MissionTarget) -> None:
self._target_point(target, name, f"STRIKE [{location.name}]: {name}",
location)
self._target_point(target, name, f"STRIKE {name}", location)
def _target_point(self, target: Union[TheaterGroundObject, Unit], name: str,
description: str, location: MissionTarget) -> None:
@@ -246,12 +248,12 @@ class WaypointBuilder:
# TODO: This seems wrong, but it's what was there before.
self.ingress_point.targets.append(location)
def cas(self, position: Point, altitude: int) -> None:
def cas(self, position: Point) -> None:
waypoint = FlightWaypoint(
FlightWaypointType.CAS,
position.x,
position.y,
altitude
500 if self.is_helo else 1000
)
waypoint.alt_type = "RADIO"
waypoint.description = "Provide CAS"
@@ -306,14 +308,13 @@ class WaypointBuilder:
self.race_track_start(start, altitude)
self.race_track_end(end, altitude)
def rtb(self, arrival: ControlPoint, is_helo: bool = False) -> None:
def rtb(self, arrival: ControlPoint) -> None:
"""Creates descent ant landing waypoints for the given control point.
Args:
arrival: Arrival airfield or carrier.
is_helo: True if the flight is a helicopter.
"""
self.descent(arrival, is_helo)
self.descent(arrival)
self.land(arrival)
def escort(self, ingress: Point, target: MissionTarget,
@@ -337,7 +338,7 @@ class WaypointBuilder:
FlightWaypointType.TARGET_GROUP_LOC,
target.position.x,
target.position.y,
self.doctrine.ingress_altitude
500 if self.is_helo else self.doctrine.ingress_altitude
)
waypoint.name = "TARGET"
waypoint.description = "Escort the package"