Refactor control points into individual classes.

This commit is contained in:
Dan Albert 2020-11-23 02:28:07 -08:00
parent c67263662d
commit 63bdbebcaa
5 changed files with 250 additions and 154 deletions

View File

@ -42,7 +42,13 @@ from dcs.unitgroup import (
from dcs.vehicles import AirDefence, Armor from dcs.vehicles import AirDefence, Armor
from gen.flights.flight import FlightType from gen.flights.flight import FlightType
from .controlpoint import ControlPoint, MissionTarget, OffMapSpawn from .controlpoint import (
Airfield,
Carrier,
ControlPoint,
Lha, MissionTarget,
OffMapSpawn,
)
from .landmap import Landmap, load_landmap, poly_contains from .landmap import Landmap, load_landmap, poly_contains
from ..utils import nm_to_meter from ..utils import nm_to_meter
@ -160,7 +166,7 @@ class MizCampaignLoader:
else: else:
importance = airport.periodicity / 10 importance = airport.periodicity / 10
cp = ControlPoint.from_airport(airport, radials, size, importance) cp = Airfield(airport, radials, size, importance)
cp.captured = airport.is_blue() cp.captured = airport.is_blue()
# Use the unlimited aircraft option to determine if an airfield should # Use the unlimited aircraft option to determine if an airfield should
@ -258,14 +264,14 @@ class MizCampaignLoader:
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
for group in self.carriers(blue): for group in self.carriers(blue):
# TODO: Name the carrier. # TODO: Name the carrier.
control_point = ControlPoint.carrier( control_point = Carrier(
"carrier", group.position, next(self.control_point_id)) "carrier", group.position, next(self.control_point_id))
control_point.captured = blue control_point.captured = blue
control_point.captured_invert = group.late_activation control_point.captured_invert = group.late_activation
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
for group in self.lhas(blue): for group in self.lhas(blue):
# TODO: Name the LHA. # TODO: Name the LHA.
control_point = ControlPoint.lha( control_point = Lha(
"lha", group.position, next(self.control_point_id)) "lha", group.position, next(self.control_point_id))
control_point.captured = blue control_point.captured = blue
control_point.captured_invert = group.late_activation control_point.captured_invert = group.late_activation
@ -466,7 +472,7 @@ class ConflictTheater:
return closest return closest
def add_json_cp(self, theater, p: dict) -> ControlPoint: def add_json_cp(self, theater, p: dict) -> ControlPoint:
cp: ControlPoint
if p["type"] == "airbase": if p["type"] == "airbase":
airbase = theater.terrain.airports[p["id"]] airbase = theater.terrain.airports[p["id"]]
@ -486,11 +492,11 @@ class ConflictTheater:
else: else:
importance = IMPORTANCE_MEDIUM importance = IMPORTANCE_MEDIUM
cp = ControlPoint.from_airport(airbase, radials, size, importance) cp = Airfield(airbase, radials, size, importance)
elif p["type"] == "carrier": elif p["type"] == "carrier":
cp = ControlPoint.carrier("carrier", Point(p["x"], p["y"]), p["id"]) cp = Carrier("carrier", Point(p["x"], p["y"]), p["id"])
else: else:
cp = ControlPoint.lha("lha", Point(p["x"], p["y"]), p["id"]) cp = Lha("lha", Point(p["x"], p["y"]), p["id"])
if "captured_invert" in p.keys(): if "captured_invert" in p.keys():
cp.captured_invert = p["captured_invert"] cp.captured_invert = p["captured_invert"]

View File

@ -4,9 +4,10 @@ import itertools
import logging import logging
import random import random
import re import re
from abc import ABC, abstractmethod
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
from typing import Dict, Iterator, List, Optional, TYPE_CHECKING, Tuple from typing import Dict, Iterator, List, Optional, TYPE_CHECKING
from dcs.mapping import Point from dcs.mapping import Point
from dcs.ships import ( from dcs.ships import (
@ -15,10 +16,11 @@ from dcs.ships import (
LHA_1_Tarawa, LHA_1_Tarawa,
Type_071_Amphibious_Transport_Dock, Type_071_Amphibious_Transport_Dock,
) )
from dcs.terrain.terrain import Airport from dcs.terrain.terrain import Airport, ParkingSlot
from dcs.unittype import FlyingType from dcs.unittype import FlyingType
from game import db from game import db
from gen.runways import RunwayAssigner, RunwayData
from gen.ground_forces.combat_stance import CombatStance from gen.ground_forces.combat_stance import CombatStance
from .base import Base from .base import Base
from .missiontarget import MissionTarget from .missiontarget import MissionTarget
@ -29,6 +31,7 @@ from .theatergroundobject import (
TheaterGroundObject, TheaterGroundObject,
VehicleGroupGroundObject, VehicleGroupGroundObject,
) )
from ..weather import Conditions
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -37,11 +40,16 @@ if TYPE_CHECKING:
class ControlPointType(Enum): class ControlPointType(Enum):
AIRBASE = 0 # An airbase with slots for everything #: An airbase with slots for everything.
AIRCRAFT_CARRIER_GROUP = 1 # A group with a Stennis type carrier (F/A-18, F-14 compatible) AIRBASE = 0
LHA_GROUP = 2 # A group with a Tarawa carrier (Helicopters & Harrier) #: A group with a Stennis type carrier (F/A-18, F-14 compatible).
FARP = 4 # A FARP, with slots for helicopters AIRCRAFT_CARRIER_GROUP = 1
FOB = 5 # A FOB (ground units only) #: A group with a Tarawa carrier (Helicopters & Harrier).
LHA_GROUP = 2
#: A FARP, with slots for helicopters
FARP = 4
#: A FOB (ground units only)
FOB = 5
OFF_MAP = 6 OFF_MAP = 6
@ -136,7 +144,7 @@ class PendingOccupancy:
return self.present + self.ordered + self.transferring return self.present + self.ordered + self.transferring
class ControlPoint(MissionTarget): class ControlPoint(MissionTarget, ABC):
position = None # type: Point position = None # type: Point
name = None # type: str name = None # type: str
@ -147,29 +155,36 @@ class ControlPoint(MissionTarget):
alt = 0 alt = 0
def __init__(self, id: int, name: str, position: Point, # TODO: Only airbases have IDs.
# TODO: Radials seem to be pointless.
# TODO: has_frontline is only reasonable for airbases.
# TODO: cptype is obsolete.
def __init__(self, cp_id: int, name: str, position: Point,
at: db.StartingPosition, radials: List[int], size: int, at: db.StartingPosition, radials: List[int], size: int,
importance: float, has_frontline=True, importance: float, has_frontline=True,
cptype=ControlPointType.AIRBASE): cptype=ControlPointType.AIRBASE):
super().__init__(" ".join(re.split(r" |-", name)[:2]), position) super().__init__(" ".join(re.split(r"[ \-]", name)[:2]), position)
self.id = id # TODO: Should be Airbase specific.
self.id = cp_id
self.full_name = name self.full_name = name
self.at = at self.at = at
self.connected_objectives: List[TheaterGroundObject] = [] self.connected_objectives: List[TheaterGroundObject] = []
self.base_defenses: List[BaseDefenseGroundObject] = [] self.base_defenses: List[BaseDefenseGroundObject] = []
self.preset_locations = PresetLocations() self.preset_locations = PresetLocations()
# TODO: Should be Airbase specific.
self.size = size self.size = size
self.importance = importance self.importance = importance
self.captured = False self.captured = False
self.captured_invert = False self.captured_invert = False
# TODO: Should be Airbase specific.
self.has_frontline = has_frontline self.has_frontline = has_frontline
self.radials = radials self.radials = radials
self.connected_points: List[ControlPoint] = [] self.connected_points: List[ControlPoint] = []
self.base: Base = Base() self.base: Base = Base()
self.cptype = cptype self.cptype = cptype
# TODO: Should be Airbase specific.
self.stances: Dict[int, CombatStance] = {} self.stances: Dict[int, CombatStance] = {}
self.airport = None
self.pending_unit_deliveries: Optional[UnitsDeliveryEvent] = None self.pending_unit_deliveries: Optional[UnitsDeliveryEvent] = None
def __repr__(self): def __repr__(self):
@ -180,35 +195,10 @@ class ControlPoint(MissionTarget):
return list( return list(
itertools.chain(self.connected_objectives, self.base_defenses)) itertools.chain(self.connected_objectives, self.base_defenses))
@classmethod
def from_airport(cls, airport: Airport, radials: List[int], size: int, importance: float, has_frontline=True):
assert airport
obj = cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline, cptype=ControlPointType.AIRBASE)
obj.airport = airport
return obj
@classmethod
def carrier(cls, name: str, at: Point, id: int):
import game.theater.conflicttheater
cp = cls(id, name, at, at, game.theater.conflicttheater.LAND, game.theater.conflicttheater.SIZE_SMALL, 1,
has_frontline=False, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP)
return cp
@classmethod
def lha(cls, name: str, at: Point, id: int):
import game.theater.conflicttheater
cp = cls(id, name, at, at, game.theater.conflicttheater.LAND, game.theater.conflicttheater.SIZE_SMALL, 1,
has_frontline=False, cptype=ControlPointType.LHA_GROUP)
return cp
@property @property
def heading(self): @abstractmethod
if self.cptype == ControlPointType.AIRBASE: def heading(self) -> int:
return self.airport.runways[0].heading ...
elif self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP]:
return 0 # TODO compute heading
else:
return 0
def __str__(self): def __str__(self):
return self.name return self.name
@ -222,21 +212,21 @@ class ControlPoint(MissionTarget):
""" """
:return: Whether this control point is an aircraft carrier :return: Whether this control point is an aircraft carrier
""" """
return self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP] return False
@property @property
def is_fleet(self): def is_fleet(self):
""" """
:return: Whether this control point is a boat (mobile) :return: Whether this control point is a boat (mobile)
""" """
return self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP] return False
@property @property
def is_lha(self): def is_lha(self):
""" """
:return: Whether this control point is an LHA :return: Whether this control point is an LHA
""" """
return self.cptype in [ControlPointType.LHA_GROUP] return False
@property @property
def sea_radials(self) -> List[int]: def sea_radials(self) -> List[int]:
@ -249,52 +239,42 @@ class ControlPoint(MissionTarget):
return result return result
@property @property
@abstractmethod
def total_aircraft_parking(self): def total_aircraft_parking(self):
""" """
:return: The maximum number of aircraft that can be stored in this control point :return: The maximum number of aircraft that can be stored in this
control point
""" """
if self.cptype == ControlPointType.AIRBASE: ...
return len(self.airport.parking_slots)
elif self.is_lha:
return 20
elif self.is_carrier:
return 90
else:
return 0
# TODO: Should be Airbase specific.
def connect(self, to: ControlPoint) -> None: def connect(self, to: ControlPoint) -> None:
self.connected_points.append(to) self.connected_points.append(to)
self.stances[to.id] = CombatStance.DEFENSIVE self.stances[to.id] = CombatStance.DEFENSIVE
def has_runway(self): @abstractmethod
def has_runway(self) -> bool:
""" """
Check whether this control point can have aircraft taking off or landing. Check whether this control point supports taking offs and landings.
:return: :return:
""" """
if self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP] : ...
for g in self.ground_objects:
if g.dcs_identifier in ["CARRIER", "LHA"]:
for group in g.groups:
for u in group.units:
if db.unit_type_from_name(u.type) in [CVN_74_John_C__Stennis, LHA_1_Tarawa, CV_1143_5_Admiral_Kuznetsov, Type_071_Amphibious_Transport_Dock]:
return True
return False
elif self.cptype in [ControlPointType.AIRBASE, ControlPointType.FARP]:
return True
else:
return True
# TODO: Should be naval specific.
def get_carrier_group_name(self): def get_carrier_group_name(self):
""" """
Get the carrier group name if the airbase is a carrier Get the carrier group name if the airbase is a carrier
:return: Carrier group name :return: Carrier group name
""" """
if self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP] : if self.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP,
ControlPointType.LHA_GROUP]:
for g in self.ground_objects: for g in self.ground_objects:
if g.dcs_identifier == "CARRIER": if g.dcs_identifier == "CARRIER":
for group in g.groups: for group in g.groups:
for u in group.units: for u in group.units:
if db.unit_type_from_name(u.type) in [CVN_74_John_C__Stennis, CV_1143_5_Admiral_Kuznetsov]: if db.unit_type_from_name(u.type) in [
CVN_74_John_C__Stennis,
CV_1143_5_Admiral_Kuznetsov]:
return group.name return group.name
elif g.dcs_identifier == "LHA": elif g.dcs_identifier == "LHA":
for group in g.groups: for group in g.groups:
@ -303,6 +283,7 @@ class ControlPoint(MissionTarget):
return group.name return group.name
return None return None
# TODO: Should be Airbase specific.
def is_connected(self, to) -> bool: def is_connected(self, to) -> bool:
return to in self.connected_points return to in self.connected_points
@ -327,6 +308,7 @@ class ControlPoint(MissionTarget):
def is_friendly(self, to_player: bool) -> bool: def is_friendly(self, to_player: bool) -> bool:
return self.captured == to_player return self.captured == to_player
# TODO: Should be Airbase specific.
def clear_base_defenses(self) -> None: def clear_base_defenses(self) -> None:
for base_defense in self.base_defenses: for base_defense in self.base_defenses:
if isinstance(base_defense, EwrGroundObject): if isinstance(base_defense, EwrGroundObject):
@ -345,6 +327,7 @@ class ControlPoint(MissionTarget):
base_defense.position) base_defense.position)
self.base_defenses = [] self.base_defenses = []
# TODO: Should be Airbase specific.
def capture(self, game: Game, for_player: bool) -> None: def capture(self, game: Game, for_player: bool) -> None:
if for_player: if for_player:
self.captured = True self.captured = True
@ -360,35 +343,9 @@ class ControlPoint(MissionTarget):
from .start_generator import BaseDefenseGenerator from .start_generator import BaseDefenseGenerator
BaseDefenseGenerator(game, self).generate() BaseDefenseGenerator(game, self).generate()
def mission_types(self, for_player: bool) -> Iterator[FlightType]: @abstractmethod
yield from super().mission_types(for_player)
if self.is_friendly(for_player):
if self.is_fleet:
yield from [
# TODO: FlightType.INTERCEPTION
# TODO: Buddy tanking for the A-4?
# TODO: Rescue chopper?
# TODO: Inter-ship logistics?
]
else:
yield from [
# TODO: FlightType.INTERCEPTION
# TODO: FlightType.LOGISTICS
]
else:
if self.is_fleet:
yield FlightType.ANTISHIP
else:
yield from [
# TODO: FlightType.STRIKE
]
def can_land(self, aircraft: FlyingType) -> bool: def can_land(self, aircraft: FlyingType) -> bool:
if self.is_carrier and aircraft not in db.CARRIER_CAPABLE: ...
return False
if self.is_lha and aircraft not in db.LHA_CAPABLE:
return False
return True
def aircraft_transferring(self, game: Game) -> int: def aircraft_transferring(self, game: Game) -> int:
if self.captured: if self.captured:
@ -421,11 +378,158 @@ class ControlPoint(MissionTarget):
return (self.total_aircraft_parking - return (self.total_aircraft_parking -
self.expected_aircraft_next_turn(game).total) self.expected_aircraft_next_turn(game).total)
@abstractmethod
def active_runway(self, conditions: Conditions,
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
...
@property
def parking_slots(self) -> Iterator[ParkingSlot]:
yield from []
class Airfield(ControlPoint):
def __init__(self, airport: Airport, radials: List[int], size: int,
importance: float, has_frontline=True):
super().__init__(airport.id, airport.name, airport.position, airport,
radials, size, importance, has_frontline,
cptype=ControlPointType.AIRBASE)
self.airport = airport
def can_land(self, aircraft: FlyingType) -> bool:
return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
yield from super().mission_types(for_player)
if self.is_friendly(for_player):
yield from [
# TODO: FlightType.INTERCEPTION
# TODO: FlightType.LOGISTICS
]
else:
yield from [
# TODO: FlightType.STRIKE
]
@property
def total_aircraft_parking(self) -> int:
return len(self.airport.parking_slots)
@property
def heading(self) -> int:
return self.airport.runways[0].heading
def has_runway(self) -> bool:
return True
def active_runway(self, conditions: Conditions,
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
assigner = RunwayAssigner(conditions)
return assigner.get_preferred_runway(self.airport)
@property
def parking_slots(self) -> Iterator[ParkingSlot]:
yield from self.airport.parking_slots
class NavalControlPoint(ControlPoint, ABC):
@property
def is_fleet(self) -> bool:
return True
def mission_types(self, for_player: bool) -> Iterator[FlightType]:
yield from super().mission_types(for_player)
if self.is_friendly(for_player):
yield from [
# TODO: FlightType.INTERCEPTION
# TODO: Buddy tanking for the A-4?
# TODO: Rescue chopper?
# TODO: Inter-ship logistics?
]
else:
yield FlightType.ANTISHIP
@property
def heading(self) -> int:
return 0 # TODO compute heading
def has_runway(self) -> bool:
# Necessary because it's possible for the carrier itself to have sunk
# while its escorts are still alive.
for g in self.ground_objects:
if g.dcs_identifier in ["CARRIER", "LHA"]:
for group in g.groups:
for u in group.units:
if db.unit_type_from_name(u.type) in [
CVN_74_John_C__Stennis, LHA_1_Tarawa,
CV_1143_5_Admiral_Kuznetsov,
Type_071_Amphibious_Transport_Dock]:
return True
return False
def active_runway(self, conditions: Conditions,
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
# TODO: Assign TACAN and ICLS earlier so we don't need this.
fallback = RunwayData(self.full_name, runway_heading=0, runway_name="")
return dynamic_runways.get(self.name, fallback)
class Carrier(NavalControlPoint):
def __init__(self, name: str, at: Point, cp_id: int):
import game.theater.conflicttheater
super().__init__(cp_id, name, at, at, game.theater.conflicttheater.LAND,
game.theater.conflicttheater.SIZE_SMALL, 1,
has_frontline=False,
cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP)
def capture(self, game: Game, for_player: bool) -> None:
raise RuntimeError("Carriers cannot be captured")
@property
def is_carrier(self):
return True
def can_land(self, aircraft: FlyingType) -> bool:
return aircraft in db.CARRIER_CAPABLE
@property
def total_aircraft_parking(self) -> int:
return 90
class Lha(NavalControlPoint):
def __init__(self, name: str, at: Point, cp_id: int):
import game.theater.conflicttheater
super().__init__(cp_id, name, at, at, game.theater.conflicttheater.LAND,
game.theater.conflicttheater.SIZE_SMALL, 1,
has_frontline=False, cptype=ControlPointType.LHA_GROUP)
def capture(self, game: Game, for_player: bool) -> None:
raise RuntimeError("LHAs cannot be captured")
@property
def is_lha(self) -> bool:
return True
def can_land(self, aircraft: FlyingType) -> bool:
return aircraft in db.LHA_CAPABLE
@property
def total_aircraft_parking(self) -> int:
return 20
class OffMapSpawn(ControlPoint): class OffMapSpawn(ControlPoint):
def __init__(self, id: int, name: str, position: Point):
def has_runway(self) -> bool:
return True
def __init__(self, cp_id: int, name: str, position: Point):
from . import IMPORTANCE_MEDIUM, SIZE_REGULAR from . import IMPORTANCE_MEDIUM, SIZE_REGULAR
super().__init__(id, name, position, at=position, radials=[], super().__init__(cp_id, name, position, at=position, radials=[],
size=SIZE_REGULAR, importance=IMPORTANCE_MEDIUM, size=SIZE_REGULAR, importance=IMPORTANCE_MEDIUM,
has_frontline=False, cptype=ControlPointType.OFF_MAP) has_frontline=False, cptype=ControlPointType.OFF_MAP)
@ -438,3 +542,15 @@ class OffMapSpawn(ControlPoint):
@property @property
def total_aircraft_parking(self) -> int: def total_aircraft_parking(self) -> int:
return 1000 return 1000
def can_land(self, aircraft: FlyingType) -> bool:
return True
@property
def heading(self) -> int:
return 0
def active_runway(self, conditions: Conditions,
dynamic_runways: Dict[str, RunwayData]) -> RunwayData:
logging.warning("TODO: Off map spawns have no runways.")
return RunwayData(self.full_name, runway_heading=0, runway_name="")

View File

@ -5,12 +5,14 @@ import logging
import random import random
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Optional from typing import Optional, TYPE_CHECKING
from dcs.weather import Weather as PydcsWeather, Wind from dcs.weather import Weather as PydcsWeather, Wind
from game.settings import Settings from game.settings import Settings
from game.theater import ConflictTheater
if TYPE_CHECKING:
from game.theater import ConflictTheater
class TimeOfDay(Enum): class TimeOfDay(Enum):

View File

@ -71,8 +71,10 @@ from game import db
from game.data.cap_capabilities_db import GUNFIGHTERS from game.data.cap_capabilities_db import GUNFIGHTERS
from game.settings import Settings from game.settings import Settings
from game.theater.controlpoint import ( from game.theater.controlpoint import (
Airfield,
ControlPoint, ControlPoint,
ControlPointType, ControlPointType,
NavalControlPoint,
OffMapSpawn, OffMapSpawn,
) )
from game.theater.theatergroundobject import TheaterGroundObject from game.theater.theatergroundobject import TheaterGroundObject
@ -696,18 +698,6 @@ class AircraftConflictGenerator:
return StartType.Cold return StartType.Cold
return StartType.Warm return StartType.Warm
def determine_runway(self, cp: ControlPoint, dynamic_runways) -> RunwayData:
fallback = RunwayData(cp.full_name, runway_heading=0, runway_name="")
if cp.cptype == ControlPointType.AIRBASE:
assigner = RunwayAssigner(self.game.conditions)
return assigner.get_preferred_runway(cp.airport)
elif cp.is_fleet:
return dynamic_runways.get(cp.name, fallback)
else:
logging.warning(
f"Unhandled departure/arrival control point: {cp.cptype}")
return fallback
def _setup_group(self, group: FlyingGroup, for_task: Type[Task], def _setup_group(self, group: FlyingGroup, for_task: Type[Task],
package: Package, flight: Flight, package: Package, flight: Flight,
dynamic_runways: Dict[str, RunwayData]) -> None: dynamic_runways: Dict[str, RunwayData]) -> None:
@ -767,7 +757,8 @@ class AircraftConflictGenerator:
divert = None divert = None
if flight.divert is not None: if flight.divert is not None:
divert = self.determine_runway(flight.divert, dynamic_runways) divert = flight.divert.active_runway(self.game.conditions,
dynamic_runways)
self.flights.append(FlightData( self.flights.append(FlightData(
package=package, package=package,
@ -777,8 +768,10 @@ class AircraftConflictGenerator:
friendly=flight.from_cp.captured, friendly=flight.from_cp.captured,
# Set later. # Set later.
departure_delay=timedelta(), departure_delay=timedelta(),
departure=self.determine_runway(flight.departure, dynamic_runways), departure=flight.departure.active_runway(self.game.conditions,
arrival=self.determine_runway(flight.arrival, dynamic_runways), dynamic_runways),
arrival=flight.arrival.active_runway(self.game.conditions,
dynamic_runways),
divert=divert, divert=divert,
# Waypoints are added later, after they've had their TOTs set. # Waypoints are added later, after they've had their TOTs set.
waypoints=[], waypoints=[],
@ -911,8 +904,7 @@ class AircraftConflictGenerator:
def clear_parking_slots(self) -> None: def clear_parking_slots(self) -> None:
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if cp.airport is not None: for parking_slot in cp.parking_slots:
for parking_slot in cp.airport.parking_slots:
parking_slot.unit_id = None parking_slot.unit_id = None
def generate_flights(self, country, ato: AirTaskingOrder, def generate_flights(self, country, ato: AirTaskingOrder,
@ -938,10 +930,7 @@ class AircraftConflictGenerator:
enemy_country: Country) -> None: enemy_country: Country) -> None:
inventories = self.game.aircraft_inventory.inventories inventories = self.game.aircraft_inventory.inventories
for control_point, inventory in inventories.items(): for control_point, inventory in inventories.items():
if isinstance(control_point, OffMapSpawn): if not isinstance(control_point, Airfield):
continue
if control_point.is_fleet:
# Don't crowd the deck since the AI will struggle.
continue continue
if control_point.captured: if control_point.captured:
@ -957,7 +946,7 @@ class AircraftConflictGenerator:
# If we run out of parking, stop spawning aircraft. # If we run out of parking, stop spawning aircraft.
return return
def _spawn_unused_at(self, control_point: ControlPoint, country: Country, def _spawn_unused_at(self, control_point: Airfield, country: Country,
aircraft: Type[FlyingType], number: int) -> None: aircraft: Type[FlyingType], number: int) -> None:
for _ in range(number): for _ in range(number):
# Creating a flight even those this isn't a fragged mission lets us # Creating a flight even those this isn't a fragged mission lets us
@ -1033,7 +1022,7 @@ class AircraftConflictGenerator:
side=country, side=country,
flight=flight, flight=flight,
origin=cp) origin=cp)
elif cp.is_fleet: elif isinstance(cp, NavalControlPoint):
group_name = cp.get_carrier_group_name() group_name = cp.get_carrier_group_name()
group = self._generate_at_group( group = self._generate_at_group(
name=namegen.next_unit_name(country, cp.id, flight.unit_type), name=namegen.next_unit_name(country, cp.id, flight.unit_type),
@ -1043,8 +1032,12 @@ class AircraftConflictGenerator:
start_type=flight.start_type, start_type=flight.start_type,
at=self.m.find_group(group_name)) at=self.m.find_group(group_name))
else: else:
if not isinstance(cp, Airfield):
raise RuntimeError(
f"Attempted to spawn at airfield for non-airfield {cp}")
group = self._generate_at_airport( group = self._generate_at_airport(
name=namegen.next_unit_name(country, cp.id, flight.unit_type), name=namegen.next_unit_name(country, cp.id,
flight.unit_type),
side=country, side=country,
unit_type=flight.unit_type, unit_type=flight.unit_type,
count=flight.count, count=flight.count,

View File

@ -7,7 +7,6 @@ from typing import Iterator, Optional
from dcs.terrain.terrain import Airport from dcs.terrain.terrain import Airport
from game.theater import ControlPoint, ControlPointType
from game.weather import Conditions from game.weather import Conditions
from .airfields import AIRFIELD_DATA from .airfields import AIRFIELD_DATA
from .radios import RadioFrequency from .radios import RadioFrequency
@ -117,23 +116,3 @@ class RunwayAssigner:
# Otherwise the only difference between the two is the distance from # Otherwise the only difference between the two is the distance from
# parking, which we don't know, so just pick the first one. # parking, which we don't know, so just pick the first one.
return best_runways[0] return best_runways[0]
def takeoff_heading(self, departure: ControlPoint) -> int:
if departure.cptype == ControlPointType.AIRBASE:
return self.get_preferred_runway(departure.airport).runway_heading
elif departure.is_fleet:
# The carrier will be angled into the wind automatically.
return (self.conditions.weather.wind.at_0m.direction + 180) % 360
logging.warning(
f"Unhandled departure control point: {departure.cptype}")
return 0
def landing_heading(self, arrival: ControlPoint) -> int:
if arrival.cptype == ControlPointType.AIRBASE:
return self.get_preferred_runway(arrival.airport).runway_heading
elif arrival.is_fleet:
# The carrier will be angled into the wind automatically.
return (self.conditions.weather.wind.at_0m.direction + 180) % 360
logging.warning(
f"Unhandled departure control point: {arrival.cptype}")
return 0