From df80ec635f2ea1684d6fc3965c1d447f31523619 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Tue, 17 Nov 2020 20:17:29 -0800 Subject: [PATCH 01/27] Add a new miz file based campaign generator. Defining a campaign using a miz file instead of as JSON has a number of advantages: * Much easier for players to mod their campaigns. * Easier to see the big picture of how objective locations will be laid out, since every control point can be seen at once. * No need to associate objective locations to control points explicitly; the campaign generator can claim objectives for control points based on distance. * Easier to create an IADS that performs well. * Non-random campaigns are easier to make. The downside is duplication across campaigns, and a less structured data format for complex objects. The former is annoying if we have to fix a bug that appears in a dozen campaigns. It's less an annoyance for needing to start from scratch since the easiest way to create a campaign will be to copy the "full" campaign for the given theater and prune it. So far I've implemented control points, base defenses, and front lines. Still need to add support for non-base defense TGOs. This currently doesn't do anything for the `radials` property of the `ControlPoint` because I'm not sure what those are. --- game/theater/conflicttheater.py | 249 +++++++++++++++++++++- game/theater/controlpoint.py | 46 +++- game/theater/start_generator.py | 60 +++--- qt_ui/windows/newgame/QCampaignList.py | 6 +- resources/campaigns/inherent_resolve.json | 79 +------ resources/campaigns/inherent_resolve.miz | Bin 0 -> 28105 bytes 6 files changed, 322 insertions(+), 118 deletions(-) create mode 100644 resources/campaigns/inherent_resolve.miz diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index c0b373ce..d6605c47 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -1,13 +1,22 @@ from __future__ import annotations -import logging +import itertools import json +import logging from dataclasses import dataclass +from functools import cached_property from itertools import tee from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple, Union +from dcs import Mission +from dcs.countries import ( + CombinedJointTaskForcesBlue, + CombinedJointTaskForcesRed, +) +from dcs.country import Country from dcs.mapping import Point +from dcs.ships import CVN_74_John_C__Stennis, LHA_1_Tarawa from dcs.terrain import ( caucasus, nevada, @@ -16,11 +25,14 @@ from dcs.terrain import ( syria, thechannel, ) -from dcs.terrain.terrain import Terrain +from dcs.terrain.terrain import Airport, Terrain +from dcs.unitgroup import MovingGroup, ShipGroup, VehicleGroup +from dcs.vehicles import AirDefence, Armor from gen.flights.flight import FlightType from .controlpoint import ControlPoint, MissionTarget from .landmap import Landmap, load_landmap, poly_contains +from ..utils import nm_to_meter Numeric = Union[int, float] @@ -73,6 +85,193 @@ def pairwise(iterable): return zip(a, b) +class MizCampaignLoader: + BLUE_COUNTRY = CombinedJointTaskForcesBlue() + RED_COUNTRY = CombinedJointTaskForcesRed() + + CV_UNIT_TYPE = CVN_74_John_C__Stennis.id + LHA_UNIT_TYPE = LHA_1_Tarawa.id + FRONT_LINE_UNIT_TYPE = Armor.APC_M113.id + + EWR_UNIT_TYPE = AirDefence.EWR_55G6.id + SAM_UNIT_TYPE = AirDefence.SAM_SA_10_S_300PS_SR_64H6E.id + GARRISON_UNIT_TYPE = AirDefence.SAM_SA_19_Tunguska_2S6.id + + BASE_DEFENSE_RADIUS = nm_to_meter(2) + + def __init__(self, miz: Path, theater: ConflictTheater) -> None: + self.theater = theater + self.mission = Mission() + self.mission.load_file(str(miz)) + self.control_point_id = itertools.count(1000) + + # If there are no red carriers there usually aren't red units. Make sure + # both countries are initialized so we don't have to deal with None. + if self.mission.country(self.BLUE_COUNTRY.name) is None: + self.mission.coalition["blue"].add_country(self.BLUE_COUNTRY) + if self.mission.country(self.RED_COUNTRY.name) is None: + self.mission.coalition["red"].add_country(self.RED_COUNTRY) + + @staticmethod + def control_point_from_airport(airport: Airport) -> ControlPoint: + # TODO: Radials? + radials = LAND + + # The wiki says this is a legacy property and to just use regular. + size = SIZE_REGULAR + + # The importance is taken from the periodicity of the airport's + # warehouse divided by 10. 30 is the default, and out of range (valid + # values are between 1.0 and 1.4). If it is used, pick the default + # importance. + if airport.periodicity == 30: + importance = IMPORTANCE_MEDIUM + else: + importance = airport.periodicity / 10 + + cp = ControlPoint.from_airport(airport, radials, size, importance) + cp.captured = airport.is_blue() + + # Use the unlimited aircraft option to determine if an airfield should + # be owned by the player when the campaign is "inverted". + cp.captured_invert = airport.unlimited_aircrafts + + return cp + + def country(self, blue: bool) -> Country: + country = self.mission.country( + self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name) + # Should be guaranteed because we initialized them. + assert country + return country + + @property + def blue(self) -> Country: + return self.country(blue=True) + + @property + def red(self) -> Country: + return self.country(blue=False) + + def carriers(self, blue: bool) -> Iterator[ShipGroup]: + for group in self.country(blue).ship_group: + if group.units[0].type == self.CV_UNIT_TYPE: + yield group + + def lhas(self, blue: bool) -> Iterator[ShipGroup]: + for group in self.country(blue).ship_group: + if group.units[0].type == self.LHA_UNIT_TYPE: + yield group + + @property + def ewrs(self) -> Iterator[VehicleGroup]: + for group in self.blue.vehicle_group: + if group.units[0].type == self.EWR_UNIT_TYPE: + yield group + + @property + def sams(self) -> Iterator[VehicleGroup]: + for group in self.blue.vehicle_group: + if group.units[0].type == self.SAM_UNIT_TYPE: + yield group + + @property + def garrisons(self) -> Iterator[VehicleGroup]: + for group in self.blue.vehicle_group: + if group.units[0].type == self.GARRISON_UNIT_TYPE: + yield group + + @cached_property + def control_points(self) -> Dict[int, ControlPoint]: + control_points = {} + for airport in self.mission.terrain.airport_list(): + if airport.is_blue() or airport.is_red(): + control_point = self.control_point_from_airport(airport) + control_points[control_point.id] = control_point + + for blue in (False, True): + for group in self.carriers(blue): + control_point = ControlPoint.carrier( + "carrier", group.position, next(self.control_point_id)) + control_point.captured = blue + control_point.captured_invert = group.late_activation + control_points[control_point.id] = control_point + for group in self.lhas(blue): + control_point = ControlPoint.lha( + "lha", group.position, next(self.control_point_id)) + control_point.captured = blue + control_point.captured_invert = group.late_activation + control_points[control_point.id] = control_point + + return control_points + + @property + def front_line_path_groups(self) -> Iterator[VehicleGroup]: + for group in self.country(blue=True).vehicle_group: + if group.units[0].type == self.FRONT_LINE_UNIT_TYPE: + yield group + + @cached_property + def front_lines(self) -> Dict[str, ComplexFrontLine]: + # Dict of front line ID to a front line. + front_lines = {} + for group in self.front_line_path_groups: + # The unit will have its first waypoint at the source CP and the + # final waypoint at the destination CP. Intermediate waypoints + # define the curve of the front line. + waypoints = [p.position for p in group.points] + origin = self.mission.terrain.nearest_airport(waypoints[0]) + if origin is None: + raise RuntimeError( + f"No airport near the first waypoint of {group.name}") + destination = self.mission.terrain.nearest_airport(waypoints[-1]) + if destination is None: + raise RuntimeError( + f"No airport near the final waypoint of {group.name}") + + # Snap the begin and end points to the control points. + waypoints[0] = origin.position + waypoints[-1] = destination.position + front_line_id = f"{origin.id}|{destination.id}" + front_lines[front_line_id] = ComplexFrontLine(origin, waypoints) + self.control_points[origin.id].connect( + self.control_points[destination.id]) + self.control_points[destination.id].connect( + self.control_points[origin.id]) + return front_lines + + def objective_info(self, group: MovingGroup) -> Tuple[ControlPoint, int]: + closest = self.theater.closest_control_point(group.position) + distance = closest.position.distance_to_point(group.position) + return closest, distance + + def add_preset_locations(self) -> None: + for group in self.garrisons: + closest, distance = self.objective_info(group) + if distance < self.BASE_DEFENSE_RADIUS: + closest.preset_locations.base_garrisons.append(group.position) + else: + logging.warning( + f"Found garrison unit too far from base: {group.name}") + + for group in self.sams: + closest, distance = self.objective_info(group) + if distance < self.BASE_DEFENSE_RADIUS: + closest.preset_locations.base_air_defense.append(group.position) + else: + closest.preset_locations.sams.append(group.position) + + for group in self.ewrs: + closest, distance = self.objective_info(group) + closest.preset_locations.ewrs.append(group.position) + + def populate_theater(self) -> None: + for control_point in self.control_points.values(): + self.theater.add_controlpoint(control_point) + self.add_preset_locations() + self.theater.set_frontline_data(self.front_lines) + + class ConflictTheater: terrain: Terrain @@ -83,17 +282,35 @@ class ConflictTheater: land_poly = None # type: Polygon """ daytime_map: Dict[str, Tuple[int, int]] - frontline_data: Optional[Dict[str, ComplexFrontLine]] = None + _frontline_data: Optional[Dict[str, ComplexFrontLine]] = None def __init__(self): self.controlpoints: List[ControlPoint] = [] - self.frontline_data = FrontLine.load_json_frontlines(self) + self._frontline_data: Optional[Dict[str, ComplexFrontLine]] = None """ self.land_poly = geometry.Polygon(self.landmap[0][0]) for x in self.landmap[1]: self.land_poly = self.land_poly.difference(geometry.Polygon(x)) """ + @property + def frontline_data(self) -> Optional[Dict[str, ComplexFrontLine]]: + if self._frontline_data is None: + self.load_frontline_data_from_file() + return self._frontline_data + + def load_frontline_data_from_file(self) -> None: + if self._frontline_data is not None: + logging.warning("Replacing existing frontline data from file") + self._frontline_data = FrontLine.load_json_frontlines(self) + if self._frontline_data is None: + self._frontline_data = {} + + def set_frontline_data(self, data: Dict[str, ComplexFrontLine]) -> None: + if self._frontline_data is not None: + logging.warning("Replacing existing frontline data") + self._frontline_data = data + def add_controlpoint(self, point: ControlPoint, connected_to: Optional[List[ControlPoint]] = None): if connected_to is None: @@ -153,11 +370,21 @@ class ConflictTheater: def enemy_points(self) -> List[ControlPoint]: return [point for point in self.controlpoints if not point.captured] + def closest_control_point(self, point: Point) -> ControlPoint: + closest = self.controlpoints[0] + closest_distance = point.distance_to_point(closest.position) + for control_point in self.controlpoints[1:]: + distance = point.distance_to_point(control_point.position) + if distance < closest_distance: + closest = control_point + closest_distance = distance + return closest + def add_json_cp(self, theater, p: dict) -> ControlPoint: if p["type"] == "airbase": - airbase = theater.terrain.airports[p["id"]].__class__ + airbase = theater.terrain.airports[p["id"]] if "radials" in p.keys(): radials = p["radials"] @@ -188,7 +415,7 @@ class ConflictTheater: return cp @staticmethod - def from_json(data: Dict[str, Any]) -> ConflictTheater: + def from_json(directory: Path, data: Dict[str, Any]) -> ConflictTheater: theaters = { "Caucasus": CaucasusTheater, "Nevada": NevadaTheater, @@ -199,6 +426,12 @@ class ConflictTheater: } theater = theaters[data["theater"]] t = theater() + + miz = data.get("miz", None) + if miz is not None: + MizCampaignLoader(directory / miz, t).populate_theater() + return t + cps = {} for p in data["player_points"]: cp = t.add_json_cp(theater, p) @@ -376,10 +609,6 @@ class FrontLine(MissionTarget): """Returns a tuple of the two control points.""" return self.control_point_a, self.control_point_b - @property - def middle_point(self): - self.point_from_a(self.attack_distance / 2) - @property def attack_distance(self): """The total distance of all segments""" diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 46ac7e00..2e682107 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -1,9 +1,11 @@ from __future__ import annotations import itertools +import random import re +from dataclasses import dataclass, field from enum import Enum -from typing import Dict, Iterator, List, TYPE_CHECKING +from typing import Dict, Iterator, List, Optional, TYPE_CHECKING from dcs.mapping import Point from dcs.ships import ( @@ -36,6 +38,43 @@ class ControlPointType(Enum): FOB = 5 # A FOB (ground units only) +@dataclass +class PresetLocations: + base_garrisons: List[Point] = field(default_factory=list) + base_air_defense: List[Point] = field(default_factory=list) + + ewrs: List[Point] = field(default_factory=list) + sams: List[Point] = field(default_factory=list) + coastal_defenses: List[Point] = field(default_factory=list) + strike_locations: List[Point] = field(default_factory=list) + + @staticmethod + def _random_from(points: List[Point]) -> Optional[Point]: + if not points: + return None + point = random.choice(points) + points.remove(point) + return point + + def random_garrison(self) -> Optional[Point]: + return self._random_from(self.base_garrisons) + + def random_base_sam(self) -> Optional[Point]: + return self._random_from(self.base_air_defense) + + def random_ewr(self) -> Optional[Point]: + return self._random_from(self.ewrs) + + def random_sam(self) -> Optional[Point]: + return self._random_from(self.sams) + + def random_coastal_defense(self) -> Optional[Point]: + return self._random_from(self.coastal_defenses) + + def random_strike_location(self) -> Optional[Point]: + return self._random_from(self.strike_locations) + + class ControlPoint(MissionTarget): position = None # type: Point @@ -57,6 +96,7 @@ class ControlPoint(MissionTarget): self.at = at self.connected_objectives: List[TheaterGroundObject] = [] self.base_defenses: List[BaseDefenseGroundObject] = [] + self.preset_locations = PresetLocations() self.size = size self.importance = importance @@ -79,7 +119,7 @@ class ControlPoint(MissionTarget): def from_airport(cls, airport: Airport, radials: List[int], size: int, importance: float, has_frontline=True): assert airport obj = cls(airport.id, airport.name, airport.position, airport, radials, size, importance, has_frontline, cptype=ControlPointType.AIRBASE) - obj.airport = airport() + obj.airport = airport return obj @classmethod @@ -157,7 +197,7 @@ class ControlPoint(MissionTarget): else: return 0 - def connect(self, to): + def connect(self, to: ControlPoint) -> None: self.connected_points.append(to) self.stances[to.id] = CombatStance.DEFENSIVE diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 95bc1c69..b16302d9 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -4,7 +4,7 @@ import logging import math import pickle import random -from typing import Any, Dict, List, Optional +from typing import Any, Callable, Dict, List, Optional from dcs.mapping import Point from dcs.task import CAP, CAS, PinpointStrike @@ -13,6 +13,17 @@ from dcs.vehicles import AirDefence from game import Game, db from game.factions.faction import Faction from game.settings import Settings +from game.theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW +from game.theater.theatergroundobject import ( + BuildingGroundObject, + CarrierGroundObject, + EwrGroundObject, + LhaGroundObject, + MissileSiteGroundObject, + SamGroundObject, + ShipGroundObject, + VehicleGroupGroundObject, +) from game.version import VERSION from gen import namegen from gen.defenses.armor_group_generator import generate_armor_group @@ -34,17 +45,6 @@ from theater import ( ControlPointType, TheaterGroundObject, ) -from game.theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW -from game.theater.theatergroundobject import ( - EwrGroundObject, - SamGroundObject, - BuildingGroundObject, - CarrierGroundObject, - LhaGroundObject, - MissileSiteGroundObject, - ShipGroundObject, - VehicleGroupGroundObject, -) GroundObjectTemplates = Dict[str, Dict[str, Any]] @@ -317,10 +317,9 @@ class BaseDefenseGenerator: self.generate_base_defenses() def generate_ewr(self) -> None: - position = self._find_location() + position = self._find_location( + "EWR", self.control_point.preset_locations.random_ewr) if position is None: - logging.error("Could not find position for " - f"{self.control_point} EWR") return group_id = self.game.next_group_id() @@ -350,10 +349,9 @@ class BaseDefenseGenerator: self.generate_garrison() def generate_garrison(self) -> None: - position = self._find_location() + position = self._find_location( + "garrison", self.control_point.preset_locations.random_garrison) if position is None: - logging.error("Could not find position for " - f"{self.control_point} garrison") return group_id = self.game.next_group_id() @@ -368,10 +366,9 @@ class BaseDefenseGenerator: self.control_point.base_defenses.append(g) def generate_sam(self) -> None: - position = self._find_location() + position = self._find_location( + "SAM", self.control_point.preset_locations.random_base_sam) if position is None: - logging.error("Could not find position for " - f"{self.control_point} SAM") return group_id = self.game.next_group_id() @@ -385,10 +382,9 @@ class BaseDefenseGenerator: self.control_point.base_defenses.append(g) def generate_shorad(self) -> None: - position = self._find_location() + position = self._find_location( + "SHORAD", self.control_point.preset_locations.random_garrison) if position is None: - logging.error("Could not find position for " - f"{self.control_point} SHORAD") return group_id = self.game.next_group_id() @@ -401,7 +397,21 @@ class BaseDefenseGenerator: g.groups.append(group) self.control_point.base_defenses.append(g) - def _find_location(self) -> Optional[Point]: + def _find_location(self, position_type: str, + get_preset: Callable[[], None]) -> Optional[Point]: + position = get_preset() + if position is None: + logging.warning( + f"Found no preset location for {self.control_point} " + f"{position_type}. Falling back to random location." + ) + position = self._find_random_location() + if position is None: + logging.error("Could not find position for " + f"{self.control_point} {position_type}.") + return position + + def _find_random_location(self) -> Optional[Point]: position = find_location(True, self.control_point.position, self.game.theater, 400, 3200, [], True) diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 617869bc..822cfaca 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -29,8 +29,10 @@ class Campaign: data = json.load(campaign_file) sanitized_theater = data["theater"].replace(" ", "") - return cls(data["name"], f"Terrain_{sanitized_theater}", data.get("authors", "???"), - data.get("description", ""), ConflictTheater.from_json(data)) + return cls(data["name"], f"Terrain_{sanitized_theater}", + data.get("authors", "???"), + data.get("description", ""), + ConflictTheater.from_json(path.parent, data)) def load_campaigns() -> List[Campaign]: diff --git a/resources/campaigns/inherent_resolve.json b/resources/campaigns/inherent_resolve.json index fc5969a5..66befcd5 100644 --- a/resources/campaigns/inherent_resolve.json +++ b/resources/campaigns/inherent_resolve.json @@ -3,82 +3,5 @@ "theater": "Syria", "authors": "Khopa", "description": "

In this scenario, you start from Jordan, and have to fight your way through eastern Syria.

", - "player_points": [ - { - "type": "airbase", - "id": "King Hussein Air College", - "size": 1000, - "importance": 1.4 - }, - { - "type": "airbase", - "id": "Incirlik", - "size": 1000, - "importance": 1.4, - "captured_invert": true - }, - { - "type": "carrier", - "id": 1001, - "x": -210000, - "y": -200000, - "captured_invert": true - }, - { - "type": "lha", - "id": 1002, - "x": -131000, - "y": -161000, - "captured_invert": true - } - ], - "enemy_points": [ - { - "type": "airbase", - "id": "Khalkhalah", - "size": 1000, - "importance": 1.2 - }, - { - "type": "airbase", - "id": "Palmyra", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Tabqa", - "size": 1000, - "importance": 1 - }, - { - "type": "airbase", - "id": "Jirah", - "size": 1000, - "importance": 1, - "captured_invert": true - } - ], - "links": [ - [ - "Khalkhalah", - "King Hussein Air College" - ], - [ - "Incirlik", - "Incirlik" - ], - [ - "Khalkhalah", - "Palmyra" - ], - [ - "Palmyra", - "Tabqa" - ], - [ - "Jirah", - "Tabqa" - ] - ] + "miz": "inherent_resolve.miz" } \ No newline at end of file diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz new file mode 100644 index 0000000000000000000000000000000000000000..bee1a2d2a853e6b69c8fe5f1b1eba47b809bdbc6 GIT binary patch literal 28105 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 literal 0 HcmV?d00001 From 1ee0aafd9af1bf2593180fad50d8318a70aea257 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 20:58:12 -0800 Subject: [PATCH 02/27] Unify TGO location selection. We currently have three methods of choosing locations for TGOs: 1. From the campaign miz 2. From the per-CP mizdata files 3. Randomly Move the selection among these sources into a single place and use it everywhere that we search for a TGO location. Longer term methods 2 and 3 will be removed. --- game/theater/controlpoint.py | 53 ++-- game/theater/start_generator.py | 337 ++++++++++++------------ gen/locations/preset_location_finder.py | 2 +- 3 files changed, 200 insertions(+), 192 deletions(-) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 2e682107..f9ff79d8 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -1,6 +1,7 @@ from __future__ import annotations import itertools +import logging import random import re from dataclasses import dataclass, field @@ -38,6 +39,19 @@ class ControlPointType(Enum): FOB = 5 # A FOB (ground units only) +class LocationType(Enum): + BaseAirDefense = "base air defense" + Coastal = "coastal defense" + Ewr = "EWR" + Garrison = "garrison" + MissileSite = "missile site" + OffshoreStrikeTarget = "offshore strike target" + Sam = "SAM" + Ship = "ship" + Shorad = "SHORAD" + StrikeTarget = "strike target" + + @dataclass class PresetLocations: base_garrisons: List[Point] = field(default_factory=list) @@ -45,9 +59,12 @@ class PresetLocations: ewrs: List[Point] = field(default_factory=list) sams: List[Point] = field(default_factory=list) + offshore: List[Point] = field(default_factory=list) coastal_defenses: List[Point] = field(default_factory=list) strike_locations: List[Point] = field(default_factory=list) + fixed_sams: List[Point] = field(default_factory=list) + @staticmethod def _random_from(points: List[Point]) -> Optional[Point]: if not points: @@ -56,23 +73,25 @@ class PresetLocations: points.remove(point) return point - def random_garrison(self) -> Optional[Point]: - return self._random_from(self.base_garrisons) - - def random_base_sam(self) -> Optional[Point]: - return self._random_from(self.base_air_defense) - - def random_ewr(self) -> Optional[Point]: - return self._random_from(self.ewrs) - - def random_sam(self) -> Optional[Point]: - return self._random_from(self.sams) - - def random_coastal_defense(self) -> Optional[Point]: - return self._random_from(self.coastal_defenses) - - def random_strike_location(self) -> Optional[Point]: - return self._random_from(self.strike_locations) + def random_for(self, location_type: LocationType) -> Optional[Point]: + if location_type == LocationType.Garrison: + return self._random_from(self.base_garrisons) + if location_type == LocationType.Sam: + return self._random_from(self.sams) + if location_type == LocationType.BaseAirDefense: + return self._random_from(self.base_air_defense) + if location_type == LocationType.Ewr: + return self._random_from(self.ewrs) + if location_type == LocationType.Shorad: + return self._random_from(self.base_garrisons) + if location_type == LocationType.OffshoreStrikeTarget: + return self._random_from(self.offshore) + if location_type == LocationType.Ship: + return self._random_from(self.offshore) + if location_type == LocationType.StrikeTarget: + return self._random_from(self.strike_locations) + logging.error(f"Unknown location type: {location_type}") + return None class ControlPoint(MissionTarget): diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index b16302d9..35a829fb 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -4,7 +4,7 @@ import logging import math import pickle import random -from typing import Any, Callable, Dict, List, Optional +from typing import Any, Dict, Optional from dcs.mapping import Point from dcs.task import CAP, CAS, PinpointStrike @@ -13,6 +13,7 @@ from dcs.vehicles import AirDefence from game import Game, db from game.factions.faction import Faction from game.settings import Settings +from game.theater import LocationType from game.theater.conflicttheater import IMPORTANCE_HIGH, IMPORTANCE_LOW from game.theater.theatergroundobject import ( BuildingGroundObject, @@ -32,8 +33,7 @@ from gen.fleet.ship_group_generator import ( generate_lha_group, generate_ship_group, ) -from gen.locations.preset_location_finder import PresetLocationFinder -from gen.locations.preset_locations import PresetLocation +from gen.locations.preset_location_finder import MizDataLocationFinder from gen.missiles.missiles_group_generator import generate_missile_group from gen.sam.sam_group_generator import ( generate_anti_air_group, @@ -164,11 +164,155 @@ class GameGenerator: control_point.base.commision_units({unit_type: count_per_type}) +class LocationFinder: + def __init__(self, game: Game, control_point: ControlPoint) -> None: + self.game = game + self.control_point = control_point + self.miz_data = MizDataLocationFinder.compute_possible_locations( + game.theater.terrain.name, control_point.full_name) + + def location_for(self, location_type: LocationType) -> Optional[Point]: + position = self.control_point.preset_locations.random_for(location_type) + if position is not None: + return position + + logging.warning(f"No campaign location for %s at %s", + location_type.value, self.control_point) + position = self.random_from_miz_data( + location_type == LocationType.OffshoreStrikeTarget) + if position is not None: + return position + + logging.debug(f"No mizdata location for %s at %s", location_type.value, + self.control_point) + position = self.random_position(location_type) + if position is not None: + return position + + logging.error(f"Could not find position for %s at %s", + location_type.value, self.control_point) + return None + + def random_from_miz_data(self, offshore: bool) -> Optional[Point]: + if offshore: + locations = self.miz_data.offshore_locations + else: + locations = self.miz_data.ashore_locations + if self.miz_data.offshore_locations: + preset = random.choice(locations) + locations.remove(preset) + return preset.position + return None + + def random_position(self, location_type: LocationType) -> Optional[Point]: + # TODO: Flesh out preset locations so we never hit this case. + logging.warning("Falling back to random location for %s at %s", + location_type.value, self.control_point) + + is_base_defense = location_type in { + LocationType.BaseAirDefense, + LocationType.Garrison, + LocationType.Shorad, + } + + on_land = location_type not in { + LocationType.OffshoreStrikeTarget, + LocationType.Ship, + } + + avoid_others = location_type not in { + LocationType.Garrison, + LocationType.MissileSite, + LocationType.Sam, + LocationType.Ship, + LocationType.Shorad, + } + + if is_base_defense: + min_range = 400 + max_range = 3200 + elif location_type == LocationType.Ship: + min_range = 5000 + max_range = 40000 + elif location_type == LocationType.MissileSite: + min_range = 2500 + max_range = 40000 + else: + min_range = 10000 + max_range = 40000 + + position = self._find_random_position(min_range, max_range, + on_land, is_base_defense, + avoid_others) + + # Retry once, searching a bit further (On some big airbases, 3200 is too + # short (Ex : Incirlik)), but searching farther on every base would be + # problematic, as some base defense units would end up very far away + # from small airfields. + if position is None and is_base_defense: + position = self._find_random_position(3200, 4800, + on_land, is_base_defense, + avoid_others) + return position + + def _find_random_position(self, min_range: int, max_range: int, + on_ground: bool, is_base_defense: bool, + avoid_others: bool) -> Optional[Point]: + """ + Find a valid ground object location + :param on_ground: Whether it should be on ground or on sea (True = on + ground) + :param theater: Theater object + :param min_range: Minimal range from point + :param max_range: Max range from point + :param is_base_defense: True if the location is for base defense. + :return: + """ + near = self.control_point.position + others = self.control_point.ground_objects + + def is_valid(point: Optional[Point]) -> bool: + if point is None: + return False + + if on_ground and not self.game.theater.is_on_land(point): + return False + elif not on_ground and not self.game.theater.is_in_sea(point): + return False + + if avoid_others: + for other in others: + if other.position.distance_to_point(point) < 10000: + return False + + if is_base_defense: + # If it's a base defense we don't care how close it is to other + # points. + return True + + # Else verify that it's not too close to another control point. + for control_point in self.game.theater.controlpoints: + if control_point != self.control_point: + if control_point.position.distance_to_point(point) < 30000: + return False + for ground_obj in control_point.ground_objects: + if ground_obj.position.distance_to_point(point) < 10000: + return False + return True + + for _ in range(300): + # Check if on land or sea + p = near.random_point_within(max_range, min_range) + if is_valid(p): + return p + return None + + class ControlPointGroundObjectGenerator: def __init__(self, game: Game, control_point: ControlPoint) -> None: self.game = game self.control_point = control_point - self.preset_locations = PresetLocationFinder.compute_possible_locations(game.theater.terrain.name, control_point.full_name) + self.location_finder = LocationFinder(game, control_point) @property def faction_name(self) -> str: @@ -205,11 +349,9 @@ class ControlPointGroundObjectGenerator: self.generate_ship() def generate_ship(self) -> None: - point = find_location(False, self.control_point.position, - self.game.theater, 5000, 40000, [], False) + point = self.location_finder.location_for( + LocationType.OffshoreStrikeTarget) if point is None: - logging.error( - f"Could not find point for {self.control_point}'s navy") return group_id = self.game.next_group_id() @@ -223,27 +365,6 @@ class ControlPointGroundObjectGenerator: g.groups.append(group) self.control_point.connected_objectives.append(g) - def pick_preset_location(self, offshore=False) -> Optional[PresetLocation]: - """ - Return a preset location if any is setup and still available for this point - @:param offshore Whether this should be an offshore location - @:return The preset location if found; None if it couldn't be found - """ - if offshore: - if len(self.preset_locations.offshore_locations) > 0: - location = random.choice(self.preset_locations.offshore_locations) - self.preset_locations.offshore_locations.remove(location) - logging.info("Picked a preset offshore location") - return location - else: - if len(self.preset_locations.ashore_locations) > 0: - location = random.choice(self.preset_locations.ashore_locations) - self.preset_locations.ashore_locations.remove(location) - logging.info("Picked a preset ashore location") - return location - logging.info("No preset location found") - return None - class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator): def generate(self) -> bool: @@ -299,6 +420,7 @@ class BaseDefenseGenerator: def __init__(self, game: Game, control_point: ControlPoint) -> None: self.game = game self.control_point = control_point + self.location_finder = LocationFinder(game, control_point) @property def faction_name(self) -> str: @@ -317,8 +439,7 @@ class BaseDefenseGenerator: self.generate_base_defenses() def generate_ewr(self) -> None: - position = self._find_location( - "EWR", self.control_point.preset_locations.random_ewr) + position = self.location_finder.location_for(LocationType.Ewr) if position is None: return @@ -349,8 +470,7 @@ class BaseDefenseGenerator: self.generate_garrison() def generate_garrison(self) -> None: - position = self._find_location( - "garrison", self.control_point.preset_locations.random_garrison) + position = self.location_finder.location_for(LocationType.Garrison) if position is None: return @@ -366,8 +486,8 @@ class BaseDefenseGenerator: self.control_point.base_defenses.append(g) def generate_sam(self) -> None: - position = self._find_location( - "SAM", self.control_point.preset_locations.random_base_sam) + position = self.location_finder.location_for( + LocationType.BaseAirDefense) if position is None: return @@ -382,8 +502,7 @@ class BaseDefenseGenerator: self.control_point.base_defenses.append(g) def generate_shorad(self) -> None: - position = self._find_location( - "SHORAD", self.control_point.preset_locations.random_garrison) + position = self.location_finder.location_for(LocationType.Garrison) if position is None: return @@ -397,34 +516,6 @@ class BaseDefenseGenerator: g.groups.append(group) self.control_point.base_defenses.append(g) - def _find_location(self, position_type: str, - get_preset: Callable[[], None]) -> Optional[Point]: - position = get_preset() - if position is None: - logging.warning( - f"Found no preset location for {self.control_point} " - f"{position_type}. Falling back to random location." - ) - position = self._find_random_location() - if position is None: - logging.error("Could not find position for " - f"{self.control_point} {position_type}.") - return position - - def _find_random_location(self) -> Optional[Point]: - position = find_location(True, self.control_point.position, - self.game.theater, 400, 3200, [], True) - - # Retry once, searching a bit further (On some big airbase, 3200 is too short (Ex : Incirlik)) - # But searching farther on every base would be problematic, as some base defense units - # would end up very far away from small airfields. - # (I know it's not good for performance, but this is only done on campaign generation) - # TODO : Make the whole process less stupid with preset possible positions for each airbase - if position is None: - position = find_location(True, self.control_point.position, - self.game.theater, 3200, 4800, [], True) - return position - class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): def __init__(self, game: Game, control_point: ControlPoint, @@ -471,23 +562,14 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): obj_name = namegen.random_objective_name() template = random.choice(list(self.templates[category].values())) - offshore = category == "oil" + if category == "oil": + location_type = LocationType.OffshoreStrikeTarget + else: + location_type = LocationType.StrikeTarget # Pick from preset locations - location = self.pick_preset_location(offshore) - - # Else try the old algorithm - if location is None: - point = find_location(not offshore, - self.control_point.position, - self.game.theater, 10000, 40000, - self.control_point.ground_objects) - else: - point = location.position - + point = self.location_finder.location_for(location_type) if point is None: - logging.error( - f"Could not find point for {obj_name} at {self.control_point}") return object_id = 0 @@ -505,22 +587,8 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): self.control_point.connected_objectives.append(g) def generate_aa_site(self) -> None: - obj_name = namegen.random_objective_name() - - # Pick from preset locations - location = self.pick_preset_location(False) - - # If no preset location, then try the old algorithm - if location is None: - position = find_location(True, self.control_point.position, - self.game.theater, 10000, 40000, - self.control_point.ground_objects) - else: - position = location.position - + position = self.location_finder.location_for(LocationType.Sam) if position is None: - logging.error( - f"Could not find point for {obj_name} at {self.control_point}") return group_id = self.game.next_group_id() @@ -537,22 +605,8 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): self.generate_missile_site() def generate_missile_site(self) -> None: - - # Pick from preset locations - location = self.pick_preset_location(False) - - # If no preset location, then try the old algorithm - if location is None: - position = find_location(True, self.control_point.position, - self.game.theater, 2500, 40000, - [], False) - else: - position = location.position - - + position = self.location_finder.location_for(LocationType.MissileSite) if position is None: - logging.info( - f"Could not find point for {self.control_point} missile site") return group_id = self.game.next_group_id() @@ -591,68 +645,3 @@ class GroundObjectGenerator: generator = AirbaseGroundObjectGenerator(self.game, control_point, self.templates) return generator.generate() - - -# TODO: https://stackoverflow.com/a/19482012/632035 -# A lot of the time spent on mission generation is spent in this function since -# just randomly guess up to 1800 times and often fail. This is particularly -# problematic while trying to find placement for navies in Nevada. -def find_location(on_ground: bool, near: Point, theater: ConflictTheater, - min_range: int, max_range: int, - others: List[TheaterGroundObject], - is_base_defense: bool = False) -> Optional[Point]: - """ - Find a valid ground object location - :param on_ground: Whether it should be on ground or on sea (True = on - ground) - :param near: Point - :param theater: Theater object - :param min_range: Minimal range from point - :param max_range: Max range from point - :param others: Other already existing ground objects - :param is_base_defense: True if the location is for base defense. - :return: - """ - point = None - for _ in range(300): - - # Check if on land or sea - p = near.random_point_within(max_range, min_range) - if on_ground and theater.is_on_land(p): - point = p - elif not on_ground and theater.is_in_sea(p): - point = p - - if point: - for angle in range(0, 360, 45): - p = point.point_from_heading(angle, 2500) - if on_ground and not theater.is_on_land(p): - point = None - break - elif not on_ground and not theater.is_in_sea(p): - point = None - break - if point: - for other in others: - if other.position.distance_to_point(point) < 10000: - point = None - break - - if point: - for control_point in theater.controlpoints: - if is_base_defense: - break - if control_point.position != near: - if point is None: - break - if control_point.position.distance_to_point(point) < 30000: - point = None - break - for ground_obj in control_point.ground_objects: - if ground_obj.position.distance_to_point(point) < 10000: - point = None - break - - if point: - return point - return None diff --git a/gen/locations/preset_location_finder.py b/gen/locations/preset_location_finder.py index 41386d90..4df32466 100644 --- a/gen/locations/preset_location_finder.py +++ b/gen/locations/preset_location_finder.py @@ -8,7 +8,7 @@ from gen.locations.preset_control_point_locations import PresetControlPointLocat from gen.locations.preset_locations import PresetLocation -class PresetLocationFinder: +class MizDataLocationFinder: @staticmethod def compute_possible_locations(terrain_name: str, cp_name: str) -> PresetControlPointLocations: From 13e372159acc99f0156a77a4c60b8ace1963596d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 21:19:12 -0800 Subject: [PATCH 03/27] Change default settings to match UI defaults. Doesn't affect the thing players see, but corrects the defaults when using the command line mission generator. --- game/settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/game/settings.py b/game/settings.py index ad496b65..ff73b63c 100644 --- a/game/settings.py +++ b/game/settings.py @@ -6,10 +6,10 @@ from typing import Dict, Optional class Settings: # Generator settings inverted: bool = False - do_not_generate_carrier: bool = False # TODO : implement - do_not_generate_lha: bool = False # TODO : implement - do_not_generate_player_navy: bool = True # TODO : implement - do_not_generate_enemy_navy: bool = True # TODO : implement + do_not_generate_carrier: bool = False + do_not_generate_lha: bool = False + do_not_generate_player_navy: bool = False + do_not_generate_enemy_navy: bool = False # Difficulty settings player_skill: str = "Good" From c1614ad5a7b98566931b4215e62acad54f683384 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 21:19:53 -0800 Subject: [PATCH 04/27] Add TODOs for carrier CP names. --- game/theater/conflicttheater.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index d6605c47..6039c4f9 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -191,12 +191,14 @@ class MizCampaignLoader: for blue in (False, True): for group in self.carriers(blue): + # TODO: Name the carrier. control_point = ControlPoint.carrier( "carrier", group.position, next(self.control_point_id)) control_point.captured = blue control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point for group in self.lhas(blue): + # TODO: Name the LHA. control_point = ControlPoint.lha( "lha", group.position, next(self.control_point_id)) control_point.captured = blue From ff751c30f9bcf90aab528b0b7dfaf8368df0c7e9 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 21:20:40 -0800 Subject: [PATCH 05/27] Add preset configuration for strike targets. --- game/theater/conflicttheater.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 6039c4f9..b0f2c1fb 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -17,6 +17,7 @@ from dcs.countries import ( from dcs.country import Country from dcs.mapping import Point from dcs.ships import CVN_74_John_C__Stennis, LHA_1_Tarawa +from dcs.statics import Fortification from dcs.terrain import ( caucasus, nevada, @@ -26,7 +27,7 @@ from dcs.terrain import ( thechannel, ) from dcs.terrain.terrain import Airport, Terrain -from dcs.unitgroup import MovingGroup, ShipGroup, VehicleGroup +from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup from dcs.vehicles import AirDefence, Armor from gen.flights.flight import FlightType @@ -96,6 +97,7 @@ class MizCampaignLoader: EWR_UNIT_TYPE = AirDefence.EWR_55G6.id SAM_UNIT_TYPE = AirDefence.SAM_SA_10_S_300PS_SR_64H6E.id GARRISON_UNIT_TYPE = AirDefence.SAM_SA_19_Tunguska_2S6.id + STRIKE_TARGET_UNIT_TYPE = Fortification.Workshop_A.id BASE_DEFENSE_RADIUS = nm_to_meter(2) @@ -181,6 +183,12 @@ class MizCampaignLoader: if group.units[0].type == self.GARRISON_UNIT_TYPE: yield group + @property + def strike_targets(self) -> Iterator[StaticGroup]: + for group in self.blue.static_group: + if group.units[0].type == self.STRIKE_TARGET_UNIT_TYPE: + yield group + @cached_property def control_points(self) -> Dict[int, ControlPoint]: control_points = {} @@ -242,7 +250,7 @@ class MizCampaignLoader: self.control_points[origin.id]) return front_lines - def objective_info(self, group: MovingGroup) -> Tuple[ControlPoint, int]: + def objective_info(self, group: Group) -> Tuple[ControlPoint, int]: closest = self.theater.closest_control_point(group.position) distance = closest.position.distance_to_point(group.position) return closest, distance @@ -267,6 +275,10 @@ class MizCampaignLoader: closest, distance = self.objective_info(group) closest.preset_locations.ewrs.append(group.position) + for group in self.strike_targets: + closest, distance = self.objective_info(group) + closest.preset_locations.strike_locations.append(group.position) + def populate_theater(self) -> None: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) From 5fb6a53cbdcc675feeb2a2c7b1e4f755f2ac2fd0 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 21:28:57 -0800 Subject: [PATCH 06/27] Add preset configuration for offshore types. --- game/theater/conflicttheater.py | 29 ++++++++++++++++++++++++++++- game/theater/controlpoint.py | 7 ++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index b0f2c1fb..8ad73838 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -16,7 +16,11 @@ from dcs.countries import ( ) from dcs.country import Country from dcs.mapping import Point -from dcs.ships import CVN_74_John_C__Stennis, LHA_1_Tarawa +from dcs.ships import ( + CVN_74_John_C__Stennis, + LHA_1_Tarawa, + USS_Arleigh_Burke_IIa, +) from dcs.statics import Fortification from dcs.terrain import ( caucasus, @@ -98,6 +102,8 @@ class MizCampaignLoader: SAM_UNIT_TYPE = AirDefence.SAM_SA_10_S_300PS_SR_64H6E.id GARRISON_UNIT_TYPE = AirDefence.SAM_SA_19_Tunguska_2S6.id STRIKE_TARGET_UNIT_TYPE = Fortification.Workshop_A.id + OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id + SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id BASE_DEFENSE_RADIUS = nm_to_meter(2) @@ -165,6 +171,12 @@ class MizCampaignLoader: if group.units[0].type == self.LHA_UNIT_TYPE: yield group + @property + def ships(self) -> Iterator[ShipGroup]: + for group in self.blue.ship_group: + if group.units[0].type == self.SHIP_UNIT_TYPE: + yield group + @property def ewrs(self) -> Iterator[VehicleGroup]: for group in self.blue.vehicle_group: @@ -189,6 +201,12 @@ class MizCampaignLoader: if group.units[0].type == self.STRIKE_TARGET_UNIT_TYPE: yield group + @property + def offshore_strike_targets(self) -> Iterator[StaticGroup]: + for group in self.blue.static_group: + if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE: + yield group + @cached_property def control_points(self) -> Dict[int, ControlPoint]: control_points = {} @@ -279,6 +297,15 @@ class MizCampaignLoader: closest, distance = self.objective_info(group) closest.preset_locations.strike_locations.append(group.position) + for group in self.offshore_strike_targets: + closest, distance = self.objective_info(group) + closest.preset_locations.offshore_strike_locations.append( + group.position) + + for group in self.ships: + closest, distance = self.objective_info(group) + closest.preset_locations.ships.append(group.position) + def populate_theater(self) -> None: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index f9ff79d8..b32ff97f 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -59,9 +59,10 @@ class PresetLocations: ewrs: List[Point] = field(default_factory=list) sams: List[Point] = field(default_factory=list) - offshore: List[Point] = field(default_factory=list) + ships: List[Point] = field(default_factory=list) coastal_defenses: List[Point] = field(default_factory=list) strike_locations: List[Point] = field(default_factory=list) + offshore_strike_locations: List[Point] = field(default_factory=list) fixed_sams: List[Point] = field(default_factory=list) @@ -85,9 +86,9 @@ class PresetLocations: if location_type == LocationType.Shorad: return self._random_from(self.base_garrisons) if location_type == LocationType.OffshoreStrikeTarget: - return self._random_from(self.offshore) + return self._random_from(self.offshore_strike_locations) if location_type == LocationType.Ship: - return self._random_from(self.offshore) + return self._random_from(self.ships) if location_type == LocationType.StrikeTarget: return self._random_from(self.strike_locations) logging.error(f"Unknown location type: {location_type}") From 20054b982574e374764bdb4214b3b14de11d85c9 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 21:29:21 -0800 Subject: [PATCH 07/27] Update Inherent Resolve campaign presets. None of these are placed precisely yet, but they're useful for testing. --- resources/campaigns/inherent_resolve.miz | Bin 28105 -> 38793 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index bee1a2d2a853e6b69c8fe5f1b1eba47b809bdbc6..881c28e423aa8e87d288b4c9953f88469ea164d5 100644 GIT binary patch delta 23150 zcmaI8by!vF7d1+EC?Q?aD2;ThG)PEycS?tg5Rpb2q@=sMkp@BO&P_;n*S7%A@0@$T z``kZx)@1uS)|_LG8GCJ@cLqAJ1O||ohK0j{fFMFx8}*^(SH3r)0~Yzu7Mxs*+sv|JG%FyeRgA`W7#xxq4AxuxB5wtJXg1DsaX zq_Wsm=(YfZ>$d}QC!Nm5DVJQTC#apPJ4>r$1B|8Ex^U)$@XRX#JfP}>-MerxisA-J9pCYpWV~%$hwwB14dq_6}u^E z*ETkj9PbSxa#@ZY_NvIfI$sN)(YY%*!8#7-lzmtI?N(^;5-(?K^3PQDK=Klo5OHr_ zc=p=Q$gFDsA!d;?}@U%_d}iV-dKjnJ+vbtXWfk0k?JI?dSxl*NNiIr6x+RNCwXpOi+OxI?d(Le1mNUgIBPu`X)k7UT$nU!EAeX zeQR;3jcNGt&T`%xpo z^C#8=6{hoiH5YhsHGUNVl0r#rN1EIhJ(AX1bFk(E@Bo0-e3Q(bhi)fSr1 zM(A~+x`%(Ry$XOOH(PFJAXWLU#b$R(EALP8S5bPh3^%L{{JZlOaa|9R!XXWA6b*PH z8EY$p(GUO+v-omqtg=9Gs+?BEb-q?UqgS9vi7@o3z=^6rP{L$hOX863z^LxdALNk* zr;I&0p^0$3_`H_SL)gosx{yD}94D&O-G(17e?jK`H-y6LbHjsT=iitmCYLp^xrTnNt`G~@w}&8*~$)7-d^^?ab2dT%;wyuW|d zhO3ShM)uBaRGM#&gz#keU2u?C)947}P=?O*26R?;eUeQeDTu-6J}S*V=O(b3m2m&R zO~9M#TyE_Cv1c|l>2_@$+DgeHb<-l`=<=W8a3&Q0SO?M&lr zj?*|eSw;>& zwzIloRRPw``24K z0gVB4ZP`l%%q!ZanWNj)nkBM#?svoy4jnA!1 zZfhIz%~m}2+|Dn(1nX~)2o^6P#!5xCf>&F|GXtwXIaS!G_%7NLOa<+hGN?W#1}ZLS zV_q&01?27l%HvWu;<*M?@Y#eP3rE<2l$+(_;F`OwmAQVd3fokquby?+r}?KFM+^Iu z+trIX!9a7GD)0+veUrJEuZm|Bf0t2EA!57bX?HO(1yN-CvmmgQ<7c7W;1y$ad6Xz5 zI|wlq%D{D*ScjBujj2NhRfC9*rB3vgzlt|W6~+N4au{-i636rWKNCFN&^^{zAOh!t zSne&1jDW>?d$-!W8!8gr9u(H@M3fiKRMuaG~SorGWQV4f(gRSfPTaQ#eR1L$tX zg|1GET*_Xhpx3=4*|XBZu{0i(XUyqJfTz4Nzu0lh@ALqQ&az#cQ;NC{mzigF=*ily z-|y$8epXL!+Bzy5Qf3P|9msrn5q4A&RLLYH1WcyrhUwsXUB*>xZOtnOQm=3LwajZB zSJRT-ZngwhUxm-Bx0JW}$A-~!EnQi!eu^Px9HB_K3Or2T|3EgLgts+1p7Tqo5s`JH zIJ9|;BWnqJmqekNs~>B_abZi8@z?pBz@Z>)n7DrE6~|k53|XMAvG$KP7v+A#R)3qd z0sy0G9dT43a<_eXIwK40;~SZUW)yH~&w2_ZxGk|8^V%i}dFl8SFWkDhIoZNi-)^4# z5UvjHIAYV*R==hVi@LpUuU60?K>NqP%_v;WVM)zF>Fp72 zBX{h!MmV+7^VmXFJZUju`~$6y4_kP2;wqXHl9q=Z1DG_DVfg`{Hia^n@CK_Go~H$S z@4CY~LWhzBh?#nawsvV*UAuaE){nJpYJa?SxLhl~Gu?A^iA(;P*{Hutbr9mxyrd)X zRR!M8NI%Rf7cg)BjAxjs-0T!FiX6cKCFxBi>@X)hEJYIXn+cALmV?au5woRa%eLIb=J7CABy@e(-_gB&?Ri#@`TIKqf<&$B%lN)k5WC0Zo< zD=;xAR8Cvu=Gf#LP>~?q%5+wBSI~IAzu0H7uI&vAV*?}w?g9xZO<P0w@p$PU0E7 zBqn$%CXjGfv3T*zb)9GzPGb3sk@=%{O5{_w1d?%34~Jl=fQjO@<50nZp4OR=6R2f5yLRy{*fAqWhSu+7iIjiOz95wIF}m`f z@q&3c1;)V@ag0;=bl#mUsDVW^B=Vgw;;Cx*F(OcBj>$eAGF~Q@LKub?0G}L+}(TxRnSb@vl0$MX# z+_&}H^IDfNJ~EC&AXKC6WX3k8cZ;;BAdCE`YciF!-?J_=>>j#h`Je4S2xOymCex_s zi$3=4_Yuv@+;M57w-y$?Ox;(;leH6BN0RY3Yx+G_5gknss8awLtjUv+5NgYWZy~;aqOM_esXu{c7WH%M7ZPlPF(ocK|I~a*OASBRO&6T&8$m z2yCT(#&0}}b~~tDXxgISw-gV}(u7Y51G ziJbsVLR$gn<@lI(%W2!y5ag_{a2ldYBf;3Ctn(pbF4@WHuA;UMjh)%yj=lj&qn$+Z z{BHkFUjRi7b5nTmi%1s|o0tQ;m|&xd)CcxAGDp`>oLiZM)M{8n-WVsbKjF17rM~EM zlOKrGVZi(2QPSe&d{Yfvs4CfMI`@ei&K&?w)!gf^s{jXbiUCt{*h8{mCW=F{z7FFF zcjn0iH3|(D5oyDUYEG|FF}RGRvP>=~W><-fd87@7z^~2(Fa=b)`qsxW9iCPi)94qN zMA@Pt7sjD(D?*NxgbZ91P>K5Ybo)cg`&}b;2u*O@_Bx{WHJ*Q5!;wMwBfR^118~G# z5#oOr+ynFghej*O5qI9#0Y*v@cl)D|3H?=qUt;DNp9o2d-s@vyk(amDp>Z%D-!Wp5 zLqTn!K(UZz8LIWmdY4Lg3&V6sVUt@O!67+!FbQeYut58=F`NeXSw^(c=O<=V%}v66 z>8gipS=qzDRq6T(=|kOSe}(da1CXeM`C*47^8fgU;O6msi-OW;s+mOzn0dX|7`qy6E5-5`>v(1<`K>kxGdCVu|Ndw&z>3 zHq|5(tMNgTeX5WU96u#}B^SU~dEe*rg(D5x(Z>0qVxQQKKYLkZoY+plof*182eWwx z<=GO?SNTxsNa+qi_Lr7V3+m}k^N{l}k-Un{|3qzIKgPIuALHeHj3;1>=+aE=*Fu?s zMw}?$RJW2Z^;Cg_xBdxYt`1Ue!=qkjLa5(@$&W=Arx^_KBlJ1$&45$rU{w|jVajrsuM z_zC1+d`D7HRC>#EBjF#<=^NY|C5YHBfG%Tbt&wEgYCQHbL~Oj@&vd1cX>9I3HA=I9 z-}?o&b*v^#O2A;_)p9X^HhWX`UVQ7@-o79XC##^DzTEUfXh z=rlCo#Z@3a9{;N25tywkN^covdfAe`t)t9|<`kI*T5rXbn63~#wKCuV8? zQm189P1TBo@w0x(bbx-0uRaVK>}TlqRsG5IVijckQ;XW={9IpEg%9RxVuqhM#OSF} z#}iKh3=T9x1a!a8Gimy>(rAP_u!N_?glmaAR#8p(0gQIF-sO{0Qv0RmsR8tz?S%Yj zqzIUC>-v6IU!?a-D_BuvIVD!78rm6DESt!!(Z166wm*G1;}kMp zYdCC5vlls6cj|<+BXB2`yr>St;wYTS(f^jNkN6KysDw=$ha_6JvOt5mEC|qFZe{spW=gc}|B{$m zjayUM3YS`*yE^=bp#KkOIy4*vlp60Jxf#kx{>C^f9O|oqO{`XaIGJCPxZp=JGoVrZ z{M&x$Epe);-5Uj_`U8NWe(cy_^<=QV8tmsIr_S*q1H!OTLG%C|bn)LTX80VlFz8B9 z=%ynq1Z)~8>xB;G7j6p&^*hN2B5);1;fjhPgr{lxk0%k(i9o~kgu^Ovgk{FURCBwN zV_UfWqTXPOPNcosi!cnCkOujIMa?vQctR$Tb}>MMwlGdZy+2ET&+3U$MySq~uSdI- z)FO)=J~tY%4J;@@eC|ef;%Z`IlfR;*wByzVg-&h9T^ZJnyF+8A1|8tj-n#V6E}qJ+ z77pV|Sd@pL=gqGsvU;#zK zkq)Xp)07N$QI z)(2J51_ld)*jqT*L=Rm;D<#m$PH)+hh9)cYnZ}Ftrcf;!P7x3NLYH|3dH4jirw^S3 z0lTKH<0;rubOf-)5wJh(I5!UfIzKz4d;*WEIwur;17oaG17(8(86S=_{pt`~#WTlUS> z=NeM1Wj3jS)``T`(BipM1DhtO z(p~1ebI)anJy}F~c4S$o_w=5_{*B$5rR;v}s{ZOoZ0pc!NVcuOmMLuOxOkj(Z+6)2 zC0Om@9;PTrljz(A)>Vfb{deszMp~m*G{xiBre zv=BEnY*%j0`_eR1D>yY*%QA}>ZhvSFuG;34($p{Au#+4;iBHEUKaEIfynP=TK;jDA zcWMO8nznXQ(9jXU^uWM;4EsqL{L{Z-e>+WxSG;HTVQ+AiO;GdL_;mXICcx?=FbwJ2 z>Ct>z6*yq)pz)^T>HjA2GSvMfPWb}s_oYsSZ^D4qmD@W2>~LcWL7%;@I0}_GtU&L8 z``+a$Yd$F{D>{|iq;g%aG339L>@giNq=>ts|N4GL+>ZzZOmWa~J>e%N;Qv`QbHNF& zAVl!b2PYmDCeCs%8cuqe>IeTo-4^q8h+J7zajHbwG&fusu7*(rnPqUKXo=8N*GAnB z^@mbKaKZ8e?$r~^?a?7~FCTCT>rC!|uLO%RC~&4Fs#bI!4x8^Eoj2L*EnjPlFmYpP zNJ*D#)J|1pQ*O{ThIHlXfjZx%y3ci6M-j_|SptpyVV7!c8q|5Rp24i7mN1xY>iEfO zfY#o>5gDwehkXs;$7V;ukl`n4qcYEbH6dx8VLy8nSI9n=XzPTOXz)^9yMI~k{v^K4 zEDAORG!F<_Six)cV^MbfU+zBEFepwXIBdLJ?jlL`??tF=3~PdAm^h6r3>F#6y;pI> zHdOcfl1K+o^n~@^E`ffkDc58U3aM%mnY7ER1K#l=Q*m}g_7X#Oa1X`9@{t0b5_dL7=~A_kug*-n8^?2d`6)yN70?z4a|Ro^clptsf?@X?(ria`N0Eg`i(=HM`2JRH_a38)fPFT&aw zR;i=&WEuaq769yFdo+F!QzLOIyj8&YVowCzFRkF9fd_3??^=!BzfTxddOMO(1M6b> zW-Msq@wu?@TaP(V{Y+jzW^+>zt=j>65nfKf_6<59}bt z!-BnEpO;l4@bAqD*g#5V>qO()522?9&rfT=)_UKMCn%uI_D*m)$OT)t5V`}(0LtEt z+?kGDo66kXwUwH8YuRqd#8kH7$(v|J9$m+%NzEs$4-LS^=S!;6XBe>9Y*N1@8&S)# z%equgfvpPq{yl`IG(E%kj#8PNLK!r!Uyw-WYMz?-=s1JJRiulUnfM?*S#1q70g8$l zZWb^RDK#O@z@hV~NSS&dI83TWt{zEmkhjd#uFTd<-norDd3m`^Cd+E;2;ifUFq z;JYX1(poPBWoi4aJ!W<;1&{BA&clR7Jh$hN)wQ-K$ks)p_4Ju#Ne$gJzp>!OVneW5 z;VhN+nZ)&mA_-$Tk7H00$CE!Yuv`LHigSPNO64O|>0ZQ4kP zdM&b#*p?cKeEY;#2n?< zRL#vZE6YHn>_#m@w$qo`K;#QN#oKg#JihQv2Y5xQmtXN>MDkU1B(+|KW#qU5l(MDN zwq&T!-lH+-5HXl^b@w;A&hE{h{0?&HcK-L-&-BI4ALZSO;=ShEp{?*=QEP%POub{3 zbOQAxoZrLvq@bf7o58t1`O}MjtadfuWL0e;gnnoy_8D{J=;tXfHbu%9iHABQO->1X z&lHWc@rgRg-99fOOdOCvq#*g^*caf~c#O1xBjNOW9EEM3PWt){qNIuI5UN6&8e1mz zlVOp^bB{)HVno>e3&}A7j%c*D0jq9n9?5|qngp$;x+is(D6cek*{TvnvUdcAC@<1R zH0H*i+$Y2I3OyZ{beWh#Y6NxuGM@vi{l}yL5A!5u{Lw(}Cf_s+UtXk5@?5`r!%2iB z`2P9=b5l))nlxoEVb6sLW9v;|sT{P^!B{lP_RFPnWeIra=P-Oo+vKGqqI%*PqSijG zVHDtdUq$tj-UArE`4^N0xC?Z;HyK81-QDCEz_P84@KZ#J!7NI=?p^T|mnH@GPKy%udH z*@Qw~Ee1ugUoFNm?<$0M5F3UM9zzeF>=Pxx)<=Z0jau6HoYv}iEZ^3;w>3=1TCW?0 z?|?ag1@i~L-g2n^G7>njT0BpVR@a#e8lUqzgq5(|uO>Q3omA$=^4Hg^78mQPgcjhA z(kUaOSoNy|+)vRTc8`AcvNm{8>UcG=yvhhg#I3}My(Zl{uMbS|zm}-SZSZxgy*d)qDrA?zpvZ)Bg(^!Awo1t;o zz=q<2MkA+LAwD9oQa8>w<3QWP?Hz(@7F?8tgMW3}HksKv2@?X{1qC_OBlhmRScmjl zn7)ypkNTj%UN@BwqGW(mq$qO3q1q}H3rmb^vnX@*&6}(g}85j_=nta={8fn50 ziGjh7lgHDPMP^ee_hPH2XLBYuj9~4l;jmcNG?fgn#NC!&f}B$D@tk5 zzm{aFhBEYKZN=A{MZmWN4Oi<{HFnOBglfJC8`|7S@1Tb@1|`txN@814V*Bwai5rzo z$|36Wlu%s_$v)I|TsIQjip^)}WLTd*$h~Di?Sz&`dwA?H<81&78YSpBL|rzlz>KHq zw_P|q4?BBgr?RnVQNsyA!Ydp?#0RppNJB-ssCNk}(M3Q6C%f(tJz3cpowr%`_X%njrm z-w2J(#xFzwG#WXLwh4@WaxP3|1AHP~7)Dy6+E%Z!dM2yox^T~Fdek1`f*cyj5SnR` zr}Dm`KZpr|u*Rng8dQ}I5eG(+r$zC_Amy6sYmjv?isqL zlCy`~7c?wicYS@`rL~o8S%>FWM&k`WNwTksUNk70UN?}kFGG0H-fA6wXP&g3eU+Fr zW4iL2aW!XTCd*!Aa|1iYgh;(@sICV!)xxu!V(Fo==-{znL_&929VJ`WV;ccr;;2VE zRg_RdWv|Q%mHyIi@5V9SlRYSIsMsaFRD>}PmzTiT)n!2d=Wk|p5*}$p{E&B0$mnb; zpa2f0Bg!n~x~#U6t?RJ$h@rZ`6?R-PDA?;t%Sutp3M>x;J9{@K&T*KKiZ(Sj!x+3W z4cj7`&RI5qGf^T?&)3CB+J=A#1|RmJ=r~*BL8l>A(1sQmkDy@i3Q!Q3X#-1o=q|Bp z%q~v&E(|aj1pFGPu1~|G)U9e7#-A|8g0}dNLdu5cxv7%^#~acELPFvHhpBcCZVB6! z2-_v|=%^J`7x3UIN{G{y-nOFNHdyk8-u6U#Axd5Ft=z7W8m>9A(erj~+-4hZPS6zF zq8rP5;T}p8jl~8w1Rbo=Mov8|e7&Dl^HZ^W&|Rc@;Gzw-_SVTN%q~IrF6aj*;}8vt zs9L$nLNa2yP!tDyTANDET`im9M5lnotONMENd`OCT6`@uCL6zCM$l;F)UqPb`uS?U z?z0E$Gfm`|;8U~WNX7TWGdar<>B~qA2;f?!nYQfRpNJw~miJ7XQHI=9;zLa^eZAhN zEVE?h;7){xeF`>srquuuT&zmapYb6)`%vUky((u1>gHifLuRQ4Mg{S^;qX0>@2Q0C z+EL2Y2on0(Vl^p@^R~($;=qWT>B5yDP$vqTNsY2;486i#eRs|f)hyxga6Fzoq)g+* z4*SYm1s;9JDl^S;R&kxDwCKFL236|w&0 z%P%T|1Yaae*TW8oih$|{Rr8&^8v~yHsf0SC(-(9@C6Zv&SIQ)s#(A|7)&fi`#tclE zPw+&wkdU!g>D4XY8!;=m4qoQ^A4tvMCq&2$i$9HiHMkYIfrXKS)Y_wG#NzNGgcvkd zs`OV{>G`@V=IWe1SSk33pVOtiC=OGUn7t){DN&S=0l){$tP!>FRMp(Cg|vtD1ZK@JTYmA`a8ZU5>%(Rfw27xd1B~J@8v3=<(}H6Z+d~5lsLL-VR7P(79gE^Cf0**{-etsuH z0i6Xv0QETo1tGs5HcrqGI)eX63F99rxgtxB^1^o^{G)fQxM*A`!vY+05vE$R%4MH} zeWM7RE_#%Cam^qAT9{N%6LD2Ud?N7K_qGT596o? zfbJv&Egd4d@uVh$?32OjS>%`1t>EVcV5!4{Bc8>Xp5^qhf$`&84nsjaSvaTZX&aB( z7^Hs|G-O3m`G=NYy1aC&pA`T9D@^Er*qp5SZ(#z6hCv|FY1^?AykF9ixWp_-@iF7v zDWhqNLXgYI@e%*+Ne>$V*gs;GoP*85)Snr!V4ysrQnzyVoVIaiwsQZoH5AfAg`jGU zzL~es&l=(-WWvY%;Pok7jNx_uN|=4=zvCU?dz|l+_DmKA8hcj#$xw!{EKF&P)v=0t zg)@kd_(yGkT?tih`(q4#rtDA~3iufjfB?;qeY zFHRMDP8x6-!?)5(39UjT!qYQct%~xMu^XHWa1IV-Vc9ti!-*AVdmTGJcs7v1m<`I7bIKlX>4ELPWci4mnW%*c{4C@v%I zwxPPc4O>|kYtgyKLF3@EwQP+_nSIBlc}K3b44xQI9#2=6o||*0y$q0UD?@y6VRdPe zew#Rsu1mS7QZC|j<@M2494SV}6G^Pkh0a|G>E>fk?a&@>KjUo(3mPSu#6)thn6hsM zYvz!-F&=%ThwpCo;y$GZzkl$6pLpg=GQ^YHxoeA#F5*nZZ5O?pf#ErI`uNaBx@j0L za33~!)Sk%B3N#=mK87>IVN<7k3(m}+4YQ!S;YT}Fm0-eRtjNM%ql8f|;)6~wVS=-w z4yDyTTI>ZyMwq)RK#CnEnP}1bEYjdO-%O*b{Nd*KcC!e?d>{|5UW=2j0t~AkJ>~qL zr}bXkKo~N27}i5zaRHDt#zk(Tu-7a@sBedf$Yd9Pl#S!^1ocTGsRX3k5&z5T>0efD zF}kg;l5HEX?Y6;u+PtdgA6F6gdzXqtKO@7UyBq)kWJCd zi4tk^g$idsi$*+;IG7h##R@}zxIF@K4RrT?-gL;g!v>b(ty_+)U0}jq{RM4h1r)CA zWauAc0_ugIn9u({TjS$&Si>Hcz1*h>hQ{wJe+1x*MrTtIdw^Wt^KSzk789Lj`1@e` zcUk1XM!a?FQK*Y=7|EYUSBJ{1Q^TBs!qETVJEHO0_=NTPg-QRm=lCI^Yo8l-9i)Zh z_=J-?LyJzbTLbV76X^z_>|Zz*3U{YLPQDvX}|KgfQ<_$}G>@9D*zkBg=((_h> z2U-8r!t<(NI3}g4F~0u&!yT`u)Yf5mxM!0l3 ztI9z6{6(w>E1o3`-uh-xNd_E_<(MGO3=R&!C(H`YqFERmk6Ak{gMRQi0h)PU?Nz4+ z??y7xw1(mPUS6$f=pbTh zh^W*a`u@XhQNuYw!frUi7xa(a8r5ocCDx<#pi#mENdXm-6=(GPTWCgXCY(80I`Vt0 zFxbEH1ZrTqNgwrj8j@`I;43ok>EFcrcNaVHlMdSkh2x&%d!h+4{pubU-?tYeBii$m zAC%5(tkM!Z9jqtsnO++6s3#}Wgcn30^9yh5F50(;0*#4)a9F?aIt2a&SLQXt7j2I< z8Waw}I#;8-2_hwX~R)*3Y>=!o;Dqex#3OKjmf>G%!lXyY_Y@K+l>I; z`W{zv34#O~C75L5Voes0z6y>nKBlUzJ2tkpJ9@EsOTa!iZm(XFdSYeF!DFE)9DMgx{laF+`U!_L8gbKoQPT~NG zv0)N=m3J?|A1UW}sA2??8qw~1eU)I6U_*u<6BT?g`E>uW=J(5#q!$hOblH@AiHrI_ z!10`nwYcjt{aTkhRfs~2TR+09xImpSWPeuVclYsYotzNjIgwm|N*Yjg^d-K`<1*+M z%PhydSr%SUW_cUIGG(xlXj0Iy8@;2Q2&a&VW{5QH*Nrg~k5uO<{@x?0)l}O{NZa+M z_Uc#dkM;ay=z%k0DXlwk6}C+yWJl(|`Gf=v>rGnbj_(?89FBHYpDoZE&Q|rN^iM^x z>K}V;;p)exgigF4wR#G~J=s~%(SHBjt2k;}A{LzBZZ0o&P~aIES;HK0_XP^azkV>G*qD6A>jBP$6s|=J?9Z(33$ZdGI_u#4fo+3 zI5T@`*&HI>!`XdSioDR?cILg;b|8=$BPU;}(W^I#;)$7+%r3fN7Gk3lf!Hi=xZp?S z8Gdpmz>(oh^_P-LqID0Bn13j#{j|5Vo{lu4WWNP?BP^`)vbW#1tSWC2;r&(O$ob+ikq98IT;ne4(gw5jMk%4`I+9)1?GkZH@ihCg=ukeD6iDHI$ zfs)2Du>HdOsLfvRu?)5Sw3YdjEVk3409ix)+|2kRue1eaiugpMl)3qb!psNMa3biZKgX@~ zJsWC1^ZAjjy)TIGFFSSb2vc>0n2nb4$X}UMG={&oR`OrY-WTMa4{(^*q-ct2ODx6O zYo{mrbSNgND8a4md*-wJ0MYpfk^OR|3zy6KZ+`o)B$NRzWjb4wJwJ%o8@xh%6C2GE zWvx#8IP#40ALonF<|#)gdl_|SK8Ft|tN)_J{1;^m1hIPn`NPf6W9KM`PtV3iLX3M%5t;Yua_xLR~2 zD~Un-@mBrj8(oL;C_!*!$g|j_BSF0_W8_^#izMf^8@*BP)n9AZ8}X%IEg(TjoOqnN zJ=CARt$JtnB{7Wh_{09N7Y<=>w+yR{(h#eN91X`8#o@J(op(JaE4m^hbduv+1n|X@ z9~}YiJ#o7CeRF?R2UvTqeHRn1fO!cb{F%=h}74KVFbYPUE_g%@cqh&aL=T26T*2yOQNP zO~dc`DF9p10uM?R9qt??zd6rBqQvF;UbafyT*idpGf ziAm&F_5+?Wv>I!$8!HL(J;1l)d!)}jh-k~S{tj$r$KSxp#eSvRLH*_%#9cmroc{Yb zy%uKJ)^~&GHdK;y+83&J%i}8Zz!Oea%k&`$9&O(}AB`Msu;Eq7J;;>iW#)XX#((M1 z$oZs^voEOTgPG?T5rbG#m80xR*7erXf7FN$NE_W?OTvui1>I<)bLx9&(Z*z4#lz-? z3Keg)L@U*c#q)pDfBy}75xez0T0!UiAote~FX$ZelbsYhSom2=>|1u&JTY-Vh`lyC z$G((@ilg8v{%!uZ@YU!4LNsE=o{4+XvZ#krTqpq z8s5sY^CRgI1G1LvY)<#s4Z&)`<)y~q^*?Jn6s!yn`V{m%y=bjw25-{2OiucH7F%Lw zcXK~hBjza2j_+Ap?uSQ5^eG0qVtD?OSY;mS_dE|EGE^j=0JdT2qJiNiOzh}TQIGO= z-%cd3T0HS_cy_#x^r_b?Q9m%5-Y2H=o6B_8_uQ%HOcc{ECn%Mdqve-ziV^!y?Amy! z3sn`xo35j($E64YVqhe()m=$rpSO!HM72!?9ode5+J#?!rV=#_F{hf|<=%}? zpc|^wm3jZVF%>O81-ik!@>E&=Ilgf^a(~MRXNOrROQH;t978SXP6H{O)inHspVDMB zUt9&PVV|Ilj`m1)x=C>DC$kDIApE_$QfPU$oq$?#fbx9gz6knJcC7B)Ysst-93y`C zysv-C;3i;=Wg8aV?fMcO)*VibWTv_YURX}wkMFoA)}S<%jAdW-CH^?kt0SG3+}TTQa&6x&pZsiXJAs$$-j9TS@^W-I8m!>}a9(NNYkk!{(<0Gg zl*50k%|ReKuq{}BTxs9=qK>ppoYvpb$cW>aWVW=Ofc!`$YB6F?_9Gq9UbBsf*|Kj3 zp4lT0nbeWyC1w-8l3j9V(Z;l_ViDuBy*~UP(C8wDyi>?AZB|Qq4={WWP>@OgH?6>I zYfv`&zX-nBgzzB3zE(IsS|H2M5tSET+_{yT)N3UM-SEDe)Pr1{$lPb(IK{d=m6ub*e0SJ&VudsB1~-Ou&>-I@pvN1B0vE zz44{tDV%#SGoSy-O89$vrBKC(q==Ijg#M|bs4z0Zh#sM@7U3VMmRLHf8Nm}Cq{UlQ}_&F!n5ky8+* z5&-A6BfV(0Rk_vey7=<2dxX5qDJ&+6sh2I3$`1(F7_kfbeo6}X1wBM{z7YWqgHlg3 zP*{0a;+fhGgz3SbhanA7%-*g`5+J}nWwp|xAL;tP;&7$Fqz}bXU@(8Jb-TmZ)<@@$ zk&^xz_aqAd$uI`Vc>TeE^yfW<(&>R9fF?+G7~3{F}y~RCNe!2Taye{W%4$KVUPCH~*ksaleJ# zH{--SkylEtWQ~&nK)BBPqwWSXUvZf=N7@-)f!9=p})fCWL_J3KdN=#d%${QFtd>V{m_Vi%SGWSPFdh+*7&9cI5Vg% zWuc7SLDKrct@J&|{cOrLd!$33Vyr8M!7KYJip@a%k!#+_PE#;B5a+CXa4B#pVZ!DXW$*p0w zCSku>P~&G2nKI$&@|`A!)Zya?7Xf^^Tf7g=``u%6D_H7`iDzq@Z*L5gZkBiRfyiHH zge4}<>hAbxuMCexW|YWex%Li>nX_NrPHgbM`?C<7!hO+>cO*6>k0d1PbxGGJRwB)y zN41An)xzIUPdqb=clKi6?jVJ59nz94xc%~WXH2*0cE|6#%FYy46x&^ZNww;Ctqzhje*$DH*lYGa&iPO@Se;C z&?af?%$=Bi7wxAH}`G9 zDN>`V+oFpgKTd>~Q_2QrA5tyG666EF;$h%!PN1RAAbutr$5&}H0oX^hfY`4yAPCk^+ zVb5*MIa&#a5%TkpFYE#-A7>YKrgJ^4PK;Em!{kC!Ndr9i_Ugy$;BVq95yg$FPv1G8 zp4EWAPf3fO`ser9#`?TdO})dyN}ycQ_AZG_0Vd?xJ4gM7^7`Lm+%{KHp(>6^oVJ)SHjfNa0 z??6WUxDfZ^N?q>&*D(gM<2T}#9s9e*E>7-_7Z?4D0|Ns)i~GA0va64< zMcpf^Zu-01&-kuh)9x{i@LD5xRC|tvjvy=)x5n?Dgzs9RUwb_%b~$jU~spDkK`Nn(Iv|-Ou{1c zewR-sIt^^^%v7MFM8=91c%R9VaJo83vsCH@q92>Q^-DZQ(^hVw zX+}q`aJ3gz6$h|$o8CZfyRNQqaxDcKyC~ui1IKGB%jDM_T}7$7HEqx8iGA}efq|c# z9&<@kCbmmI=UXl&T8iCs`jcjabn5!11cnY0=6p_4=bUzoc1eGdT^bs?D61bZ5pLKw z(6;w9%#bxS%8yk6G-W%BklsOsv+Iq7u8G3d(E<4Q6u{BsY_7q^$#8Tr5Tj9mn}2qO zXHyTMcO>?g)eA?K-P_Eis26iHHq4H;ctsyRjQl#q40GXRNVO?{JtEjQZfDK2p;!F_ zjP~`-OiSb4&7U+wirWCOcNhaZBV1(R?=UeNV=NfirAV*4FJn2$>Q6|DmqJrpbdzqc zuQX5A9Dx#-Q`4(b){|!Ch%U!CQ_m_$a)xe$*AxAE#FI206eC-&Np}x-JGUz0lZj@h zx3%gcH?*330es?oGuku#b+*TurVVG5QXD@;X80!Or)O>e?U`(i-=R4+*QeppDQ9G7 zeCwm{xLS56d((hdvvX=c%Ax|eidH6j4Y_D#FMtMJ-nF+FMEqR8YidTb&cK}Dt;`hY z+5tN&=Fx#BLR_y6qA{*Nnr@l-VNuas9#g$`1h)R{G&B{pW7I3MhXU@-`QH*Y-Vq88 zeV?hA8QC+v`=j^%!n_}kTX65pd104$kA!&FapovtyL;v1Lfg`O8w3xtCQQzG!Z9kd z8OR#oLfYcH2LE);#lVTKHeWY~%~6!iaj}rD&RPBV*8r7Yd~EyrmRIW-Wcc`8z%bqQ z?dcyg)1LVNG9hB;pG-1UHwl4d;suf+0vg{sCgFKH+Bpi0X(NQZ*84tjjGbKAi%)CV z%pNRxy$InGbavA*Sst9wx%;qpd42V*q76vn(*B-ZwbGF=QT$TKUIpqyc!v#%%LU!` z_`&E$Fzu1EyJxyO#_W8Z7e%*seDqUVRgKYL*ZC+)!;-Ap zT#|bAv#o}@WSs)ndg;>c&OmAHN#~RJd19c2pf-Or#V$ z#zJNJ|Ec0Uqv3AaKdy%qJ%}D62qDpX4U#OYCOQ!{dKYbfN|2(L)!EgS8_}&U;zkf` zusTcD2C-Un8}^YrFP{H@zGupHX6D6QGiT2AV&*d*U+f9~ez^lj;)@G8*{&f8#{j%- zBdk0}xyP<#4;Jc))y1;n@#3f6OH1sWqw|%@dB~>FgC8!NnTdJ>Jk?-mY_OlVukWrM zlWVWiL5Y)dOEe-1k)x9BUcbNMy?{$}%++lSmFWpS9~(f+q~|W>$$hA8feystFuH-R zORwgmHoD?P31Pc zchNO5;6-qSo3nlPWMK1AvKoRe037h5$NWG5QO;eh{{E+-NAeH6r}zHWub*Z1o~TC+ zh|guk@TJb=jqnCR_^;jv*O2z2!I&TNF>HWx`&lJ2Pa_^{TKx|_He3e~yvDg;?O#>j zv#1q53s3Z9-Kfy$zQRMB6qu-}14`S1uqVl>EX)RnwzU%jgF;&l$44SoiXgg$AZ{+B z_2s%%LZJTAzRyLI{c+fJ4kZkLKe#_(pK4=Ci<_okcW(c(Uy`Jn7miva!$+^8)Ik(@4lWte$-$#wy zFC>&rb35RF7T}Va^*y7*ltLQXF%OZ)b>H+sS^?fym8C+!r#RY>Sm}|%D#K=JQ5lH8 zZzJI-e1K}vWP;V50LZm>*!=1zCYgTp-2{rTX_EU1jf<6UfkV_f0$r=XCU*YqXREV| zg4x@TW#?&jXY>1sljl*W}(g8GCY7Z;W&- zW2SJ=Y5+f=vt$|5JO7;JcPM_lL}>|vpRrl2?Mb`X7$`pfSZzt?QQ+|J#}bLI`>8`G zGTB3Bxd=t2(iWQ2ek?>uUSX*E|b+kN-?vh;jtl z_PuLhw~@w^U8*WT-EZQTVwdm?*RfnaONqvBufeCdc=QP!N0!)d?3 zgYVXUQW!-JTzXP-ltL%zuSO2PKzv(EN>k8prF5?ReT$H`{%;l~Zk!`errDPA(J||8 z)`}b-N}3y(KFgi#U@$xW_^a7*GW!02y_XwtYx{yQ(YX?f+Oxm_GKh+V(g!uek^>L4 zkq!Ze)H{!`2qoX2WjbI%1u z18UKO{<}OQ1#iBsu+J_Unoj&aeU^tF|3|L!*>j)g`xE@J(lw@8Tn~>AML>yJ;>oX~ zb{zJCa>OoFa!t{F?k&OjSwP_p7&NRd!{WT}xoc0~bEcU`pwdSc(gG^?eY*D{$YG{S z#zY!7ZnUOYZ$0reC8@e#eQ01PZd-}^>(;+%q`~8ZuI#FRuJ4*ch3B%Klgq(f69YXs zH15S9k_6o8?+6+9U)$V%`P0QUGF-t0kXOS0TRjr6EKdXshmz zFKnT$a@sp(GJklERm4qijLcNfc9frfN7U(hR_YUjzDL(&B9WQ0)8ZGZ|DPM@p~3O9 z=PC)wjCq=n2sdB_a;GP1(dcm*kg^A*t3$Q>Tf9Tl5JqW@m|2d@jlNFTNv=s3k+UKgh)pE$C+Gkk;xN;^*Vf{)tu^^@KMO3G(wa&hrAmo=}3qoH{l?!dwi z$LAXt+rtP1D{W}Vk2TfsjxP7%zL>c;>sN+=}MiFJHj{;{UYntx_~a8k~mRtCy}yiW_{!wYMgGuw6$ z&2;~qw9af-5bgw;n=mv7(q2kGST|cwynj8un zx3q1njNh^=NOJgb8!AC*)FXK;Cpt`YXd-(#c)AWG(qFVYpqiIH^rxmaMyuF;1q`oX z_TI+RT=)`JzE8we#_jBE1057ps~XB3YqfW4@&>aBkkI|YxFF_1*D;wycvWK~Mee#C zhc=T5#bJ)jx@?Pc4PB$CvXQ{@6>Ss2--k`OHnm52MRjn!L$K_MP}VKMscg6W#Fs{3 z`>hDUpQ+?87>-B|=cg^0S2AiX*=^8bbTBpZbWt(OE@v}( zPwGUPJkt`eI&kB6n@be_sRZIHZ}-1kdq^sG2zKV7y?yNsQSu@b-kO8!|VwxpZW`x&>9UbXELqAtpy93a&S% zt|I5>=X;@iS3ddV)0bZ8YATuLZ0$)rEy-NzC(tl>McfD(H8It-7{xy=UA%xcxve|OGH}&6;M41W+)3&EP~0m_U+;dk~-h0Tkz3oUjL_lcIBY8#vGRCI)+Q8m=~W3 zZB<$kHK;FC#vZRA!08*V$?ICoqzr2hg!%mz9MGu{$|g#_0sVp4MQH5#Q_#67XCJKE zNQlMi7s?y$4JHI!pM%6`=JA%*=J#8qMM1sYp1xNieGu2yE5^+CwTITUh=%2AOmoDd z%9AiQnotFau{i3BL4HHwQ-hw3D+2yeMzFFN(7)ErgKk2@z)h7BJ}O(=wF+bB4aJwh zgnvf_NAeAUB(rV4V#gn%Oa@xa%_)x175zx1ckeiV!`@f)xS{*Qm4DUEMz*zwDR$hq z)Th!#nPKGKv{g@}^K~$}3h2b~yF)$R`Qt>>mW6a#@*-?GO1Bc`m0E9;QNywem?^6I zf+=Qa&b}d{>m=-C9W6Pei zgWX+Q( z?X$4Y(KNkBzsbRsh!X82zfaZ$#K_99)ysT0wB?opTt+<`LrOEFwI$qh*!1K=ux_p! zyb2DP*P?!+%%RdPY*m#Y*R99v$wg$B?(H+#Bq4b02J772y_rQB?Z^4XZKbV|;hqpk zgdzKLbV0SD*!alt*VnyS#wG%AdQe8Q1FXe@SFEJ|uIiCgEX;cv`ohevNQ50M#GAUn z{_F)XUf+{HEa7$bs1AuDDR-b<33b1G4=6d*oOOd)?yXZcQ$eJmO$bXpyG*L{imW&CTS;AYxd@*l}B;XbjjO5^`XRsx?A^^A2R3FOK7WW)=!M5CP}-; z8>N@s&e&mN>cnOS#7kZ65w%f36uIM(xXTRO@%u- zAS~(L%4~7@Noj@$C?&nTtlx+ne}z%Q$^K275Ko@u=nR`V>}ux2<{KuhRkSb?^NGg}YlH-p*!6 zy+s$-VNMz~5cbeXobf``)@HOiLhf~vV6j4UO6#{kGJQzOE~Amy995hJ)o`s0W?Yo~ z<6hIvgi^k3wFVPvGMhwRIWP8s*hD#?;iVaAuNJ~+-8FLc!6A9+DvY|RIl=l1zy8x4 z5ss&3BIJ3bq1OH~UiPUztMadBP`i$!sKg(&@(_aa0`y5f5 z#IyYM27{D$@kY=)nX>hx8&$rjPzvq&^2HCX)wfUb>v;|9I6+%HsQI~N%bFNZz`&nW zJH;wA?q1tE2m4W}-?A`%q1TGYU`ri zS}#hxWcd+&`)C){RBovu8r*eao*-Kmv?FmQbHnrzy?a-1TkjG$NEpK@&MyD&p`}YR z<VD6ca?! zD|}Sy97V%oBzTa6GzSu60PhWr@dN_H2Q})YT7iO6B-Q7eI@68{Fq%6d6%dgV{42!m z)Tk~w7sjiJoX*A5mIkm6Qd7Q(&T`n?mlz9*@mf;3dJ-}QyHZE8-t1qgj{JO?BE>K1 zJA>hNoXu5G>N@q2!1T=(1K{Dr+?9_l{o4odUHB!5>#NI#^vPEAGvH(w_k-2UfkShs zpvIZZ9($@Gf4y26+UcMe(d?v2rDVB`)|J1@OEOju zPw+%BF>!HE6&u@yaKPu>H+x@}cqI)i?tJxHRgQ_gI0*?&Kts29rxtM^Q57w<`Y|7-9@T$+D+ljWmHqAqn0OcLDxo^n-#ME$RBI+M1F zu+TH^(yRRurG;8pADPCf6b1dI3?f;Qk$e%N)VzNM`%aPER2LX>qt)bWgn0jadiXLx z1{&gR&^NCAABJ<9yZb%P|HbAcB&?TA=Rb+`Z<(i;CMt3(ZP@)DS*lr@t2n!;0f>~0 zgyerb=>IL3isUl9|C|0hchf9Q*+Yg*_ib8$hvaQs1__D3i=&;tucHBolIqVms!P4R K9LoUxWBmu^<#DgpumH3HRDbl^5K0)m^{BZMdL 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 From 1e70e654eda3e525c14d0e7381fd25936f09a375 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 21:32:44 -0800 Subject: [PATCH 08/27] Don't attempt loading miz files as JSON. --- qt_ui/windows/newgame/QCampaignList.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 822cfaca..09839224 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -38,7 +38,7 @@ class Campaign: def load_campaigns() -> List[Campaign]: campaign_dir = Path("resources\\campaigns") campaigns = [] - for path in campaign_dir.iterdir(): + for path in campaign_dir.glob("*.json"): try: logging.debug(f"Loading campaign from {path}...") campaign = Campaign.from_json(path) From 5e4802f05ef7119da43ce60ac072376c6195bd8d Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 21:50:29 -0800 Subject: [PATCH 09/27] Return base defense locations to pool on capture. When a base is captured we clear its defenses. Those locations need to be returned to the preset location pool so they can be used for the new base defenses. --- game/theater/controlpoint.py | 24 +++++++++++++++++++++-- game/theater/start_generator.py | 3 ++- game/theater/theatergroundobject.py | 4 ++-- resources/campaigns/inherent_resolve.miz | Bin 38793 -> 42140 bytes 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index b32ff97f..ec80a083 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -23,7 +23,10 @@ from .base import Base from .missiontarget import MissionTarget from .theatergroundobject import ( BaseDefenseGroundObject, + EwrGroundObject, + SamGroundObject, TheaterGroundObject, + VehicleGroupGroundObject, ) if TYPE_CHECKING: @@ -282,6 +285,24 @@ class ControlPoint(MissionTarget): def is_friendly(self, to_player: bool) -> bool: return self.captured == to_player + def clear_base_defenses(self) -> None: + for base_defense in self.base_defenses: + if isinstance(base_defense, EwrGroundObject): + self.preset_locations.ewrs.append(base_defense.position) + elif isinstance(base_defense, SamGroundObject): + self.preset_locations.base_air_defense.append( + base_defense.position) + elif isinstance(base_defense, VehicleGroupGroundObject): + self.preset_locations.base_garrisons.append( + base_defense.position) + else: + logging.error( + "Could not determine preset location type for " + f"{base_defense}. Assuming garrison type.") + self.preset_locations.base_garrisons.append( + base_defense.position) + self.base_defenses = [] + def capture(self, game: Game, for_player: bool) -> None: if for_player: self.captured = True @@ -293,9 +314,8 @@ class ControlPoint(MissionTarget): self.base.aircraft = {} self.base.armor = {} - # Handle cyclic dependency. + self.clear_base_defenses() from .start_generator import BaseDefenseGenerator - self.base_defenses = [] BaseDefenseGenerator(game, self).generate() def mission_types(self, for_player: bool) -> Iterator[FlightType]: diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 35a829fb..81aac268 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -502,7 +502,8 @@ class BaseDefenseGenerator: self.control_point.base_defenses.append(g) def generate_shorad(self) -> None: - position = self.location_finder.location_for(LocationType.Garrison) + position = self.location_finder.location_for( + LocationType.BaseAirDefense) if position is None: return diff --git a/game/theater/theatergroundobject.py b/game/theater/theatergroundobject.py index c267a0eb..7f3f44a9 100644 --- a/game/theater/theatergroundobject.py +++ b/game/theater/theatergroundobject.py @@ -243,8 +243,8 @@ class BaseDefenseGroundObject(TheaterGroundObject): # TODO: Differentiate types. -# This type gets used both for AA sites (SAM, AAA, or SHORAD) but also for the -# armor garrisons at airbases. These should each be split into their own types. +# This type gets used both for AA sites (SAM, AAA, or SHORAD). These should each +# be split into their own types. class SamGroundObject(BaseDefenseGroundObject): def __init__(self, name: str, group_id: int, position: Point, control_point: ControlPoint, for_airbase: bool) -> None: diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index 881c28e423aa8e87d288b4c9953f88469ea164d5..c5d00d7f2903cc79861dfb8cba949eae2204bdf1 100644 GIT binary patch delta 26363 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 OV{i8C(bsbRUHu<7o|=jP delta 22926 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 Y0!)aLNTf3Y<&~3tq!|VgmH+?%0N$;Sr~m)} From cecf611f91da0a4712d077b3863fdbb2d0fbb6ae Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 21:57:26 -0800 Subject: [PATCH 10/27] Add docs for PresetLocations. --- game/theater/controlpoint.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index ec80a083..5c549b21 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -57,20 +57,39 @@ class LocationType(Enum): @dataclass class PresetLocations: + """Defines the preset locations loaded from the campaign mission file.""" + + #: Locations used for spawning ground defenses for bases. base_garrisons: List[Point] = field(default_factory=list) + + #: Locations used for spawning air defenses for bases. Used by SAMs, AAA, + #: and SHORADs. base_air_defense: List[Point] = field(default_factory=list) + #: Locations used by EWRs. ewrs: List[Point] = field(default_factory=list) + + #: Locations used by SAMs outside of bases. sams: List[Point] = field(default_factory=list) + + #: Locations used by non-carrier ships. Carriers and LHAs are not random. ships: List[Point] = field(default_factory=list) + + #: Locations used by coastal defenses. coastal_defenses: List[Point] = field(default_factory=list) + + #: Locations used by ground based strike objectives. strike_locations: List[Point] = field(default_factory=list) + + #: Locations used by offshore strike objectives. offshore_strike_locations: List[Point] = field(default_factory=list) + #: Locations of SAMs which should always be spawned. fixed_sams: List[Point] = field(default_factory=list) @staticmethod def _random_from(points: List[Point]) -> Optional[Point]: + """Finds, removes, and returns a random position from the given list.""" if not points: return None point = random.choice(points) @@ -78,6 +97,11 @@ class PresetLocations: return point def random_for(self, location_type: LocationType) -> Optional[Point]: + """Returns a position suitable for the given location type. + + The location, if found, will be claimed by the caller and not available + to subsequent calls. + """ if location_type == LocationType.Garrison: return self._random_from(self.base_garrisons) if location_type == LocationType.Sam: From bc3cd50a6ca29eb0f826f3d6ccc3ef35e0865e6c Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Thu, 19 Nov 2020 23:47:07 -0800 Subject: [PATCH 11/27] Add support for required SAMs in campaigns. "Required" SAMs (designative by redfor long range SAM launchers in the ME) will always be spawned during campaign generation. This makes it possible to build a semi-guaranteed IADS (the exact type of SAM is dependent) on the choice of faction. Requierd SAMs will consume the slots of random SAMs during generation. Later we should differentiate between strategic SAMs like SA-10s and tactical SAMs like SA-11s so we can fill in the medium range SAMs at random locations among the fixed long range SAMs. --- game/theater/conflicttheater.py | 24 +++++++++++++++++++++++ game/theater/controlpoint.py | 2 +- game/theater/start_generator.py | 20 ++++++++++++++++++- resources/campaigns/inherent_resolve.miz | Bin 42140 -> 43422 bytes 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 8ad73838..566118f9 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -105,6 +105,20 @@ class MizCampaignLoader: OFFSHORE_STRIKE_TARGET_UNIT_TYPE = Fortification.Oil_platform.id SHIP_UNIT_TYPE = USS_Arleigh_Burke_IIa.id + # Multiple options for the required SAMs so campaign designers can more + # easily see the coverage of their IADS. Designers focused on campaigns that + # will primarily use SA-2s can place SA-2 launchers to ensure that they will + # have adequate coverage, and designers focused on campaigns that will + # primarily use SA-10s can do the same. + REQUIRED_SAM_UNIT_TYPES = { + AirDefence.SAM_Hawk_LN_M192, + AirDefence.SAM_Patriot_LN_M901, + AirDefence.SAM_SA_10_S_300PS_LN_5P85C, + AirDefence.SAM_SA_10_S_300PS_LN_5P85D, + AirDefence.SAM_SA_2_LN_SM_90, + AirDefence.SAM_SA_3_S_125_LN_5P73, + } + BASE_DEFENSE_RADIUS = nm_to_meter(2) def __init__(self, miz: Path, theater: ConflictTheater) -> None: @@ -207,6 +221,12 @@ class MizCampaignLoader: if group.units[0].type == self.OFFSHORE_STRIKE_TARGET_UNIT_TYPE: yield group + @property + def required_sams(self) -> Iterator[VehicleGroup]: + for group in self.red.vehicle_group: + if group.units[0].type == self.REQUIRED_SAM_UNIT_TYPES: + yield group + @cached_property def control_points(self) -> Dict[int, ControlPoint]: control_points = {} @@ -306,6 +326,10 @@ class MizCampaignLoader: closest, distance = self.objective_info(group) closest.preset_locations.ships.append(group.position) + for group in self.required_sams: + closest, distance = self.objective_info(group) + closest.preset_locations.required_sams.append(group.position) + def populate_theater(self) -> None: for control_point in self.control_points.values(): self.theater.add_controlpoint(control_point) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 5c549b21..4e61cfa5 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -85,7 +85,7 @@ class PresetLocations: offshore_strike_locations: List[Point] = field(default_factory=list) #: Locations of SAMs which should always be spawned. - fixed_sams: List[Point] = field(default_factory=list) + required_sams: List[Point] = field(default_factory=list) @staticmethod def _random_from(points: List[Point]) -> Optional[Point]: diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 81aac268..2eee490a 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -544,15 +544,31 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): # Always generate at least one AA point. self.generate_aa_site() + skip_sams = self.generate_required_aa() + # And between 2 and 7 other objectives. amount = random.randrange(2, 7) for i in range(amount): # 1 in 4 additional objectives are AA. if random.randint(0, 3) == 0: - self.generate_aa_site() + if skip_sams > 0: + skip_sams -= 1 + else: + self.generate_aa_site() else: self.generate_ground_point() + def generate_required_aa(self) -> int: + """Generates the AA sites that are required by the campaign. + + Returns: + The number of AA sites that were generated. + """ + sams = self.control_point.preset_locations.required_sams + for position in sams: + self.generate_aa_at(position) + return len(sams) + def generate_ground_point(self) -> None: try: category = random.choice(self.faction.building_set) @@ -591,7 +607,9 @@ class AirbaseGroundObjectGenerator(ControlPointGroundObjectGenerator): position = self.location_finder.location_for(LocationType.Sam) if position is None: return + self.generate_aa_at(position) + def generate_aa_at(self, position: Point) -> None: group_id = self.game.next_group_id() g = SamGroundObject(namegen.random_objective_name(), group_id, diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index c5d00d7f2903cc79861dfb8cba949eae2204bdf1..12dbaed8ec60fbf467659916adcc3234b55fe0ea 100644 GIT binary patch delta 25943 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 z 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 E3wEqE@Bjb+ delta 24680 zcmZ^}c_5U37dB3osVpOe7+cx1XJ3;_vK5s*dlH7R@3$@cQWVNo2}uaaFxHGMMRsCr zBfGI1yZ4^a_xn80@Atlc)R^0GKIdHLI@dY(J(JaYDx>`ra7T-boRQ?z>C+@6BwQrd zsJwlnjldr(s7N5-i?f5fyMqgS8Dr`WlPwe5^9IgFW;v5TdxccafSA0qb6)fk@U3~I z_sQ~?zNvNE#R}iGxIy$04)vP3V0a1snf?P#v=b9NSh7)>o=anj>(Axa!($ebm z&dBk~hzoFZ__Gpoe1PqqpFTE&W$BL9%lQHbDS7|R@m?U~sJCD2V0@+S@MzzKzjdWv zc{i}z#RB-gAGXnckp1wmW1Jt7pL%?4;OWYvqsjJ03qw`!a@9bDTfouU`s`r6k~fK= zg~wshY%%Kaj}GP%tlE`kmt5bBCX0O-bD=X+(zmpjB{=~)C{X4-H%*4p}JjSD)=W6V!)L_ z)kq+-!5v7Cveskwl)3Xk>u6^+Q&WV>-x^)T)T7x>zx`G(m+1B!X0jOrt> zbJv}DV6;BSA@$W)1yy-fx#|NZMa7SRV)fPipT~>$bAEi$_qMok33Enng&8`Ye$HPx zSe5-?v3(M?VQ(nHa_tDW$Eo72S}T`%;Nv6Z>$~c(UPd>8JKEct4Uki=k_$RUQt9RM zYs9)RM%tG^6LmXFfLWL1#K;x+#f23BqG6vre6IWO+q9FXi#NWwDJQ3W-eP}6S0zwE zE=W~gM0&ZOlS5CmQx7GU!5K-_g8c^6R?*HLA&%&cJbc{qU3~()g8bHS2eyR?0oxyD zXm_S7%iHI(NBVzRBsv6r&dfO4nAki#lHu6I*qADO)2ri4azB=moq^w+YX1pzi*dFL z*1I1vA7{WafDGqs(}Jnp{N(a3jH=#t*YTcG6pF{{wiVi*^DuNb&X3 zanQQen&t8ABx15N$TjOz1%9!iyeqlc+%+?XGi%I#^C2*qiS_aG{pzTklwMNfF8l>2 z^LE5p62D@Ik?Sq7PYx?j#sdAy{wlkhzHWZvG(v;ad+_1e@qke$m45T*`{k)CMT@v& zn8jVe2vgM{{2(eP8QWgVup?HlvT>o+d=+)DuTjszk*%j&zyCGX4$?ZFb+|T|GrUq= zHIteRFjE}QsRT|Q&Tu9kDJdx)EU(V39+d0?_3r9b%P*=_&Grsg0BBby6n=Ag^R>m% z`EpMA4V@LmmrM28uxt^zHZdDuPb(Gz3?i9Vy7r{#hZTQ*nO-be8HCsy=C^tuudZ&) zPh(~`u;te_#$bQmrO&4>u0&usQJiKLorz-yo_;>7bEpf?fwYV)k-GjPy~M(G;E`xM zg&S%wNvwT*w%G#MflJnU9S2no9?eewp6UF$7X8gzuF)EXdxssypc4g}Qn@;+S336Vvca8g@ z$6NCHx!Y&TR&TGx7Qbi!|FJNc3%+1B7Ybo|DCETa@UnzR&4VHEU#O{1kvRC823~6v ztpSY8%zR2eTlkcQ*Q`F7{JiS6<~zAXzhZVX)&9Z?7?APaZr;0&^39D_omBhz6<9mr zO7Gi_@;GnNqe5;8fG5B=CFm%0s<19YmA+1)V|Q$_qx^+pWpyC}n5`^nIm|4nQRwqmvIL@Qx+M=Q?18~%ZcMhuJ{rA zTSsfF8#zz2>kioJbT>Zj^!5XFQ=f3hq*p#GzZU*By{@XDAeX&Vqo{hYG0{=sALNH9 zQB_`{>>19PdONwiN$&w_6BG`YW3(j`V&7FPoF4Y4`-@Q3dfz~G1B3&%>Mkel#cMS)kaukDc zZS|cKDtlJ;EB9Acjt-|cd;gsNovk~2yS06Fb z@^qsa!^$^2gn;jZz{so)&*9d_uGGS^V*Y&QVNS9)U}hhL=cLDH6pDDPp~NaHzZWhY z@YT+3c^^{tZc&f!FAjOG&sQdQe40^g&JR$LQ{M3yIF9nvz$i+7**9qxDaJ5&>CQh9 zTct2uZr;@_hoY)7f|Qka4t&>xW}jT|3tCuf&L1z%7DxUDx;|Fj8}Yu6OP_T3eHIn6N9`5J16K)mLd_GV!Qz9&s!qFSl&%nv*_a_Ih>DI5oVY#$;xe zq!{vDHeMCL&tz@My{f+)Z5tlvO7c&h^`uf8@(l#osUx!SA3P*T=|O zQ+rtlJErDTULX1FfM_GL8^jWzU0tuwj?!??YRu+CTJ1qG?yA?8q@J0>rH^F?`1#LL ztqkXN6mN`V4`Tbej^vc86G#;SU0tY(w%_$oz!;-?>|lTFENgr0`)iRbFSYGZtjQ~> z*l_cr+Hp_j&+~7cOCIdV$@>5d;?CKs2U3BPQ;X9NOX@Q_5(V}+=I^ecMn}*42RiON zLS_x%SKno2ir(B07_Qju4pfxW(?Y%MC+ZSiA$1%q|*MBVgHbXo+ZrAn)zm3X;jrKIXmH^3_WQmhYs(rh* zk%Zq3cJkNDXTvx-vmE%7GapGB7tZ;qm1Jlqq-nvGQb#Luw%BX@MCGYfQnjB}*^Z5x zN%DnDWXJA~Hp_mvnpInh)_(qF5eRG7e}hxk$B(}n(V6v2m3SL_2DAM77X_|>+3PL( z`$%7^jSW*zqI$-7mHrtyPbE##m>Yc)cWzqP(G3H(W*=*@uYx?$B$|ZZ0z9VIddQN3 zr&H8=9Jvap$F@TJrFq&(lj6CD%k#IC&4PJ`8@UB3Zu$M=OHdq=T+W-F#G15ZBh`-J zc|xTLy>%)nn1^eaK8ciY9(9r$nWh~fE{a=cxrSMil>JS{n|N=nPdwYCxJ7y52=3sB z`!axmJa$?*{{uc=PC+F}3dk0)|5AN@K^$qYyL^0*RBZxkCCJz92;qmSTH&mJ1p{=(GV$NzDJS=aXGN#u^%mhG z;Z@ao^!wT3+#vJPvS6fuUA}%GR?Kdym5ghAhXtpLd~gjJ!|L(lj&r z+ug2s<4%(T2l2fX7fP4;tfHFc!Gi~0+{L?|<{R%%;XQ?S^u!R0y^uNTs(Pf+k0_mU4sU-qz7m{?Rv_DXHR zQtfup*tSy%|2^6SQNH-%5(JW^A5lXSh3AaDUkg1z8hQ(fi?>))Yf+jD}s$OXn4W7ez@2Qy9l7vQ-G+LY{ zIkprIZa8&XI2Z}(ZmWjH|IF+QEiopURDNn1pe<861W7i7vI2k`bSlQ8_k3bM6#t6q zXIwvYt2|r!ZH11Jx%~dC&wHfF*`^A+1=?qS2dwq`+H9%!+goil9s1D~axSR)di~BU z-6JjJjEM~Ghq_---PN%_;OV_Scy}9S5C!ErE){ ztV%8%)G!qpFA4;mj#+^IK7-n8ukMfKv~He)q{5mTI-lz?a=^DD>jLvv%D+uTitkK8 zM1_Os2!90;4nijWGFu8yv7VJ$yRRo>z;j8)K({eBa*t;yH|=qCmTr%Sa%Np^#>T6h zGfAjz>%O3!M4X!0nq&1;>Gq_`mA7#^Nv1ynA6y^c1MYhEkMc95s8}wzYvrrFlvhg^ zt5a|h4*akuEF6fmfLhP1AM)dct3I_If8Kn%{$_Rc2`W`(=ch|Rw}gTA`w26iytVw} z`%ds{GcV^=ZRW|=kDY%WpHnvJvMlnRS4a0igae1>dLlm@+dQcKzskrkUBKRp_)o3^PCA8^Nzz%dAs&Z55d?6q- zgYy5fmXxM%Z?hG&I;Ln}jgm1PEaRxg`v_Aqg*8Jx^*wWhy_iHJV(SApdnY3kZ!^~K zW)%e<&Wv!WKE=3n=~k%l6_S+6cnn4^zr42n4gh4|=%bZI^D2^&E-v%_4i(YLP`I^) zI3hUO$~w)5gkhGPVQ}RfA1nva8|ZH{^M3qN9M6S!jB@%-LdG2T%Pg(aSok>5Xj;CA z=*lLvZmDrF(Rn{#f;b{4Rl>cxC42NDmn zSH~D-o~I3+=HHgVk9P%BMTdnE4hQ{yLz6Q!p+~H&^_BPsB5-@3YkSa*-_SIETH{(2 z46}_oFe*OXzkVfz&Q=r%pxa})9m{P~U!H$@>ddP&vmf%v2(A3EU^JJ+EoyqGnl)v# z+SD?agyWdVgPXX84?wL6n);mVoUkW<5ha2p2)cWZ1VI@^Y8~uDLg()!Hu|pR0bNo6FaOS-3#EDekMnBobF^*tt@UxE^SL9&1_Aq+IY{#tXVa z!9!5mDKnDNayp6Y)aNRh5p=;wR`mE4JKYwyTQA<-PZ_)jxD6;UT+)matYkoZA)2Y9 z)uV>g(R6EGQA2pXPA)|3m_7kb2UiB~8IdFYpA&$GSTl*W zn0~$cMK2mP;#FqN^L~k^IfCyI2w_CD)fBfj3@j7!J9?8ZV1IjRr>S|E6Y>NKiI04F z7eFakmrT39k4O0US6VD!3>Q>P;wR@KT0dX5N(;8KWMP3PS|wJSh={{5n+c0$THSU7 zPlg_yv$>Js{?)2zp5=|zV;0tkh{#{!+q@AGl~zh&Yl2U>_LQA44VljNXv zI^MO%$B6A~q5Ad{D8CO<&AvdfU}93PCbDYUDkO5!$_H5+dFl3(iq+3UxA<)7HIvG> zcg1}>_+rm9@7K`WUw6Ie!@>~}IXTI#lmS}%Y;bh4LLHmDJlh`5PfkxvQUZhk`#E&I ze4twGO&mnqZH`CnB#1E9UX@wPP$;*S_HWkS#=DXdJoL0Qcy3*y5$KBqn*0>>9Pgi2 zlvnpf@`<6wQa_Bz6MiH#zN659aKphK_RAnzjOSaXFz8O{=W5yfhj_s3E_^ zV;rQrU1KC(w{gpNnNaVeosza}i(HWR6coXNQAUJJa*Jy>h>O?wQYTY3RlhN3|7>U? zXKIA3F6=YE_r{eY>s#sdN%FH{-`-fSk}%$6FS^P2b@-0ux@gi~<7d5rXP3_2Wfe{C zJVn*??X$SBQuxygOc76IU91L-jm}pTUfO;_eMQ`CoWPZ$N$n;_xtCHIUJBs5`91+tyCbclu4K_x|a*K!6%V@ibUh7LKu+ z$mDNLzmevgFk%ZLOi8sE8x{ z8Dg6q(au>6?&6(!!fJhdw4&rq^cf7TqdjO2LgNtiuly5_sv+M*0MdNFG`J7A2{|K=>wh!$U)(a}EC`IK;ybNe^+R&DM|Zc_BdDqjLG13y62 z0*H9&X>Bu?DVA5-3EV9inPX@{J_%9R7d#?(%Zo&qiK0slhMF!b@fSjrrF8^|AQ3>WpGGlAm84;Oi98fe{s)P?6FGFgp1e9pTrr z=+KFh)(SQaAgpcl0jWl;8E+Ku-Fz(22Hd*DuEFk8MyV{-p zBWZtS`pa$fg^LkU+}2S(r_b?6v0NOhiAuhP{{6w6t(1yGchxn}!eRfzdr%}Is@_|L z5PA!2D}5SBhiyg|YG%^c7!xH1JW##A)tPA^Gi-nP`89P0|0PWVc;ahx zd`yHN!7^t_bw#~Ipi~}}$dtBboy;y?253D$LSrm6nhfMZ6M9((#@m42s>oA6Ydd73 z_Z5XGbkwn*a(egkqUdf4Uki{$AOtl7)Y_WpH6&WvpWYGc75GCmjm4i{b`GTDdF`dh z@nqyB7XqCKO|Z@P;502?onn~ggKB^H=cRJ;K&ddAO}2%}CN9Tvr+NUx4I<+3g*kS&V)U2zUIVYQ?&`ljSd;JuzzRCK5I z+ho20r(2)mNBi%B4YhP@^i6qR<~_-^Ra5%d%dgUiy$m*hN+Vi_Nk>Dn9pSnViSE3y zSR4IZKLtqPNf>&@!b4bI)NaMm5O)~FfJpiS3f%8E;0kk25hTH7XU_9iVUgpB$PG9t znwpLbO@i>JgS>bJFMI5_7pg_8GCi+jTKh!dy=_^AC^`=sofApuBbJLCSJ8MubyBcy zO|f!D{ofqy=89=YC>W@;N8eQT)!qa2C7>nNq6J28tUs^_!Aq<=KNR)b;&WNhu5akf zw0~n#!;@r}%Ho|0{v@C!LYgfljHsEQjn)uwDK%}h&i@^($y~kI zr7w(@8m&M3urNVg?o_7O9{62xZ->Qiz&Fo73aS#;%psLK%TJCD3?rLdN-{_;aMQP9V|6vt=zIwgegR}s(VbW73 zXAyLp@p60RXrQn!2+|nq`OCAdBLQn$mQ_2OBh#A;(j6;8s*4j-2iN6qrC8iX)#H>u z9)49 zB{x2GDo`0HrTZ`2#qxXZd8Je*pEXc$pmC`U?|Gq%enFe(A)ebCbesQolwc;M7W3~7D5Ui?o|uLSvTM%^r}doEp0~Vhk2O$uXIz={U1_nr z^BcjPuBZJxpjaT4TOJ|%)!N}UztwFB;jbN3bDM^(Zs*_NX5cI@m9Sfrain^sZShq) zi(TU~w@gM~yN^FbvO-#4<9k{@Pw|2<&CDCg3LJzh!#nR24DU~M|H<0&on3Iqbdo6Q zeS_M>+(xwl0MgU7|jVK$bP6f_ap>S*doBUr<*w|PM z@Fip5oc5xn38YscZJ_ZBZJwuiQMsiFKk*7%_Q07K2X^Nf<8)6ZK2Mfp1sVmd5zVK9 znc7Wy^V8p6Bvk&zfHJLT?ZXEH)m*H;TSz>w29I&o{4@7D)GXWA6n5T0H(h|=S{LH3 zkk;FnK|A9io*yQkN#U+=j&NmoCqs8hH9tpJxv;nM+k4%H_qt((Mcv^+D^#Tr>kPhH zJ%E%er1d+f7Gg_v^Mmwf{b^;4lbd7h7GF|%Mt$m8ho;HrCM2 zc>eBM48N5&o&&A8cL2dXhP1u>b8DDQ=UUYo;GK;WL^z-p8M#xHl%g^8Y zu2M^~w@iFrhZST7+}7U$hhvM1-b+U^uWbWY$Ml^XrbzzoJ;BjogjMSIEZU{c-qE6d zSEfzR3O|y(%q;?V;|4lWu%U29dfxoe=;mSDcDAo-W|j_`SZda=mNycQmB{1SA5*is z(8Om>QTRG<7AK5ST;Oj*s<{;S7m#D+l;U#9ZJ;b1k6 z3$+amPueYMNzy)(u%*6GnJRH5#X#1_7DoSE`uPy#ZgF11MG3aqjIDvZ6!Gm~2}^45 zaP@{XQkw=JN6Q~Eke2U5=4_QzLQEgP8v=0VU6g?8P2IVOAdgnFeuh+z-WS__8nx1C z;io0~8lYpp(Wz_f8JowMqU+VTFksEMi5 z%$3B_KZZr*BZRx-IQfA^EV0@XS@A&_FsWDbo2t|DVBuc@9`SFi8h*+Gy zw?RnD^`>4q=EpE$uy$cB%MNi#2HNC=Y)^9L!jfD#$=P-Zl8S(ghsLpkX{fg>+i6p?${Oiqx`{up34aEg zcNoQN#_uqO<)^r?mQxS{WRcLlfEIU2o3Hiqvc z{y{Nxt54)H%w3YF-FWueF{Ex#7Vd&dFM2y+79)~saD@mX^#v$5mT1-UFy{Kfe3`TVd^K?IcFB z-IT<*rzAEFEaH@Lk--wAEx~I84QYE#M(nkPm+((mV#(szHdCro&x$iCD#Zn!X(0s- zNwK7s+5$S5Q!N9E5*gKs@Tl+JaqJprI?3BrBJ#v8#6sdg8nTFUoN6Jk(v-|*puy+@ z=+K+A1vte5Zi}8`FD-vGHOalOg#Bab(}5(7GvMjqqfBwid2E$2MA;iiS;Zq#>6AoH z=qm|JCGt25@bpT`mUqEm%7z-aQ*oCpPZoqw1Pez?DLIyC3btT*c`!mbuDo)Jrix3( zLDuOt&V;iFp>pFgQ2I8uHjL1JETSCWnmNE;tZZq1IBD&?T1+!qXuKq11a3xOGVkdl zI36|^yq}^zuikuTjYLRy=u<&ROfG7^?D=)IsEWi`!APDKgKh4wcBR4p>>E=ggngrF z>W$?|e4()yMB%?o7aj^V4E3&xcT%uu090TyO$|OiPpd1Yv4fQ~*->TlI2NAg*OQYh zFgM+7&+_qz$@t-_J{rCp3zR=)V())LEGaI^-V1j5cWfEeO(e>}f4wTNcCLQ#wZ?Iz z4#nM=w6hXVe%G$flj<}3IrE*9>~Eu8ka#P-oWTbH{&ovlA^ zzO%_Lr*F5lk!klm^W0Q4^|Vhc$<2u8R?}*68J4yzzK(GjQg*W*r6?JA36ooc7CFt{ z?GX)Ldgw8&ng=gE1NitW3{3_+8^W@OQlh*K%IJ08UA?I5Ff|cgqxnk7fDRsQU87Dy z(493dflg3-YNQ1^V=$-+Fl;wwqd4!=CMQ&+ZK_wNDo-1*cYeIKZ~jh97!cmJ{3l~c zr8xC7E$4_Gy)FL8x&g`2@~x4A!THOuuRa{(oXP7L@D+1Blj6eMN@(Q&M-MXmn=$Nv zdeDG?ZJFtF`B&4|7)w8a5IzuV0d83HC%LJ!@SpluBREc)`k{fB7C|IH)!~Pnh{Soz zjHOj^U0xP@qCkx_r3sR@(8xS_H#h8a`G@fr@vP!435i(GYsG&Uu+u~(A;=hl1eB}u`1)m%f>*oU3UI)P6lpi!z^#4o)jAYge{WAT1Mje z2KtrhZzD?^;=z%h5fSlL*rHOzgCQd}LX3?q)NHbIqJgsKoiZGJ*bLgi_blMS2!A%J z8$-y(2Q3Nek}tw@k8c_c$|irfd>imD{|est%J00WN&J1jzmrntRkU@=t>wp~yzJnJ zC(KawUqj%aTHF3LV{gX&1hR9A_^1?b+@pyzk48l15BpB=TG_&;86+L{H!BpBOk4h&S!P&Xj zqJ>@@U}yw@PQxHzUSk}v$RoK2tt)#&<_)DJ|0KS#D%VK?v!^1Q4|?R^Za_#U7$m{s zFR}3gk2FnSSG?V%%#)re<>$%i{#zfpQ>_ha+Y@rzt)&V ziW4#5myfiW-mS8XY!go1DaCu}B=U|pJ$v3{^}W{sAX zIc3c|_3le(JHW;IyP(}=5d;jhA#fPdP+HEU5Rp)Mv$b=3uv8&<5V8`QK<06hdQU0m z!&bAN(yOh%Bs`RY69lidig;u+N5yBV%P(GbyE|nYaCYk2lLDKs;P0u~)my{nhSXYr zt!>W@6?lB2n%HVYy}?qmpRmUxBVrIJec`e6)@F`Hf#Yhc=+)LOszT@6P65=>*K3m* zOiMBLwoJ^X16XU~;tTGT;VtA~OOB}9=nEGs5ooGiZ_U9P2>Eho)d*4N>@KnSW zQFTY{OjOOAwqy|ZtB6<#qO_seb!{7Iw1+ZcZ&(Zib@R+$-JF-BEx{V~AVL$sjFGho zf!{6xSW1-ab8l<8Vd3k0r~0D>qRvdaudB%RmC_OM`?bcUunl5aqpYwUvz-2gv_{3J zWS~@T4yoonk1BX$Q`)pB_tGPS2qC-#dzDt9c%U?v`?FOU#(ZS|!$7IltGp3<P*|zQ*yd@x1Rdz5W9;=o^5S!eq5uavNOH8SXh1?kP!8}U=P@qOdvi_ zNcd25gtdmP4u!N%tZlCj336~c600*Or``&F0*lcC=Z*A)QH=xjdS8jL*9cDvF1c7H@XI*b42U~F@$41}D?-LU6KcoQ(>|fpO?jdEkKl-U1)iV zuo|_ByG%HYx8;1lyP5fBapGuUJX`65svCr^u^dGvRD3UZJW7oPz@TvYSU2z)MA$6S zYwum84opP;E?a8ol`Pb;b!$N`f{YlNIF74j>%`^O>schYT5?uVfmh&RxE^nVc4QeP zb|wjt37!omP=IK*E^>>VMYdmIvoE5W?(ag>ZVXTJ476r|4o`9=IHVB=h(Xf)$D_`_ z9uYoRuTYT-gN;Pk`jBxw-<`9tkItz_yM6j7|EK6V+LpJ>@$u+&Fe(CgVJrlMaVada zAG5Fv1SxyyJUVSTN?fi&5y|FZ2=i_vIcQ19cuF3ggGVF-&)y2C%pMii*9+$&Ug>y4 zDg}ARlaL%}3WS;&oj2VSA00CqLqEmWzlf}!FlR?neLH`;^5hwi*+Vkdg41S{uB2up z-h!_3yd;%3?%HV>v`92-e|q>KBM((4-=4srr8UWSn)%yPSME`@@N{mo)81O)>GyBE z^dv9^V2)x_I|(rf(Zm|paRYmnn9b$5bgNOmmaWbx+m7d36#1bx>cjkA8K$hViXaCF zCFYLv&#s*XDdElD^7Dbc+5M9$r;Egm5XZD6{9za(_purDZ^k#T-TmW--4liRCM%au z(^aHnlc)p%9vi_@@yL{x^MAdn#VrzCYQ-(?u^F=0E?)9(L{ejAh&yFmx&uwi)(tEr z(KeMWwNxHXhNVpCzTS=jhP9ty^I)}Srrt%l?7>dRPKBklHVXw~6_F4Xj4O;E+j@=y zZ;DRby^LENv;;=)NWsI2q^UwsP~@|+$m)BvMA#-@*Yv~Q)Zn|GLg&^ri)=LcH@uD$ z652gQfOm;1Zp;LnPWER+FKZlIY>N(&52CWgVF)%B!z8=p>U}sLv$69iiy(^_596mV zQld=+N)dwtLWt273!FsfpNk0NWvL|07KaK%n5UyST!>YcXbv`Rc*S>-u~r>pYX4NH zGF_KZr%dUKA?n-3|JNSDBVL}wxA<>-b~G(Nd9g_+JyM0p1Oo(=T61AE!&ftGe(L|B z({{@!4D6@%`-}3rJU0)S*TU?lc-_?}6iyL1%G|(Cjy%^u;aW+KynEl}_W7DcG64T% zLIE-{fKRDQJ=@^vMbqGUV}c&^cM7Q$!gg^C?fm%)+`BxUFSmiG;5bNCwsIoC7@Cb@ zbB(L|BV;!V(wRVQ$T@f@$ZwHzaQ&sfb-rpwia42nEq|n0Qe#@|{<21PLT8k+Wh0Q5`Gm3o7KiUo!2rOqqY7&opihxLf+#-;=*nq{5 z?f=swT3;x!d++|LW=A~R9oLV|?q;(a9<9=xucG@}XrkLb--^-<(8AsX0Raessl^fU z2F059r@NUQp^YiP9wV}_z}Bpu5*zUjafzr3TJar+;~Y;B%-SN7`;Tjqr!}h_?@(u9 zzkCS1ty1;t4$|r0f{3ak>2N8hQ>FC~o~0-SICDVy98)#$ z6mB@jr01GGCsQgbDxn$m{fR+$WCP!=?FZH!(%*ml67itIT&oP5d+^3|&P&VVvJhs$ z+S_^bVUa1{+*dVw2gQOnre2P}oP`!-)GI-2lv(BB_*OWadDO$aZ0voT3?D9~3W8v&A|l6z1(YA-XfrRCzMwC%&@=r)xFX5m-oq zbDN+)D_BV)`V`%=D~ySky&RIB);@hp@b1#_y}l z1+x)^T`QPPy5Tmv4*Wa+k)Zk2fmPsmGLW`BkbFnY4RH3f-m{?SxzdD?F@sH?l$GCVrG+z>~5eHyKoim_c2teAHG z4APB2y|C330)ARP9f!V!iDLlaS zi_+-+n=1=#=3K=PgsDerb^2}eOKCdU)Tql$M9W+>4Jq1rjnOdais1G1a_B>y(WRQw z_VUEy`D=fu3Bid*DV|1|izSIq&=CxO!@CHFWdTTb%layN^@n3GQl+Lx zOOE7seRkmUx9XLqH=4XVR%|;~Oe|<^`gK;v$M223U$NQFYOX)!ExnGUHkqw+nS{`9r_WH{Ks=#&rUO9i~ym9jX}_56yoh z1}*iJHjP8a%^w2v_O+5B<%y7N{(Xzq-rZ@;QHZ4U{?_znVtFEec0Hz>&n|(ni@ndL z%1^wQb`J&_AF;-qxxbS4Ko$|3ci5VPH%}gu)m-jRIp*TwxTVAxt)VO2ew|qo(r$Ga z-MTqx&d6ZB-Z>Kd!)E8lC9Vw?wVAhRwEEDQy>6qJ!~@`$e&U#Okqef|*)-Yz4maL% znxjpbu}Y)tMMjlyW_EiAr5m(;I_V6CI|>^yTmSdsOOqEq@O+x9* zXpXn(;t2A_$iOs*S(`F>6~qmC12;Jw0YAmBz}~fl5T|*P?di+xx-(f8$6EQIx!n*=g@~wjK*B&vANJ z-Ux~GO_ZMIxKP!4+AVQZ6@Q1AOjKethY7A5RkYCm#UZTaF)FIi+-*<^KPR z3QE_OfQnRAWcY;646J;*CIpPUvYSoA$$k9;t~6pa?74p&c+3(_5zz%B=b!(LBZaK; zmddk$$4OAVX`Qq>_bk9Vfz4+w2^ki~ZJ#+EOu^}RONG3>^>h-nxj6Hrz@(BY$mtxH zlL9|D&s?6nMCPgUJn(TD^ii~}fR6W8`{89LsWz2&c=-$M31-%jOEx=8WKVTkp9Kn> zP5%gSKYVP)&fTnn{GM0)7gwQufqofJ0eDQZrWzk`5-T^S8KUoVVw)yEdHMJWxkEKi6RQBvS1zZ#&melW#-BV6m)UXk}rh;7m zuVW^rWLDc$z(C5NoxO%FHN3iw{J+uf477M!+}#EsH@77Mwt4L^fWW z4!i97`!A6S%M$gU8bs3Px2Z@Cx8ghb7|NE9QU(6#^j+vk36IaGv)1<6EQ{DAlh$p0 z79{W_V?o3H&{Y_H3qKK)vF+j%c&YO7=tuJR_h$Oub_&wd>72T#mNx=Je0C~#vYpq^ z6=dsV`@zV2@t8&F*s@QaUG0cFMj`(M`E%=y$U8 zGiY&-!#_RqUFCMJF8htdvRxuaX3INhC6jKTzHry(Ek%qs& zxEK_sBX9IR$mPiWo)`W-@0PNM0mm?}eUe;z(m;K_^Y`J{b9|bDMv;9sJAI%c=(j%e zdwe$i$zQ-D4F~_*MOj{48R+6(L8Nzga&)`SpiWVJb!2&Gv^3B2Bs<|vBlU%lvOF7* z76b!xTy%hI{L#vy$lQVS*hG~dg94Y`S|z8zMOl#FYfj-7DNhdxh5G% z_1@PL+}kSIx+MW|bmA&CR$_Wp%)ND0r&r=V$LrU5zfX`_o9vfb9_d@)gLi+qU!YNt zCzz8bs1LPUm{evvM|*c_Z_p3Tg1$gIc9!bW79pafJexaU(7ZY?|As{N>GALKf_|OR zcMgU6iTY{=H`NT@b5!?T;^$7zTl|8de54$@ST`K5&5eFm`on{!0J88ZfqUy$-PbBs}bRZcg-phaGP71|PbDQWjY^2Wm3+sgF5OihA!txW)K zPyR6ldnbBL?wjv9!tyydaz+$_w4|>J*pfI_28y{it_t=761E&M`gyv)A{$T{OoYIm+GmPI|v@5J>WS^J1PwA3E^ESKx#&8ooZs9RCBw z`M*%)mH0(Riz-Avlv#b`T!y)0>y{Kcv6UkIo`Y=leo|t4WRBWNz9i<{2+U&-TKVe=jVt>H318%Zi$gd zyqi{i%7aSR*p#C0inl$rDLl2Gdw*D`jh1;N zv;^+8JAD%O6aUt@IKkoX5?6Fv`L7GV$NGsYPcPat1j^C^85ndYLcS_PM>Zv2<7%67 zM3?fNHV^8m$al{Qg2W9ITlB*NTwiMMS5{R2l?EX@Aptw0G$U{kawhB=PZKQa=_$9wM)$;EzdUeZdiOD_04`vd z*sSjn5d24bzv4^vQ`0X__s8yjd;|!6cob8kF1t7>klto~#9o`am3n^h=9}5aZb>lV zu@ZCjsSr0%4U{q-^{6YRI1-AR7b!d2U0G1^pWag=^xnt5Ffs$FfnA)?wiQ`22h41p z&m^__m{Y78+`6vb=#`k}h>6MDYcx}4r8?K6j7-Wq95DefgaweWs7RgflL80Hca?yd zrTDGuNe0tf_MD7uv6|d|WwfSkMcn&W@AgXAa>l$n@u3CI?fcTp736KF*BRJ`N{+>D%ugIYY8N~3@{sV4U3%^2h6wsy(Eu!)jw~zc z;_aC=%r6~Qa8*?KyHr>1$=9q>~5aNsMqZcVrEf*|JM3& zM=HwSzbor#(`)O-{u=gOY*ELiu3ivILCk-4oUav9&#se#d3(GD%=PBugsS(jsAH*P zPj~wO{G7e6^K5Em$wO8DqbIhrQtm@n_&2ck#c-;bunb)N!IsXxyFvhv`J@vtNKC$5 zsWmyhk~I@5;}ftxzj4%=+}c#xnCkNTI$MD9&tc?xS2@DGGZmtwC}e?KQ5vV(b=g0hME@BNKK@H6 zE-|Rjc4)x5({XBQSBqDFp!~wH!SL`%svN+sGJc3~;;)=idW3D{Xz%Q(M2T78;ua6! z>^ncsurC8?u*AiC#ZccFo-q~wqeo`5uAb;C`K!p-YQUnn4zzx_f?KXxK3YL!4y>1_ z$@kZ0@?$JVe2=z=V=HVzUx{JN($6`W`otG zbN(ZpE{KAuEnQvXhikF=xp41~>ee;y3(bPJCF6Hm`WABmD8+w&S?YagrEKY0AHp0G z*rjeX^v!Z}(zf)FZhy5n{4T0Rcu?mk;HZhxrvOMus(;4YhXW!m%2_|xHRYfYI< zImNkgog?BJn5E@FqNY}}L9iE+QCAD}IOpmm2ozl+2)O@_VF@E>Fy&q-Qc`9q1LUxc znveK={N^xL)b0&(l{^^0CrziE2E>+B_r?|+OUdTh15UQKLh$BZ;qx&MEWL7fp0%9R z)RK>eyJamDmVI$wV-qWa@<5RJPY<9N^3q{?(dRo{w6{qj{T-0<<-hPr??uT)& zXDA~P{WHXzxMPidhhZSxi@1$JqG#5XzU}&B1f+8F)}N6?yfuHD&*K}Ni;j3`Cp)B2 zw1!L@;5UFl;uk4AFerw0^n7Q;bNNGZ=84Z%(q4luTL*XwH8Q&UJwZr*|WRGAGHMtr}GxD;5RJm}TB!Z-i~1Xtv2SRa&dEa2bb8g^X> zyBjkbi)D)4)k-VlJ$r|vW?zZy6$CUlNdeev=-5klYjd5_TjT+(hR8ceWS-6{fdLqF z7a$aA;)*!jga0kzGiA0Xx{$c68_UX6g>sN@NTZIxQ4V@{By{bXtZdhQ*w&4G@4PS$ zNBmC`P^-G&3)#d?XJao_JhsDUd6nD@ug2^zbr9syn(5lpkb_B*Uc8^yX%e(k51CLE5qK1)#M1i1Cv19@dNvFoW3Eb@r}`_{E{rf_ABTU4rp zw|9^mX|$m(Sh4)KSkU^uC2MWfr|7a!3yfG`8~XxLqBsD2w}Fz);htY@Y^_Anl3&XX zsynQ|(I*dsEIU4A4pP?DQ+}2(OWAE{Iy`i!SM9ZlWhRCFYabDq@qAN&Zr!K@*vQ2{ z<-PH*N`j9AgGeO%(1L}d&(7|VabhGnY-Z4OjUd=Lb`)z9yrCm2!%NVI(I@0G^v{NK z3UN6nhL2mWB___UD^ok&GrX~fzv6qVVUX!i({46f$HdUA{4|O3iY)AP}Ttod) zfNlX>(QHN8P#m%d(%o_zJ~V|sO-Vm^{oUi&i_es%-a5a51fjQUHf?!}0bhEIwpM}~YVEjDtI+(K^1;ST#f zer$grVB}|AQ>J;(h%C_XBdL&Ke+f^V$(UH#3-bE$94xm-GE z>o}hY7yM6Cw2zOw)#8E-w!KY6aBdUd%UUd3XIAFZi7`YchsjiF`|j_~u)^nhY{B2y zB698ttqyMm_=Z?mVdT!MGF!!~KBx%6-az_&VU;u%H~ z(YzzQDzl|A(#^ynv?g|kD}(XQSBuvt7~GCCNkc|OS#i!Hk<61Q-j)qnzm1-67#lx^ zuD8Lx`fOd7zc2#y-=hV7t`}~#^vx6#sWE)@-eHQ-hj2SIY?^C6^!z9s>t+=>*;sS2 ziffI1syX0>-?9HIk*`p}+62)@nT`lR9%K`z4w7InoO+(aA&bT2-@B^^D}mg~SPc1K zsye}kHXH5yn&dXiuo6D^iTnvdX`t*ZQu|^l-AKcZss7?Vd-cJ&fPW;)a<_~bZ!io_ zs!e4q_3cLB^bt60S1tK;IxXN{a%ZN^!( zuL)cSfNHYc-&zy^iL6EBnBND$m+BV|`zC^%LhEnKV-&5+B_|!b;65Yr{z2MY8>gn$ zwK21^MeE!!knF(vhptvlTdnuTI^%q;>}S3ljkxw?t>C@C%qKG(8HY338eeFC(z~_kLB-{E(~>! zx7`aE^x5-Djuw!|v7)JKB>f=GU(r3i;{93e&0(Sjcs71dC#(EVB3=jzi`JGk*yI&`N>Ak;A{AZpo*Yo3T1;Nj_% zoaQ;L&CzLa?7`NSR*-~+|4G%I_P6)x9z=*yp zgK9R!r&Bd|XA{aZ+DBV*AfNOb5`TD2EuRHJrPpHmRY4 z3)DUJWAfT4kT+Hd{ZIMGQ|0AN_sG5P-MNFY?%4R9qNW(d8;^ZVeNWXQvgF3@J!JbN!@*YjhCpNqOA?hhsN4 zbCsrOo7630bI~Zc&+^|AF(9z@6lJ-iPnxmZUoP!t*tP02;c3UV^p77lJAp~vudAtY zbBoJOd+g`*+PW~XJ0PiWEb0*hwhxy}3zQCQy%_pEvXtBya541vYhze49HVUBKg;dK zy-{C%1RPOYf_P{FH0tX9Xwtf-sP*ACUm4ZcIketz?e8|uv+}O|yRJ6z5_o!+2$+To z2zZq3weH2TA*R22xsTAltqE3I8GI5I8KLeUNT_S&M>7tMg+`PyIIRa9W?9di9OFf{ zlwYWU{s~r>KeHYmD9UEe+X(jiP5Rl@6@X6x${)rS^EJJ@D8D|nsowEkqx$5lq6ui{ zvZGj$T-650S?R_Amfc%EApPpJ;>$R^pX$0aKHU#Adb4N9L(1QD>07M=r>ljJ6mgTO z2W?E2jD#zqf-EU}nUjgLVZTFZ%;v}2dvG{|thX{OfV@>Z%4BWwMizql^gBCP{}L3~ zI)hA8nGJhW$$kU*zOPxnT0lrdiZjpZ!tsg3nEL!`jq$8xih6rY=pXZTsDO`B&7u*a z?a(-;y0Gp=LCn=T7weN?{bTN%LnS53c1SjD@9itgRg|%J_oG?2z(L;VnFcKMV2|3e zKiaxIlt0S0;%2RC)?e3ZT1A5fi{t_C80t6`z{Lru3;o@8E~XqDu5=!?W80#cP)lxx z_tTC1w|xQ>ZEj@Oy!KvU;ec!Usc9F9PlF#Gf+9V?K;Iq+v&lL^`FSlU;yVHc9$v$1q z5obkMr}bXaZ_-zgmUVNm)ALT^?X6#2;Ma!!@t?xP-bvQTJXxZ$f(LZude1tyHX>0L zOKM*b3jBs;O|T`v9n+9tCX8|IDDa9Dsc!EW7_QUJ;6jlGDmC&0)3rzOJ`&FeYOXy0`F})sx(45 zT&SK;KWu9B$+Nwo$FQ%9AI7yl`N~?UrPd9ofi1;CHf?ybZfqf~yLb?VIM4i%z$EdYY%oEcJ;dVq-I3YX zf|t`!N&Z&8!`DJyg0&ie>pq)L$IyjbElYu}XTKk3v}pesZu}5iI8z1K$<=(w;&eN z`8nNIn4a*o^47bWEP&D|$JOMF8svKb%6?^_yMGX+9XDKXwMBdT;V{y4HCmX$eC9bM zLg%_gjmI>!W*3w`HurMx3jd$bB#GH16+RETq`G7`g!v!#hv3I!>!1KK*HvdbaRg^a z&&i;_$3l$DO|iRz(Lm7Nxu@nYB;+cYvzXk|Mv&)Y#_ywFh?Y1T9i|$SzTE3UVXQ#r z7x?4pv5{Y5Yq`|*RQA6`v%>VELpQ*jpoEXAg^M}1J_865W_vH`_H!m4ty2ZZfnTqu zLLI6ic(vVbW}rV_8Z-PV_2l{Kuq031ESefOj!b=SBvNVcHXe{5Mi|8y;k4hTf7Ihw zYjKJEy2ANV(v}-gwK%DiChC6cmj(4Owr@rWpdL>c$(P6LTc>mEewJD>8gXhfd7trn zHN7IJosdn)55f19AIY^jyM}tNJn5u2jJNF@q&xr6xkA zk%`b4bE3t&1~4Esea&{^zK@+6(^OjJ9U~XL(as+oDR+#t9ljUc8cpR9%v!O`lf3=+ z!1I0S`PzVQFFyLCbh$WE<^LvKVkG4pJt_>*GU%_Fm+2VtN*3gk>FgFCzp=JSe;mv|3kq=p8>v#w%3gQl;uUR69zFf(VWc&7 zYF2LdRD@n2N?6n&bE^*?=5U;mhNvCeZWy73t!gzJo}!3ZVfM!=OiUaN24i^Lc+#q?f87>$;$Ic4`e71zQM z>V59j=yKVal=*MH$OpEz(8uhYb^f`x92B&)<{QlnL+P~m}&K9+$W%_h4SN51g(G17R+pOY9{E(j}$a=&~kd(GOMkK7bhrclkn|ALaQP3&a|ZbC!oETlxEG1mQdl2LCaH`v#1b4jpO zHhJ)AbH?8`R^f!pk!v-K4Dd5$mkm545vZ4=V{Egj?=;inzN<34X?m+jjxk3p<93({ z!kwx{Su+-_#%fH;`c~DlW~IX;12Pj5Wvn_E04j>%4?RB!keOXXS9!y@&*Ud1E{*sL zw6rg9{jC8REPt7{d=~lc!<+3f*AjTKm50rl?@N|F!*a;8ACpuTQ{DusB`T$U_%tyT zWRCo4rKrXCHwN;?M$vWA$!j1Rm(DKdGN(GDq*uoqGba1469q0}o_Qf)f=b1fHK~s? z0SjZFiKFENVfi(sv7b4=ERBqOZ&rq9i}n9X>QbxtvL9%+$Q!Wm01iF;I3%@8kGPP* z7@{@)H%3RkXgRK&Q{uCBm;1`o%dQ!05ZQyjt~Dy<)wbm?%DxOmtuj2t!I^ARyyNz7 zxNH@!!r#AiVnoSId<^hoxXKC*sNd;93;?);iPmh47A_#q;KJWvhO72FHd9Z&uodvi zn77MGRNOYPj^|fZML#V8@EWEkQUdoGE@qWUFE5!OcJw$hV#?yeRBGbgi=ql^$^4eh zh8ZI+c$D9EvW}dr#y#D<$Hcpwhm7kfBEb_42Y=n{f2cm%S-;F_bN|6W>r2c!hzWSe zd2;=b_(BRLA$r3#+zn^L_avPCxa0YWQsFOae7stfn}e~2--^_$7W(&A!ZKImJcToi z4xpfELuY2^LOF9E6%KrzYP75_pDMYuJx zY3cgnkQ$wQptQuh6?U9iE`7@P6N4*H^%LR~e~q*rW{Q-Oh_wYRhm+!Zhab%q+Ll>P5=pdF#5yZpBT&GPj50rEb@IYi^QGIAp zVY&DRE&a=|i6&ZUDH`oHi> wXa76Sl)gm3zp2p`I3jw!J)xsIBA8Gzbk?Z|ZImS4!B+%;V!L=h@4t)x1CIEXVgLXD From 4e910c4b09b2283f42e84457a3bff75cbf83ae88 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 00:18:00 -0800 Subject: [PATCH 12/27] Maybe correct fishbed radios. Maybe fixes https://github.com/Khopa/dcs_liberation/issues/377 --- gen/radios.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gen/radios.py b/gen/radios.py index c2180fe3..87b8661f 100644 --- a/gen/radios.py +++ b/gen/radios.py @@ -134,7 +134,7 @@ RADIOS: List[Radio] = [ Radio("RSIU-4V", MHz(100), MHz(150), step=MHz(1)), # MiG-21bis - Radio("RSIU-5V", MHz(100), MHz(150), step=MHz(1)), + Radio("RSIU-5V", MHz(118), MHz(140), step=MHz(1)), # Ka-50 # Note: Also capable of 100MHz-150MHz, but we can't model gaps. From 18b6f7b84ce82985460b5f6b8a8a41a3e618a429 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 01:58:55 -0800 Subject: [PATCH 13/27] Add off-map spawn locations. The AI isn't making use of these yet, but it's not smart enough to do so anyway. Would benefit from an icon to differentiate it on the map. I'm stretching the definition of "control point" quite a bit. We might want to put a class above `ControlPoint` for `AirSpawnLocation` to represent types of spawn locations that can't be captured and don't have ground objectives. Fixes https://github.com/Khopa/dcs_liberation/issues/274 --- game/game.py | 5 +- game/theater/conflicttheater.py | 24 +++++- game/theater/controlpoint.py | 15 ++++ game/theater/start_generator.py | 16 +++- game/utils.py | 4 + gen/aircraft.py | 44 ++++++---- gen/flights/ai_flight_planner.py | 11 ++- gen/flights/waypointbuilder.py | 80 ++++++++++++------ qt_ui/widgets/map/QMapControlPoint.py | 5 +- qt_ui/widgets/map/QMapObject.py | 9 +- qt_ui/windows/basemenu/QBaseMenu2.py | 1 - qt_ui/windows/basemenu/QBaseMenuTabs.py | 47 +++++----- .../windows/mission/flight/QFlightCreator.py | 6 +- .../flight/waypoints/QFlightWaypointList.py | 10 +-- resources/campaigns/inherent_resolve.miz | Bin 43422 -> 45228 bytes 15 files changed, 178 insertions(+), 99 deletions(-) diff --git a/game/game.py b/game/game.py index d69f9cc8..20d3ddb1 100644 --- a/game/game.py +++ b/game/game.py @@ -26,7 +26,7 @@ from .event.frontlineattack import FrontlineAttackEvent from .factions.faction import Faction from .infos.information import Information from .settings import Settings -from .theater import ConflictTheater, ControlPoint +from .theater import ConflictTheater, ControlPoint, OffMapSpawn from .weather import Conditions, TimeOfDay COMMISION_UNIT_VARIETY = 4 @@ -289,6 +289,9 @@ class Game: if len(potential_cp_armor) == 0: potential_cp_armor = self.theater.enemy_points() + potential_cp_armor = [p for p in potential_cp_armor if + not isinstance(p, OffMapSpawn)] + i = 0 potential_units = db.FACTIONS[self.enemy_name].frontline_units diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py index 566118f9..7766c90e 100644 --- a/game/theater/conflicttheater.py +++ b/game/theater/conflicttheater.py @@ -16,6 +16,7 @@ from dcs.countries import ( ) from dcs.country import Country from dcs.mapping import Point +from dcs.planes import F_15C from dcs.ships import ( CVN_74_John_C__Stennis, LHA_1_Tarawa, @@ -31,11 +32,17 @@ from dcs.terrain import ( thechannel, ) from dcs.terrain.terrain import Airport, Terrain -from dcs.unitgroup import Group, ShipGroup, StaticGroup, VehicleGroup +from dcs.unitgroup import ( + FlyingGroup, + Group, + ShipGroup, + StaticGroup, + VehicleGroup, +) from dcs.vehicles import AirDefence, Armor from gen.flights.flight import FlightType -from .controlpoint import ControlPoint, MissionTarget +from .controlpoint import ControlPoint, MissionTarget, OffMapSpawn from .landmap import Landmap, load_landmap, poly_contains from ..utils import nm_to_meter @@ -94,6 +101,8 @@ class MizCampaignLoader: BLUE_COUNTRY = CombinedJointTaskForcesBlue() RED_COUNTRY = CombinedJointTaskForcesRed() + OFF_MAP_UNIT_TYPE = F_15C.id + CV_UNIT_TYPE = CVN_74_John_C__Stennis.id LHA_UNIT_TYPE = LHA_1_Tarawa.id FRONT_LINE_UNIT_TYPE = Armor.APC_M113.id @@ -175,6 +184,11 @@ class MizCampaignLoader: def red(self) -> Country: return self.country(blue=False) + def off_map_spawns(self, blue: bool) -> Iterator[FlyingGroup]: + for group in self.country(blue).plane_group: + if group.units[0].type == self.OFF_MAP_UNIT_TYPE: + yield group + def carriers(self, blue: bool) -> Iterator[ShipGroup]: for group in self.country(blue).ship_group: if group.units[0].type == self.CV_UNIT_TYPE: @@ -236,6 +250,12 @@ class MizCampaignLoader: control_points[control_point.id] = control_point for blue in (False, True): + for group in self.off_map_spawns(blue): + control_point = OffMapSpawn(next(self.control_point_id), + str(group.name), group.position) + control_point.captured = blue + control_point.captured_invert = group.late_activation + control_points[control_point.id] = control_point for group in self.carriers(blue): # TODO: Name the carrier. control_point = ControlPoint.carrier( diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 4e61cfa5..88d622a6 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -40,6 +40,7 @@ class ControlPointType(Enum): LHA_GROUP = 2 # A group with a Tarawa carrier (Helicopters & Harrier) FARP = 4 # A FARP, with slots for helicopters FOB = 5 # A FOB (ground units only) + OFF_MAP = 6 class LocationType(Enum): @@ -364,3 +365,17 @@ class ControlPoint(MissionTarget): yield from [ # TODO: FlightType.STRIKE ] + + +class OffMapSpawn(ControlPoint): + def __init__(self, id: int, name: str, position: Point): + from . import IMPORTANCE_MEDIUM, SIZE_REGULAR + super().__init__(id, name, position, at=position, radials=[], + size=SIZE_REGULAR, importance=IMPORTANCE_MEDIUM, + has_frontline=False, cptype=ControlPointType.OFF_MAP) + + def capture(self, game: Game, for_player: bool) -> None: + raise RuntimeError("Off map control points cannot be captured") + + def mission_types(self, for_player: bool) -> Iterator[FlightType]: + yield from [] diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 2eee490a..ed30750b 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -42,8 +42,7 @@ from gen.sam.sam_group_generator import ( from theater import ( ConflictTheater, ControlPoint, - ControlPointType, - TheaterGroundObject, + ControlPointType, OffMapSpawn, ) GroundObjectTemplates = Dict[str, Dict[str, Any]] @@ -139,7 +138,13 @@ class GameGenerator: control_point.base.commision_points = {} control_point.base.strength = 1 + # The tasks here are confusing. PinpointStrike for some reason means + # ground units. for task in [PinpointStrike, CAP, CAS, AirDefence]: + if isinstance(control_point, OffMapSpawn): + # Off-map spawn locations start with no aircraft. + continue + if IMPORTANCE_HIGH <= control_point.importance <= IMPORTANCE_LOW: raise ValueError( f"CP importance must be between {IMPORTANCE_LOW} and " @@ -366,6 +371,11 @@ class ControlPointGroundObjectGenerator: self.control_point.connected_objectives.append(g) +class NoOpGroundObjectGenerator(ControlPointGroundObjectGenerator): + def generate(self) -> bool: + return True + + class CarrierGroundObjectGenerator(ControlPointGroundObjectGenerator): def generate(self) -> bool: if not super().generate(): @@ -660,6 +670,8 @@ class GroundObjectGenerator: generator = CarrierGroundObjectGenerator(self.game, control_point) elif control_point.cptype == ControlPointType.LHA_GROUP: generator = LhaGroundObjectGenerator(self.game, control_point) + elif isinstance(control_point, OffMapSpawn): + generator = NoOpGroundObjectGenerator(self.game, control_point) else: generator = AirbaseGroundObjectGenerator(self.game, control_point, self.templates) diff --git a/game/utils.py b/game/utils.py index 44652472..58bc5018 100644 --- a/game/utils.py +++ b/game/utils.py @@ -12,3 +12,7 @@ def meter_to_nm(value_in_meter: float) -> int: def nm_to_meter(value_in_nm: float) -> int: return int(value_in_nm * 1852) + + +def knots_to_kph(knots: float) -> int: + return int(knots * 1.852) diff --git a/gen/aircraft.py b/gen/aircraft.py index 9eccfb17..9edc52f4 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -70,7 +70,7 @@ from dcs.unittype import FlyingType, UnitType from game import db from game.data.cap_capabilities_db import GUNFIGHTERS from game.settings import Settings -from game.utils import nm_to_meter +from game.utils import knots_to_kph, nm_to_meter from gen.airsupportgen import AirSupport from gen.ato import AirTaskingOrder, Package from gen.callsigns import create_group_callsign_from_unit @@ -84,7 +84,11 @@ from gen.flights.flight import ( from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio from gen.runways import RunwayData from theater import TheaterGroundObject -from game.theater.controlpoint import ControlPoint, ControlPointType +from game.theater.controlpoint import ( + ControlPoint, + ControlPointType, + OffMapSpawn, +) from .conflictgen import Conflict from .flights.flightplan import ( CasFlightPlan, @@ -92,7 +96,7 @@ from .flights.flightplan import ( PatrollingFlightPlan, SweepFlightPlan, ) -from .flights.traveltime import TotEstimator +from .flights.traveltime import GroundSpeed, TotEstimator from .naming import namegen from .runways import RunwayAssigner @@ -804,31 +808,37 @@ class AircraftConflictGenerator: group_size=count, parking_slots=None) - def _generate_inflight(self, name: str, side: Country, unit_type: FlyingType, count: int, at: Point) -> FlyingGroup: - assert count > 0 + def _generate_inflight(self, name: str, side: Country, flight: Flight, + origin: ControlPoint) -> FlyingGroup: + assert flight.count > 0 + at = origin.position - if unit_type in helicopters.helicopter_map.values(): + alt_type = "RADIO" + if isinstance(origin, OffMapSpawn): + alt = flight.flight_plan.waypoints[0].alt + alt_type = flight.flight_plan.waypoints[0].alt_type + elif flight.unit_type in helicopters.helicopter_map.values(): alt = WARM_START_HELI_ALT - speed = WARM_START_HELI_AIRSPEED else: alt = WARM_START_ALTITUDE - speed = WARM_START_AIRSPEED + + speed = knots_to_kph(GroundSpeed.for_flight(flight, alt)) pos = Point(at.x + random.randint(100, 1000), at.y + random.randint(100, 1000)) - logging.info("airgen: {} for {} at {} at {}".format(unit_type, side.id, alt, speed)) + logging.info("airgen: {} for {} at {} at {}".format(flight.unit_type, side.id, alt, speed)) group = self.m.flight_group( country=side, name=name, - aircraft_type=unit_type, + aircraft_type=flight.unit_type, airport=None, position=pos, altitude=alt, speed=speed, maintask=None, - group_size=count) + group_size=flight.count) - group.points[0].alt_type = "RADIO" + group.points[0].alt_type = alt_type return group def _generate_at_group(self, name: str, side: Country, @@ -974,9 +984,8 @@ class AircraftConflictGenerator: group = self._generate_inflight( name=namegen.next_unit_name(country, cp.id, flight.unit_type), side=country, - unit_type=flight.unit_type, - count=flight.count, - at=cp.position) + flight=flight, + origin=cp) elif cp.is_fleet: group_name = cp.get_carrier_group_name() group = self._generate_at_group( @@ -1002,9 +1011,8 @@ class AircraftConflictGenerator: group = self._generate_inflight( name=namegen.next_unit_name(country, cp.id, flight.unit_type), side=country, - unit_type=flight.unit_type, - count=flight.count, - at=cp.position) + flight=flight, + origin=cp) group.points[0].alt = 1500 return group diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index ee458908..7d152efa 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -50,7 +50,7 @@ from theater import ( ControlPoint, FrontLine, MissionTarget, - TheaterGroundObject, + OffMapSpawn, TheaterGroundObject, SamGroundObject, ) @@ -232,8 +232,12 @@ class PackageBuilder: if assignment is None: return False airfield, aircraft = assignment + if isinstance(airfield, OffMapSpawn): + start_type = "In Flight" + else: + start_type = self.start_type flight = Flight(self.package, aircraft, plan.num_aircraft, airfield, - plan.task, self.start_type) + plan.task, start_type) self.package.add_flight(flight) return True @@ -406,6 +410,9 @@ class ObjectiveFinder: CP. """ for cp in self.friendly_control_points(): + if isinstance(cp, OffMapSpawn): + # Off-map spawn locations don't need protection. + continue airfields_in_proximity = self.closest_airfields_to(cp) airfields_in_threat_range = airfields_in_proximity.airfields_within( self.AIRFIELD_THREAT_RANGE diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index f220ebb4..346a4498 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -8,11 +8,15 @@ from dcs.unit import Unit from dcs.unitgroup import VehicleGroup from game.data.doctrine import Doctrine -from game.utils import nm_to_meter +from game.utils import feet_to_meter from game.weather import Conditions -from theater import ControlPoint, MissionTarget, TheaterGroundObject +from theater import ( + ControlPoint, + MissionTarget, + OffMapSpawn, + TheaterGroundObject, +) from .flight import Flight, FlightWaypoint, FlightWaypointType -from ..runways import RunwayAssigner @dataclass(frozen=True) @@ -34,8 +38,7 @@ class WaypointBuilder: def is_helo(self) -> bool: return getattr(self.flight.unit_type, "helicopter", False) - @staticmethod - def takeoff(departure: ControlPoint) -> FlightWaypoint: + def takeoff(self, departure: ControlPoint) -> FlightWaypoint: """Create takeoff waypoint for the given arrival airfield or carrier. Note that the takeoff waypoint will automatically be created by pydcs @@ -46,36 +49,59 @@ class WaypointBuilder: departure: Departure airfield or carrier. """ position = departure.position - waypoint = FlightWaypoint( - FlightWaypointType.TAKEOFF, - position.x, - position.y, - 0 - ) - waypoint.name = "TAKEOFF" - waypoint.alt_type = "RADIO" - waypoint.description = "Takeoff" - waypoint.pretty_name = "Takeoff" + if isinstance(departure, OffMapSpawn): + waypoint = FlightWaypoint( + FlightWaypointType.NAV, + position.x, + position.y, + 500 if self.is_helo else self.doctrine.rendezvous_altitude + ) + waypoint.name = "NAV" + waypoint.alt_type = "BARO" + waypoint.description = "Enter theater" + waypoint.pretty_name = "Enter theater" + else: + waypoint = FlightWaypoint( + FlightWaypointType.TAKEOFF, + position.x, + position.y, + 0 + ) + waypoint.name = "TAKEOFF" + waypoint.alt_type = "RADIO" + waypoint.description = "Takeoff" + waypoint.pretty_name = "Takeoff" return waypoint - @staticmethod - def land(arrival: ControlPoint) -> FlightWaypoint: + def land(self, arrival: ControlPoint) -> FlightWaypoint: """Create descent waypoint for the given arrival airfield or carrier. Args: arrival: Arrival airfield or carrier. """ position = arrival.position - waypoint = FlightWaypoint( - FlightWaypointType.LANDING_POINT, - position.x, - position.y, - 0 - ) - waypoint.name = "LANDING" - waypoint.alt_type = "RADIO" - waypoint.description = "Land" - waypoint.pretty_name = "Land" + if isinstance(arrival, OffMapSpawn): + waypoint = FlightWaypoint( + FlightWaypointType.NAV, + position.x, + position.y, + 500 if self.is_helo else self.doctrine.rendezvous_altitude + ) + waypoint.name = "NAV" + waypoint.alt_type = "BARO" + waypoint.description = "Exit theater" + waypoint.pretty_name = "Exit theater" + else: + waypoint = FlightWaypoint( + FlightWaypointType.LANDING_POINT, + position.x, + position.y, + 0 + ) + waypoint.name = "LANDING" + waypoint.alt_type = "RADIO" + waypoint.description = "Land" + waypoint.pretty_name = "Land" return waypoint def hold(self, position: Point) -> FlightWaypoint: diff --git a/qt_ui/widgets/map/QMapControlPoint.py b/qt_ui/widgets/map/QMapControlPoint.py index e59cdbfb..7ef55952 100644 --- a/qt_ui/widgets/map/QMapControlPoint.py +++ b/qt_ui/widgets/map/QMapControlPoint.py @@ -79,11 +79,8 @@ class QMapControlPoint(QMapObject): for connected in self.control_point.connected_points: if connected.captured: + menu.addAction(self.capture_action) break - else: - return - - menu.addAction(self.capture_action) def cheat_capture(self) -> None: self.control_point.capture(self.game_model.game, for_player=True) diff --git a/qt_ui/widgets/map/QMapObject.py b/qt_ui/widgets/map/QMapObject.py index a3c57c19..16f07061 100644 --- a/qt_ui/widgets/map/QMapObject.py +++ b/qt_ui/widgets/map/QMapObject.py @@ -47,9 +47,12 @@ class QMapObject(QGraphicsRectItem): object_details_action.triggered.connect(self.on_click) menu.addAction(object_details_action) - new_package_action = QAction(f"New package") - new_package_action.triggered.connect(self.open_new_package_dialog) - menu.addAction(new_package_action) + # Not all locations have valid objetives. Off-map spawns, for example, + # have no mission types. + if list(self.mission_target.mission_types(for_player=True)): + new_package_action = QAction(f"New package") + new_package_action.triggered.connect(self.open_new_package_dialog) + menu.addAction(new_package_action) self.add_context_menu_actions(menu) diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index cf5e1a34..cfdb8128 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -18,7 +18,6 @@ class QBaseMenu2(QDialog): # Attrs self.cp = cp self.game_model = game_model - self.is_carrier = self.cp.cptype in [ControlPointType.AIRCRAFT_CARRIER_GROUP, ControlPointType.LHA_GROUP] self.objectName = "menuDialogue" # Widgets diff --git a/qt_ui/windows/basemenu/QBaseMenuTabs.py b/qt_ui/windows/basemenu/QBaseMenuTabs.py index 0c82c86e..fd2f7ae7 100644 --- a/qt_ui/windows/basemenu/QBaseMenuTabs.py +++ b/qt_ui/windows/basemenu/QBaseMenuTabs.py @@ -5,39 +5,30 @@ from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand from qt_ui.windows.basemenu.base_defenses.QBaseDefensesHQ import QBaseDefensesHQ from qt_ui.windows.basemenu.ground_forces.QGroundForcesHQ import QGroundForcesHQ from qt_ui.windows.basemenu.intel.QIntelInfo import QIntelInfo -from theater import ControlPoint +from theater import ControlPoint, OffMapSpawn class QBaseMenuTabs(QTabWidget): def __init__(self, cp: ControlPoint, game_model: GameModel): super(QBaseMenuTabs, self).__init__() - self.cp = cp - if cp: - - if not cp.captured: - if not cp.is_carrier: - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Base Defenses") - self.intel = QIntelInfo(cp, game_model.game) - self.addTab(self.intel, "Intel") - else: - if cp.has_runway(): - self.airfield_command = QAirfieldCommand(cp, game_model) - self.addTab(self.airfield_command, "Airfield Command") - - if not cp.is_carrier: - self.ground_forces_hq = QGroundForcesHQ(cp, game_model) - self.addTab(self.ground_forces_hq, "Ground Forces HQ") - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Base Defenses") - else: - self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) - self.addTab(self.base_defenses_hq, "Fleet") + if not cp.captured: + if not cp.is_carrier and not isinstance(cp, OffMapSpawn): + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Base Defenses") + self.intel = QIntelInfo(cp, game_model.game) + self.addTab(self.intel, "Intel") else: - tabError = QFrame() - l = QGridLayout() - l.addWidget(QLabel("No Control Point")) - tabError.setLayout(l) - self.addTab(tabError, "No Control Point") \ No newline at end of file + if cp.has_runway(): + self.airfield_command = QAirfieldCommand(cp, game_model) + self.addTab(self.airfield_command, "Airfield Command") + + if cp.is_carrier: + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Fleet") + elif not isinstance(cp, OffMapSpawn): + self.ground_forces_hq = QGroundForcesHQ(cp, game_model) + self.addTab(self.ground_forces_hq, "Ground Forces HQ") + self.base_defenses_hq = QBaseDefensesHQ(cp, game_model.game) + self.addTab(self.base_defenses_hq, "Base Defenses") \ No newline at end of file diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index f4fe6041..80fbf219 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -18,7 +18,7 @@ from qt_ui.widgets.QLabeledWidget import QLabeledWidget from qt_ui.widgets.combos.QAircraftTypeSelector import QAircraftTypeSelector from qt_ui.widgets.combos.QFlightTypeComboBox import QFlightTypeComboBox from qt_ui.widgets.combos.QOriginAirfieldSelector import QOriginAirfieldSelector -from theater import ControlPoint +from theater import ControlPoint, OffMapSpawn class QFlightCreator(QDialog): @@ -107,7 +107,9 @@ class QFlightCreator(QDialog): origin = self.airfield_selector.currentData() size = self.flight_size_spinner.value() - if self.game.settings.perf_ai_parking_start: + if isinstance(origin, OffMapSpawn): + start_type = "In Flight" + elif self.game.settings.perf_ai_parking_start: start_type = "Cold" else: start_type = "Warm" diff --git a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py index 381d8e39..c8d4562f 100644 --- a/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py +++ b/qt_ui/windows/mission/flight/waypoints/QFlightWaypointList.py @@ -42,15 +42,7 @@ class QFlightWaypointList(QTableView): self.model.setHorizontalHeaderLabels(["Name", "Alt", "TOT/DEPART"]) - # The first waypoint is set up by pydcs at mission generation time, so - # we need to add that waypoint manually. - takeoff = FlightWaypoint(self.flight.from_cp.position.x, - self.flight.from_cp.position.y, 0) - takeoff.description = "Take Off" - takeoff.name = takeoff.pretty_name = "Take Off from " + self.flight.from_cp.name - takeoff.alt_type = "RADIO" - - waypoints = itertools.chain([takeoff], self.flight.points) + waypoints = self.flight.flight_plan.waypoints for row, waypoint in enumerate(waypoints): self.add_waypoint_row(row, self.flight, waypoint) self.selectionModel().setCurrentIndex(self.indexAt(QPoint(1, 1)), diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index 12dbaed8ec60fbf467659916adcc3234b55fe0ea..6b1e4be3d6e13481feb6c6b6596d63b240dcec9d 100644 GIT binary patch delta 29410 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 From 7c22f6e83b030a168f93e84f3999ecfb037ac0fc Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 02:19:38 -0800 Subject: [PATCH 14/27] Change mach function to take altitude in meters. All of the callers are passing altitude in meters because that's what pydcs uses. This still returns knots which makes it extra weird, but that's what almost all of the callers expect. It's probably a good idea to introduce some explicit types for the various distance and speed units to avoid these sorts of mistakes. --- gen/flights/traveltime.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gen/flights/traveltime.py b/gen/flights/traveltime.py index 742dfce3..7cc45069 100644 --- a/gen/flights/traveltime.py +++ b/gen/flights/traveltime.py @@ -45,20 +45,21 @@ class GroundSpeed: return int(cls.from_mach(mach, altitude)) # knots @staticmethod - def from_mach(mach: float, altitude: int) -> float: + def from_mach(mach: float, altitude_m: int) -> float: """Returns the ground speed in knots for the given mach and altitude. Args: mach: The mach number to convert to ground speed. - altitude: The altitude in feet. + altitude_m: The altitude in meters. Returns: The ground speed corresponding to the given altitude and mach number in knots. """ # https://www.grc.nasa.gov/WWW/K-12/airplane/atmos.html - if altitude <= 36152: - temperature_f = 59 - 0.00356 * altitude + altitude_ft = altitude_m * 3.28084 + if altitude_ft <= 36152: + temperature_f = 59 - 0.00356 * altitude_ft else: # There's another formula for altitudes over 82k feet, but we better # not be planning waypoints that high... From 976ee51bf512294ce62d235f9261cd2bee2b12fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Mu=C3=B1oz=20Fernandez?= Date: Fri, 20 Nov 2020 11:32:18 +0100 Subject: [PATCH 15/27] feat: Added Financial Income to GroundObjectMenu building window --- qt_ui/windows/groundobject/QBuildingInfo.py | 10 +++++++- .../windows/groundobject/QGroundObjectMenu.py | 23 ++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/qt_ui/windows/groundobject/QBuildingInfo.py b/qt_ui/windows/groundobject/QBuildingInfo.py index e474a59f..3fe4e2e2 100644 --- a/qt_ui/windows/groundobject/QBuildingInfo.py +++ b/qt_ui/windows/groundobject/QBuildingInfo.py @@ -2,7 +2,7 @@ import os from PySide2.QtGui import QPixmap from PySide2.QtWidgets import QGroupBox, QHBoxLayout, QVBoxLayout, QLabel - +from game.db import REWARDS class QBuildingInfo(QGroupBox): @@ -28,6 +28,14 @@ class QBuildingInfo(QGroupBox): layout = QVBoxLayout() layout.addWidget(self.header) layout.addWidget(self.name) + + if self.building.category in REWARDS.keys(): + income_label_text = 'Value: ' + str(REWARDS[self.building.category]) + "M" + if self.building.is_dead: + income_label_text = '' + income_label_text + '' + self.reward = QLabel(income_label_text) + layout.addWidget(self.reward) + footer = QHBoxLayout() self.setLayout(layout) diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py index dcfed0a3..cb07732c 100644 --- a/qt_ui/windows/groundobject/QGroundObjectMenu.py +++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py @@ -8,7 +8,7 @@ from dcs import Point from game import Game, db from game.data.building_data import FORTIFICATION_BUILDINGS -from game.db import PRICES, unit_type_of, PinpointStrike +from game.db import PRICES, REWARDS, unit_type_of, PinpointStrike from gen.defenses.armor_group_generator import generate_armor_group_of_type_and_size from gen.sam.sam_group_generator import get_faction_possible_sams_generator from qt_ui.uiconstants import EVENT_ICONS @@ -51,6 +51,8 @@ class QGroundObjectMenu(QDialog): self.mainLayout.addWidget(self.intelBox) else: self.mainLayout.addWidget(self.buildingBox) + if self.cp.captured: + self.mainLayout.addWidget(self.financesBox) self.actionLayout = QHBoxLayout() @@ -105,11 +107,30 @@ class QGroundObjectMenu(QDialog): self.buildingBox = QGroupBox("Buildings :") self.buildingsLayout = QGridLayout() j = 0 + + total_income = 0 + received_income = 0 for i, building in enumerate(self.buildings): if building.dcs_identifier not in FORTIFICATION_BUILDINGS: self.buildingsLayout.addWidget(QBuildingInfo(building, self.ground_object), j/3, j%3) j = j + 1 + if building.category in REWARDS.keys(): + total_income = total_income + REWARDS[building.category] + if not building.is_dead: + received_income = received_income + REWARDS[building.category] + + + self.financesBox = QGroupBox("Finances: ") + self.financesBoxLayout = QGridLayout() + + str_total_income = 'Available: ' + str(total_income) + "M" + str_percived_income = 'Receiving: ' + str(received_income) + "M" + + self.financesBoxLayout.addWidget(QLabel(str_total_income), 2, 1) + self.financesBoxLayout.addWidget(QLabel(str_percived_income), 2, 2) + + self.financesBox.setLayout(self.financesBoxLayout) self.buildingBox.setLayout(self.buildingsLayout) self.intelBox.setLayout(self.intelLayout) From 0f1b396dd25f5538da6e199862b9a63aef4167eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Mu=C3=B1oz=20Fernandez?= Date: Fri, 20 Nov 2020 11:34:03 +0100 Subject: [PATCH 16/27] chore: variable name changes --- qt_ui/windows/groundobject/QGroundObjectMenu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py index cb07732c..f98bc3d2 100644 --- a/qt_ui/windows/groundobject/QGroundObjectMenu.py +++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py @@ -125,10 +125,10 @@ class QGroundObjectMenu(QDialog): self.financesBoxLayout = QGridLayout() str_total_income = 'Available: ' + str(total_income) + "M" - str_percived_income = 'Receiving: ' + str(received_income) + "M" + str_received_income = 'Receiving: ' + str(received_income) + "M" self.financesBoxLayout.addWidget(QLabel(str_total_income), 2, 1) - self.financesBoxLayout.addWidget(QLabel(str_percived_income), 2, 2) + self.financesBoxLayout.addWidget(QLabel(str_received_income), 2, 2) self.financesBox.setLayout(self.financesBoxLayout) self.buildingBox.setLayout(self.buildingsLayout) From 1553e5efd5d94d079480b5c1a9f68cedab4ec41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Mu=C3=B1oz=20Fernandez?= Date: Fri, 20 Nov 2020 11:46:06 +0100 Subject: [PATCH 17/27] chore: syntax cleaning and redundant variable cleanup --- qt_ui/windows/groundobject/QBuildingInfo.py | 1 - qt_ui/windows/groundobject/QGroundObjectMenu.py | 11 +++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/qt_ui/windows/groundobject/QBuildingInfo.py b/qt_ui/windows/groundobject/QBuildingInfo.py index 3fe4e2e2..fcf6366b 100644 --- a/qt_ui/windows/groundobject/QBuildingInfo.py +++ b/qt_ui/windows/groundobject/QBuildingInfo.py @@ -38,4 +38,3 @@ class QBuildingInfo(QGroupBox): footer = QHBoxLayout() self.setLayout(layout) - diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py index f98bc3d2..a1fda851 100644 --- a/qt_ui/windows/groundobject/QGroundObjectMenu.py +++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py @@ -106,8 +106,8 @@ class QGroundObjectMenu(QDialog): self.buildingBox = QGroupBox("Buildings :") self.buildingsLayout = QGridLayout() - j = 0 + j = 0 total_income = 0 received_income = 0 for i, building in enumerate(self.buildings): @@ -120,15 +120,10 @@ class QGroundObjectMenu(QDialog): if not building.is_dead: received_income = received_income + REWARDS[building.category] - self.financesBox = QGroupBox("Finances: ") self.financesBoxLayout = QGridLayout() - - str_total_income = 'Available: ' + str(total_income) + "M" - str_received_income = 'Receiving: ' + str(received_income) + "M" - - self.financesBoxLayout.addWidget(QLabel(str_total_income), 2, 1) - self.financesBoxLayout.addWidget(QLabel(str_received_income), 2, 2) + self.financesBoxLayout.addWidget(QLabel("Available: " + str(total_income) + "M"), 2, 1) + self.financesBoxLayout.addWidget(QLabel("Receiving: " + str(received_income) + "M"), 2, 2) self.financesBox.setLayout(self.financesBoxLayout) self.buildingBox.setLayout(self.buildingsLayout) From 5695cf4ac50a537dd60de3557cc39acef5c0d228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20Mu=C3=B1oz=20Fernandez?= Date: Fri, 20 Nov 2020 12:38:06 +0100 Subject: [PATCH 18/27] fix: fixes #325 Budget calculation and enemy reinforcement calculation now takes into account the destroyed buildings --- game/game.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game/game.py b/game/game.py index 20d3ddb1..e9cc42fc 100644 --- a/game/game.py +++ b/game/game.py @@ -151,7 +151,7 @@ class Game: reward = PLAYER_BUDGET_BASE * len(self.theater.player_points()) for cp in self.theater.player_points(): for g in cp.ground_objects: - if g.category in REWARDS.keys(): + if g.category in REWARDS.keys() and not g.is_dead: reward = reward + REWARDS[g.category] return reward else: @@ -274,7 +274,7 @@ class Game: production = 0.0 for enemy_point in self.theater.enemy_points(): for g in enemy_point.ground_objects: - if g.category in REWARDS.keys(): + if g.category in REWARDS.keys() and not g.is_dead: production = production + REWARDS[g.category] production = production * 0.75 From a9fcfe60f4f7befd5e2ec1667b021ae897f49c1f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 15:36:19 -0800 Subject: [PATCH 19/27] Add arrival/divert airfield selection. Breaks save compat because it adds new fields to `Flight` that have no constant default. Removing all of our other save compat at the same time. Note that player flights with a divert point will have a nav point for their actual landing point. This is because we place the divert point last, and DCS won't let us have a land point anywhere but the final waypoint. It would allow a LandingReFuAr point, but they're only generated for player flights anyway so it doesn't really matter. Fixes https://github.com/Khopa/dcs_liberation/issues/342 --- gen/aircraft.py | 35 +++---- gen/flights/ai_flight_planner.py | 6 +- gen/flights/flight.py | 14 ++- gen/flights/flightplan.py | 96 +++++++++++-------- gen/flights/waypointbuilder.py | 34 +++++++ .../combos/QArrivalAirfieldSelector.py | 47 +++++++++ qt_ui/widgets/map/QLiberationMap.py | 5 +- .../windows/mission/flight/QFlightCreator.py | 46 +++++++-- 8 files changed, 213 insertions(+), 70 deletions(-) create mode 100644 qt_ui/widgets/combos/QArrivalAirfieldSelector.py diff --git a/gen/aircraft.py b/gen/aircraft.py index 9edc52f4..13de2c05 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -695,6 +695,18 @@ class AircraftConflictGenerator: return StartType.Cold return StartType.Warm + def determine_runway(self, cp: ControlPoint, dynamic_runways) -> RunwayData: + fallback = RunwayData(cp.full_name, runway_heading=0, runway_name="") + if cp.cptype == ControlPointType.AIRBASE: + assigner = RunwayAssigner(self.game.conditions) + return assigner.get_preferred_runway(cp.airport) + elif cp.is_fleet: + return dynamic_runways.get(cp.name, fallback) + else: + logging.warning( + f"Unhandled departure/arrival control point: {cp.cptype}") + return fallback + def _setup_group(self, group: FlyingGroup, for_task: Type[Task], package: Package, flight: Flight, dynamic_runways: Dict[str, RunwayData]) -> None: @@ -752,19 +764,9 @@ class AircraftConflictGenerator: channel = self.get_intra_flight_channel(unit_type) group.set_frequency(channel.mhz) - # TODO: Support for different departure/arrival airfields. - cp = flight.from_cp - fallback_runway = RunwayData(cp.full_name, runway_heading=0, - runway_name="") - if cp.cptype == ControlPointType.AIRBASE: - assigner = RunwayAssigner(self.game.conditions) - departure_runway = assigner.get_preferred_runway( - flight.from_cp.airport) - elif cp.is_fleet: - departure_runway = dynamic_runways.get(cp.name, fallback_runway) - else: - logging.warning(f"Unhandled departure control point: {cp.cptype}") - departure_runway = fallback_runway + divert = None + if flight.divert is not None: + divert = self.determine_runway(flight.divert, dynamic_runways) self.flights.append(FlightData( package=package, @@ -774,10 +776,9 @@ class AircraftConflictGenerator: friendly=flight.from_cp.captured, # Set later. departure_delay=timedelta(), - departure=departure_runway, - arrival=departure_runway, - # TODO: Support for divert airfields. - divert=None, + departure=self.determine_runway(flight.departure, dynamic_runways), + arrival=self.determine_runway(flight.arrival, dynamic_runways), + divert=divert, # Waypoints are added later, after they've had their TOTs set. waypoints=[], intra_flight_channel=channel diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 7d152efa..1cd48ed7 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -236,8 +236,10 @@ class PackageBuilder: start_type = "In Flight" else: start_type = self.start_type - flight = Flight(self.package, aircraft, plan.num_aircraft, airfield, - plan.task, start_type) + + flight = Flight(self.package, aircraft, plan.num_aircraft, plan.task, + start_type, departure=airfield, arrival=airfield, + divert=None) self.package.add_flight(flight) return True diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 2b5e35ea..56d9d04a 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -65,6 +65,7 @@ class FlightWaypointType(Enum): INGRESS_DEAD = 20 INGRESS_SWEEP = 21 INGRESS_BAI = 22 + DIVERT = 23 class FlightWaypoint: @@ -133,12 +134,15 @@ class FlightWaypoint: class Flight: def __init__(self, package: Package, unit_type: FlyingType, count: int, - from_cp: ControlPoint, flight_type: FlightType, - start_type: str) -> None: + flight_type: FlightType, start_type: str, + departure: ControlPoint, arrival: ControlPoint, + divert: Optional[ControlPoint]) -> None: self.package = package self.unit_type = unit_type self.count = count - self.from_cp = from_cp + self.departure = departure + self.arrival = arrival + self.divert = divert self.flight_type = flight_type # TODO: Replace with FlightPlan. self.targets: List[MissionTarget] = [] @@ -157,6 +161,10 @@ class Flight: custom_waypoints=[] ) + @property + def from_cp(self) -> ControlPoint: + return self.departure + @property def points(self) -> List[FlightWaypoint]: return self.flight_plan.waypoints[1:] diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 918861e2..8df8dc5f 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -68,6 +68,10 @@ class FlightPlan: @property def waypoints(self) -> List[FlightWaypoint]: """A list of all waypoints in the flight plan, in order.""" + return list(self.iter_waypoints()) + + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + """Iterates over all waypoints in the flight plan, in order.""" raise NotImplementedError @property @@ -166,8 +170,7 @@ class FlightPlan: class LoiterFlightPlan(FlightPlan): hold: FlightWaypoint - @property - def waypoints(self) -> List[FlightWaypoint]: + def iter_waypoints(self) -> Iterator[FlightWaypoint]: raise NotImplementedError @property @@ -193,8 +196,7 @@ class FormationFlightPlan(LoiterFlightPlan): join: FlightWaypoint split: FlightWaypoint - @property - def waypoints(self) -> List[FlightWaypoint]: + def iter_waypoints(self) -> Iterator[FlightWaypoint]: raise NotImplementedError @property @@ -295,8 +297,7 @@ class PatrollingFlightPlan(FlightPlan): return self.patrol_end_time return None - @property - def waypoints(self) -> List[FlightWaypoint]: + def iter_waypoints(self) -> Iterator[FlightWaypoint]: raise NotImplementedError @property @@ -312,15 +313,17 @@ class PatrollingFlightPlan(FlightPlan): class BarCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint land: FlightWaypoint + divert: Optional[FlightWaypoint] - @property - def waypoints(self) -> List[FlightWaypoint]: - return [ + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield from [ self.takeoff, self.patrol_start, self.patrol_end, self.land, ] + if self.divert is not None: + yield self.divert @dataclass(frozen=True) @@ -328,16 +331,18 @@ class CasFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint target: FlightWaypoint land: FlightWaypoint + divert: Optional[FlightWaypoint] - @property - def waypoints(self) -> List[FlightWaypoint]: - return [ + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield from [ self.takeoff, self.patrol_start, self.target, self.patrol_end, self.land, ] + if self.divert is not None: + yield self.divert def request_escort_at(self) -> Optional[FlightWaypoint]: return self.patrol_start @@ -350,16 +355,18 @@ class CasFlightPlan(PatrollingFlightPlan): class TarCapFlightPlan(PatrollingFlightPlan): takeoff: FlightWaypoint land: FlightWaypoint + divert: Optional[FlightWaypoint] lead_time: timedelta - @property - def waypoints(self) -> List[FlightWaypoint]: - return [ + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield from [ self.takeoff, self.patrol_start, self.patrol_end, self.land, ] + if self.divert is not None: + yield self.divert @property def tot_offset(self) -> timedelta: @@ -400,19 +407,23 @@ class StrikeFlightPlan(FormationFlightPlan): egress: FlightWaypoint split: FlightWaypoint land: FlightWaypoint + divert: Optional[FlightWaypoint] - @property - def waypoints(self) -> List[FlightWaypoint]: - return [ + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield from [ self.takeoff, self.hold, self.join, self.ingress - ] + self.targets + [ + ] + yield from self.targets + yield from[ self.egress, self.split, self.land, ] + if self.divert is not None: + yield self.divert @property def package_speed_waypoints(self) -> Set[FlightWaypoint]: @@ -511,17 +522,19 @@ class SweepFlightPlan(LoiterFlightPlan): sweep_start: FlightWaypoint sweep_end: FlightWaypoint land: FlightWaypoint + divert: Optional[FlightWaypoint] lead_time: timedelta - @property - def waypoints(self) -> List[FlightWaypoint]: - return [ + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield from [ self.takeoff, self.hold, self.sweep_start, self.sweep_end, self.land, ] + if self.divert is not None: + yield self.divert @property def tot_waypoint(self) -> Optional[FlightWaypoint]: @@ -567,9 +580,8 @@ class SweepFlightPlan(LoiterFlightPlan): class CustomFlightPlan(FlightPlan): custom_waypoints: List[FlightWaypoint] - @property - def waypoints(self) -> List[FlightWaypoint]: - return self.custom_waypoints + def iter_waypoints(self) -> Iterator[FlightWaypoint]: + yield from self.custom_waypoints @property def tot_waypoint(self) -> Optional[FlightWaypoint]: @@ -774,10 +786,11 @@ class FlightPlanBuilder: package=self.package, flight=flight, patrol_duration=self.doctrine.cap_duration, - takeoff=builder.takeoff(flight.from_cp), + takeoff=builder.takeoff(flight.departure), patrol_start=start, patrol_end=end, - land=builder.land(flight.from_cp) + land=builder.land(flight.arrival), + divert=builder.divert(flight.divert) ) def generate_sweep(self, flight: Flight) -> SweepFlightPlan: @@ -800,11 +813,12 @@ class FlightPlanBuilder: package=self.package, flight=flight, lead_time=timedelta(minutes=5), - takeoff=builder.takeoff(flight.from_cp), + takeoff=builder.takeoff(flight.departure), hold=builder.hold(self._hold_point(flight)), sweep_start=start, sweep_end=end, - land=builder.land(flight.from_cp) + land=builder.land(flight.arrival), + divert=builder.divert(flight.divert) ) def racetrack_for_objective(self, @@ -900,10 +914,11 @@ class FlightPlanBuilder: # requests an escort the CAP flight will remain on station for the # duration of the escorted mission, or until it is winchester/bingo. patrol_duration=self.doctrine.cap_duration, - takeoff=builder.takeoff(flight.from_cp), + takeoff=builder.takeoff(flight.departure), patrol_start=start, patrol_end=end, - land=builder.land(flight.from_cp) + land=builder.land(flight.arrival), + divert=builder.divert(flight.divert) ) def generate_dead(self, flight: Flight, @@ -965,14 +980,15 @@ class FlightPlanBuilder: return StrikeFlightPlan( package=self.package, flight=flight, - takeoff=builder.takeoff(flight.from_cp), + takeoff=builder.takeoff(flight.departure), hold=builder.hold(self._hold_point(flight)), join=builder.join(self.package.waypoints.join), ingress=ingress, targets=[target], egress=egress, split=builder.split(self.package.waypoints.split), - land=builder.land(flight.from_cp) + land=builder.land(flight.arrival), + divert=builder.divert(flight.divert) ) def generate_cas(self, flight: Flight) -> CasFlightPlan: @@ -999,11 +1015,12 @@ class FlightPlanBuilder: package=self.package, flight=flight, patrol_duration=self.doctrine.cas_duration, - takeoff=builder.takeoff(flight.from_cp), + takeoff=builder.takeoff(flight.departure), patrol_start=builder.ingress_cas(ingress, location), target=builder.cas(center), patrol_end=builder.egress(egress, location), - land=builder.land(flight.from_cp) + land=builder.land(flight.arrival), + divert=builder.divert(flight.divert) ) @staticmethod @@ -1030,7 +1047,7 @@ class FlightPlanBuilder: def _hold_point(self, flight: Flight) -> Point: assert self.package.waypoints is not None - origin = flight.from_cp.position + origin = flight.departure.position target = self.package.target.position join = self.package.waypoints.join origin_to_target = origin.distance_to_point(target) @@ -1118,14 +1135,15 @@ class FlightPlanBuilder: return StrikeFlightPlan( package=self.package, flight=flight, - takeoff=builder.takeoff(flight.from_cp), + takeoff=builder.takeoff(flight.departure), hold=builder.hold(self._hold_point(flight)), join=builder.join(self.package.waypoints.join), ingress=ingress, targets=target_waypoints, egress=builder.egress(self.package.waypoints.egress, location), split=builder.split(self.package.waypoints.split), - land=builder.land(flight.from_cp) + land=builder.land(flight.arrival), + divert=builder.divert(flight.divert) ) def _retreating_rendezvous_point(self, attack_transition: Point) -> Point: @@ -1201,7 +1219,7 @@ class FlightPlanBuilder: ) for airfield in cache.closest_airfields: for flight in self.package.flights: - if flight.from_cp == airfield: + if flight.departure == airfield: return airfield raise RuntimeError( "Could not find any airfield assigned to this package" diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index 346a4498..fa992419 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -104,6 +104,40 @@ class WaypointBuilder: waypoint.pretty_name = "Land" return waypoint + def divert(self, + divert: Optional[ControlPoint]) -> Optional[FlightWaypoint]: + """Create divert waypoint for the given arrival airfield or carrier. + + Args: + divert: Divert airfield or carrier. + """ + if divert is None: + return None + + position = divert.position + if isinstance(divert, OffMapSpawn): + if self.is_helo: + altitude = 500 + else: + altitude = self.doctrine.rendezvous_altitude + altitude_type = "BARO" + else: + altitude = 0 + altitude_type = "RADIO" + + waypoint = FlightWaypoint( + FlightWaypointType.DIVERT, + position.x, + position.y, + altitude + ) + waypoint.alt_type = altitude_type + waypoint.name = "DIVERT" + waypoint.description = "Divert" + waypoint.pretty_name = "Divert" + waypoint.only_for_player = True + return waypoint + def hold(self, position: Point) -> FlightWaypoint: waypoint = FlightWaypoint( FlightWaypointType.LOITER, diff --git a/qt_ui/widgets/combos/QArrivalAirfieldSelector.py b/qt_ui/widgets/combos/QArrivalAirfieldSelector.py new file mode 100644 index 00000000..a1fe88bb --- /dev/null +++ b/qt_ui/widgets/combos/QArrivalAirfieldSelector.py @@ -0,0 +1,47 @@ +"""Combo box for selecting a departure airfield.""" +from typing import Iterable + +from PySide2.QtWidgets import QComboBox +from dcs.planes import PlaneType + +from game import db +from game.theater.controlpoint import ControlPoint + + +class QArrivalAirfieldSelector(QComboBox): + """A combo box for selecting a flight's arrival or divert airfield. + + The combo box will automatically be populated with all airfields the given + aircraft type is able to land at. + """ + + def __init__(self, destinations: Iterable[ControlPoint], + aircraft: PlaneType, optional_text: str) -> None: + super().__init__() + self.destinations = list(destinations) + self.aircraft = aircraft + self.optional_text = optional_text + self.rebuild_selector() + self.setCurrentIndex(0) + + def change_aircraft(self, aircraft: PlaneType) -> None: + if self.aircraft == aircraft: + return + self.aircraft = aircraft + self.rebuild_selector() + + def valid_destination(self, destination: ControlPoint) -> bool: + if destination.is_carrier and self.aircraft not in db.CARRIER_CAPABLE: + return False + if destination.is_lha and self.aircraft not in db.LHA_CAPABLE: + return False + return True + + def rebuild_selector(self) -> None: + self.clear() + for destination in self.destinations: + if self.valid_destination(destination): + self.addItem(destination.name, destination) + self.model().sort(0) + self.insertItem(0, self.optional_text, None) + self.update() diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index fb5802c3..d0189203 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -373,6 +373,10 @@ class QLiberationMap(QGraphicsView): FlightWaypointType.TARGET_SHIP, ) for idx, point in enumerate(flight.flight_plan.waypoints[1:]): + if point.waypoint_type == FlightWaypointType.DIVERT: + # Don't clutter the map showing divert points. + continue + new_pos = self._transform_point(Point(point.x, point.y)) self.draw_flight_path(scene, prev_pos, new_pos, is_player, selected) @@ -386,7 +390,6 @@ class QLiberationMap(QGraphicsView): self.draw_waypoint_info(scene, idx + 1, point, new_pos, flight.flight_plan) prev_pos = tuple(new_pos) - self.draw_flight_path(scene, prev_pos, pos, is_player, selected) def draw_waypoint(self, scene: QGraphicsScene, position: Tuple[int, int], player: bool, selected: bool) -> None: diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 80fbf219..604bcd57 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -16,6 +16,8 @@ from qt_ui.uiconstants import EVENT_ICONS from qt_ui.widgets.QFlightSizeSpinner import QFlightSizeSpinner from qt_ui.widgets.QLabeledWidget import QLabeledWidget from qt_ui.widgets.combos.QAircraftTypeSelector import QAircraftTypeSelector +from qt_ui.widgets.combos.QArrivalAirfieldSelector import \ + QArrivalAirfieldSelector from qt_ui.widgets.combos.QFlightTypeComboBox import QFlightTypeComboBox from qt_ui.widgets.combos.QOriginAirfieldSelector import QOriginAirfieldSelector from theater import ControlPoint, OffMapSpawn @@ -49,16 +51,30 @@ class QFlightCreator(QDialog): self.on_aircraft_changed) layout.addLayout(QLabeledWidget("Aircraft:", self.aircraft_selector)) - self.airfield_selector = QOriginAirfieldSelector( + self.departure = QOriginAirfieldSelector( self.game.aircraft_inventory, [cp for cp in game.theater.controlpoints if cp.captured], self.aircraft_selector.currentData() ) - self.airfield_selector.availability_changed.connect(self.update_max_size) - layout.addLayout(QLabeledWidget("Airfield:", self.airfield_selector)) + self.departure.availability_changed.connect(self.update_max_size) + layout.addLayout(QLabeledWidget("Departure:", self.departure)) + + self.arrival = QArrivalAirfieldSelector( + [cp for cp in game.theater.controlpoints if cp.captured], + self.aircraft_selector.currentData(), + "Same as departure" + ) + layout.addLayout(QLabeledWidget("Arrival:", self.arrival)) + + self.divert = QArrivalAirfieldSelector( + [cp for cp in game.theater.controlpoints if cp.captured], + self.aircraft_selector.currentData(), + "None" + ) + layout.addLayout(QLabeledWidget("Divert:", self.divert)) self.flight_size_spinner = QFlightSizeSpinner() - self.update_max_size(self.airfield_selector.available) + self.update_max_size(self.departure.available) layout.addLayout(QLabeledWidget("Size:", self.flight_size_spinner)) self.client_slots_spinner = QFlightSizeSpinner( @@ -82,10 +98,16 @@ class QFlightCreator(QDialog): def verify_form(self) -> Optional[str]: aircraft: PlaneType = self.aircraft_selector.currentData() - origin: ControlPoint = self.airfield_selector.currentData() + origin: ControlPoint = self.departure.currentData() + arrival: ControlPoint = self.arrival.currentData() + divert: ControlPoint = self.divert.currentData() size: int = self.flight_size_spinner.value() if not origin.captured: return f"{origin.name} is not owned by your coalition." + if arrival is not None and not arrival.captured: + return f"{arrival.name} is not owned by your coalition." + if divert is not None and not divert.captured: + return f"{divert.name} is not owned by your coalition." available = origin.base.aircraft.get(aircraft, 0) if not available: return f"{origin.name} has no {aircraft.id} available." @@ -104,16 +126,22 @@ class QFlightCreator(QDialog): task = self.task_selector.currentData() aircraft = self.aircraft_selector.currentData() - origin = self.airfield_selector.currentData() + origin = self.departure.currentData() + arrival = self.arrival.currentData() + divert = self.divert.currentData() size = self.flight_size_spinner.value() + if arrival is None: + arrival = origin + if isinstance(origin, OffMapSpawn): start_type = "In Flight" elif self.game.settings.perf_ai_parking_start: start_type = "Cold" else: start_type = "Warm" - flight = Flight(self.package, aircraft, size, origin, task, start_type) + flight = Flight(self.package, aircraft, size, task, start_type, origin, + arrival, divert) flight.client_count = self.client_slots_spinner.value() # noinspection PyUnresolvedReferences @@ -122,7 +150,9 @@ class QFlightCreator(QDialog): def on_aircraft_changed(self, index: int) -> None: new_aircraft = self.aircraft_selector.itemData(index) - self.airfield_selector.change_aircraft(new_aircraft) + self.departure.change_aircraft(new_aircraft) + self.arrival.change_aircraft(new_aircraft) + self.divert.change_aircraft(new_aircraft) def update_max_size(self, available: int) -> None: self.flight_size_spinner.setMaximum(min(available, 4)) From ae68a35a1a507cecf54abbe940ec7f6a51899f69 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 17:00:47 -0800 Subject: [PATCH 20/27] Remove save compat since it's breaking anyway. Removal of old paths/names for things that no longer exist. --- game/event/event.py | 4 +-- game/game.py | 6 ----- game/inventory.py | 8 ++++-- game/models/frontline_data.py | 2 +- game/operation/operation.py | 2 +- game/theater/frontline.py | 1 - game/theater/start_generator.py | 5 ++-- game/weather.py | 2 +- gen/aircraft.py | 12 ++++----- gen/conflictgen.py | 3 ++- gen/flights/ai_flight_planner.py | 26 +++++++++---------- gen/flights/closestairfields.py | 2 +- gen/flights/flightplan.py | 14 ++++------ gen/flights/waypointbuilder.py | 5 ++-- gen/ground_forces/ai_ground_planner.py | 4 +-- gen/groundobjectsgen.py | 4 +-- gen/runways.py | 2 +- qt_ui/widgets/base/QAirportInformation.py | 2 +- qt_ui/widgets/combos/QFlightTypeComboBox.py | 2 +- .../QPredefinedWaypointSelectionComboBox.py | 2 +- qt_ui/widgets/map/QFrontLine.py | 2 +- qt_ui/widgets/map/QLiberationMap.py | 17 ++++++------ qt_ui/widgets/map/QMapControlPoint.py | 2 +- qt_ui/widgets/map/QMapGroundObject.py | 2 +- qt_ui/windows/basemenu/QBaseMenu2.py | 2 +- qt_ui/windows/basemenu/QBaseMenuTabs.py | 4 +-- qt_ui/windows/basemenu/QRecruitBehaviour.py | 6 ++--- .../airfield/QAircraftRecruitmentMenu.py | 4 ++- .../basemenu/airfield/QAirfieldCommand.py | 2 +- .../base_defenses/QBaseDefenseGroupInfo.py | 10 +++++-- .../basemenu/base_defenses/QBaseDefensesHQ.py | 6 +++-- .../base_defenses/QBaseInformation.py | 15 +++++++---- .../ground_forces/QArmorRecruitmentMenu.py | 4 ++- .../basemenu/ground_forces/QGroundForcesHQ.py | 2 +- .../ground_forces/QGroundForcesStrategy.py | 7 ++--- .../QGroundForcesStrategySelector.py | 2 +- qt_ui/windows/basemenu/intel/QIntelInfo.py | 17 +++++++----- .../windows/groundobject/QGroundObjectMenu.py | 21 +++++++++++---- .../windows/mission/flight/QFlightCreator.py | 2 +- qt_ui/windows/newgame/QCampaignList.py | 2 +- theater/__init__.py | 2 -- theater/base.py | 2 -- theater/conflicttheater.py | 2 -- theater/controlpoint.py | 2 -- theater/frontline.py | 3 --- theater/theatergroundobject.py | 2 -- 46 files changed, 132 insertions(+), 118 deletions(-) delete mode 100644 game/theater/frontline.py delete mode 100644 theater/__init__.py delete mode 100644 theater/base.py delete mode 100644 theater/conflicttheater.py delete mode 100644 theater/controlpoint.py delete mode 100644 theater/frontline.py delete mode 100644 theater/theatergroundobject.py diff --git a/game/event/event.py b/game/event/event.py index 8cc4aea7..44fc37e2 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging import math -from typing import Dict, List, Optional, Type, TYPE_CHECKING +from typing import Dict, List, Optional, TYPE_CHECKING, Type from dcs.mapping import Point from dcs.task import Task @@ -12,8 +12,8 @@ from game import db, persistency from game.debriefing import Debriefing from game.infos.information import Information from game.operation.operation import Operation +from game.theater import ControlPoint from gen.ground_forces.combat_stance import CombatStance -from theater import ControlPoint if TYPE_CHECKING: from ..game import Game diff --git a/game/game.py b/game/game.py index e9cc42fc..44c0ef3e 100644 --- a/game/game.py +++ b/game/game.py @@ -202,12 +202,6 @@ class Game: LuaPluginManager.load_settings(self.settings) ObjectiveDistanceCache.set_theater(self.theater) - # Save game compatibility. - - # TODO: Remove in 2.3. - if not hasattr(self, "conditions"): - self.conditions = self.generate_conditions() - def pass_turn(self, no_action: bool = False) -> None: logging.info("Pass turn") self.informations.append(Information("End of turn #" + str(self.turn), "-" * 40, 0)) diff --git a/game/inventory.py b/game/inventory.py index 89f5afa1..67c09618 100644 --- a/game/inventory.py +++ b/game/inventory.py @@ -1,11 +1,15 @@ """Inventory management APIs.""" +from __future__ import annotations + from collections import defaultdict -from typing import Dict, Iterable, Iterator, Set, Tuple +from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING from dcs.unittype import UnitType from gen.flights.flight import Flight -from theater import ControlPoint + +if TYPE_CHECKING: + from game.theater import ControlPoint class ControlPointAircraftInventory: diff --git a/game/models/frontline_data.py b/game/models/frontline_data.py index 94947135..586ebd58 100644 --- a/game/models/frontline_data.py +++ b/game/models/frontline_data.py @@ -1,4 +1,4 @@ -from theater import ControlPoint +from game.theater import ControlPoint class FrontlineData: diff --git a/game/operation/operation.py b/game/operation/operation.py index 0ff06ebe..1e01065b 100644 --- a/game/operation/operation.py +++ b/game/operation/operation.py @@ -15,6 +15,7 @@ from dcs.triggers import TriggerStart from dcs.unittype import UnitType from game.plugins import LuaPluginManager +from game.theater import ControlPoint from gen import Conflict, FlightType, VisualGenerator from gen.aircraft import AIRCRAFT_DATA, AircraftConflictGenerator, FlightData from gen.airfields import AIRFIELD_DATA @@ -29,7 +30,6 @@ from gen.kneeboard import KneeboardGenerator from gen.radios import RadioFrequency, RadioRegistry from gen.tacan import TacanRegistry from gen.triggergen import TRIGGER_RADIUS_MEDIUM, TriggersGenerator -from theater import ControlPoint from .. import db from ..debriefing import Debriefing diff --git a/game/theater/frontline.py b/game/theater/frontline.py deleted file mode 100644 index 3b57f9b6..00000000 --- a/game/theater/frontline.py +++ /dev/null @@ -1 +0,0 @@ -"""Only here to keep compatibility for save games generated in version 2.2.0""" diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index ed30750b..c5232a32 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -39,10 +39,11 @@ from gen.sam.sam_group_generator import ( generate_anti_air_group, generate_ewr_group, generate_shorad_group, ) -from theater import ( +from . import ( ConflictTheater, ControlPoint, - ControlPointType, OffMapSpawn, + ControlPointType, + OffMapSpawn, ) GroundObjectTemplates = Dict[str, Dict[str, Any]] diff --git a/game/weather.py b/game/weather.py index d6775614..e8efd6e7 100644 --- a/game/weather.py +++ b/game/weather.py @@ -10,7 +10,7 @@ from typing import Optional from dcs.weather import Weather as PydcsWeather, Wind from game.settings import Settings -from theater import ConflictTheater +from game.theater import ConflictTheater class TimeOfDay(Enum): diff --git a/gen/aircraft.py b/gen/aircraft.py index 13de2c05..f3690915 100644 --- a/gen/aircraft.py +++ b/gen/aircraft.py @@ -70,6 +70,12 @@ from dcs.unittype import FlyingType, UnitType from game import db from game.data.cap_capabilities_db import GUNFIGHTERS from game.settings import Settings +from game.theater.controlpoint import ( + ControlPoint, + ControlPointType, + OffMapSpawn, +) +from game.theater.theatergroundobject import TheaterGroundObject from game.utils import knots_to_kph, nm_to_meter from gen.airsupportgen import AirSupport from gen.ato import AirTaskingOrder, Package @@ -83,12 +89,6 @@ from gen.flights.flight import ( ) from gen.radios import MHz, Radio, RadioFrequency, RadioRegistry, get_radio from gen.runways import RunwayData -from theater import TheaterGroundObject -from game.theater.controlpoint import ( - ControlPoint, - ControlPointType, - OffMapSpawn, -) from .conflictgen import Conflict from .flights.flightplan import ( CasFlightPlan, diff --git a/gen/conflictgen.py b/gen/conflictgen.py index 6a5a8e07..35be5956 100644 --- a/gen/conflictgen.py +++ b/gen/conflictgen.py @@ -5,7 +5,8 @@ from typing import Tuple from dcs.country import Country from dcs.mapping import Point -from theater import ConflictTheater, ControlPoint, FrontLine +from game.theater.conflicttheater import ConflictTheater, FrontLine +from game.theater.controlpoint import ControlPoint AIR_DISTANCE = 40000 diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 1cd48ed7..216972dd 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -21,6 +21,19 @@ from dcs.unittype import FlyingType, UnitType from game import db from game.data.radar_db import UNITS_WITH_RADAR from game.infos.information import Information +from game.theater import ( + ControlPoint, + FrontLine, + MissionTarget, + OffMapSpawn, + SamGroundObject, + TheaterGroundObject, +) +# Avoid importing some types that cause circular imports unless type checking. +from game.theater.theatergroundobject import ( + EwrGroundObject, + NavalGroundObject, VehicleGroupGroundObject, +) from game.utils import nm_to_meter from gen import Conflict from gen.ato import Package @@ -46,19 +59,6 @@ from gen.flights.flight import ( ) from gen.flights.flightplan import FlightPlanBuilder from gen.flights.traveltime import TotEstimator -from theater import ( - ControlPoint, - FrontLine, - MissionTarget, - OffMapSpawn, TheaterGroundObject, - SamGroundObject, -) - -# Avoid importing some types that cause circular imports unless type checking. -from game.theater.theatergroundobject import ( - EwrGroundObject, - NavalGroundObject, VehicleGroupGroundObject, -) if TYPE_CHECKING: from game import Game diff --git a/gen/flights/closestairfields.py b/gen/flights/closestairfields.py index a6045dde..5bba28db 100644 --- a/gen/flights/closestairfields.py +++ b/gen/flights/closestairfields.py @@ -1,7 +1,7 @@ """Objective adjacency lists.""" from typing import Dict, Iterator, List, Optional -from theater import ConflictTheater, ControlPoint, MissionTarget +from game.theater import ConflictTheater, ControlPoint, MissionTarget class ClosestAirfields: diff --git a/gen/flights/flightplan.py b/gen/flights/flightplan.py index 8df8dc5f..d8758e32 100644 --- a/gen/flights/flightplan.py +++ b/gen/flights/flightplan.py @@ -7,20 +7,19 @@ generating the waypoints for the mission. """ from __future__ import annotations -import math -from datetime import timedelta -from functools import cached_property import logging +import math import random from dataclasses import dataclass +from datetime import timedelta +from functools import cached_property from typing import Iterator, List, Optional, Set, TYPE_CHECKING, Tuple from dcs.mapping import Point from dcs.unit import Unit from game.data.doctrine import Doctrine -from game.utils import nm_to_meter -from theater import ( +from game.theater import ( ControlPoint, FrontLine, MissionTarget, @@ -28,6 +27,7 @@ from theater import ( TheaterGroundObject, ) from game.theater.theatergroundobject import EwrGroundObject +from game.utils import nm_to_meter from .closestairfields import ObjectiveDistanceCache from .flight import Flight, FlightType, FlightWaypoint, FlightWaypointType from .traveltime import GroundSpeed, TravelTime @@ -393,10 +393,6 @@ class TarCapFlightPlan(PatrollingFlightPlan): return super().patrol_end_time -# TODO: Remove when breaking save compat. -FrontLineCapFlightPlan = TarCapFlightPlan - - @dataclass(frozen=True) class StrikeFlightPlan(FormationFlightPlan): takeoff: FlightWaypoint diff --git a/gen/flights/waypointbuilder.py b/gen/flights/waypointbuilder.py index fa992419..74731929 100644 --- a/gen/flights/waypointbuilder.py +++ b/gen/flights/waypointbuilder.py @@ -8,14 +8,13 @@ from dcs.unit import Unit from dcs.unitgroup import VehicleGroup from game.data.doctrine import Doctrine -from game.utils import feet_to_meter -from game.weather import Conditions -from theater import ( +from game.theater import ( ControlPoint, MissionTarget, OffMapSpawn, TheaterGroundObject, ) +from game.weather import Conditions from .flight import Flight, FlightWaypoint, FlightWaypointType diff --git a/gen/ground_forces/ai_ground_planner.py b/gen/ground_forces/ai_ground_planner.py index db1deb03..b0f14df4 100644 --- a/gen/ground_forces/ai_ground_planner.py +++ b/gen/ground_forces/ai_ground_planner.py @@ -2,12 +2,12 @@ import random from enum import Enum from typing import Dict, List -from dcs.vehicles import Armor, Artillery, Infantry, Unarmed from dcs.unittype import VehicleType +from dcs.vehicles import Armor, Artillery, Infantry, Unarmed import pydcs_extensions.frenchpack.frenchpack as frenchpack +from game.theater import ControlPoint from gen.ground_forces.combat_stance import CombatStance -from theater import ControlPoint TYPE_TANKS = [ Armor.MBT_T_55, diff --git a/gen/groundobjectsgen.py b/gen/groundobjectsgen.py index edd58e6d..09385c78 100644 --- a/gen/groundobjectsgen.py +++ b/gen/groundobjectsgen.py @@ -20,14 +20,14 @@ from dcs.task import ( EPLRS, OptAlarmState, ) -from dcs.unit import Ship, Vehicle, Unit +from dcs.unit import Ship, Unit, Vehicle from dcs.unitgroup import Group, ShipGroup, StaticGroup from dcs.unittype import StaticType, UnitType from game import db from game.data.building_data import FORTIFICATION_UNITS, FORTIFICATION_UNITS_ID from game.db import unit_type_from_name -from theater import ControlPoint, TheaterGroundObject +from game.theater import ControlPoint, TheaterGroundObject from game.theater.theatergroundobject import ( BuildingGroundObject, CarrierGroundObject, GenericCarrierGroundObject, diff --git a/gen/runways.py b/gen/runways.py index 5323c37b..658cc846 100644 --- a/gen/runways.py +++ b/gen/runways.py @@ -7,8 +7,8 @@ from typing import Iterator, Optional from dcs.terrain.terrain import Airport +from game.theater import ControlPoint, ControlPointType from game.weather import Conditions -from theater import ControlPoint, ControlPointType from .airfields import AIRFIELD_DATA from .radios import RadioFrequency from .tacan import TacanChannel diff --git a/qt_ui/widgets/base/QAirportInformation.py b/qt_ui/widgets/base/QAirportInformation.py index 4fc1474c..b110e59a 100644 --- a/qt_ui/widgets/base/QAirportInformation.py +++ b/qt_ui/widgets/base/QAirportInformation.py @@ -1,6 +1,6 @@ from PySide2.QtWidgets import QGridLayout, QLabel, QGroupBox, QVBoxLayout, QLCDNumber -from theater import ControlPoint, Airport +from game.theater import ControlPoint, Airport class QAirportInformation(QGroupBox): diff --git a/qt_ui/widgets/combos/QFlightTypeComboBox.py b/qt_ui/widgets/combos/QFlightTypeComboBox.py index 6ba9e455..1918dd4d 100644 --- a/qt_ui/widgets/combos/QFlightTypeComboBox.py +++ b/qt_ui/widgets/combos/QFlightTypeComboBox.py @@ -2,7 +2,7 @@ from PySide2.QtWidgets import QComboBox -from theater import ConflictTheater, MissionTarget +from game.theater import ConflictTheater, MissionTarget class QFlightTypeComboBox(QComboBox): diff --git a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py index 8af3c3f4..8f40afde 100644 --- a/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py +++ b/qt_ui/widgets/combos/QPredefinedWaypointSelectionComboBox.py @@ -1,10 +1,10 @@ from PySide2.QtGui import QStandardItem, QStandardItemModel from game import Game +from game.theater import ControlPointType from gen import BuildingGroundObject, Conflict, FlightWaypointType from gen.flights.flight import FlightWaypoint from qt_ui.widgets.combos.QFilteredComboBox import QFilteredComboBox -from theater import ControlPointType class QPredefinedWaypointSelectionComboBox(QFilteredComboBox): diff --git a/qt_ui/widgets/map/QFrontLine.py b/qt_ui/widgets/map/QFrontLine.py index 1849f5ff..2ca71953 100644 --- a/qt_ui/widgets/map/QFrontLine.py +++ b/qt_ui/widgets/map/QFrontLine.py @@ -13,11 +13,11 @@ from PySide2.QtWidgets import ( ) import qt_ui.uiconstants as const +from game.theater import FrontLine from qt_ui.dialogs import Dialog from qt_ui.models import GameModel from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.mission.QPackageDialog import QNewPackageDialog -from theater import FrontLine class QFrontLine(QGraphicsLineItem): diff --git a/qt_ui/widgets/map/QLiberationMap.py b/qt_ui/widgets/map/QLiberationMap.py index d0189203..50fc5fba 100644 --- a/qt_ui/widgets/map/QLiberationMap.py +++ b/qt_ui/widgets/map/QLiberationMap.py @@ -3,7 +3,7 @@ from __future__ import annotations import datetime import logging import math -from typing import Iterable, List, Optional, Tuple, Iterator +from typing import Iterable, Iterator, List, Optional, Tuple from PySide2.QtCore import QPointF, Qt from PySide2.QtGui import ( @@ -27,6 +27,13 @@ from dcs.mapping import point_from_heading import qt_ui.uiconstants as CONST from game import Game, db +from game.theater import ControlPoint +from game.theater.conflicttheater import FrontLine +from game.theater.theatergroundobject import ( + EwrGroundObject, + MissileSiteGroundObject, + TheaterGroundObject, +) from game.utils import meter_to_feet from game.weather import TimeOfDay from gen import Conflict @@ -39,13 +46,7 @@ from qt_ui.widgets.map.QLiberationScene import QLiberationScene from qt_ui.widgets.map.QMapControlPoint import QMapControlPoint from qt_ui.widgets.map.QMapGroundObject import QMapGroundObject from qt_ui.windows.GameUpdateSignal import GameUpdateSignal -from theater import ControlPoint -from game.theater.conflicttheater import FrontLine -from game.theater.theatergroundobject import ( - EwrGroundObject, - MissileSiteGroundObject, - TheaterGroundObject, -) + def binomial(i: int, n: int) -> float: """Binomial coefficient""" diff --git a/qt_ui/widgets/map/QMapControlPoint.py b/qt_ui/widgets/map/QMapControlPoint.py index 7ef55952..0f88bf7e 100644 --- a/qt_ui/widgets/map/QMapControlPoint.py +++ b/qt_ui/widgets/map/QMapControlPoint.py @@ -4,9 +4,9 @@ from PySide2.QtGui import QColor, QPainter from PySide2.QtWidgets import QAction, QMenu import qt_ui.uiconstants as const +from game.theater import ControlPoint from qt_ui.models import GameModel from qt_ui.windows.basemenu.QBaseMenu2 import QBaseMenu2 -from theater import ControlPoint from .QMapObject import QMapObject from ...displayoptions import DisplayOptions from ...windows.GameUpdateSignal import GameUpdateSignal diff --git a/qt_ui/widgets/map/QMapGroundObject.py b/qt_ui/widgets/map/QMapGroundObject.py index a7d857f3..f1d3e542 100644 --- a/qt_ui/widgets/map/QMapGroundObject.py +++ b/qt_ui/widgets/map/QMapGroundObject.py @@ -8,8 +8,8 @@ import qt_ui.uiconstants as const from game import Game from game.data.building_data import FORTIFICATION_BUILDINGS from game.db import REWARDS +from game.theater import ControlPoint, TheaterGroundObject from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu -from theater import ControlPoint, TheaterGroundObject from .QMapObject import QMapObject from ...displayoptions import DisplayOptions diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index cfdb8128..f9f7c159 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -2,12 +2,12 @@ from PySide2.QtCore import Qt from PySide2.QtGui import QCloseEvent, QPixmap from PySide2.QtWidgets import QDialog, QGridLayout, QHBoxLayout, QLabel, QWidget +from game.theater import ControlPoint, ControlPointType from qt_ui.models import GameModel from qt_ui.uiconstants import EVENT_ICONS from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.basemenu.QBaseMenuTabs import QBaseMenuTabs from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour -from theater import ControlPoint, ControlPointType class QBaseMenu2(QDialog): diff --git a/qt_ui/windows/basemenu/QBaseMenuTabs.py b/qt_ui/windows/basemenu/QBaseMenuTabs.py index fd2f7ae7..1e705372 100644 --- a/qt_ui/windows/basemenu/QBaseMenuTabs.py +++ b/qt_ui/windows/basemenu/QBaseMenuTabs.py @@ -1,11 +1,11 @@ -from PySide2.QtWidgets import QFrame, QGridLayout, QLabel, QTabWidget +from PySide2.QtWidgets import QTabWidget +from game.theater import ControlPoint, OffMapSpawn from qt_ui.models import GameModel from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand from qt_ui.windows.basemenu.base_defenses.QBaseDefensesHQ import QBaseDefensesHQ from qt_ui.windows.basemenu.ground_forces.QGroundForcesHQ import QGroundForcesHQ from qt_ui.windows.basemenu.intel.QIntelInfo import QIntelInfo -from theater import ControlPoint, OffMapSpawn class QBaseMenuTabs(QTabWidget): diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index b41ac68a..5fdd1289 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -1,3 +1,5 @@ +import logging + from PySide2.QtWidgets import ( QGroupBox, QHBoxLayout, @@ -6,11 +8,9 @@ from PySide2.QtWidgets import ( QSizePolicy, QSpacerItem, ) -import logging from dcs.unittype import UnitType -from theater import db - +from game import db class QRecruitBehaviour: diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index a01aaaa9..2dbbd3ca 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -11,13 +11,15 @@ from PySide2.QtWidgets import ( QVBoxLayout, QWidget, ) +from dcs.task import CAP, CAS from dcs.unittype import UnitType +from game import db from game.event.event import UnitsDeliveryEvent +from game.theater import ControlPoint from qt_ui.models import GameModel from qt_ui.uiconstants import ICONS from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour -from theater import CAP, CAS, ControlPoint, db class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): diff --git a/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py b/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py index 9965115a..97c804bf 100644 --- a/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py +++ b/qt_ui/windows/basemenu/airfield/QAirfieldCommand.py @@ -1,10 +1,10 @@ from PySide2.QtWidgets import QFrame, QGridLayout, QGroupBox, QVBoxLayout +from game.theater import ControlPoint from qt_ui.models import GameModel from qt_ui.windows.basemenu.airfield.QAircraftRecruitmentMenu import \ QAircraftRecruitmentMenu from qt_ui.windows.mission.QPlannedFlightsView import QPlannedFlightsView -from theater import ControlPoint class QAirfieldCommand(QFrame): diff --git a/qt_ui/windows/basemenu/base_defenses/QBaseDefenseGroupInfo.py b/qt_ui/windows/basemenu/base_defenses/QBaseDefenseGroupInfo.py index 350cf5e8..6d46b35b 100644 --- a/qt_ui/windows/basemenu/base_defenses/QBaseDefenseGroupInfo.py +++ b/qt_ui/windows/basemenu/base_defenses/QBaseDefenseGroupInfo.py @@ -1,10 +1,16 @@ from PySide2.QtCore import Qt -from PySide2.QtWidgets import QGridLayout, QLabel, QGroupBox, QPushButton, QVBoxLayout +from PySide2.QtWidgets import ( + QGridLayout, + QGroupBox, + QLabel, + QPushButton, + QVBoxLayout, +) +from game.theater import ControlPoint, TheaterGroundObject from qt_ui.dialogs import Dialog from qt_ui.uiconstants import VEHICLES_ICONS from qt_ui.windows.groundobject.QGroundObjectMenu import QGroundObjectMenu -from theater import ControlPoint, TheaterGroundObject class QBaseDefenseGroupInfo(QGroupBox): diff --git a/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py b/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py index 5ad1f6c9..75a45eb0 100644 --- a/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py +++ b/qt_ui/windows/basemenu/base_defenses/QBaseDefensesHQ.py @@ -1,7 +1,9 @@ from PySide2.QtWidgets import QFrame, QGridLayout + from game import Game -from qt_ui.windows.basemenu.base_defenses.QBaseInformation import QBaseInformation -from theater import ControlPoint +from game.theater import ControlPoint +from qt_ui.windows.basemenu.base_defenses.QBaseInformation import \ + QBaseInformation class QBaseDefensesHQ(QFrame): diff --git a/qt_ui/windows/basemenu/base_defenses/QBaseInformation.py b/qt_ui/windows/basemenu/base_defenses/QBaseInformation.py index f5325887..50ec2f81 100644 --- a/qt_ui/windows/basemenu/base_defenses/QBaseInformation.py +++ b/qt_ui/windows/basemenu/base_defenses/QBaseInformation.py @@ -1,10 +1,15 @@ from PySide2.QtGui import Qt -from PySide2.QtWidgets import QGridLayout, QLabel, QGroupBox, QVBoxLayout, QFrame, QWidget, QScrollArea +from PySide2.QtWidgets import ( + QFrame, + QGridLayout, + QScrollArea, + QVBoxLayout, + QWidget, +) -from game import db -from qt_ui.uiconstants import AIRCRAFT_ICONS, VEHICLES_ICONS -from qt_ui.windows.basemenu.base_defenses.QBaseDefenseGroupInfo import QBaseDefenseGroupInfo -from theater import ControlPoint, Airport +from game.theater import Airport, ControlPoint +from qt_ui.windows.basemenu.base_defenses.QBaseDefenseGroupInfo import \ + QBaseDefenseGroupInfo class QBaseInformation(QFrame): diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index ec1cabf6..d00c6b9d 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -6,11 +6,13 @@ from PySide2.QtWidgets import ( QVBoxLayout, QWidget, ) +from dcs.task import PinpointStrike +from game import db from game.event import UnitsDeliveryEvent +from game.theater import ControlPoint from qt_ui.models import GameModel from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour -from theater import ControlPoint, PinpointStrike, db class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py index bb18594f..39cba843 100644 --- a/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py +++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesHQ.py @@ -1,11 +1,11 @@ from PySide2.QtWidgets import QFrame, QGridLayout +from game.theater import ControlPoint from qt_ui.models import GameModel from qt_ui.windows.basemenu.ground_forces.QArmorRecruitmentMenu import \ QArmorRecruitmentMenu from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategy import \ QGroundForcesStrategy -from theater import ControlPoint class QGroundForcesHQ(QFrame): diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py index 0b7b4db6..3aee8c50 100644 --- a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py +++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategy.py @@ -1,8 +1,9 @@ -from PySide2.QtWidgets import QLabel, QGroupBox, QVBoxLayout +from PySide2.QtWidgets import QGroupBox, QLabel, QVBoxLayout from game import Game -from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import QGroundForcesStrategySelector -from theater import ControlPoint +from game.theater import ControlPoint +from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import \ + QGroundForcesStrategySelector class QGroundForcesStrategy(QGroupBox): diff --git a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py index 09c3fa5b..4acd8731 100644 --- a/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py +++ b/qt_ui/windows/basemenu/ground_forces/QGroundForcesStrategySelector.py @@ -1,6 +1,6 @@ from PySide2.QtWidgets import QComboBox -from theater import ControlPoint, CombatStance +from game.theater import CombatStance, ControlPoint class QGroundForcesStrategySelector(QComboBox): diff --git a/qt_ui/windows/basemenu/intel/QIntelInfo.py b/qt_ui/windows/basemenu/intel/QIntelInfo.py index bc7cb13b..e422ef3a 100644 --- a/qt_ui/windows/basemenu/intel/QIntelInfo.py +++ b/qt_ui/windows/basemenu/intel/QIntelInfo.py @@ -1,11 +1,14 @@ +from PySide2.QtWidgets import ( + QFrame, + QGridLayout, + QGroupBox, + QLabel, + QVBoxLayout, +) +from dcs.task import CAP, CAS, Embarking, PinpointStrike - -from PySide2.QtWidgets import QLabel, QGroupBox, QVBoxLayout, QFrame, QGridLayout -from dcs.task import Embarking, CAS, PinpointStrike, CAP - -from game import Game -from qt_ui.windows.basemenu.ground_forces.QGroundForcesStrategySelector import QGroundForcesStrategySelector -from theater import ControlPoint, db +from game import Game, db +from game.theater import ControlPoint class QIntelInfo(QFrame): diff --git a/qt_ui/windows/groundobject/QGroundObjectMenu.py b/qt_ui/windows/groundobject/QGroundObjectMenu.py index a1fda851..abbf5c8c 100644 --- a/qt_ui/windows/groundobject/QGroundObjectMenu.py +++ b/qt_ui/windows/groundobject/QGroundObjectMenu.py @@ -2,20 +2,31 @@ import logging from PySide2 import QtCore from PySide2.QtGui import Qt -from PySide2.QtWidgets import QHBoxLayout, QDialog, QGridLayout, QLabel, QGroupBox, QVBoxLayout, QPushButton, \ - QComboBox, QSpinBox, QMessageBox +from PySide2.QtWidgets import ( + QComboBox, + QDialog, + QGridLayout, + QGroupBox, + QHBoxLayout, + QLabel, + QMessageBox, + QPushButton, + QSpinBox, + QVBoxLayout, +) from dcs import Point from game import Game, db from game.data.building_data import FORTIFICATION_BUILDINGS -from game.db import PRICES, REWARDS, unit_type_of, PinpointStrike -from gen.defenses.armor_group_generator import generate_armor_group_of_type_and_size +from game.db import PRICES, PinpointStrike, REWARDS, unit_type_of +from game.theater import ControlPoint, TheaterGroundObject +from gen.defenses.armor_group_generator import \ + generate_armor_group_of_type_and_size from gen.sam.sam_group_generator import get_faction_possible_sams_generator from qt_ui.uiconstants import EVENT_ICONS from qt_ui.widgets.QBudgetBox import QBudgetBox from qt_ui.windows.GameUpdateSignal import GameUpdateSignal from qt_ui.windows.groundobject.QBuildingInfo import QBuildingInfo -from theater import ControlPoint, TheaterGroundObject class QGroundObjectMenu(QDialog): diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 604bcd57..0e0bf773 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -10,6 +10,7 @@ from PySide2.QtWidgets import ( from dcs.planes import PlaneType from game import Game +from game.theater import ControlPoint, OffMapSpawn from gen.ato import Package from gen.flights.flight import Flight from qt_ui.uiconstants import EVENT_ICONS @@ -20,7 +21,6 @@ from qt_ui.widgets.combos.QArrivalAirfieldSelector import \ QArrivalAirfieldSelector from qt_ui.widgets.combos.QFlightTypeComboBox import QFlightTypeComboBox from qt_ui.widgets.combos.QOriginAirfieldSelector import QOriginAirfieldSelector -from theater import ControlPoint, OffMapSpawn class QFlightCreator(QDialog): diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py index 09839224..86ce0461 100644 --- a/qt_ui/windows/newgame/QCampaignList.py +++ b/qt_ui/windows/newgame/QCampaignList.py @@ -12,7 +12,7 @@ from PySide2.QtGui import QStandardItem, QStandardItemModel from PySide2.QtWidgets import QAbstractItemView, QListView import qt_ui.uiconstants as CONST -from theater import ConflictTheater +from game.theater import ConflictTheater @dataclass(frozen=True) diff --git a/theater/__init__.py b/theater/__init__.py deleted file mode 100644 index f6b256d8..00000000 --- a/theater/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# For save game compatibility. Remove before 2.3. -from game.theater import * diff --git a/theater/base.py b/theater/base.py deleted file mode 100644 index fc28c91b..00000000 --- a/theater/base.py +++ /dev/null @@ -1,2 +0,0 @@ -# For save compat. Remove in 2.3. -from game.theater.base import * diff --git a/theater/conflicttheater.py b/theater/conflicttheater.py deleted file mode 100644 index e1566178..00000000 --- a/theater/conflicttheater.py +++ /dev/null @@ -1,2 +0,0 @@ -# For save compat. Remove in 2.3. -from game.theater.conflicttheater import * diff --git a/theater/controlpoint.py b/theater/controlpoint.py deleted file mode 100644 index 90a6b164..00000000 --- a/theater/controlpoint.py +++ /dev/null @@ -1,2 +0,0 @@ -# For save compat. Remove in 2.3. -from game.theater.controlpoint import * diff --git a/theater/frontline.py b/theater/frontline.py deleted file mode 100644 index 5ddb5706..00000000 --- a/theater/frontline.py +++ /dev/null @@ -1,3 +0,0 @@ -# For save compat. Remove in 2.3. -from game.theater.frontline import * -from game.theater.conflicttheater import FrontLine \ No newline at end of file diff --git a/theater/theatergroundobject.py b/theater/theatergroundobject.py deleted file mode 100644 index 3c77455d..00000000 --- a/theater/theatergroundobject.py +++ /dev/null @@ -1,2 +0,0 @@ -# For save compat. Remove in 2.3. -from game.theater.theatergroundobject import * From a594f45aaeae87c53c23d1e54b95da4d1f430974 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 17:29:58 -0800 Subject: [PATCH 21/27] Pick divert airfields when planning. https://github.com/Khopa/dcs_liberation/issues/342 --- game/inventory.py | 18 ++++++------- game/theater/controlpoint.py | 8 ++++++ gen/flights/ai_flight_planner.py | 25 ++++++++++++++++--- qt_ui/widgets/combos/QAircraftTypeSelector.py | 4 +-- .../combos/QArrivalAirfieldSelector.py | 15 +++-------- .../widgets/combos/QOriginAirfieldSelector.py | 6 ++--- 6 files changed, 47 insertions(+), 29 deletions(-) diff --git a/game/inventory.py b/game/inventory.py index 67c09618..3c92a80f 100644 --- a/game/inventory.py +++ b/game/inventory.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections import defaultdict from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING -from dcs.unittype import UnitType +from dcs.unittype import FlyingType from gen.flights.flight import Flight @@ -17,9 +17,9 @@ class ControlPointAircraftInventory: def __init__(self, control_point: ControlPoint) -> None: self.control_point = control_point - self.inventory: Dict[UnitType, int] = defaultdict(int) + self.inventory: Dict[FlyingType, int] = defaultdict(int) - def add_aircraft(self, aircraft: UnitType, count: int) -> None: + def add_aircraft(self, aircraft: FlyingType, count: int) -> None: """Adds aircraft to the inventory. Args: @@ -28,7 +28,7 @@ class ControlPointAircraftInventory: """ self.inventory[aircraft] += count - def remove_aircraft(self, aircraft: UnitType, count: int) -> None: + def remove_aircraft(self, aircraft: FlyingType, count: int) -> None: """Removes aircraft from the inventory. Args: @@ -47,7 +47,7 @@ class ControlPointAircraftInventory: ) self.inventory[aircraft] -= count - def available(self, aircraft: UnitType) -> int: + def available(self, aircraft: FlyingType) -> int: """Returns the number of available aircraft of the given type. Args: @@ -59,14 +59,14 @@ class ControlPointAircraftInventory: return 0 @property - def types_available(self) -> Iterator[UnitType]: + def types_available(self) -> Iterator[FlyingType]: """Iterates over all available aircraft types.""" for aircraft, count in self.inventory.items(): if count > 0: yield aircraft @property - def all_aircraft(self) -> Iterator[Tuple[UnitType, int]]: + def all_aircraft(self) -> Iterator[Tuple[FlyingType, int]]: """Iterates over all available aircraft types, including amounts.""" for aircraft, count in self.inventory.items(): if count > 0: @@ -106,9 +106,9 @@ class GlobalAircraftInventory: return self.inventories[control_point] @property - def available_types_for_player(self) -> Iterator[UnitType]: + def available_types_for_player(self) -> Iterator[FlyingType]: """Iterates over all aircraft types available to the player.""" - seen: Set[UnitType] = set() + seen: Set[FlyingType] = set() for control_point, inventory in self.inventories.items(): if control_point.captured: for aircraft in inventory.types_available: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 88d622a6..dc2097c1 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -16,6 +16,7 @@ from dcs.ships import ( Type_071_Amphibious_Transport_Dock, ) from dcs.terrain.terrain import Airport +from dcs.unittype import FlyingType from game import db from gen.ground_forces.combat_stance import CombatStance @@ -366,6 +367,13 @@ class ControlPoint(MissionTarget): # TODO: FlightType.STRIKE ] + def can_land(self, aircraft: FlyingType) -> bool: + if self.is_carrier and aircraft not in db.CARRIER_CAPABLE: + return False + if self.is_lha and aircraft not in db.LHA_CAPABLE: + return False + return True + class OffMapSpawn(ControlPoint): def __init__(self, id: int, name: str, position: Point): diff --git a/gen/flights/ai_flight_planner.py b/gen/flights/ai_flight_planner.py index 216972dd..182e0455 100644 --- a/gen/flights/ai_flight_planner.py +++ b/gen/flights/ai_flight_planner.py @@ -16,7 +16,7 @@ from typing import ( Type, ) -from dcs.unittype import FlyingType, UnitType +from dcs.unittype import FlyingType from game import db from game.data.radar_db import UNITS_WITH_RADAR @@ -119,7 +119,7 @@ class AircraftAllocator: def find_aircraft_for_flight( self, flight: ProposedFlight - ) -> Optional[Tuple[ControlPoint, UnitType]]: + ) -> Optional[Tuple[ControlPoint, FlyingType]]: """Finds aircraft suitable for the given mission. Searches for aircraft capable of performing the given mission within the @@ -190,7 +190,7 @@ class AircraftAllocator: def find_aircraft_of_type( self, flight: ProposedFlight, types: List[Type[FlyingType]], - ) -> Optional[Tuple[ControlPoint, UnitType]]: + ) -> Optional[Tuple[ControlPoint, FlyingType]]: airfields_in_range = self.closest_airfields.airfields_within( flight.max_distance ) @@ -214,6 +214,8 @@ class PackageBuilder: global_inventory: GlobalAircraftInventory, is_player: bool, start_type: str) -> None: + self.closest_airfields = closest_airfields + self.is_player = is_player self.package = Package(location) self.allocator = AircraftAllocator(closest_airfields, global_inventory, is_player) @@ -239,10 +241,25 @@ class PackageBuilder: flight = Flight(self.package, aircraft, plan.num_aircraft, plan.task, start_type, departure=airfield, arrival=airfield, - divert=None) + divert=self.find_divert_field(aircraft, airfield)) self.package.add_flight(flight) return True + def find_divert_field(self, aircraft: FlyingType, + arrival: ControlPoint) -> Optional[ControlPoint]: + divert_limit = nm_to_meter(150) + for airfield in self.closest_airfields.airfields_within(divert_limit): + if airfield.captured != self.is_player: + continue + if airfield == arrival: + continue + if not airfield.can_land(aircraft): + continue + if isinstance(airfield, OffMapSpawn): + continue + return airfield + return None + def build(self) -> Package: """Returns the built package.""" return self.package diff --git a/qt_ui/widgets/combos/QAircraftTypeSelector.py b/qt_ui/widgets/combos/QAircraftTypeSelector.py index 1f490e4d..2be6e48c 100644 --- a/qt_ui/widgets/combos/QAircraftTypeSelector.py +++ b/qt_ui/widgets/combos/QAircraftTypeSelector.py @@ -3,13 +3,13 @@ from typing import Iterable from PySide2.QtWidgets import QComboBox -from dcs.planes import PlaneType +from dcs.unittype import FlyingType class QAircraftTypeSelector(QComboBox): """Combo box for selecting among the given aircraft types.""" - def __init__(self, aircraft_types: Iterable[PlaneType]) -> None: + def __init__(self, aircraft_types: Iterable[FlyingType]) -> None: super().__init__() for aircraft in aircraft_types: self.addItem(f"{aircraft.id}", userData=aircraft) diff --git a/qt_ui/widgets/combos/QArrivalAirfieldSelector.py b/qt_ui/widgets/combos/QArrivalAirfieldSelector.py index a1fe88bb..c5d89b90 100644 --- a/qt_ui/widgets/combos/QArrivalAirfieldSelector.py +++ b/qt_ui/widgets/combos/QArrivalAirfieldSelector.py @@ -2,7 +2,7 @@ from typing import Iterable from PySide2.QtWidgets import QComboBox -from dcs.planes import PlaneType +from dcs.unittype import FlyingType from game import db from game.theater.controlpoint import ControlPoint @@ -16,7 +16,7 @@ class QArrivalAirfieldSelector(QComboBox): """ def __init__(self, destinations: Iterable[ControlPoint], - aircraft: PlaneType, optional_text: str) -> None: + aircraft: FlyingType, optional_text: str) -> None: super().__init__() self.destinations = list(destinations) self.aircraft = aircraft @@ -24,23 +24,16 @@ class QArrivalAirfieldSelector(QComboBox): self.rebuild_selector() self.setCurrentIndex(0) - def change_aircraft(self, aircraft: PlaneType) -> None: + def change_aircraft(self, aircraft: FlyingType) -> None: if self.aircraft == aircraft: return self.aircraft = aircraft self.rebuild_selector() - def valid_destination(self, destination: ControlPoint) -> bool: - if destination.is_carrier and self.aircraft not in db.CARRIER_CAPABLE: - return False - if destination.is_lha and self.aircraft not in db.LHA_CAPABLE: - return False - return True - def rebuild_selector(self) -> None: self.clear() for destination in self.destinations: - if self.valid_destination(destination): + if destination.can_land(self.aircraft): self.addItem(destination.name, destination) self.model().sort(0) self.insertItem(0, self.optional_text, None) diff --git a/qt_ui/widgets/combos/QOriginAirfieldSelector.py b/qt_ui/widgets/combos/QOriginAirfieldSelector.py index 14bdbb47..ce1c6301 100644 --- a/qt_ui/widgets/combos/QOriginAirfieldSelector.py +++ b/qt_ui/widgets/combos/QOriginAirfieldSelector.py @@ -3,7 +3,7 @@ from typing import Iterable from PySide2.QtCore import Signal from PySide2.QtWidgets import QComboBox -from dcs.planes import PlaneType +from dcs.unittype import FlyingType from game.inventory import GlobalAircraftInventory from game.theater.controlpoint import ControlPoint @@ -20,7 +20,7 @@ class QOriginAirfieldSelector(QComboBox): def __init__(self, global_inventory: GlobalAircraftInventory, origins: Iterable[ControlPoint], - aircraft: PlaneType) -> None: + aircraft: FlyingType) -> None: super().__init__() self.global_inventory = global_inventory self.origins = list(origins) @@ -28,7 +28,7 @@ class QOriginAirfieldSelector(QComboBox): self.rebuild_selector() self.currentIndexChanged.connect(self.index_changed) - def change_aircraft(self, aircraft: PlaneType) -> None: + def change_aircraft(self, aircraft: FlyingType) -> None: if self.aircraft == aircraft: return self.aircraft = aircraft From 20091292f42a79a55f03b7c70e24c659bfbc1334 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 17:37:20 -0800 Subject: [PATCH 22/27] Remove dead code. --- qt_ui/widgets/base/QAirportInformation.py | 52 ----------------------- 1 file changed, 52 deletions(-) delete mode 100644 qt_ui/widgets/base/QAirportInformation.py diff --git a/qt_ui/widgets/base/QAirportInformation.py b/qt_ui/widgets/base/QAirportInformation.py deleted file mode 100644 index b110e59a..00000000 --- a/qt_ui/widgets/base/QAirportInformation.py +++ /dev/null @@ -1,52 +0,0 @@ -from PySide2.QtWidgets import QGridLayout, QLabel, QGroupBox, QVBoxLayout, QLCDNumber - -from game.theater import ControlPoint, Airport - - -class QAirportInformation(QGroupBox): - - def __init__(self, cp:ControlPoint, airport:Airport): - super(QAirportInformation, self).__init__(airport.name) - self.cp = cp - self.airport = airport - self.init_ui() - - def init_ui(self): - self.layout = QGridLayout() - - # Runway information - self.runways = QGroupBox("Runways") - self.runwayLayout = QGridLayout() - for i, runway in enumerate(self.airport.runways): - - # Seems like info is missing in pydcs, even if the attribute is there - lr = "" - if runway.leftright == 1: - lr = "L" - elif runway.leftright == 2: - lr = "R" - - self.runwayLayout.addWidget(QLabel("Runway " + str(runway.heading) + lr), i, 0) - - # Seems like info is missing in pydcs, even if the attribute is there - if runway.ils: - self.runwayLayout.addWidget(QLabel("ILS "), i, 1) - self.runwayLayout.addWidget(QLCDNumber(6, runway.ils), i, 1) - else: - self.runwayLayout.addWidget(QLabel("NO ILS"), i, 1) - - - self.runways.setLayout(self.runwayLayout) - self.layout.addWidget(self.runways, 0, 0) - - self.layout.addWidget(QLabel("Parking Slots :"), 1, 0) - self.layout.addWidget(QLabel(str(len(self.airport.parking_slots))), 1, 1) - - - stretch = QVBoxLayout() - stretch.addStretch() - - self.layout.addLayout(stretch, 2, 0) - self.setLayout(self.layout) - - From f8b2dbe2833ee03a1559cd65b4608b2c5a513e85 Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 18:04:27 -0800 Subject: [PATCH 23/27] Move unit delivery ownership out of the UI. Breaks save compat, but we need to have this knowledge outside the UI so we can know whether or not we can ferry aircraft to the airfield without overflowing parking. --- game/event/event.py | 2 +- game/game.py | 8 +--- game/theater/controlpoint.py | 6 +++ qt_ui/windows/basemenu/QRecruitBehaviour.py | 42 +++++++++---------- .../airfield/QAircraftRecruitmentMenu.py | 10 +---- .../ground_forces/QArmorRecruitmentMenu.py | 9 +--- 6 files changed, 31 insertions(+), 46 deletions(-) diff --git a/game/event/event.py b/game/event/event.py index 44fc37e2..ea5f3b80 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -11,12 +11,12 @@ from dcs.unittype import UnitType from game import db, persistency from game.debriefing import Debriefing from game.infos.information import Information -from game.operation.operation import Operation from game.theater import ControlPoint from gen.ground_forces.combat_stance import CombatStance if TYPE_CHECKING: from ..game import Game + from game.operation.operation import Operation DIFFICULTY_LOG_BASE = 1.1 EVENT_DEPARTURE_MAX_DISTANCE = 340000 diff --git a/game/game.py b/game/game.py index 44c0ef3e..3b29c46c 100644 --- a/game/game.py +++ b/game/game.py @@ -160,9 +160,6 @@ class Game: def _budget_player(self): self.budget += self.budget_reward_amount - def awacs_expense_commit(self): - self.budget -= AWACS_BUDGET_COST - def units_delivery_event(self, to_cp: ControlPoint) -> UnitsDeliveryEvent: event = UnitsDeliveryEvent(attacker_name=self.player_name, defender_name=self.player_name, @@ -172,10 +169,6 @@ class Game: self.events.append(event) return event - def units_delivery_remove(self, event: Event): - if event in self.events: - self.events.remove(event) - def initiate_event(self, event: Event): #assert event in self.events logging.info("Generating {} (regular)".format(event)) @@ -242,6 +235,7 @@ class Game: self.aircraft_inventory.reset() for cp in self.theater.controlpoints: + cp.pending_unit_deliveries = self.units_delivery_event(cp) self.aircraft_inventory.set_from_control_point(cp) # Plan flights & combat for next turn diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index dc2097c1..ca21d463 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -33,6 +33,7 @@ from .theatergroundobject import ( if TYPE_CHECKING: from game import Game from gen.flights.flight import FlightType + from ..event import UnitsDeliveryEvent class ControlPointType(Enum): @@ -158,6 +159,7 @@ class ControlPoint(MissionTarget): self.cptype = cptype self.stances: Dict[int, CombatStance] = {} self.airport = None + self.pending_unit_deliveries: Optional[UnitsDeliveryEvent] = None @property def ground_objects(self) -> List[TheaterGroundObject]: @@ -387,3 +389,7 @@ class OffMapSpawn(ControlPoint): def mission_types(self, for_player: bool) -> Iterator[FlightType]: yield from [] + + @property + def available_aircraft_slots(self) -> int: + return 1000 diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 5fdd1289..5cb26a81 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -11,12 +11,14 @@ from PySide2.QtWidgets import ( from dcs.unittype import UnitType from game import db +from game.event import UnitsDeliveryEvent +from game.theater import ControlPoint +from qt_ui.models import GameModel class QRecruitBehaviour: - game = None - cp = None - deliveryEvent = None + game_model: GameModel + cp: ControlPoint existing_units_labels = None bought_amount_labels = None maximum_units = -1 @@ -24,12 +26,16 @@ class QRecruitBehaviour: BUDGET_FORMAT = "Available Budget: ${}M" def __init__(self) -> None: - self.deliveryEvent = None self.bought_amount_labels = {} self.existing_units_labels = {} self.recruitable_types = [] self.update_available_budget() + @property + def pending_deliveries(self) -> UnitsDeliveryEvent: + assert self.cp.pending_unit_deliveries + return self.cp.pending_unit_deliveries + @property def budget(self) -> int: return self.game_model.game.budget @@ -47,7 +53,7 @@ class QRecruitBehaviour: exist.setLayout(existLayout) existing_units = self.cp.base.total_units_of_type(unit_type) - scheduled_units = self.deliveryEvent.units.get(unit_type, 0) + scheduled_units = self.pending_deliveries.units.get(unit_type, 0) unitName = QLabel("" + db.unit_type_name_2(unit_type) + "") unitName.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) @@ -103,7 +109,7 @@ class QRecruitBehaviour: def _update_count_label(self, unit_type: UnitType): self.bought_amount_labels[unit_type].setText("{}".format( - unit_type in self.deliveryEvent.units and "{}".format(self.deliveryEvent.units[unit_type]) or "0" + unit_type in self.pending_deliveries.units and "{}".format(self.pending_deliveries.units[unit_type]) or "0" )) self.existing_units_labels[unit_type].setText("{}".format( @@ -129,7 +135,7 @@ class QRecruitBehaviour: price = db.PRICES[unit_type] if self.budget >= price: - self.deliveryEvent.deliver({unit_type: 1}) + self.pending_deliveries.deliver({unit_type: 1}) self.budget -= price else: # TODO : display modal warning @@ -138,12 +144,12 @@ class QRecruitBehaviour: self.update_available_budget() def sell(self, unit_type): - if self.deliveryEvent.units.get(unit_type, 0) > 0: + if self.pending_deliveries.units.get(unit_type, 0) > 0: price = db.PRICES[unit_type] self.budget += price - self.deliveryEvent.units[unit_type] = self.deliveryEvent.units[unit_type] - 1 - if self.deliveryEvent.units[unit_type] == 0: - del self.deliveryEvent.units[unit_type] + self.pending_deliveries.units[unit_type] = self.pending_deliveries.units[unit_type] - 1 + if self.pending_deliveries.units[unit_type] == 0: + del self.pending_deliveries.units[unit_type] elif self.cp.base.total_units_of_type(unit_type) > 0: price = db.PRICES[unit_type] self.budget += price @@ -154,20 +160,14 @@ class QRecruitBehaviour: @property def total_units(self): - total = 0 for unit_type in self.recruitables_types: total += self.cp.base.total_units(unit_type) - print(unit_type, total, self.cp.base.total_units(unit_type)) - print("--------------------------------") - if self.deliveryEvent: - for unit_bought in self.deliveryEvent.units: + if self.pending_deliveries: + for unit_bought in self.pending_deliveries.units: if db.unit_task(unit_bought) in self.recruitables_types: - total += self.deliveryEvent.units[unit_bought] - print(unit_bought, total, self.deliveryEvent.units[unit_bought]) - - print("=============================") + total += self.pending_deliveries.units[unit_bought] return total @@ -181,4 +181,4 @@ class QRecruitBehaviour: """ Set the maximum number of units that can be bought """ - self.recruitables_types = recruitables_types \ No newline at end of file + self.recruitables_types = recruitables_types diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 2dbbd3ca..3e8fef9f 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -15,7 +15,6 @@ from dcs.task import CAP, CAS from dcs.unittype import UnitType from game import db -from game.event.event import UnitsDeliveryEvent from game.theater import ControlPoint from qt_ui.models import GameModel from qt_ui.uiconstants import ICONS @@ -27,17 +26,10 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): QFrame.__init__(self) self.cp = cp self.game_model = game_model - self.deliveryEvent: Optional[UnitsDeliveryEvent] = None self.bought_amount_labels = {} self.existing_units_labels = {} - for event in self.game_model.game.events: - if event.__class__ == UnitsDeliveryEvent and event.from_cp == self.cp: - self.deliveryEvent = event - if not self.deliveryEvent: - self.deliveryEvent = self.game_model.game.units_delivery_event(self.cp) - # Determine maximum number of aircrafts that can be bought self.set_maximum_units(self.cp.available_aircraft_slots) self.set_recruitable_types([CAP, CAS]) @@ -94,7 +86,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): def sell(self, unit_type: UnitType): # Don't need to remove aircraft from the inventory if we're canceling # orders. - if self.deliveryEvent.units.get(unit_type, 0) <= 0: + if self.pending_deliveries.units.get(unit_type, 0) <= 0: global_inventory = self.game_model.game.aircraft_inventory inventory = global_inventory.for_control_point(self.cp) try: diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index d00c6b9d..c359eaaf 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -9,7 +9,6 @@ from PySide2.QtWidgets import ( from dcs.task import PinpointStrike from game import db -from game.event import UnitsDeliveryEvent from game.theater import ControlPoint from qt_ui.models import GameModel from qt_ui.windows.basemenu.QRecruitBehaviour import QRecruitBehaviour @@ -25,12 +24,6 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): self.bought_amount_labels = {} self.existing_units_labels = {} - for event in self.game_model.game.events: - if event.__class__ == UnitsDeliveryEvent and event.from_cp == self.cp: - self.deliveryEvent = event - if not self.deliveryEvent: - self.deliveryEvent = self.game_model.game.units_delivery_event(self.cp) - self.init_ui() def init_ui(self): @@ -63,4 +56,4 @@ class QArmorRecruitmentMenu(QFrame, QRecruitBehaviour): scroll.setWidgetResizable(True) scroll.setWidget(scroll_content) main_layout.addWidget(scroll) - self.setLayout(main_layout) \ No newline at end of file + self.setLayout(main_layout) From c4b8a4174215dd164d86cef02dee162c7e02ea9a Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 18:25:03 -0800 Subject: [PATCH 24/27] Move calculation of aircraft amounts into game. The planner needs to know how much space is still expected to be available next turn. --- game/event/event.py | 15 ++++++----- game/game.py | 2 +- game/theater/base.py | 27 ++++++++----------- game/theater/controlpoint.py | 9 +++++++ qt_ui/windows/basemenu/QBaseMenu2.py | 2 +- qt_ui/windows/basemenu/QRecruitBehaviour.py | 20 -------------- .../airfield/QAircraftRecruitmentMenu.py | 17 +++++++++--- 7 files changed, 44 insertions(+), 48 deletions(-) diff --git a/game/event/event.py b/game/event/event.py index ea5f3b80..c4506fab 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -352,11 +352,13 @@ class Event: logging.info(info.text) - class UnitsDeliveryEvent(Event): + informational = True - def __init__(self, attacker_name: str, defender_name: str, from_cp: ControlPoint, to_cp: ControlPoint, game): + def __init__(self, attacker_name: str, defender_name: str, + from_cp: ControlPoint, to_cp: ControlPoint, + game: Game) -> None: super(UnitsDeliveryEvent, self).__init__(game=game, location=to_cp.position, from_cp=from_cp, @@ -364,17 +366,16 @@ class UnitsDeliveryEvent(Event): attacker_name=attacker_name, defender_name=defender_name) - self.units: Dict[UnitType, int] = {} + self.units: Dict[Type[UnitType], int] = {} - def __str__(self): + def __str__(self) -> str: return "Pending delivery to {}".format(self.to_cp) - def deliver(self, units: Dict[UnitType, int]): + def deliver(self, units: Dict[Type[UnitType], int]) -> None: for k, v in units.items(): self.units[k] = self.units.get(k, 0) + v - def skip(self): - + def skip(self) -> None: for k, v in self.units.items(): info = Information("Ally Reinforcement", str(k.id) + " x " + str(v) + " at " + self.to_cp.name, self.game.turn) self.game.informations.append(info) diff --git a/game/game.py b/game/game.py index 3b29c46c..dd03be4f 100644 --- a/game/game.py +++ b/game/game.py @@ -316,7 +316,7 @@ class Game: if i > 50 or budget_for_aircraft <= 0: break target_cp = random.choice(potential_cp_armor) - if target_cp.base.total_planes >= MAX_AIRCRAFT: + if target_cp.base.total_aircraft >= MAX_AIRCRAFT: continue unit = random.choice(potential_units) price = db.PRICES[unit] * 2 diff --git a/game/theater/base.py b/game/theater/base.py index 47b3580e..40f604fc 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -4,9 +4,8 @@ import math import typing from typing import Dict, Type -from dcs.planes import PlaneType from dcs.task import CAP, CAS, Embarking, PinpointStrike, Task -from dcs.unittype import UnitType, VehicleType +from dcs.unittype import FlyingType, UnitType, VehicleType from dcs.vehicles import AirDefence, Armor from game import db @@ -21,20 +20,16 @@ BASE_MIN_STRENGTH = 0 class Base: - aircraft = {} # type: typing.Dict[PlaneType, int] - armor = {} # type: typing.Dict[VehicleType, int] - aa = {} # type: typing.Dict[AirDefence, int] - strength = 1 # type: float def __init__(self): - self.aircraft = {} - self.armor = {} - self.aa = {} + self.aircraft: Dict[FlyingType, int] = {} + self.armor: Dict[VehicleType, int] = {} + self.aa: Dict[AirDefence, int] = {} self.commision_points: Dict[Type, float] = {} self.strength = 1 @property - def total_planes(self) -> int: + def total_aircraft(self) -> int: return sum(self.aircraft.values()) @property @@ -83,7 +78,7 @@ class Base: logging.info("{} for {} ({}): {}".format(self, for_type, count, result)) return result - def _find_best_planes(self, for_type: Task, count: int) -> typing.Dict[PlaneType, int]: + def _find_best_planes(self, for_type: Task, count: int) -> typing.Dict[FlyingType, int]: return self._find_best_unit(self.aircraft, for_type, count) def _find_best_armor(self, for_type: Task, count: int) -> typing.Dict[Armor, int]: @@ -155,7 +150,7 @@ class Base: if task: count = sum([v for k, v in self.aircraft.items() if db.unit_task(k) == task]) else: - count = self.total_planes + count = self.total_aircraft count = int(math.ceil(count * PLANES_SCRAMBLE_FACTOR * self.strength)) return min(min(max(count, PLANES_SCRAMBLE_MIN_BASE), int(PLANES_SCRAMBLE_MAX_BASE * multiplier)), count) @@ -167,18 +162,18 @@ class Base: # previous logic removed because we always want the full air defense capabilities. return self.total_aa - def scramble_sweep(self, multiplier: float) -> typing.Dict[PlaneType, int]: + def scramble_sweep(self, multiplier: float) -> typing.Dict[FlyingType, int]: return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) def scramble_last_defense(self): # return as many CAP-capable aircraft as we can since this is the last defense of the base # (but not more than 20 - that's just nuts) - return self._find_best_planes(CAP, min(self.total_planes, 20)) + return self._find_best_planes(CAP, min(self.total_aircraft, 20)) - def scramble_cas(self, multiplier: float) -> typing.Dict[PlaneType, int]: + def scramble_cas(self, multiplier: float) -> typing.Dict[FlyingType, int]: return self._find_best_planes(CAS, self.scramble_count(multiplier, CAS)) - def scramble_interceptors(self, multiplier: float) -> typing.Dict[PlaneType, int]: + def scramble_interceptors(self, multiplier: float) -> typing.Dict[FlyingType, int]: return self._find_best_planes(CAP, self.scramble_count(multiplier, CAP)) def assemble_attack(self) -> typing.Dict[Armor, int]: diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index ca21d463..87cf20ed 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -376,6 +376,15 @@ class ControlPoint(MissionTarget): return False return True + @property + def expected_aircraft_next_turn(self) -> int: + total = self.base.total_aircraft + assert self.pending_unit_deliveries + for unit_bought in self.pending_unit_deliveries.units: + if issubclass(unit_bought, FlyingType): + total += self.pending_unit_deliveries.units[unit_bought] + return total + class OffMapSpawn(ControlPoint): def __init__(self, id: int, name: str, position: Point): diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index f9f7c159..3740448a 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -57,7 +57,7 @@ class QBaseMenu2(QDialog): title = QLabel("" + self.cp.name + "") title.setAlignment(Qt.AlignLeft | Qt.AlignTop) title.setProperty("style", "base-title") - unitsPower = QLabel("{} / {} / Runway : {}".format(self.cp.base.total_planes, self.cp.base.total_armor, + unitsPower = QLabel("{} / {} / Runway : {}".format(self.cp.base.total_aircraft, self.cp.base.total_armor, "Available" if self.cp.has_runway() else "Unavailable")) self.topLayout.addWidget(title) self.topLayout.addWidget(unitsPower) diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 5cb26a81..8e0e77d3 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -126,13 +126,6 @@ class QRecruitBehaviour: QRecruitBehaviour.BUDGET_FORMAT.format(self.budget)) def buy(self, unit_type): - - if self.maximum_units > 0: - if self.total_units + 1 > self.maximum_units: - logging.info("Not enough space left !") - # TODO : display modal warning - return - price = db.PRICES[unit_type] if self.budget >= price: self.pending_deliveries.deliver({unit_type: 1}) @@ -158,19 +151,6 @@ class QRecruitBehaviour: self._update_count_label(unit_type) self.update_available_budget() - @property - def total_units(self): - total = 0 - for unit_type in self.recruitables_types: - total += self.cp.base.total_units(unit_type) - - if self.pending_deliveries: - for unit_bought in self.pending_deliveries.units: - if db.unit_task(unit_bought) in self.recruitables_types: - total += self.pending_deliveries.units[unit_bought] - - return total - def set_maximum_units(self, maximum_units): """ Set the maximum number of units that can be bought diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 3e8fef9f..3ece7855 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -1,3 +1,4 @@ +import logging from typing import Optional, Set from PySide2.QtCore import Qt @@ -37,7 +38,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): self.bought_amount_labels = {} self.existing_units_labels = {} - self.hangar_status = QHangarStatus(self.total_units, self.cp.available_aircraft_slots) + self.hangar_status = QHangarStatus(self.total_aircraft, self.cp.available_aircraft_slots) self.init_ui() @@ -80,8 +81,18 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): self.setLayout(main_layout) def buy(self, unit_type): + if self.maximum_units > 0: + if self.total_aircraft + 1 > self.maximum_units: + logging.debug(f"No space for additional aircraft at {self.cp}.") + return + super().buy(unit_type) - self.hangar_status.update_label(self.total_units, self.cp.available_aircraft_slots) + self.hangar_status.update_label(self.total_aircraft, + self.cp.available_aircraft_slots) + + @property + def total_aircraft(self) -> int: + return self.cp.expected_aircraft_next_turn def sell(self, unit_type: UnitType): # Don't need to remove aircraft from the inventory if we're canceling @@ -99,7 +110,7 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): "assigned to a mission?", QMessageBox.Ok) return super().sell(unit_type) - self.hangar_status.update_label(self.total_units, self.cp.available_aircraft_slots) + self.hangar_status.update_label(self.total_aircraft, self.cp.available_aircraft_slots) class QHangarStatus(QHBoxLayout): From 75edbb62f1e671328e58a18e732b97286a828beb Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 18:31:19 -0800 Subject: [PATCH 25/27] Clean up available parking code a bit. Moved more into the `ControlPoint`. --- game/theater/controlpoint.py | 8 +++-- .../airfield/QAircraftRecruitmentMenu.py | 29 +++++++++---------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 87cf20ed..476f831a 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -235,7 +235,7 @@ class ControlPoint(MissionTarget): return result @property - def available_aircraft_slots(self): + def total_aircraft_parking(self): """ :return: The maximum number of aircraft that can be stored in this control point """ @@ -385,6 +385,10 @@ class ControlPoint(MissionTarget): total += self.pending_unit_deliveries.units[unit_bought] return total + @property + def unclaimed_parking(self) -> int: + return self.total_aircraft_parking - self.expected_aircraft_next_turn + class OffMapSpawn(ControlPoint): def __init__(self, id: int, name: str, position: Point): @@ -400,5 +404,5 @@ class OffMapSpawn(ControlPoint): yield from [] @property - def available_aircraft_slots(self) -> int: + def total_aircraft_parking(self) -> int: return 1000 diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 3ece7855..7c159e94 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -32,13 +32,13 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): self.existing_units_labels = {} # Determine maximum number of aircrafts that can be bought - self.set_maximum_units(self.cp.available_aircraft_slots) + self.set_maximum_units(self.cp.total_aircraft_parking) self.set_recruitable_types([CAP, CAS]) self.bought_amount_labels = {} self.existing_units_labels = {} - self.hangar_status = QHangarStatus(self.total_aircraft, self.cp.available_aircraft_slots) + self.hangar_status = QHangarStatus(self.cp) self.init_ui() @@ -82,17 +82,12 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): def buy(self, unit_type): if self.maximum_units > 0: - if self.total_aircraft + 1 > self.maximum_units: + if self.cp.unclaimed_parking <= 0: logging.debug(f"No space for additional aircraft at {self.cp}.") return super().buy(unit_type) - self.hangar_status.update_label(self.total_aircraft, - self.cp.available_aircraft_slots) - - @property - def total_aircraft(self) -> int: - return self.cp.expected_aircraft_next_turn + self.hangar_status.update_label() def sell(self, unit_type: UnitType): # Don't need to remove aircraft from the inventory if we're canceling @@ -110,22 +105,26 @@ class QAircraftRecruitmentMenu(QFrame, QRecruitBehaviour): "assigned to a mission?", QMessageBox.Ok) return super().sell(unit_type) - self.hangar_status.update_label(self.total_aircraft, self.cp.available_aircraft_slots) + self.hangar_status.update_label() class QHangarStatus(QHBoxLayout): - def __init__(self, current_amount: int, max_amount: int): - super(QHangarStatus, self).__init__() + def __init__(self, control_point: ControlPoint) -> None: + super().__init__() + self.control_point = control_point + self.icon = QLabel() self.icon.setPixmap(ICONS["Hangar"]) self.text = QLabel("") - self.update_label(current_amount, max_amount) + self.update_label() self.addWidget(self.icon, Qt.AlignLeft) self.addWidget(self.text, Qt.AlignLeft) self.addStretch(50) self.setAlignment(Qt.AlignLeft) - def update_label(self, current_amount: int, max_amount: int): - self.text.setText("{}/{}".format(current_amount, max_amount)) + def update_label(self) -> None: + current_amount = self.control_point.expected_aircraft_next_turn + max_amount = self.control_point.total_aircraft_parking + self.text.setText(f"{current_amount}/{max_amount}") From 8c5b808eba3e5b564e78ec04880b4fbab4a227a8 Mon Sep 17 00:00:00 2001 From: walterroach <37820425+walterroach@users.noreply.github.com> Date: Fri, 20 Nov 2020 20:38:32 -0600 Subject: [PATCH 26/27] inherent resolve frontlines --- resources/campaigns/inherent_resolve.miz | Bin 45228 -> 45041 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/campaigns/inherent_resolve.miz b/resources/campaigns/inherent_resolve.miz index 6b1e4be3d6e13481feb6c6b6596d63b240dcec9d..cb4e2f875b1a7869e93ccb3e0cec6e6e3c134f56 100644 GIT binary patch literal 45041 zcmaI-1yqy&`v;7Jlt@eg$uU4mlo}$8ngI$@1_~%4Fa(qw4I>nkW+Rl40V)D2NHwoV0#Cfi`yBDT#ka;=+WLOl^8NhxG2`pK zNl6y<93oKNSksubJR6eVW_AM?R0SR^dn@ecH)r&&4c4XCR;#9^T{@WBlbH(i*rz#M+||69rusVV3(9>b zp!4Ri-}h=_ll!(&{^2BnIF(apu@f+Q^UzH>+3c_|vtWzsFu>P!d3na!>A-uMDzEO( zq=}4;rhsAK?D*0X$|~Lb(DCmsa-1);eec4=MlK zpCFFX1n%x=as_UBuZ+H}L#<~-68wEv7Czi0NgaGmT@7@DCYzb2tv?BosVn}?yAPjD z88-G1HG8P~(6?cGV!3I?etKWTU4WIPQ zK2%LMH*cwuFiCBDT7Ee1ZMwTI zBMbd&B~Hu0Q`6Et0@c(iw)_-oQoiJ2#^xvHHuJ{SluVb|JK~9pgpT0NYym^BjYGJy zMATe-r1fWiW2&~ctQ>V0g9w2ikY6b0+NDg;~BN~N#E7Y27HjV6S*|#y0Vi zqB%xEC-8M1Tu5gBaqSMR`Hn$RH`mOHz{7O22M45_4io0Cb>Kh4sh4V%uT-wSGO|6} zJax4@+j~kYuQhfI5KMh9smN}YKCx1{x_)0;k>g=%X5DIsku6W+&CvW-xuNaHUjCWV zhs&M!cP2=aImK&U=(uQL7G@-Iu1r<4#?k`i)$ryC z8h0bx?eIEh1%HoysmG6wx#i@YLZ6pmd*BfpX5ta6 zWeWTO?|bfXg~z|*Vw`wo6kc6tGN^(nHRc}pCkTird-g`^4IpM+?Fc!LS=l<@g@=Gp)IRX3Y zYlD5mwcEQiwfPbOf4Vs?4M+`~n<*pIJ%o74Z>#zzJ-Rf!*mLOf)T%C^_t$N#?tv;y zMXjnh>mD}UY_CZr-6yh9MrwIKRbsX|KC*XeXth(Zu2Vs0U-T-n^Tc;qKmP2+z2!jx z(}OYF!>Re3n1G7id8SoS0Zc0-e{XHKZgdtr@cN8zU|8T0|xk4IbDCK0sL8R=xI5@@Qr7RrAhmyv&Ims%6)==UYq7ONKtL%mxfmH39h#ZL5+Y{$BCb`j_?teD7HYprgi>~sr zvD++-5Xu|m8g@@=zF9Y%`{L*3tMrSNUtsq(Uhc|d4%@^R7f)Abch|h#Fvw3-8oH?R z{k2Z18TTSHcW1g`pz84T!ylhs zyKfH|>6NOEY&w10r2;?HvmY2J{swAaBV>3jW{L092KczTBZuye$UnylJZupYpR&y^ zj+BXK4pyq#&GO1l9~#>J%1}1#-@RR)-c9=5b_i3hB~sr!#I}kFWU0wDyjuv|-t7o3 zH%r`Kypaz|dUys@$J~FT%Dg>?cHiDhq5FAR&4W>r`^tiXWgon5>+yknOE{H_s z&iF|8EaEB;`}b~j(}a`$iZFVca=*y>UjDreAZJV}CmJrxmDQqb}}Pvl{gXacW+UibZpvR!Ufcp-8@ zkWuSc+*4~EE#J#*F<>5%8v3~J<7Vj<-Rg0SF%HbcwAe1q3aT4r!Ro(G3r}89ij-eINq$j?4_Fm4XTYTY|2)|%2cbuB= zXN(JDoE%KVpPC{7_Rd#&=>gC<(9Ca$+U<5jc7BX-ro(pm|TLL+L5VR}M5z%5;^ zW1>Epn0OGY;YcpQa7_^t^&(p!$I@3^w`@h!spC!ocLdbU`%FC#M!r3tAGE=L&cOgp zggGax8)%&{e|L=Hq=A;$k(2RHa`??w@@@{y3Q6>o*IX1qU4I}j$|e#zT%pD(m`wH8 zOJVUv-L+W$$3`N_uB=wB90a(+i}y4llpr`T5}5&|!ok*Em+vG4(f6W`JEo-&V%?@S zD{T7o{nAK`E}fPQa6fV|ZiuA^UwGEYm}4Z;%fP7~-gMDCHVTLqIruC>JW{|LG*cY$ zoZv^jq4n@;Dk7Cu*$eAovZ|o>0^qnxsvAWyA|i}8(2M@99N)r>34K32tFD~GZ`&`{ zh&r9PVM{TJR>b5gn{za+2!Z~_go((-%4)bX7dUzJTN!$e$Ikz$PLyu&g(Ae57svsh zb7eox#&oBB6{ESiKc2WVRa=Q|#dcscLucu~?=m#WXjVE!?GG=WbE6tDmCg`o%O@>V zKg62*VL^(Z{Rzbm()8%H{64#rdFkmM63&|?`g}qy>oDS2Joi+isAnT@(*P^B_Yq(8 zP3z?6wfP^uqiT~61im!TuhusEiFs!KxubQb!{TKT=6Y4z8GAFm;N?qod|xtAWh-I% z+qXkbcJF27`4~Ri6-(T^Cl;Zum2@sI?OfjXJV&|Ns!FTA6*bMFtrvk;e>5&PZ!QYd zz*E2Au*)AbPs2+~2X~e=UHkL4h@$61)Qv7~J{aV&-+nkTsbFJn zbl|owW~XK(a{DmjKy$+`M$MaHfd^TEp!JISJp^4@ycwo}3JseL3=H%Anio*yG|zJ8 z?lSgStYm`;+HCpqzLW2?_M5hj-eMvVQ=D;W_p4(^x!)Q(6>c-Txcz1{9elQAlIGjf zU4ieSLJNUCCvGTKa_ZieE1nLy-p><#EvjZv zGuHLbgS4~R7PzGDpnSB+#$;MLG{oh1x1X2qP_hNl%aQ9GujB~>xz&CfC2N97<>W+q zV8iaf7m#$yH==0ay>3;5mS54U=%7`}UEd!VIjGY(T(Mk{w8tI#8Hrhy(6n{GkHW+YgNF^NLADj~}RkexJi<7`x2w1>Bg^wQIduuT85f zW?h`cyF63$yUTVTJi7mLGSb;sAIHb1?}v z%f)%>PSc`y|LIG%ubp#Z5UgxFbwRd%>w%rLk5h>49_xXPgQ+L$z3HCrwOeo4_gtHn z*~YDQDWYF)r-()C48G~lj45GgqwmiBT7Li8!}Lqs+cdxnvAGf&^{8-H=im?h{O2hQ z_zX{(0v$nU?#jW3`-9HKMRoAN<0}lni?A?(X&->CTnv2wdGY$V7MSO?GfMlh07O5p zYB}SFo`8LKzS=Ja;6+%-1l7@i#8Il)sL=TG9haiTLzki*bn#C0k1ja2<+O8$Lb#Q# z2y}u&b#Wd?tU+XBdUrzOeYQ3cTwOAd0-I-{@y((_5dGT44BU`v%LumI`sxy~@7D6q z2T^AlT*T!Q9U@S>lDOmjI{RnUx6V8LXuVPuRA0ee4Ei_&{qfWPY3$3~P3*?za_V?e z_17~y3)<@!7o7!{HN>tVB@LJ44<0H7k!r`>vgzWD{fxA_Rg>nvD2^GlC|oB{gGkPj z&mAN*5pLh1Td!}|^7X6B!aeUlzz+5&wnoWGjalxc3Y1;<|FL@#s|cSEgc^QvzwTg> zrs!w02!=ErGBKR=9{fp%nOf=wZ*^CX(YB3t4-;+~=$1){bPo|?mcVc)gtsx@sM20l z+gGMU9uq$0pRi30$0A z)ax4R&c-}ndj`A;QPYU$n9HU z)HP>nWZN$tpE6}L)O zRcMs6l^(7e8$~!^e_##Xl+-O|%(u$E9+A^YynC+iyzzOlP>v}+Ys_IOX}37E`M5A_ z@_gEzm>A}Mjo3R)d`WI-X`bQz#EFv3^9M4OFPgc9VGHNso~E+U>ff0~5vPK_*;evC zSy83U7;b2qZ##Xd_V`%D@XynWOD>1|3{L<-; z|1{Wr&Ys-5JKUBW0)I@6_8tJG-L}yUl=jz8xZ-{9vO4dx-erN6BiW8zdqdR@eg#T> zSd#e{QjyiUN}~MAPIU(Vf-?;j{sh%~jF{U)KHrjudmHIL-pda2;qQAE?xt6#!#{OB zdYwzmy(!?`BucJ7)-22^c;DjSMu%oZs|Kq^?2Any|GScX7w;FCRBs7>gOFdX%#>U{%N;=?)gmy^N^-|Pz>-d4QMw;1O#W1TzbRIOvDk;PZq z#HKhP(}^00W8k1^QVF%{qvnWz5%G3R6LT1_HkuLF=$sZ-nm?p^VqtM~J7YE3kl=7y zwJU33r=+HfFf_fH_JIS5iF`~FS>TiMf7zAN(>x+3?O?e1lx4=kDv4Wc!Fdd8G4_NV zB@X3v)i-U~AO7f};$UE%$n184t#b6dotpU-CU#t-?6RPk7$6S`4gXjd-ubZ_?wGHL zd>+oHp(BtP6dpSKtgwJ?@zNPxWFm`stnHo*_kxBa;~M*Q zS%w|hkBfzbTUsS~A7{S+d;)`O%rzGOYjtD8*G86inV35bjXo~ygrIQtzdEA^b^GUR z&Jk5`T{ejl_T?9>2x4LpN(@iDXWd-{p<2vr!Hw!M#+R*Pyul)zKjNFdx0t1tc1Icu zj?p*VVR)<-Mgw9UyGwP>YLMIC11@bga(@2s2EjrW6=%@gC%2sCsuM8)C7 z@sl)-RM5++v|;?0nNMP98uKoNbtYFU+vhVNslxbZb@<;O3)>$NY^6IH{~}DtDy*U= zESM%N=gOW2Oh?Y}>KMmoSCo`ZQG#yNoRuyRNG3@Q=2Pm8Czns2**FQ+P-Q)}dvK;J zdts)eCasZ2#Oaij$IBC9`oSp`7o4e^Xn|~BMxr=0-&D{=H2K(mIn9^q`Kp4iEZzhE z;w3S&=V#+Fa#obe6dZH%&W)-bTUfRC8PUI(bl1p~BA5IFkSV zokio1h!O}cHiRJiO17n5R+gE$oKQfheIO+ZeheSGlf@wsX^2x$vCVbUu;yF1$WJ5B z%%YC_xpa~jZxa4?^pqX!iF~eJf2RaWEmRFQAZR=zcv^KXWK%X#RdPvIl^ImNS%BLO zGx3IPVWSIfCcN|YNOD!|;^Ul3GSdOsdUHcULPtl1RIwwL-rl(hiB^pWA(!U;OoO11x0Zkqwd@hMPfAjEXK{{iW#tTcVQFdXeVmC&l9|bQ`0Kmk zf^+spwsY&;oE8;u!x`?+-7b@(7qQyBtv_V}6oB4X>i={p4e2dpx4*i?;cz(RpHGbB%yRL_0ZKT6L*9y0nxm*xl^Za;?Ar87_-D8`cp@l28EaC*yhc;Nccpqm)xp0Gr;Gs1kxbG!qsYTsx zuwdMJ;T^(<`cZjUsLDJiOm{>U#>@(;w=5fRVPYjTh6L%&x&D@BWk}R@mlP5~ODRcG zB!Y43F0iM#k);hK@Lh0MCK)IRIBOnA9teQmteS2@2m|!hVIaDyWyJ-HL=?%>sXMUb7JfNz}#>IW)4W# z?ATMY&HYvhr~vtH@gn8k(Jy#$2PNW~ ztipLN^tWoK__TyOuS`O(6v2$$0HN_P(E!`xy{&Q`X| z+{hFUE5;Cq@6xc+aGDnujdW);&q(PVNrZ$m$QBv{qC-{TshywMb5=b4TcwkF{;IRK z;u&hJwH~7)-y4Zy*J;%6(CZ3EEQNewAwKbX9Wdsv-DN#{l}+G|4!by@hU|opt}f8R zzWB4pKS?!C1nl!{XgnpBhe?2Ii?WA~>VdVz(0~9ld$}UAu|JVFsRxDp9bK86s+asq z%;tMbySs3JJG@*TdNdkS(kYuLOpaPc_^&UlD4OQ~`gmbM}=kHdGNQnG9>7 zw9TjCslnm1dfgN9FUstNO zuzD6wNk&6s3oZo)=sXgvZ+z~bZIa8*a7I|@k*82(U*lj>dK8m`W8Am(Q48_Tz&7j;EhF8rMe#uvd- zJZiphDSIq1WvO@ICk=8cO(8c!0xc?lr(6K zFVs04Zi@T!RMKcV7gY=#>gmsFU@2yWgoToeKxk|}5+^;e+B=Ipmr1BG7O*)yRbVjJG#WLV-F)suQgQ-SSh zLvQYW@%7vm03@iIaJ8Wu1A-LHsqU$StE_P9f+^dUlk?c0W4fkw-R@e?qK#*JJ!#5) z^QT+l^h$b_v}-fNm-Wf811b#)9l8PRMfHuZz1ZwmDTT(pDB^Sy*x^oyZQ$zSD}43D#l)v5#&yF3t11!KibeVa1dBUR909qq45Kn0^WArNeO!10i>P5*fXB(l&2>fkM@!u=^I@Hc0A!N*W zpFBm129}!R8gmzS^^UrrH}|5j)5-IWQQEvi_5EQ6H*h?_pnN+@Ek7qEZ~EC%yWJNW zUi1Tc&L}MWSihZ!5@W&xCk6f`Wh9Zk{^Y5Ke}& ze2IZ^U7nK!x-?DW{FQ+~fwSIQt;YKrekTp)?`&(z-}l{i0i#eO;U zT_Iv3!C3^&C`ljLU{&CKoRi&{nG+M#^xl^`UfQEOd;VCYu(7EC090*BR$OtK!UZ97fh z%m;N_$-VbAJjSkuQfL=W`qQ9sb6x|p8OVg$iS8TLUkMvy6?imp-^_zE%e!o|r#VdA zV}~9%YkYmF-&b!S^Hx5S9hw;|!LK`AlKv}P!Ad*3;WO)4jU^YqPuK7ti{2j(*S=z5 z<=9_0l72P3WX*DyJ(9N2wrzTe#*wd-!81wnoAV#ewe5T=mL=;m;Ft7|(Kb-#^R_yl zkmb&H^r_Sh1LZhI17#@%xn(FHpMmF)&HQz-)lkQ1t~fihFcxtSxis^J_pD+bvLAzQ z@axW4+_CL^k0y|>l)*oe?x`&QdD6C1<+fjM2~C(X+4F{Fp!VF~9Dt#RG;8<)p5L64opa zL)}?g@)g_J4&^J*&l#Up?Q>eczDHZ!$-Yuha^;-vv^69^TbwJ){Y%qP1TNISL=(`; z>jug%+327N;k}P0bV9@Y6esx#4a3+=u^r(_9SvC4Nq5=oU?u*RX$yVZCaiuV`AQi) zn|-%!`h!l}Oi{7yM|Rnd9h6$J@z@(FRvt>Q@i<?jAu} zSn*Rm(K|!s*q+#V{W)(E*8yYFWP?0N_lu%fE71A({wxn;;C=&>=ie1iE}wohl-Jg4 zf4e22JY*~sWRwyZkZD`8cKd;|Rhj&k@Jef7$2nRV_{{}>e{EDSVMBZt5!hKAMqO|v zEO=$y=weuuA|!~hQickyktPX7-V5{0!~<3}a{bpQZ@UD7+5}SDO61XoR}hXnc{c~? zcu~1ripLY-x}tp5qmGiId@4QM3L(+`{YksdER1}^JfDdMtX5Y~gtJQwH(ph^zj58c zg`=_bJ(5293gRcXJR}HD%NvZ(cng()#B|#%&a{}tAcLX+{((P}EsC@x>nX~Os^^xa zkLt(4o;9tv5sDK>bl-6+J(twx(rjzz*&r`c!$Y4cn^%9kkr8+dBDkSHP2h8$% zQ&tKZ)-|OcMWfU#n7L{2PnS>1asn1WSfZ{KTI9A2mikn`v0^w)SyR};A9=HdvV{v% zXo~q81BVF@!?qUoGRt+_pKF0d<6XO@(gL;QglNAHa5gcXrH^=K^~U=+hsqo7vk;bu zRI5_s)Y=>auZE^m(i8)=8HS|O+=sHA zrJpjXwT)sVJAwG-eVhmNjav>99aS@VPp^;SadVQC^}rd^8QE9X*w94eIU-CQ9|N@> zFvnkggH($~SlC|`Texn4yaCW|=xhB}&W;AX0;Y-|ma(5czS11vlEZI0ln zgqysPbpIL1cx2%F%Jc^*XNDfaq9^v-riANqSL{(OiI_osPO=!hQ32=LV9ym$3?19;6HCR_k|%U$5OZ5 zCqm4N^qEU+BCe3#Mvj=t-BWHV=Sjgd)IyauiU+7FO&Y`k2;s4j*E%q3IR{k9Nitz2 z>*?vuy~`FwL#Y6o`lP~(9!%UOH(Rx4t`D4i)y5sK!S#(c(VtmQK+Y2asG&yGdmra! zB701afA_3V4rOG!a4d@7;%Eo4ki_9&FD<};7*I&Z@ZR4msG|(5&x_F`d#`7|K(g`a zZu007OZ^_j>pX>0ZmM42Lk-lPoy8o7Y!{_3JVny}>kGIgS+pp(_}{)@{OuTiUAA>d z**Phw_{Q$ntZkFojEmz2CDID+h2j<5(jk$fV?w5LM_pm#1eblhm*bduo0W3|Kq3C3 z|M-4P^-F}ojV7LRApi$UeHXh_ltq8ebMVc1yeKRBTD575l8CLUt$>vYr%C}gc|{r% zG6vTJ`;WXN>j_SKN;)cxN5<&e?`VFcLbL(iavcExL_-=qtV-2o!K)s zHA|!pa^01KRZL{jVeQvyp7}kW&57gQ@$;u^+=#HLYg$&tRUIb@{M8QVCOt0C8)q<3cV>dS<39a`0ERKmD#vMYku^=0LvRC$HPf0VgZ4Va~rcQYxC z2nErT$q|jQ(R87(yJw)mjR0$_N)@h_CS{;U5sinSjQPc!n%H!F`mRdKlGFtuW&G zD#9OV%X(5^2BJblCceD*v{2j%3cmAAUyFgX9e<^(;xg8%rB)~^gN_Vr3Swyig&EJF z5*E2Xh5*8tip0#V7?z-{Ey|reYB{GZyYWvSbrfH{aa^fG+$P8KmnavX^yj(-L-^mW z40&fQpukQY+@xNRL|&ccC2i;;ODn78obxr2)^94V<)(mVFA^nj84e@ZXOjVg-Iq5AZ-xEmbr+zj1i(?IL3x2%Px?l>JMHU3W@d5i+3 ze6in2f?O=-Ad<#YWU$@GjJr>fML@N@vcOUz?F2%M(sC<$-)7g8!JCN3rE=m|QU1Cu zt#ODal~!0)N^2lbWL7}8Dla#~kI2_ADX_7aqasntj~2lL!BRt2OAT3I zVM!y!H*dU@4PbjWe|?W5=tuj-!5C%H|Jo~sq~W`Yce zcuN;PuQjQjBnt$v-8eZT#4A#&o<%#Ji2LEEew;1GuVpJ76I=9lz&~R3mn%)4ZUNMR znGc6o?}0Vo3zqF1upA0qS&KWrYliT>U;iof@#X_mljRDKC_bI%(R@8g?`Z9NGA%Ot z@IoO;db+x4qB=T%H!4e#kOS5d(c>+z(0YQ;zJqLGkFf=be)L!9M+;I5u0Kgff%EV3 z)q@1IefgdwB7!4^&mNx1O?lS;!^=b-e6}Bawb`t~mG`Z9@%w_6i<{u39qza*Cs7sYSQ%%hHAtdxJk=1%%AIM!KcRx7hTNQKtCzE zqRTm%WH#<8zYxJn8E98XLUy`upmwSd-uTIcCyx&D+6L+PYh3XPNg4tpH!H{l!MbiGYlg4tQ zqRGZ|P>YXQXFO@AE?|1&8MjR{zA$kl$llk)1JH*6)IyFCSOMLHU_^Frv`%u}teY1# zff43JIVQo-I1((u1^bjN>4ZU~7rL5aNF(p8ap8zHj#6h>mTrr6krwN(wPb|Ve>Tn? z`Mt4^%xxWBuSBV#!=E0d{VAddQ25FPOS!cZhV+!Bl6M4VB5v)FAi26ZC4p?|@=z}; z#JauDD!&&`2|2|?YKn=Zp8|v#awM5xp7qFJmucMHuZ7GQl4(7%j1tFk0+I~`HX^}9 z6ElR$+MDL$I0WE$$4M)dOMUd#)pp#fHX> zz!v6A>Y>yGKxP0!fQyb)93+$eXhcQdhlRZHpY%Qmpk@@K3zxq3Bn%<;pV#Cd0om4% zutk?{SHr#X2~d-Nseyqoh;yT4Jdndk8mEKh;S0sJt%KYo;p=s`_dUlwv2ho}&7z>8 z34m+=00g5U)GvFxV0THzsYi)92Bw@{=)e~MjnbpWe-lHxY^;<=DD>8JaEKZzW}2}@ z6K`*bWnu4><%yz&|9VXY6SIGd_~R|FEY|;!Fhgse{)I`I_2*F>e-#Rt6Yl&f3`Ubh zsQBHUqfP$6R7ehU;B)uugt^3T;Il{0zE4o*Wa3vR{DiuzhM^giL(qf_#GPs5#QNEI zuPX!|*Hp7Ub)W!s<@RQmkAtiNIQHvj@nj!%;Bj!diqwW^KjwFj2_oZ(x?7b z;1#=;qY5k@D|>QpBd={>)=ZL_s~fK^kZ~;HMTlmh%v%^RHGtVm6(Kz2GGIkCc9lan z#d+2kI}|{ZM21u}r8Y<7C{r2`Ivzm>Od3M4ReLU%eUX>{r1YB}nx@jiUxW|OtrS%w z)ES#jkqisQ`-^ z1QTf%$d$wH?Y{!F+h<3e@dg3u%}}y z{a9N*Ve~PUNnXqzXvo|Okb1oTkSmC9STR2N4kzhdbb^^-KP>W*oFw#7Vuk2`bt3q$ zPBfdo7b-mK7GDfP3m=*I${S2SwQO1^D!}f?EPx}-`_CalFth%^L*|gHB?<2~9|#ae z)aB;(_c~F(eiG6~5L?{v5DddIG6*wDTHb<5_~q)2tP@c+n3@+DO6kR6R_Xn8uL)fE zs}M|g6QbCYt8AUFi&jvM5XxEn%=$8QntlmeRalpa;irf#Eepb1m!N*C5CPk!fKEyv znc$pTM?G~?lgm-`MJ?|g2P7<3Xgj>m{#ev|l2M05A?=HNo ziYUHR)V%~VUgB1z$25?xcI=`0^#FxVX*Lp; zq#1e{HT|wgWO9vO%M);R%izycC9m|ZwaFPlpzO#93;vTtMv$tqO_L6$l<^DPCw~fQ zvGmtv*iU-DxkxxG5EN#C^3B4vVcHY$+6(3vCX(V9&73^v?oT5GjH}+Npc2284PcuY z225Fi1d;=Z20k+o?G<=wX9?L4(?0>#%(U|e3 z0*epaAEYAarZvJM#@=PNkHW^fo=|#XXhR_xj#@5~C&2c_ny$-u(=oB{wf~3`yhfz^g|V@zq05BU}Dz zgBqgw)ZDOOK6LBUz~S|E8L$0YoEy;5mZx9vhuw6FuEcfE1}D?CL=jP+3Rt4fQ$;4(ls;jurr?j9UnqGLYq_+Lu}q#O zK%Wl;|tcbVt-e0(iQiLWFhr;5(~mO z0ci*$`r0#u;}QC7|63IR^c9AYXaNA!F9Rccq=Fi4R%CTY(ZV+C>=Q8ITZ1T{&|5f5Bn87<*NjOh$6li=;=o-0P)1kQ=W(n^X{u?;6qp6R(}05s6c1`VV%2* z=TuHNyOyFWA<1OumL3G4+yG+G`y^l$K<+X|$B(fP05T%Il1ag{;NOnvKt(WZB)2mP zU}(}5oSq__lcS60DtAF@Xm&ce*kG>~sq(RRl(Ydt$0-5F&=T(dmtoESG7MjZXFNF5PV^6QPL59fS?cM>J|-9c|B562*k4xxJRVJtkt81) z*v7TL2M&gMWWAHAlp&fpwVkr+$6;wmf{EA&x4uZiI)_tw6geQr(L6_Z>Bx>c#wioc%?JJSWqC{e_OqmR?1)=km?EdU*^1 z+#wBq9r*U#$HuV|YGA34BtPyWQxn|wcs*lmBcJwHX^ja()Amq7v5L9Rkz}Tnn*3yt zTN3iTi4~y0G{0J9TNO%sbx+Q(OLEdj0^gnEdw(qQg+l$!F3pbGzJ+a#4S*TRJ`Byy=VK6?gsq&`GH&b zG>S|}6hjQugb9cuDF%8!I*O8v{KUfYm?Nqt4mtER%Ir2CLiQCPOG?V`#K$nU7h}ws z5JV_9GWn{mp5+{ctRs5~CG)ph7P_BIKHjUWR(9qB2y~=>!S5^Q3V=P*crH?s^)Dlq zVJ2H9iJ#E-AB_2EE}Iq@R@y5zYf=OYYC4L;S1SNdW3dHE_hy?Yv?CMnQDm^il>ak6yD6H&;q-RfTm3h-mkq0O6jK%SDuVn14#HOaajz5|*crra^0USNZbT9+5 zu)fJL{Oy~&$=s6<4} zwmjF{<5-Wcv_{xc4jxX~mT&RM*4>xBgKaoAw4lh!7FMkm1`M+`j;U5L;W53d13JvX?pjA zyAm^u73mKayK)N>D~*9Iq+$8|>Syh3-1^Z3$hlg;g759g+H2*aDN?^Jxe3)wTH z`57=J^rNefx97KI)wA$6n{43s-%agneiw9Ke?fWqdKOBINUr1iW%5TGtb#v2ZgJ2< z`|X16R*QeS7o|F1yOrg6^nksl{X6hu%5}n;Tl)A)fzI?cF_aiO-A|mumGJG@U-Iyk zBSd;uJ!#r<_d)i};hyB}dUiKD1|}(P^-G(`!`>(`te2Ln2KdV7eBm(Ue8`kN@P@I? zMl-uOBeLx^qph*q>l6K>v*o?xa}GNT(;lZ3%SP>Wg1>KvX zXL{E?actN6v!cY5U5)7AgJr+kfk)1MYb}j=gI36rFEtZubwgu3`x9Jk?(BB249pL` z{ruLZ+&8vI%9y>?w%vSP5B`X+U;_LO<|YC+c5+`|E1);H5}84|dGp&P1DSMWKFf>N zRt)fD;2vwxI?YJm#d5okMx<0dxIo+DbsqPK?c7~-e1q+yL_$M~%zVlT>1;(cp_utP zv!-1VaZXv`tyc(q59V`tP(?p6RJS*};_ICW5B`}t_Gi+nr}5qV==j#w-X9$CO(I0% z+|KOd;MzKo*AE~3A-3Hd641j8-%eks=BUk=>Jw(p7OzX!w^?M>m%d~s5SAJ0WKlK! zVs!|S7m#aK=K}fc|MS?y=ydkY!5feOvnKyPHH7_y0q2mk-nBaEZ1GS<)ayk3Vxu*P z?5mKA^PL3r#rdi`rUot+db_8(N(!@To+&-deY`2TRgh)2>SNq7G;qa#Udp%w|9T^J zl*zuYT5Zfi4^?f8@^?tN-(i#c7@=3~eKv6PqT`}cfg;--j+w)!i}UgsT<68Ybrg_x zmG9>Z-`6yV&WDLQNbcjoD}`Luh?oTZA&(~-d2 zivpab_Ha;SUy90y{2IE*KI7lD7yK-5qsleGl24pB0U$YEi0lix<|z4u3p63~<->j> zg8+=@?Rb3g?eb50jo~#!__f_PpIvnf&<~AD*$qn-D?X(be2Ts|-=6!VBgC+DJ=kKM z%GkIIZQO+~Dl3D1GU*ayFBXwe3U7Xiuly!eUGQyxwhoOisQ4jlHn4DM=ac5(uNlvt z@=iw{E5v$AbVptFCRhf|>VsnT!Qy+5(Q#d7&^K4W;gxfFL~p-c)m=R}9bu>Y z^8E?_(U*>4U0NrIAzv=8N)OR4d8dEY0ihJ}c@?EP*E>Mj#VH=`>H4-@nv2eIzkM!I z2Vk*!pxTY>$m!7rjct6&u)b{|Hc(X!wRYG3vQ-nxqSL>!>Ox+=j7Z#X&e7m^JV1>0W{2XYgCbi;`8i23rM1x}C60RYg~_%Yw@`bXPcYU>v2XK)U2GQ1 zZlNk#Uh4i~ZHN{54Z(FoPL&c^e?tlb=ie^0tk)F26u{MXLTdUS4CAHx%Ca_MuFj9% zLcQ6z^&4XD@Tck)+idT5g-Nw9i5Ah~1PNFR%Hhq1)(16D;NJ*G%aYW}TE&DF#87!( zhL_1~f5o-_uHTTnTPW|}kPm7crJ6?1*!d>&4#&0nlQv^?v3eqWeu5Mo-zYl1MU>}0 zR&n!pmK-NE?i!-job3Ji%I*4J_u;snV4n8Tl!j()6b#YhdYO91?U+g#ZX9oov7UOa z`UKNp4=VehhJT0(n`FuFYc(C_)7frcG@EZP|Ge%v@Xpcx3Fg)ZwKGpZzag*tgm>oj z1hwNn|1kNWmdnzW%mMt$_3$Oj6U;#=!`qFXXMMpOrJ-dUpV``}>9qH))H76HDqaBu{M z|12E)ZtJJ#b_{Yc0#obw*_p)YXi{34By~@qr<ck z=u^Jvn0$mE+qOQdT%=nu>Q2+iprAPTVn)5N^5YRsLk6mU=%N1N^xpdIzQLCTH4>jd zdh4<3D>eaBWtgize3)vH6e1s&fnfJxn$cZ>{Tz^ka}`r(s1?e9ebE`JYvQ-SZ+x z=^+Z1Ba3dLiLstzx$q;VSxlP5*dFAYzuU@*2tTv|y&AKI1O-9aTGrpmfEyJzB)L zu-9>-3xmC*5O)n$6}E#|+EFiPn`ZqjpkLVbzmWLr|KE`Od0Egw3CR^`%!P&rni}`Z zZu=L6C7nzi&%2O8@kO8Fi>LDceChbVzL=!4Z75TG`G2^23#d4nW`8&Y_XKxB2<{HS zf(LhZXK{BYKyVH2?(P~axGui9yTjsqo9B7&ci;OzeR}uIncd$^chyu?cgds_(hbNK zq%1u$sF1R7pUXxPDQ)-@%5y3B7*qpwO9ZgIGRyvX>`cC|wRirX+c-(A0YPp<^>)*G z!6UL!m&uXLI`{EY?ULvyQ3CFXYg@n(bbIER@NMqmsmedxP9t)*YFqqY{9)rZvUmpJ zkE%y|T`)_cu1rTTNWfceB0V<4Ws4y^1{I5R@zfbN9KD!Ez}cs@iwz<{R=St-^_7qR z%Eb9Z5}oDa#M%WKDRkLGAs!G`Q~}Wvh=I@Hxcx{FKReHD3DK5k_1kGcr<_~vox+Jr z**~Hzmdy10?(6C5zmJUSoCKg+`Hx<4iGT_wY0{UaszBVZj;N!);FHeU-e}CGudAY$ z0hJJhdj)FPi?i-Bvhm9|?rvf8SiiJV_et)0VXL{vRL@Prr9{Ye#B>=iG@ z@UEdOet^CX1cPkz?tY6ysNzM<`Cn9-{%@)fX0merrAnCqx62!=?HVS%-6jAxtaq&@ zMwHWp@3amwp@e@YG>%=qo%$cDm=1OL$+zEF|0{(4a{4rlaXy68%Z@~hC^-|Aiz{F4 zn(b;%wt_UslQL?eG~AZH-3ItT2J@(J;`Be$vASmCwA}u$I4m!VnPDuB)Rj~8DJerw z){G}J&&PHktQOGuoE;^4ca0jsa?9d!-3_8GSGs-v55&AmO(g_E^>CBM zlapooxvO;i{7fimNVAX8u`ZC1pHsopKpUt_&Cd=>vwU)NF{j@hd;!fj*!`=#A&jcD z{QqN+^*mbCflJH#`*5Vthj*e(Yonp{Oxpuypxe{Vgbz14&|9eg!Pprz0G~f3W&JOk z-LYez0RCaK*io9a2VHrZ$MN;!I3u+A#orTV`wj_*a8l?efHvg9P&0XD8~v)Y$in9q zKd-bq8%r0=W{c&rnf!+0QUiJAM3Rx2J};ID$)Ak2uX-_fLFD8;+Ej81p4K@xzmtmE z6+BCt*j4{SS+ACSz+{mcmqSERB`v=cVHy%AyVzka9lxWa^~|$?&2n#rM9x{d>=2PK zWZjr*3xV+c3t@4`HuoQhr{+w+`u{?-ry{wsK_F=OsU^e3jA_GLjJochUSg2(P(WBq%b?jMvX420LqfMUZ9@(YHKJ2?`Ad@E z=af5Jy8jj*|1%J-UBgQ91P0CQtG)HL_>S@U@8KEHu8Qm54+ARyPR57vjL^D&S=9N7 z>u|chbWV094^S=@Gox07rk$F1qwtI5TB0)}3~N~Iyy?QkrcPHKi!wQ|Z<~RTn!o1P zA!8y*c%{rgR%X)G_L`09a{Ip+R#NEd%OkUfbP(1kn<0lMGykGRs=F+_8)wY=1yYgb zgypa{U<8^s^-OqolS97wpv}=mpWbB98hpN)tf2Aque^1B17=@ulH||^9Qk>In zFa9T-YedPIyVh-8e1h-APg%TA1EL@xcM8@fW#?ma5c`x2Y)b`vwW+lIYr?4dBsQ_S zKnOb*TK0I*=5M|oY`v4g2X?+S;hJFGe48(9?QuZ;gI*TfV7!K*F zcXn1Te{IXZ;xxUq&$g&Uz}bnEMFM!6jaMgaOXoy-T^U@D*{jT$UMf(N`PT;0WCQ1( zLhi!BC*&pxYq_6L%u{1STd;P6wd$(k^7yPFm+~xenF$N_NmCsnhjvNyK?TY zrTkYlWEG)#rR6;;sFDgu-~I74OIA)CS0^P}shk6n658h3o$T(E?mo9~n{0l2*#c7#$@ z9rSQ8=L3!aM#NkjzUFp6n|bo_ceEV-#;qScO#12Q%gF;^Ox)ePeB^zlwTLIWwf6Ml z;XSO%hm`TEoNB-Fd%Y_~V_vN#_2&Ocn$_j|;BMvJx_Yh>A4?W5)2sMoDeI6VGbB(O zAtyfr(%NmVk0_U-5S@qF-?+cIh|$Y@70C1a8w<}N>izKglD|-#;MOUBF#2in82so` zZs&NoZC;%1E9k@fwtSAA?Z}xbE075mG+I>z%ekC(sik5_H+2KL&S}vL_o-HNZXc`a zy{-wYNkvOzB+l1lLI&U$;;L?9jiXTcA&i7x%VbC=%$PA1vGig z=5;+deR+B74hH9=lm5Eq{7=UIRdxK^<@9Bk>s<(qT~2|uq(pxTxe($0i5Q0WXGx0C{+;rr*hG|C z8Lc3=_SdIB=A$g`wyoQ0P5%j64Emh{*{^s1!hH8n3@OA%`PXOiuaEK%z#cDI&xPQw_|4D^7I{pUmz1x-v>T9|AeD^ob)Zf7i53IlTBjOxoPJXXGdH(y*yqO{+9@~R$ z3+IGabiDRi+pj)XO=~E_Z+IWpND+7;UsZt@<--Ff2RC`)zr&kZN-%;cChaZQ7(1tk7-6v**4wv^-1ED?$Z&GZ_Wy*63ELA9^*p?~YFhp&@45e{$dq6) zX$;!=>8AQZ-*6h#$!pKisA2`-(|n8zI$kaOI|y(rY0nI7i-&`_4=j5C1+O$cK&sg7|Qdt%q(4NBu?qh z%ezr4jhaOROBgoo1oHJ)=<%~KGi-ACcBU?Y^TXke@P&!lm6r~|qU@(HDWSS@lkx&G z0y67zW~4={7y3`7RLR-C*%MV@xyqM{;oSj1eN%bs8Auni`NeQ;(XQQkdc%L2Xm(-k z>i%%L;)+#6)0-i~F^H1yIPoiQ+kHjqAdyjTndhFxF5pTJRbTe$`t;%!WbNq6=>xuL zi?!nRz7&Y!Ms_*VfJ?7NJeXsuUT4R1SZ?wJ)prUs2~rIv1I76_UI7LRdqLVU;b5MP zy%C~Igo(F)#}S12WI}qMwK2WDlOcl9rQHHuoV~=#<`5m{)Sw1!>}XMtHrE?+RqD&G z{$yFYV0p&n(u3mLuc8r_8qa6#_BEW+b#2{A%IuhZa1P4X9`AXd!Ql<3y3xLk@#MR4 zm}Fpj^oOClb@#28OB2pckE#0ea}wW-4nIHs>%LCEhrZq011XX*(&*v)j)4Km>Dk;yYx8ozvYV$hd0NO+ogDmZ;{rZ_oi!d zIv*Enb9B{zS{gk|p8D=b!Jd{)_6FYVp&eP-_53{(ir)qKN8?9$ z)=-3!E7T7fS_e4bK6|UaY-5LnZr>Br858MW8gLGoVP6JmL;5Z6v6AF7e&R|hL z@b{+@^*IcLT2pE<0dw2>hCA}p?C?(hZ?$E!m4Zf9+cZe_#jx-_MsXF+fP3;+}2$5-1xU3iQgkT?qztc)2sTZ zezWr*w;12e*{oMcInVNJKaW=9{RaC*WU6bR3l{n-XXY51-t%423(soa)&$orcdKJN zmfY(Zpvoc$qNOL$vR?14?>Xy@JqNB+28yAeUf-5qETf@r>; zkJn7yUB12-dk+y@O!}l9r?2Zl#6&?P&cev37hrwA=gWPrYG04X>~@!|h`~<#uRtJi z`=@F&_sc-YsUUs*=MF3N)#Q9VzF)=F=sG%GXnr2Az^crZv}=tq`06EopUb<42wZu$ zeLLu&(SXnNb;L$no-9vY`=j-Dk=*;hY z9+q!pW4_%#te!YG%boAVs;l%@ z>k&V*`uJTQoxbOeSGc}*dOY2nPENYI z8?^BU+}3*EU5Tm7xs7$7&33;$Z`tvZF8$hX;AVfjYa{G%t@hOBJ2P@Gx7*gEEdx)s~0v+m=B>+9B|Gak&)=1YSp|CudKD*u|>qw?!3c zdR!q#-Sh#?-|mjfyZQZCs;Bk3)+*5)_BYNWUa#L;Ath@cwk<`D!N4UZ!tl4u>eXdz z`(`MTw7aU1WS>#52&zWvq2kFl{q~p3g~@FGw+F$jbB*klb0Rh8C@zS6I6{*eot=Z&a0yML0WN&yXB3pECmYr^ax7|}9?i(d9Cp8lZ_((Hx zpNdqEl<#G8qc}Uu#*B71ooQvpd!(7FbBY-rEMEN@RSvDk9YCtz1@SgsFYvl=4>e2pnztIl?{9bWTnxmW4X%dh zMI_s0ozK}gM^H6*Z}$Qp7O!04?UY*hFYL~dSH{~JA$&X_eY4bq-rdFHe#`19ozK?@ zig&(^yX*d;o_&#dwDWd;c9aJElY8LhH@-|E_-!%7y|z|8#2JViA_+KN@^%O|B~;7WnASv!QU*kgW9ycX7Y_%!29*bKnc`XHh#Ktz>ru6_0f}c z;x< zE-i>}QAYJQBxz?^?r7f9l0s=hF;PZ zHpt$ACZZD7OGCFGl)Uzm3yzdXrG`2lKy$bB45!@I*7Q%!)SCJ+WY((5$Mgo*@nt>^ zj3u^BAC*8$Hv-d6o5xpDp`NJea6h0lr0H2CMHO@7OdLy9!9l*t&;R=ESbW)V-lBq} zdv-79>6!}S^}~O2`lt&HdUd-vyj`%NH_cm<~`7~Q@@(2 z_ss$DCefrPT0ysT6*WVr3auU~4`t|a0gxZBsZCBO5-4Y=(93Uev2ECNY@5q(>&at; z%{A>9gU{eJ($&O*S1M>}^LdRprFR&#)cZ-;WzXMLl+LfFN%^e9eh$CrDs(G}$_F)j zdV#TTF2rj=BhiW~40+Y(#J@;RE!Re3pjWwyC*Dz-bOfLqleE2WbkQ?4yO5w3%fPX( zC1tP{ro=tOYPg*AUkm%i?YGVydQD|a6a2-OT7Vl#QiaX~S<>2K#@Ikj z6jO#g6)Lk-|NYn%g~1JT2!iC5HMolMsh%k=j zPqGWI?e9Gn&eRd;hz1FLB(B+}pDzqj5hnUrKh`868sqIxWINI1i{W!NjS4ERSI^7G zL^HlE&vod&5+BUG{MulBz$S0#ZQ$CM_gx%1{k8k5yws4mih9`PW9 z%Q@)o7Fu^=Ia++%XUU-M26}Yrbo`nTqPu>cLEtN063Ine420S6%7XQ5dmfx=B(B~D zl@8@f6p`XN7rXtwbyis6KkO12g!DH^FY49|*&MZ{0G9ri?&5xTKJAt;19!UH#?E~ovs+hfYsLG_?nvrT&SwGVb&l~}&@SR)SzQvf z%ggOh=fLQ7;_qHA^X?;rGzePnrU4pQyrN`u1M5s$dgK=*D8P|GZ;^x)2kZxMsqqHLI1AdGWintgW&gLW zqk<*_2|wEWZl@;E387I|9~o8bk3E?9Dqf^%pc%R0!VUBwrWf=Zq#I7sQ-x+v9cnV& z_{;;@E)pEVpM?xIfZ?pIfC7`6uUGzQ3aQ<*Qy1`1bcm+`8A4`8M26swAmXR8_itTg z{+Fu6Y{@wk$U*P8-VkS5>0 z;_)1nozNdw*4DK{HDT^}Yp*Y84~i&8iFIb;$X=bN2;O%*1&xo&zflOfmvE&&9$v2c zDg2*zz%Wia4k4qxdnfPo?j0`V9Wd4`%(hITV&X!oGAc|a0Apv!3tJV|VT|KtyK9FQ%jXSXGu?wLWI_V9m+Q zv#q?m{7Fx*+oPq+$IAgcxdrF3sLVmkA+dPiQuGUi@Rey8W zxj33T$DvCbFd6{cRSPoKR*)ty-Ur<}Rqr?H={PvlB?1 z%i_v`HGD@;DRx~`?v* zgSle`KQt5v$@D)#w)XoL9rm~T{O;SUnOZ^Yim+rljm;Wsp7ns`l`0mBdoZIw#Nah>pQ` zkqHt5+o;g;_k${jEVlt69j?e#wk8hh;+$!Y0#t)_ch!((ox;1bPO9}PwCT?J)2ptA zR2_lZbk09eliz$S8g|*Zkw@pzBFX#zl=E2v>epQGN>TYV%KMjKbpn=C%-MqmC>FPL z`1g0`Ueeo--MZf|R#&eHuIR3;__;Z}0jh}3M=YUR>UZsvm#KqUe<>h_KDg?YE0cUqPPuRs3o54TQW?;}Z$27SWmvnheC~KVW zvbU^;D12pxzrq`)gx4eNhc&m7ocQyh1V#H#AiB;WjR$~O@f1*alKdnxOi60vDEV#1 zxzv+M;|tCO-{M!fZe|mIlmuJ`I(#tmKSHA5l9Efr+I9{{@>cDIBUHBhQ*E94oSd-F z7Fil>_E?y2_?1fr-8=frWL!}I7m1=8!Flw)MN&?`lJhre)Jzsx%`L2Gz3(n5s$}ib z@ef*;UBDrOp=NFohe}Hk(X(VzL=NoNCJ_w3B3pR?tzQgTkVGUZrUm8~&2uGq&&7j> zABX2nAz%xev9m{D9mfx{9i^#C;HLh*Z`VPc@8MO3xU{AJEobK*msw`1sCL9H{`({x7mEGnI~qqXx|U0R+?wG!|xZY zw|!H#7`e8FNhug+A&e&lDiikwA7r&~tEfqi#53c>SIpLT#lj%l(S$m)E44Sc}0hj86G$nhMKBbC_}t;6%|YnPAKIMVQ%VOaQ6{ z1@3CPLo9B+_IrQtEX?PJ!My^qynvA!3`x9MD7q*L?^LbpVmASn?Q%KI&BEE~6j3*k z7wx#FlE^h8W2Iv!t_+t`VLS&#^bN&?IfmMSJY`fEOVbK_ZWyShnsc^rVl!vz!3d5!n_RkusjqGBIou0fo*I()Aew>P_7ouKF?5*t_ zRO3a{S74Osu<}fJsUU1r8~Y%Y+i|0`gq2CH&)H@WO~K!1>q1h;Uqd5bM$_qOP^jSy zXMjhoI(uGI8L8U1!}xj0m>u86QqhE!nO`wjE`83~*4ES9fco8> z>wUai0mmZiEy)ZVs>rFCVb4|xp_bPer3LeeG-D5ZTx7`?T67xx22|8|hV1C1(OkkL z5}@;(G+ESEo0?t?da#vr4StcLV#@2$lvHzR{CUH3vqP8>eVlI1gr2PIEGjx?$IXq* zCEJ6_T4boCetzM~R07oUA<47ac>dnc8Jat#1bbzuI~GKfpC@{wHbrv0`6m(!h23A@ zr*ihbb6d5DTqf_@4%0RJF(C#kvF}5M}TfHq!~WdB)MQn%o4xB?Lu zzb?YoB0XKI>(3WGm!Q}%jiT!lS8FWK5$c4zlwSgk`Gli*+|;_YScPh|H$)m3X1J)P zW|22cjvHF1O}WGo!toS^F!8W~}VWFXc8z>0<6C6g1|q`H^b6Z#Iw4t5R>A$|zf4WfM#-6@G;XE@^B zxk$7m#9T=74c%QJSPk%u`{kuOX~ObFA4%l%D4mm^7uf!#g@#osDoOy}L`RZ`v++W7 zbRWfs8Cw7ZrynU$|4}R%Z7Fx|p8Gl9$%TIrRkqR^V`|EE=s*klX8|1>E3XOJPAspw zLz;>bPrD>}*y1P8#*twce733Ut8kA73gJtsxQdD|l3NS6MZ=CIa>m6ZqXzrj1sK|H z_jV1v<^i111rzTOk&5TQ#W63Sxz(530Z@JQ+WPbT7JIBYe*!l`>}nYwr`DK4rO@CQ zW3FGUW8*v~)kvv@1&|;Ju%hv?-YM=UnwAzc;l|5R$)ibZ11++guFSBpamYJb0{7XP zsxZy`xDc!)h|>__~2aYaG4#nT^3Ya zr(QN3d>GEQ?4n*)Xs~Hzs1^Elp$;jlW5S*!gI$sL792fA!X63ZMcL!}e1pgJ#26WCwGwP88tWf<8Ok_ELBbtQ>C4-YM;XRUn^TE45d1(P|X*t!K4f6M2Pu3l<7w|xak;7wb= zNlJ^I8SPwSEUjWAhILlpnH!P{x@-6p*B@a13PtHRB?%Sl+L|wm27Po5rZQ8HS`Bs3q`o+zxk!et$&mg`K?-9dfghT|N zt&ye?zz6FzLGoBy_-C-41OzI7et|{(9eucOlafN|>dR!F<~u?_Fv|Web}~}t#1B$T zjfbW(Vd=~JyOS+0Ui&6tvi|BaN$geSWc-;@>J5#Zij+3P21dW=?_6@pfW2VV<+u9l z2;_v0fV-Ot|wMu{vQ*b&Ecxv!9?s8ElijX^dWB4~Yu%hZN)yyw6fDTP4$jUD)}y>$8ePd_#5^Whz7jCF8e~`h@+PvqJ51Y`F6ha@US!o8m$MB zPBQvEp^i_!!vEj(Baei36**+xNCWA3|L^+I#?W5L)XC1p(b#lUmL5ThVSd`ZKitsY>ufmnh22t_>tXZ`*O_n=gd5y31wJWheJmDL6y@%DeoGvyN65 zgAG340&%}sd4}DdgipIO9}G;}H=MFn@}JKf&!4%2(7k}!-kyU&lc!9{`$GE$FHwF` zGsjPtdR?OfmAisYPt5@v=2_dkev`60W@#P7A>c#l?)Jik%GJvlY9cHx#qRZI)vK8H zn-@euyPB&wHNl&s_PT*~pY0mKe;dUzX_tu~{UYf`Qdr0?q`;xf#w&Xw#$gM=e+0O zp0MeL7|w3-=eAUSLfu!6pZ(Rpx&^oxfaeGLR}n>G7v7^{K;yc-q==$8>pgdE#`KVM zv_N)LyKI6{{nMlcj_ZY6PY|Q?^^lMtVtl*Y^F<0=-}#H7GnhgI5;^UDjoZB;=KE@q zy1C?Nn00AypLbKbkX(lU0mE!7tQF0x2mHz{$wen;e;ud-HIyyfx7p8a8@T=Gzz41B ze>o6lsCyX5;+Dv5yDUYdkJe|x4w!>Uqzk-4QG*)MXAQD}o7LDeXicKWZ9p=x&BTkI zD}t*>OtC_A5-NYUXKU@gDs42u8dR?%7>lel@X6_kjwP1oTNTBSvXU66>vx?8SNlsJ zH(4+n*f9CHOV8&fmFR(khK#sRk0mmWl z7loK)@^I(7^zhV(T@z)*XrvScv{_u7pQ*@9Gj?}WM6r~=z;=)jRxDYr+^j8p#+O9Y zE()w%KC9(DE4-{(k01bJP9%;$d9au6quY7L5sXS065MU=NTROExl&lsvbO8Wv6=ZP z$Va~X%)wJvODrrZc{bv;)59(T+@%L!+AC0yRawa93^w&>H_W3C`0uDrwJBg`m!+)W z{v1}uPI#kbNEIxgD5{{)>zt#3*UJfsHhg$Pb)F8InGj+5mYv=b5U*OWqrpIzmpA$i zs?q;JB&e0tS!hiSWL>`7p<@DSWB2@NN67VZ&NVWzn7cesRVLN3P}2WKhHOVW`lC~n zvSWlb_3)ef&TWsuD}E_IGI&X3mrvbDuTuQ%f&%S>MK!{yQx1h3s(I+;&y-ZR&H01V`LP_FOrd8iyfNhOLX#&_ z#NP}++NmOe!YIAayu~&e4LAt6PkK?fKLafk#RnE^EG})&16=75Ty#H}Jx9=XVg_*m zOo=p^PPzhaH=>3Z78CJYwIn6RmdkiG!4`DZp5>c))(;#={4ZxA6Alvy}R7)yaKju$Sw>?FDONxLV#PZ*dw_L3ladP#m^CGuTFfdl(EK2JS4Qy5Z0=5G8}C9 z=~m+upxo5ql#1{zNy$gj()@f8LHM|n9!nu2`kSDh24igyF8tW>_%n*7;+dvj$`7}f zKZnz>mE&NNvgs)o&j_jl5sKw-gD+hPtNg_j)J-6=Pm3BVz1nF;^!U!eq0veUm+TtX zh&H2vv?pO*1A`uc!Naj0+{U9*ysCvl)!f6=u5XwOny#pJP(>BxZB8OpP&AN?_-Gh2 zgNRkvJy#p8s69UR^m(3@RA>gv&zBBbn8Kry4i6wX!ai}A{ftQ-9Nfi6IdGtw8IMRX z1Ff|_@hN>Gxiqok1bQP6b(2cNuHl+PBe+Fc0}1HYvFEV;xdUIkAERE^+WGeGQGE)M zdWl4^I)STYu0quLF&4R5k=2O@0p>dUV%VO$GPACl68Bd)5%A-f;JOb;y0e`+3#Gr* zN{L}^9HYJlT5V2|2Wg^}e(Lyqi|2Xs=v3wyO0xS!8fZBY{oQoVzVYP*5HH>bvWUI= zI8mA!pl`siz4jtnPKPc6*R|F!@1KOKn=@xJ*w+v1EI{wJ6?hLr_C#kzijnTIWlUcA z&?*$rNbQ(b+H0jNco!;}h{4N#&7z4Np|JFERkc&{s{xQeZ9x>;n!2U|Zn3gb_Cg>7 zD1~ogmYKFuN=NuuZn@A=+6zPboD!C(0ai`!Aq>Wmp5Kx&&qLV~%2*rhD{)%!`TF8^ z9IoMtYDAtkD4uoAV6!DPO1gvhfowZlP0^a{ox{mW-rUy_rR#adP(xONn^2mhkmd+K z>AE3S@Swn#rpXIST!6^v6D{oIiHOHUH0cxuRyf-2D%n^7&Oj;3AB)X3>p(XjkL*kO z5d~?K(So_bHO|fbh2BY~MJ!Lsz6&x-8Ld77w8|m`&IxS$DuuT7hR<99)SwXT4_4e= z$G+RK`iEC^$`Y-@qE^dHA?w^Ry#WGj%~z?{Rz_wG@`t8OIHAX%owIKrI>{=29QAxGHCd zMI^jz4iDO8a-!unq)9ILR!!V@1!#odAQ{vu1Z!~wY56}tFu@@cm@nbt6}^yE&f}r& z**w3yGOcTuE3OeCZ?)~f8EaZIdy-j;bYm4l?Ca0Mj4?F&5*ij3d3Bb?B%hW!1MDMdSO-bh{{zfu;+7{ohgovbW!JWL%23}G5qJl zmuybaDm8D$s1ELZGhXGel63AD~to*YMSv$rd6yl%Yn=&xKnhW7QDdwwiME1&?8 zey>DtiXl+*SqR~oj^7Sb+b#mHZMqfIxP9~x+qp$W`-WqbP$kqL{-VLn`lFpvuS-C3 zz=%Z&xbe)78Wi;Xh(10Hp+MQe#!}fKjsLjGKJS=@PB?2Lq!wIol-~#Km)doy4@Wm~QcgDifJF0B$QAzzH9aly!rL z!U>1=^fVj(6t6uTJdqRX*L-W{ENwr4*T2C8JFMO@~SV1D1N@f z)l`^ojOW5&s<~qg;Pt6F=s)WQTzV3Q_6vcNsJwy3a=X!vfhQm1*Aeo!de^s5``PmW z$%fOQ@y~EelMOdDR`Tt4o4Cz5K1FBlSM|91R$qxEj!ECV5(PpeyWL*+U)hM!!IvqM z>%yF;dKU#+*i zfDF<}_Rm`pLH1%5+Jb$?vk`VrLC)J;40=)B#4uFiz$%e9V-t7&2EAJny^CWp^&((Xd6m}z0H{XjIsYelG-v^&5tE@EWM8ZCZ= za22T%mVM$w3CFsH!{HK7Re<#E^`YOM&*&GqYzqztOzS7rjN&o5pAL~DaRiPK<3CH7 zT`JTtMgu7hG3~f*lg@or&8u5!qb>JcQ^~lWJ`_(WL?rC^GD~^V$+@Yelw!{_QN=8|Ys74q4Jg&{ zamA0*UHW7Ff-fcan1UpcN0$y90q;#=J2xWhE^Xx|>jtG4EqzXsG$kvC3H?cEE1(`k zO=-hi?=mhaHr=unxtJU}T{F;&VQCd72@2Cue{EYZV)WI|6!TLXM_ zfvNTMsa)#m6TPrqBf?(|(=wO_P$J4QXRJI!NqV~TKv`5(rM3{j_Wwqz4v|2I-~{;X z#6`rUMyk*==?iQhB1_76Z0w)v7{#(u7JeiPTXY_M?q^%cu=}R&f<|Q?-jnTWkoK_pahqySxo7EG!pV!w#;Ey#vFhoX!e<6-e=DALOgdMzL6W4N*(0> zp%{G0d_?4yE%&zC_Bly6Xz}M>ZQQ=Mbb8Co6+91|=+J$!Dwa0FHzM9*^b6h z!<0z47sK$v&C{**c>21rms<3&Q?`y}vE`|&E~y^2Cxxu~ZTJS5;5-o@Hw32~F$dZp z7OfVp^bKA8HFLfQ)>(``R={m-v+VstHXK>aqv-eMeg!!5fk#d^AC@0tqy2LZC z$|2cLBTa^|{RGkHZR^}S>TS;>vx-$8rqk{sHn}G9?Q7?Hdm#AFG}5!Sn|^qgqe(Fs!187ci;3WpO+ORNkN?jhS>mo|l=ejRL>X{%_i z69jO&8vn(b_Dm}))GPj6xn=M$nt>#zF+xEzWi?pit#u03GFYp2*r_(2gY(JnHKHf* zkLDe02*B@qIY&s42ce+9cj`$Mkmh9Sdlg|dY<*?(l0X92cBYI%FLUjFzi|rQu%VGr z?lZd$oRBmv&WvIWDwB3d@YI(|@Oz~vy3tT4Lk06b1jFRZui7AC%U3~A3c16pksp<3#>j= zDhlF6qij}%C0ENU)oMh}vUB^%_icQN{~BTP`Te1aPWrNnPQj=p=^brL5^AmGM`=tq z#p5y_uv0~_wr8;{U0^VyHNke)HvKBS{*)8}dqDdc6bN}(C`*LyEs$%yO%^LI)3$Zc zK6=saZPq9W$j{sT=})T`Ti__Ohwx6lKnc?{{l-g!am)DG$I7^m78Fn0wlb;r6PsA@ znmSrObf+(0e)OuSlhtj()wQbQRI-$;^e@s8u`xt-)>%u=PE+EzINzn!`}@n7ZpAcl zzur4oyXzSMr7G@hzNi_yXpkl;QE#p$A)Cv0LQE}=rO@`jo3f9lOXfd&g#_{WamNMg zp2qbXU7NGv75`Xd@HywL=day5?4=S}r@g(N8f4sTojVY$!bV7pGIpSQ zE`8TqTCaxQDpudfh;F?cFl?R->VFE|u`%$UECZ{2?guKxvP*fho=*lA($EH{9|zw^ z3S$gs&UO7_-ed;*eE9aRn`7TJFgB z!GNDc`ub-~QTE6uw@aIhSC<+Z_V`XuKD?W)Fhy_tJLzie_H7%%6Is8jpq1Z^-8xzD znL)Q)uN+d<=Yw7kjJVB7Z)2zH|6g@q85CEyZ3_gF;O-=l;E=}MJp^}Yys^gJHMj?d zh9(ePLI(&EoDS~p7Tld6!7j;t=j5KR>Q=p~_v`hj-d(HLp7Y20v3sns#+?0ho`(Oo zDjmk`t@&_hZ5s4PdbW_U>_uY#EAry#)rwL45r7{hUMFhq)D?cj=eNrZC3&rE8ZUZImX@{+rDzSkC{O&8i*+;$|BB}L}dK0#C6=5>L9mZau>F)RfhqI3* z#`#W+MV2sl00wsY%y&pep&e`LPK(*eU2;uCK;xtTK}p`2uV+b-o(VakMT?h*IW4QClQ))Tr0o{ zob@0Lr;i+hue#Wo-dgvZU}$)ujWWCl69zF#(Sc!mVPAf@%`?QMAWdVURCE@{rA<6a zwNtOkodTmu^43JzuXbXk*rnC@>SDO{Vl$U~{JN}}V_97;1)BE@)bHcIbM1HbCXqE| z?aNJRn7KDXEe7s$K3Z@>4F#y9n3?;$dt(?H=kg{}tLa8e=YIfzd(Y;7`ub?H*R$dz z6*190XaYF`WaSlf<(s_A)UlexFdz5m4`qX%<^@^dpg<8`rzf;!5s?`wz<+VzJ2Bhb z!qCVJwBzxNV*c^>$31(&@LNvcS&wiH&K^4*VA{zbof;r^&-K4i;j+n|7cCu#NC$+U z{IIG!N>=vSUgC0|u`G}^FJ_QV>|6z;q~%b1OFelm0Imv9%>~n#pO1@X6jqM}a$8@4Hk& zdVTydg&(lsxe!c4JZt<5!%Rm2qrx`|OYQW0XZG~E;cAV~B8@RUFk92wdkk87KZQ{0 z(Ud`}4Ih_nw1&A*nC;0(KMbxoZj0NWUlp$zB-w$w24 ze!>OQK`utDVa_xykEi{9H$qSMOPL6`veCqHK9uo!ZJ*!t20O{^BlpiGNnX-kMTuZ> zB;h zaqLU=V8KW1Q7dJAcd-A0TpZk-7K&s&io4y7-cRd(yc+1$49OM;^a<}_Cmq9<`GgyC zYOa?gxyc%o98N-9i>35%V>&#l8n>#EJKY75k;$XwJmuA6XoKy^yy^+d5Amn$Rkc6s zxEK|qW4xK!;>+T{%GGLq(f+fcnu)7&0QMbcZ|F&yc8V_94w9ePlf zmlf)4Rw6lc{fXyUXKGZf(10N9;-v?wyveT*(?a(8A-wGoI-uIT)T9E)wxE}XqAAG ze4F7$jsOa8Gq(O$gdy7bFC{-11}JGBj^shjtbl{FxFw&-V71x8ieq3;a?CN2@BG}H z$LOEU7aFs{ZW|^&ZPS@os3TRM(5o}{J=4Dh4ux+h>EmQmnFW|1NP;+(eQoo(MR^%2 zh@_P`H9gDsy6@Q{)j;7EB6MXSTqeQX#!}vQ&uoG1yBXj>OqA3qgvOd0)TMA2AY<^r zXLWobH*?7V&4UbH&2+I)O3nnly_Q`L&%6;(FA%z;kSs0~F@EDo;uhDaT=ZaXfZGof zx0O1Z>2YZ(P}H}&4yJhW+U$0mDGGp06_=$~4ScI>y)?JMrXUl1&(=A1?0(|#j!1X& zekQ!*g4FQfwH?^YsNe_0k70QBDTP!f)c0CW z6}RR}DD-bO5(&0zOm#%cFAx^ibjr>R%PdsT<3%4jjm6jA+#t1Ai{?*~wZofYDaBe8 z+^2U4+`%-{vC~1bQ}edjy_qLUbPOixGcK#_3Nh8;a`=C3!X-sk8EdAe-K5?MyAFvek1jn&wvSCX~#o zkt7pHmnDc>EgLLa}jFJCf@L?_=@o83Gq`3b*J zZ7)5VW1|N;3yuChgcQ7uBe-CB{UxHQ1&-JwVLQehF;z^7{x-~Ux}$%ZzC`DZj5Qgw zwaCN~=UtXG2kv`UCYYif6U&%+4Ym)Vet(!wO+-OtW3k&L^oD=xJD_>IkPt3G%oFQ0 z?38Ms$_l2%%xE*X3_J*3ryBm05&+M1Sfq>UQL0-o<$Jqd7huiazwdoD^=J zF7)bDmvSNvOmyr&$G%{388OxKy!!EkpSMuKcgry<_zg>WOUi{mo`gwh8IG>gli3C4 z=qVBnd(b{%93-{NP`8$57mOb*AmB5oD<)RRaq?D=BH)&fM~Jn;idO4}2d}JB;4`*7 zWAd?mf9TgD!)E(~jQ#dSto9q%yQ+Oosg<1Un2*t$l(To6Ux1DC!@&%u|MF4%;yY8o z;u#EhaA#BUKDp$JJq+s1GbdgOZ&t1T=Kj;JzRt7NVZq32Jz(na0VnQPGhbw_UxjW5 z)eR)|rTc-^w8-u1RPOM^3uXU{RU5_dHSpb9z=yGJww+5@Yj;55+KR}_vunMcfX|Ul zJP%qPPFxwyJNA^7-%jRkVxOue(<^`7yxBZo{zVC3fd-?V7G{(#L3hhV!z zXzLc8b30*kX@Ex!yLcI;_aM@>JW&sa$$R5JkgFDhjOtd2Vf^K_9jO4EaFn1wU>Zj{ zF-Kqjjs!<4(V^06K@o7EOsEyP-uR%(+H!zB7Hd>}rLs9M?g-*K5LkCQS*#h-p!h_P zZ{>M2p>uD&LlOLu!EaaX%)v50io;Rzf0_`IE8YH&NSlZc0X4RIxxZF{$$4vHl_6}f zLf|lI?Zd390k|_j_mReK#AN@61)&0Y_P8Of18V-%vfiJHI6q2!^FkX$-+iSQ9s4tXw2=bTurBT)aQ ze9Ps2|8oAR!zfU62DX(^64mSXK3zzY{rbyT;87sR$^q_LAl z()5mbt;gHs67%yj2Pu1aVMoQdoB(LbFw5VQi;!b>l_2}aU!k^%;dA{mjUZFK$aSYy z%7(^snWi|$aE&+|n^k><8$z@1)sLs&(K~DW{MP6ZbAO&uCd#)B!cHYwGR}$Sov70t zO!(jSa(GOF(?aG#ny2aX<$pYWy37gRm}70<)V_k?#Ttn}hOl6qdp5cw>oK=z@jPO~F~{(5j8B zekp{i}pr$C@9@~@Nc&XF&g_>2u5HZ4`6oriz5(=_ezDt%x9+qGP( z#D>Af^@Lu%U>emzrasBqKja69S1w{9!n<8zID2KIM9VGj4*B5K7w~fwXe?v(fqZwi;|D}M96?b7W2(?xv zfA13vxu*}-qRAFK-sgT6fNC2FFG_*ieu|NIkY^n-uouQ!QzfH~b-#)izGbYz^GPlP z$2~l4lcv}24r>Qb9_2bqd)%TIOS2C!2VYzKz&$%cSGG!nbIUEz~ zqldpF9dMRKSs^Ux9?6@|R!3&~kB?Vk!$OJFrwv;v!p;39kO&s5FRii)2H3 zc}Nd5@FL+j{z>PLRBgMBR8A-Ol8z4b3w zLc_gZm7)r{$uplIA9_o5Li*yiVY`ZBh?!Fr4RmD2lJ5(++wAS^f-YB}T76WUdlSgY z2D2kZnvI)^gmfw^-3s{~4PP2CmKdsas)#HF@Xc|sSIl05uYF3kuc z&`0Iwc1kVvYBEVi1&p#Y5v`gOZvr6d za8&tv8WisXl2qj~VT4P*DW>M>Qql>e_2B?^oe{qNmNvEnY^$0sM>dDGXYs5_i*5p; zFUWe`)@qDvfAY**o?0{|p~{iaT|hlC@TvI~6y79f7Y`diqgS5wrDmAgzSEHMc&}`8TG@>XFncd%?N{M9QL)>| zGZ2|dp!$w}?CVv~6S@N$Hd$ ze5aoMq|HES%67-7n-$LG=0GYp3s{I{#9!uzu2MzImpK$PXNRby?=negEr{`4_T_Rv zP01wj2{kP&sMJmcL<-DxGQU7uiv`2UYAWBUYt?&k;Ep2E1mmHG>Qxf zB?T*1soYA z^qv(&zoId-ym)_1*g#{#ra0gDH4nEorlu$HZKC8(*8R~X3Hfn2vg^Fy*USsl7nE6N zg3F0gpG$u1?ow(W-FteV1UxjvkuKp~$51=pIb7ZSg4n`@2GVnx{qOXY2vj$4yV&So zs|q$ON_0_EzgO;f%J_OYLaHw*#E}p!OZ8bgv4my~Y%Xz<(1t_T0w;ty>&1B3Nid6+}H)Tr-c9Kt*N9@>ShpOv*D@_$!Xz zt9)htDP+uIIo!btD9DeS(ASNd>SZijBa45C@P}U^HY%)b$+S7aq?k}Jyd%o_tG9$V zGD^smJOULdF zsY0CHTq3XFxK1@}rQy29=8as z#4cIKT5icEGYP3pU_1lhH5UX^1u@IJ<7E#A^)iqA1t$CDC1DV5$_=k)u@dRBw8hQx%EJO&(u0%(wPLr`j9VPk&ibOIA!b@708cbD@=)YL75Pl zI!Q};H+~@y&DRR3fyi_)o!Bakb9j!Lh<)r4sZ=H4d$2xeWUzq}Xmhe3 zF9Wq*l>OYBgEL$1+-otg=+@XpMb;Fv%jTs((^ArFro+lXyBNe@>SY$ciG*tY6wF{W zX~i=!ts86aW}j*PRdLJBE6Tv35wwvea1A@n(0}eN!I`Ko=Abi^-*E_V+vg{UYLXK| zy4mBhIlmS_+l|k&_mg#FyH%q0p+`hBqi%N-Fv4-zmsZWtFZr99kg7wjL=rB3M%z7ut72Fb z>GDzj)GThv4P=HBp3Z|CBs7xxzP0z<6L zOxCgM*1tpTB;D3sw{9fAcjh?6hAhN$KPC?TAkYR+YjU!kGwS{akczvtFo8=+!$9i)FQ~_8hx%=<9;g* z?#ls$676SWbG{Qo6Hlk)Ol^p^-m43Tf$ADtzuS6jR4q9U&F**C^+Jd&39mXyMQ(Rv zv23Fwb5<6hv)k<>1{ho03C0hR#~oz8d81E&1N6%7>RZsl^deJ7(dIz8>Sox+`eB$g zt9}HeQAfv$^`f`aEYQ2W(x6AVeF>vEMh1L)a{ZafZyr#|93GtqdgUtWfiatvdo0aKU-;J}E^ZUtD#5xQ`2! z4M&#C%lb1?={^%~mO@DDLE%%W&9syfR+*wzj18{oGW1MvV}Vz_*+{5ZYznDd%qc0h zCYPpZwg|Jgv`PDB?<1&pv+;3MBowENym%EV6JKg)_R5C9XARr_CavxV|F|`J+z>SC zeqsoONA1wiXu!n%D>wSr zeT#GarLL0R$zhLP-S9I6lge5Sa*%Z7ezvE4vIoNkKKQJjX5;*-r@eaK-Yqq38MPYi zmvAhrJ#&#IgA3+aIB3T*e2a~q_M7X#fKb80>GJ(DbkNKP0A`6kbFt)rY!>ddv>z-r zN{+=jS}FJV?kuKnTq71zm``6oL;|>$kM1`DmX{w^>QY-g0s_XtiRN{WE{FM8pN9)r zWm9+EVu8nU%MS9gJaHEF_79);bj}c2r8f>8OUUx4MZ9&Flk_Q041B7x$MPSQO3g}2 zEIOJRa1+h@mfe`=d(KhiWJxq$uN5`E)v#BHu=Hmn>T0EExSbWFJ2D_;-Nmi;nbBD| zzc^|I%~QL~e-&1a%2FM<-%;M_^M!6oJwD}!iEU)&UTEWJDo@RhFYYtq%!MT5Cb!_l zZ1c|dcjkppH0+$Og!Fw0(0hUIpG^5<&+M3A`YOo&hsV)e{-^5lZtN+aM&+62_S+X9p{H;~}XWT!x34h}h^8USP_-D{Rm-cUv bBJAG_ts;-|^!LQmM}Qtz#p`hj0pY&@HPVXf literal 45228 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 V00QFg0mNq*JWnO(^AHTo{{TnyR Date: Fri, 20 Nov 2020 21:35:17 -0800 Subject: [PATCH 27/27] Correct type annotations. --- game/db.py | 2 +- game/event/event.py | 24 +++++++++++---------- game/inventory.py | 10 ++++----- game/theater/base.py | 2 +- gen/flights/flight.py | 6 +++--- qt_ui/windows/basemenu/QRecruitBehaviour.py | 5 +++-- 6 files changed, 26 insertions(+), 23 deletions(-) diff --git a/game/db.py b/game/db.py index c73f7dbf..05eaad06 100644 --- a/game/db.py +++ b/game/db.py @@ -1244,7 +1244,7 @@ def unit_type_name_2(unit_type) -> str: return unit_type.name and unit_type.name or unit_type.id -def unit_type_from_name(name: str) -> Optional[UnitType]: +def unit_type_from_name(name: str) -> Optional[Type[UnitType]]: if name in vehicle_map: return vehicle_map[name] elif name in plane_map: diff --git a/game/event/event.py b/game/event/event.py index c4506fab..db8e5ee6 100644 --- a/game/event/event.py +++ b/game/event/event.py @@ -107,14 +107,16 @@ class Event: for destroyed_aircraft in debriefing.killed_aircrafts: try: cpid = int(destroyed_aircraft.split("|")[3]) - type = db.unit_type_from_name(destroyed_aircraft.split("|")[4]) - if cpid in cp_map.keys(): + aircraft = db.unit_type_from_name( + destroyed_aircraft.split("|")[4]) + if cpid in cp_map: cp = cp_map[cpid] - if type in cp.base.aircraft.keys(): - logging.info("Aircraft destroyed : " + str(type)) - cp.base.aircraft[type] = max(0, cp.base.aircraft[type]-1) - except Exception as e: - print(e) + if aircraft in cp.base.aircraft: + logging.info(f"Aircraft destroyed: {aircraft}") + cp.base.aircraft[aircraft] = max( + 0, cp.base.aircraft[aircraft] - 1) + except Exception: + logging.exception("Failed to commit destroyed aircraft") # ------------------------------ # Destroyed ground units @@ -123,13 +125,13 @@ class Event: for killed_ground_unit in debriefing.killed_ground_units: try: cpid = int(killed_ground_unit.split("|")[3]) - type = db.unit_type_from_name(killed_ground_unit.split("|")[4]) + aircraft = db.unit_type_from_name(killed_ground_unit.split("|")[4]) if cpid in cp_map.keys(): killed_unit_count_by_cp[cpid] = killed_unit_count_by_cp[cpid] + 1 cp = cp_map[cpid] - if type in cp.base.armor.keys(): - logging.info("Ground unit destroyed : " + str(type)) - cp.base.armor[type] = max(0, cp.base.armor[type] - 1) + if aircraft in cp.base.armor.keys(): + logging.info("Ground unit destroyed : " + str(aircraft)) + cp.base.armor[aircraft] = max(0, cp.base.armor[aircraft] - 1) except Exception as e: print(e) diff --git a/game/inventory.py b/game/inventory.py index 3c92a80f..80adb72b 100644 --- a/game/inventory.py +++ b/game/inventory.py @@ -2,7 +2,7 @@ from __future__ import annotations from collections import defaultdict -from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING +from typing import Dict, Iterable, Iterator, Set, Tuple, TYPE_CHECKING, Type from dcs.unittype import FlyingType @@ -17,9 +17,9 @@ class ControlPointAircraftInventory: def __init__(self, control_point: ControlPoint) -> None: self.control_point = control_point - self.inventory: Dict[FlyingType, int] = defaultdict(int) + self.inventory: Dict[Type[FlyingType], int] = defaultdict(int) - def add_aircraft(self, aircraft: FlyingType, count: int) -> None: + def add_aircraft(self, aircraft: Type[FlyingType], count: int) -> None: """Adds aircraft to the inventory. Args: @@ -28,7 +28,7 @@ class ControlPointAircraftInventory: """ self.inventory[aircraft] += count - def remove_aircraft(self, aircraft: FlyingType, count: int) -> None: + def remove_aircraft(self, aircraft: Type[FlyingType], count: int) -> None: """Removes aircraft from the inventory. Args: @@ -47,7 +47,7 @@ class ControlPointAircraftInventory: ) self.inventory[aircraft] -= count - def available(self, aircraft: FlyingType) -> int: + def available(self, aircraft: Type[FlyingType]) -> int: """Returns the number of available aircraft of the given type. Args: diff --git a/game/theater/base.py b/game/theater/base.py index 40f604fc..ba8a72f8 100644 --- a/game/theater/base.py +++ b/game/theater/base.py @@ -22,7 +22,7 @@ BASE_MIN_STRENGTH = 0 class Base: def __init__(self): - self.aircraft: Dict[FlyingType, int] = {} + self.aircraft: Dict[Type[FlyingType], int] = {} self.armor: Dict[VehicleType, int] = {} self.aa: Dict[AirDefence, int] = {} self.commision_points: Dict[Type, float] = {} diff --git a/gen/flights/flight.py b/gen/flights/flight.py index 56d9d04a..276a6396 100644 --- a/gen/flights/flight.py +++ b/gen/flights/flight.py @@ -2,7 +2,7 @@ from __future__ import annotations from datetime import timedelta from enum import Enum -from typing import Dict, List, Optional, TYPE_CHECKING +from typing import Dict, List, Optional, TYPE_CHECKING, Type from dcs.mapping import Point from dcs.point import MovingPoint, PointAction @@ -133,8 +133,8 @@ class FlightWaypoint: class Flight: - def __init__(self, package: Package, unit_type: FlyingType, count: int, - flight_type: FlightType, start_type: str, + def __init__(self, package: Package, unit_type: Type[FlyingType], + count: int, flight_type: FlightType, start_type: str, departure: ControlPoint, arrival: ControlPoint, divert: Optional[ControlPoint]) -> None: self.package = package diff --git a/qt_ui/windows/basemenu/QRecruitBehaviour.py b/qt_ui/windows/basemenu/QRecruitBehaviour.py index 8e0e77d3..7f462c57 100644 --- a/qt_ui/windows/basemenu/QRecruitBehaviour.py +++ b/qt_ui/windows/basemenu/QRecruitBehaviour.py @@ -1,4 +1,5 @@ import logging +from typing import Type from PySide2.QtWidgets import ( QGroupBox, @@ -106,7 +107,7 @@ class QRecruitBehaviour: return row + 1 - def _update_count_label(self, unit_type: UnitType): + def _update_count_label(self, unit_type: Type[UnitType]): self.bought_amount_labels[unit_type].setText("{}".format( unit_type in self.pending_deliveries.units and "{}".format(self.pending_deliveries.units[unit_type]) or "0" @@ -125,7 +126,7 @@ class QRecruitBehaviour: child.setText( QRecruitBehaviour.BUDGET_FORMAT.format(self.budget)) - def buy(self, unit_type): + def buy(self, unit_type: Type[UnitType]): price = db.PRICES[unit_type] if self.budget >= price: self.pending_deliveries.deliver({unit_type: 1})