Flesh out typing information, enforce.

(cherry picked from commit fb9a0fe833)
This commit is contained in:
Dan Albert
2021-07-07 17:41:29 -07:00
parent 7cfd6b7151
commit 4e9d661c0c
99 changed files with 426 additions and 453 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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"

View File

@@ -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)

View File

@@ -8,5 +8,5 @@ class FrontlineAttackEvent(Event):
future unique Event handling
"""
def __str__(self):
def __str__(self) -> str:
return "Frontline attack"

View File

@@ -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)

View File

@@ -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.")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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"])

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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), ...

View File

@@ -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