More adaptation for pydcs updates.

This is as much as we can do until pydcs actually adds the py.typed
file. Once that's added there are a few ugly monkey patching corners
that will just need `# type: ignore` for now, but we can't pre-add those
since we have mypy warning us about superfluous ignore comments.
This commit is contained in:
Dan Albert 2021-07-09 14:13:20 -07:00
parent 469dd49def
commit 96c7b87ac7
27 changed files with 238 additions and 207 deletions

View File

@ -5,14 +5,14 @@ import inspect
import logging import logging
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, Iterator, Optional, Set, Tuple, Union, cast, Any from typing import Dict, Iterator, Optional, Set, Tuple, cast, Any
from dcs.unitgroup import FlyingGroup from dcs.unitgroup import FlyingGroup
from dcs.weapons_data import Weapons, weapon_ids from dcs.weapons_data import Weapons, weapon_ids
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
PydcsWeapon = Dict[str, Any] PydcsWeapon = Any
PydcsWeaponAssignment = Tuple[int, PydcsWeapon] PydcsWeaponAssignment = Tuple[int, PydcsWeapon]
@ -83,7 +83,7 @@ class Pylon:
# configuration. # configuration.
return weapon in self.allowed or weapon.cls_id == "<CLEAN>" return weapon in self.allowed or weapon.cls_id == "<CLEAN>"
def equip(self, group: FlyingGroup, weapon: Weapon) -> None: def equip(self, group: FlyingGroup[Any], weapon: Weapon) -> None:
if not self.can_equip(weapon): if not self.can_equip(weapon):
logging.error(f"Pylon {self.number} cannot equip {weapon.name}") logging.error(f"Pylon {self.number} cannot equip {weapon.name}")
group.load_pylon(self.make_pydcs_assignment(weapon), self.number) group.load_pylon(self.make_pydcs_assignment(weapon), self.number)

View File

@ -31,7 +31,7 @@ from dcs.ships import (
from dcs.terrain.terrain import Airport from dcs.terrain.terrain import Airport
from dcs.unit import Ship from dcs.unit import Ship
from dcs.unitgroup import ShipGroup, StaticGroup from dcs.unitgroup import ShipGroup, StaticGroup
from dcs.unittype import UnitType from dcs.unittype import UnitType, FlyingType, ShipType, VehicleType
from dcs.vehicles import ( from dcs.vehicles import (
vehicle_map, vehicle_map,
) )
@ -256,7 +256,7 @@ Aircraft livery overrides. Syntax as follows:
`Identifier` is aircraft identifier (as used troughout the file) and "LiveryName" (with double quotes) `Identifier` is aircraft identifier (as used troughout the file) and "LiveryName" (with double quotes)
is livery name as found in mission editor. is livery name as found in mission editor.
""" """
PLANE_LIVERY_OVERRIDES = { PLANE_LIVERY_OVERRIDES: dict[Type[FlyingType], str] = {
FA_18C_hornet: "VFA-34", # default livery for the hornet is blue angels one FA_18C_hornet: "VFA-34", # default livery for the hornet is blue angels one
} }
@ -329,7 +329,7 @@ REWARDS = {
StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point] StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point]
def upgrade_to_supercarrier(unit: Type[Ship], name: str) -> Type[Ship]: def upgrade_to_supercarrier(unit: Type[ShipType], name: str) -> Type[ShipType]:
if unit == Stennis: if unit == Stennis:
if name == "CVN-71 Theodore Roosevelt": if name == "CVN-71 Theodore Roosevelt":
return CVN_71 return CVN_71
@ -362,6 +362,14 @@ def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
return None return None
def vehicle_type_from_name(name: str) -> Type[VehicleType]:
return vehicle_map[name]
def ship_type_from_name(name: str) -> Type[ShipType]:
return ship_map[name]
def country_id_from_name(name: str) -> int: def country_id_from_name(name: str) -> int:
for k, v in country_dict.items(): for k, v in country_dict.items():
if v.name == name: if v.name == name:

View File

@ -105,6 +105,7 @@ class PatrolConfig:
) )
# TODO: Split into PlaneType and HelicopterType?
@dataclass(frozen=True) @dataclass(frozen=True)
class AircraftType(UnitType[Type[FlyingType]]): class AircraftType(UnitType[Type[FlyingType]]):
carrier_capable: bool carrier_capable: bool
@ -144,12 +145,23 @@ class AircraftType(UnitType[Type[FlyingType]]):
return kph(self.dcs_unit_type.max_speed) return kph(self.dcs_unit_type.max_speed)
def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency: def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency:
from gen.radios import ChannelInUseError, MHz from gen.radios import ChannelInUseError, kHz
if self.intra_flight_radio is not None: if self.intra_flight_radio is not None:
return radio_registry.alloc_for_radio(self.intra_flight_radio) return radio_registry.alloc_for_radio(self.intra_flight_radio)
freq = MHz(self.dcs_unit_type.radio_frequency) # The default radio frequency is set in megahertz. For some aircraft, it is a
# floating point value. For all current aircraft, adjusting to kilohertz will be
# sufficient to convert to an integer.
in_khz = float(self.dcs_unit_type.radio_frequency) * 1000
if not in_khz.is_integer():
logging.warning(
f"Found unexpected sub-kHz default radio for {self}: {in_khz} kHz. "
"Truncating to integer. The truncated frequency may not be valid for "
"the aircraft."
)
freq = kHz(int(in_khz))
try: try:
radio_registry.reserve(freq) radio_registry.reserve(freq)
except ChannelInUseError: except ChannelInUseError:

View File

@ -78,8 +78,8 @@ class GroundLosses:
player_airlifts: List[AirliftUnits] = field(default_factory=list) player_airlifts: List[AirliftUnits] = field(default_factory=list)
enemy_airlifts: List[AirliftUnits] = field(default_factory=list) enemy_airlifts: List[AirliftUnits] = field(default_factory=list)
player_ground_objects: List[GroundObjectUnit] = field(default_factory=list) player_ground_objects: List[GroundObjectUnit[Any]] = field(default_factory=list)
enemy_ground_objects: List[GroundObjectUnit] = field(default_factory=list) enemy_ground_objects: List[GroundObjectUnit[Any]] = field(default_factory=list)
player_buildings: List[Building] = field(default_factory=list) player_buildings: List[Building] = field(default_factory=list)
enemy_buildings: List[Building] = field(default_factory=list) enemy_buildings: List[Building] = field(default_factory=list)
@ -166,7 +166,7 @@ class Debriefing:
yield from self.ground_losses.enemy_airlifts yield from self.ground_losses.enemy_airlifts
@property @property
def ground_object_losses(self) -> Iterator[GroundObjectUnit]: def ground_object_losses(self) -> Iterator[GroundObjectUnit[Any]]:
yield from self.ground_losses.player_ground_objects yield from self.ground_losses.player_ground_objects
yield from self.ground_losses.enemy_ground_objects yield from self.ground_losses.enemy_ground_objects

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from dcs import Point from dcs import Point
@ -7,7 +9,7 @@ class PointWithHeading(Point):
self.heading = 0 self.heading = 0
@staticmethod @staticmethod
def from_point(point: Point, heading: int) -> Point: def from_point(point: Point, heading: int) -> PointWithHeading:
p = PointWithHeading() p = PointWithHeading()
p.x = point.x p.x = point.x
p.y = point.y p.y = point.y

9
game/positioned.py Normal file
View File

@ -0,0 +1,9 @@
from typing import Protocol
from dcs import Point
class Positioned(Protocol):
@property
def position(self) -> Point:
raise NotImplementedError

View File

