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
from collections import defaultdict
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.weapons_data import Weapons, weapon_ids
from game.dcs.aircrafttype import AircraftType
PydcsWeapon = Dict[str, Any]
PydcsWeapon = Any
PydcsWeaponAssignment = Tuple[int, PydcsWeapon]
@ -83,7 +83,7 @@ class Pylon:
# configuration.
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):
logging.error(f"Pylon {self.number} cannot equip {weapon.name}")
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.unit import Ship
from dcs.unitgroup import ShipGroup, StaticGroup
from dcs.unittype import UnitType
from dcs.unittype import UnitType, FlyingType, ShipType, VehicleType
from dcs.vehicles import (
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)
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
}
@ -329,7 +329,7 @@ REWARDS = {
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 name == "CVN-71 Theodore Roosevelt":
return CVN_71
@ -362,6 +362,14 @@ def unit_type_from_name(name: str) -> Optional[Type[UnitType]]:
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:
for k, v in country_dict.items():
if v.name == name:

View File

@ -105,6 +105,7 @@ class PatrolConfig:
)
# TODO: Split into PlaneType and HelicopterType?
@dataclass(frozen=True)
class AircraftType(UnitType[Type[FlyingType]]):
carrier_capable: bool
@ -144,12 +145,23 @@ class AircraftType(UnitType[Type[FlyingType]]):
return kph(self.dcs_unit_type.max_speed)
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:
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:
radio_registry.reserve(freq)
except ChannelInUseError:

View File

