From a9fcfe60f4f7befd5e2ec1667b021ae897f49c1f Mon Sep 17 00:00:00 2001 From: Dan Albert Date: Fri, 20 Nov 2020 15:36:19 -0800 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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)