From 96c7b87ac7ef9382ffad9c3b5846fd8c0df8b0a2 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 9 Jul 2021 14:13:20 -0700 Subject: [PATCH] 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. --- game/data/weapons.py | 6 +-- game/db.py | 14 +++-- game/dcs/aircrafttype.py | 16 +++++- game/debriefing.py | 6 +-- game/point_with_heading.py | 4 +- game/positioned.py | 9 ++++ game/theater/conflicttheater.py | 80 ++++++++++++++--------------- game/theater/controlpoint.py | 2 +- game/theater/frontline.py | 23 +++++---- game/theater/missiontarget.py | 3 +- game/theater/theatergroundobject.py | 32 +++++------- game/unitdelivery.py | 14 +++-- game/unitmap.py | 39 ++++++++------ gen/aircraft.py | 51 +++++++----------- gen/airsupportgen.py | 16 ++++-- gen/armor.py | 17 +++--- gen/callsigns.py | 3 +- gen/conflictgen.py | 2 + gen/environmentgen.py | 2 +- gen/flights/ai_flight_planner_db.py | 5 +- gen/flights/flight.py | 4 +- gen/flights/flightplan.py | 12 ++--- gen/flights/waypointbuilder.py | 10 ++-- gen/groundobjectsgen.py | 59 ++++++++++----------- gen/naming.py | 7 ++- gen/sam/group_generator.py | 2 +- gen/triggergen.py | 7 ++- 27 files changed, 238 insertions(+), 207 deletions(-) create mode 100644 game/positioned.py diff --git a/game/data/weapons.py b/game/data/weapons.py index b3e3b059..50042a1b 100644 --- a/game/data/weapons.py +++ b/game/data/weapons.py @@ -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 == "" - 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) diff --git a/game/db.py b/game/db.py index c5554ac8..2504d4f3 100644 --- a/game/db.py +++ b/game/db.py @@ -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: diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index a851abfd..dd9b5282 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -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: diff --git a/game/debriefing.py b/game/debriefing.py index 21927d8e..e4e3bf0f 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -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 diff --git a/game/point_with_heading.py b/game/point_with_heading.py index 69d62e9c..a87914a1 100644 --- a/game/point_with_heading.py +++ b/game/point_with_heading.py @@ -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 diff --git a/game/positioned.py b/game/positioned.py new file mode 100644 index 00000000..09952351 --- /dev/null +++ b/game/positioned.py @@ -0,0 +1,9 @@ +from typing import Protocol + +from dcs import Point + + +class Positioned(Protocol): + @property + def position(self) -> Point: + raise NotImplementedError diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 29400a16..e0f4d69a 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -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") diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index ea5d981a..ecf77341 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -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 diff --git a/game/theater/frontline.py b/game/theater/frontline.py index 7002913f..c8f2fd6b 100644 --- a/game/theater/frontline.py +++ b/game/theater/frontline.py @@ -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: diff --git a/game/theater/missiontarget.py b/game/theater/missiontarget.py index ea426603..813172fa 100644 --- a/game/theater/missiontarget.py +++ b/game/theater/missiontarget.py @@ -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 [] diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index a49b63ab..d9aab7ea 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -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: diff --git a/game/unitdelivery.py b/game/unitdelivery.py index dfbc2409..ff7841c6 100644 --- a/game/unitdelivery.py +++ b/game/unitdelivery.py @@ -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] diff --git a/game/unitmap.py b/game/unitmap.py index a0485a9c..a1d5c110 100644 --- a/game/unitmap.py +++ b/game/unitmap.py @@ -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" diff --git a/gen/aircraft.py b/gen/aircraft.py index 3fa1b94d..c0caa0a0 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -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, diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 3bb95d26..37fc30bb 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -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( diff --git a/gen/armor.py b/gen/armor.py index 5fc691d8..2777e585 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -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]: diff --git a/gen/callsigns.py b/gen/callsigns.py index 8ebda467..a722606f 100644 --- a/gen/callsigns.py +++ b/gen/callsigns.py @@ -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] diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 723898cc..5576805a 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -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 diff --git a/gen/environmentgen.py b/gen/environmentgen.py index 65e053ed..cd4d09ff 100644 --- a/gen/environmentgen.py +++ b/gen/environmentgen.py @@ -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: diff --git a/gen/flights/ai_flight_planner_db.py b/gen/flights/ai_flight_planner_db.py index 6ed26bd4..9569cfaa 100644 --- a/gen/flights/ai_flight_planner_db.py +++ b/gen/flights/ai_flight_planner_db.py @@ -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 diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 855fad95..05ee4c19 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -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 diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index d7ad8b9e..afc8657f 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -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, diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index f861b4b8..e911bebd 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -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: diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index e14544e2..ad6d0262 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -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, diff --git a/gen/naming.py b/gen/naming.py index b342ecaa..e43d629c 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -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( diff --git a/gen/sam/group_generator.py b/gen/sam/group_generator.py index 6a286888..2fb800f8 100644 --- a/gen/sam/group_generator.py +++ b/gen/sam/group_generator.py @@ -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) diff --git a/gen/triggergen.py b/gen/triggergen.py index 6616456d..2a70d204 100644 --- a/gen/triggergen.py +++ b/gen/triggergen.py @@ -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 )