class Coalition:
    def __init__(self, name, bullseye=None):
        self.name = name
        self.countries = {}  # type: Dict[str, Country]
        self.bullseye = bullseye
        self.nav_points = []  # TODO

    @staticmethod
    def _sort_keys(points):
        keys = []
        for imp_point_idx in points:
            keys.append(int(imp_point_idx))

        keys.sort()
        return keys

    @staticmethod
    def _import_moving_point(mission, group: unitgroup.Group, imp_group) -> unitgroup.Group:
        keys = Coalition._sort_keys(imp_group["route"]["points"])

        for imp_point_idx in keys:
            imp_point = imp_group["route"]["points"][imp_point_idx]
            point = MovingPoint(Point(0, 0, mission.terrain))
            point.load_from_dict(imp_point, mission.translation)
            group.add_point(point)
        return group

    @staticmethod
    def _import_static_point(mission, group: unitgroup.Group, imp_group) -> unitgroup.Group:
        keys = Coalition._sort_keys(imp_group["route"]["points"])

        for imp_point_idx in keys:
            imp_point = imp_group["route"]["points"][imp_point_idx]
            point = StaticPoint(Point(0, 0, mission.terrain))
            point.load_from_dict(imp_point, mission.translation)
            group.add_point(point)
        return group

    @staticmethod
    def _park_unit_on_airport(
            mission: 'Mission',
            group: unitgroup.Group,
            unit: Union[Plane, Helicopter]) -> List[StatusMessage]:
        ret: List[StatusMessage] = []
        if group.points[0].airdrome_id is not None and unit.parking is not None:
            airport = mission.terrain.airport_by_id(group.points[0].airdrome_id)
            slot = airport.parking_slot(unit.parking)
            if slot is not None:
                unit.set_parking(slot)
            else:
                msg = "Parking slot id '{i}' for unit '{u}' in group '{p}' on airport '{a}' " \
                      "not valid, placing on next free".format(i=unit.parking, u=unit.name,
                                                               a=airport.name, p=group.name)
                print("WARN", msg, file=sys.stderr)
                ret.append(StatusMessage(msg, MessageType.PARKING_SLOT_NOT_VALID, MessageSeverity.WARN))
                slot = airport.free_parking_slot(unit.unit_type)
                if slot is not None:
                    unit.set_parking(slot)
                else:
                    msg = "No free parking slots for unit '{u}' in unit group '{p}' on airport '{a}', ignoring"\
                        .format(u=unit.name, a=airport.name, p=group.name)
                    print("ERRO", msg, file=sys.stderr)
                    ret.append(StatusMessage(msg, MessageType.PARKING_SLOTS_FULL, MessageSeverity.ERROR))
        return ret

    @staticmethod
    def get_name(mission: "Mission", name: str) -> str:
        # Group, unit names are not localized for missions are created in 2.7.
        if mission.version < 19:
            return str(mission.translation.get_string(name))
        else:
            return name

    def load_from_dict(self, mission, d) -> List[StatusMessage]:
        status: List[StatusMessage] = []
        for country_idx in d["country"]:
            imp_country = d["country"][country_idx]
            _country = countries.get_by_id(imp_country["id"])

            if "vehicle" in imp_country:
                for vgroup_idx in imp_country["vehicle"]["group"]:
                    vgroup = imp_country["vehicle"]["group"][vgroup_idx]
                    vg = unitgroup.VehicleGroup(vgroup["groupId"], self.get_name(mission, vgroup["name"]),
                                                vgroup["start_time"])
                    vg.load_from_dict(vgroup, mission.terrain)
                    mission.current_group_id = max(mission.current_group_id, vg.id)

                    Coalition._import_moving_point(mission, vg, vgroup)

                    # units
                    for imp_unit_idx in vgroup["units"]:
                        imp_unit = vgroup["units"][imp_unit_idx]
                        unit = Vehicle(
                            mission.terrain,
                            id=imp_unit["unitId"],
                            name=self.get_name(mission, imp_unit["name"]),
                            _type=imp_unit["type"])
                        unit.load_from_dict(imp_unit)

                        mission.current_unit_id = max(mission.current_unit_id, unit.id)
                        vg.add_unit(unit)
                    _country.add_vehicle_group(vg)

            if "ship" in imp_country:
                for group_idx in imp_country["ship"]["group"]:
                    imp_group = imp_country["ship"]["group"][group_idx]
                    ship_group = unitgroup.ShipGroup(imp_group["groupId"], self.get_name(mission, imp_group["name"]),
                                                     imp_group["start_time"])
                    ship_group.load_from_dict(imp_group, mission.terrain)
                    mission.current_group_id = max(mission.current_group_id, ship_group.id)

                    Coalition._import_moving_point(mission, ship_group, imp_group)

                    # units
                    for imp_unit_idx in imp_group["units"]:
                        imp_unit = imp_group["units"][imp_unit_idx]
                        ship = Ship(
                            mission.terrain,
                            id=imp_unit["unitId"],
                            name=self.get_name(mission, imp_unit["name"]),
                            _type=ships.ship_map[imp_unit["type"]])
                        ship.load_from_dict(imp_unit)

                        mission.current_unit_id = max(mission.current_unit_id, ship.id)
                        ship_group.add_unit(ship)
                    _country.add_ship_group(ship_group)

            if "plane" in imp_country:
                for pgroup_idx in imp_country["plane"]["group"]:
                    pgroup = imp_country["plane"]["group"][pgroup_idx]
                    plane_group = unitgroup.PlaneGroup(pgroup["groupId"],
                                                       self.get_name(mission, pgroup["name"]),
                                                       pgroup["start_time"])
                    plane_group.load_from_dict(pgroup, mission.terrain)
                    mission.current_group_id = max(mission.current_group_id, plane_group.id)

                    Coalition._import_moving_point(mission, plane_group, pgroup)

                    # units
                    for imp_unit_idx in pgroup["units"]:
                        imp_unit = pgroup["units"][imp_unit_idx]
                        plane = Plane(
                            mission.terrain,
                            _id=imp_unit["unitId"],
                            name=self.get_name(mission, imp_unit["name"]),
                            _type=planes.plane_map[imp_unit["type"]],
                            _country=_country)
                        plane.load_from_dict(imp_unit)

                        if _country.reserve_onboard_num(plane.onboard_num):
                            msg = "{c} Plane '{p}' already using tail number: {t}".format(
                                c=self.name.upper(), p=plane.name, t=plane.onboard_num)
                            status.append(StatusMessage(msg, MessageType.ONBOARD_NUM_DUPLICATE, MessageSeverity.WARN))
                            print("WARN:", msg, file=sys.stderr)
                        status += self._park_unit_on_airport(mission, plane_group, plane)

                        mission.current_unit_id = max(mission.current_unit_id, plane.id)
                        plane_group.add_unit(plane)

                    # check runway start
                    # if plane_group.points[0].airdrome_id is not None and plane_group.units[0].parking is None:
                    #     airport = mission.terrain.airport_by_id(plane_group.points[0].airdrome_id)
                    #     airport.occupy_runway(plane_group)
                    _country.add_plane_group(plane_group)

            if "helicopter" in imp_country:
                for pgroup_idx in imp_country["helicopter"]["group"]:
                    pgroup = imp_country["helicopter"]["group"][pgroup_idx]
                    helicopter_group = unitgroup.HelicopterGroup(
                        pgroup["groupId"],
                        self.get_name(mission, pgroup["name"]),
                        pgroup["start_time"])
                    helicopter_group.load_from_dict(pgroup, mission.terrain)
                    mission.current_group_id = max(mission.current_group_id, helicopter_group.id)

                    Coalition._import_moving_point(mission, helicopter_group, pgroup)

                    # units
                    for imp_unit_idx in pgroup["units"]:
                        imp_unit = pgroup["units"][imp_unit_idx]
                        heli = Helicopter(
                            mission.terrain,
                            _id=imp_unit["unitId"],
                            name=self.get_name(mission, imp_unit["name"]),
                            _type=helicopters.helicopter_map[imp_unit["type"]],
                            _country=_country)
                        heli.load_from_dict(imp_unit)

                        if _country.reserve_onboard_num(heli.onboard_num):
                            msg = "{c} Helicopter '{h}' already using tail number: {t}".format(
                                c=self.name.upper(), h=heli.name, t=heli.onboard_num)
                            status.append(StatusMessage(msg, MessageType.ONBOARD_NUM_DUPLICATE, MessageSeverity.WARN))
                            print("WARN:", msg, file=sys.stderr)
                        status += self._park_unit_on_airport(mission, helicopter_group, heli)

                        mission.current_unit_id = max(mission.current_unit_id, heli.id)
                        helicopter_group.add_unit(heli)

                    # check runway start
                    # if helicopter_group.points[0].airdrome_id is not None and helicopter_group.units[0].parking is None:
                    #     airport = mission.terrain.airport_by_id(helicopter_group.points[0].airdrome_id)
                    #     airport.occupy_runway(helicopter_group)
                    _country.add_helicopter_group(helicopter_group)

            if "static" in imp_country:
                for sgroup_idx in imp_country["static"]["group"]:
                    sgroup = imp_country["static"]["group"][sgroup_idx]
                    static_group = unitgroup.StaticGroup(sgroup["groupId"],
                                                         self.get_name(mission, sgroup["name"]))
                    static_group.load_from_dict(sgroup, mission.terrain)
                    mission.current_group_id = max(mission.current_group_id, static_group.id)

                    Coalition._import_static_point(mission, static_group, sgroup)

                    # units
                    for imp_unit_idx in sgroup["units"]:
                        imp_unit = sgroup["units"][imp_unit_idx]
                        static: Static
                        if imp_unit["type"] == "FARP":
                            static = FARP(
                                mission.terrain,
                                unit_id=imp_unit["unitId"],
                                name=self.get_name(mission, imp_unit["name"]))
                        elif imp_unit["type"] == "SINGLE_HELIPAD":
                            static = SingleHeliPad(
                                mission.terrain,
                                unit_id=imp_unit["unitId"],
                                name=self.get_name(mission, imp_unit["name"]))
                        else:
                            static = Static(
                                unit_id=imp_unit["unitId"],
                                name=self.get_name(mission, imp_unit["name"]),
                                _type=imp_unit["type"],
                                terrain=mission.terrain)
                        static.load_from_dict(imp_unit)

                        mission.current_unit_id = max(mission.current_unit_id, static.id)
                        static_group.add_unit(static)
                    _country.add_static_group(static_group)
            self.add_country(_country)
        return status

    def set_bullseye(self, bulls):
        self.bullseye = bulls

    def add_country(self, country):
        self.countries[country.name] = country
        return country

    def remove_country(self, name):
        return self.countries.pop(name)

    def swap_country(self, coalition, name):
        return coalition.add_country(self.remove_country(name))

    def country(self, country_name: str):
        return self.countries.get(country_name, None)

    def country_by_id(self, _id: int):
        for cn in self.countries:
            c = self.countries[cn]
            if c.id == _id:
                return c
        return None

    def find_group(self, group_name, search="exact"):
        for c in self.countries:
            g = self.countries[c].find_group(group_name, search)
            if g:
                return g

        return None

    def dict(self):
        d = {"name": self.name}
        if self.bullseye:
            d["bullseye"] = self.bullseye
        d["country"] = {}
        i = 1
        for country in sorted(self.countries.keys()):
            d["country"][i] = self.country(country).dict()
            i += 1
        d["nav_points"] = {}
        return d class Drawings:
    def __init__(self, terrain: Terrain) -> None:
        self._terrain = terrain
        self.options = Options()
        self.layers = [
            Layer(True, StandardLayer.Red.value, [], terrain),
            Layer(True, StandardLayer.Blue.value, [], terrain),
            Layer(True, StandardLayer.Neutral.value, [], terrain),
            Layer(True, StandardLayer.Common.value, [], terrain),
            Layer(True, StandardLayer.Author.value, [], terrain),
        ]

    def load_from_dict(self, data: Dict[str, Any]) -> None:
        self.options.load_from_dict(data["options"])
        self.layers = []
        for layer_index in sorted(data["layers"].keys()):
            layer_data = data["layers"][layer_index]
            layer = Layer(True, "", [], self._terrain)
            layer.load_from_dict(layer_data)
            self.layers.append(layer)

    def dict(self):
        d = {}
        d["options"] = self.options.dict()

        d["layers"] = {}
        i = 1
        for layer in self.layers:
            d["layers"][i] = layer.dict()
            i += 1

        return d

    def get_layer_by_name(self, layer_name: str):
        for layer in self.layers:
            if layer.name == layer_name:
                return layer

    def get_layer(self, layer: StandardLayer):
        return self.get_layer_by_name(layer.value) class Terrain:
    city_graph = Graph()  # type: Graph
    temperature = [
        (-4, 14),
        (-8, 14),
        (-6, 16),
        (0, 19),
        (1, 24),
        (8, 30),
        (12, 33),
        (12, 32),
        (10, 28),
        (2, 22),
        (-2, 14),
        (-4, 12)
    ]
    assert(len(temperature) == 12)

    def __init__(
        self, name: str,
        projection_parameters: TransverseMercator,
        bounds: mapping.Rectangle,
        map_view_default: MapView,
    ) -> None:
        self.name = name
        self.projection_parameters = projection_parameters
        self.bounds = bounds
        self.map_view_default = map_view_default
        self.center = {"lat": 0.0, "long": 0.0}  # WGS84 decimal
        self.bullseye_blue = {"x": 0, "y": 0}
        self.bullseye_red = {"x": 0, "y": 0}
        self.airports = {}  # type: Dict[str,Airport]

        self._point_to_ll_transformer = Transformer.from_crs(
            self.projection_parameters.to_crs(), CRS("WGS84")
        )
        self._ll_to_point_transformer = Transformer.from_crs(
            CRS("WGS84"), self.projection_parameters.to_crs()
        )

    def __getstate__(self) -> Dict[str, Any]:
        state = self.__dict__.copy()
        # Transformers are not pickleable. Remove them from the serialized data and
        # recreate on load.
        del state["_point_to_ll_transformer"]
        del state["_ll_to_point_transformer"]
        return state

    def __setstate__(self, state: Dict[str, Any]) -> None:
        self.__dict__.update(state)
        # Regenerate any state that was not persisted.
        self._point_to_ll_transformer = Transformer.from_crs(
            self.projection_parameters.to_crs(), CRS("WGS84")
        )
        self._ll_to_point_transformer = Transformer.from_crs(
            CRS("WGS84"), self.projection_parameters.to_crs()
        )

    def weather(self, dt: datetime, weather_: weather.Weather):
        # check if there might be the season for thunderstorms
        if 4 < dt.month < 11:
            if random.random() > 0.9:
                weather_.random_thunderstorm()
                return self.random_season_temperature(dt)

        weather_.dynamic_weather(random.choice(list(weather.Weather.BaricSystem)), random.randint(1, 4))
        return self.random_season_temperature(dt)

    def airport_by_id(self, _id: int) -> Optional[Airport]:
        for x in self.airports:
            if self.airports[x].id == _id:
                return self.airports[x]
        return None

    def airport_list(self) -> Iterator[Airport]:
        for x in self.airports:
            yield self.airports[x]

    def nearest_airport(self, position: mapping.Point, coalition: str = None) -> Optional[Airport]:
        airports = list(self.airports.values())
        if coalition:
            airports = [x for x in airports if
                        x.coalition.lower() == coalition.lower()]

        dist = sys.float_info.max
        airport = None
        for x in airports:
            d = position.distance_to_point(x.position)
            if d < dist:
                airport = x
                dist = d

        return airport

    def airport_within(self, position: mapping.Point, distance) -> List[Airport]:
        """Return all airports within the radius of a given point.

        Args:
            position(mapping.Point): Center of circle
            distance: Distance to the point

        Returns:
            Sequence of airports within range.
        """
        return [self.airports[x] for x in self.airports
                if self.airports[x].position.distance_to_point(position) < distance]

    def random_season_temperature(self, dt: datetime) -> int:
        return random.randint(self.temperature[dt.month - 1][0], self.temperature[dt.month - 1][1]) class Warehouses:
    def __init__(self, terrain: Terrain):
        self.terrain = terrain
        self.warehouses: Dict[str, Any] = {}

    def load_dict(self, data):
        for x in data.get("airports", {}):
            self.terrain.airport_by_id(x).load_from_dict(data["airports"][x])

    def __str__(self):
        airports = self.terrain.airports
        d = {
            "warehouses": self.warehouses,
            "airports": {airports[x].id: airports[x].dict() for x in airports}
        }
        return lua.dumps(d, "warehouses", 1) class Triggers:
    def __init__(self, terrain: Terrain) -> None:
        self._terrain = terrain
        self.current_zone_id = 0
        self._zones = []  # type: List[TriggerZone]

    def load_from_dict(self, data: Dict[str, Any]) -> None:
        self.current_zone_id = 0
        self._zones = []
        for x in data["zones"]:
            imp_zone = data["zones"][x]
            tz = TriggerZone(
                imp_zone["zoneId"],
                mapping.Point(imp_zone["x"], imp_zone["y"], self._terrain),
                imp_zone["radius"],
                imp_zone["hidden"],
                imp_zone["name"],
                imp_zone["color"],
                imp_zone.get("properties", {})
            )
            self._zones.append(tz)
            self.current_zone_id = max(self.current_zone_id, tz.id)

    def add_triggerzone(self,
                        position: mapping.Point,
                        radius=1500,
                        hidden=False,
                        name="",
                        color=None,
                        properties=None
                        ) -> TriggerZone:

        self.current_zone_id += 1

        tz = TriggerZone(self.current_zone_id, position, radius, hidden, name, color, properties)
        self._zones.append(tz)
        return tz

    def clear(self):
        self._zones.clear()

    def zones(self) -> List[TriggerZone]:
        return self._zones

    def dict(self):
        return {
            "zones": {i + 1: self._zones[i].dict() for i in range(0, len(self._zones))}
        } class Country:
    callsign: Dict[str, List[str]] = {}
    planes: List[Type[PlaneType]] = []
    helicopters: List[Type[HelicopterType]] = []

    def __init__(self, _id, name):
        self.id = _id
        self.name = name
        self.vehicle_group = []  # type: List[VehicleGroup]
        self.ship_group = []  # type: List[ShipGroup]
        self.plane_group = []  # type: List[PlaneGroup]
        self.helicopter_group = []  # type: List[HelicopterGroup]
        self.static_group = []  # type: List[StaticGroup]
        self.current_callsign_id = 99
        self.current_callsign_category = {}  # type: Dict[str,int]
        self._tail_numbers: Set[str] = set()

    def add_vehicle_group(self, vgroup):
        self.vehicle_group.append(vgroup)

    def add_ship_group(self, sgroup):
        self.ship_group.append(sgroup)

    def add_plane_group(self, pgroup):
        self.plane_group.append(pgroup)

    def add_helicopter_group(self, hgroup):
        self.helicopter_group.append(hgroup)

    def add_aircraft_group(self, group: FlyingGroup) -> None:
        if group.units[0].unit_type.helicopter:
            assert isinstance(group, HelicopterGroup)
            self.helicopter_group.append(group)
        else:
            assert isinstance(group, PlaneGroup)
            self.plane_group.append(group)

    def add_static_group(self, sgroup):
        self.static_group.append(sgroup)

    def remove_static_group(self, sgroup):
        for i in range(0, len(self.static_group)):
            if sgroup.id == self.static_group[i].id:
                del self.static_group[i]
                return True

        return False

    def find_group(self, group_name, search="exact"):
        groups = [self.vehicle_group,
                  self.ship_group,
                  self.plane_group,
                  self.helicopter_group,
                  self.static_group]
        for search_group in groups:
            for group in search_group:
                if find_map[search](group.name, group_name):
                    return group
        return None

    def find_vehicle_group(self, name: str, search="exact"):
        for group in self.vehicle_group:
            if find_map[search](group.name, name):
                return group
        return None

    def find_ship_group(self, name: str, search="exact"):
        for group in self.ship_group:
            if find_map[search](group.name, name):
                return group
        return None

    def find_plane_group(self, name: str, search="exact"):
        for group in self.plane_group:
            if find_map[search](group.name, name):
                return group

    def find_helicopter_group(self, name: str, search="exact"):
        for group in self.helicopter_group:
            if find_map[search](group.name, name):
                return group

    def find_static_group(self, name: str, search="exact"):
        for group in self.static_group:
            if find_map[search](group.name, name):
                return group
        return None

    def vehicle_group_within(self, point, distance) -> List[Group]:
        """Return all vehicle groups within the radius of a given point.

        Args:
            point(mapping.Point): Center of circle
            distance: Distance to the point

        Returns:
            Sequence of vehicle groups within range.
        """
        return [x for x in self.vehicle_group if x.position.distance_to_point(point) < distance]

    def static_group_within(self, point, distance) -> List[Group]:
        """Return all static groups within the radius of a given point.

        Args:
            point(mapping.Point): Center of circle
            distance: Distance to the point

        Returns:
            Sequence of static groups within range.
        """
        return [x for x in self.static_group if x.position.distance_to_point(point) < distance]

    def next_callsign_id(self):
        self.current_callsign_id += 1
        return self.current_callsign_id

    def next_callsign_category(self, category):
        if category not in self.current_callsign_category:
            self.current_callsign_category[category] = 0
            return self.callsign.get(category)[0]

        self.current_callsign_category[category] += 1
        if self.current_callsign_category[category] >= len(self.callsign[category]):
            self.current_callsign_category[category] = 0
        return self.callsign.get(category)[self.current_callsign_category[category]]

    @property
    def unused_onboard_numbers(self) -> Set[str]:
        return self._tail_numbers

    def reset_onboard_numbers(self):
        """
        Resets/clears reserved onboard numbers for this country.
        :return:
        """
        self._tail_numbers = set()

    def reserve_onboard_num(self, number: str) -> bool:
        """
        Reserve the give onboard_num (tail number), if already used return True.
        :param int number: onboard num
        :return: True if number is already in use, else False
        """
        is_in = number in self._tail_numbers
        self._tail_numbers.add(number)
        return is_in

    def next_onboard_num(self) -> str:
        free_set = {"{:03}".format(x) for x in range(10, 999)} - self._tail_numbers
        tailnum = free_set.pop()
        self.reserve_onboard_num(tailnum)
        return tailnum

    def dict(self):
        d = {
            "name": self.name,
            "id": self.id
        }

        if self.vehicle_group:
            d["vehicle"] = {"group": {}}
            i = 1
            for vgroup in self.vehicle_group:
                d["vehicle"]["group"][i] = vgroup.dict()
                i += 1

        if self.ship_group:
            d["ship"] = {"group": {}}
            i = 1
            for group in self.ship_group:
                d["ship"]["group"][i] = group.dict()
                i += 1

        if self.plane_group:
            d["plane"] = {"group": {}}
            i = 1
            for plane_group in self.plane_group:
                d["plane"]["group"][i] = plane_group.dict()
                i += 1

        if self.helicopter_group:
            d["helicopter"] = {"group": {}}
            i = 1
            for group in self.helicopter_group:
                d["helicopter"]["group"][i] = group.dict()
                i += 1

        if self.static_group:
            d["static"] = {"group": {}}
            i = 1
            for static_group in self.static_group:
                d["static"]["group"][i] = static_group.dict()
                i += 1
        return d

    def __str__(self):
        return str(self.id) + "," + self.name + "," + str(self.vehicle_group) class ForcedOptions:

    class Views(Enum):
        OnlyMap = "optview_onlymap"
        MyAircraft = "optview_myaircraft"
        Allies = "optview_allies"
        OnlyAllies = "optview_onlyallies"
        All = "optview_all"

    class CivilTraffic(Enum):
        Off = ""
        Low = "low"
        Medium = "medium"
        High = "high"

    class GEffect(Enum):
        None_ = ""
        Game = "game"
        Realistic = "realistic"

    class Labels(Enum):
        None_ = 0
        Full = 1
        Abbreviate = 2
        DotOnly = 3
        NeutralDot = 4

    def __init__(self):
        # In the order they appear in the ME.
        self.permit_crash: Optional[bool] = None
        self.external_views: Optional[bool] = None
        self.options_view: Optional[ForcedOptions.Views] = None
        self.labels: Optional[ForcedOptions.Labels] = None
        self.easy_flight: Optional[bool] = None
        self.easy_radar: Optional[bool] = None
        self.immortal: Optional[bool] = None
        self.fuel: Optional[bool] = None
        self.weapons: Optional[bool] = None
        self.easy_communication: Optional[bool] = None
        self.radio: Optional[bool] = None
        self.unrestricted_satnav: Optional[bool] = None
        self.padlock: Optional[bool] = None
        self.wake_turbulence: Optional[bool] = None
        self.geffect: Optional[bool] = None
        self.accidental_failures = None
        self.mini_hud: Optional[bool] = None
        self.cockpit_visual_recon_mode: Optional[bool] = None
        self.user_marks: Optional[bool] = None
        self.civil_traffic: Optional[ForcedOptions.CivilTraffic] = None
        self.birds: Optional[int] = None
        self.cockpit_status_bar: Optional[bool] = None
        self.battle_damage_assessment: Optional[bool] = None

    def load_from_dict(self, d):
        self.fuel = d.get("fuel")
        self.easy_radar = d.get("easyRadar")
        self.mini_hud = d.get("miniHUD")
        self.accidental_failures = d.get("accidental_failures")
        if d.get("optionsView") is not None:
            self.options_view = ForcedOptions.Views(d["optionsView"])
        self.permit_crash = d.get("permitCrash")
        self.immortal = d.get("immortal")
        self.easy_communication = d.get("easyCommunication")
        self.cockpit_visual_recon_mode = d.get("cockpitVisualRM")
        self.easy_flight = d.get("easyFlight")
        self.radio = d.get("radio")
        if d.get("geffect") is not None:
            geffect = ForcedOptions.GEffect.Game.value if d["geffect"] == "reduced" else d["geffect"]
            self.geffect = ForcedOptions.GEffect(geffect)
        self.external_views = d.get("externalViews")
        self.birds = d.get("birds")
        if d.get("civTraffic") is not None:
            self.civil_traffic = ForcedOptions.CivilTraffic(d["civTraffic"])
        self.weapons = d.get("weapons")
        self.padlock = d.get("padlock")
        if "labels" in d:
            self.labels = ForcedOptions.Labels(d["labels"])
        self.unrestricted_satnav = d.get("unrestrictedSATNAV")
        self.wake_turbulence = d.get("wakeTurbulence")
        self.cockpit_status_bar = d.get("cockpitStatusBarAllowed")
        self.battle_damage_assessment = d.get("RBDAI")
        self.user_marks = d.get("userMarks")

    def dict(self):
        d = {}
        if self.fuel is not None:
            d["fuel"] = self.fuel
        if self.easy_radar is not None:
            d["easyRadar"] = self.easy_radar
        if self.mini_hud is not None:
            d["miniHUD"] = self.mini_hud
        if self.accidental_failures is not None:
            d["accidental_failures"] = self.accidental_failures
        if self.options_view is not None:
            d["optionsView"] = self.options_view.value
        if self.permit_crash is not None:
            d["permitCrash"] = self.permit_crash
        if self.immortal is not None:
            d["immortal"] = self.immortal
        if self.easy_communication is not None:
            d["easyCommunication"] = self.easy_communication
        if self.cockpit_visual_recon_mode is not None:
            d["cockpitVisualRM"] = self.cockpit_visual_recon_mode
        if self.easy_flight is not None:
            d["easyFlight"] = self.easy_flight
        if self.radio is not None:
            d["radio"] = self.radio
        if self.geffect is not None:
            d["geffect"] = self.geffect.value
        if self.external_views is not None:
            d["externalViews"] = self.external_views
        if self.birds is not None:
            d["birds"] = self.birds
        if self.civil_traffic is not None:
            d["civTraffic"] = self.civil_traffic.value
        if self.weapons is not None:
            d["weapons"] = self.weapons
        if self.padlock is not None:
            d["padlock"] = self.padlock
        if self.labels is not None:
            d["labels"] = self.labels.value
        if self.unrestricted_satnav is not None:
            d["unrestrictedSATNAV"] = self.unrestricted_satnav
        if self.wake_turbulence is not None:
            d["wakeTurbulence"] = self.wake_turbulence
        if self.cockpit_status_bar is not None:
            d["cockpitStatusBarAllowed"] = self.cockpit_status_bar
        if self.battle_damage_assessment is not None:
            d["RBDAI"] = self.battle_damage_assessment
        if self.user_marks is not None:
            d["userMarks"] = self.user_marks
        return d class Goals:
    def __init__(self):
        self.goals = {
            "red": [],  # type list[Goal]
            "blue": [],  # type list[Goal]
            "offline": []  # type list[Goal]
        }

    def load_from_dict(self, data):
        for x in data:
            g = Goal()
            g.load_from_dict(data[x])
            self.goals[data[x]["side"].lower()].append(g)

    def add_red(self, g: Goal):
        g.side = "RED"
        self.goals["red"].append(g)

    def add_blue(self, g: Goal):
        g.side = "BLUE"
        self.goals["blue"].append(g)

    def add_offline(self, g: Goal):
        g.side = "OFFLINE"
        self.goals["offline"].append(g)

    def generate_result(self):
        d = {
            "total": len(self.goals["blue"]) + len(self.goals["red"]) + len(self.goals["offline"]),
            "blue": {},
            "red": {},
            "offline": {}
        }
        funcstr = "if mission.result.{side}.conditions[{idx}]() then mission.result.{side}.actions[{idx}]() end"
        for side in ["blue", "red", "offline"]:
            d[side]["conditions"] = {i + 1: condition.Condition.condition_str(self.goals[side][i].rules)
                                     for i in range(0, len(self.goals[side]))}
            d[side]["actions"] = {i + 1: "a_set_mission_result(" + str(self.goals[side][i].score) + ")"
                                  for i in range(0, len(self.goals[side])) if self.goals[side][i].rules}
            d[side]["func"] = {
                i + 1: funcstr.format(side=side, idx=i + 1)
                for i in range(0, len(self.goals[side])) if self.goals[side][i].rules}
        return d

    def dict(self):
        return {i + 1: self.goals[side][i].dict()
                for side in ["blue", "red", "offline"] for i in range(0, len(self.goals[side]))} class GroundControl:
    def __init__(self):
        self.pilot_can_control_vehicles = False
        self.red_game_masters = 0
        self.red_tactical_commander = 0
        self.red_jtac = 0
        self.red_observer = 0

        self.blue_game_masters = 0
        self.blue_tactical_commander = 0
        self.blue_jtac = 0
        self.blue_observer = 0

        self.neutrals_game_masters = 0
        self.neutrals_tactical_commander = 0
        self.neutrals_jtac = 0
        self.neutrals_observer = 0

    def load_from_dict(self, d):
        if d is None:
            return
        self.pilot_can_control_vehicles = d["isPilotControlVehicles"]

        self.red_game_masters = int(d["roles"]["instructor"]["red"])
        self.red_tactical_commander = int(d["roles"]["artillery_commander"]["red"])
        self.red_jtac = int(d["roles"]["forward_observer"]["red"])
        self.red_observer = int(d["roles"]["observer"]["red"])

        self.blue_game_masters = int(d["roles"]["instructor"]["blue"])
        self.blue_tactical_commander = int(d["roles"]["artillery_commander"]["blue"])
        self.blue_jtac = int(d["roles"]["forward_observer"]["blue"])
        self.blue_observer = int(d["roles"]["observer"]["blue"])

        self.neutrals_game_masters = int(d["roles"]["instructor"].get("neutrals", 0))
        self.neutrals_tactical_commander = int(d["roles"]["artillery_commander"].get("neutrals", 0))
        self.neutrals_jtac = int(d["roles"]["forward_observer"].get("neutrals", 0))
        self.neutrals_observer = int(d["roles"]["observer"].get("neutrals", 0))

    def dict(self):
        return {
            "isPilotControlVehicles": self.pilot_can_control_vehicles,
            "roles": {
                "instructor": {
                    "red": self.red_game_masters,
                    "blue": self.blue_game_masters,
                    "neutrals": self.neutrals_game_masters
                },
                "artillery_commander": {
                    "red": self.red_tactical_commander,
                    "blue": self.blue_tactical_commander,
                    "neutrals": self.neutrals_tactical_commander
                },
                "forward_observer": {
                    "red": self.red_jtac,
                    "blue": self.blue_jtac,
                    "neutrals": self.neutrals_jtac
                },
                "observer": {
                    "red": self.red_observer,
                    "blue": self.blue_observer,
                    "neutrals": self.neutrals_observer
                },
            }
        } class StaticPoint:
    def __init__(self, position: mapping.Point) -> None:
        self.position = copy.copy(position)
        self.alt = 0
        self.type = ""
        self.name: str = ""
        self.speed = 0.0
        self.formation_template = ""
        self.action = PointAction.None_  # type: PointAction
        self.landing_refuel_rearm_time: Optional[int] = None

    def load_from_dict(self, d: Dict[str, Any], translation) -> None:
        self.alt = d["alt"]
        self.type = d["type"]
        self.position.x = d["x"]
        self.position.y = d["y"]
        self.action = PointAction(d["action"])
        self.formation_template = d["formation_template"]
        self.speed = d["speed"]
        if "name" in d:
            if d["name"].startswith("DictKey_Translation_"):
                self.name = str(translation.get_string(d["name"]))
            else:
                self.name = d["name"]
        try:
            self.landing_refuel_rearm_time = int(d["timeReFuAr"])
        except KeyError:
            self.landing_refuel_rearm_time = None

    def dict(self) -> Dict[str, Any]:
        if not isinstance(self.name, str):
            raise TypeError("Point name expected to be `str`")
        d = {
            "alt": self.alt,
            "type": self.type,
            "name": self.name,
            "x": self.position.x,
            "y": self.position.y,
            "speed": round(self.speed, 13),
            "formation_template": self.formation_template,
            "action": self.action.value
        }

        if self.landing_refuel_rearm_time is not None:
            d["timeReFuAr"] = self.landing_refuel_rearm_time

        return d class MovingPoint(StaticPoint):
    def __init__(self, position: mapping.Point) -> None:
        super().__init__(position)
        self.type = "Turning Point"
        self.action: PointAction = PointAction.TurningPoint
        self.alt_type = "BARO"
        self.ETA = 0
        self.ETA_locked = True
        self.speed_locked = True
        self.tasks: List[task.Task] = []
        self.properties: Optional[PointProperties] = None
        self.airdrome_id: Optional[int] = None
        self.helipad_id = None
        self.link_unit = None

    def load_from_dict(self, d, translation):
        super(MovingPoint, self).load_from_dict(d, translation)
        self.alt_type = d.get("alt_type", None)
        self.ETA_locked = d["ETA_locked"]
        self.ETA = d["ETA"]
        self.speed_locked = d["speed_locked"]
        if d.get("task") is not None:
            task_keys = sorted(d["task"]["params"]["tasks"].keys())
            for t in task_keys:
                self.tasks.append(task._create_from_dict(d["task"]["params"]["tasks"][t]))
        self.airdrome_id = d.get("airdromeId", None)
        self.helipad_id = d.get("helipadId", None)
        self.link_unit = d.get("linkUnit", None)
        if d.get("properties"):
            self.properties = PointProperties()
            self.properties.load_from_dict(d.get("properties"))
        else:
            self.properties = None

    def find_task(self, task_type):
        """Searches tasks in this point for the given task class

        :param task_type: task class to search, :py:mod:`dcs.task`
        :return: task instance if found, else None
        """
        for t in self.tasks:
            if isinstance(t, task_type):
                return t
        return None

    def add_task(self, task_: task.Task):
        self.tasks.append(task_)

    def dict(self):
        d = super(MovingPoint, self).dict()
        d["alt_type"] = self.alt_type
        d["ETA"] = self.ETA
        d["ETA_locked"] = self.ETA_locked
        d["speed_locked"] = self.speed_locked
        tasks = {}
        for i in range(0, len(self.tasks)):
            self.tasks[i].number = i + 1
            tasks[i + 1] = self.tasks[i].dict()
        d["task"] = {
            "id": "ComboTask",
            "params": {
                "tasks": {i + 1: self.tasks[i].dict() for i in range(0, len(self.tasks))}
            }
        }
        if self.airdrome_id is not None:
            d["airdromeId"] = self.airdrome_id
        if self.helipad_id is not None:
            d["helipadId"] = self.helipad_id
        if self.link_unit is not None:
            d["linkUnit"] = self.link_unit
        if self.properties is not None:
            d["properties"] = self.properties.dict()
        return d class PointAction(Enum):
    None_ = ""
    TurningPoint = "Turning Point"
    FlyOverPoint = "Fly Over Point"
    FromParkingArea = "From Parking Area"
    FromParkingAreaHot = "From Parking Area Hot"
    FromRunway = "From Runway"
    Landing = "Landing"
    OnRoad = "On Road"
    OffRoad = "Off Road"
    LineAbreast = "Rank"
    Cone = "Cone"
    Vee = "Vee"
    Diamond = "Diamond"
    EchelonLeft = "EchelonL"
    EchelonRight = "EchelonR"
    FromGroundArea = "From Ground Area"
    FromGroundAreaHot = "From Ground Area Hot"
    LandingReFuAr = "LandingReFuAr"
    Custom = "Custom" class PointProperties:
    def __init__(self, vnav: VNav = VNav.V2D, scale: Scale = Scale.Enroute, steer: Steer = Steer.ToTo, angle=None):
        self.vnav = vnav
        self.scale = scale
        self.steer = steer
        if angle is not None:
            self.angle = 1
            self.vangle = angle
        else:
            self.angle = 0
            self.vangle = 0

    def load_from_dict(self, d):
        self.vnav = VNav(d.get("vnav", VNav.VNone.value))
        self.scale = Scale(d.get("scale", Scale.None_.value))
        self.steer = Steer(d.get("steer", Steer.None_.value))
        self.angle = d.get("angle", 0)
        self.vangle = d.get("vangle", 0)

    def dict(self):
        return {
            "vnav": self.vnav.value,
            "scale": self.scale.value,
            "angle": self.angle,
            "vangle": self.vangle,
            "steer": self.steer.value
        } class Translation:
    def __init__(self, _mission):
        self.strings = {}  # type: Dict[str,Dict[str,str]]
        self.mission = _mission

    def has_string(self, _id: str, lang: str = 'DEFAULT') -> bool:
        return _id in self.strings[lang]

    def set_string(self, _id, string, lang='DEFAULT'):
        if lang not in self.strings:
            self.strings[lang] = {}
        self.strings[lang][_id] = string
        return _id

    def get_string(self, _id: str) -> String:
        return String(_id, self)

    def create_string(self, s: str, lang: str = 'DEFAULT') -> String:
        _id = 'DictKey_Translation_{dict_id}'.format(dict_id=self.mission.next_dict_id())
        self.set_string(_id, s, lang)
        return String(_id, self)

    def delete_string(self, _id):
        for lang in self.strings:
            if _id in self.strings[lang]:
                del self.strings[lang][_id]

    def languages(self) -> List[str]:
        return list(self.strings.keys())

    def dict(self, lang='DEFAULT'):
        if lang in self.strings:
            return {x: self.strings[lang][x] for x in self.strings[lang]}
        return {}

    def __str__(self):
        return str(self.strings)

    def __repr__(self):
        return repr(self.strings) class String:
    def __init__(self, _id='', translation=None):
        self.translation = translation
        self.id = _id

    def set(self, text, lang='DEFAULT'):
        self.translation.set_string(self.id, text, lang)
        return str(self)

    def str(self, lang='DEFAULT'):
        if self.translation:
            return self.translation.strings[lang][self.id]

        return ""

    def __str__(self):
        return self.str('DEFAULT')

    def __repr__(self):
        return self.id + ":" + str(self) class ResourceKey:
    def __init__(self, res_key: str):
        self.res_key = res_key

    @property
    def key(self) -> str:
        return self.res_key

    def __str__(self):
        return self.res_key class Unit:
    def __init__(self, _id, terrain: Terrain, name: Optional[str] = None, type="") -> None:
        if type == "":
            breakpoint()
        self.type = type
        self._terrain = terrain
        self.position = mapping.Point(0, 0, self._terrain)
        self.heading = 0.0
        self.id = _id
        self.skill: Optional[Skill] = Skill.Average
        self.name: str = name if name else ""
        self.livery_id: Optional[str] = None

    def load_from_dict(self, d: Dict[str, Any]) -> None:
        self.position = mapping.Point(d["x"], d["y"], self._terrain)
        self.heading = math.degrees(d["heading"])
        self.skill = Skill(d.get("skill")) if d.get("skill") else None
        self.livery_id = d.get("livery_id")

    def clone(self, _id):
        new = copy.copy(self)
        new.id = _id
        return new

    def dict(self):
        if not isinstance(self.name, str):
            raise TypeError("Point name expected to be `str`")
        d = {
            "type": self.type,
            "x": self.position.x,
            "y": self.position.y,
            "heading": round(math.radians(self.heading), 13),
            "unitId": self.id,
            "name": self.name
        }
        if self.livery_id:
            d["livery_id"] = self.livery_id
        if self.skill is not None:
            d["skill"] = self.skill.value
        return d

    def __repr__(self):
        return self.__class__.__name__ + '(' + str(self.dict()) + ')' class Ship(Unit):
    def __init__(self, terrain: Terrain, id=None, name: Optional[str] = None, _type=None):
        super().__init__(id, terrain, name, _type.id)
        self.transportable = {"randomTransportable": False}
        self.frequency = 127500000

    def set_frequency(self, frequency: int) -> None:
        """Sets the communications frequency for this unit.

        Args:
            frequency: The frequency of the communications channel in hertz.
        """
        self.frequency = frequency

    def load_from_dict(self, d: Dict[str, Any]) -> None:
        super().load_from_dict(d)
        self.frequency = d.get("frequency", self.frequency)
        self.transportable = d["transportable"]

    def dict(self):
        d = super(Ship, self).dict()
        d["frequency"] = self.frequency
        d["transportable"] = self.transportable
        return d class Vehicle(Unit):
    def __init__(self, terrain: Terrain, id: Optional[int] = None, name: Optional[str] = None, _type="Sandbox"):
        super().__init__(id, terrain, name, _type)
        self.player_can_drive = False
        self.transportable = {"randomTransportable": False}

    def load_from_dict(self, d: Dict[str, Any]) -> None:
        super().load_from_dict(d)
        self.player_can_drive = d["playerCanDrive"]
        self.transportable = d["transportable"]

    def dict(self):
        d = super(Vehicle, self).dict()
        d["playerCanDrive"] = self.player_can_drive
        d["transportable"] = self.transportable
        return d class Static(Unit):
    def __init__(self, unit_id: int, name: Optional[str], _type: Union[str, Type[UnitType]], terrain: Terrain) -> None:
        from .planes import PlaneType
        from .helicopters import HelicopterType

        if isinstance(_type, str):
            _id = _type
        else:
            _id = _type.id

        super().__init__(unit_id, terrain, name, _id)
        self.skill = None
        self.shape_name: Optional[str] = None
        self.rate = None
        self.mass = None

        if not isinstance(_type, str):
            if issubclass(_type, StaticType):
                self.category = _type.category
                self.can_cargo = _type.can_cargo
                self.shape_name = _type.shape_name
                self.rate = _type.rate
                self.mass = 1000 if _type.can_cargo else None
            elif issubclass(_type, PlaneType):
                self.category = "Planes"
                self.can_cargo = False
            elif issubclass(_type, HelicopterType):
                self.category = "Helicopters"
                self.can_cargo = False
            elif issubclass(_type, ShipType):
                self.category = "Ships"
                self.can_cargo = False
            elif issubclass(_type, VehicleType):
                self.category = "Vehicles"
                self.can_cargo = False

    def load_from_dict(self, d: Dict[str, Any]) -> None:
        super().load_from_dict(d)
        self.can_cargo = d.get("canCargo", False)
        self.category = d["category"]
        self.shape_name = d.get("shape_name", None)
        self.rate = d.get("rate")
        self.mass = d.get("mass")

    def dict(self):
        d = super(Static, self).dict()
        d["category"] = self.category
        d["canCargo"] = self.can_cargo
        if self.shape_name is not None:
            d["shape_name"] = self.shape_name
        if self.rate is not None:
            d["rate"] = self.rate
        if self.mass is not None:
            d["mass"] = self.mass
        return d class Plane(FlyingUnit):
    def __init__(self, terrain: Terrain, _id=None, name=None, _type: Type[PlaneType] = A_10C, _country=None):
        super().__init__(_id, name, _type, terrain, _country) class Helicopter(FlyingUnit):
    def __init__(self, terrain: Terrain, _id=None, name=None, _type: Type[HelicopterType] = Ka_50, _country=None):
        super().__init__(_id, name, _type, terrain, _country)
        self.rope_length = 15

    def load_from_dict(self, d):
        super(Helicopter, self).load_from_dict(d)
        if "ropeLength" in d:
            self.rope_length = d["ropeLength"]

    def dict(self):
        d = super(Helicopter, self).dict()
        if self.rope_length:
            d["ropeLength"] = self.rope_length
        return d class HelicopterType(FlyingType):
    helicopter = True class StatusMessage:
    def __init__(self, message: str, type_: MessageType, severity: MessageSeverity = MessageSeverity.ERROR):
        self._message = message
        self._type = type_
        self._severity = severity

    @property
    def type(self) -> MessageType:
        return self._type

    @property
    def severity(self) -> MessageSeverity:
        return self._severity

    @property
    def message(self) -> str:
        return self._message class MessageType(IntEnum):
    ONBOARD_NUM_DUPLICATE = 0
    PARKING_SLOT_NOT_VALID = 1
    PARKING_SLOTS_FULL = 3
    MISSION_FORMAT_OLD = 4 class MessageSeverity(IntEnum):
    INFO = 0
    WARN = 1
    ERROR = 2
