Initial attempt

This commit is contained in:
Raffson
2024-02-26 00:00:27 +01:00
parent 9e1a642eb2
commit 87b630f8d5
16 changed files with 172 additions and 17 deletions

View File

@@ -19,6 +19,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 .campaigncarrierconfig import CampaignCarrierConfig
from .campaigngroundconfig import TgoConfig
from .mizcampaignloader import MizCampaignLoader
@@ -140,6 +141,13 @@ class Campaign:
return CampaignAirWingConfig({})
return CampaignAirWingConfig.from_campaign_data(squadron_data, theater)
def load_carrier_config(self) -> CampaignCarrierConfig:
try:
carrier_data = self.data["carriers"]
except KeyError:
return CampaignCarrierConfig({})
return CampaignCarrierConfig.from_campaign_data(carrier_data)
def load_ground_forces_config(self) -> TgoConfig:
ground_forces = self.data.get("ground_forces", {})
if not ground_forces:

View File

@@ -0,0 +1,42 @@
from __future__ import annotations
import logging
from collections import defaultdict
from dataclasses import dataclass
from typing import Any, TYPE_CHECKING
from game.dcs.shipunittype import ShipUnitType
if TYPE_CHECKING:
pass
@dataclass(frozen=True)
class CarrierConfig:
preferred_name: str
preferred_type: ShipUnitType
@classmethod
def from_data(cls, data: dict[str, Any]) -> CarrierConfig:
return CarrierConfig(
str(data["preferred_name"]), ShipUnitType.named(data["preferred_type"])
)
@dataclass(frozen=True)
class CampaignCarrierConfig:
by_original_name: dict[str, CarrierConfig]
@classmethod
def from_campaign_data(
cls, data: dict[str, Any]
) -> CampaignCarrierConfig:
by_original_name: dict[str, CarrierConfig] = defaultdict()
for original_name, carrier_config_data in data.items():
try:
carrier_config = CarrierConfig.from_data(carrier_config_data)
by_original_name[original_name] = carrier_config
except KeyError:
logging.warning(f"Skipping invalid carrier config for '{original_name}'")
return CampaignCarrierConfig(by_original_name)

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
import itertools
import logging
from collections import defaultdict
from dataclasses import dataclass, field
from functools import cached_property
from typing import Optional, Dict, Type, List, Any, Iterator, TYPE_CHECKING, Set
@@ -93,8 +94,8 @@ class Faction:
# Required mods or asset packs
requirements: Dict[str, str] = field(default_factory=dict)
# Possible carrier names
carrier_names: Set[str] = field(default_factory=set)
# Possible aircraft carrier units
carriers: Dict[ShipUnitType, Set[str]] = field(default_factory=dict)
# Possible helicopter carrier names
helicopter_carrier_names: Set[str] = field(default_factory=set)
@@ -241,8 +242,29 @@ class Faction:
faction.requirements = json.get("requirements", {})
faction.carrier_names = json.get("carrier_names", [])
faction.helicopter_carrier_names = json.get("helicopter_carrier_names", [])
# First try to load the carriers in the new format which
# specifies different names for different carrier types
loaded_carriers = load_carriers(json)
carriers: List[ShipUnitType] = [
unit
for unit in faction.naval_units
if unit.unit_class
in [
UnitClass.AIRCRAFT_CARRIER,
UnitClass.HELICOPTER_CARRIER,
]
]
carrier_names = json.get("carrier_names", [])
for c in carriers:
if c.variant_id not in loaded_carriers:
if c.unit_class == UnitClass.AIRCRAFT_CARRIER:
loaded_carriers[c] = carrier_names
elif c.unit_class == UnitClass.HELICOPTER_CARRIER:
loaded_carriers[c] = faction.helicopter_carrier_names
faction.carriers = loaded_carriers
faction.naval_units.union(faction.carriers.keys())
faction.has_jtac = json.get("has_jtac", False)
jtac_name = json.get("jtac_unit", None)
@@ -592,3 +614,14 @@ def load_all_ships(data: list[str]) -> List[Type[ShipType]]:
if item is not None:
items.append(item)
return items
def load_carriers(json: Dict[str, Any]) -> Dict[ShipUnitType, Set[str]]:
# Load carriers
items: Dict[ShipUnitType, Set[str]] = defaultdict(Set[str])
carriers = json.get("carriers", {})
for carrier_shiptype, shipnames in carriers.items():
shiptype = ShipUnitType.named(carrier_shiptype)
if shiptype is not None:
items[shiptype] = shipnames
return items