@ -33,11 +33,10 @@ from dcs.terrain import (
) )
from dcs.terrain.terrain import Airport, Terrain from dcs.terrain.terrain import Airport, Terrain
from dcs.unitgroup import ( from dcs.unitgroup import (
FlyingGroup,
Group,
ShipGroup, ShipGroup,
StaticGroup, StaticGroup,
VehicleGroup, VehicleGroup,
PlaneGroup,
) )
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from pyproj import CRS, Transformer from pyproj import CRS, Transformer
@ -57,6 +56,7 @@ from .landmap import Landmap, load_landmap, poly_contains
from .latlon import LatLon from .latlon import LatLon
from .projections import TransverseMercator from .projections import TransverseMercator
from ..point_with_heading import PointWithHeading from ..point_with_heading import PointWithHeading
from ..positioned import Positioned
from ..profiling import logged_duration from ..profiling import logged_duration
from ..scenery_group import SceneryGroup from ..scenery_group import SceneryGroup
from ..utils import Distance, meters from ..utils import Distance, meters
@ -185,7 +185,7 @@ class MizCampaignLoader:
def red(self) -> Country: def red(self) -> Country:
return self.country(blue=False) return self.country(blue=False)
def off_map_spawns(self, blue: bool) -> Iterator[FlyingGroup]: def off_map_spawns(self, blue: bool) -> Iterator[PlaneGroup]:
for group in self.country(blue).plane_group: for group in self.country(blue).plane_group:
if group.units[0].type == self.OFF_MAP_UNIT_TYPE: if group.units[0].type == self.OFF_MAP_UNIT_TYPE:
yield group yield group
@ -309,26 +309,26 @@ class MizCampaignLoader:
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.carriers(blue): for ship in self.carriers(blue):
# TODO: Name the carrier. # TODO: Name the carrier.
control_point = Carrier( control_point = Carrier(
"carrier", group.position, next(self.control_point_id) "carrier", ship.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 = ship.late_activation
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
for group in self.lhas(blue): for ship in self.lhas(blue):
# TODO: Name the LHA.db # TODO: Name the LHA.db
control_point = Lha("lha", group.position, next(self.control_point_id)) control_point = Lha("lha", ship.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 = ship.late_activation
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
for group in self.fobs(blue): for fob in self.fobs(blue):
control_point = Fob( control_point = Fob(
str(group.name), group.position, next(self.control_point_id) str(fob.name), fob.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 = fob.late_activation
control_points[control_point.id] = control_point control_points[control_point.id] = control_point
return control_points return control_points
@ -389,22 +389,22 @@ class MizCampaignLoader:
origin, list(reversed(waypoints)) origin, list(reversed(waypoints))
) )
def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]: def objective_info(self, near: Positioned) -> Tuple[ControlPoint, Distance]:
closest = self.theater.closest_control_point(group.position) closest = self.theater.closest_control_point(near.position)
distance = meters(closest.position.distance_to_point(group.position)) distance = meters(closest.position.distance_to_point(near.position))
return closest, distance return closest, distance
def add_preset_locations(self) -> None: def add_preset_locations(self) -> None:
for group in self.offshore_strike_targets: for static in self.offshore_strike_targets:
closest, distance = self.objective_info(group) closest, distance = self.objective_info(static)
closest.preset_locations.offshore_strike_locations.append( closest.preset_locations.offshore_strike_locations.append(
PointWithHeading.from_point(group.position, group.units[0].heading) PointWithHeading.from_point(static.position, static.units[0].heading)
) )
for group in self.ships: for ship in self.ships:
closest, distance = self.objective_info(group) closest, distance = self.objective_info(ship)
closest.preset_locations.ships.append( closest.preset_locations.ships.append(
PointWithHeading.from_point(group.position, group.units[0].heading) PointWithHeading.from_point(ship.position, ship.units[0].heading)
) )
for group in self.missile_sites: for group in self.missile_sites:
@ -455,33 +455,33 @@ class MizCampaignLoader:
PointWithHeading.from_point(group.position, group.units[0].heading) PointWithHeading.from_point(group.position, group.units[0].heading)
) )
for group in self.helipads: for static in self.helipads:
closest, distance = self.objective_info(group) closest, distance = self.objective_info(static)
closest.helipads.append( closest.helipads.append(
PointWithHeading.from_point(group.position, group.units[0].heading) PointWithHeading.from_point(static.position, static.units[0].heading)
) )
for group in self.factories: for static in self.factories:
closest, distance = self.objective_info(group) closest, distance = self.objective_info(static)
closest.preset_locations.factories.append( closest.preset_locations.factories.append(
PointWithHeading.from_point(group.position, group.units[0].heading) PointWithHeading.from_point(static.position, static.units[0].heading)
) )
for group in self.ammunition_depots: for static in self.ammunition_depots:
closest, distance = self.objective_info(group) closest, distance = self.objective_info(static)
closest.preset_locations.ammunition_depots.append( closest.preset_locations.ammunition_depots.append(
PointWithHeading.from_point(group.position, group.units[0].heading) PointWithHeading.from_point(static.position, static.units[0].heading)
) )
for group in self.strike_targets: for static in self.strike_targets:
closest, distance = self.objective_info(group) closest, distance = self.objective_info(static)
closest.preset_locations.strike_locations.append( closest.preset_locations.strike_locations.append(
PointWithHeading.from_point(group.position, group.units[0].heading) PointWithHeading.from_point(static.position, static.units[0].heading)
) )
for group in self.scenery: for scenery_group in self.scenery:
closest, distance = self.objective_info(group) closest, distance = self.objective_info(scenery_group)
closest.preset_locations.scenery.append(group) closest.preset_locations.scenery.append(scenery_group)
def populate_theater(self) -> None: def populate_theater(self) -> None:
for control_point in self.control_points.values(): for control_point in self.control_points.values():
@ -587,12 +587,12 @@ class ConflictTheater:
return True return True
def nearest_land_pos(self, point: Point, extend_dist: int = 50) -> Point: def nearest_land_pos(self, near: Point, extend_dist: int = 50) -> Point:
"""Returns the nearest point inside a land exclusion zone from point """Returns the nearest point inside a land exclusion zone from point
`extend_dist` determines how far inside the zone the point should be placed""" `extend_dist` determines how far inside the zone the point should be placed"""
if self.is_on_land(point): if self.is_on_land(near):
return point return near
point = geometry.Point(point.x, point.y) point = geometry.Point(near.x, near.y)
nearest_points = [] nearest_points = []
if not self.landmap: if not self.landmap:
raise RuntimeError("Landmap not initialized") raise RuntimeError("Landmap not initialized")

View File

@ -751,7 +751,7 @@ class ControlPoint(MissionTarget, ABC):
return len([obj for obj in self.connected_objectives if obj.category == "ammo"]) return len([obj for obj in self.connected_objectives if obj.category == "ammo"])
@property @property
def strike_targets(self) -> List[Union[MissionTarget, Unit]]: def strike_targets(self) -> Sequence[Union[MissionTarget, Unit]]:
return [] return []
@property @property

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import Iterator, List, Tuple from typing import Iterator, List, Tuple, Any
from dcs.mapping import Point from dcs.mapping import Point
@ -66,7 +66,15 @@ class FrontLine(MissionTarget):
self.segments: List[FrontLineSegment] = [ self.segments: List[FrontLineSegment] = [
FrontLineSegment(a, b) for a, b in pairwise(route) FrontLineSegment(a, b) for a, b in pairwise(route)
] ]
self.name = f"Front line {blue_point}/{red_point}" super().__init__(
f"Front line {blue_point}/{red_point}",
self.point_from_a(self._position_distance),
)
def __setstate__(self, state: dict[str, Any]) -> None:
self.__dict__.update(state)
if not hasattr(self, "position"):
self.position = self.point_from_a(self._position_distance)
def control_point_hostile_to(self, player: bool) -> ControlPoint: def control_point_hostile_to(self, player: bool) -> ControlPoint:
if player: if player:
@ -87,14 +95,6 @@ class FrontLine(MissionTarget):
] ]
yield from super().mission_types(for_player) yield from super().mission_types(for_player)
@property
def position(self) -> Point:
"""
The position where the conflict should occur
according to the current strength of each control point.
"""
return self.point_from_a(self._position_distance)
@property @property
def points(self) -> Iterator[Point]: def points(self) -> Iterator[Point]:
yield self.segments[0].point_a yield self.segments[0].point_a
@ -149,6 +149,9 @@ class FrontLine(MissionTarget):
) )
else: else:
remaining_dist -= segment.attack_distance remaining_dist -= segment.attack_distance
raise RuntimeError(
f"Could not find front line point {distance} from {self.blue_cp}"
)
@property @property
def _position_distance(self) -> float: def _position_distance(self) -> float:

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from collections import Sequence
from typing import Iterator, TYPE_CHECKING, List, Union from typing import Iterator, TYPE_CHECKING, List, Union
from dcs.mapping import Point from dcs.mapping import Point
@ -45,5 +46,5 @@ class MissionTarget:
] ]
@property @property
def strike_targets(self) -> List[Union[MissionTarget, Unit]]: def strike_targets(self) -> Sequence[Union[MissionTarget, Unit]]:
return [] return []

