Add campaign support for ferry-only bases.

Fixes https://github.com/dcs-liberation/dcs_liberation/issues/3170.
This commit is contained in:
Dan Albert
2023-09-21 21:08:42 -07:00
parent e43874e553
commit 2344fc0b5c
12 changed files with 256 additions and 45 deletions

View File

@@ -17,6 +17,7 @@ from game.theater.iadsnetwork.iadsnetwork import IadsNetwork
from game.theater.theaterloader import TheaterLoader
from game.version import CAMPAIGN_FORMAT_VERSION
from .campaignairwingconfig import CampaignAirWingConfig
from .controlpointconfig import ControlPointConfig
from .factionrecommendation import FactionRecommendation
from .mizcampaignloader import MizCampaignLoader
@@ -123,7 +124,15 @@ class Campaign:
) from ex
with logged_duration("Importing miz data"):
MizCampaignLoader(self.path.parent / miz, t).populate_theater()
MizCampaignLoader(
self.path.parent / miz,
t,
dict(
ControlPointConfig.iter_from_data(
self.data.get("control_points", {})
)
),
).populate_theater()
# TODO: Move into MizCampaignLoader so this doesn't have unknown initialization
# in ConflictTheater.

View File

@@ -0,0 +1,90 @@
from dcs import Point
from dcs.terrain import Airport
from game.campaignloader.controlpointconfig import ControlPointConfig
from game.theater import (
Airfield,
Carrier,
ConflictTheater,
ControlPoint,
Fob,
Lha,
OffMapSpawn,
)
class ControlPointBuilder:
def __init__(
self, theater: ConflictTheater, configs: dict[str | int, ControlPointConfig]
) -> None:
self.theater = theater
self.config = configs
def create_airfield(self, airport: Airport) -> Airfield:
cp = Airfield(airport, self.theater, 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".
cp.captured_invert = airport.unlimited_aircrafts
self._apply_config(airport.id, cp)
return cp
def create_fob(
self,
name: str,
position: Point,
theater: ConflictTheater,
starts_blue: bool,
captured_invert: bool,
) -> Fob:
cp = Fob(name, position, theater, starts_blue)
cp.captured_invert = captured_invert
self._apply_config(name, cp)
return cp
def create_carrier(
self,
name: str,
position: Point,
theater: ConflictTheater,
starts_blue: bool,
captured_invert: bool,
) -> Carrier:
cp = Carrier(name, position, theater, starts_blue)
cp.captured_invert = captured_invert
self._apply_config(name, cp)
return cp
def create_lha(
self,
name: str,
position: Point,
theater: ConflictTheater,
starts_blue: bool,
captured_invert: bool,
) -> Lha:
cp = Lha(name, position, theater, starts_blue)
cp.captured_invert = captured_invert
self._apply_config(name, cp)
return cp
def create_off_map(
self,
name: str,
position: Point,
theater: ConflictTheater,
starts_blue: bool,
captured_invert: bool,
) -> OffMapSpawn:
cp = OffMapSpawn(name, position, theater, starts_blue)
cp.captured_invert = captured_invert
self._apply_config(name, cp)
return cp
def _apply_config(self, cp_id: str | int, control_point: ControlPoint) -> None:
config = self.config.get(cp_id)
if config is None:
return
control_point.ferry_only = config.ferry_only

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
from collections.abc import Iterator
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class ControlPointConfig:
ferry_only: bool
@staticmethod
def from_data(data: dict[str, Any]) -> ControlPointConfig:
return ControlPointConfig(ferry_only=data.get("ferry_only", False))
@staticmethod
def iter_from_data(
data: dict[str | int, Any]
) -> Iterator[tuple[str | int, ControlPointConfig]]:
for name_or_id, cp_data in data.items():
yield name_or_id, ControlPointConfig.from_data(cp_data)

View File

@@ -12,20 +12,14 @@ from dcs.country import Country
from dcs.planes import F_15C
from dcs.ships import HandyWind, LHA_Tarawa, Stennis, USS_Arleigh_Burke_IIa
from dcs.statics import Fortification, Warehouse
from dcs.terrain import Airport
from dcs.unitgroup import PlaneGroup, ShipGroup, StaticGroup, VehicleGroup
from dcs.vehicles import AirDefence, Armor, MissilesSS, Unarmed
from game.campaignloader.controlpointbuilder import ControlPointBuilder
from game.campaignloader.controlpointconfig import ControlPointConfig
from game.profiling import logged_duration
from game.scenery_group import SceneryGroup
from game.theater.controlpoint import (
Airfield,
Carrier,
ControlPoint,
Fob,
Lha,
OffMapSpawn,
)
from game.theater.controlpoint import ControlPoint
from game.theater.presetlocation import PresetLocation
if TYPE_CHECKING:
@@ -92,8 +86,14 @@ class MizCampaignLoader:
STRIKE_TARGET_UNIT_TYPE = Fortification.Tech_combine.id
def __init__(self, miz: Path, theater: ConflictTheater) -> None:
def __init__(
self,
miz: Path,
theater: ConflictTheater,
control_point_configs: dict[str | int, ControlPointConfig],
) -> None:
self.theater = theater
self.control_point_builder = ControlPointBuilder(theater, control_point_configs)
self.mission = Mission()
with logged_duration("Loading miz"):
self.mission.load_file(str(miz))
@@ -105,15 +105,6 @@ class MizCampaignLoader:
if self.mission.country(self.RED_COUNTRY.name) is None:
self.mission.coalition["red"].add_country(self.RED_COUNTRY)
def control_point_from_airport(self, airport: Airport) -> ControlPoint:
cp = Airfield(airport, self.theater, 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".
cp.captured_invert = airport.unlimited_aircrafts
return cp
def country(self, blue: bool) -> Country:
country = self.mission.country(
self.BLUE_COUNTRY.name if blue else self.RED_COUNTRY.name
@@ -240,36 +231,49 @@ class MizCampaignLoader:
@cached_property
def control_points(self) -> dict[UUID, ControlPoint]:
control_points = {}
control_points: dict[UUID, ControlPoint] = {}
control_point: ControlPoint
for airport in self.mission.terrain.airport_list():
if airport.is_blue() or airport.is_red():
control_point = self.control_point_from_airport(airport)
control_point = self.control_point_builder.create_airfield(airport)
control_points[control_point.id] = control_point
for blue in (False, True):
for group in self.off_map_spawns(blue):
control_point = OffMapSpawn(
str(group.name), group.position, self.theater, starts_blue=blue
control_point = self.control_point_builder.create_off_map(
str(group.name),
group.position,
self.theater,
starts_blue=blue,
captured_invert=group.late_activation,
)
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, self.theater, starts_blue=blue
control_point = self.control_point_builder.create_carrier(
ship.name,
ship.position,
self.theater,
starts_blue=blue,
captured_invert=ship.late_activation,
)
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, self.theater, starts_blue=blue
control_point = self.control_point_builder.create_lha(
ship.name,
ship.position,
self.theater,
starts_blue=blue,
captured_invert=ship.late_activation,
)
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, self.theater, starts_blue=blue
control_point = self.control_point_builder.create_fob(
str(fob.name),
fob.position,
self.theater,
starts_blue=blue,
captured_invert=fob.late_activation,
)
control_point.captured_invert = fob.late_activation
control_points[control_point.id] = control_point
return control_points

View File

@@ -53,6 +53,8 @@ class AirWing:
for control_point in airfield_cache.operational_airfields:
if control_point.captured != self.player:
continue
if control_point.ferry_only:
continue
capable_at_base = []
for squadron in control_point.squadrons:
if squadron.can_auto_assign_mission(location, task, size, this_turn):
@@ -91,6 +93,8 @@ class AirWing:
best_aircraft_for_task = AircraftType.priority_list_for_task(task)
for aircraft, squadrons in self.squadrons.items():
for squadron in squadrons:
if squadron.location.ferry_only:
continue
if squadron.untasked_aircraft and squadron.capable_of(task):
aircrafts.append(aircraft)
if aircraft not in best_aircraft_for_task:

View File

@@ -356,6 +356,7 @@ class ControlPoint(MissionTarget, SidcDescribable, ABC):
self.ground_unit_orders = GroundUnitOrders(self)
self.target_position: Optional[Point] = None
self.ferry_only = False
# Initialized late because ControlPoints are constructed before the game is.
self._front_line_db: Database[FrontLine] | None = None

View File

@@ -185,4 +185,7 @@ VERSION = _build_version_string()
#:
#: Version 10.10
#: * Support for Sinai.
#:
#: Version 10.11
#: * Support for ferry-only bases.
CAMPAIGN_FORMAT_VERSION = (10, 10)