Store the owning coalition in ControlPoint.

This is needed fairly often, and we have a lot of Game being passed
around to ControlPoint methods specifically to support this. Just store
the owning Coalition directly in the ControlPoint to clean up. I haven't
cleaned up *every* API here, but did that aircraft allocations as an
example.
This commit is contained in:
Dan Albert 2021-08-28 16:39:20 -07:00
parent 74577752e0
commit 4715773bba
15 changed files with 75 additions and 83 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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