From 4e9d661c0cc9200228ecd59078ca641aaed490b2 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Wed, 7 Jul 2021 17:41:29 -0700 Subject: [PATCH] Flesh out typing information, enforce. (cherry picked from commit fb9a0fe833198d41a911f22d5efc4e3c4829c316) --- game/db.py | 5 +- game/debriefing.py | 14 +-- game/event/airwar.py | 6 +- game/event/event.py | 6 +- game/event/frontlineattack.py | 2 +- game/factions/faction.py | 15 +-- game/game.py | 81 +++++++--------- game/infos/information.py | 4 +- game/models/destroyed_units.py | 13 --- game/models/game_stats.py | 15 ++- game/operation/operation.py | 17 ++-- game/persistency.py | 14 ++- game/plugins/luaplugin.py | 4 +- game/point_with_heading.py | 4 +- game/profiling.py | 10 +- game/settings.py | 4 +- game/squadrons.py | 5 +- game/theater/base.py | 14 +-- game/theater/conflicttheater.py | 13 ++- game/theater/controlpoint.py | 36 ++++---- game/theater/frontline.py | 6 +- game/theater/landmap.py | 12 +-- game/theater/theatergroundobject.py | 4 +- game/threatzones.py | 21 ++++- game/transfers.py | 8 +- game/utils.py | 9 +- game/weather.py | 2 +- gen/aircraft.py | 20 +++- gen/airsupportgen.py | 17 ++-- gen/armor.py | 18 +--- gen/coastal/coastal_group_generator.py | 14 ++- gen/coastal/silkworm.py | 12 ++- gen/conflictgen.py | 8 +- gen/defenses/armor_group_generator.py | 5 +- gen/defenses/armored_group_generator.py | 8 +- gen/environmentgen.py | 2 +- gen/fleet/carrier_group.py | 2 +- gen/fleet/cn_dd_group.py | 2 +- gen/fleet/dd_group.py | 2 +- gen/fleet/lacombattanteII.py | 5 +- gen/fleet/lha_group.py | 2 +- gen/fleet/ru_dd_group.py | 2 +- gen/fleet/schnellboot.py | 2 +- gen/fleet/ship_group_generator.py | 47 +++++++--- gen/fleet/uboat.py | 2 +- gen/fleet/ww2lst.py | 2 +- gen/flights/ai_flight_planner.py | 2 +- gen/flights/flight.py | 5 +- gen/forcedoptionsgen.py | 2 +- gen/ground_forces/ai_ground_planner.py | 13 ++- gen/groundobjectsgen.py | 2 +- gen/kneeboard.py | 5 +- .../preset_control_point_locations.py | 22 ----- gen/locations/preset_locations.py | 21 ----- gen/missiles/missiles_group_generator.py | 11 ++- gen/missiles/scud_site.py | 12 ++- gen/missiles/v1_group.py | 12 ++- gen/naming.py | 16 ++-- gen/radios.py | 2 +- gen/sam/aaa_bofors.py | 2 +- gen/sam/aaa_flak.py | 2 +- gen/sam/aaa_flak18.py | 2 +- gen/sam/aaa_ks19.py | 2 +- gen/sam/aaa_ww2_ally_flak.py | 2 +- gen/sam/aaa_zsu57.py | 2 +- gen/sam/aaa_zu23_insurgent.py | 2 +- gen/sam/airdefensegroupgenerator.py | 2 +- gen/sam/cold_war_flak.py | 4 +- gen/sam/ewrs.py | 3 +- gen/sam/freya_ewr.py | 2 +- gen/sam/group_generator.py | 49 ++++++---- gen/sam/sam_avenger.py | 2 +- gen/sam/sam_chaparral.py | 2 +- gen/sam/sam_gepard.py | 2 +- gen/sam/sam_hawk.py | 2 +- gen/sam/sam_hq7.py | 2 +- gen/sam/sam_linebacker.py | 2 +- gen/sam/sam_patriot.py | 2 +- gen/sam/sam_rapier.py | 2 +- gen/sam/sam_roland.py | 2 +- gen/sam/sam_sa10.py | 2 +- gen/sam/sam_sa11.py | 2 +- gen/sam/sam_sa13.py | 2 +- gen/sam/sam_sa15.py | 2 +- gen/sam/sam_sa17.py | 2 +- gen/sam/sam_sa19.py | 2 +- gen/sam/sam_sa2.py | 2 +- gen/sam/sam_sa3.py | 2 +- gen/sam/sam_sa6.py | 2 +- gen/sam/sam_sa8.py | 2 +- gen/sam/sam_sa9.py | 2 +- gen/sam/sam_vulcan.py | 2 +- gen/sam/sam_zsu23.py | 2 +- gen/sam/sam_zu23.py | 2 +- gen/sam/sam_zu23_ural.py | 2 +- gen/sam/sam_zu23_ural_insurgent.py | 10 +- gen/triggergen.py | 12 +-- gen/visualgen.py | 92 ++----------------- mypy.ini | 2 +- 99 files changed, 426 insertions(+), 453 deletions(-) delete mode 100644 game/models/destroyed_units.py delete mode 100644 gen/locations/preset_control_point_locations.py delete mode 100644 gen/locations/preset_locations.py diff --git a/game/db.py b/game/db.py index 61426d12..2d3d0c01 100644 --- a/game/db.py +++ b/game/db.py @@ -29,6 +29,7 @@ from dcs.ships import ( CV_1143_5, ) from dcs.terrain.terrain import Airport +from dcs.unit import Ship from dcs.unitgroup import ShipGroup, StaticGroup from dcs.unittype import UnitType from dcs.vehicles import ( @@ -326,7 +327,7 @@ REWARDS = { StartingPosition = Union[ShipGroup, StaticGroup, Airport, Point] -def upgrade_to_supercarrier(unit, name: str): +def upgrade_to_supercarrier(unit: Type[Ship], name: str) -> Type[Ship]: if unit == Stennis: if name == "CVN-71 Theodore Roosevelt": return CVN_71 @@ -359,7 +360,7 @@ def unit_type_from_name(name: str) -> Optional[Type[UnitType]]: return None -def country_id_from_name(name): +def country_id_from_name(name: str) -> int: for k, v in country_dict.items(): if v.name == name: return k diff --git a/game/debriefing.py b/game/debriefing.py index 212ea7a4..21927d8e 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -15,6 +15,7 @@ from typing import ( Iterator, List, TYPE_CHECKING, + Union, ) from game import db @@ -104,8 +105,9 @@ class StateData: #: Names of vehicle (and ship) units that were killed during the mission. killed_ground_units: List[str] - #: Names of static units that were destroyed during the mission. - destroyed_statics: List[str] + #: List of descriptions of destroyed statics. Format of each element is a mapping of + #: the coordinate type ("x", "y", "z", "type", "orientation") to the value. + destroyed_statics: List[dict[str, Union[float, str]]] #: Mangled names of bases that were captured during the mission. base_capture_events: List[str] @@ -370,13 +372,13 @@ class PollDebriefingFileThread(threading.Thread): self.game = game self.unit_map = unit_map - def stop(self): + def stop(self) -> None: self._stop_event.set() - def stopped(self): + def stopped(self) -> bool: return self._stop_event.is_set() - def run(self): + def run(self) -> None: if os.path.isfile("state.json"): last_modified = os.path.getmtime("state.json") else: @@ -401,7 +403,7 @@ class PollDebriefingFileThread(threading.Thread): def wait_for_debriefing( - callback: Callable[[Debriefing], None], game: Game, unit_map + callback: Callable[[Debriefing], None], game: Game, unit_map: UnitMap ) -> PollDebriefingFileThread: thread = PollDebriefingFileThread(callback, game, unit_map) thread.start() diff --git a/game/event/airwar.py b/game/event/airwar.py index ed22f3af..7b860a1b 100644 --- a/game/event/airwar.py +++ b/game/event/airwar.py @@ -1,14 +1,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING from .event import Event -if TYPE_CHECKING: - from game.theater import ConflictTheater - class AirWarEvent(Event): """Event handler for the air battle""" - def __str__(self): + def __str__(self) -> str: return "AirWar" diff --git a/game/event/event.py b/game/event/event.py index 1d554fca..daac7f27 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -38,13 +38,13 @@ class Event: def __init__( self, - game, + game: Game, from_cp: ControlPoint, target_cp: ControlPoint, location: Point, attacker_name: str, defender_name: str, - ): + ) -> None: self.game = game self.from_cp = from_cp self.to_cp = target_cp @@ -265,7 +265,7 @@ class Event: except Exception: logging.exception(f"Could not process base capture {captured}") - def commit(self, debriefing: Debriefing): + def commit(self, debriefing: Debriefing) -> None: logging.info("Committing mission results") self.commit_air_losses(debriefing) diff --git a/game/event/frontlineattack.py b/game/event/frontlineattack.py index d7749a2a..fefb3617 100644 --- a/game/event/frontlineattack.py +++ b/game/event/frontlineattack.py @@ -8,5 +8,5 @@ class FrontlineAttackEvent(Event): future unique Event handling """ - def __str__(self): + def __str__(self) -> str: return "Frontline attack" diff --git a/game/factions/faction.py b/game/factions/faction.py index 83a8f0fa..2a0156c2 100644 --- a/game/factions/faction.py +++ b/game/factions/faction.py @@ -3,7 +3,7 @@ from __future__ import annotations import itertools import logging from dataclasses import dataclass, field -from typing import Optional, Dict, Type, List, Any, Iterator +from typing import Optional, Dict, Type, List, Any, Iterator, TYPE_CHECKING import dcs from dcs.countries import country_dict @@ -25,6 +25,9 @@ from game.data.groundunitclass import GroundUnitClass from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType +if TYPE_CHECKING: + from game.theater.start_generator import ModSettings + @dataclass class Faction: @@ -257,7 +260,7 @@ class Faction: if unit.unit_class is unit_class: yield unit - def apply_mod_settings(self, mod_settings) -> Faction: + def apply_mod_settings(self, mod_settings: ModSettings) -> Faction: # aircraft if not mod_settings.a4_skyhawk: self.remove_aircraft("A-4E-C") @@ -319,17 +322,17 @@ class Faction: self.remove_air_defenses("KS19Generator") return self - def remove_aircraft(self, name): + def remove_aircraft(self, name: str) -> None: for i in self.aircrafts: if i.dcs_unit_type.id == name: self.aircrafts.remove(i) - def remove_air_defenses(self, name): + def remove_air_defenses(self, name: str) -> None: for i in self.air_defenses: if i == name: self.air_defenses.remove(i) - def remove_vehicle(self, name): + def remove_vehicle(self, name: str) -> None: for i in self.frontline_units: if i.dcs_unit_type.id == name: self.frontline_units.remove(i) @@ -342,7 +345,7 @@ def load_ship(name: str) -> Optional[Type[ShipType]]: return None -def load_all_ships(data) -> List[Type[ShipType]]: +def load_all_ships(data: list[str]) -> List[Type[ShipType]]: items = [] for name in data: item = load_ship(name) diff --git a/game/game.py b/game/game.py index 6ec7257c..810f8831 100644 --- a/game/game.py +++ b/game/game.py @@ -1,24 +1,21 @@ -from game.dcs.aircrafttype import AircraftType import itertools import logging import random import sys from datetime import date, datetime, timedelta from enum import Enum -from typing import Any, List +from typing import Any, List, Type, Union from dcs.action import Coalition from dcs.mapping import Point from dcs.task import CAP, CAS, PinpointStrike from dcs.vehicles import AirDefence -from pydcs_extensions.a4ec.a4ec import A_4E_C from faker import Faker -from game import db from game.inventory import GlobalAircraftInventory from game.models.game_stats import GameStats from game.plugins import LuaPluginManager -from gen import aircraft, naming +from gen import naming from gen.ato import AirTaskingOrder from gen.conflictgen import Conflict from gen.flights.ai_flight_planner import CoalitionMissionPlanner @@ -37,7 +34,7 @@ from .procurement import AircraftProcurementRequest, ProcurementAi from .profiling import logged_duration from .settings import Settings, AutoAtoBehavior from .squadrons import AirWing -from .theater import ConflictTheater +from .theater import ConflictTheater, ControlPoint from .theater.bullseye import Bullseye from .theater.transitnetwork import TransitNetwork, TransitNetworkBuilder from .threatzones import ThreatZones @@ -115,7 +112,7 @@ class Game: self.informations.append(Information("Game Start", "-" * 40, 0)) # Culling Zones are for areas around points of interest that contain things we may not wish to cull. self.__culling_zones: List[Point] = [] - self.__destroyed_units: List[str] = [] + self.__destroyed_units: list[dict[str, Union[float, str]]] = [] self.savepath = "" self.budget = player_budget self.enemy_budget = enemy_budget @@ -190,7 +187,7 @@ class Game: self.theater, self.current_day, self.current_turn_time_of_day, self.settings ) - def sanitize_sides(self): + def sanitize_sides(self) -> None: """ Make sure the opposing factions are using different countries :return: @@ -228,14 +225,9 @@ class Game: return self.blue_bullseye return self.red_bullseye - def _roll(self, prob, mult): - if self.settings.version == "dev": - # always generate all events for dev - return 100 - else: - return random.randint(1, 100) <= prob * mult - - def _generate_player_event(self, event_class, player_cp, enemy_cp): + def _generate_player_event( + self, event_class: Type[Event], player_cp: ControlPoint, enemy_cp: ControlPoint + ) -> None: self.events.append( event_class( self, @@ -247,7 +239,7 @@ class Game: ) ) - def _generate_events(self): + def _generate_events(self) -> None: for front_line in self.theater.conflicts(): self._generate_player_event( FrontlineAttackEvent, @@ -261,21 +253,22 @@ class Game: else: self.enemy_budget += amount - def process_player_income(self): + def process_player_income(self) -> None: self.budget += Income(self, player=True).total - def process_enemy_income(self): + def process_enemy_income(self) -> None: # TODO: Clean up save compat. if not hasattr(self, "enemy_budget"): self.enemy_budget = 0 self.enemy_budget += Income(self, player=False).total - def initiate_event(self, event: Event) -> UnitMap: + @staticmethod + def initiate_event(event: Event) -> UnitMap: # assert event in self.events logging.info("Generating {} (regular)".format(event)) return event.generate() - def finish_event(self, event: Event, debriefing: Debriefing): + def finish_event(self, event: Event, debriefing: Debriefing) -> None: logging.info("Finishing event {}".format(event)) event.commit(debriefing) @@ -284,16 +277,6 @@ class Game: else: logging.info("finish_event: event not in the events!") - def is_player_attack(self, event): - if isinstance(event, Event): - return ( - event - and event.attacker_name - and event.attacker_name == self.player_faction.name - ) - else: - raise RuntimeError(f"{event} was passed when an Event type was expected") - def on_load(self, game_still_initializing: bool = False) -> None: if not hasattr(self, "name_generator"): self.name_generator = naming.namegen @@ -400,7 +383,7 @@ class Game: # Autosave progress persistency.autosave(self) - def check_win_loss(self): + def check_win_loss(self) -> TurnState: player_airbases = { cp for cp in self.theater.player_points() if cp.runway_is_operational() } @@ -567,7 +550,7 @@ class Game: def current_day(self) -> date: return self.date + timedelta(days=self.turn // 4) - def next_unit_id(self): + def next_unit_id(self) -> int: """ Next unit id for pre-generated units """ @@ -608,7 +591,7 @@ class Game: return self.blue_navmesh return self.red_navmesh - def compute_conflicts_position(self): + def compute_conflicts_position(self) -> None: """ Compute the current conflict center position(s), mainly used for culling calculation :return: List of points of interests @@ -667,15 +650,15 @@ class Game: self.__culling_zones = zones - def add_destroyed_units(self, data): + def add_destroyed_units(self, data: dict[str, Union[float, str]]) -> None: pos = Point(data["x"], data["z"]) if self.theater.is_on_land(pos): self.__destroyed_units.append(data) - def get_destroyed_units(self): + def get_destroyed_units(self) -> list[dict[str, Union[float, str]]]: return self.__destroyed_units - def position_culled(self, pos): + def position_culled(self, pos: Point) -> bool: """ Check if unit can be generated at given position depending on culling performance settings :param pos: Position you are tryng to spawn stuff at @@ -688,7 +671,7 @@ class Game: return False return True - def get_culling_zones(self): + def get_culling_zones(self) -> list[Point]: """ Check culling points :return: List of culling zones @@ -696,30 +679,28 @@ class Game: return self.__culling_zones # 1 = red, 2 = blue - def get_player_coalition_id(self): + def get_player_coalition_id(self) -> int: return 2 - def get_enemy_coalition_id(self): + def get_enemy_coalition_id(self) -> int: return 1 - def get_player_coalition(self): + def get_player_coalition(self) -> Coalition: return Coalition.Blue - def get_enemy_coalition(self): + def get_enemy_coalition(self) -> Coalition: return Coalition.Red - def get_player_color(self): + def get_player_color(self) -> str: return "blue" - def get_enemy_color(self): + def get_enemy_color(self) -> str: return "red" - def process_win_loss(self, turn_state: TurnState): + def process_win_loss(self, turn_state: TurnState) -> None: if turn_state is TurnState.WIN: - return self.message( - "Congratulations, you are victorious! Start a new campaign to continue." + self.message( + "Congratulations, you are victorious! Start a new campaign to continue." ) elif turn_state is TurnState.LOSS: - return self.message( - "Game Over, you lose. Start a new campaign to continue." - ) + self.message("Game Over, you lose. Start a new campaign to continue.") diff --git a/game/infos/information.py b/game/infos/information.py index 1c132d46..efc3fb96 100644 --- a/game/infos/information.py +++ b/game/infos/information.py @@ -2,13 +2,13 @@ import datetime class Information: - def __init__(self, title="", text="", turn=0): + def __init__(self, title: str = "", text: str = "", turn: int = 0) -> None: self.title = title self.text = text self.turn = turn self.timestamp = datetime.datetime.now() - def __str__(self): + def __str__(self) -> str: return "[{}][{}] {} {}".format( self.timestamp.strftime("%Y-%m-%d %H:%M:%S") if self.timestamp is not None diff --git a/game/models/destroyed_units.py b/game/models/destroyed_units.py deleted file mode 100644 index 7d0de042..00000000 --- a/game/models/destroyed_units.py +++ /dev/null @@ -1,13 +0,0 @@ -class DestroyedUnit: - """ - Store info about a destroyed unit - """ - - x: int - y: int - name: str - - def __init__(self, x, y, name): - self.x = x - self.y = y - self.name = name diff --git a/game/models/game_stats.py b/game/models/game_stats.py index c2be800f..7e828021 100644 --- a/game/models/game_stats.py +++ b/game/models/game_stats.py @@ -1,4 +1,9 @@ -from typing import List +from __future__ import annotations + +from typing import List, TYPE_CHECKING + +if TYPE_CHECKING: + from game import Game class FactionTurnMetadata: @@ -10,7 +15,7 @@ class FactionTurnMetadata: vehicles_count: int = 0 sam_count: int = 0 - def __init__(self): + def __init__(self) -> None: self.aircraft_count = 0 self.vehicles_count = 0 self.sam_count = 0 @@ -24,7 +29,7 @@ class GameTurnMetadata: allied_units: FactionTurnMetadata enemy_units: FactionTurnMetadata - def __init__(self): + def __init__(self) -> None: self.allied_units = FactionTurnMetadata() self.enemy_units = FactionTurnMetadata() @@ -34,10 +39,10 @@ class GameStats: Store statistics for the current game """ - def __init__(self): + def __init__(self) -> None: self.data_per_turn: List[GameTurnMetadata] = [] - def update(self, game): + def update(self, game: Game) -> None: """ Save data for current turn :param game: Game we want to save the data about diff --git a/game/operation/operation.py b/game/operation/operation.py index a44dd5aa..b976c5e7 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -62,7 +62,7 @@ class Operation: plugin_scripts: List[str] = [] @classmethod - def prepare(cls, game: Game): + def prepare(cls, game: Game) -> None: with open("resources/default_options.lua", "r") as f: options_dict = loads(f.read())["options"] cls._set_mission(Mission(game.theater.terrain)) @@ -107,7 +107,7 @@ class Operation: cls.current_mission = mission @classmethod - def _setup_mission_coalitions(cls): + def _setup_mission_coalitions(cls) -> None: cls.current_mission.coalition["blue"] = Coalition( "blue", bullseye=cls.game.blue_bullseye.to_pydcs() ) @@ -163,7 +163,7 @@ class Operation: airsupportgen: AirSupportConflictGenerator, jtacs: List[JtacInfo], airgen: AircraftConflictGenerator, - ): + ) -> None: """Generates subscribed MissionInfoGenerator objects (currently kneeboards and briefings)""" gens: List[MissionInfoGenerator] = [ @@ -251,7 +251,7 @@ class Operation: # beacon list. @classmethod - def _generate_ground_units(cls): + def _generate_ground_units(cls) -> None: cls.groundobjectgen = GroundObjectsGenerator( cls.current_mission, cls.game, @@ -266,7 +266,12 @@ class Operation: """Add destroyed units to the Mission""" for d in cls.game.get_destroyed_units(): try: - utype = db.unit_type_from_name(d["type"]) + type_name = d["type"] + if not isinstance(type_name, str): + raise TypeError( + "Expected the type of the destroyed static to be a string" + ) + utype = db.unit_type_from_name(type_name) except KeyError: continue @@ -418,7 +423,7 @@ class Operation: CargoShipGenerator(cls.current_mission, cls.game, cls.unit_map).generate() @classmethod - def reset_naming_ids(cls): + def reset_naming_ids(cls) -> None: namegen.reset_numbers() @classmethod diff --git a/game/persistency.py b/game/persistency.py index d9d9d135..7685dd09 100644 --- a/game/persistency.py +++ b/game/persistency.py @@ -1,15 +1,19 @@ +from __future__ import annotations + import logging import os import pickle import shutil from pathlib import Path -from typing import Optional +from typing import Optional, TYPE_CHECKING +if TYPE_CHECKING: + from game import Game _dcs_saved_game_folder: Optional[str] = None -def setup(user_folder: str): +def setup(user_folder: str) -> None: global _dcs_saved_game_folder _dcs_saved_game_folder = user_folder if not save_dir().exists(): @@ -38,7 +42,7 @@ def mission_path_for(name: str) -> str: return os.path.join(base_path(), "Missions", name) -def load_game(path): +def load_game(path: str) -> Optional[Game]: with open(path, "rb") as f: try: save = pickle.load(f) @@ -49,7 +53,7 @@ def load_game(path): return None -def save_game(game) -> bool: +def save_game(game: Game) -> bool: try: with open(_temporary_save_file(), "wb") as f: pickle.dump(game, f) @@ -60,7 +64,7 @@ def save_game(game) -> bool: return False -def autosave(game) -> bool: +def autosave(game: Game) -> bool: """ Autosave to the autosave location :param game: Game to save diff --git a/game/plugins/luaplugin.py b/game/plugins/luaplugin.py index b58446a9..5799c748 100644 --- a/game/plugins/luaplugin.py +++ b/game/plugins/luaplugin.py @@ -38,7 +38,7 @@ class PluginSettings: self.settings = Settings() self.initialize_settings() - def set_settings(self, settings: Settings): + def set_settings(self, settings: Settings) -> None: self.settings = settings self.initialize_settings() @@ -146,7 +146,7 @@ class LuaPlugin(PluginSettings): return cls(definition) - def set_settings(self, settings: Settings): + def set_settings(self, settings: Settings) -> None: super().set_settings(settings) for option in self.definition.options: option.set_settings(self.settings) diff --git a/game/point_with_heading.py b/game/point_with_heading.py index fa322723..69d62e9c 100644 --- a/game/point_with_heading.py +++ b/game/point_with_heading.py @@ -2,12 +2,12 @@ from dcs import Point class PointWithHeading(Point): - def __init__(self): + def __init__(self) -> None: super(PointWithHeading, self).__init__(0, 0) self.heading = 0 @staticmethod - def from_point(point: Point, heading: int): + def from_point(point: Point, heading: int) -> Point: p = PointWithHeading() p.x = point.x p.y = point.y diff --git a/game/profiling.py b/game/profiling.py index 82c2c326..219453d9 100644 --- a/game/profiling.py +++ b/game/profiling.py @@ -5,7 +5,8 @@ import timeit from collections import defaultdict from contextlib import contextmanager from datetime import timedelta -from typing import Iterator +from types import TracebackType +from typing import Iterator, Optional, Type @contextmanager @@ -23,7 +24,12 @@ class MultiEventTracer: def __enter__(self) -> MultiEventTracer: return self - def __exit__(self, exc_type, exc_val, exc_tb) -> None: + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: for event, duration in self.events.items(): logging.debug("%s took %s", event, duration) diff --git a/game/settings.py b/game/settings.py index 49aee945..fc297cb9 100644 --- a/game/settings.py +++ b/game/settings.py @@ -1,7 +1,7 @@ from dataclasses import dataclass, field from datetime import timedelta from enum import Enum, unique -from typing import Dict, Optional +from typing import Dict, Optional, Any from dcs.forcedoptions import ForcedOptions @@ -104,7 +104,7 @@ class Settings: def set_plugin_option(self, identifier: str, enabled: bool) -> None: self.plugins[self.plugin_settings_key(identifier)] = enabled - def __setstate__(self, state) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: # __setstate__ is called with the dict of the object being unpickled. We # can provide save compatibility for new settings options (which # normally would not be present in the unpickled object) by creating a diff --git a/game/squadrons.py b/game/squadrons.py index 9b13eed3..f3793916 100644 --- a/game/squadrons.py +++ b/game/squadrons.py @@ -13,6 +13,7 @@ from typing import ( Optional, Iterator, Sequence, + Any, ) import yaml @@ -196,7 +197,7 @@ class Squadron: def send_on_leave(pilot: Pilot) -> None: pilot.send_on_leave() - def return_from_leave(self, pilot: Pilot): + def return_from_leave(self, pilot: Pilot) -> None: if not self.has_unfilled_pilot_slots: raise RuntimeError( f"Cannot return {pilot} from leave because {self} is full" @@ -290,7 +291,7 @@ class Squadron: player=player, ) - def __setstate__(self, state) -> None: + def __setstate__(self, state: dict[str, Any]) -> None: # TODO: Remove save compat. if "auto_assignable_mission_types" not in state: state["auto_assignable_mission_types"] = set(state["mission_types"]) diff --git a/game/theater/base.py b/game/theater/base.py index 7d1bff11..4e727825 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -8,15 +8,15 @@ from game.dcs.aircrafttype import AircraftType from game.dcs.groundunittype import GroundUnitType from game.dcs.unittype import UnitType -BASE_MAX_STRENGTH = 1 -BASE_MIN_STRENGTH = 0 +BASE_MAX_STRENGTH = 1.0 +BASE_MIN_STRENGTH = 0.0 class Base: - def __init__(self): + def __init__(self) -> None: self.aircraft: dict[AircraftType, int] = {} self.armor: dict[GroundUnitType, int] = {} - self.strength = 1 + self.strength = 1.0 @property def total_aircraft(self) -> int: @@ -42,7 +42,7 @@ class Base: ] ) - def commission_units(self, units: dict[Any, int]): + def commission_units(self, units: dict[Any, int]) -> None: for unit_type, unit_count in units.items(): if unit_count <= 0: continue @@ -58,7 +58,7 @@ class Base: target_dict[unit_type] = target_dict.get(unit_type, 0) + unit_count - def commit_losses(self, units_lost: dict[Any, int]): + def commit_losses(self, units_lost: dict[Any, int]) -> None: for unit_type, count in units_lost.items(): target_dict: dict[Any, int] if unit_type in self.aircraft: @@ -77,7 +77,7 @@ class Base: if target_dict[unit_type] == 0: del target_dict[unit_type] - def affect_strength(self, amount): + def affect_strength(self, amount: float) -> None: self.strength += amount if self.strength > BASE_MAX_STRENGTH: self.strength = BASE_MAX_STRENGTH diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 1db5cee5..fae2edb0 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -5,7 +5,7 @@ import math from dataclasses import dataclass from functools import cached_property from pathlib import Path -from typing import Any, Dict, Iterator, List, Optional, Tuple +from typing import Any, Dict, Iterator, List, Optional, Tuple, TYPE_CHECKING from dcs import Mission from dcs.countries import ( @@ -61,6 +61,9 @@ from ..profiling import logged_duration from ..scenery_group import SceneryGroup from ..utils import Distance, meters +if TYPE_CHECKING: + from . import TheaterGroundObject + SIZE_TINY = 150 SIZE_SMALL = 600 SIZE_REGULAR = 1000 @@ -505,7 +508,7 @@ class ConflictTheater: """ daytime_map: Dict[str, Tuple[int, int]] - def __init__(self): + def __init__(self) -> None: self.controlpoints: List[ControlPoint] = [] self.point_to_ll_transformer = Transformer.from_crs( self.projection_parameters.to_crs(), CRS("WGS84") @@ -537,10 +540,12 @@ class ConflictTheater: CRS("WGS84"), self.projection_parameters.to_crs() ) - def add_controlpoint(self, point: ControlPoint): + def add_controlpoint(self, point: ControlPoint) -> None: self.controlpoints.append(point) - def find_ground_objects_by_obj_name(self, obj_name): + def find_ground_objects_by_obj_name( + self, obj_name: str + ) -> list[TheaterGroundObject]: found = [] for cp in self.controlpoints: for g in cp.ground_objects: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index d43e8a2f..e7daf471 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -290,9 +290,9 @@ class ControlPoint(MissionTarget, ABC): at: db.StartingPosition, size: int, importance: float, - has_frontline=True, - cptype=ControlPointType.AIRBASE, - ): + has_frontline: bool = True, + cptype: ControlPointType = ControlPointType.AIRBASE, + ) -> None: super().__init__(name, position) # TODO: Should be Airbase specific. self.id = cp_id @@ -322,7 +322,7 @@ class ControlPoint(MissionTarget, ABC): self.target_position: Optional[Point] = None - def __repr__(self): + def __repr__(self) -> str: return f"<{self.__class__}: {self.name}>" @property @@ -334,11 +334,11 @@ class ControlPoint(MissionTarget, ABC): def heading(self) -> int: ... - def __str__(self): + def __str__(self) -> str: return self.name @property - def is_global(self): + def is_global(self) -> bool: return not self.connected_points def transitive_connected_friendly_points( @@ -405,21 +405,21 @@ class ControlPoint(MissionTarget, ABC): return False @property - def is_carrier(self): + def is_carrier(self) -> bool: """ :return: Whether this control point is an aircraft carrier """ return False @property - def is_fleet(self): + def is_fleet(self) -> bool: """ :return: Whether this control point is a boat (mobile) """ return False @property - def is_lha(self): + def is_lha(self) -> bool: """ :return: Whether this control point is an LHA """ @@ -439,7 +439,7 @@ class ControlPoint(MissionTarget, ABC): @property @abstractmethod - def total_aircraft_parking(self): + def total_aircraft_parking(self) -> int: """ :return: The maximum number of aircraft that can be stored in this control point @@ -471,7 +471,7 @@ class ControlPoint(MissionTarget, ABC): ... # TODO: Should be naval specific. - def get_carrier_group_name(self): + def get_carrier_group_name(self) -> Optional[str]: """ Get the carrier group name if the airbase is a carrier :return: Carrier group name @@ -497,10 +497,12 @@ class ControlPoint(MissionTarget, ABC): return None # TODO: Should be Airbase specific. - def is_connected(self, to) -> bool: + def is_connected(self, to: ControlPoint) -> bool: return to in self.connected_points - def find_ground_objects_by_obj_name(self, obj_name): + def find_ground_objects_by_obj_name( + self, obj_name: str + ) -> list[TheaterGroundObject]: found = [] for g in self.ground_objects: if g.obj_name == obj_name: @@ -522,7 +524,7 @@ class ControlPoint(MissionTarget, ABC): f"vehicles have been captured and sold for ${total}M." ) - def retreat_ground_units(self, game: Game): + def retreat_ground_units(self, game: Game) -> None: # When there are multiple valid destinations, deliver units to whichever # base is least defended first. The closest approximation of unit # strength we have is price @@ -764,8 +766,8 @@ class ControlPoint(MissionTarget, ABC): class Airfield(ControlPoint): def __init__( - self, airport: Airport, size: int, importance: float, has_frontline=True - ): + self, airport: Airport, size: int, importance: float, has_frontline: bool = True + ) -> None: super().__init__( airport.id, airport.name, @@ -960,7 +962,7 @@ class Carrier(NavalControlPoint): raise RuntimeError("Carriers cannot be captured") @property - def is_carrier(self): + def is_carrier(self) -> bool: return True def can_operate(self, aircraft: AircraftType) -> bool: diff --git a/game/theater/frontline.py b/game/theater/frontline.py index 8d46327c..7002913f 100644 --- a/game/theater/frontline.py +++ b/game/theater/frontline.py @@ -88,7 +88,7 @@ class FrontLine(MissionTarget): yield from super().mission_types(for_player) @property - def position(self): + def position(self) -> Point: """ The position where the conflict should occur according to the current strength of each control point. @@ -107,12 +107,12 @@ class FrontLine(MissionTarget): return self.blue_cp, self.red_cp @property - def attack_distance(self): + def attack_distance(self) -> float: """The total distance of all segments""" return sum(i.attack_distance for i in self.segments) @property - def attack_heading(self): + def attack_heading(self) -> float: """The heading of the active attack segment from player to enemy control point""" return self.active_segment.attack_heading diff --git a/game/theater/landmap.py b/game/theater/landmap.py index 29d551b3..2cc3867c 100644 --- a/game/theater/landmap.py +++ b/game/theater/landmap.py @@ -14,7 +14,7 @@ class Landmap: exclusion_zones: MultiPolygon sea_zones: MultiPolygon - def __post_init__(self): + def __post_init__(self) -> None: if not self.inclusion_zones.is_valid: raise RuntimeError("Inclusion zones not valid") if not self.exclusion_zones.is_valid: @@ -36,13 +36,5 @@ def load_landmap(filename: str) -> Optional[Landmap]: return None -def poly_contains(x, y, poly: Union[MultiPolygon, Polygon]): +def poly_contains(x: float, y: float, poly: Union[MultiPolygon, Polygon]) -> bool: return poly.contains(geometry.Point(x, y)) - - -def poly_centroid(poly) -> Tuple[float, float]: - x_list = [vertex[0] for vertex in poly] - y_list = [vertex[1] for vertex in poly] - x = sum(x_list) / len(poly) - y = sum(y_list) / len(poly) - return (x, y) diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index 49fb8fd9..4a7c8990 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -217,7 +217,7 @@ class BuildingGroundObject(TheaterGroundObject): heading: int, control_point: ControlPoint, dcs_identifier: str, - is_fob_structure=False, + is_fob_structure: bool = False, ) -> None: super().__init__( name=name, @@ -438,7 +438,7 @@ class CoastalSiteGroundObject(TheaterGroundObject): group_id: int, position: Point, control_point: ControlPoint, - heading, + heading: int, ) -> None: super().__init__( name=name, diff --git a/game/threatzones.py b/game/threatzones.py index 4d29c6c3..14ee8599 100644 --- a/game/threatzones.py +++ b/game/threatzones.py @@ -27,7 +27,10 @@ ThreatPoly = Union[MultiPolygon, Polygon] class ThreatZones: def __init__( - self, airbases: ThreatPoly, air_defenses: ThreatPoly, radar_sam_threats + self, + airbases: ThreatPoly, + air_defenses: ThreatPoly, + radar_sam_threats: ThreatPoly, ) -> None: self.airbases = airbases self.air_defenses = air_defenses @@ -44,8 +47,10 @@ class ThreatZones: boundary = self.closest_boundary(point) return meters(boundary.distance_to_point(point)) + # Type checking ignored because singledispatchmethod doesn't work with required type + # definitions. The implementation methods are all typed, so should be fine. @singledispatchmethod - def threatened(self, position) -> bool: + def threatened(self, position) -> bool: # type: ignore raise NotImplementedError @threatened.register @@ -61,8 +66,10 @@ class ThreatZones: LineString([self.dcs_to_shapely_point(a), self.dcs_to_shapely_point(b)]) ) + # Type checking ignored because singledispatchmethod doesn't work with required type + # definitions. The implementation methods are all typed, so should be fine. @singledispatchmethod - def threatened_by_aircraft(self, target) -> bool: + def threatened_by_aircraft(self, target) -> bool: # type: ignore raise NotImplementedError @threatened_by_aircraft.register @@ -82,8 +89,10 @@ class ThreatZones: LineString((self.dcs_to_shapely_point(p.position) for p in waypoints)) ) + # Type checking ignored because singledispatchmethod doesn't work with required type + # definitions. The implementation methods are all typed, so should be fine. @singledispatchmethod - def threatened_by_air_defense(self, target) -> bool: + def threatened_by_air_defense(self, target) -> bool: # type: ignore raise NotImplementedError @threatened_by_air_defense.register @@ -102,8 +111,10 @@ class ThreatZones: self.dcs_to_shapely_point(target.position) ) + # Type checking ignored because singledispatchmethod doesn't work with required type + # definitions. The implementation methods are all typed, so should be fine. @singledispatchmethod - def threatened_by_radar_sam(self, target) -> bool: + def threatened_by_radar_sam(self, target) -> bool: # type: ignore raise NotImplementedError @threatened_by_radar_sam.register diff --git a/game/transfers.py b/game/transfers.py index aa56958e..22e0f2be 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -561,8 +561,14 @@ class PendingTransfers: self.pending_transfers.append(new_transfer) return new_transfer + # Type checking ignored because singledispatchmethod doesn't work with required type + # definitions. The implementation methods are all typed, so should be fine. @singledispatchmethod - def cancel_transport(self, transport, transfer: TransferOrder) -> None: + def cancel_transport( # type: ignore + self, + transport, + transfer: TransferOrder, + ) -> None: pass @cancel_transport.register diff --git a/game/utils.py b/game/utils.py index 68e38d31..2370c56f 100644 --- a/game/utils.py +++ b/game/utils.py @@ -2,8 +2,9 @@ from __future__ import annotations import itertools import math +from collections import Iterable from dataclasses import dataclass -from typing import Union +from typing import Union, Any METERS_TO_FEET = 3.28084 FEET_TO_METERS = 1 / METERS_TO_FEET @@ -16,12 +17,12 @@ MS_TO_KPH = 3.6 KPH_TO_MS = 1 / MS_TO_KPH -def heading_sum(h, a) -> int: +def heading_sum(h: int, a: int) -> int: h += a return h % 360 -def opposite_heading(h): +def opposite_heading(h: int) -> int: return heading_sum(h, 180) @@ -180,7 +181,7 @@ def mach(value: float, altitude: Distance) -> Speed: SPEED_OF_SOUND_AT_SEA_LEVEL = knots(661.5) -def pairwise(iterable): +def pairwise(iterable: Iterable[Any]) -> Iterable[tuple[Any, Any]]: """ itertools recipe s -> (s0,s1), (s1,s2), (s2, s3), ... diff --git a/game/weather.py b/game/weather.py index fc077634..97c58f97 100644 --- a/game/weather.py +++ b/game/weather.py @@ -83,7 +83,7 @@ class Weather: raise NotImplementedError @staticmethod - def random_wind(minimum: int, maximum) -> WindConditions: + def random_wind(minimum: int, maximum: int) -> WindConditions: wind_direction = random.randint(0, 360) at_0m_factor = 1 at_2000m_factor = 2 diff --git a/gen/aircraft.py b/gen/aircraft.py index 949e5e98..668b8d95 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -536,7 +536,11 @@ class AircraftConflictGenerator: ) def _add_radio_waypoint( - self, group: FlyingGroup, position, altitude: Distance, airspeed: int = 600 + self, + group: FlyingGroup, + position: Point, + altitude: Distance, + airspeed: int = 600, ) -> MovingPoint: point = group.add_waypoint(position, altitude.meters, airspeed) point.alt_type = "RADIO" @@ -547,7 +551,7 @@ class AircraftConflictGenerator: group: FlyingGroup, cp: ControlPoint, at: Optional[db.StartingPosition] = None, - ): + ) -> MovingPoint: if at is None: at = cp.at position = at if isinstance(at, Point) else at.position @@ -563,7 +567,8 @@ class AircraftConflictGenerator: group.land_at(at) return destination_waypoint - def _at_position(self, at) -> Point: + @staticmethod + def _at_position(at: Union[Point, ShipGroup, Type[Airport]]) -> Point: if isinstance(at, Point): return at elif isinstance(at, ShipGroup): @@ -593,7 +598,10 @@ class AircraftConflictGenerator: parking_slot.unit_id = None def generate_flights( - self, country, ato: AirTaskingOrder, dynamic_runways: Dict[str, RunwayData] + self, + country: Country, + ato: AirTaskingOrder, + dynamic_runways: Dict[str, RunwayData], ) -> None: for package in ato.packages: @@ -719,7 +727,9 @@ class AircraftConflictGenerator: trigger.add_condition(CoalitionHasAirdrome(coalition, flight.from_cp.id)) - def generate_planned_flight(self, cp, country, flight: Flight): + def generate_planned_flight( + self, cp: ControlPoint, country: Country, flight: Flight + ) -> FlyingGroup: name = namegen.next_aircraft_name(country, cp.id, flight) try: if flight.start_type == "In Flight": diff --git a/gen/airsupportgen.py b/gen/airsupportgen.py index 7ed159e2..3bb95d26 100644 --- a/gen/airsupportgen.py +++ b/gen/airsupportgen.py @@ -1,11 +1,12 @@ +from __future__ import annotations + import logging from dataclasses import dataclass, field from datetime import timedelta -from typing import List, Type, Tuple, Optional +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.unittype import UnitType from dcs.task import ( AWACS, ActivateBeaconCommand, @@ -14,15 +15,17 @@ from dcs.task import ( SetImmortalCommand, SetInvisibleCommand, ) +from dcs.unittype import UnitType -from game import db -from .flights.ai_flight_planner_db import AEWC_CAPABLE -from .naming import namegen from .callsigns import callsign_for_support_unit from .conflictgen import Conflict +from .flights.ai_flight_planner_db import AEWC_CAPABLE +from .naming import namegen from .radios import RadioFrequency, RadioRegistry from .tacan import TacanBand, TacanChannel, TacanRegistry +if TYPE_CHECKING: + from game import Game TANKER_DISTANCE = 15000 TANKER_ALT = 4572 @@ -70,7 +73,7 @@ class AirSupportConflictGenerator: self, mission: Mission, conflict: Conflict, - game, + game: Game, radio_registry: RadioRegistry, tacan_registry: TacanRegistry, ) -> None: @@ -95,7 +98,7 @@ class AirSupportConflictGenerator: return (TANKER_ALT + 500, 596) return (TANKER_ALT, 574) - def generate(self): + def generate(self) -> None: player_cp = ( self.conflict.blue_cp if self.conflict.blue_cp.captured diff --git a/gen/armor.py b/gen/armor.py index ff8e8180..5fc691d8 100644 --- a/gen/armor.py +++ b/gen/armor.py @@ -97,7 +97,7 @@ class GroundConflictGenerator: self.unit_map = unit_map self.jtacs: List[JtacInfo] = [] - def _enemy_stance(self): + def _enemy_stance(self) -> CombatStance: """Picks the enemy stance according to the number of planned groups on the frontline for each side""" if len(self.enemy_planned_combat_groups) > len( self.player_planned_combat_groups @@ -122,17 +122,7 @@ class GroundConflictGenerator: ] ) - @staticmethod - def _group_point(point: Point, base_distance) -> Point: - distance = random.randint( - int(base_distance * SPREAD_DISTANCE_FACTOR[0]), - int(base_distance * SPREAD_DISTANCE_FACTOR[1]), - ) - return point.random_point_within( - distance, base_distance * SPREAD_DISTANCE_SIZE_FACTOR - ) - - def generate(self): + def generate(self) -> None: position = Conflict.frontline_position( self.conflict.front_line, self.game.theater ) @@ -724,7 +714,7 @@ class GroundConflictGenerator: distance_from_frontline: int, heading: int, spawn_heading: int, - ): + ) -> Point: shifted = conflict_position.point_from_heading( heading, random.randint(0, combat_width) ) @@ -798,7 +788,7 @@ class GroundConflictGenerator: count: int, at: Point, move_formation: PointAction = PointAction.OffRoad, - heading=0, + heading: int = 0, ) -> VehicleGroup: if side == self.conflict.attackers_country: diff --git a/gen/coastal/coastal_group_generator.py b/gen/coastal/coastal_group_generator.py index 160712e0..0d263e3b 100644 --- a/gen/coastal/coastal_group_generator.py +++ b/gen/coastal/coastal_group_generator.py @@ -1,6 +1,11 @@ import logging import random -from game import db +from typing import Optional + +from dcs.unitgroup import VehicleGroup + +from game import db, Game +from game.theater.theatergroundobject import CoastalSiteGroundObject from gen.coastal.silkworm import SilkwormGenerator COASTAL_MAP = { @@ -8,10 +13,13 @@ COASTAL_MAP = { } -def generate_coastal_group(game, ground_object, faction_name: str): +def generate_coastal_group( + game: Game, ground_object: CoastalSiteGroundObject, faction_name: str +) -> Optional[VehicleGroup]: """ This generate a coastal defenses group - :return: Nothing, but put the group reference inside the ground object + :return: The generated group, or None if this faction does not support coastal + defenses. """ faction = db.FACTIONS[faction_name] if len(faction.coastal_defenses) > 0: diff --git a/gen/coastal/silkworm.py b/gen/coastal/silkworm.py index 1ffe6b04..ccb1374d 100644 --- a/gen/coastal/silkworm.py +++ b/gen/coastal/silkworm.py @@ -1,14 +1,20 @@ +from dcs.unitgroup import VehicleGroup from dcs.vehicles import MissilesSS, Unarmed, AirDefence +from game import Game +from game.factions.faction import Faction +from game.theater.theatergroundobject import CoastalSiteGroundObject from gen.sam.group_generator import GroupGenerator -class SilkwormGenerator(GroupGenerator): - def __init__(self, game, ground_object, faction): +class SilkwormGenerator(GroupGenerator[VehicleGroup]): + def __init__( + self, game: Game, ground_object: CoastalSiteGroundObject, faction: Faction + ) -> None: super(SilkwormGenerator, self).__init__(game, ground_object) self.faction = faction - def generate(self): + def generate(self) -> None: positions = self.get_circular_position(5, launcher_distance=120, coverage=180) diff --git a/gen/conflictgen.py b/gen/conflictgen.py index eabf4e4e..723898cc 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from typing import Tuple, Optional @@ -54,7 +56,7 @@ class Conflict: def frontline_position( cls, frontline: FrontLine, theater: ConflictTheater ) -> Tuple[Point, int]: - attack_heading = frontline.attack_heading + attack_heading = int(frontline.attack_heading) position = cls.find_ground_position( frontline.position, FRONTLINE_LENGTH, @@ -91,7 +93,7 @@ class Conflict: defender: Country, front_line: FrontLine, theater: ConflictTheater, - ): + ) -> Conflict: assert cls.has_frontline_between(front_line.blue_cp, front_line.red_cp) position, heading, distance = cls.frontline_vector(front_line, theater) conflict = cls( @@ -138,7 +140,7 @@ class Conflict: max_distance: int, heading: int, theater: ConflictTheater, - coerce=True, + coerce: bool = True, ) -> Optional[Point]: """ Finds the nearest valid ground position along a provided heading and it's inverse up to max_distance. diff --git a/gen/defenses/armor_group_generator.py b/gen/defenses/armor_group_generator.py index fc549ca9..1ed04e06 100644 --- a/gen/defenses/armor_group_generator.py +++ b/gen/defenses/armor_group_generator.py @@ -1,4 +1,5 @@ import random +from typing import Optional from dcs.unitgroup import VehicleGroup @@ -12,7 +13,9 @@ from gen.defenses.armored_group_generator import ( ) -def generate_armor_group(faction: str, game, ground_object): +def generate_armor_group( + faction: str, game: Game, ground_object: VehicleGroupGroundObject +) -> Optional[VehicleGroup]: """ This generate a group of ground units :return: Generated group diff --git a/gen/defenses/armored_group_generator.py b/gen/defenses/armored_group_generator.py index f68b520b..51058b88 100644 --- a/gen/defenses/armored_group_generator.py +++ b/gen/defenses/armored_group_generator.py @@ -1,12 +1,14 @@ import random +from dcs.unitgroup import VehicleGroup + from game import Game from game.dcs.groundunittype import GroundUnitType from game.theater.theatergroundobject import VehicleGroupGroundObject from gen.sam.group_generator import GroupGenerator -class ArmoredGroupGenerator(GroupGenerator): +class ArmoredGroupGenerator(GroupGenerator[VehicleGroup]): def __init__( self, game: Game, @@ -35,7 +37,7 @@ class ArmoredGroupGenerator(GroupGenerator): ) -class FixedSizeArmorGroupGenerator(GroupGenerator): +class FixedSizeArmorGroupGenerator(GroupGenerator[VehicleGroup]): def __init__( self, game: Game, @@ -47,7 +49,7 @@ class FixedSizeArmorGroupGenerator(GroupGenerator): self.unit_type = unit_type self.size = size - def generate(self): + def generate(self) -> None: spacing = random.randint(20, 70) index = 0 diff --git a/gen/environmentgen.py b/gen/environmentgen.py index 5e393e04..65e053ed 100644 --- a/gen/environmentgen.py +++ b/gen/environmentgen.py @@ -30,7 +30,7 @@ class EnvironmentGenerator: self.mission.weather.wind_at_2000 = wind.at_2000m self.mission.weather.wind_at_8000 = wind.at_8000m - def generate(self): + def generate(self) -> None: self.mission.start_time = self.conditions.start_time self.set_clouds(self.conditions.weather.clouds) self.set_fog(self.conditions.weather.fog) diff --git a/gen/fleet/carrier_group.py b/gen/fleet/carrier_group.py index 4200caca..b25902a9 100644 --- a/gen/fleet/carrier_group.py +++ b/gen/fleet/carrier_group.py @@ -6,7 +6,7 @@ from dcs.ships import USS_Arleigh_Burke_IIa, TICONDEROG class CarrierGroupGenerator(ShipGroupGenerator): - def generate(self): + def generate(self) -> None: # Carrier Strike Group 8 if self.faction.carrier_names[0] == "Carrier Strike Group 8": diff --git a/gen/fleet/cn_dd_group.py b/gen/fleet/cn_dd_group.py index 91c710a0..c47cc6ac 100644 --- a/gen/fleet/cn_dd_group.py +++ b/gen/fleet/cn_dd_group.py @@ -20,7 +20,7 @@ if TYPE_CHECKING: class ChineseNavyGroupGenerator(ShipGroupGenerator): - def generate(self): + def generate(self) -> None: include_frigate = random.choice([True, True, False]) include_dd = random.choice([True, False]) diff --git a/gen/fleet/dd_group.py b/gen/fleet/dd_group.py index db5dd0dd..d3875088 100644 --- a/gen/fleet/dd_group.py +++ b/gen/fleet/dd_group.py @@ -23,7 +23,7 @@ class DDGroupGenerator(ShipGroupGenerator): super(DDGroupGenerator, self).__init__(game, ground_object, faction) self.ddtype = ddtype - def generate(self): + def generate(self) -> None: self.add_unit( self.ddtype, "DD1", diff --git a/gen/fleet/lacombattanteII.py b/gen/fleet/lacombattanteII.py index 7de47da1..6638dd4a 100644 --- a/gen/fleet/lacombattanteII.py +++ b/gen/fleet/lacombattanteII.py @@ -1,12 +1,15 @@ from dcs.ships import La_Combattante_II +from game import Game from game.factions.faction import Faction from game.theater import TheaterGroundObject from gen.fleet.dd_group import DDGroupGenerator class LaCombattanteIIGroupGenerator(DDGroupGenerator): - def __init__(self, game, ground_object: TheaterGroundObject, faction: Faction): + def __init__( + self, game: Game, ground_object: TheaterGroundObject, faction: Faction + ): super(LaCombattanteIIGroupGenerator, self).__init__( game, ground_object, faction, La_Combattante_II ) diff --git a/gen/fleet/lha_group.py b/gen/fleet/lha_group.py index a1a78d37..a7a896b9 100644 --- a/gen/fleet/lha_group.py +++ b/gen/fleet/lha_group.py @@ -4,7 +4,7 @@ from gen.sam.group_generator import ShipGroupGenerator class LHAGroupGenerator(ShipGroupGenerator): - def generate(self): + def generate(self) -> None: # Add carrier if len(self.faction.helicopter_carrier) > 0: diff --git a/gen/fleet/ru_dd_group.py b/gen/fleet/ru_dd_group.py index 8ec15d26..4354b5fb 100644 --- a/gen/fleet/ru_dd_group.py +++ b/gen/fleet/ru_dd_group.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: class RussianNavyGroupGenerator(ShipGroupGenerator): - def generate(self): + def generate(self) -> None: include_frigate = random.choice([True, True, False]) include_dd = random.choice([True, False]) diff --git a/gen/fleet/schnellboot.py b/gen/fleet/schnellboot.py index 83a83fdf..d5fe16a6 100644 --- a/gen/fleet/schnellboot.py +++ b/gen/fleet/schnellboot.py @@ -6,7 +6,7 @@ from gen.sam.group_generator import ShipGroupGenerator class SchnellbootGroupGenerator(ShipGroupGenerator): - def generate(self): + def generate(self) -> None: for i in range(random.randint(2, 4)): self.add_unit( diff --git a/gen/fleet/ship_group_generator.py b/gen/fleet/ship_group_generator.py index 03ab852f..1cd8338c 100644 --- a/gen/fleet/ship_group_generator.py +++ b/gen/fleet/ship_group_generator.py @@ -1,7 +1,17 @@ +from __future__ import annotations + import logging import random +from typing import TYPE_CHECKING, Optional + +from dcs.unitgroup import ShipGroup from game import db +from game.theater.theatergroundobject import ( + LhaGroundObject, + CarrierGroundObject, + ShipGroundObject, +) from gen.fleet.carrier_group import CarrierGroupGenerator from gen.fleet.cn_dd_group import ChineseNavyGroupGenerator, Type54GroupGenerator from gen.fleet.dd_group import ( @@ -21,6 +31,9 @@ from gen.fleet.schnellboot import SchnellbootGroupGenerator from gen.fleet.uboat import UBoatGroupGenerator from gen.fleet.ww2lst import WW2LSTGroupGenerator +if TYPE_CHECKING: + from game import Game + SHIP_MAP = { "SchnellbootGroupGenerator": SchnellbootGroupGenerator, @@ -39,10 +52,12 @@ SHIP_MAP = { } -def generate_ship_group(game, ground_object, faction_name: str): +def generate_ship_group( + game: Game, ground_object: ShipGroundObject, faction_name: str +) -> Optional[ShipGroup]: """ This generate a ship group - :return: Nothing, but put the group reference inside the ground object + :return: The generated group, or None if this faction does not support ships. """ faction = db.FACTIONS[faction_name] if len(faction.navy_generators) > 0: @@ -61,26 +76,30 @@ def generate_ship_group(game, ground_object, faction_name: str): return None -def generate_carrier_group(faction: str, game, ground_object): - """ - This generate a carrier group - :param parentCp: The parent control point +def generate_carrier_group( + faction: str, game: Game, ground_object: CarrierGroundObject +) -> ShipGroup: + """Generates a carrier group. + + :param faction: The faction the TGO belongs to. + :param game: The Game the group is being generated for. :param ground_object: The ground object which will own the ship group - :param country: Owner country - :return: Nothing, but put the group reference inside the ground object + :return: The generated group. """ generator = CarrierGroupGenerator(game, ground_object, db.FACTIONS[faction]) generator.generate() return generator.get_generated_group() -def generate_lha_group(faction: str, game, ground_object): - """ - This generate a lha carrier group - :param parentCp: The parent control point +def generate_lha_group( + faction: str, game: Game, ground_object: LhaGroundObject +) -> ShipGroup: + """Generate an LHA group. + + :param faction: The faction the TGO belongs to. + :param game: The Game the group is being generated for. :param ground_object: The ground object which will own the ship group - :param country: Owner country - :return: Nothing, but put the group reference inside the ground object + :return: The generated group. """ generator = LHAGroupGenerator(game, ground_object, db.FACTIONS[faction]) generator.generate() diff --git a/gen/fleet/uboat.py b/gen/fleet/uboat.py index 6333021f..ee8c3114 100644 --- a/gen/fleet/uboat.py +++ b/gen/fleet/uboat.py @@ -6,7 +6,7 @@ from gen.sam.group_generator import ShipGroupGenerator class UBoatGroupGenerator(ShipGroupGenerator): - def generate(self): + def generate(self) -> None: for i in range(random.randint(1, 4)): self.add_unit( diff --git a/gen/fleet/ww2lst.py b/gen/fleet/ww2lst.py index 7ed63fbe..e3ac7de6 100644 --- a/gen/fleet/ww2lst.py +++ b/gen/fleet/ww2lst.py @@ -6,7 +6,7 @@ from gen.sam.group_generator import ShipGroupGenerator class WW2LSTGroupGenerator(ShipGroupGenerator): - def generate(self): + def generate(self) -> None: # Add LS Samuel Chase self.add_unit( diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 7bbee129..29c9ae96 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -1057,7 +1057,7 @@ class CoalitionMissionPlanner: # delayed until their takeoff time by AirConflictGenerator. package.time_over_target = next(start_time) + tot - def message(self, title, text) -> None: + def message(self, title: str, text: str) -> None: """Emits a planning message to the player. If the mission planner belongs to the players coalition, this emits a diff --git a/gen/flights/flight.py b/gen/flights/flight.py index cf5c011f..855fad95 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -8,7 +8,6 @@ from dcs.mapping import Point from dcs.point import MovingPoint, PointAction from dcs.unit import Unit -from game import db from game.dcs.aircrafttype import AircraftType from game.squadrons import Pilot, Squadron from game.theater.controlpoint import ControlPoint, MissionTarget @@ -323,12 +322,12 @@ class Flight: def clear_roster(self) -> None: self.roster.clear() - def __repr__(self): + def __repr__(self) -> str: if self.custom_name: return f"{self.custom_name} {self.count} x {self.unit_type}" return f"[{self.flight_type}] {self.count} x {self.unit_type}" - def __str__(self): + def __str__(self) -> str: if self.custom_name: return f"{self.custom_name} {self.count} x {self.unit_type}" return f"[{self.flight_type}] {self.count} x {self.unit_type}" diff --git a/gen/forcedoptionsgen.py b/gen/forcedoptionsgen.py index d18db095..ea1c854b 100644 --- a/gen/forcedoptionsgen.py +++ b/gen/forcedoptionsgen.py @@ -43,7 +43,7 @@ class ForcedOptionsGenerator: if blue.unrestricted_satnav or red.unrestricted_satnav: self.mission.forced_options.unrestricted_satnav = True - def generate(self): + def generate(self) -> None: self._set_options_view() self._set_external_views() self._set_labels() diff --git a/gen/ground_forces/ai_ground_planner.py b/gen/ground_forces/ai_ground_planner.py index 63d5b1ab..45d98c01 100644 --- a/gen/ground_forces/ai_ground_planner.py +++ b/gen/ground_forces/ai_ground_planner.py @@ -1,13 +1,18 @@ +from __future__ import annotations + import logging import random from enum import Enum -from typing import Dict, List +from typing import Dict, List, TYPE_CHECKING from game.data.groundunitclass import GroundUnitClass from game.dcs.groundunittype import GroundUnitType from game.theater import ControlPoint from gen.ground_forces.combat_stance import CombatStance +if TYPE_CHECKING: + from game import Game + MAX_COMBAT_GROUP_PER_CP = 10 @@ -54,7 +59,7 @@ class CombatGroup: self.role = role self.start_position = None - def __str__(self): + def __str__(self) -> str: s = f"ROLE : {self.role}\n" if self.size: s += f"UNITS {self.unit_type} * {self.size}" @@ -62,7 +67,7 @@ class CombatGroup: class GroundPlanner: - def __init__(self, cp: ControlPoint, game): + def __init__(self, cp: ControlPoint, game: Game) -> None: self.cp = cp self.game = game self.connected_enemy_cp = [ @@ -82,7 +87,7 @@ class GroundPlanner: self.units_per_cp[cp.id] = [] self.reserve: List[CombatGroup] = [] - def plan_groundwar(self): + def plan_groundwar(self) -> None: ground_unit_limit = self.cp.frontline_unit_count_limit diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index 2350f160..55e7aee5 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -624,7 +624,7 @@ class GroundObjectsGenerator: self.icls_alloc = iter(range(1, 21)) self.runways: Dict[str, RunwayData] = {} - def generate(self): + def generate(self) -> None: for cp in self.game.theater.controlpoints: if cp.captured: country_name = self.game.player_country diff --git a/gen/kneeboard.py b/gen/kneeboard.py index 9a074940..35c22799 100644 --- a/gen/kneeboard.py +++ b/gen/kneeboard.py @@ -91,7 +91,10 @@ class KneeboardPageWriter: return self.x, self.y def text( - self, text: str, font=None, fill: Tuple[int, int, int] = (0, 0, 0) + self, + text: str, + font: Optional[ImageFont.ImageFont] = None, + fill: Tuple[int, int, int] = (0, 0, 0), ) -> None: if font is None: font = self.content_font diff --git a/gen/locations/preset_control_point_locations.py b/gen/locations/preset_control_point_locations.py deleted file mode 100644 index e4be5136..00000000 --- a/gen/locations/preset_control_point_locations.py +++ /dev/null @@ -1,22 +0,0 @@ -from dataclasses import dataclass, field - -from typing import List - -from gen.locations.preset_locations import PresetLocation - - -@dataclass -class PresetControlPointLocations: - """A repository of preset locations for a given control point""" - - # List of possible ashore locations to generate objects (Represented in miz file by an APC_AAV_7_Amphibious) - ashore_locations: List[PresetLocation] = field(default_factory=list) - - # List of possible offshore locations to generate ship groups (Represented in miz file by an Oliver Hazard Perry) - offshore_locations: List[PresetLocation] = field(default_factory=list) - - # Possible antiship missiles sites locations (Represented in miz file by Iranian Silkworm missiles) - antiship_locations: List[PresetLocation] = field(default_factory=list) - - # List of possible powerplants locations (Represented in miz file by static Workshop A object, USA) - powerplant_locations: List[PresetLocation] = field(default_factory=list) diff --git a/gen/locations/preset_locations.py b/gen/locations/preset_locations.py deleted file mode 100644 index 89bdffbc..00000000 --- a/gen/locations/preset_locations.py +++ /dev/null @@ -1,21 +0,0 @@ -from dataclasses import dataclass - -from dcs import Point - - -@dataclass -class PresetLocation: - """A preset location""" - - position: Point - heading: int - id: str - - def __str__(self): - return ( - "-" * 10 - + "X: {}\n Y: {}\nHdg: {}°\nId: {}".format( - self.position.x, self.position.y, self.heading, self.id - ) - + "-" * 10 - ) diff --git a/gen/missiles/missiles_group_generator.py b/gen/missiles/missiles_group_generator.py index 72251516..63f1bb80 100644 --- a/gen/missiles/missiles_group_generator.py +++ b/gen/missiles/missiles_group_generator.py @@ -1,13 +1,20 @@ import logging import random -from game import db +from typing import Optional + +from dcs.unitgroup import VehicleGroup + +from game import db, Game +from game.theater.theatergroundobject import MissileSiteGroundObject from gen.missiles.scud_site import ScudGenerator from gen.missiles.v1_group import V1GroupGenerator MISSILES_MAP = {"V1GroupGenerator": V1GroupGenerator, "ScudGenerator": ScudGenerator} -def generate_missile_group(game, ground_object, faction_name: str): +def generate_missile_group( + game: Game, ground_object: MissileSiteGroundObject, faction_name: str +) -> Optional[VehicleGroup]: """ This generate a missiles group :return: Nothing, but put the group reference inside the ground object diff --git a/gen/missiles/scud_site.py b/gen/missiles/scud_site.py index 67c9a0ad..0c5fd953 100644 --- a/gen/missiles/scud_site.py +++ b/gen/missiles/scud_site.py @@ -1,16 +1,22 @@ import random +from dcs.unitgroup import VehicleGroup from dcs.vehicles import Unarmed, MissilesSS, AirDefence +from game import Game +from game.factions.faction import Faction +from game.theater.theatergroundobject import MissileSiteGroundObject from gen.sam.group_generator import GroupGenerator -class ScudGenerator(GroupGenerator): - def __init__(self, game, ground_object, faction): +class ScudGenerator(GroupGenerator[VehicleGroup]): + def __init__( + self, game: Game, ground_object: MissileSiteGroundObject, faction: Faction + ) -> None: super(ScudGenerator, self).__init__(game, ground_object) self.faction = faction - def generate(self): + def generate(self) -> None: # Scuds self.add_unit( diff --git a/gen/missiles/v1_group.py b/gen/missiles/v1_group.py index 60c94db8..8cfb1dda 100644 --- a/gen/missiles/v1_group.py +++ b/gen/missiles/v1_group.py @@ -1,16 +1,22 @@ import random +from dcs.unitgroup import VehicleGroup from dcs.vehicles import Unarmed, MissilesSS, AirDefence +from game import Game +from game.factions.faction import Faction +from game.theater.theatergroundobject import MissileSiteGroundObject from gen.sam.group_generator import GroupGenerator -class V1GroupGenerator(GroupGenerator): - def __init__(self, game, ground_object, faction): +class V1GroupGenerator(GroupGenerator[VehicleGroup]): + def __init__( + self, game: Game, ground_object: MissileSiteGroundObject, faction: Faction + ) -> None: super(V1GroupGenerator, self).__init__(game, ground_object) self.faction = faction - def generate(self): + def generate(self) -> None: # Ramps self.add_unit( diff --git a/gen/naming.py b/gen/naming.py index f43c0a2e..b342ecaa 100644 --- a/gen/naming.py +++ b/gen/naming.py @@ -257,7 +257,7 @@ class NameGenerator: existing_alphas: List[str] = [] @classmethod - def reset(cls): + def reset(cls) -> None: cls.number = 0 cls.infantry_number = 0 cls.convoy_number = 0 @@ -266,7 +266,7 @@ class NameGenerator: cls.existing_alphas = [] @classmethod - def reset_numbers(cls): + def reset_numbers(cls) -> None: cls.number = 0 cls.infantry_number = 0 cls.aircraft_number = 0 @@ -274,7 +274,9 @@ class NameGenerator: cls.cargo_ship_number = 0 @classmethod - def next_aircraft_name(cls, country: Country, parent_base_id: int, flight: Flight): + def next_aircraft_name( + cls, country: Country, parent_base_id: int, flight: Flight + ) -> str: cls.aircraft_number += 1 try: if flight.custom_name: @@ -315,17 +317,17 @@ class NameGenerator: ) @classmethod - def next_awacs_name(cls, country: Country): + def next_awacs_name(cls, country: Country) -> str: cls.number += 1 return "awacs|{}|{}|0|".format(country.id, cls.number) @classmethod - def next_tanker_name(cls, country: Country, unit_type: AircraftType): + def next_tanker_name(cls, country: Country, unit_type: AircraftType) -> str: cls.number += 1 return "tanker|{}|{}|0|{}".format(country.id, cls.number, unit_type.name) @classmethod - def next_carrier_name(cls, country: Country): + def next_carrier_name(cls, country: Country) -> str: cls.number += 1 return "carrier|{}|{}|0|".format(country.id, cls.number) @@ -340,7 +342,7 @@ class NameGenerator: return f"Cargo Ship {cls.cargo_ship_number:03}" @classmethod - def random_objective_name(cls): + def random_objective_name(cls) -> str: if cls.animals: animal = random.choice(cls.animals) cls.animals.remove(animal) diff --git a/gen/radios.py b/gen/radios.py index 22968397..ced4ac9c 100644 --- a/gen/radios.py +++ b/gen/radios.py @@ -15,7 +15,7 @@ class RadioFrequency: #: The frequency in kilohertz. hertz: int - def __str__(self): + def __str__(self) -> str: if self.hertz >= 1000000: return self.format("MHz", 1000000) return self.format("kHz", 1000) diff --git a/gen/sam/aaa_bofors.py b/gen/sam/aaa_bofors.py index b4c87e34..f6e21977 100644 --- a/gen/sam/aaa_bofors.py +++ b/gen/sam/aaa_bofors.py @@ -15,7 +15,7 @@ class BoforsGenerator(AirDefenseGroupGenerator): name = "Bofors AAA" - def generate(self): + def generate(self) -> None: index = 0 for i in range(4): diff --git a/gen/sam/aaa_flak.py b/gen/sam/aaa_flak.py index 882e5ad3..68dee391 100644 --- a/gen/sam/aaa_flak.py +++ b/gen/sam/aaa_flak.py @@ -24,7 +24,7 @@ class FlakGenerator(AirDefenseGroupGenerator): name = "Flak Site" - def generate(self): + def generate(self) -> None: index = 0 mixed = random.choice([True, False]) unit_type = random.choice(GFLAK) diff --git a/gen/sam/aaa_flak18.py b/gen/sam/aaa_flak18.py index 60f1d389..17725a33 100644 --- a/gen/sam/aaa_flak18.py +++ b/gen/sam/aaa_flak18.py @@ -15,7 +15,7 @@ class Flak18Generator(AirDefenseGroupGenerator): name = "WW2 Flak Site" - def generate(self): + def generate(self) -> None: spacing = random.randint(30, 60) index = 0 diff --git a/gen/sam/aaa_ks19.py b/gen/sam/aaa_ks19.py index f173dab2..7f062bfe 100644 --- a/gen/sam/aaa_ks19.py +++ b/gen/sam/aaa_ks19.py @@ -14,7 +14,7 @@ class KS19Generator(AirDefenseGroupGenerator): name = "KS-19 AAA Site" - def generate(self): + def generate(self) -> None: self.add_unit( highdigitsams.AAA_SON_9_Fire_Can, "TR", diff --git a/gen/sam/aaa_ww2_ally_flak.py b/gen/sam/aaa_ww2_ally_flak.py index 7e58718f..5fc18ddc 100644 --- a/gen/sam/aaa_ww2_ally_flak.py +++ b/gen/sam/aaa_ww2_ally_flak.py @@ -15,7 +15,7 @@ class AllyWW2FlakGenerator(AirDefenseGroupGenerator): name = "WW2 Ally Flak Site" - def generate(self): + def generate(self) -> None: positions = self.get_circular_position(4, launcher_distance=30, coverage=360) for i, position in enumerate(positions): diff --git a/gen/sam/aaa_zsu57.py b/gen/sam/aaa_zsu57.py index 5da161c8..909ce549 100644 --- a/gen/sam/aaa_zsu57.py +++ b/gen/sam/aaa_zsu57.py @@ -13,7 +13,7 @@ class ZSU57Generator(AirDefenseGroupGenerator): name = "ZSU-57-2 Group" - def generate(self): + def generate(self) -> None: num_launchers = 4 positions = self.get_circular_position( num_launchers, launcher_distance=110, coverage=360 diff --git a/gen/sam/aaa_zu23_insurgent.py b/gen/sam/aaa_zu23_insurgent.py index a91d143e..ef2ec419 100644 --- a/gen/sam/aaa_zu23_insurgent.py +++ b/gen/sam/aaa_zu23_insurgent.py @@ -15,7 +15,7 @@ class ZU23InsurgentGenerator(AirDefenseGroupGenerator): name = "Zu-23 Site" - def generate(self): + def generate(self) -> None: index = 0 for i in range(4): index = index + 1 diff --git a/gen/sam/airdefensegroupgenerator.py b/gen/sam/airdefensegroupgenerator.py index bc192691..d74027c1 100644 --- a/gen/sam/airdefensegroupgenerator.py +++ b/gen/sam/airdefensegroupgenerator.py @@ -38,7 +38,7 @@ class AirDefenseRange(Enum): self.default_role = default_role -class AirDefenseGroupGenerator(GroupGenerator, ABC): +class AirDefenseGroupGenerator(GroupGenerator[VehicleGroup], ABC): """ This is the base for all SAM group generators """ diff --git a/gen/sam/cold_war_flak.py b/gen/sam/cold_war_flak.py index 4030321b..788482ec 100644 --- a/gen/sam/cold_war_flak.py +++ b/gen/sam/cold_war_flak.py @@ -18,7 +18,7 @@ class EarlyColdWarFlakGenerator(AirDefenseGroupGenerator): name = "Early Cold War Flak Site" - def generate(self): + def generate(self) -> None: spacing = random.randint(30, 60) index = 0 @@ -90,7 +90,7 @@ class ColdWarFlakGenerator(AirDefenseGroupGenerator): name = "Cold War Flak Site" - def generate(self): + def generate(self) -> None: spacing = random.randint(30, 60) index = 0 diff --git a/gen/sam/ewrs.py b/gen/sam/ewrs.py index 3678fe79..e20adc72 100644 --- a/gen/sam/ewrs.py +++ b/gen/sam/ewrs.py @@ -1,12 +1,13 @@ from typing import Type +from dcs.unitgroup import VehicleGroup from dcs.vehicles import AirDefence from dcs.unittype import VehicleType from gen.sam.group_generator import GroupGenerator -class EwrGenerator(GroupGenerator): +class EwrGenerator(GroupGenerator[VehicleGroup]): unit_type: Type[VehicleType] @classmethod diff --git a/gen/sam/freya_ewr.py b/gen/sam/freya_ewr.py index fbd8dbfb..7c61a25c 100644 --- a/gen/sam/freya_ewr.py +++ b/gen/sam/freya_ewr.py @@ -13,7 +13,7 @@ class FreyaGenerator(AirDefenseGroupGenerator): name = "Freya EWR Site" - def generate(self): + def generate(self) -> None: # TODO : would be better with the Concrete structure that is supposed to protect it self.add_unit( diff --git a/gen/sam/group_generator.py b/gen/sam/group_generator.py index f41b9543..2c7bc54d 100644 --- a/gen/sam/group_generator.py +++ b/gen/sam/group_generator.py @@ -3,13 +3,15 @@ from __future__ import annotations import logging import math import random -from typing import TYPE_CHECKING, Type +from collections import Iterable +from typing import TYPE_CHECKING, Type, TypeVar, Generic from dcs import unitgroup from dcs.mapping import Point from dcs.point import PointAction from dcs.unit import Ship, Vehicle -from dcs.unittype import VehicleType +from dcs.unitgroup import MovingGroup, ShipGroup +from dcs.unittype import VehicleType, UnitType from game.dcs.groundunittype import GroundUnitType from game.factions.faction import Faction @@ -19,12 +21,15 @@ if TYPE_CHECKING: from game.game import Game +GroupType = TypeVar("GroupType", bound=MovingGroup) + + # TODO: Generate a group description rather than a pydcs group. # It appears that all of this work gets redone at miz generation time (see # groundobjectsgen for an example). We can do less work and include the data we # care about in the format we want if we just generate our own group description # types rather than pydcs groups. -class GroupGenerator: +class GroupGenerator(Generic[GroupType]): price: int @@ -38,10 +43,10 @@ class GroupGenerator: wp = self.vg.add_waypoint(self.position, PointAction.OffRoad, 0) wp.ETA_locked = True - def generate(self): + def generate(self) -> None: raise NotImplementedError - def get_generated_group(self) -> unitgroup.VehicleGroup: + def get_generated_group(self) -> GroupType: return self.vg def add_unit( @@ -58,7 +63,7 @@ class GroupGenerator: def add_unit_to_group( self, - group: unitgroup.VehicleGroup, + group: GroupType, unit_type: Type[VehicleType], name: str, position: Point, @@ -78,7 +83,9 @@ class GroupGenerator: return unit - def get_circular_position(self, num_units, launcher_distance, coverage=90): + def get_circular_position( + self, num_units: int, launcher_distance: int, coverage: int = 90 + ) -> Iterable[tuple[float, float, int]]: """ Given a position on the map, array a group of units in a circle a uniform distance from the unit :param num_units: @@ -104,21 +111,20 @@ class GroupGenerator: else: current_offset = self.heading current_offset -= outer_offset * (math.ceil(num_units / 2) - 1) - for x in range(1, num_units + 1): - positions.append( - ( - self.position.x - + launcher_distance * math.cos(math.radians(current_offset)), - self.position.y - + launcher_distance * math.sin(math.radians(current_offset)), - current_offset, - ) + for _ in range(1, num_units + 1): + x: float = self.position.x + launcher_distance * math.cos( + math.radians(current_offset) ) + y: float = self.position.y + launcher_distance * math.sin( + math.radians(current_offset) + ) + heading = current_offset + positions.append((x, y, int(heading))) current_offset += outer_offset return positions -class ShipGroupGenerator(GroupGenerator): +class ShipGroupGenerator(GroupGenerator[ShipGroup]): """Abstract class for other ship generator classes""" def __init__( @@ -133,7 +139,14 @@ class ShipGroupGenerator(GroupGenerator): wp = self.vg.add_waypoint(self.position, 0) wp.ETA_locked = True - def add_unit(self, unit_type, name, pos_x, pos_y, heading) -> Ship: + def add_unit( + self, + unit_type: Type[UnitType], + name: str, + pos_x: float, + pos_y: float, + heading: int, + ) -> Ship: unit = Ship(self.game.next_unit_id(), f"{self.go.group_name}|{name}", unit_type) unit.position.x = pos_x unit.position.y = pos_y diff --git a/gen/sam/sam_avenger.py b/gen/sam/sam_avenger.py index 75df690f..ac72b709 100644 --- a/gen/sam/sam_avenger.py +++ b/gen/sam/sam_avenger.py @@ -15,7 +15,7 @@ class AvengerGenerator(AirDefenseGroupGenerator): name = "Avenger Group" - def generate(self): + def generate(self) -> None: num_launchers = 2 self.add_unit( diff --git a/gen/sam/sam_chaparral.py b/gen/sam/sam_chaparral.py index 3489fd8b..2a746f95 100644 --- a/gen/sam/sam_chaparral.py +++ b/gen/sam/sam_chaparral.py @@ -15,7 +15,7 @@ class ChaparralGenerator(AirDefenseGroupGenerator): name = "Chaparral Group" - def generate(self): + def generate(self) -> None: num_launchers = 2 self.add_unit( diff --git a/gen/sam/sam_gepard.py b/gen/sam/sam_gepard.py index cb752f90..05b04068 100644 --- a/gen/sam/sam_gepard.py +++ b/gen/sam/sam_gepard.py @@ -15,7 +15,7 @@ class GepardGenerator(AirDefenseGroupGenerator): name = "Gepard Group" - def generate(self): + def generate(self) -> None: num_launchers = 2 positions = self.get_circular_position( diff --git a/gen/sam/sam_hawk.py b/gen/sam/sam_hawk.py index 007b0b99..f65faf09 100644 --- a/gen/sam/sam_hawk.py +++ b/gen/sam/sam_hawk.py @@ -17,7 +17,7 @@ class HawkGenerator(AirDefenseGroupGenerator): name = "Hawk Site" - def generate(self): + def generate(self) -> None: self.add_unit( AirDefence.Hawk_sr, "SR", diff --git a/gen/sam/sam_hq7.py b/gen/sam/sam_hq7.py index 8c6f5d2a..89a81097 100644 --- a/gen/sam/sam_hq7.py +++ b/gen/sam/sam_hq7.py @@ -17,7 +17,7 @@ class HQ7Generator(AirDefenseGroupGenerator): name = "HQ-7 Site" - def generate(self): + def generate(self) -> None: self.add_unit( AirDefence.HQ_7_STR_SP, "STR", diff --git a/gen/sam/sam_linebacker.py b/gen/sam/sam_linebacker.py index 224e09bf..397c38a7 100644 --- a/gen/sam/sam_linebacker.py +++ b/gen/sam/sam_linebacker.py @@ -15,7 +15,7 @@ class LinebackerGenerator(AirDefenseGroupGenerator): name = "Linebacker Group" - def generate(self): + def generate(self) -> None: num_launchers = 2 self.add_unit( diff --git a/gen/sam/sam_patriot.py b/gen/sam/sam_patriot.py index 3fb8a995..55c4be2b 100644 --- a/gen/sam/sam_patriot.py +++ b/gen/sam/sam_patriot.py @@ -15,7 +15,7 @@ class PatriotGenerator(AirDefenseGroupGenerator): name = "Patriot Battery" - def generate(self): + def generate(self) -> None: # Command Post self.add_unit( AirDefence.Patriot_str, diff --git a/gen/sam/sam_rapier.py b/gen/sam/sam_rapier.py index a2c1d07b..aac88d64 100644 --- a/gen/sam/sam_rapier.py +++ b/gen/sam/sam_rapier.py @@ -16,7 +16,7 @@ class RapierGenerator(AirDefenseGroupGenerator): name = "Rapier AA Site" - def generate(self): + def generate(self) -> None: self.add_unit( AirDefence.Rapier_fsa_blindfire_radar, "BT", diff --git a/gen/sam/sam_roland.py b/gen/sam/sam_roland.py index 30d51aa5..57c3ab0e 100644 --- a/gen/sam/sam_roland.py +++ b/gen/sam/sam_roland.py @@ -14,7 +14,7 @@ class RolandGenerator(AirDefenseGroupGenerator): name = "Roland Site" - def generate(self): + def generate(self) -> None: num_launchers = 2 self.add_unit( AirDefence.Roland_Radar, diff --git a/gen/sam/sam_sa10.py b/gen/sam/sam_sa10.py index 282515ab..ef633226 100644 --- a/gen/sam/sam_sa10.py +++ b/gen/sam/sam_sa10.py @@ -28,7 +28,7 @@ class SA10Generator(AirDefenseGroupGenerator): self.ln1 = AirDefence.S_300PS_5P85C_ln self.ln2 = AirDefence.S_300PS_5P85D_ln - def generate(self): + def generate(self) -> None: # Search Radar self.add_unit( self.sr1, "SR1", self.position.x, self.position.y + 40, self.heading diff --git a/gen/sam/sam_sa11.py b/gen/sam/sam_sa11.py index 8e53e6b5..873ee0d5 100644 --- a/gen/sam/sam_sa11.py +++ b/gen/sam/sam_sa11.py @@ -15,7 +15,7 @@ class SA11Generator(AirDefenseGroupGenerator): name = "SA-11 Buk Battery" - def generate(self): + def generate(self) -> None: self.add_unit( AirDefence.SA_11_Buk_SR_9S18M1, "SR", diff --git a/gen/sam/sam_sa13.py b/gen/sam/sam_sa13.py index d364edf3..0c81e042 100644 --- a/gen/sam/sam_sa13.py +++ b/gen/sam/sam_sa13.py @@ -15,7 +15,7 @@ class SA13Generator(AirDefenseGroupGenerator): name = "SA-13 Strela Group" - def generate(self): + def generate(self) -> None: self.add_unit( Unarmed.UAZ_469, "UAZ", diff --git a/gen/sam/sam_sa15.py b/gen/sam/sam_sa15.py index ca0d4b22..c0a6d852 100644 --- a/gen/sam/sam_sa15.py +++ b/gen/sam/sam_sa15.py @@ -13,7 +13,7 @@ class SA15Generator(AirDefenseGroupGenerator): name = "SA-15 Tor Group" - def generate(self): + def generate(self) -> None: num_launchers = 2 positions = self.get_circular_position( num_launchers, launcher_distance=120, coverage=360 diff --git a/gen/sam/sam_sa17.py b/gen/sam/sam_sa17.py index c59eb263..1544a043 100644 --- a/gen/sam/sam_sa17.py +++ b/gen/sam/sam_sa17.py @@ -14,7 +14,7 @@ class SA17Generator(AirDefenseGroupGenerator): name = "SA-17 Grizzly Battery" - def generate(self): + def generate(self) -> None: self.add_unit( AirDefence.SA_11_Buk_SR_9S18M1, "SR", diff --git a/gen/sam/sam_sa19.py b/gen/sam/sam_sa19.py index fb0fabe8..8611a310 100644 --- a/gen/sam/sam_sa19.py +++ b/gen/sam/sam_sa19.py @@ -15,7 +15,7 @@ class SA19Generator(AirDefenseGroupGenerator): name = "SA-19 Tunguska Group" - def generate(self): + def generate(self) -> None: num_launchers = 2 if num_launchers == 1: diff --git a/gen/sam/sam_sa2.py b/gen/sam/sam_sa2.py index 70d57f2d..0d2546c5 100644 --- a/gen/sam/sam_sa2.py +++ b/gen/sam/sam_sa2.py @@ -15,7 +15,7 @@ class SA2Generator(AirDefenseGroupGenerator): name = "SA-2/S-75 Site" - def generate(self): + def generate(self) -> None: self.add_unit( AirDefence.P_19_s_125_sr, "SR", diff --git a/gen/sam/sam_sa3.py b/gen/sam/sam_sa3.py index 802a28b6..b75555d1 100644 --- a/gen/sam/sam_sa3.py +++ b/gen/sam/sam_sa3.py @@ -15,7 +15,7 @@ class SA3Generator(AirDefenseGroupGenerator): name = "SA-3/S-125 Site" - def generate(self): + def generate(self) -> None: self.add_unit( AirDefence.P_19_s_125_sr, "SR", diff --git a/gen/sam/sam_sa6.py b/gen/sam/sam_sa6.py index 67975914..af9a6ffc 100644 --- a/gen/sam/sam_sa6.py +++ b/gen/sam/sam_sa6.py @@ -15,7 +15,7 @@ class SA6Generator(AirDefenseGroupGenerator): name = "SA-6 Kub Site" - def generate(self): + def generate(self) -> None: self.add_unit( AirDefence.Kub_1S91_str, "STR", diff --git a/gen/sam/sam_sa8.py b/gen/sam/sam_sa8.py index dc6184c2..35afab86 100644 --- a/gen/sam/sam_sa8.py +++ b/gen/sam/sam_sa8.py @@ -13,7 +13,7 @@ class SA8Generator(AirDefenseGroupGenerator): name = "SA-8 OSA Site" - def generate(self): + def generate(self) -> None: num_launchers = 2 positions = self.get_circular_position( num_launchers, launcher_distance=120, coverage=180 diff --git a/gen/sam/sam_sa9.py b/gen/sam/sam_sa9.py index add7358c..6ee35518 100644 --- a/gen/sam/sam_sa9.py +++ b/gen/sam/sam_sa9.py @@ -15,7 +15,7 @@ class SA9Generator(AirDefenseGroupGenerator): name = "SA-9 Group" - def generate(self): + def generate(self) -> None: self.add_unit( Unarmed.UAZ_469, "UAZ", diff --git a/gen/sam/sam_vulcan.py b/gen/sam/sam_vulcan.py index ea0b8834..9a458db0 100644 --- a/gen/sam/sam_vulcan.py +++ b/gen/sam/sam_vulcan.py @@ -15,7 +15,7 @@ class VulcanGenerator(AirDefenseGroupGenerator): name = "Vulcan Group" - def generate(self): + def generate(self) -> None: num_launchers = 2 positions = self.get_circular_position( diff --git a/gen/sam/sam_zsu23.py b/gen/sam/sam_zsu23.py index 8f9d0529..5e64d5df 100644 --- a/gen/sam/sam_zsu23.py +++ b/gen/sam/sam_zsu23.py @@ -15,7 +15,7 @@ class ZSU23Generator(AirDefenseGroupGenerator): name = "ZSU-23 Group" - def generate(self): + def generate(self) -> None: num_launchers = 4 positions = self.get_circular_position( diff --git a/gen/sam/sam_zu23.py b/gen/sam/sam_zu23.py index 7c73da0f..2a2e2f4b 100644 --- a/gen/sam/sam_zu23.py +++ b/gen/sam/sam_zu23.py @@ -15,7 +15,7 @@ class ZU23Generator(AirDefenseGroupGenerator): name = "ZU-23 Group" - def generate(self): + def generate(self) -> None: index = 0 for i in range(4): index = index + 1 diff --git a/gen/sam/sam_zu23_ural.py b/gen/sam/sam_zu23_ural.py index fe2f38fa..85ca1d20 100644 --- a/gen/sam/sam_zu23_ural.py +++ b/gen/sam/sam_zu23_ural.py @@ -15,7 +15,7 @@ class ZU23UralGenerator(AirDefenseGroupGenerator): name = "ZU-23 Ural Group" - def generate(self): + def generate(self) -> None: num_launchers = 4 positions = self.get_circular_position( diff --git a/gen/sam/sam_zu23_ural_insurgent.py b/gen/sam/sam_zu23_ural_insurgent.py index aea7c92b..7d70300a 100644 --- a/gen/sam/sam_zu23_ural_insurgent.py +++ b/gen/sam/sam_zu23_ural_insurgent.py @@ -15,7 +15,11 @@ class ZU23UralInsurgentGenerator(AirDefenseGroupGenerator): name = "ZU-23 Ural Insurgent Group" - def generate(self): + @classmethod + def range(cls) -> AirDefenseRange: + return AirDefenseRange.AAA + + def generate(self) -> None: num_launchers = 4 positions = self.get_circular_position( @@ -29,7 +33,3 @@ class ZU23UralInsurgentGenerator(AirDefenseGroupGenerator): position[1], position[2], ) - - @classmethod - def range(cls) -> AirDefenseRange: - return AirDefenseRange.AAA diff --git a/gen/triggergen.py b/gen/triggergen.py index a8e29a42..6616456d 100644 --- a/gen/triggergen.py +++ b/gen/triggergen.py @@ -51,11 +51,11 @@ class TriggersGenerator: capture_zone_types = (Fob,) capture_zone_flag = 600 - def __init__(self, mission: Mission, game: Game): + def __init__(self, mission: Mission, game: Game) -> None: self.mission = mission self.game = game - def _set_allegiances(self, player_coalition: str, enemy_coalition: str): + def _set_allegiances(self, player_coalition: str, enemy_coalition: str) -> None: """ Set airbase initial coalition """ @@ -87,7 +87,7 @@ class TriggersGenerator: cp.captured and player_coalition or enemy_coalition ) - def _set_skill(self, player_coalition: str, enemy_coalition: str): + def _set_skill(self, player_coalition: str, enemy_coalition: str) -> None: """ Set skill level for all aircraft in the mission """ @@ -103,7 +103,7 @@ class TriggersGenerator: for vehicle_group in country.vehicle_group: vehicle_group.set_skill(skill_level) - def _gen_markers(self): + def _gen_markers(self) -> None: """ Generate markers on F10 map for each existing objective """ @@ -188,7 +188,7 @@ class TriggersGenerator: recapture_trigger.add_action(ClearFlag(flag=flag)) self.mission.triggerrules.triggers.append(recapture_trigger) - def generate(self): + def generate(self) -> None: player_coalition = "blue" enemy_coalition = "red" @@ -198,7 +198,7 @@ class TriggersGenerator: self._generate_capture_triggers(player_coalition, enemy_coalition) @classmethod - def get_capture_zone_flag(cls): + def get_capture_zone_flag(cls) -> int: flag = cls.capture_zone_flag cls.capture_zone_flag += 1 return flag diff --git a/gen/visualgen.py b/gen/visualgen.py index 0fa9c335..765a8f3c 100644 --- a/gen/visualgen.py +++ b/gen/visualgen.py @@ -1,9 +1,8 @@ from __future__ import annotations import random -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any -from dcs.mapping import Point from dcs.mission import Mission from dcs.unit import Static from dcs.unittype import StaticType @@ -11,7 +10,7 @@ from dcs.unittype import StaticType if TYPE_CHECKING: from game import Game -from .conflictgen import Conflict, FRONTLINE_LENGTH +from .conflictgen import Conflict class MarkerSmoke(StaticType): @@ -46,13 +45,7 @@ class MassiveSmoke(StaticType): rate = 1 -class Outpost(StaticType): - id = "outpost" - name = "outpost" - category = "Fortifications" - - -def __monkey_static_dict(self: Static): +def __monkey_static_dict(self: Static) -> dict[str, Any]: global __original_static_dict d = __original_static_dict(self) @@ -65,7 +58,6 @@ def __monkey_static_dict(self: Static): __original_static_dict = Static.dict Static.dict = __monkey_static_dict -FRONT_SMOKE_SPACING = 800 FRONT_SMOKE_RANDOM_SPREAD = 4000 FRONT_SMOKE_TYPE_CHANCES = { 2: MassiveSmoke, @@ -74,29 +66,13 @@ FRONT_SMOKE_TYPE_CHANCES = { 100: Smoke, } -DESTINATION_SMOKE_AMOUNT_FACTOR = 0.03 -DESTINATION_SMOKE_DISTANCE_FACTOR = 1 -DESTINATION_SMOKE_TYPE_CHANCES = { - 5: BigSmoke, - 100: Smoke, -} - - -def turn_heading(heading, fac): - heading += fac - if heading > 359: - heading = heading - 359 - if heading < 0: - heading = 359 + heading - return heading - class VisualGenerator: - def __init__(self, mission: Mission, game: Game): + def __init__(self, mission: Mission, game: Game) -> None: self.mission = mission self.game = game - def _generate_frontline_smokes(self): + def _generate_frontline_smokes(self) -> None: for front_line in self.game.theater.conflicts(): from_cp = front_line.blue_cp to_cp = front_line.red_cp @@ -128,61 +104,5 @@ class VisualGenerator: ) break - def _generate_stub_planes(self): - pass - """ - mission_units = set() - for coalition_name, coalition in self.mission.coalition.items(): - for country in coalition.countries.values(): - for group in country.plane_group + country.helicopter_group + country.vehicle_group: - for unit in group.units: - mission_units.add(db.unit_type_of(unit)) - - for unit_type in mission_units: - self.mission.static_group(self.mission.country(self.game.player_country), "a", unit_type, Point(0, 300000), hidden=True)""" - - def generate_target_smokes(self, target): - spread = target.size * DESTINATION_SMOKE_DISTANCE_FACTOR - for _ in range( - 0, - int( - target.size - * DESTINATION_SMOKE_AMOUNT_FACTOR - * (1.1 - target.base.strength) - ), - ): - for k, v in DESTINATION_SMOKE_TYPE_CHANCES.items(): - if random.randint(0, 100) <= k: - position = target.position.random_point_within(0, spread) - if not self.game.theater.is_on_land(position): - break - - self.mission.static_group( - self.mission.country(self.game.enemy_country), - "", - _type=v, - position=position, - hidden=True, - ) - break - - def generate_transportation_marker(self, at: Point): - self.mission.static_group( - self.mission.country(self.game.player_country), - "", - _type=MarkerSmoke, - position=at, - ) - - def generate_transportation_destination(self, at: Point): - self.generate_transportation_marker(at.point_from_heading(0, 20)) - self.mission.static_group( - self.mission.country(self.game.player_country), - "", - _type=Outpost, - position=at, - ) - - def generate(self): + def generate(self) -> None: self._generate_frontline_smokes() - self._generate_stub_planes() diff --git a/mypy.ini b/mypy.ini index 480b478b..b4d0925b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -6,7 +6,7 @@ check_untyped_defs = True disallow_any_generics = True # disallow_any_unimported = True disallow_untyped_decorators = True -# disallow_untyped_defs = True +disallow_untyped_defs = True follow_imports = silent # implicit_reexport = False namespace_packages = True