@ -78,8 +78,8 @@ class GroundLosses:
player_airlifts: List[AirliftUnits] = field(default_factory=list)
enemy_airlifts: List[AirliftUnits] = field(default_factory=list)
player_ground_objects: List[GroundObjectUnit] = field(default_factory=list)
enemy_ground_objects: List[GroundObjectUnit] = field(default_factory=list)
player_ground_objects: List[GroundObjectUnit[Any]] = field(default_factory=list)
enemy_ground_objects: List[GroundObjectUnit[Any]] = field(default_factory=list)
player_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
@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.enemy_ground_objects

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from dcs import Point
@ -7,7 +9,7 @@ class PointWithHeading(Point):
self.heading = 0
@staticmethod
def from_point(point: Point, heading: int) -> Point:
def from_point(point: Point, heading: int) -> PointWithHeading:
p = PointWithHeading()
p.x = point.x
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.unitgroup import (
FlyingGroup,
Group,
ShipGroup,
StaticGroup,
VehicleGroup,
PlaneGroup,
)
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from pyproj import CRS, Transformer
@ -57,6 +56,7 @@ from .landmap import Landmap, load_landmap, poly_contains
from .latlon import LatLon
from .projections import TransverseMercator
from ..point_with_heading import PointWithHeading
from ..positioned import Positioned
from ..profiling import logged_duration
from ..scenery_group import SceneryGroup
from ..utils import Distance, meters
@ -185,7 +185,7 @@ class MizCampaignLoader:
def red(self) -> Country:
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:
if group.units[0].type == self.OFF_MAP_UNIT_TYPE:
yield group
@ -309,26 +309,26 @@ class MizCampaignLoader:
control_point.captured = blue
control_point.captured_invert = group.late_activation
control_points[control_point.id] = control_point
for group in self.carriers(blue):
for ship in self.carriers(blue):
# TODO: Name the 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_invert = group.late_activation
control_point.captured_invert = ship.late_activation
control_points[control_point.id] = control_point
for group in self.lhas(blue):
for ship in self.lhas(blue):
# 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_invert = group.late_activation
control_point.captured_invert = ship.late_activation
control_points[control_point.id] = control_point
for group in self.fobs(blue):
for fob in self.fobs(blue):
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_invert = group.late_activation
control_point.captured_invert = fob.late_activation
control_points[control_point.id] = control_point
return control_points
@ -389,22 +389,22 @@ class MizCampaignLoader:
origin, list(reversed(waypoints))
)
def objective_info(self, group: Group) -> Tuple[ControlPoint, Distance]:
closest = self.theater.closest_control_point(group.position)
distance = meters(closest.position.distance_to_point(group.position))
def objective_info(self, near: Positioned) -> Tuple[ControlPoint, Distance]:
closest = self.theater.closest_control_point(near.position)
distance = meters(closest.position.distance_to_point(near.position))
return closest, distance
def add_preset_locations(self) -> None:
for group in self.offshore_strike_targets:
closest, distance = self.objective_info(group)
for static in self.offshore_strike_targets:
closest, distance = self.objective_info(static)
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:
closest, distance = self.objective_info(group)
for ship in self.ships:
closest, distance = self.objective_info(ship)
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:
@ -455,33 +455,33 @@ class MizCampaignLoader:
PointWithHeading.from_point(group.position, group.units[0].heading)
)
for group in self.helipads:
closest, distance = self.objective_info(group)
for static in self.helipads:
closest, distance = self.objective_info(static)
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:
closest, distance = self.objective_info(group)
for static in self.factories:
closest, distance = self.objective_info(static)
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:
closest, distance = self.objective_info(group)
for static in self.ammunition_depots:
closest, distance = self.objective_info(static)
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:
closest, distance = self.objective_info(group)
for static in self.strike_targets:
closest, distance = self.objective_info(static)
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:
closest, distance = self.objective_info(group)
closest.preset_locations.scenery.append(group)
for scenery_group in self.scenery:
closest, distance = self.objective_info(scenery_group)
closest.preset_locations.scenery.append(scenery_group)
def populate_theater(self) -> None:
for control_point in self.control_points.values():
@ -587,12 +587,12 @@ class ConflictTheater:
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
`extend_dist` determines how far inside the zone the point should be placed"""
if self.is_on_land(point):
return point
point = geometry.Point(point.x, point.y)
if self.is_on_land(near):
return near
point = geometry.Point(near.x, near.y)
nearest_points = []
if not self.landmap:
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"])
@property
def strike_targets(self) -> List[Union[MissionTarget, Unit]]:
def strike_targets(self) -> Sequence[Union[MissionTarget, Unit]]:
return []
@property

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import logging
from dataclasses import dataclass
from typing import Iterator, List, Tuple
from typing import Iterator, List, Tuple, Any
from dcs.mapping import Point
@ -66,7 +66,15 @@ class FrontLine(MissionTarget):
self.segments: List[FrontLineSegment] = [
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:
if player:
@ -87,14 +95,6 @@ class FrontLine(MissionTarget):
]
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
def points(self) -> Iterator[Point]:
yield self.segments[0].point_a
@ -149,6 +149,9 @@ class FrontLine(MissionTarget):
)
else:
remaining_dist -= segment.attack_distance
raise RuntimeError(
f"Could not find front line point {distance} from {self.blue_cp}"
)
@property
def _position_distance(self) -> float:

View File

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

View File

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

View File

@ -5,8 +5,6 @@ from collections import defaultdict
from dataclasses import dataclass
from typing import Optional, TYPE_CHECKING, Any
from dcs.unittype import UnitType as DcsUnitType
from game.theater import ControlPoint
from .dcs.groundunittype import GroundUnitType
from .dcs.unittype import UnitType
@ -48,27 +46,27 @@ class PendingUnitDeliveries:
self.units = defaultdict(int)
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)
}
self.refund(game, ground_units)
for gu in ground_units.keys():
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():
logging.info(f"Refunding {count} {unit_type} at {self.destination.name}")
game.adjust_budget(
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)
if pending_units is None:
pending_units = 0
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)
return self.pending_orders(unit_type) + current_units
@ -81,9 +79,9 @@ class PendingUnitDeliveries:
)
self.refund_ground_units(game)
bought_units: dict[UnitType[DcsUnitType], int] = {}
bought_units: dict[UnitType[Any], 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():
coalition = "Ally" if self.destination.captured else "Enemy"
d: dict[Any, int]

View File

@ -2,10 +2,10 @@
import itertools
import math
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.unitgroup import FlyingGroup, Group, VehicleGroup
from dcs.unit import Vehicle, Ship
from dcs.unitgroup import FlyingGroup, VehicleGroup, StaticGroup, ShipGroup, MovingGroup
from game.dcs.groundunittype import GroundUnitType
from game.squadrons import Pilot
@ -27,11 +27,14 @@ class FrontLineUnit:
origin: ControlPoint
UnitT = TypeVar("UnitT", Ship, Vehicle)
@dataclass(frozen=True)
class GroundObjectUnit:
class GroundObjectUnit(Generic[UnitT]):
ground_object: TheaterGroundObject[Any]
group: Group
unit: Unit
group: MovingGroup[UnitT]
unit: UnitT
@dataclass(frozen=True)
@ -56,13 +59,13 @@ class UnitMap:
self.aircraft: Dict[str, FlyingUnit] = {}
self.airfields: Dict[str, Airfield] = {}
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.convoys: Dict[str, ConvoyUnit] = {}
self.cargo_ships: Dict[str, CargoShip] = {}
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):
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
@ -85,7 +88,7 @@ class UnitMap:
return self.airfields.get(name, None)
def add_front_line_units(
self, group: Group, origin: ControlPoint, unit_type: GroundUnitType
self, group: VehicleGroup, origin: ControlPoint, unit_type: GroundUnitType
) -> None:
for unit in group.units:
# The actual name is a String (the pydcs translatable string), which
@ -101,8 +104,8 @@ class UnitMap:
def add_ground_object_units(
self,
ground_object: TheaterGroundObject[Any],
persistence_group: Group,
miz_group: Group,
persistence_group: Union[ShipGroup, VehicleGroup],
miz_group: Union[ShipGroup, VehicleGroup],
) -> None:
"""Adds a group associated with a TGO to the unit map.
@ -131,10 +134,10 @@ class UnitMap:
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)
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()):
# The actual name is a String (the pydcs translatable string), which
# doesn't define __eq__.
@ -146,7 +149,7 @@ class UnitMap:
def convoy_unit(self, name: str) -> Optional[ConvoyUnit]:
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:
# 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
@ -163,7 +166,9 @@ class UnitMap:
def cargo_ship(self, name: str) -> Optional[CargoShip]:
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))
for idx, transport in enumerate(group.units):
# 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]:
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
# doesn't define __eq__.
# The name of the initiator in the DCS dead event will have " object"