import copy
import os
import sys
import tempfile
import zipfile
import random
import dcs.countries as countries
import dcs.helicopters as helicopters
import dcs.lua as lua
import dcs.mapping as mapping
import dcs.planes as planes
import dcs.task as task
import dcs.unitgroup as unitgroup
import dcs.unittype as unittype
import dcs.weather as weather
import dcs.triggers as triggers
import dcs.terrain as terrain_
import dcs.condition as condition
import dcs.action as action
import dcs.unit as unit
from collections import defaultdict
from datetime import datetime, timezone, timedelta
from enum import Enum
from pathlib import Path
from typing import Any, List, Dict, Sequence, Union, Optional, Type
from dcs.coalition import Coalition
from dcs.drawing.drawings import Drawings
from dcs.terrain.terrain import Terrain, Warehouses
from dcs.triggers import Triggers
from dcs.country import Country
from dcs.forcedoptions import ForcedOptions
from dcs.goals import Goals
from dcs.groundcontrol import GroundControl
from dcs.point import StaticPoint, MovingPoint, PointAction, PointProperties
from dcs.translation import Translation, String, ResourceKey
from dcs.unit import Unit, Ship, Vehicle, Static
from dcs.flyingunit import Plane, Helicopter
from dcs.helicopters import HelicopterType
from dcs.planes import PlaneType
from dcs.status_message import StatusMessage, MessageType, MessageSeverity
        from dcs.unit import farp_mapping
        if terrain is None:
            terrain = terrain_.Caucasus()

        self.current_unit_id = 0
        self.current_group_id = 0
        self.current_dict_id = 0
        self.filename: Optional[str] = None

        self.translation = Translation(self)
        self.map_resource = MapResource(self)

        self._description_text = self.string("")
        self._description_bluetask = self.string("")
        self._description_redtask = self.string("")
        self._sortie = self.string("")
        self.pictureFileNameR: List[Union[ResourceKey, str]] = []
        self.pictureFileNameB: List[Union[ResourceKey, str]] = []
        self.version = Mission._CURRENT_MIZ_VERSION
        self.currentKey = 0
        self.start_time = datetime.fromtimestamp(1306886400 + 43200, timezone.utc)  # 01-06-2011 12:00:00 UTC
        self.random_weather = False
        """If set to True a random weather will be generated"""
        self.terrain = terrain
        self.triggerrules = triggers.Rules()
        self.triggers = Triggers(self.terrain)
        self.bypassed_triggers = None
        self.bypassed_trigrules = None
        self.bypassed_trig = None
        self.init_script_file = None
        self.init_script = None
        self.options = Options()
        self.warehouses = Warehouses(self.terrain)
        self.goals = Goals()
        self.drawings = Drawings(self.terrain)
        blue = Coalition("blue")
        blue.add_country(countries.Australia())
        blue.add_country(countries.Belgium())
        blue.add_country(countries.Canada())
        blue.add_country(countries.Croatia())
        blue.add_country(countries.CzechRepublic())
        blue.add_country(countries.Denmark())
        blue.add_country(countries.France())
        blue.add_country(countries.Georgia())
        blue.add_country(countries.Germany())
        blue.add_country(countries.Israel())
        blue.add_country(countries.Italy())
        blue.add_country(countries.Norway())
        blue.add_country(countries.Poland())
        blue.add_country(countries.SouthKorea())
        blue.add_country(countries.Spain())
        blue.add_country(countries.TheNetherlands())
        blue.add_country(countries.UK())
        blue.add_country(countries.USA())
        blue.add_country(countries.Turkey())

        red = Coalition("red")
        red.add_country(countries.Abkhazia())
        red.add_country(countries.Belarus())
        red.add_country(countries.China())
        red.add_country(countries.Iran())
        red.add_country(countries.Kazakhstan())
        red.add_country(countries.NorthKorea())
        red.add_country(countries.Russia())
        red.add_country(countries.Serbia())
        red.add_country(countries.SouthOssetia())
        red.add_country(countries.Syria())
        red.add_country(countries.Ukraine())

        blue.bullseye = terrain.bullseye_blue
        red.bullseye = terrain.bullseye_red

        self.coalition = {"blue": blue, "red": red}  # type: Dict[str, Coalition]

        self.map = self.terrain.map_view_default  # type: terrain_.MapView

        # TODO: Unknown type. Not implemented.
        self.failures: Dict[str, Any] = {}
        self.groundControl = GroundControl()
        self.forced_options = ForcedOptions()
        self.needModules: Dict[str, bool] = {}
        self.weather = weather.Weather(self.terrain)
        # TODO used modules
        self.usedModules = {
            'Su-25A by Eagle Dynamics': True,
            'MiG-21Bis AI by Leatherneck Simulations': True,
            'UH-1H Huey by Belsimtek': True,
            'Su-25T by Eagle Dynamics': True,
            'F-86F Sabre by Belsimtek': True,
            'Su-27 Flanker by Eagle Dynamics': True,
            'Hawk T.1A AI by VEAO Simulations': True,
            'MiG-15bis AI by Eagle Dynamics': True,
            'Ka-50 Black Shark by Eagle Dynamics': True,
            'Combined Arms by Eagle Dynamics': True,
            'L-39C/ZA by Eagle Dynamics': True,
            'A-10C Warthog by Eagle Dynamics': True,
            'F-5E/E-3 by Belsimtek': True,
            'C-101 Aviojet': True,
            'TF-51D Mustang by Eagle Dynamics': True,
            './CoreMods/aircraft/MQ-9 Reaper': True,
            'C-101 Aviojet by AvioDev': True,
            'P-51D Mustang by Eagle Dynamics': True,
            'A-10A by Eagle Dynamics': True,
            'L-39C': True,
            'World War II AI Units by Eagle Dynamics': True,
            'MiG-15bis by Belsimtek': True,
            'F-15C': True,
            'Flaming Cliffs by Eagle Dynamics': True,
            'Bf 109 K-4 by Eagle Dynamics': True,
            'Mi-8MTV2 Hip by Belsimtek': True,
            'MiG-21Bis by Leatherneck Simulations': True,
            'M-2000C by RAZBAM Sims': True,
            'M-2000C AI by RAZBAM Sims': True,
            'FW-190D9 Dora by Eagle Dynamics': True,
            'Caucasus': True,
            'Hawk T.1A by VEAO Simulations': True,
            'F-86F Sabre AI by Eagle Dynamics': True
        }

        self.aircraft_kneeboards: Dict[Type[unittype.FlyingType], List[Path]] = defaultdict(list)


    def load_file(self, filename: str, bypass_triggers: bool = False) -> List[StatusMessage]: