diff --git a/game/ato/flight.py b/game/ato/flight.py index cb9437513..8b33ebb93 100644 --- a/game/ato/flight.py +++ b/game/ato/flight.py @@ -37,6 +37,7 @@ class Flight(SidcDescribable): def __init__( self, package: Package, + country: str, squadron: Squadron, count: int, flight_type: FlightType, @@ -48,6 +49,7 @@ def __init__( ) -> None: self.id = uuid.uuid4() self.package = package + self.country = country self.coalition = squadron.coalition self.squadron = squadron self.squadron.claim_inventory(count) diff --git a/game/coalition.py b/game/coalition.py index 9c3d45351..0617d796a 100644 --- a/game/coalition.py +++ b/game/coalition.py @@ -71,6 +71,10 @@ def coalition_id(self) -> int: return 2 return 1 + @property + def country_name(self) -> str: + return self.faction.country + @property def opponent(self) -> Coalition: assert self._opponent is not None diff --git a/game/commander/packagebuilder.py b/game/commander/packagebuilder.py index 9f5d56620..ed96901a8 100644 --- a/game/commander/packagebuilder.py +++ b/game/commander/packagebuilder.py @@ -26,11 +26,13 @@ def __init__( air_wing: AirWing, flight_db: Database[Flight], is_player: bool, + package_country: str, start_type: StartType, asap: bool, ) -> None: self.closest_airfields = closest_airfields self.is_player = is_player + self.package_country = package_country self.package = Package(location, flight_db, auto_asap=asap) self.air_wing = air_wing self.start_type = start_type @@ -62,6 +64,7 @@ def plan_flight(self, plan: ProposedFlight) -> bool: flight = Flight( self.package, + self.package_country, squadron, plan.num_aircraft, plan.task, diff --git a/game/commander/packagefulfiller.py b/game/commander/packagefulfiller.py index 40d609b74..30ddf4854 100644 --- a/game/commander/packagefulfiller.py +++ b/game/commander/packagefulfiller.py @@ -156,6 +156,7 @@ def plan_mission( self.air_wing, self.flight_db, self.is_player, + self.coalition.country_name, self.default_start_type, mission.asap, ) diff --git a/game/dcs/countries.py b/game/dcs/countries.py deleted file mode 100644 index 7fe6805d6..000000000 --- a/game/dcs/countries.py +++ /dev/null @@ -1,9 +0,0 @@ -from dcs.countries import country_dict -from dcs.country import Country - - -def country_with_name(name: str) -> Country: - for country in country_dict.values(): - if country.name == name: - return country() - raise KeyError(f"No country found named {name}") diff --git a/game/debriefing.py b/game/debriefing.py index e21f8ba6f..a7ece4868 100644 --- a/game/debriefing.py +++ b/game/debriefing.py @@ -157,8 +157,8 @@ def __init__( self.game = game self.unit_map = unit_map - self.player_country = game.blue.faction.country.name - self.enemy_country = game.red.faction.country.name + self.player_country = game.blue.country_name + self.enemy_country = game.red.country_name self.air_losses = self.dead_aircraft() self.ground_losses = self.dead_ground_units() diff --git a/game/factions/faction.py b/game/factions/faction.py index 04df97ab2..8bab82113 100644 --- a/game/factions/faction.py +++ b/game/factions/faction.py @@ -7,7 +7,7 @@ from typing import Any, Dict, Iterator, List, Optional, TYPE_CHECKING, Type import dcs -from dcs.country import Country +from dcs.countries import country_dict from dcs.unittype import ShipType, StaticType, UnitType as DcsUnitType from game.armedforces.forcegroup import ForceGroup @@ -28,7 +28,6 @@ from game.data.groups import GroupRole from game.data.units import UnitClass from game.dcs.aircrafttype import AircraftType -from game.dcs.countries import country_with_name from game.dcs.groundunittype import GroundUnitType from game.dcs.shipunittype import ShipUnitType from game.dcs.unittype import UnitType @@ -44,7 +43,7 @@ class Faction: locales: Optional[List[str]] # Country used by this faction - country: Country + country: str = field(default="") # Nice name of the faction name: str = field(default="") @@ -169,15 +168,15 @@ def air_defenses(self) -> list[str]: @classmethod def from_dict(cls: Type[Faction], json: Dict[str, Any]) -> Faction: - try: - country = country_with_name(json["country"]) - except KeyError as ex: - raise KeyError( - f'Faction\'s country ("{json.get("country")}") is not a valid DCS ' - "country ID" - ) from ex - - faction = Faction(locales=json.get("locales"), country=country) + faction = Faction(locales=json.get("locales")) + + faction.country = json.get("country", "/") + if faction.country not in [c.name for c in country_dict.values()]: + raise AssertionError( + 'Faction\'s country ("{}") is not a valid DCS country ID'.format( + faction.country + ) + ) faction.name = json.get("name", "") if not faction.name: diff --git a/game/game.py b/game/game.py index 799decb30..ba1fae6ed 100644 --- a/game/game.py +++ b/game/game.py @@ -24,7 +24,6 @@ from .campaignloader import CampaignAirWingConfig from .coalition import Coalition from .db.gamedb import GameDb -from .dcs.countries import country_with_name from .infos.information import Information from .persistence import SaveManager from .profiling import logged_duration @@ -180,15 +179,13 @@ def sanitize_sides(player_faction: Faction, enemy_faction: Faction) -> None: Make sure the opposing factions are using different countries :return: """ - # TODO: This should just be rejected and sent back to the user to fix. - # This isn't always something that the original faction can support. if player_faction.country == enemy_faction.country: - if player_faction.country.name == "USA": - enemy_faction.country = country_with_name("USAF Aggressors") - elif player_faction.country.name == "Russia": - enemy_faction.country = country_with_name("USSR") + if player_faction.country == "USA": + enemy_faction.country = "USAF Aggressors" + elif player_faction.country == "Russia": + enemy_faction.country = "USSR" else: - enemy_faction.country = country_with_name("Russia") + enemy_faction.country = "Russia" def faction_for(self, player: bool) -> Faction: return self.coalition_for(player).faction @@ -199,10 +196,13 @@ def faker_for(self, player: bool) -> Faker: def air_wing_for(self, player: bool) -> AirWing: return self.coalition_for(player).air_wing + def country_for(self, player: bool) -> str: + return self.coalition_for(player).country_name + @property def neutral_country(self) -> Type[Country]: """Return the best fitting country that can be used as neutral faction in the generated mission""" - countries_in_use = {self.red.faction.country, self.blue.faction.country} + countries_in_use = [self.red.country_name, self.blue.country_name] if UnitedNationsPeacekeepers not in countries_in_use: return UnitedNationsPeacekeepers elif Switzerland.name not in countries_in_use: diff --git a/game/missiongenerator/aircraft/aircraftgenerator.py b/game/missiongenerator/aircraft/aircraftgenerator.py index 912ba4bda..fcbc51214 100644 --- a/game/missiongenerator/aircraft/aircraftgenerator.py +++ b/game/missiongenerator/aircraft/aircraftgenerator.py @@ -17,8 +17,8 @@ from game.ato.package import Package from game.ato.starttype import StartType from game.factions.faction import Faction -from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.missiongenerator.missiondata import MissionData +from game.missiongenerator.lasercoderegistry import LaserCodeRegistry from game.radio.radios import RadioRegistry from game.radio.tacan import TacanRegistry from game.runways import RunwayData @@ -143,6 +143,7 @@ def _spawn_unused_for( # TODO: Special flight type? flight = Flight( Package(squadron.location, self.game.db.flights), + faction.country, squadron, 1, FlightType.BARCAP, diff --git a/game/missiongenerator/airsupportgenerator.py b/game/missiongenerator/airsupportgenerator.py index ca0128fad..1dbc7c091 100644 --- a/game/missiongenerator/airsupportgenerator.py +++ b/game/missiongenerator/airsupportgenerator.py @@ -73,7 +73,7 @@ def generate(self) -> None: else self.conflict.red_cp ) - country = self.game.blue.faction.country + country = self.mission.country(self.game.blue.country_name) if not self.game.settings.disable_legacy_tanker: fallback_tanker_number = 0 diff --git a/game/missiongenerator/cargoshipgenerator.py b/game/missiongenerator/cargoshipgenerator.py index 460420e25..ec7e6577c 100644 --- a/game/missiongenerator/cargoshipgenerator.py +++ b/game/missiongenerator/cargoshipgenerator.py @@ -29,9 +29,12 @@ def generate(self) -> None: self.generate_cargo_ship(ship) def generate_cargo_ship(self, ship: CargoShip) -> ShipGroup: + country = self.mission.country( + self.game.coalition_for(ship.player_owned).country_name + ) waypoints = ship.route group = self.mission.ship_group( - self.game.coalition_for(ship.player_owned).faction.country, + country, ship.name, HandyWind, position=waypoints[0], diff --git a/game/missiongenerator/convoygenerator.py b/game/missiongenerator/convoygenerator.py index e338617ac..a1fe60b7d 100644 --- a/game/missiongenerator/convoygenerator.py +++ b/game/missiongenerator/convoygenerator.py @@ -70,11 +70,13 @@ def _create_mixed_unit_group( units: dict[GroundUnitType, int], for_player: bool, ) -> VehicleGroup: + country = self.mission.country(self.game.coalition_for(for_player).country_name) + unit_types = list(units.items()) main_unit_type, main_unit_count = unit_types[0] group = self.mission.vehicle_group( - self.game.coalition_for(for_player).faction.country, + country, name, main_unit_type.dcs_unit_type, position=position, diff --git a/game/missiongenerator/flotgenerator.py b/game/missiongenerator/flotgenerator.py index 9cab293d8..af7a97a30 100644 --- a/game/missiongenerator/flotgenerator.py +++ b/game/missiongenerator/flotgenerator.py @@ -151,7 +151,7 @@ def generate(self) -> None: utype = AircraftType.named("MQ-9 Reaper") jtac = self.mission.flight_group( - country=self.game.blue.faction.country, + country=self.mission.country(self.game.blue.country_name), name=namegen.next_jtac_name(), aircraft_type=utype.dcs_unit_type, position=position[0], @@ -716,7 +716,7 @@ def _generate_groups( spawn_heading = ( self.conflict.heading.left if is_player else self.conflict.heading.right ) - country = self.game.coalition_for(is_player).faction.country + country = self.game.coalition_for(is_player).country_name for group in groups: if group.role == CombatGroupRole.ARTILLERY: distance_from_frontline = ( @@ -734,7 +734,7 @@ def _generate_groups( g = self._generate_group( is_player, - country, + self.mission.country(country), group.unit_type, group.size, final_position, @@ -750,7 +750,7 @@ def _generate_groups( self.gen_infantry_group_for_group( g, is_player, - country, + self.mission.country(country), spawn_heading.opposite, ) diff --git a/game/missiongenerator/logisticsgenerator.py b/game/missiongenerator/logisticsgenerator.py index 30c27b4f9..089a6bda8 100644 --- a/game/missiongenerator/logisticsgenerator.py +++ b/game/missiongenerator/logisticsgenerator.py @@ -93,8 +93,9 @@ def generate_logistics(self) -> LogisticsInfo: "ctld", "logisticunit" ): # Spawn logisticsunit at pickup zones + country = self.mission.country(self.flight.country) logistic_unit = self.mission.static_group( - self.flight.squadron.coalition.faction.country, + country, f"{self.group.name}logistic", Fortification.FARP_Ammo_Dump_Coating, pickup_point, diff --git a/game/missiongenerator/missiongenerator.py b/game/missiongenerator/missiongenerator.py index c94a2905e..127789477 100644 --- a/game/missiongenerator/missiongenerator.py +++ b/game/missiongenerator/missiongenerator.py @@ -138,12 +138,19 @@ def setup_mission_coalitions(self) -> None: "neutrals", bullseye=Bullseye(Point(0, 0, self.mission.terrain)).to_pydcs() ) - p_country = self.game.blue.faction.country - e_country = self.game.red.faction.country - self.mission.coalition["blue"].add_country(p_country) - self.mission.coalition["red"].add_country(e_country) + p_country = self.game.blue.country_name + e_country = self.game.red.country_name + self.mission.coalition["blue"].add_country( + country_dict[country_id_from_name(p_country)]() + ) + self.mission.coalition["red"].add_country( + country_dict[country_id_from_name(e_country)]() + ) - belligerents = {p_country, e_country} + belligerents = [ + country_id_from_name(p_country), + country_id_from_name(e_country), + ] for country in country_dict.keys(): if country not in belligerents: self.mission.coalition["neutrals"].add_country(country_dict[country]()) @@ -284,18 +291,18 @@ def generate_air_units(self, tgo_generator: TgoGenerator) -> None: aircraft_generator.clear_parking_slots() aircraft_generator.generate_flights( - self.game.blue.faction.country, + self.mission.country(self.game.blue.country_name), self.game.blue.ato, tgo_generator.runways, ) aircraft_generator.generate_flights( - self.game.red.faction.country, + self.mission.country(self.game.red.country_name), self.game.red.ato, tgo_generator.runways, ) aircraft_generator.spawn_unused_aircraft( - self.game.blue.faction.country, - self.game.red.faction.country, + self.mission.country(self.game.blue.country_name), + self.mission.country(self.game.red.country_name), ) for flight in aircraft_generator.flights: @@ -327,7 +334,7 @@ def generate_destroyed_units(self) -> None: pos = Point(cast(float, d["x"]), cast(float, d["z"]), self.mission.terrain) if utype is not None and not self.game.position_culled(pos): self.mission.static_group( - country=self.game.blue.faction.country, + country=self.mission.country(self.game.blue.country_name), name="", _type=utype, hidden=True, diff --git a/game/missiongenerator/tgogenerator.py b/game/missiongenerator/tgogenerator.py index 69b15d65c..e7683f26c 100644 --- a/game/missiongenerator/tgogenerator.py +++ b/game/missiongenerator/tgogenerator.py @@ -599,7 +599,7 @@ def generate(self) -> None: return # Note: Helipad are generated as neutral object in order not to interfer with # capture triggers - country = self.game.coalition_for(self.cp.captured).faction.country + country = self.m.country(self.game.coalition_for(self.cp.captured).country_name) for i, helipad in enumerate(self.cp.helipads): heading = helipad.heading.degrees @@ -675,7 +675,7 @@ def __init__( def generate(self) -> None: for cp in self.game.theater.controlpoints: - country = self.game.coalition_for(cp.captured).faction.country + country = self.m.country(self.game.coalition_for(cp.captured).country_name) # Generate helipads helipad_gen = HelipadGenerator( diff --git a/game/missiongenerator/visualsgenerator.py b/game/missiongenerator/visualsgenerator.py index 56a19708b..14aa5af3c 100644 --- a/game/missiongenerator/visualsgenerator.py +++ b/game/missiongenerator/visualsgenerator.py @@ -99,7 +99,7 @@ def _generate_frontline_smokes(self) -> None: break self.mission.static_group( - self.game.red.faction.country, + self.mission.country(self.game.red.country_name), "", _type=v, position=pos, diff --git a/game/squadrons/squadron.py b/game/squadrons/squadron.py index 9e5c731a5..21fed8694 100644 --- a/game/squadrons/squadron.py +++ b/game/squadrons/squadron.py @@ -7,7 +7,6 @@ from datetime import datetime from typing import Optional, Sequence, TYPE_CHECKING -from dcs.country import Country from faker import Faker from game.ato import Flight, FlightType, Package @@ -29,7 +28,7 @@ class Squadron: name: str nickname: Optional[str] - country: Country + country: str role: str aircraft: AircraftType max_size: int @@ -72,7 +71,7 @@ def __hash__(self) -> int: ( self.name, self.nickname, - self.country.id, + self.country, self.role, self.aircraft, ) @@ -419,6 +418,7 @@ def plan_ferry_flight(self, package: Package, size: int) -> None: flight = Flight( package, + self.coalition.country_name, self, size, FlightType.FERRY, diff --git a/game/squadrons/squadrondef.py b/game/squadrons/squadrondef.py index c9a88a6da..1c009dab0 100644 --- a/game/squadrons/squadrondef.py +++ b/game/squadrons/squadrondef.py @@ -5,10 +5,8 @@ from typing import Optional, TYPE_CHECKING import yaml -from dcs.country import Country from game.dcs.aircrafttype import AircraftType -from game.dcs.countries import country_with_name from game.squadrons.operatingbases import OperatingBases from game.squadrons.pilot import Pilot @@ -21,7 +19,7 @@ class SquadronDef: name: str nickname: Optional[str] - country: Country + country: str role: str aircraft: AircraftType livery: Optional[str] @@ -73,7 +71,7 @@ def from_yaml(cls, path: Path) -> SquadronDef: return SquadronDef( name=data["name"], nickname=data.get("nickname"), - country=country_with_name(data["country"]), + country=data["country"], role=data["role"], aircraft=unit_type, livery=data.get("livery"), diff --git a/game/squadrons/squadrondefloader.py b/game/squadrons/squadrondefloader.py index 1d79fb351..cfd1197ee 100644 --- a/game/squadrons/squadrondefloader.py +++ b/game/squadrons/squadrondefloader.py @@ -29,13 +29,13 @@ def load(self) -> dict[AircraftType, list[SquadronDef]]: squadrons: dict[AircraftType, list[SquadronDef]] = defaultdict(list) country = self.faction.country faction = self.faction - any_country = country.name.startswith("Combined Joint Task Forces ") + any_country = country.startswith("Combined Joint Task Forces ") for directory in self.squadron_directories(): for path, squadron_def in self.load_squadrons_from(directory): if not any_country and squadron_def.country != country: logging.debug( "Not using squadron for non-matching country (is " - f"{squadron_def.country.name}, need {country.name}: {path}" + f"{squadron_def.country}, need {country}: {path}" ) continue if squadron_def.aircraft not in faction.aircrafts: diff --git a/game/transfers.py b/game/transfers.py index 806a79a82..86f946d4a 100644 --- a/game/transfers.py +++ b/game/transfers.py @@ -357,6 +357,7 @@ def create_airlift_flight(self, squadron: Squadron) -> int: flight = Flight( self.package, + self.game.country_for(squadron.player), squadron, flight_size, FlightType.TRANSPORT, diff --git a/qt_ui/widgets/combos/liveryselector.py b/qt_ui/widgets/combos/liveryselector.py index 38c4a55fb..3538d5c82 100644 --- a/qt_ui/widgets/combos/liveryselector.py +++ b/qt_ui/widgets/combos/liveryselector.py @@ -1,5 +1,6 @@ from __future__ import annotations +import dcs.countries from PySide6.QtWidgets import QComboBox from dcs.liveries.livery import Livery @@ -21,7 +22,9 @@ def set_squadron(self, squadron: Squadron) -> None: self.clear() self.addItem("Default", None) for idx, livery in enumerate( - squadron.aircraft.dcs_unit_type.iter_liveries_for_country(squadron.country) + squadron.aircraft.dcs_unit_type.iter_liveries_for_country( + dcs.countries.get_by_name(squadron.country) + ) ): self.addItem(livery.name, livery) if squadron.livery == livery.id: diff --git a/qt_ui/windows/mission/flight/QFlightCreator.py b/qt_ui/windows/mission/flight/QFlightCreator.py index 5702304b5..aa657deda 100644 --- a/qt_ui/windows/mission/flight/QFlightCreator.py +++ b/qt_ui/windows/mission/flight/QFlightCreator.py @@ -40,6 +40,7 @@ def __init__(self, game: Game, package: Package, parent=None) -> None: self.game = game self.package = package self.custom_name_text = None + self.country = self.game.blue.country_name # Make dialog modal to prevent background windows to close unexpectedly. self.setModal(True) @@ -182,6 +183,7 @@ def create_flight(self) -> None: flight = Flight( self.package, + self.country, squadron, # A bit of a hack to work around the old API. Not actually relevant because # the roster is passed explicitly. Needs a refactor. diff --git a/tests/test_factions.py b/tests/test_factions.py index 96b5a3537..58e206675 100644 --- a/tests/test_factions.py +++ b/tests/test_factions.py @@ -41,7 +41,7 @@ def test_load_valid_faction(self) -> None: with (RESOURCES_DIR / "valid_faction.json").open("r") as data: faction = Faction.from_dict(json.load(data)) - self.assertEqual(faction.country.name, "USA") + self.assertEqual(faction.country, "USA") self.assertEqual(faction.name, "USA 2005") self.assertEqual(faction.authors, "Khopa") self.assertEqual(faction.description, "This is a test description")