View File

@ -2,13 +2,13 @@ from __future__ import annotations
import itertools import itertools
import logging import logging
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, Any
from dcs.mapping import Point from dcs.mapping import Point
from dcs.triggers import TriggerZone from dcs.triggers import TriggerZone
from dcs.unit import Unit from dcs.unit import Unit
from dcs.unitgroup import Group, ShipGroup, VehicleGroup from dcs.unitgroup import ShipGroup, VehicleGroup
from dcs.unittype import VehicleType
from .. import db from .. import db
from ..data.radar_db import ( from ..data.radar_db import (
@ -47,7 +47,7 @@ NAME_BY_CATEGORY = {
} }
GroupT = TypeVar("GroupT", bound=Group) GroupT = TypeVar("GroupT", ShipGroup, VehicleGroup)
class TheaterGroundObject(MissionTarget, Generic[GroupT]): class TheaterGroundObject(MissionTarget, Generic[GroupT]):
@ -150,7 +150,7 @@ class TheaterGroundObject(MissionTarget, Generic[GroupT]):
return True return True
return False return False
def _max_range_of_type(self, group: Group, range_type: str) -> Distance: def _max_range_of_type(self, group: GroupT, range_type: str) -> Distance:
if not self.might_have_aa: if not self.might_have_aa:
return meters(0) return meters(0)
@ -171,13 +171,13 @@ class TheaterGroundObject(MissionTarget, Generic[GroupT]):
def max_detection_range(self) -> Distance: def max_detection_range(self) -> Distance:
return max(self.detection_range(g) for g in self.groups) return max(self.detection_range(g) for g in self.groups)
def detection_range(self, group: Group) -> Distance: def detection_range(self, group: GroupT) -> Distance:
return self._max_range_of_type(group, "detection_range") return self._max_range_of_type(group, "detection_range")
def max_threat_range(self) -> Distance: def max_threat_range(self) -> Distance:
return max(self.threat_range(g) for g in self.groups) return max(self.threat_range(g) for g in self.groups)
def threat_range(self, group: Group, radar_only: bool = False) -> Distance: def threat_range(self, group: GroupT, radar_only: bool = False) -> Distance:
return self._max_range_of_type(group, "threat_range") return self._max_range_of_type(group, "threat_range")
@property @property
@ -190,7 +190,7 @@ class TheaterGroundObject(MissionTarget, Generic[GroupT]):
return False return False
@property @property
def strike_targets(self) -> List[Union[MissionTarget, Unit]]: def strike_targets(self) -> Sequence[Union[MissionTarget, Unit]]:
return self.units return self.units
@property @property
@ -497,33 +497,25 @@ class SamGroundObject(TheaterGroundObject[VehicleGroup]):
def might_have_aa(self) -> bool: def might_have_aa(self) -> bool:
return True return True
def threat_range(self, group: Group, radar_only: bool = False) -> Distance: def threat_range(self, group: VehicleGroup, radar_only: bool = False) -> Distance:
max_non_radar = meters(0) max_non_radar = meters(0)
live_trs = set() live_trs = set()
max_telar_range = meters(0) max_telar_range = meters(0)
launchers = set() launchers = set()
for unit in group.units: for unit in group.units:
unit_type = db.unit_type_from_name(unit.type) unit_type = db.vehicle_type_from_name(unit.type)
if unit_type is None or not issubclass(unit_type, VehicleType):
continue
if unit_type in TRACK_RADARS: if unit_type in TRACK_RADARS:
live_trs.add(unit_type) live_trs.add(unit_type)
elif unit_type in TELARS: elif unit_type in TELARS:
max_telar_range = max( max_telar_range = max(max_telar_range, meters(unit_type.threat_range))
max_telar_range, meters(getattr(unit_type, "threat_range", 0))
)
elif unit_type in LAUNCHER_TRACKER_PAIRS: elif unit_type in LAUNCHER_TRACKER_PAIRS:
launchers.add(unit_type) launchers.add(unit_type)
else: else:
max_non_radar = max( max_non_radar = max(max_non_radar, meters(unit_type.threat_range))
max_non_radar, meters(getattr(unit_type, "threat_range", 0))
)
max_tel_range = meters(0) max_tel_range = meters(0)
for launcher in launchers: for launcher in launchers:
if LAUNCHER_TRACKER_PAIRS[launcher] in live_trs: if LAUNCHER_TRACKER_PAIRS[launcher] in live_trs:
max_tel_range = max( max_tel_range = max(max_tel_range, meters(unit_type.threat_range))
max_tel_range, meters(getattr(launcher, "threat_range"))
)
if radar_only: if radar_only:
return max(max_tel_range, max_telar_range) return max(max_tel_range, max_telar_range)
else: else:

View File

@ -5,8 +5,6 @@ from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional, TYPE_CHECKING, Any from typing import Optional, TYPE_CHECKING, Any
from dcs.unittype import UnitType as DcsUnitType
from game.theater import ControlPoint from game.theater import ControlPoint
from .dcs.groundunittype import GroundUnitType from .dcs.groundunittype import GroundUnitType
from .dcs.unittype import UnitType from .dcs.unittype import UnitType
@ -48,27 +46,27 @@ class PendingUnitDeliveries:
self.units = defaultdict(int) self.units = defaultdict(int)
def refund_ground_units(self, game: Game) -> None: def refund_ground_units(self, game: Game) -> None:
ground_units: dict[UnitType[DcsUnitType], int] = { ground_units: dict[UnitType[Any], int] = {
u: self.units[u] for u in self.units.keys() if isinstance(u, GroundUnitType) u: self.units[u] for u in self.units.keys() if isinstance(u, GroundUnitType)
} }
self.refund(game, ground_units) self.refund(game, ground_units)
for gu in ground_units.keys(): for gu in ground_units.keys():
del self.units[gu] del self.units[gu]
def refund(self, game: Game, units: dict[UnitType[DcsUnitType], int]) -> None: def refund(self, game: Game, units: dict[UnitType[Any], int]) -> None:
for unit_type, count in units.items(): for unit_type, count in units.items():
logging.info(f"Refunding {count} {unit_type} at {self.destination.name}") logging.info(f"Refunding {count} {unit_type} at {self.destination.name}")
game.adjust_budget( game.adjust_budget(
unit_type.price * count, player=self.destination.captured unit_type.price * count, player=self.destination.captured
) )
def pending_orders(self, unit_type: UnitType[DcsUnitType]) -> int: def pending_orders(self, unit_type: UnitType[Any]) -> int:
pending_units = self.units.get(unit_type) pending_units = self.units.get(unit_type)
if pending_units is None: if pending_units is None:
pending_units = 0 pending_units = 0
return pending_units return pending_units
def available_next_turn(self, unit_type: UnitType[DcsUnitType]) -> int: def available_next_turn(self, unit_type: UnitType[Any]) -> int:
current_units = self.destination.base.total_units_of_type(unit_type) current_units = self.destination.base.total_units_of_type(unit_type)
return self.pending_orders(unit_type) + current_units return self.pending_orders(unit_type) + current_units
@ -81,9 +79,9 @@ class PendingUnitDeliveries:
) )
self.refund_ground_units(game) self.refund_ground_units(game)
bought_units: dict[UnitType[DcsUnitType], int] = {} bought_units: dict[UnitType[Any], int] = {}
units_needing_transfer: dict[GroundUnitType, int] = {} units_needing_transfer: dict[GroundUnitType, int] = {}
sold_units: dict[UnitType[DcsUnitType], int] = {} sold_units: dict[UnitType[Any], int] = {}
for unit_type, count in self.units.items(): for unit_type, count in self.units.items():
coalition = "Ally" if self.destination.captured else "Enemy" coalition = "Ally" if self.destination.captured else "Enemy"
d: dict[Any, int] d: dict[Any, int]

