Reduce mission planning dependence on Game.

This commit is contained in:
Dan Albert 2021-07-13 15:20:42 -07:00
parent 17c19d453b
commit 24f6aff8c8
13 changed files with 212 additions and 190 deletions

View File

@ -11,7 +11,7 @@ from game.navmesh import NavMesh
from game.profiling import logged_duration, MultiEventTracer
from game.threatzones import ThreatZones
from game.transfers import PendingTransfers
from gen.flights.ai_flight_planner import CoalitionMissionPlanner
from gen.flights.ai_flight_planner import CoalitionMissionPlanner, MissionScheduler
if TYPE_CHECKING:
from game import Game
@ -181,13 +181,20 @@ class Coalition:
def plan_missions(self) -> None:
color = "Blue" if self.player else "Red"
with MultiEventTracer() as tracer:
mission_planner = CoalitionMissionPlanner(self.game, self.player)
mission_planner = CoalitionMissionPlanner(
self,
self.game.theater,
self.game.aircraft_inventory,
self.game.settings,
)
with tracer.trace(f"{color} mission planning"):
with tracer.trace(f"{color} mission identification"):
commander = TheaterCommander(self.game, self.player)
commander.plan_missions(mission_planner, tracer)
with tracer.trace(f"{color} mission fulfillment"):
mission_planner.fulfill_missions()
with tracer.trace(f"{color} mission scheduling"):
MissionScheduler(
self, self.game.settings.desired_player_mission_duration
).schedule_missions()
def plan_procurement(self) -> None:
# The first turn needs to buy a *lot* of aircraft to fill CAPs, so it gets much

View File

@ -18,12 +18,12 @@ from game.theater.theatergroundobject import (
IadsGroundObject,
NavalGroundObject,
)
from game.transfers import CargoShip, Convoy
from game.utils import meters, nautical_miles
from gen.flights.closestairfields import ObjectiveDistanceCache, ClosestAirfields
if TYPE_CHECKING:
from game import Game
from game.transfers import CargoShip, Convoy
MissionTargetType = TypeVar("MissionTargetType", bound=MissionTarget)

View File

@ -8,4 +8,4 @@ from game.htn import CompoundTask, Method
class ProtectAirSpace(CompoundTask[TheaterState]):
def each_valid_method(self, state: TheaterState) -> Iterator[Method[TheaterState]]:
for cp in state.vulnerable_control_points:
yield [PlanBarcap(cp)]
yield [PlanBarcap(cp, state.barcap_rounds)]

View File