View File

@@ -201,8 +201,6 @@ class Migrator:
c.faction.air_defense_units = set(c.faction.air_defense_units)
if isinstance(c.faction.missiles, list):
c.faction.missiles = set(c.faction.missiles)
if isinstance(c.faction.carrier_names, list):
c.faction.carrier_names = set(c.faction.carrier_names)
if isinstance(c.faction.helicopter_carrier_names, list):
c.faction.helicopter_carrier_names = set(
c.faction.helicopter_carrier_names

View File

@@ -12,7 +12,7 @@ from game import Game
from game.factions.faction import Faction
from game.naming import namegen
from game.scenery_group import SceneryGroup
from game.theater import PointWithHeading, PresetLocation
from game.theater import PointWithHeading, PresetLocation, NavalControlPoint
from game.theater.theatergroundobject import (
BuildingGroundObject,
IadsBuildingGroundObject,
@@ -30,8 +30,10 @@ from .theatergroup import IadsGroundGroup, IadsRole, SceneryUnit, TheaterGroup
from ..armedforces.armedforces import ArmedForces
from ..armedforces.forcegroup import ForceGroup
from ..campaignloader.campaignairwingconfig import CampaignAirWingConfig
from ..campaignloader.campaigncarrierconfig import CampaignCarrierConfig
from ..campaignloader.campaigngroundconfig import TgoConfig
from ..data.groups import GroupTask
from ..dcs.shipunittype import ShipUnitType
from ..profiling import logged_duration
from ..settings import Settings
@@ -49,6 +51,7 @@ class GeneratorSettings:
no_player_navy: bool
no_enemy_navy: bool
tgo_config: TgoConfig
carrier_config: CampaignCarrierConfig
squadrons_start_full: bool
@@ -125,7 +128,7 @@ class GameGenerator:
def should_remove_carrier(self, player: bool) -> bool:
faction = self.player if player else self.enemy
return self.generator_settings.no_carrier or not faction.carrier_names
return self.generator_settings.no_carrier or not faction.carriers
def should_remove_lha(self, player: bool) -> bool:
faction = self.player if player else self.enemy
@@ -221,14 +224,49 @@ class GenericCarrierGroundObjectGenerator(ControlPointGroundObjectGenerator):
carrier = next(self.control_point.ground_objects[-1].units)
carrier.name = carrier_name
def apply_carrier_config(self) -> None:
assert isinstance(self.control_point, NavalControlPoint)
# If the campaign designer has specified a preferred name, use that
# Note that the preferred name needs to exist in the faction, so we
# don't end up with Kuznetsov carriers called CV-59 Forrestal
preferred_name = None
preferred_type = None
carrier_map = self.generator_settings.carrier_config.by_original_name
if ccfg := carrier_map.get(self.control_point.name):
preferred_name = ccfg.preferred_name
preferred_type = ccfg.preferred_type
carrier_unit = self.control_point.ground_objects[0].groups[0].units[0]
carrier_type = preferred_type if preferred_type else carrier_unit.unit_type
assert isinstance(carrier_type, ShipUnitType)
if preferred_type and self.faction.has_access_to_dcs_type(preferred_type.dcs_unit_type):
carrier_unit.type = carrier_type.dcs_unit_type
if (
preferred_name
):
self.control_point.name = preferred_name
else:
# Otherwise pick randomly from the names specified for that particular carrier type
carrier_names = self.faction.carriers.get(carrier_type)
if carrier_names:
self.control_point.name = random.choice(list(carrier_names))
else:
self.control_point.name = carrier_type.display_name
# Prevents duplicate carrier or LHA names in campaigns with more that one of either.
for carrier_type_key in self.faction.carriers:
for carrier_name in self.faction.carriers[carrier_type_key]:
if carrier_name == self.control_point.name:
self.faction.carriers[carrier_type_key].remove(
self.control_point.name
)
class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
def generate(self) -> bool:
if not super().generate():
return False
carrier_names = self.faction.carrier_names
if not carrier_names:
carriers = self.faction.carriers
if not carriers:
logging.info(
f"Skipping generation of {self.control_point.name} because "
f"{self.faction_name} has no carriers"
@@ -239,6 +277,7 @@ class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
if not unit_group:
logging.error(f"{self.faction_name} has no access to AircraftCarrier")
return False
self.generate_ground_object_from_group(
unit_group,
PresetLocation(
@@ -248,7 +287,7 @@ class CarrierGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
),
GroupTask.AIRCRAFT_CARRIER,
)
self.update_carrier_name(random.choice(list(carrier_names)))
self.apply_carrier_config()
return True
@@ -280,7 +319,7 @@ class LhaGroundObjectGenerator(GenericCarrierGroundObjectGenerator):
),
GroupTask.HELICOPTER_CARRIER,
)
self.update_carrier_name(random.choice(list(lha_names)))
self.apply_carrier_config()
return True

View File

@@ -317,6 +317,7 @@ def create_game(
no_player_navy=False,
no_enemy_navy=False,
tgo_config=campaign.load_ground_forces_config(),
carrier_config=campaign.load_carrier_config(),
),
ModSettings(
a4_skyhawk=False,

View File

@@ -84,6 +84,7 @@ class NewGameWizard(QtWidgets.QWizard):
no_player_navy=self.field("no_player_navy"),
no_enemy_navy=self.field("no_enemy_navy"),
tgo_config=campaign.load_ground_forces_config(),
carrier_config=campaign.load_carrier_config(),
squadrons_start_full=self.field("squadrons_start_full"),
)
mod_settings = ModSettings(

View File

@@ -81,6 +81,14 @@ settings:
#default="Full"
carriers:
Naval-1:
preferred_name: CVN-74
preferred_type: CVN-74 John C. Stennis
Naval-2:
preferred_name: The Harry
preferred_type: CVN-75 Harry S. Truman
#Squadrons and order of battle settings
squadrons:
@@ -156,7 +164,7 @@ squadrons:
aircraft:
- CH-53E
size: 2
#CV-59 Forrestal
#CV-59 Forrestal
Naval-2:
- primary: AEW&C
aircraft:

View File

@@ -46,14 +46,14 @@
],
"preset_groups": [
"Hawk",
"Patriot"
"Patriot",
"Carrier Strike Group 8"
],
"naval_units": [
"FFG Oliver Hazard Perry",
"DDG Arleigh Burke IIa",
"CG Ticonderoga",
"LHA-1 Tarawa",
"CVN-74 John C. Stennis"
"LHA-1 Tarawa"
],
"missiles": [],
"air_defense_units": [

View File

@@ -2,7 +2,11 @@ name: Carrier Strike Group 8
tasks:
- Navy
units:
- CVN-71 Theodore Roosevelt
- CVN-72 Abraham Lincoln
- CVN-73 George Washington
- CVN-74 John C. Stennis
- CVN-75 Harry S. Truman
- DDG Arleigh Burke IIa
- CG Ticonderoga
layouts:

View File

@@ -0,0 +1,4 @@
class: AircraftCarrier
price: 0
variants:
CVN-71 Theodore Roosevelt: null

View File

@@ -0,0 +1,4 @@
class: AircraftCarrier
price: 0
variants:
CVN-72 Abraham Lincoln: null

View File

@@ -0,0 +1,4 @@
class: AircraftCarrier
price: 0
variants:
CVN-73 George Washington: null

View File

@@ -0,0 +1,4 @@
class: AircraftCarrier
price: 0
variants:
CVN-75 Harry S. Truman: null

View File

@@ -0,0 +1,4 @@
class: AircraftCarrier
price: 0
variants:
CV 1143.5 Admiral Kuznetsov(2017): null

View File

@@ -92,12 +92,13 @@ class TestFactionLoader(unittest.TestCase):
self.assertIn(Infantry.Soldier_M249, faction.infantry_units)
self.assertIn(Stennis.name, faction.naval_units)
self.assertIn(Stennis, faction.carriers.keys())
self.assertIn(LHA_Tarawa.name, faction.naval_units)
self.assertIn("mod", faction.requirements.keys())
self.assertIn("Some mod is required", faction.requirements.values())
self.assertEqual(4, len(faction.carrier_names))
self.assertEqual(4, len(faction.carriers))
self.assertEqual(5, len(faction.helicopter_carrier_names))
@pytest.mark.skip(reason="Faction unit names in the json files are outdated")