View File

@ -2,10 +2,10 @@
import itertools import itertools
import math import math
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Optional, Any from typing import Dict, Optional, Any, Union, TypeVar, Generic
from dcs.unit import Unit from dcs.unit import Vehicle, Ship
from dcs.unitgroup import FlyingGroup, Group, VehicleGroup from dcs.unitgroup import FlyingGroup, VehicleGroup, StaticGroup, ShipGroup, MovingGroup
from game.dcs.groundunittype import GroundUnitType from game.dcs.groundunittype import GroundUnitType
from game.squadrons import Pilot from game.squadrons import Pilot
@ -27,11 +27,14 @@ class FrontLineUnit:
origin: ControlPoint origin: ControlPoint
UnitT = TypeVar("UnitT", Ship, Vehicle)
@dataclass(frozen=True) @dataclass(frozen=True)
class GroundObjectUnit: class GroundObjectUnit(Generic[UnitT]):
ground_object: TheaterGroundObject[Any] ground_object: TheaterGroundObject[Any]
group: Group group: MovingGroup[UnitT]
unit: Unit unit: UnitT
@dataclass(frozen=True) @dataclass(frozen=True)
@ -56,13 +59,13 @@ class UnitMap:
self.aircraft: Dict[str, FlyingUnit] = {} self.aircraft: Dict[str, FlyingUnit] = {}
self.airfields: Dict[str, Airfield] = {} self.airfields: Dict[str, Airfield] = {}
self.front_line_units: Dict[str, FrontLineUnit] = {} self.front_line_units: Dict[str, FrontLineUnit] = {}
self.ground_object_units: Dict[str, GroundObjectUnit] = {} self.ground_object_units: Dict[str, GroundObjectUnit[Any]] = {}
self.buildings: Dict[str, Building] = {} self.buildings: Dict[str, Building] = {}
self.convoys: Dict[str, ConvoyUnit] = {} self.convoys: Dict[str, ConvoyUnit] = {}
self.cargo_ships: Dict[str, CargoShip] = {} self.cargo_ships: Dict[str, CargoShip] = {}
self.airlifts: Dict[str, AirliftUnits] = {} self.airlifts: Dict[str, AirliftUnits] = {}
def add_aircraft(self, group: FlyingGroup, flight: Flight) -> None: def add_aircraft(self, group: FlyingGroup[Any], flight: Flight) -> None:
for pilot, unit in zip(flight.roster.pilots, group.units): for pilot, unit in zip(flight.roster.pilots, group.units):
# The actual name is a String (the pydcs translatable string), which # The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__. # doesn't define __eq__.
@ -85,7 +88,7 @@ class UnitMap:
return self.airfields.get(name, None) return self.airfields.get(name, None)
def add_front_line_units( def add_front_line_units(
self, group: Group, origin: ControlPoint, unit_type: GroundUnitType self, group: VehicleGroup, origin: ControlPoint, unit_type: GroundUnitType
) -> None: ) -> None:
for unit in group.units: for unit in group.units:
# The actual name is a String (the pydcs translatable string), which # The actual name is a String (the pydcs translatable string), which
@ -101,8 +104,8 @@ class UnitMap:
def add_ground_object_units( def add_ground_object_units(
self, self,
ground_object: TheaterGroundObject[Any], ground_object: TheaterGroundObject[Any],
persistence_group: Group, persistence_group: Union[ShipGroup, VehicleGroup],
miz_group: Group, miz_group: Union[ShipGroup, VehicleGroup],
) -> None: ) -> None:
"""Adds a group associated with a TGO to the unit map. """Adds a group associated with a TGO to the unit map.
@ -131,10 +134,10 @@ class UnitMap:
ground_object, persistence_group, persistent_unit ground_object, persistence_group, persistent_unit
) )
def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit]: def ground_object_unit(self, name: str) -> Optional[GroundObjectUnit[Any]]:
return self.ground_object_units.get(name, None) return self.ground_object_units.get(name, None)
def add_convoy_units(self, group: Group, convoy: Convoy) -> None: def add_convoy_units(self, group: VehicleGroup, convoy: Convoy) -> None:
for unit, unit_type in zip(group.units, convoy.iter_units()): for unit, unit_type in zip(group.units, convoy.iter_units()):
# The actual name is a String (the pydcs translatable string), which # The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__. # doesn't define __eq__.
@ -146,7 +149,7 @@ class UnitMap:
def convoy_unit(self, name: str) -> Optional[ConvoyUnit]: def convoy_unit(self, name: str) -> Optional[ConvoyUnit]:
return self.convoys.get(name, None) return self.convoys.get(name, None)
def add_cargo_ship(self, group: Group, ship: CargoShip) -> None: def add_cargo_ship(self, group: ShipGroup, ship: CargoShip) -> None:
if len(group.units) > 1: if len(group.units) > 1:
# Cargo ship "groups" are single units. Killing the one ship kills the whole # Cargo ship "groups" are single units. Killing the one ship kills the whole
# transfer. If we ever want to add escorts or create multiple cargo ships in # transfer. If we ever want to add escorts or create multiple cargo ships in
@ -163,7 +166,9 @@ class UnitMap:
def cargo_ship(self, name: str) -> Optional[CargoShip]: def cargo_ship(self, name: str) -> Optional[CargoShip]:
return self.cargo_ships.get(name, None) return self.cargo_ships.get(name, None)
def add_airlift_units(self, group: FlyingGroup, transfer: TransferOrder) -> None: def add_airlift_units(
self, group: FlyingGroup[Any], transfer: TransferOrder
) -> None:
capacity_each = math.ceil(transfer.size / len(group.units)) capacity_each = math.ceil(transfer.size / len(group.units))
for idx, transport in enumerate(group.units): for idx, transport in enumerate(group.units):
# Slice the units in groups based on the capacity of each unit. Cargo is # Slice the units in groups based on the capacity of each unit. Cargo is
@ -186,7 +191,9 @@ class UnitMap:
def airlift_unit(self, name: str) -> Optional[AirliftUnits]: def airlift_unit(self, name: str) -> Optional[AirliftUnits]:
return self.airlifts.get(name, None) return self.airlifts.get(name, None)
def add_building(self, ground_object: BuildingGroundObject, group: Group) -> None: def add_building(
self, ground_object: BuildingGroundObject, group: StaticGroup
) -> None:
# The actual name is a String (the pydcs translatable string), which # The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__. # doesn't define __eq__.
# The name of the initiator in the DCS dead event will have " object" # The name of the initiator in the DCS dead event will have " object"

View File