@ -17,6 +17,7 @@ if TYPE_CHECKING:
@dataclass
class PlanBarcap(TheaterCommanderTask):
target: ControlPoint
rounds: int
def preconditions_met(self, state: TheaterState) -> bool:
if state.player and not state.ato_automation_enabled:
@ -29,19 +30,7 @@ class PlanBarcap(TheaterCommanderTask):
def execute(
self, mission_planner: CoalitionMissionPlanner, tracer: MultiEventTracer
) -> None:
# Plan enough rounds of CAP that the target has coverage over the expected
# mission duration.
mission_duration = int(
mission_planner.game.settings.desired_player_mission_duration.total_seconds()
)
barcap_duration = int(
mission_planner.faction.doctrine.cap_duration.total_seconds()
)
for _ in range(
0,
mission_duration,
barcap_duration,
):
for _ in range(self.rounds):
mission_planner.plan_mission(
ProposedMission(
self.target,

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import dataclasses
import itertools
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Union, Optional
@ -18,11 +19,11 @@ from game.theater.theatergroundobject import (
VehicleGroupGroundObject,
)
from game.threatzones import ThreatZones
from game.transfers import Convoy, CargoShip
from gen.ground_forces.combat_stance import CombatStance
if TYPE_CHECKING:
from game import Game
from game.transfers import Convoy, CargoShip
@dataclass
@ -30,6 +31,7 @@ class TheaterState(WorldState["TheaterState"]):
player: bool
stance_automation_enabled: bool
ato_automation_enabled: bool
barcap_rounds: int
vulnerable_control_points: list[ControlPoint]
active_front_lines: list[FrontLine]
front_line_stances: dict[FrontLine, Optional[CombatStance]]
@ -86,6 +88,7 @@ class TheaterState(WorldState["TheaterState"]):
player=self.player,
stance_automation_enabled=self.stance_automation_enabled,
ato_automation_enabled=self.ato_automation_enabled,
barcap_rounds=self.barcap_rounds,
vulnerable_control_points=list(self.vulnerable_control_points),
active_front_lines=list(self.active_front_lines),
front_line_stances=dict(self.front_line_stances),
@ -120,10 +123,20 @@ class TheaterState(WorldState["TheaterState"]):
auto_stance = game.settings.automate_front_line_stance
auto_ato = game.settings.auto_ato_behavior is not AutoAtoBehavior.Disabled
ordered_capturable_points = finder.prioritized_unisolated_points()
# Plan enough rounds of CAP that the target has coverage over the expected
# mission duration.
mission_duration = game.settings.desired_player_mission_duration.total_seconds()
barcap_duration = game.coalition_for(
player
).doctrine.cap_duration.total_seconds()
barcap_rounds = math.ceil(mission_duration / barcap_duration)
return TheaterState(
player=player,
stance_automation_enabled=auto_stance,
ato_automation_enabled=auto_ato,
barcap_rounds=barcap_rounds,
vulnerable_control_points=list(finder.vulnerable_control_points()),
active_front_lines=list(finder.front_lines()),
front_line_stances={f: None for f in finder.front_lines()},

View File

@ -4,7 +4,7 @@ import itertools
import logging
from abc import ABC
from collections import Sequence
from typing import Iterator, List, TYPE_CHECKING, Union, Generic, TypeVar, Any
from typing import Iterator, List, TYPE_CHECKING, Union, Generic, TypeVar
from dcs.mapping import Point
from dcs.triggers import TriggerZone
@ -257,13 +257,17 @@ class BuildingGroundObject(TheaterGroundObject[VehicleGroup]):
def kill(self) -> None:
self._dead = True
def iter_building_group(self) -> Iterator[TheaterGroundObject[Any]]:
def iter_building_group(self) -> Iterator[BuildingGroundObject]:
for tgo in self.control_point.ground_objects:
if tgo.obj_name == self.obj_name and not tgo.is_dead:
if (
tgo.obj_name == self.obj_name
and not tgo.is_dead
and isinstance(tgo, BuildingGroundObject)
):
yield tgo
@property
def strike_targets(self) -> List[Union[MissionTarget, Unit]]:
def strike_targets(self) -> List[BuildingGroundObject]:
return list(self.iter_building_group())
@property

View File

@ -340,7 +340,9 @@ class AirliftPlanner:
transfer.transport = transport
self.package.add_flight(flight)
planner = FlightPlanBuilder(self.game, self.package, self.for_player)
planner = FlightPlanBuilder(
self.package, self.game.coalition_for(self.for_player), self.game.theater
)
planner.populate_flight_plan(flight)
self.game.aircraft_inventory.claim_for_flight(flight)
return flight_size

View File

@ -14,26 +14,26 @@ from typing import (
Tuple,
)
from game.commander import TheaterCommander
from game.commander.missionproposals import ProposedFlight, ProposedMission, EscortType
from game.commander.objectivefinder import ObjectiveFinder
from game.data.doctrine import Doctrine
from game.dcs.aircrafttype import AircraftType
from game.factions.faction import Faction
from game.infos.information import Information
from game.procurement import AircraftProcurementRequest
from game.profiling import logged_duration, MultiEventTracer
from game.profiling import MultiEventTracer
from game.settings import Settings
from game.squadrons import AirWing, Squadron
from game.theater import (
ControlPoint,
MissionTarget,
OffMapSpawn,
ConflictTheater,
)
from game.threatzones import ThreatZones
from game.utils import nautical_miles
from gen.ato import Package
from gen.ato import Package, AirTaskingOrder
from gen.flights.ai_flight_planner_db import aircraft_for_task
from gen.flights.closestairfields import (
ClosestAirfields,
ObjectiveDistanceCache,
)
from gen.flights.flight import (
Flight,
@ -44,7 +44,7 @@ from gen.flights.traveltime import TotEstimator
# Avoid importing some types that cause circular imports unless type checking.
if TYPE_CHECKING:
from game import Game
from game.coalition import Coalition
from game.inventory import GlobalAircraftInventory
@ -201,6 +201,68 @@ class PackageBuilder:
self.package.remove_flight(flight)
class MissionScheduler:
def __init__(self, coalition: Coalition, desired_mission_length: timedelta) -> None:
self.coalition = coalition
self.desired_mission_length = desired_mission_length
def schedule_missions(self) -> None:
"""Identifies and plans mission for the turn."""
def start_time_generator(
count: int, earliest: int, latest: int, margin: int
) -> Iterator[timedelta]:
interval = (latest - earliest) // count
for time in range(earliest, latest, interval):
error = random.randint(-margin, margin)
yield timedelta(seconds=max(0, time + error))
dca_types = {
FlightType.BARCAP,
FlightType.TARCAP,
}
previous_cap_end_time: Dict[MissionTarget, timedelta] = defaultdict(timedelta)
non_dca_packages = [
p for p in self.coalition.ato.packages if p.primary_task not in dca_types
]
start_time = start_time_generator(
count=len(non_dca_packages),
earliest=5 * 60,
latest=int(self.desired_mission_length.total_seconds()),
margin=5 * 60,
)
for package in self.coalition.ato.packages:
tot = TotEstimator(package).earliest_tot()
if package.primary_task in dca_types:
previous_end_time = previous_cap_end_time[package.target]
if tot > previous_end_time:
# Can't get there exactly on time, so get there ASAP. This
# will typically only happen for the first CAP at each
# target.
package.time_over_target = tot
else:
package.time_over_target = previous_end_time
departure_time = package.mission_departure_time
# Should be impossible for CAPs
if departure_time is None:
logging.error(f"Could not determine mission end time for {package}")
continue
previous_cap_end_time[package.target] = departure_time
elif package.auto_asap:
package.set_tot_asap()
else:
# But other packages should be spread out a bit. Note that take
# times are delayed, but all aircraft will become active at
# mission start. This makes it more worthwhile to attack enemy
# airfields to hit grounded aircraft, since they're more likely
# to be present. Runway and air started aircraft will be
# delayed until their takeoff time by AirConflictGenerator.
package.time_over_target = next(start_time) + tot
class CoalitionMissionPlanner:
"""Coalition flight planning AI.
@ -224,18 +286,46 @@ class CoalitionMissionPlanner:
TODO: Stance and doctrine-specific planning behavior.
"""
def __init__(self, game: Game, is_player: bool) -> None:
self.game = game
self.is_player = is_player
self.objective_finder = ObjectiveFinder(self.game, self.is_player)
self.ato = self.game.coalition_for(is_player).ato
self.threat_zones = self.game.threat_zone_for(not self.is_player)
self.procurement_requests = self.game.procurement_requests_for(self.is_player)
self.faction: Faction = self.game.faction_for(self.is_player)
def __init__(
self,
coalition: Coalition,
theater: ConflictTheater,
aircraft_inventory: GlobalAircraftInventory,
settings: Settings,
) -> None:
self.coalition = coalition
self.theater = theater
self.aircraft_inventory = aircraft_inventory
self.player_missions_asap = settings.auto_ato_player_missions_asap
self.default_start_type = settings.default_start_type
@property
def is_player(self) -> bool:
return self.coalition.player
@property
def ato(self) -> AirTaskingOrder:
return self.coalition.ato
@property
def air_wing(self) -> AirWing:
return self.coalition.air_wing
@property
def doctrine(self) -> Doctrine:
return self.faction.doctrine
return self.coalition.doctrine
@property
def threat_zones(self) -> ThreatZones:
return self.coalition.opponent.threat_zone
def add_procurement_request(
self, request: AircraftProcurementRequest, priority: bool
) -> None:
if priority:
self.coalition.procurement_requests.insert(0, request)
else:
self.coalition.procurement_requests.append(request)
def air_wing_can_plan(self, mission_type: FlightType) -> bool:
"""Returns True if it is possible for the air wing to plan this mission type.
@ -245,21 +335,7 @@ class CoalitionMissionPlanner:
also possible for the player to exclude mission types from their squadron
designs.
"""
return self.game.air_wing_for(self.is_player).can_auto_plan(mission_type)
def fulfill_missions(self) -> None:
"""Identifies and plans mission for the turn."""
player = "Blue" if self.is_player else "Red"
with logged_duration(f"{player} mission scheduling"):
self.stagger_missions()
for cp in self.objective_finder.friendly_control_points():
inventory = self.game.aircraft_inventory.for_control_point(cp)
for aircraft, available in inventory.all_aircraft:
self.message("Unused aircraft", f"{available} {aircraft} from {cp}")
coalition_text = "player" if self.is_player else "opfor"
logging.debug(f"Planned {len(self.ato.packages)} {coalition_text} missions")
return self.air_wing.can_auto_plan(mission_type)
def plan_flight(
self,
@ -277,12 +353,9 @@ class CoalitionMissionPlanner:
task_capability=flight.task,
number=flight.num_aircraft,
)
if for_reserves:
# Reserves are planned for critical missions, so prioritize
# those orders over aircraft needed for non-critical missions.
self.procurement_requests.insert(0, purchase_order)
else:
self.procurement_requests.append(purchase_order)
# Reserves are planned for critical missions, so prioritize those orders
# over aircraft needed for non-critical missions.
self.add_procurement_request(purchase_order, priority=for_reserves)
def scrub_mission_missing_aircraft(
self,
@ -300,10 +373,9 @@ class CoalitionMissionPlanner:
missing_types_str = ", ".join(sorted([t.name for t in missing_types]))
builder.release_planned_aircraft()
desc = "reserve aircraft" if reserves else "aircraft"
self.message(
"Insufficient aircraft",
logging.debug(
f"Not enough {desc} in range for {mission.location.name} "
f"capable of: {missing_types_str}",
f"capable of: {missing_types_str}"
)
def check_needed_escorts(self, builder: PackageBuilder) -> Dict[EscortType, bool]:
@ -325,12 +397,12 @@ class CoalitionMissionPlanner:
"""Allocates aircraft for a proposed mission and adds it to the ATO."""
builder = PackageBuilder(
mission.location,
self.objective_finder.closest_airfields_to(mission.location),
self.game.aircraft_inventory,
self.game.air_wing_for(self.is_player),
ObjectiveDistanceCache.get_closest_airfields(mission.location),
self.aircraft_inventory,
self.air_wing,
self.is_player,
self.game.country_for(self.is_player),
self.game.settings.default_start_type,
self.coalition.country_name,
self.default_start_type,
mission.asap,
)
@ -374,7 +446,7 @@ class CoalitionMissionPlanner:
# the other flights in the package. Escorts will not be able to
# contribute to this.
flight_plan_builder = FlightPlanBuilder(
self.game, builder.package, self.is_player
builder.package, self.coalition, self.theater
)
for flight in builder.package.flights:
with tracer.trace("Flight plan population"):
@ -410,75 +482,8 @@ class CoalitionMissionPlanner:
with tracer.trace("Flight plan population"):
flight_plan_builder.populate_flight_plan(flight)
if package.has_players and self.game.settings.auto_ato_player_missions_asap:
if package.has_players and self.player_missions_asap:
package.auto_asap = True
package.set_tot_asap()
self.ato.add_package(package)
def stagger_missions(self) -> None:
def start_time_generator(
count: int, earliest: int, latest: int, margin: int
) -> Iterator[timedelta]:
interval = (latest - earliest) // count
for time in range(earliest, latest, interval):
error = random.randint(-margin, margin)
yield timedelta(seconds=max(0, time + error))
dca_types = {
FlightType.BARCAP,
FlightType.TARCAP,
}
previous_cap_end_time: Dict[MissionTarget, timedelta] = defaultdict(timedelta)
non_dca_packages = [
p for p in self.ato.packages if p.primary_task not in dca_types
]
start_time = start_time_generator(
count=len(non_dca_packages),
earliest=5 * 60,
latest=int(
self.game.settings.desired_player_mission_duration.total_seconds()
),
margin=5 * 60,
)
for package in self.ato.packages:
tot = TotEstimator(package).earliest_tot()
if package.primary_task in dca_types:
previous_end_time = previous_cap_end_time[package.target]
if tot > previous_end_time:
# Can't get there exactly on time, so get there ASAP. This
# will typically only happen for the first CAP at each
# target.
package.time_over_target = tot
else:
package.time_over_target = previous_end_time
departure_time = package.mission_departure_time
# Should be impossible for CAPs
if departure_time is None:
logging.error(f"Could not determine mission end time for {package}")
continue
previous_cap_end_time[package.target] = departure_time
elif package.auto_asap:
package.set_tot_asap()
else:
# But other packages should be spread out a bit. Note that take
# times are delayed, but all aircraft will become active at
# mission start. This makes it more worthwhile to attack enemy
# airfields to hit grounded aircraft, since they're more likely
# to be present. Runway and air started aircraft will be
# delayed until their takeoff time by AirConflictGenerator.
package.time_over_target = next(start_time) + tot
def message(self, title: str, text: str) -> None:
"""Emits a planning message to the player.
If the mission planner belongs to the players coalition, this emits a
message to the info panel.
"""
if self.is_player:
self.game.informations.append(Information(title, text, self.game.turn))
else:
logging.info(f"{title}: {text}")

View File

@ -28,8 +28,14 @@ from game.theater import (
SamGroundObject,
TheaterGroundObject,
NavalControlPoint,
ConflictTheater,
)
from game.theater.theatergroundobject import EwrGroundObject, NavalGroundObject
from game.theater.theatergroundobject import (
EwrGroundObject,
NavalGroundObject,
BuildingGroundObject,
)
from game.threatzones import ThreatZones
from game.utils import Distance, Speed, feet, meters, nautical_miles, knots
from .closestairfields import ObjectiveDistanceCache
from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType
@ -38,8 +44,8 @@ from .waypointbuilder import StrikeTarget, WaypointBuilder
from ..conflictgen import Conflict, FRONTLINE_LENGTH
if TYPE_CHECKING:
from game import Game
from gen.ato import Package
from game.coalition import Coalition
from game.transfers import Convoy
INGRESS_TYPES = {
@ -864,7 +870,9 @@ class CustomFlightPlan(FlightPlan):
class FlightPlanBuilder:
"""Generates flight plans for flights."""
def __init__(self, game: Game, package: Package, is_player: bool) -> None:
def __init__(
self, package: Package, coalition: Coalition, theater: ConflictTheater
) -> None:
# TODO: Plan similar altitudes for the in-country leg of the mission.
# Waypoint altitudes for a given flight *shouldn't* differ too much
# between the join and split points, so we don't need speeds for each
@ -872,11 +880,21 @@ class FlightPlanBuilder:
# hold too well right now since nothing is stopping each waypoint from
# jumping 20k feet each time, but that's a huge waste of energy we
# should be avoiding anyway.
self.game = game
self.package = package
self.is_player = is_player
self.doctrine: Doctrine = self.game.faction_for(self.is_player).doctrine
self.threat_zones = self.game.threat_zone_for(not self.is_player)
self.coalition = coalition
self.theater = theater
@property
def is_player(self) -> bool:
return self.coalition.player
@property
def doctrine(self) -> Doctrine:
return self.coalition.doctrine
@property
def threat_zones(self) -> ThreatZones:
return self.coalition.opponent.threat_zone
def populate_flight_plan(
self,
@ -1022,7 +1040,7 @@ class FlightPlanBuilder:
)
def preferred_join_point(self) -> Optional[Point]:
path = self.game.navmesh_for(self.is_player).shortest_path(
path = self.coalition.nav_mesh.shortest_path(
self.package_airfield().position, self.package.target.position
)
for point in reversed(path):
@ -1043,26 +1061,16 @@ class FlightPlanBuilder:
raise InvalidObjectiveLocation(flight.flight_type, location)
targets: List[StrikeTarget] = []
if len(location.groups) > 0 and location.dcs_identifier == "AA":
if isinstance(location, BuildingGroundObject):
# A building "group" is implemented as multiple TGOs with the same name.
for building in location.strike_targets:
targets.append(StrikeTarget(building.category, building))
else:
# TODO: Replace with DEAD?
# Strike missions on SEAD targets target units.
for g in location.groups:
for j, u in enumerate(g.units):
targets.append(StrikeTarget(f"{u.type} #{j}", u))
else:
# TODO: Does this actually happen?
# ConflictTheater is built with the belief that multiple ground
# objects have the same name. If that's the case,
# TheaterGroundObject needs some refactoring because it behaves very
# differently for SAM sites than it does for strike targets.
buildings = self.game.theater.find_ground_objects_by_obj_name(
location.obj_name
)
for building in buildings:
if building.is_dead:
continue
targets.append(StrikeTarget(building.category, building))
return self.strike_flightplan(
flight, location, FlightWaypointType.INGRESS_STRIKE, targets
@ -1083,7 +1091,7 @@ class FlightPlanBuilder:
else:
patrol_alt = feet(25000)
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
orbit = builder.orbit(orbit_location, patrol_alt)
return AwacsFlightPlan(
@ -1175,7 +1183,7 @@ class FlightPlanBuilder:
)
)
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
start, end = builder.race_track(start_pos, end_pos, patrol_alt)
return BarCapFlightPlan(
@ -1211,7 +1219,7 @@ class FlightPlanBuilder:
heading, -self.doctrine.sweep_distance.meters
)
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
start, end = builder.sweep(start_pos, target, self.doctrine.ingress_altitude)
hold = builder.hold(self._hold_point(flight))
@ -1251,7 +1259,7 @@ class FlightPlanBuilder:
altitude = feet(1500)
altitude_is_agl = True
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
pickup = None
nav_to_pickup = []
@ -1373,9 +1381,7 @@ class FlightPlanBuilder:
self, origin: Point, front_line: FrontLine
) -> Tuple[Point, Point]:
# Find targets waypoints
ingress, heading, distance = Conflict.frontline_vector(
front_line, self.game.theater
)
ingress, heading, distance = Conflict.frontline_vector(front_line, self.theater)
center = ingress.point_from_heading(heading, distance / 2)
orbit_center = center.point_from_heading(
heading - 90,
@ -1414,7 +1420,7 @@ class FlightPlanBuilder:
)
# Create points
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
if isinstance(location, FrontLine):
orbit0p, orbit1p = self.racetrack_for_frontline(
@ -1545,7 +1551,7 @@ class FlightPlanBuilder:
def generate_escort(self, flight: Flight) -> StrikeFlightPlan:
assert self.package.waypoints is not None
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
ingress, target, egress = builder.escort(
self.package.waypoints.ingress,
self.package.target,
@ -1588,9 +1594,7 @@ class FlightPlanBuilder:
if not isinstance(location, FrontLine):
raise InvalidObjectiveLocation(flight.flight_type, location)
ingress, heading, distance = Conflict.frontline_vector(
location, self.game.theater
)
ingress, heading, distance = Conflict.frontline_vector(location, self.theater)
center = ingress.point_from_heading(heading, distance / 2)
egress = ingress.point_from_heading(heading, distance)
@ -1599,7 +1603,7 @@ class FlightPlanBuilder:
if egress_distance < ingress_distance:
ingress, egress = egress, ingress
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
return CasFlightPlan(
package=self.package,
@ -1655,7 +1659,7 @@ class FlightPlanBuilder:
orbit_heading - 90, racetrack_half_distance
)
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
tanker_type = flight.unit_type
if tanker_type.patrol_altitude is not None:
@ -1776,7 +1780,7 @@ class FlightPlanBuilder:
flight: The flight to generate the landing waypoint for.
arrival: Arrival airfield or carrier.
"""
builder = WaypointBuilder(flight, self.game, self.is_player)
builder = WaypointBuilder(flight, self.coalition)
return builder.land(arrival)
def strike_flightplan(
@ -1788,7 +1792,7 @@ class FlightPlanBuilder:
lead_time: timedelta = timedelta(),
) -> StrikeFlightPlan:
assert self.package.waypoints is not None
builder = WaypointBuilder(flight, self.game, self.is_player, targets)
builder = WaypointBuilder(flight, self.coalition, targets)
target_waypoints: List[FlightWaypoint] = []
if targets is not None:

View File

@ -15,10 +15,10 @@ from typing import (
from dcs.mapping import Point
from dcs.unit import Unit
from dcs.unitgroup import Group, VehicleGroup, ShipGroup
from dcs.unitgroup import VehicleGroup, ShipGroup
if TYPE_CHECKING:
from game import Game
from game.coalition import Coalition
from game.transfers import MultiGroupTransport
from game.theater import (
@ -43,17 +43,15 @@ class WaypointBuilder:
def __init__(
self,
flight: Flight,
game: Game,
player: bool,
coalition: Coalition,
targets: Optional[List[StrikeTarget]] = None,
) -> None:
self.flight = flight
self.conditions = game.conditions
self.doctrine = game.faction_for(player).doctrine
self.threat_zones = game.threat_zone_for(not player)
self.navmesh = game.navmesh_for(player)
self.doctrine = coalition.doctrine
self.threat_zones = coalition.opponent.threat_zone
self.navmesh = coalition.nav_mesh
self.targets = targets
self._bullseye = game.bullseye_for(player)
self._bullseye = coalition.bullseye
@property
def is_helo(self) -> bool:

View File

@ -180,7 +180,7 @@ class QPackageDialog(QDialog):
self.game.aircraft_inventory.claim_for_flight(flight)
self.package_model.add_flight(flight)
planner = FlightPlanBuilder(
self.game, self.package_model.package, is_player=True
self.package_model.package, self.game.blue, self.game.theater
)
try:
planner.populate_flight_plan(flight)

View File

@ -100,6 +100,6 @@ class FlightAirfieldDisplay(QGroupBox):
def update_flight_plan(self) -> None:
planner = FlightPlanBuilder(
self.game, self.package_model.package, is_player=True
self.package_model.package, self.game.blue, self.game.theater
)
planner.populate_flight_plan(self.flight)

View File

@ -37,7 +37,7 @@ class QFlightWaypointTab(QFrame):
self.game = game
self.package = package
self.flight = flight
self.planner = FlightPlanBuilder(self.game, package, is_player=True)
self.planner = FlightPlanBuilder(package, game.blue, game.theater)
self.flight_waypoint_list: Optional[QFlightWaypointList] = None
self.rtb_waypoint: Optional[QPushButton] = None