View File

@ -321,7 +321,7 @@ class AircraftConflictGenerator:
@staticmethod
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]:
faction = self.game.faction_for(player=flight.departure.captured)
@ -342,7 +342,7 @@ class AircraftConflictGenerator:
return livery
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)
if livery is None:
return
@ -458,8 +458,8 @@ class AircraftConflictGenerator:
unit_type: Type[FlyingType],
count: int,
start_type: str,
airport: Optional[Airport] = None,
) -> FlyingGroup:
airport: Airport,
) -> FlyingGroup[Any]:
assert count > 0
logging.info("airgen: {} for {} at {}".format(unit_type, side.id, airport))
@ -476,7 +476,7 @@ class AircraftConflictGenerator:
def _generate_inflight(
self, name: str, side: Country, flight: Flight, origin: ControlPoint
) -> FlyingGroup:
) -> FlyingGroup[Any]:
assert flight.count > 0
at = origin.position
@ -521,7 +521,7 @@ class AircraftConflictGenerator:
count: int,
start_type: str,
at: Union[ShipGroup, StaticGroup],
) -> FlyingGroup:
) -> FlyingGroup[Any]:
assert count > 0
logging.info("airgen: {} for {} at unit {}".format(unit_type, side.id, at))
@ -546,27 +546,6 @@ class AircraftConflictGenerator:
point.alt_type = "RADIO"
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
def _at_position(at: Union[Point, ShipGroup, Type[Airport]]) -> Point:
if isinstance(at, Point):
@ -578,7 +557,7 @@ class AircraftConflictGenerator:
else:
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:
p.pylons.clear()
@ -729,7 +708,7 @@ class AircraftConflictGenerator:
def generate_planned_flight(
self, cp: ControlPoint, country: Country, flight: Flight
) -> FlyingGroup:
) -> FlyingGroup[Any]:
name = namegen.next_aircraft_name(country, cp.id, flight)
try:
if flight.start_type == "In Flight":
@ -738,13 +717,19 @@ class AircraftConflictGenerator:
)
elif isinstance(cp, NavalControlPoint):
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(
name=name,
side=country,
unit_type=flight.unit_type.dcs_unit_type,
count=flight.count,
start_type=flight.start_type,
at=self.m.find_group(group_name),
at=carrier_group,
)
else:
if not isinstance(cp, Airfield):
@ -775,7 +760,7 @@ class AircraftConflictGenerator:
@staticmethod
def set_reduced_fuel(
flight: Flight, group: FlyingGroup[Any], unit_type: Type[PlaneType]
flight: Flight, group: FlyingGroup[Any], unit_type: Type[FlyingType]
) -> None:
if unit_type is Su_33:
for unit in group.units:
@ -803,7 +788,7 @@ class AircraftConflictGenerator:
flight: Flight,
group: FlyingGroup[Any],
react_on_threat: Optional[OptReactOnThreat.Values] = None,
roe: Optional[OptROE.Values] = None,
roe: Optional[int] = None,
rtb_winchester: Optional[OptRTBOnOutOfAmmo.Values] = None,
restrict_jettison: Optional[bool] = None,
mission_uses_gun: bool = True,
@ -1438,7 +1423,7 @@ class CasIngressBuilder(PydcsWaypointBuilder):
if isinstance(self.flight.flight_plan, CasFlightPlan):
waypoint.add_task(
EngageTargetsInZone(
position=self.flight.flight_plan.target,
position=self.flight.flight_plan.target.position,
radius=int(self.flight.flight_plan.engagement_distance.meters),
targets=[
Targets.All.GroundUnits.GroundVehicles,

View File

@ -6,7 +6,7 @@ from datetime import timedelta
from typing import List, Type, Tuple, Optional, TYPE_CHECKING
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 (
AWACS,
ActivateBeaconCommand,
@ -111,6 +111,11 @@ class AirSupportConflictGenerator:
for i, tanker_unit_type in enumerate(
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.
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type)
freq = self.radio_registry.alloc_uhf()
@ -130,7 +135,7 @@ class AirSupportConflictGenerator:
self.mission.country(self.game.player_country), tanker_unit_type
),
airport=None,
plane_type=tanker_unit_type.dcs_unit_type,
plane_type=unit_type,
position=tanker_position,
altitude=alt,
race_distance=58000,
@ -200,12 +205,17 @@ class AirSupportConflictGenerator:
awacs_unit = possible_awacs[0]
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(
country=self.mission.country(self.game.player_country),
name=namegen.next_awacs_name(
self.mission.country(self.game.player_country)
),
plane_type=awacs_unit,
plane_type=unit_type,
altitude=AWACS_ALT,
airport=None,
position=self.conflict.position.random_point_within(

View File

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

View File

@ -1,12 +1,13 @@
"""Support for working with DCS group callsigns."""
import logging
import re
from typing import Any
from dcs.unitgroup import FlyingGroup
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.
# Convert to either "Overlord" or "Flight 123".
lead = group.units[0]

View File

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

View File

@ -22,7 +22,7 @@ class EnvironmentGenerator:
def set_fog(self, fog: Optional[Fog]) -> None:
if fog is None:
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
def set_wind(self, wind: WindConditions) -> None:

View File

@ -1,5 +1,6 @@
import logging
from typing import List, Type
from collections import Sequence
from typing import Type
from dcs.helicopters import (
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)
if task in cap_missions:
return CAP_CAPABLE

View File

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

View File

@ -1084,22 +1084,22 @@ class FlightPlanBuilder:
patrol_alt = feet(25000)
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(
package=self.package,
flight=flight,
takeoff=builder.takeoff(flight.departure),
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(
orbit_location.position, flight.arrival.position, patrol_alt
orbit.position, flight.arrival.position, patrol_alt
),
land=builder.land(flight.arrival),
divert=builder.divert(flight.divert),
bullseye=builder.bullseye(),
hold=orbit_location,
hold=orbit,
hold_duration=timedelta(hours=4),
)
@ -1167,7 +1167,7 @@ class FlightPlanBuilder:
if isinstance(location, FrontLine):
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(
random.randint(
int(self.doctrine.min_patrol_altitude.meters),
@ -1176,7 +1176,7 @@ class FlightPlanBuilder:
)
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(
package=self.package,

View File

@ -15,7 +15,7 @@ from typing import (
from dcs.mapping import Point
from dcs.unit import Unit
from dcs.unitgroup import Group, VehicleGroup
from dcs.unitgroup import Group, VehicleGroup, ShipGroup
if TYPE_CHECKING:
from game import Game
@ -35,7 +35,7 @@ from .flight import Flight, FlightWaypoint, FlightWaypointType
class StrikeTarget:
name: str
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
# the escort flights a flight plan including the ingress point, target
# area, and egress point.
ingress = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target)
ingress_wp = self.ingress(FlightWaypointType.INGRESS_ESCORT, ingress, target)
waypoint = FlightWaypoint(
FlightWaypointType.TARGET_GROUP_LOC,
@ -458,8 +458,8 @@ class WaypointBuilder:
waypoint.description = "Escort the package"
waypoint.pretty_name = "Target area"
egress = self.egress(egress, target)
return ingress, waypoint, egress
egress_wp = self.egress(egress, target)
return ingress_wp, waypoint, egress_wp
@staticmethod
def pickup(control_point: ControlPoint) -> FlightWaypoint:

View File

@ -19,12 +19,13 @@ from typing import (
TypeVar,
Any,
Generic,
Union,
)
from dcs import Mission, Point, unitgroup
from dcs.action import SceneryDestructionZone
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.task import (
ActivateBeaconCommand,
@ -36,12 +37,12 @@ from dcs.task import (
from dcs.triggers import TriggerStart, TriggerZone
from dcs.unit import Ship, Unit, Vehicle, SingleHeliPad
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 game import db
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.theatergroundobject import (
BuildingGroundObject,
@ -102,10 +103,7 @@ class GenericGroundObjectGenerator(Generic[TgoT]):
logging.warning(f"Found empty group in {self.ground_object}")
continue
unit_type = unit_type_from_name(group.units[0].type)
if unit_type is None:
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
unit_type = vehicle_type_from_name(group.units[0].type)
vg = self.m.vehicle_group(
self.country,
group.name,
@ -129,18 +127,21 @@ class GenericGroundObjectGenerator(Generic[TgoT]):
self._register_unit_group(group, vg)
@staticmethod
def enable_eplrs(group: Group, unit_type: Type[UnitType]) -> None:
if hasattr(unit_type, "eplrs"):
if unit_type.eplrs:
group.points[0].tasks.append(EPLRS(group.id))
def enable_eplrs(group: VehicleGroup, unit_type: Type[VehicleType]) -> None:
if unit_type.eplrs:
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:
group.points[0].tasks.append(OptAlarmState(2))
else:
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.ground_object, persistence_group, miz_group
)
@ -161,7 +162,7 @@ class MissileSiteGenerator(GenericGroundObjectGenerator[MissileSiteGroundObject]
for group in self.ground_object.groups:
vg = self.m.find_group(group.name)
if vg is not None:
targets = self.possible_missile_targets(vg)
targets = self.possible_missile_targets()
if targets:
target = random.choice(targets)
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."
)
def possible_missile_targets(self, vg: Group) -> List[Point]:
def possible_missile_targets(self) -> List[Point]:
"""
Find enemy control points in range
: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] = []
for cp in self.game.theater.controlpoints:
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:
targets.append(cp.position)
return targets
@ -238,7 +239,7 @@ class BuildingSiteGenerator(GenericGroundObjectGenerator[BuildingGroundObject]):
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:
group = self.m.vehicle_group(
country=self.country,
@ -389,13 +390,12 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
self.add_runway_data(brc or 0, atc, tacan, tacan_callsign, icls)
self._register_unit_group(group, ship_group)
def get_carrier_type(self, group: Group) -> Type[UnitType]:
unit_type = unit_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 get_carrier_type(self, group: ShipGroup) -> Type[ShipType]:
return ship_type_from_name(group.units[0].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)
ship_group = self.m.ship_group(
@ -487,7 +487,7 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
class CarrierGenerator(GenericCarrierGenerator):
"""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)
if self.game.settings.supercarrier:
unit_type = db.upgrade_to_supercarrier(unit_type, self.control_point.name)
@ -542,14 +542,11 @@ class ShipObjectGenerator(GenericGroundObjectGenerator[ShipGroundObject]):
if not group.units:
logging.warning(f"Found empty group in {self.ground_object}")
continue
self.generate_group(group, ship_type_from_name(group.units[0].type))
unit_type = unit_type_from_name(group.units[0].type)
if unit_type is None:
raise RuntimeError(f"Unrecognized unit type: {group.units[0].type}")
self.generate_group(group, unit_type)
def generate_group(self, group_def: Group, first_unit_type: Type[UnitType]) -> None:
def generate_group(
self, group_def: ShipGroup, first_unit_type: Type[ShipType]
) -> None:
group = self.m.ship_group(
self.country,
group_def.name,

View File

@ -1,9 +1,8 @@
import random
import time
from typing import List
from typing import List, Any
from dcs.country import Country
from dcs.unittype import UnitType as DcsUnitType
from game.dcs.aircrafttype import AircraftType
from game.dcs.unittype import UnitType
@ -297,7 +296,7 @@ class NameGenerator:
@classmethod
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:
cls.number += 1
return "unit|{}|{}|{}|{}|".format(
@ -306,7 +305,7 @@ class NameGenerator:
@classmethod
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:
cls.infantry_number += 1
return "infantry|{}|{}|{}|{}|".format(

View File

@ -157,7 +157,7 @@ class ShipGroupGenerator(
super().__init__(
game,
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
wp = self.vg.add_waypoint(self.position, 0)

View File

@ -83,7 +83,12 @@ class TriggersGenerator:
for cp in self.game.theater.controlpoints:
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
)