@ -321,7 +321,7 @@ class AircraftConflictGenerator:
@staticmethod @staticmethod
def livery_from_db(flight: Flight) -> Optional[str]: def livery_from_db(flight: Flight) -> Optional[str]:
return db.PLANE_LIVERY_OVERRIDES.get(flight.unit_type) return db.PLANE_LIVERY_OVERRIDES.get(flight.unit_type.dcs_unit_type)
def livery_from_faction(self, flight: Flight) -> Optional[str]: def livery_from_faction(self, flight: Flight) -> Optional[str]:
faction = self.game.faction_for(player=flight.departure.captured) faction = self.game.faction_for(player=flight.departure.captured)
@ -342,7 +342,7 @@ class AircraftConflictGenerator:
return livery return livery
return None return None
def _setup_livery(self, flight: Flight, group: FlyingGroup) -> None: def _setup_livery(self, flight: Flight, group: FlyingGroup[Any]) -> None:
livery = self.livery_for(flight) livery = self.livery_for(flight)
if livery is None: if livery is None:
return return
@ -458,8 +458,8 @@ class AircraftConflictGenerator:
unit_type: Type[FlyingType], unit_type: Type[FlyingType],
count: int, count: int,
start_type: str, start_type: str,
airport: Optional[Airport] = None, airport: Airport,
) -> FlyingGroup: ) -> FlyingGroup[Any]:
assert count > 0 assert count > 0
logging.info("airgen: {} for {} at {}".format(unit_type, side.id, airport)) logging.info("airgen: {} for {} at {}".format(unit_type, side.id, airport))
@ -476,7 +476,7 @@ class AircraftConflictGenerator:
def _generate_inflight( def _generate_inflight(
self, name: str, side: Country, flight: Flight, origin: ControlPoint self, name: str, side: Country, flight: Flight, origin: ControlPoint
) -> FlyingGroup: ) -> FlyingGroup[Any]:
assert flight.count > 0 assert flight.count > 0
at = origin.position at = origin.position
@ -521,7 +521,7 @@ class AircraftConflictGenerator:
count: int, count: int,
start_type: str, start_type: str,
at: Union[ShipGroup, StaticGroup], at: Union[ShipGroup, StaticGroup],
) -> FlyingGroup: ) -> FlyingGroup[Any]:
assert count > 0 assert count > 0
logging.info("airgen: {} for {} at unit {}".format(unit_type, side.id, at)) logging.info("airgen: {} for {} at unit {}".format(unit_type, side.id, at))
@ -546,27 +546,6 @@ class AircraftConflictGenerator:
point.alt_type = "RADIO" point.alt_type = "RADIO"
return point return point
def _rtb_for(
self,
group: FlyingGroup[Any],
cp: ControlPoint,
at: Optional[db.StartingPosition] = None,
) -> MovingPoint:
if at is None:
at = cp.at
position = at if isinstance(at, Point) else at.position
last_waypoint = group.points[-1]
if last_waypoint is not None:
heading = position.heading_between_point(last_waypoint.position)
tod_location = position.point_from_heading(heading, RTB_DISTANCE)
self._add_radio_waypoint(group, tod_location, last_waypoint.alt)
destination_waypoint = self._add_radio_waypoint(group, position, RTB_ALTITUDE)
if isinstance(at, Airport):
group.land_at(at)
return destination_waypoint
@staticmethod @staticmethod
def _at_position(at: Union[Point, ShipGroup, Type[Airport]]) -> Point: def _at_position(at: Union[Point, ShipGroup, Type[Airport]]) -> Point:
if isinstance(at, Point): if isinstance(at, Point):
@ -578,7 +557,7 @@ class AircraftConflictGenerator:
else: else:
assert False assert False
def _setup_payload(self, flight: Flight, group: FlyingGroup) -> None: def _setup_payload(self, flight: Flight, group: FlyingGroup[Any]) -> None:
for p in group.units: for p in group.units:
p.pylons.clear() p.pylons.clear()
@ -729,7 +708,7 @@ class AircraftConflictGenerator:
def generate_planned_flight( def generate_planned_flight(
self, cp: ControlPoint, country: Country, flight: Flight self, cp: ControlPoint, country: Country, flight: Flight
) -> FlyingGroup: ) -> FlyingGroup[Any]:
name = namegen.next_aircraft_name(country, cp.id, flight) name = namegen.next_aircraft_name(country, cp.id, flight)
try: try:
if flight.start_type == "In Flight": if flight.start_type == "In Flight":
@ -738,13 +717,19 @@ class AircraftConflictGenerator:
) )
elif isinstance(cp, NavalControlPoint): elif isinstance(cp, NavalControlPoint):
group_name = cp.get_carrier_group_name() group_name = cp.get_carrier_group_name()
carrier_group = self.m.find_group(group_name)
if not isinstance(carrier_group, ShipGroup):
raise RuntimeError(
f"Carrier group {carrier_group} is a "
"{carrier_group.__class__.__name__}, expected a ShipGroup"
)
group = self._generate_at_group( group = self._generate_at_group(
name=name, name=name,
side=country, side=country,
unit_type=flight.unit_type.dcs_unit_type, unit_type=flight.unit_type.dcs_unit_type,
count=flight.count, count=flight.count,
start_type=flight.start_type, start_type=flight.start_type,
at=self.m.find_group(group_name), at=carrier_group,
) )
else: else:
if not isinstance(cp, Airfield): if not isinstance(cp, Airfield):
@ -775,7 +760,7 @@ class AircraftConflictGenerator:
@staticmethod @staticmethod
def set_reduced_fuel( def set_reduced_fuel(
flight: Flight, group: FlyingGroup[Any], unit_type: Type[PlaneType] flight: Flight, group: FlyingGroup[Any], unit_type: Type[FlyingType]
) -> None: ) -> None:
if unit_type is Su_33: if unit_type is Su_33:
for unit in group.units: for unit in group.units:
@ -803,7 +788,7 @@ class AircraftConflictGenerator:
flight: Flight, flight: Flight,
group: FlyingGroup[Any], group: FlyingGroup[Any],
react_on_threat: Optional[OptReactOnThreat.Values] = None, react_on_threat: Optional[OptReactOnThreat.Values] = None,
roe: Optional[OptROE.Values] = None, roe: Optional[int] = None,
rtb_winchester: Optional[OptRTBOnOutOfAmmo.Values] = None, rtb_winchester: Optional[OptRTBOnOutOfAmmo.Values] = None,
restrict_jettison: Optional[bool] = None, restrict_jettison: Optional[bool] = None,
mission_uses_gun: bool = True, mission_uses_gun: bool = True,
@ -1438,7 +1423,7 @@ class CasIngressBuilder(PydcsWaypointBuilder):
if isinstance(self.flight.flight_plan, CasFlightPlan): if isinstance(self.flight.flight_plan, CasFlightPlan):
waypoint.add_task( waypoint.add_task(
EngageTargetsInZone( EngageTargetsInZone(
position=self.flight.flight_plan.target, position=self.flight.flight_plan.target.position,
radius=int(self.flight.flight_plan.engagement_distance.meters), radius=int(self.flight.flight_plan.engagement_distance.meters),
targets=[ targets=[
Targets.All.GroundUnits.GroundVehicles, Targets.All.GroundUnits.GroundVehicles,

View File

@ -6,7 +6,7 @@ from datetime import timedelta
from typing import List, Type, Tuple, Optional, TYPE_CHECKING from typing import List, Type, Tuple, Optional, TYPE_CHECKING
from dcs.mission import Mission, StartType from dcs.mission import Mission, StartType
from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135 from dcs.planes import IL_78M, KC130, KC135MPRS, KC_135, PlaneType
from dcs.task import ( from dcs.task import (
AWACS, AWACS,
ActivateBeaconCommand, ActivateBeaconCommand,
@ -111,6 +111,11 @@ class AirSupportConflictGenerator:
for i, tanker_unit_type in enumerate( for i, tanker_unit_type in enumerate(
self.game.faction_for(player=True).tankers self.game.faction_for(player=True).tankers
): ):
unit_type = tanker_unit_type.dcs_unit_type
if not issubclass(unit_type, PlaneType):
logging.warning(f"Refueling aircraft {unit_type} must be a plane")
continue
# TODO: Make loiter altitude a property of the unit type. # TODO: Make loiter altitude a property of the unit type.
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type) alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type)
freq = self.radio_registry.alloc_uhf() freq = self.radio_registry.alloc_uhf()
@ -130,7 +135,7 @@ class AirSupportConflictGenerator:
self.mission.country(self.game.player_country), tanker_unit_type self.mission.country(self.game.player_country), tanker_unit_type
), ),
airport=None, airport=None,
plane_type=tanker_unit_type.dcs_unit_type, plane_type=unit_type,
position=tanker_position, position=tanker_position,
altitude=alt, altitude=alt,
race_distance=58000, race_distance=58000,
@ -200,12 +205,17 @@ class AirSupportConflictGenerator:
awacs_unit = possible_awacs[0] awacs_unit = possible_awacs[0]
freq = self.radio_registry.alloc_uhf() freq = self.radio_registry.alloc_uhf()
unit_type = awacs_unit.dcs_unit_type
if not issubclass(unit_type, PlaneType):
logging.warning(f"AWACS aircraft {unit_type} must be a plane")
return
awacs_flight = self.mission.awacs_flight( awacs_flight = self.mission.awacs_flight(
country=self.mission.country(self.game.player_country), country=self.mission.country(self.game.player_country),
name=namegen.next_awacs_name( name=namegen.next_awacs_name(
self.mission.country(self.game.player_country) self.mission.country(self.game.player_country)
), ),
plane_type=awacs_unit, plane_type=unit_type,
altitude=AWACS_ALT, altitude=AWACS_ALT,
airport=None, airport=None,
position=self.conflict.position.random_point_within( position=self.conflict.position.random_point_within(

View File

@ -23,7 +23,7 @@ from dcs.task import (
SetInvisibleCommand, SetInvisibleCommand,
) )
from dcs.triggers import Event, TriggerOnce from dcs.triggers import Event, TriggerOnce
from dcs.unit import Vehicle from dcs.unit import Vehicle, Skill
from dcs.unitgroup import VehicleGroup from dcs.unitgroup import VehicleGroup
from game.data.groundunitclass import GroundUnitClass from game.data.groundunitclass import GroundUnitClass
@ -359,7 +359,6 @@ class GroundConflictGenerator:
self.mission.triggerrules.triggers.append(artillery_fallback) self.mission.triggerrules.triggers.append(artillery_fallback)
for u in dcs_group.units: for u in dcs_group.units:
u.initial = True
u.heading = forward_heading + random.randint(-5, 5) u.heading = forward_heading + random.randint(-5, 5)
return True return True
return False return False
@ -568,10 +567,10 @@ class GroundConflictGenerator:
) )
# Fallback task # Fallback task
fallback = ControlledTask(GoToWaypoint(to_index=len(dcs_group.points))) task = ControlledTask(GoToWaypoint(to_index=len(dcs_group.points)))
fallback.enabled = False task.enabled = False
dcs_group.add_trigger_action(Hold()) dcs_group.add_trigger_action(Hold())
dcs_group.add_trigger_action(fallback) dcs_group.add_trigger_action(task)
# Create trigger # Create trigger
fallback = TriggerOnce(Event.NoEvent, "Morale manager #" + str(dcs_group.id)) fallback = TriggerOnce(Event.NoEvent, "Morale manager #" + str(dcs_group.id))
@ -632,7 +631,7 @@ class GroundConflictGenerator:
@param enemy_groups Potential enemy groups @param enemy_groups Potential enemy groups
@param n number of nearby groups to take @param n number of nearby groups to take
""" """
targets = [] # type: List[Optional[VehicleGroup]] targets = [] # type: List[VehicleGroup]
sorted_list = sorted( sorted_list = sorted(
enemy_groups, enemy_groups,
key=lambda group: player_group.points[0].position.distance_to_point( key=lambda group: player_group.points[0].position.distance_to_point(
@ -714,7 +713,7 @@ class GroundConflictGenerator:
distance_from_frontline: int, distance_from_frontline: int,
heading: int, heading: int,
spawn_heading: int, spawn_heading: int,
) -> Point: ) -> Optional[Point]:
shifted = conflict_position.point_from_heading( shifted = conflict_position.point_from_heading(
heading, random.randint(0, combat_width) heading, random.randint(0, combat_width)
) )
@ -764,9 +763,9 @@ class GroundConflictGenerator:
heading=opposite_heading(spawn_heading), heading=opposite_heading(spawn_heading),
) )
if is_player: if is_player:
g.set_skill(self.game.settings.player_skill) g.set_skill(Skill(self.game.settings.player_skill))
else: else:
g.set_skill(self.game.settings.enemy_vehicle_skill) g.set_skill(Skill(self.game.settings.enemy_vehicle_skill))
positioned_groups.append((g, group)) positioned_groups.append((g, group))
if group.role in [CombatGroupRole.APC, CombatGroupRole.IFV]: if group.role in [CombatGroupRole.APC, CombatGroupRole.IFV]:

View File

@ -1,12 +1,13 @@
"""Support for working with DCS group callsigns.""" """Support for working with DCS group callsigns."""
import logging import logging
import re import re
from typing import Any
from dcs.unitgroup import FlyingGroup from dcs.unitgroup import FlyingGroup
from dcs.flyingunit import FlyingUnit from dcs.flyingunit import FlyingUnit
def callsign_for_support_unit(group: FlyingGroup) -> str: def callsign_for_support_unit(group: FlyingGroup[Any]) -> str:
# Either something like Overlord11 for Western AWACS, or else just a number. # Either something like Overlord11 for Western AWACS, or else just a number.
# Convert to either "Overlord" or "Flight 123". # Convert to either "Overlord" or "Flight 123".
lead = group.units[0] lead = group.units[0]

View File

@ -63,6 +63,8 @@ class Conflict:
heading_sum(attack_heading, 90), heading_sum(attack_heading, 90),
theater, theater,
) )
if position is None:
raise RuntimeError("Could not find front line position")
return position, opposite_heading(attack_heading) return position, opposite_heading(attack_heading)
@classmethod @classmethod

View File

@ -22,7 +22,7 @@ class EnvironmentGenerator:
def set_fog(self, fog: Optional[Fog]) -> None: def set_fog(self, fog: Optional[Fog]) -> None:
if fog is None: if fog is None:
return return
self.mission.weather.fog_visibility = fog.visibility.meters self.mission.weather.fog_visibility = int(fog.visibility.meters)
self.mission.weather.fog_thickness = fog.thickness self.mission.weather.fog_thickness = fog.thickness
def set_wind(self, wind: WindConditions) -> None: def set_wind(self, wind: WindConditions) -> None:

View File

@ -1,5 +1,6 @@
import logging import logging
from typing import List, Type from collections import Sequence
from typing import Type
from dcs.helicopters import ( from dcs.helicopters import (
AH_1W, AH_1W,
@ -415,7 +416,7 @@ REFUELING_CAPABALE = [
] ]
def dcs_types_for_task(task: FlightType) -> list[Type[FlyingType]]: def dcs_types_for_task(task: FlightType) -> Sequence[Type[FlyingType]]:
cap_missions = (FlightType.BARCAP, FlightType.TARCAP, FlightType.SWEEP) cap_missions = (FlightType.BARCAP, FlightType.TARCAP, FlightType.SWEEP)
if task in cap_missions: if task in cap_missions:
return CAP_CAPABLE return CAP_CAPABLE

View File

@ -2,7 +2,7 @@ from __future__ import annotations
from datetime import timedelta from datetime import timedelta
from enum import Enum from enum import Enum
from typing import List, Optional, TYPE_CHECKING, Union from typing import List, Optional, TYPE_CHECKING, Union, Sequence
from dcs.mapping import Point from dcs.mapping import Point
from dcs.point import MovingPoint, PointAction from dcs.point import MovingPoint, PointAction
@ -153,7 +153,7 @@ class FlightWaypoint:
# Only used in the waypoint list in the flight edit page. No sense # Only used in the waypoint list in the flight edit page. No sense
# having three names. A short and long form is enough. # having three names. A short and long form is enough.
self.description = "" self.description = ""
self.targets: List[Union[MissionTarget, Unit]] = [] self.targets: Sequence[Union[MissionTarget, Unit]] = []
self.obj_name = "" self.obj_name = ""
self.pretty_name = "" self.pretty_name = ""
self.only_for_player = False self.only_for_player = False

View File

@ -1084,22 +1084,22 @@ class FlightPlanBuilder:
patrol_alt = feet(25000) patrol_alt = feet(25000)
builder = WaypointBuilder(flight, self.game, self.is_player) builder = WaypointBuilder(flight, self.game, self.is_player)
orbit_location = builder.orbit(orbit_location, patrol_alt) orbit = builder.orbit(orbit_location, patrol_alt)
return AwacsFlightPlan( return AwacsFlightPlan(
package=self.package, package=self.package,
flight=flight, flight=flight,
takeoff=builder.takeoff(flight.departure), takeoff=builder.takeoff(flight.departure),
nav_to=builder.nav_path( nav_to=builder.nav_path(
flight.departure.position, orbit_location.position, patrol_alt flight.departure.position, orbit.position, patrol_alt
), ),
nav_from=builder.nav_path( nav_from=builder.nav_path(
orbit_location.position, flight.arrival.position, patrol_alt orbit.position, flight.arrival.position, patrol_alt
), ),
land=builder.land(flight.arrival), land=builder.land(flight.arrival),
divert=builder.divert(flight.divert), divert=builder.divert(flight.divert),
bullseye=builder.bullseye(), bullseye=builder.bullseye(),
hold=orbit_location, hold=orbit,
hold_duration=timedelta(hours=4), hold_duration=timedelta(hours=4),
) )
@ -1167,7 +1167,7 @@ class FlightPlanBuilder:
if isinstance(location, FrontLine): if isinstance(location, FrontLine):
raise InvalidObjectiveLocation(flight.flight_type, location) raise InvalidObjectiveLocation(flight.flight_type, location)
start, end = self.racetrack_for_objective(location, barcap=True) start_pos, end_pos = self.racetrack_for_objective(location, barcap=True)
patrol_alt = meters( patrol_alt = meters(
random.randint( random.randint(
int(self.doctrine.min_patrol_altitude.meters), int(self.doctrine.min_patrol_altitude.meters),
@ -1176,7 +1176,7 @@ class FlightPlanBuilder:
) )
builder = WaypointBuilder(flight, self.game, self.is_player) builder = WaypointBuilder(flight, self.game, self.is_player)
start, end = builder.race_track(start, end, patrol_alt) start, end = builder.race_track(start_pos, end_pos, patrol_alt)
return BarCapFlightPlan( return BarCapFlightPlan(
package=self.package, package=self.package,

View File

@ -15,7 +15,7 @@ from typing import (
from dcs.mapping import Point from dcs.mapping import Point
from dcs.unit import Unit from dcs.unit import Unit
from dcs.unitgroup import Group, VehicleGroup from dcs.unitgroup import Group, VehicleGroup, ShipGroup
if TYPE_CHECKING: if TYPE_CHECKING:
from game import Game from game import Game
@ -35,7 +35,7 @@ from .flight import Flight, FlightWaypoint, FlightWaypointType
class StrikeTarget: class StrikeTarget:
name: str name: str
target: Union[ target: Union[
VehicleGroup, TheaterGroundObject[Any], Unit, Group, MultiGroupTransport VehicleGroup, TheaterGroundObject[Any], Unit, ShipGroup, MultiGroupTransport
] ]
@ -444,7 +444,7 @@ class WaypointBuilder:
# description in gen.aircraft.JoinPointBuilder), so instead we give # description in gen.aircraft.JoinPointBuilder), so instead we give
# the escort flights a flight plan including the ingress point, target # the escort flights a flight plan including the ingress point, target
# area, and egress point. # area, and egress point.
ingress = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target) ingress_wp = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target)
waypoint = FlightWaypoint( waypoint = FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC, FlightWaypointType.TARGET_GROUP_LOC,
@ -458,8 +458,8 @@ class WaypointBuilder:
waypoint.description = "Escort the package" waypoint.description = "Escort the package"
waypoint.pretty_name = "Target area" waypoint.pretty_name = "Target area"
egress = self.egress(egress, target) egress_wp = self.egress(egress, target)
return ingress, waypoint, egress return ingress_wp, waypoint, egress_wp
@staticmethod @staticmethod
def pickup(control_point: ControlPoint) -> FlightWaypoint: def pickup(control_point: ControlPoint) -> FlightWaypoint:

View File

@ -19,12 +19,13 @@ from typing import (
TypeVar, TypeVar,
Any, Any,
Generic, Generic,
Union,
) )
from dcs import Mission, Point, unitgroup from dcs import Mission, Point, unitgroup
from dcs.action import SceneryDestructionZone from dcs.action import SceneryDestructionZone
from dcs.country import Country from dcs.country import Country
from dcs.point import StaticPoint from dcs.point import StaticPoint, MovingPoint
from dcs.statics import Fortification, fortification_map, warehouse_map from dcs.statics import Fortification, fortification_map, warehouse_map
from dcs.task import ( from dcs.task import (
ActivateBeaconCommand, ActivateBeaconCommand,
@ -36,12 +37,12 @@ from dcs.task import (
from dcs.triggers import TriggerStart, TriggerZone from dcs.triggers import TriggerStart, TriggerZone
from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad
from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup
from dcs.unittype import StaticType, UnitType from dcs.unittype import StaticType, UnitType, ShipType, VehicleType
from dcs.vehicles import vehicle_map from dcs.vehicles import vehicle_map
from game import db from game import db
from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID
from game.db import unit_type_from_name from game.db import unit_type_from_name, ship_type_from_name, vehicle_type_from_name
from game.theater import ControlPoint, TheaterGroundObject from game.theater import ControlPoint, TheaterGroundObject
from game.theater.theatergroundobject import ( from game.theater.theatergroundobject import (
BuildingGroundObject, BuildingGroundObject,
@ -102,10 +103,7 @@ class GenericGroundObjectGenerator(Generic[TgoT]):
logging.warning(f"Found empty group in {self.ground_object}") logging.warning(f"Found empty group in {self.ground_object}")
continue continue
unit_type = unit_type_from_name(group.units[0].type) unit_type = vehicle_type_from_name(group.units[0].type)
if unit_type is None:
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
vg = self.m.vehicle_group( vg = self.m.vehicle_group(
self.country, self.country,
group.name, group.name,
@ -129,18 +127,21 @@ class GenericGroundObjectGenerator(Generic[TgoT]):
self._register_unit_group(group, vg) self._register_unit_group(group, vg)
@staticmethod @staticmethod
def enable_eplrs(group: Group, unit_type: Type[UnitType]) -> None: def enable_eplrs(group: VehicleGroup, unit_type: Type[VehicleType]) -> None:
if hasattr(unit_type, "eplrs"): if unit_type.eplrs:
if unit_type.eplrs: group.points[0].tasks.append(EPLRS(group.id))
group.points[0].tasks.append(EPLRS(group.id))
def set_alarm_state(self, group: Group) -> None: def set_alarm_state(self, group: Union[ShipGroup, VehicleGroup]) -> None:
if self.game.settings.perf_red_alert_state: if self.game.settings.perf_red_alert_state:
group.points[0].tasks.append(OptAlarmState(2)) group.points[0].tasks.append(OptAlarmState(2))
else: else:
group.points[0].tasks.append(OptAlarmState(1)) group.points[0].tasks.append(OptAlarmState(1))
def _register_unit_group(self, persistence_group: Group, miz_group: Group) -> None: def _register_unit_group(
self,
persistence_group: Union[ShipGroup, VehicleGroup],
miz_group: Union[ShipGroup, VehicleGroup],
) -> None:
self.unit_map.add_ground_object_units( self.unit_map.add_ground_object_units(
self.ground_object, persistence_group, miz_group self.ground_object, persistence_group, miz_group
) )
@ -161,7 +162,7 @@ class MissileSiteGenerator(GenericGroundObjectGenerator[MissileSiteGroundObject]
for group in self.ground_object.groups: for group in self.ground_object.groups:
vg = self.m.find_group(group.name) vg = self.m.find_group(group.name)
if vg is not None: if vg is not None:
targets = self.possible_missile_targets(vg) targets = self.possible_missile_targets()
if targets: if targets:
target = random.choice(targets) target = random.choice(targets)
real_target = target.point_from_heading( real_target = target.point_from_heading(
@ -178,7 +179,7 @@ class MissileSiteGenerator(GenericGroundObjectGenerator[MissileSiteGroundObject]
"Couldn't setup missile site to fire, group was not generated." "Couldn't setup missile site to fire, group was not generated."
) )
def possible_missile_targets(self, vg: Group) -> List[Point]: def possible_missile_targets(self) -> List[Point]:
""" """
Find enemy control points in range Find enemy control points in range
:param vg: Vehicle group we are searching a target for (There is always only oe group right now) :param vg: Vehicle group we are searching a target for (There is always only oe group right now)
@ -187,7 +188,7 @@ class MissileSiteGenerator(GenericGroundObjectGenerator[MissileSiteGroundObject]
targets: List[Point] = [] targets: List[Point] = []
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if cp.captured != self.ground_object.control_point.captured: if cp.captured != self.ground_object.control_point.captured:
distance = cp.position.distance_to_point(vg.position) distance = cp.position.distance_to_point(self.ground_object.position)
if distance < self.missile_site_range: if distance < self.missile_site_range:
targets.append(cp.position) targets.append(cp.position)
return targets return targets
@ -238,7 +239,7 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator[BuildingGroundObject]):
f"{self.ground_object.dcs_identifier} not found in static maps" f"{self.ground_object.dcs_identifier} not found in static maps"
) )
def generate_vehicle_group(self, unit_type: Type[UnitType]) -> None: def generate_vehicle_group(self, unit_type: Type[VehicleType]) -> None:
if not self.ground_object.is_dead: if not self.ground_object.is_dead:
group = self.m.vehicle_group( group = self.m.vehicle_group(
country=self.country, country=self.country,
@ -389,13 +390,12 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
self.add_runway_data(brc or 0, atc, tacan, tacan_callsign, icls) self.add_runway_data(brc or 0, atc, tacan, tacan_callsign, icls)
self._register_unit_group(group, ship_group) self._register_unit_group(group, ship_group)
def get_carrier_type(self, group: Group) -> Type[UnitType]: def get_carrier_type(self, group: ShipGroup) -> Type[ShipType]:
unit_type = unit_type_from_name(group.units[0].type) return ship_type_from_name(group.units[0].type)
if unit_type is None:
raise RuntimeError(f"Unrecognized carrier name: {group.units[0].type}")
return unit_type
def configure_carrier(self, group: Group, atc_channel: RadioFrequency) -> ShipGroup: def configure_carrier(
self, group: ShipGroup, atc_channel: RadioFrequency
) -> ShipGroup:
unit_type = self.get_carrier_type(group) unit_type = self.get_carrier_type(group)
ship_group = self.m.ship_group( ship_group = self.m.ship_group(
@ -487,7 +487,7 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
class CarrierGenerator(GenericCarrierGenerator): class CarrierGenerator(GenericCarrierGenerator):
"""Generator for CV(N) groups.""" """Generator for CV(N) groups."""
def get_carrier_type(self, group: Group) -> UnitType: def get_carrier_type(self, group: ShipGroup) -> Type[ShipType]:
unit_type = super().get_carrier_type(group) unit_type = super().get_carrier_type(group)
if self.game.settings.supercarrier: if self.game.settings.supercarrier:
unit_type = db.upgrade_to_supercarrier(unit_type, self.control_point.name) unit_type = db.upgrade_to_supercarrier(unit_type, self.control_point.name)
@ -542,14 +542,11 @@ class ShipObjectGenerator(GenericGroundObjectGenerator[ShipGroundObject]):
if not group.units: if not group.units:
logging.warning(f"Found empty group in {self.ground_object}") logging.warning(f"Found empty group in {self.ground_object}")
continue continue
self.generate_group(group, ship_type_from_name(group.units[0].type))
unit_type = unit_type_from_name(group.units[0].type) def generate_group(
if unit_type is None: self, group_def: ShipGroup, first_unit_type: Type[ShipType]
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}") ) -> None:
self.generate_group(group, unit_type)
def generate_group(self, group_def: Group, first_unit_type: Type[UnitType]) -> None:
group = self.m.ship_group( group = self.m.ship_group(
self.country, self.country,
group_def.name, group_def.name,

View File

@ -1,9 +1,8 @@
import random import random
import time import time
from typing import List from typing import List, Any
from dcs.country import Country from dcs.country import Country
from dcs.unittype import UnitType as DcsUnitType
from game.dcs.aircrafttype import AircraftType from game.dcs.aircrafttype import AircraftType
from game.dcs.unittype import UnitType from game.dcs.unittype import UnitType
@ -297,7 +296,7 @@ class NameGenerator:
@classmethod @classmethod
def next_unit_name( def next_unit_name(
cls, country: Country, parent_base_id: int, unit_type: UnitType[DcsUnitType] cls, country: Country, parent_base_id: int, unit_type: UnitType[Any]
) -> str: ) -> str:
cls.number += 1 cls.number += 1
return "unit|{}|{}|{}|{}|".format( return "unit|{}|{}|{}|{}|".format(
@ -306,7 +305,7 @@ class NameGenerator:
@classmethod @classmethod
def next_infantry_name( def next_infantry_name(
cls, country: Country, parent_base_id: int, unit_type: UnitType[DcsUnitType] cls, country: Country, parent_base_id: int, unit_type: UnitType[Any]
) -> str: ) -> str:
cls.infantry_number += 1 cls.infantry_number += 1
return "infantry|{}|{}|{}|{}|".format( return "infantry|{}|{}|{}|{}|".format(

View File

@ -157,7 +157,7 @@ class ShipGroupGenerator(
super().__init__( super().__init__(
game, game,
ground_object, ground_object,
unitgroup.ShipGroup(self.game.next_group_id(), self.go.group_name), unitgroup.ShipGroup(game.next_group_id(), ground_object.group_name),
) )
self.faction = faction self.faction = faction
wp = self.vg.add_waypoint(self.position, 0) wp = self.vg.add_waypoint(self.position, 0)

View File

@ -83,7 +83,12 @@ class TriggersGenerator:
for cp in self.game.theater.controlpoints: for cp in self.game.theater.controlpoints:
if isinstance(cp, Airfield): if isinstance(cp, Airfield):
self.mission.terrain.airport_by_id(cp.at.id).set_coalition( cp_airport = self.mission.terrain.airport_by_id(cp.airport.id)
if cp_airport is None:
raise RuntimeError(
f"Could not find {cp.airport.name} in the mission"
)
cp_airport.set_coalition(
cp.captured and player_coalition or enemy_coalition cp.captured and player_coalition or enemy_coalition
) )