diff --git a/game/campaignloader/mizcampaignloader.py b/game/campaignloader/mizcampaignloader.py index d217250f..29dec2ff 100644 --- a/game/campaignloader/mizcampaignloader.py +++ b/game/campaignloader/mizcampaignloader.py @@ -105,8 +105,7 @@ class MizCampaignLoader: @staticmethod def control_point_from_airport(airport: Airport) -> ControlPoint: - cp = Airfield(airport) - cp.captured = airport.is_blue() + cp = Airfield(airport, starts_blue=airport.is_blue()) # Use the unlimited aircraft option to determine if an airfield should # be owned by the player when the campaign is "inverted". @@ -249,30 +248,38 @@ class MizCampaignLoader: 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 + next(self.control_point_id), + str(group.name), + group.position, + starts_blue=blue, ) - control_point.captured = blue control_point.captured_invert = group.late_activation control_points[control_point.id] = control_point for ship in self.carriers(blue): control_point = Carrier( - ship.name, ship.position, next(self.control_point_id) + ship.name, + ship.position, + next(self.control_point_id), + starts_blue=blue, ) - control_point.captured = blue control_point.captured_invert = ship.late_activation control_points[control_point.id] = control_point for ship in self.lhas(blue): control_point = Lha( - ship.name, ship.position, next(self.control_point_id) + ship.name, + ship.position, + next(self.control_point_id), + starts_blue=blue, ) - control_point.captured = blue control_point.captured_invert = ship.late_activation control_points[control_point.id] = control_point for fob in self.fobs(blue): control_point = Fob( - str(fob.name), fob.position, next(self.control_point_id) + str(fob.name), + fob.position, + next(self.control_point_id), + starts_blue=blue, ) - control_point.captured = blue control_point.captured_invert = fob.late_activation control_points[control_point.id] = control_point diff --git a/game/commander/objectivefinder.py b/game/commander/objectivefinder.py index e684b98e..21fdd6f5 100644 --- a/game/commander/objectivefinder.py +++ b/game/commander/objectivefinder.py @@ -157,10 +157,7 @@ class ObjectiveFinder: for control_point in self.enemy_control_points(): if not isinstance(control_point, Airfield): continue - if ( - control_point.allocated_aircraft(self.game).total_present - >= min_aircraft - ): + if control_point.allocated_aircraft().total_present >= min_aircraft: airfields.append(control_point) return self._targets_by_range(airfields) diff --git a/game/game.py b/game/game.py index 2da28c2a..6d059692 100644 --- a/game/game.py +++ b/game/game.py @@ -123,6 +123,9 @@ class Game: self.blue.set_opponent(self.red) self.red.set_opponent(self.blue) + for control_point in self.theater.controlpoints: + control_point.finish_init(self) + self.blue.configure_default_air_wing(air_wing_config) self.red.configure_default_air_wing(air_wing_config) diff --git a/game/procurement.py b/game/procurement.py index 46048170..094e78c7 100644 --- a/game/procurement.py +++ b/game/procurement.py @@ -74,7 +74,7 @@ class ProcurementAi: self.game.coalition_for(self.is_player).transfers ) armor_investment += cp_ground_units.total_value - cp_aircraft = cp.allocated_aircraft(self.game) + cp_aircraft = cp.allocated_aircraft() aircraft_investment += cp_aircraft.total_value total_investment = aircraft_investment + armor_investment @@ -252,7 +252,7 @@ class ProcurementAi: for cp in distance_cache.operational_airfields: if not cp.is_friendly(self.is_player): continue - if cp.unclaimed_parking(self.game) < request.number: + if cp.unclaimed_parking() < request.number: continue if self.threat_zones.threatened(cp.position): threatened.append(cp) diff --git a/game/purchaseadapter.py b/game/purchaseadapter.py index 6376f15c..7da8e4e9 100644 --- a/game/purchaseadapter.py +++ b/game/purchaseadapter.py @@ -92,12 +92,9 @@ class PurchaseAdapter(Generic[ItemType]): class AircraftPurchaseAdapter(PurchaseAdapter[Squadron]): - def __init__( - self, control_point: ControlPoint, coalition: Coalition, game: Game - ) -> None: - super().__init__(coalition) + def __init__(self, control_point: ControlPoint) -> None: + super().__init__(control_point.coalition) self.control_point = control_point - self.game = game def pending_delivery_quantity(self, item: Squadron) -> int: return item.pending_deliveries @@ -106,10 +103,7 @@ class AircraftPurchaseAdapter(PurchaseAdapter[Squadron]): return item.owned_aircraft def can_buy(self, item: Squadron) -> bool: - return ( - super().can_buy(item) - and self.control_point.unclaimed_parking(self.game) > 0 - ) + return super().can_buy(item) and self.control_point.unclaimed_parking() > 0 def can_sell(self, item: Squadron) -> bool: return item.untasked_aircraft > 0 diff --git a/game/theater/controlpoint.py b/game/theater/controlpoint.py index 8fe4dbb5..ef7aa594 100644 --- a/game/theater/controlpoint.py +++ b/game/theater/controlpoint.py @@ -55,6 +55,7 @@ if TYPE_CHECKING: from game import Game from gen.flights.flight import FlightType from game.squadrons.squadron import Squadron + from ..coalition import Coalition from ..transfers import PendingTransfers FREE_FRONTLINE_UNIT_SUPPLY: int = 15 @@ -280,7 +281,6 @@ class ControlPoint(MissionTarget, ABC): position = None # type: Point name = None # type: str - captured = False has_frontline = True alt = 0 @@ -294,6 +294,7 @@ class ControlPoint(MissionTarget, ABC): name: str, position: Point, at: db.StartingPosition, + starts_blue: bool, has_frontline: bool = True, cptype: ControlPointType = ControlPointType.AIRBASE, ) -> None: @@ -302,11 +303,12 @@ class ControlPoint(MissionTarget, ABC): self.id = cp_id self.full_name = name self.at = at + self.starts_blue = starts_blue self.connected_objectives: List[TheaterGroundObject[Any]] = [] self.preset_locations = PresetLocations() self.helipads: List[PointWithHeading] = [] - self.captured = False + self._coalition: Optional[Coalition] = None self.captured_invert = False # TODO: Should be Airbase specific. self.has_frontline = has_frontline @@ -328,6 +330,20 @@ class ControlPoint(MissionTarget, ABC): def __repr__(self) -> str: return f"<{self.__class__}: {self.name}>" + @property + def coalition(self) -> Coalition: + if self._coalition is None: + raise RuntimeError("ControlPoint not fully initialized: coalition not set") + return self._coalition + + def finish_init(self, game: Game) -> None: + assert self._coalition is None + self._coalition = game.coalition_for(self.starts_blue) + + @property + def captured(self) -> bool: + return self.coalition.player + @property def ground_objects(self) -> List[TheaterGroundObject[Any]]: return list(self.connected_objectives) @@ -561,7 +577,7 @@ class ControlPoint(MissionTarget, ABC): ) def aircraft_retreat_destination( - self, game: Game, airframe: AircraftType + self, airframe: AircraftType ) -> Optional[ControlPoint]: closest = ObjectiveDistanceCache.get_closest_airfields(self) # TODO: Should be airframe dependent. @@ -574,7 +590,7 @@ class ControlPoint(MissionTarget, ABC): continue if airbase.captured != self.captured: continue - if airbase.unclaimed_parking(game) > 0: + if airbase.unclaimed_parking() > 0: return airbase return None @@ -594,27 +610,23 @@ class ControlPoint(MissionTarget, ABC): # TODO: Should be Airbase specific. def capture(self, game: Game, for_player: bool) -> None: - coalition = game.coalition_for(for_player) - self.ground_unit_orders.refund_all(coalition) + new_coalition = game.coalition_for(for_player) + self.ground_unit_orders.refund_all(self.coalition) for squadron in self.squadrons: squadron.refund_orders() self.retreat_ground_units(game) self.retreat_air_units(game) self.depopulate_uncapturable_tgos() - if for_player: - self.captured = True - else: - self.captured = False - + self._coalition = new_coalition self.base.set_strength_to_minimum() @abstractmethod def can_operate(self, aircraft: AircraftType) -> bool: ... - def unclaimed_parking(self, game: Game) -> int: - return self.total_aircraft_parking - self.allocated_aircraft(game).total + def unclaimed_parking(self) -> int: + return self.total_aircraft_parking - self.allocated_aircraft().total @abstractmethod def active_runway( @@ -666,7 +678,7 @@ class ControlPoint(MissionTarget, ABC): u.position.x = u.position.x + delta.x u.position.y = u.position.y + delta.y - def allocated_aircraft(self, _game: Game) -> AircraftAllocations: + def allocated_aircraft(self) -> AircraftAllocations: present: dict[AircraftType, int] = defaultdict(int) on_order: dict[AircraftType, int] = defaultdict(int) for squadron in self.squadrons: @@ -771,13 +783,14 @@ class ControlPoint(MissionTarget, ABC): class Airfield(ControlPoint): - def __init__(self, airport: Airport, has_frontline: bool = True) -> None: + def __init__(self, airport: Airport, starts_blue: bool) -> None: super().__init__( airport.id, airport.name, airport.position, airport, - has_frontline, + starts_blue, + has_frontline=True, cptype=ControlPointType.AIRBASE, ) self.airport = airport @@ -941,12 +954,13 @@ class NavalControlPoint(ControlPoint, ABC): class Carrier(NavalControlPoint): - def __init__(self, name: str, at: Point, cp_id: int): + def __init__(self, name: str, at: Point, cp_id: int, starts_blue: bool): super().__init__( cp_id, name, at, at, + starts_blue, has_frontline=False, cptype=ControlPointType.AIRCRAFT_CARRIER_GROUP, ) @@ -981,12 +995,13 @@ class Carrier(NavalControlPoint): class Lha(NavalControlPoint): - def __init__(self, name: str, at: Point, cp_id: int): + def __init__(self, name: str, at: Point, cp_id: int, starts_blue: bool): super().__init__( cp_id, name, at, at, + starts_blue, has_frontline=False, cptype=ControlPointType.LHA_GROUP, ) @@ -1014,12 +1029,13 @@ class OffMapSpawn(ControlPoint): def runway_is_operational(self) -> bool: return True - def __init__(self, cp_id: int, name: str, position: Point): + def __init__(self, cp_id: int, name: str, position: Point, starts_blue: bool): super().__init__( cp_id, name, position, - at=position, + position, + starts_blue, has_frontline=False, cptype=ControlPointType.OFF_MAP, ) @@ -1067,12 +1083,13 @@ class OffMapSpawn(ControlPoint): class Fob(ControlPoint): - def __init__(self, name: str, at: Point, cp_id: int): + def __init__(self, name: str, at: Point, cp_id: int, starts_blue: bool): super().__init__( cp_id, name, at, at, + starts_blue, has_frontline=True, cptype=ControlPointType.FOB, ) diff --git a/game/theater/start_generator.py b/game/theater/start_generator.py index 1281490d..7ccc3267 100644 --- a/game/theater/start_generator.py +++ b/game/theater/start_generator.py @@ -8,8 +8,6 @@ from datetime import datetime from typing import Any, Dict, Iterable, List, Set from dcs.mapping import Point -from dcs.task import CAP, CAS, PinpointStrike -from dcs.vehicles import AirDefence from game import Game from game.factions.faction import Faction @@ -30,7 +28,6 @@ from game.theater.theatergroundobject import ( ) from game.utils import Heading from game.version import VERSION -from gen.naming import namegen from gen.coastal.coastal_group_generator import generate_coastal_group from gen.defenses.armor_group_generator import generate_armor_group from gen.fleet.ship_group_generator import ( @@ -39,6 +36,7 @@ from gen.fleet.ship_group_generator import ( generate_ship_group, ) from gen.missiles.missiles_group_generator import generate_missile_group +from gen.naming import namegen from gen.sam.airdefensegroupgenerator import AirDefenseRange from gen.sam.ewr_group_generator import generate_ewr_group from gen.sam.sam_group_generator import generate_anti_air_group @@ -61,7 +59,6 @@ class GeneratorSettings: start_date: datetime player_budget: int enemy_budget: int - midgame: bool inverted: bool no_carrier: bool no_lha: bool @@ -121,11 +118,6 @@ class GameGenerator: def prepare_theater(self) -> None: to_remove: List[ControlPoint] = [] - # Auto-capture half the bases if midgame. - if self.generator_settings.midgame: - control_points = self.theater.controlpoints - for control_point in control_points[: len(control_points) // 2]: - control_point.captured = True # Remove carrier and lha, invert situation if needed for cp in self.theater.controlpoints: @@ -135,21 +127,12 @@ class GameGenerator: to_remove.append(cp) if self.generator_settings.inverted: - cp.captured = cp.captured_invert + cp.starts_blue = cp.captured_invert # do remove for cp in to_remove: self.theater.controlpoints.remove(cp) - # TODO: Fix this. This captures all bases for blue. - # reapply midgame inverted if needed - if self.generator_settings.midgame and self.generator_settings.inverted: - for i, cp in enumerate(reversed(self.theater.controlpoints)): - if i > len(self.theater.controlpoints): - break - else: - cp.captured = True - class ControlPointGroundObjectGenerator: def __init__( diff --git a/game/transfers.py b/game/transfers.py index 3a8d62f6..d7db80af 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -752,7 +752,7 @@ class PendingTransfers: ) def order_airlift_assets_at(self, control_point: ControlPoint) -> None: - unclaimed_parking = control_point.unclaimed_parking(self.game) + unclaimed_parking = control_point.unclaimed_parking() # Buy a maximum of unclaimed_parking only to prevent that aircraft procurement # take place at another base gap = min( diff --git a/qt_ui/main.py b/qt_ui/main.py index c4d6e4e0..ff3c6e5a 100644 --- a/qt_ui/main.py +++ b/qt_ui/main.py @@ -252,7 +252,6 @@ def create_game( start_date=start_date, player_budget=DEFAULT_BUDGET, enemy_budget=DEFAULT_BUDGET, - midgame=False, inverted=inverted, no_carrier=False, no_lha=False, diff --git a/qt_ui/windows/basemenu/QBaseMenu2.py b/qt_ui/windows/basemenu/QBaseMenu2.py index 7c333e1b..5f1d925e 100644 --- a/qt_ui/windows/basemenu/QBaseMenu2.py +++ b/qt_ui/windows/basemenu/QBaseMenu2.py @@ -190,7 +190,7 @@ class QBaseMenu2(QDialog): self.repair_button.setDisabled(True) def update_intel_summary(self) -> None: - aircraft = self.cp.allocated_aircraft(self.game_model.game).total_present + aircraft = self.cp.allocated_aircraft().total_present parking = self.cp.total_aircraft_parking ground_unit_limit = self.cp.frontline_unit_count_limit deployable_unit_info = "" diff --git a/qt_ui/windows/basemenu/QBaseMenuTabs.py b/qt_ui/windows/basemenu/QBaseMenuTabs.py index 38640f24..3ec0e403 100644 --- a/qt_ui/windows/basemenu/QBaseMenuTabs.py +++ b/qt_ui/windows/basemenu/QBaseMenuTabs.py @@ -1,6 +1,6 @@ from PySide2.QtWidgets import QTabWidget -from game.theater import ControlPoint, OffMapSpawn, Fob +from game.theater import ControlPoint, Fob from qt_ui.models import GameModel from qt_ui.windows.basemenu.DepartingConvoysMenu import DepartingConvoysMenu from qt_ui.windows.basemenu.airfield.QAirfieldCommand import QAirfieldCommand @@ -13,7 +13,7 @@ class QBaseMenuTabs(QTabWidget): super(QBaseMenuTabs, self).__init__() if not cp.captured: - self.intel = QIntelInfo(cp, game_model.game) + self.intel = QIntelInfo(cp) self.addTab(self.intel, "Intel") self.departing_convoys = DepartingConvoysMenu(cp, game_model) diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 99b18d5e..4d157dee 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -21,12 +21,7 @@ from game.purchaseadapter import AircraftPurchaseAdapter class QAircraftRecruitmentMenu(UnitTransactionFrame[Squadron]): def __init__(self, cp: ControlPoint, game_model: GameModel) -> None: - super().__init__( - game_model, - AircraftPurchaseAdapter( - cp, game_model.game.coalition_for(cp.captured), game_model.game - ), - ) + super().__init__(game_model, AircraftPurchaseAdapter(cp)) self.cp = cp self.game_model = game_model self.purchase_groups = {} @@ -96,7 +91,7 @@ class QHangarStatus(QHBoxLayout): self.setAlignment(Qt.AlignLeft) def update_label(self) -> None: - next_turn = self.control_point.allocated_aircraft(self.game_model.game) + next_turn = self.control_point.allocated_aircraft() max_amount = self.control_point.total_aircraft_parking components = [f"{next_turn.total_present} present"] diff --git a/qt_ui/windows/basemenu/intel/QIntelInfo.py b/qt_ui/windows/basemenu/intel/QIntelInfo.py index d73682db..91b13efb 100644 --- a/qt_ui/windows/basemenu/intel/QIntelInfo.py +++ b/qt_ui/windows/basemenu/intel/QIntelInfo.py @@ -11,22 +11,20 @@ from PySide2.QtWidgets import ( QWidget, ) -from game import Game from game.theater import ControlPoint class QIntelInfo(QFrame): - def __init__(self, cp: ControlPoint, game: Game): + def __init__(self, cp: ControlPoint): super(QIntelInfo, self).__init__() self.cp = cp - self.game = game layout = QVBoxLayout() scroll_content = QWidget() intel_layout = QVBoxLayout() units_by_task: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int)) - for unit_type, count in self.cp.allocated_aircraft(game).present.items(): + for unit_type, count in self.cp.allocated_aircraft().present.items(): if count: task_type = unit_type.dcs_unit_type.task_default.name units_by_task[task_type][unit_type.name] += count diff --git a/qt_ui/windows/intel.py b/qt_ui/windows/intel.py index 288b87fe..8ae11087 100644 --- a/qt_ui/windows/intel.py +++ b/qt_ui/windows/intel.py @@ -77,7 +77,7 @@ class AircraftIntelLayout(IntelTableLayout): total = 0 for control_point in game.theater.control_points_for(player): - allocation = control_point.allocated_aircraft(game) + allocation = control_point.allocated_aircraft() base_total = allocation.total_present total += base_total if not base_total: diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py index 0b1f4539..c362d0c6 100644 --- a/qt_ui/windows/newgame/QNewGameWizard.py +++ b/qt_ui/windows/newgame/QNewGameWizard.py @@ -94,7 +94,6 @@ class NewGameWizard(QtWidgets.QWizard): enemy_budget=int(self.field("enemy_starting_money")), # QSlider forces integers, so we use 1 to 50 and divide by 10 to # give 0.1 to 5.0. - midgame=False, inverted=self.field("invertMap"), no_carrier=self.field("no_carrier"), no_lha=self.field("no_lha"),