mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
Refactor control points into individual classes.
This commit is contained in:
parent
c67263662d
commit
63bdbebcaa
@ -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"]
|
||||||
|
|||||||
@ -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="")
|
||||||
|
|||||||
@ -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):
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user