Flesh out typing information, enforce.

This commit is contained in:
Dan Albert 2021-07-07 17:41:29 -07:00
parent 69c3d41a8a
commit fb9a0fe833
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 (
@ -328,7 +329,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
@ -361,7 +362,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

@ -592,8 +592,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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ class BoforsGenerator(AirDefenseGroupGenerator):
name = "Bofors AAA"
def generate(self):
def generate(self) -> None:
index = 0
for i in range(4):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ class AvengerGenerator(AirDefenseGroupGenerator):
name = "Avenger Group"
def generate(self):
def generate(self) -> None:
num_launchers = 2
self.add_unit(

View File

@ -15,7 +15,7 @@ class ChaparralGenerator(AirDefenseGroupGenerator):
name = "Chaparral Group"
def generate(self):
def generate(self) -> None:
num_launchers = 2
self.add_unit(

View File

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

View File

@ -17,7 +17,7 @@ class HawkGenerator(AirDefenseGroupGenerator):
name = "Hawk Site"
def generate(self):
def generate(self) -> None:
self.add_unit(
AirDefence.Hawk_sr,
"SR",

View File

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

View File

@ -15,7 +15,7 @@ class LinebackerGenerator(AirDefenseGroupGenerator):
name = "Linebacker Group"
def generate(self):
def generate(self) -> None:
num_launchers = 2
self.add_unit(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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