diff --git a/game/armedforces/forcegroup.py b/game/armedforces/forcegroup.py index b382b75a..0bafacfe 100644 --- a/game/armedforces/forcegroup.py +++ b/game/armedforces/forcegroup.py @@ -289,7 +289,7 @@ class ForceGroup: unit.id = game.next_unit_id() # Add unit name escaped so that we do not have scripting issues later unit.name = escape_string_for_lua( - unit.unit_type.name if unit.unit_type else unit.type.name + unit.unit_type.variant_id if unit.unit_type else unit.type.name ) unit.position = PointWithHeading.from_point( ground_object.position + unit.position, diff --git a/game/dcs/aircrafttype.py b/game/dcs/aircrafttype.py index 87b136ec..622d5ef6 100644 --- a/game/dcs/aircrafttype.py +++ b/game/dcs/aircrafttype.py @@ -39,6 +39,7 @@ from game.radio.channels import ( ViperChannelNamer, WarthogChannelNamer, ) +from game.savecompat import has_save_compat_for from game.utils import ( Distance, ImperialUnits, @@ -219,7 +220,7 @@ class AircraftType(UnitType[Type[FlyingType]]): @classmethod def register(cls, unit_type: AircraftType) -> None: - cls._by_name[unit_type.name] = unit_type + cls._by_name[unit_type.variant_id] = unit_type cls._by_unit_type[unit_type.dcs_unit_type].append(unit_type) @property @@ -297,7 +298,7 @@ class AircraftType(UnitType[Type[FlyingType]]): else: # Slow like warbirds or helicopters # Use whichever is slowest - mach 0.35 or 50% of max speed - logging.debug(f"{self.name} max_speed * 0.5 is {max_speed * 0.5}") + logging.debug(f"{self.variant_id} max_speed * 0.5 is {max_speed * 0.5}") return min(Speed.from_mach(0.35, altitude), max_speed * 0.5) def alloc_flight_radio(self, radio_registry: RadioRegistry) -> RadioFrequency: @@ -352,9 +353,14 @@ class AircraftType(UnitType[Type[FlyingType]]): def task_priority(self, task: FlightType) -> int: return self.task_priorities[task] + @has_save_compat_for(9) def __setstate__(self, state: dict[str, Any]) -> None: + # Save compat: the `name` field has been renamed `variant_id`. + if "name" in state: + state["variant_id"] = state.pop("name") + # Update any existing models with new data on load. - updated = AircraftType.named(state["name"]) + updated = AircraftType.named(state["variant_id"]) state.update(updated.__dict__) self.__dict__.update(state) @@ -485,7 +491,7 @@ class AircraftType(UnitType[Type[FlyingType]]): for variant in data.get("variants", [aircraft.id]): yield AircraftType( dcs_unit_type=aircraft, - name=variant, + variant_id=variant, description=data.get( "description", f"No data. Google {variant}", @@ -552,4 +558,4 @@ class AircraftType(UnitType[Type[FlyingType]]): AircraftType._custom_weapon_injections(aircraft, data) def __hash__(self) -> int: - return hash(self.name) + return hash(self.variant_id) diff --git a/game/dcs/groundunittype.py b/game/dcs/groundunittype.py index 1030ba14..ba32dff3 100644 --- a/game/dcs/groundunittype.py +++ b/game/dcs/groundunittype.py @@ -12,6 +12,7 @@ from dcs.vehicles import vehicle_map from game.data.units import UnitClass from game.dcs.unittype import UnitType +from game.savecompat import has_save_compat_for @dataclass @@ -65,9 +66,20 @@ class GroundUnitType(UnitType[Type[VehicleType]]): dict[type[VehicleType], list[GroundUnitType]] ] = defaultdict(list) + @has_save_compat_for(9) + def __setstate__(self, state: dict[str, Any]) -> None: + # Save compat: the `name` field has been renamed `variant_id`. + if "name" in state: + state["variant_id"] = state.pop("name") + + # Update any existing models with new data on load. + updated = GroundUnitType.named(state["variant_id"]) + state.update(updated.__dict__) + self.__dict__.update(state) + @classmethod def register(cls, unit_type: GroundUnitType) -> None: - cls._by_name[unit_type.name] = unit_type + cls._by_name[unit_type.variant_id] = unit_type cls._by_unit_type[unit_type.dcs_unit_type].append(unit_type) @classmethod @@ -115,7 +127,7 @@ class GroundUnitType(UnitType[Type[VehicleType]]): dcs_unit_type=vehicle, unit_class=unit_class, spawn_weight=data.get("spawn_weight", 0), - name=variant, + variant_id=variant, description=data.get( "description", f"No data. Google {variant}", diff --git a/game/dcs/shipunittype.py b/game/dcs/shipunittype.py index cf36d1cd..4c739e9c 100644 --- a/game/dcs/shipunittype.py +++ b/game/dcs/shipunittype.py @@ -4,7 +4,7 @@ import logging from collections import defaultdict from dataclasses import dataclass from pathlib import Path -from typing import ClassVar, Iterator, Type +from typing import ClassVar, Iterator, Type, Any import yaml from dcs.ships import ship_map @@ -12,6 +12,7 @@ from dcs.unittype import ShipType from game.data.units import UnitClass from game.dcs.unittype import UnitType +from game.savecompat import has_save_compat_for @dataclass(frozen=True) @@ -21,9 +22,20 @@ class ShipUnitType(UnitType[Type[ShipType]]): list ) + @has_save_compat_for(9) + def __setstate__(self, state: dict[str, Any]) -> None: + # Save compat: the `name` field has been renamed `variant_id`. + if "name" in state: + state["variant_id"] = state.pop("name") + + # Update any existing models with new data on load. + updated = ShipUnitType.named(state["variant_id"]) + state.update(updated.__dict__) + self.__dict__.update(state) + @classmethod def register(cls, unit_type: ShipUnitType) -> None: - cls._by_name[unit_type.name] = unit_type + cls._by_name[unit_type.variant_id] = unit_type cls._by_unit_type[unit_type.dcs_unit_type].append(unit_type) @classmethod @@ -66,7 +78,7 @@ class ShipUnitType(UnitType[Type[ShipType]]): yield ShipUnitType( dcs_unit_type=ship, unit_class=unit_class, - name=variant, + variant_id=variant, description=data.get( "description", f"No data. Google {variant}", diff --git a/game/dcs/unittype.py b/game/dcs/unittype.py index aa977987..e8da39ba 100644 --- a/game/dcs/unittype.py +++ b/game/dcs/unittype.py @@ -16,7 +16,7 @@ DcsUnitTypeT = TypeVar("DcsUnitTypeT", bound=Type[DcsUnitType]) @dataclass(frozen=True) class UnitType(ABC, Generic[DcsUnitTypeT]): dcs_unit_type: DcsUnitTypeT - name: str + variant_id: str description: str year_introduced: str country_of_origin: str @@ -28,7 +28,7 @@ class UnitType(ABC, Generic[DcsUnitTypeT]): _loaded: ClassVar[bool] = False def __str__(self) -> str: - return self.name + return self.variant_id @property def dcs_id(self) -> str: diff --git a/game/factions/faction.py b/game/factions/faction.py index c8b6bc09..53593c6e 100644 --- a/game/factions/faction.py +++ b/game/factions/faction.py @@ -169,7 +169,7 @@ class Faction: def air_defenses(self) -> list[str]: """Returns the Air Defense types""" # This is used for the faction overview in NewGameWizard - air_defenses = [a.name for a in self.air_defense_units] + air_defenses = [a.variant_id for a in self.air_defense_units] air_defenses.extend( [ pg.name diff --git a/game/missiongenerator/aircraft/flightgroupconfigurator.py b/game/missiongenerator/aircraft/flightgroupconfigurator.py index d47e65bf..e39cc701 100644 --- a/game/missiongenerator/aircraft/flightgroupconfigurator.py +++ b/game/missiongenerator/aircraft/flightgroupconfigurator.py @@ -209,7 +209,7 @@ class FlightGroupConfigurator: TankerInfo( group_name=str(self.group.name), callsign=callsign, - variant=self.flight.unit_type.name, + variant=self.flight.unit_type.variant_id, freq=channel, tacan=tacan, start_time=self.flight.flight_plan.patrol_start_time, diff --git a/game/naming.py b/game/naming.py index ad9d4693..4a663ab4 100644 --- a/game/naming.py +++ b/game/naming.py @@ -484,13 +484,13 @@ class NameGenerator: else: name_str = "{} {}".format(flight.package.target.name, flight.flight_type) return "{}|{}|{}|{}|".format( - name_str, country.id, cls.aircraft_number, flight.unit_type.name + name_str, country.id, cls.aircraft_number, flight.unit_type.variant_id ) @classmethod def next_unit_name(cls, country: Country, unit_type: UnitType[Any]) -> str: cls.number += 1 - return "unit|{}|{}|{}|".format(country.id, cls.number, unit_type.name) + return "unit|{}|{}|{}|".format(country.id, cls.number, unit_type.variant_id) @classmethod def next_infantry_name(cls, country: Country, unit_type: UnitType[Any]) -> str: @@ -498,7 +498,7 @@ class NameGenerator: return "infantry|{}|{}|{}|".format( country.id, cls.infantry_number, - unit_type.name, + unit_type.variant_id, ) @classmethod @@ -509,7 +509,7 @@ class NameGenerator: @classmethod def next_tanker_name(cls, country: Country, unit_type: AircraftType) -> str: cls.number += 1 - return "tanker|{}|{}|0|{}".format(country.id, cls.number, unit_type.name) + return "tanker|{}|{}|0|{}".format(country.id, cls.number, unit_type.variant_id) @classmethod def next_carrier_name(cls, country: Country) -> str: diff --git a/game/purchaseadapter.py b/game/purchaseadapter.py index aeeff043..5f2a801e 100644 --- a/game/purchaseadapter.py +++ b/game/purchaseadapter.py @@ -142,7 +142,7 @@ class AircraftPurchaseAdapter(PurchaseAdapter[Squadron]): separator = "
" else: separator = " " - return separator.join([item.aircraft.name, str(item)]) + return separator.join([item.aircraft.variant_id, str(item)]) def unit_type_of(self, item: Squadron) -> AircraftType: return item.aircraft diff --git a/qt_ui/windows/AirWingConfigurationDialog.py b/qt_ui/windows/AirWingConfigurationDialog.py index 6411c259..68367f39 100644 --- a/qt_ui/windows/AirWingConfigurationDialog.py +++ b/qt_ui/windows/AirWingConfigurationDialog.py @@ -611,12 +611,12 @@ class AircraftTypeList(QListView): self.add_aircraft_type(aircraft) def remove_aircraft_type(self, aircraft: AircraftType): - for item in self.item_model.findItems(aircraft.name): + for item in self.item_model.findItems(aircraft.variant_id): self.item_model.removeRow(item.row()) self.page_index_changed.emit(self.selectionModel().currentIndex().row()) def add_aircraft_type(self, aircraft: AircraftType): - aircraft_item = QStandardItem(aircraft.name) + aircraft_item = QStandardItem(aircraft.variant_id) icon = self.icon_for(aircraft) if icon is not None: aircraft_item.setIcon(icon) @@ -728,7 +728,7 @@ class AirWingConfigurationTab(QWidget): ) # Add Squadron - if not self.type_list.item_model.findItems(selected_type.name): + if not self.type_list.item_model.findItems(selected_type.variant_id): self.type_list.add_aircraft_type(selected_type) # TODO Select the newly added type self.squadrons_panel.add_squadron_to_panel(squadron) @@ -814,8 +814,8 @@ class SquadronAircraftTypeSelector(QComboBox): super().__init__() self.setSizeAdjustPolicy(self.AdjustToContents) - for type in sorted(types, key=lambda type: type.name): - self.addItem(type.name, type) + for type in sorted(types, key=lambda type: type.variant_id): + self.addItem(type.variant_id, type) if selected_aircraft: self.setCurrentText(selected_aircraft) diff --git a/qt_ui/windows/AirWingDialog.py b/qt_ui/windows/AirWingDialog.py index 41ab20df..42477acc 100644 --- a/qt_ui/windows/AirWingDialog.py +++ b/qt_ui/windows/AirWingDialog.py @@ -43,7 +43,7 @@ class SquadronDelegate(TwoColumnRowDelegate): nickname = "" return f"{squadron.name}{nickname}" elif (row, column) == (0, 1): - return squadron.aircraft.name + return squadron.aircraft.variant_id elif (row, column) == (1, 0): return squadron.location.name elif (row, column) == (1, 1): @@ -130,7 +130,7 @@ class AircraftInventoryData: player = "Player" if pilot.player else "AI" yield AircraftInventoryData( flight.departure.name, - flight.unit_type.name, + flight.unit_type.variant_id, flight_type, target, pilot_name, @@ -143,7 +143,7 @@ class AircraftInventoryData: ) -> Iterator[AircraftInventoryData]: for _ in range(0, squadron.untasked_aircraft): yield AircraftInventoryData( - squadron.name, squadron.aircraft.name, "Idle", "N/A", "N/A", "N/A" + squadron.name, squadron.aircraft.variant_id, "Idle", "N/A", "N/A", "N/A" ) diff --git a/qt_ui/windows/QDebriefingWindow.py b/qt_ui/windows/QDebriefingWindow.py index 6729ebb6..e4aefc64 100644 --- a/qt_ui/windows/QDebriefingWindow.py +++ b/qt_ui/windows/QDebriefingWindow.py @@ -23,7 +23,9 @@ class LossGrid(QGridLayout): def __init__(self, debriefing: Debriefing, player: bool) -> None: super().__init__() - self.add_loss_rows(debriefing.air_losses.by_type(player), lambda u: u.name) + self.add_loss_rows( + debriefing.air_losses.by_type(player), lambda u: u.variant_id + ) self.add_loss_rows( debriefing.front_line_losses_by_type(player), lambda u: str(u) ) diff --git a/qt_ui/windows/QUnitInfoWindow.py b/qt_ui/windows/QUnitInfoWindow.py index fbc7e405..904cb146 100644 --- a/qt_ui/windows/QUnitInfoWindow.py +++ b/qt_ui/windows/QUnitInfoWindow.py @@ -66,7 +66,7 @@ class QUnitInfoWindow(QDialog): self.setModal(True) self.game = game self.unit_type = unit_type - self.name = unit_type.name + self.name = unit_type.variant_id self.setWindowTitle(f"Unit Info: {self.name}") self.setWindowIcon(QIcon("./resources/icon.png")) self.setMinimumHeight(570) @@ -93,7 +93,7 @@ class QUnitInfoWindow(QDialog): self.details_grid_layout.setMargin(0) self.name_box = QLabel( - f"Name: {unit_type.manufacturer} {unit_type.name}" + f"Name: {unit_type.manufacturer} {unit_type.variant_id}" ) self.name_box.setProperty("style", "info-element") diff --git a/qt_ui/windows/basemenu/DepartingConvoysMenu.py b/qt_ui/windows/basemenu/DepartingConvoysMenu.py index c334f0bb..c1da87e3 100644 --- a/qt_ui/windows/basemenu/DepartingConvoysMenu.py +++ b/qt_ui/windows/basemenu/DepartingConvoysMenu.py @@ -33,11 +33,11 @@ class DepartingConvoyInfo(QGroupBox): if unit_type.dcs_id in VEHICLES_ICONS.keys(): icon.setPixmap(VEHICLES_ICONS[unit_type.dcs_id]) else: - icon.setText("" + unit_type.name + "") + icon.setText("" + unit_type.variant_id + "") icon.setProperty("style", "icon-armor") unit_layout.addWidget(icon, idx, 0) unit_layout.addWidget( - QLabel(f"{count} x {unit_type.name}"), + QLabel(f"{count} x {unit_type.variant_id}"), idx, 1, ) diff --git a/qt_ui/windows/basemenu/NewUnitTransferDialog.py b/qt_ui/windows/basemenu/NewUnitTransferDialog.py index 77274fa1..b7a2c299 100644 --- a/qt_ui/windows/basemenu/NewUnitTransferDialog.py +++ b/qt_ui/windows/basemenu/NewUnitTransferDialog.py @@ -64,7 +64,7 @@ class UnitTransferList(QFrame): task_box_layout = QGridLayout() scroll_content.setLayout(task_box_layout) - units_column = sorted(cp.base.armor, key=lambda u: u.name) + units_column = sorted(cp.base.armor, key=lambda u: u.variant_id) count = 0 for count, unit_type in enumerate(units_column): @@ -173,7 +173,7 @@ class ScrollingUnitTransferGrid(QFrame): unit_types = set(self.game_model.game.faction_for(player=True).ground_units) sorted_units = sorted( {u for u in unit_types if self.cp.base.total_units_of_type(u)}, - key=lambda u: u.name, + key=lambda u: u.variant_id, ) for row, unit_type in enumerate(sorted_units): self.add_unit_row(unit_type, task_box_layout, row) @@ -205,7 +205,7 @@ class ScrollingUnitTransferGrid(QFrame): origin_inventory = self.cp.base.total_units_of_type(unit_type) - unit_name = QLabel(f"{unit_type.name}") + unit_name = QLabel(f"{unit_type.variant_id}") unit_name.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) ) diff --git a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py index 14b2b633..95d487d4 100644 --- a/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/airfield/QAircraftRecruitmentMenu.py @@ -44,7 +44,9 @@ class QAircraftRecruitmentMenu(UnitTransactionFrame[Squadron]): for squadron in cp.squadrons: unit_types.add(squadron.aircraft) - sorted_squadrons = sorted(cp.squadrons, key=lambda s: (s.aircraft.name, s.name)) + sorted_squadrons = sorted( + cp.squadrons, key=lambda s: (s.aircraft.variant_id, s.name) + ) for row, squadron in enumerate(sorted_squadrons): self.add_purchase_row(squadron, task_box_layout, row) diff --git a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py index 6149ce8e..e420db3b 100644 --- a/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py +++ b/qt_ui/windows/basemenu/ground_forces/QArmorRecruitmentMenu.py @@ -32,7 +32,7 @@ class QArmorRecruitmentMenu(UnitTransactionFrame[GroundUnitType]): unit_types = list( set(self.game_model.game.faction_for(player=True).ground_units) ) - unit_types.sort(key=lambda u: u.name) + unit_types.sort(key=lambda u: u.variant_id) for row, unit_type in enumerate(unit_types): self.add_purchase_row(unit_type, task_box_layout, row) stretch = QVBoxLayout() diff --git a/qt_ui/windows/basemenu/intel/QIntelInfo.py b/qt_ui/windows/basemenu/intel/QIntelInfo.py index 795fd73d..fe9f9c85 100644 --- a/qt_ui/windows/basemenu/intel/QIntelInfo.py +++ b/qt_ui/windows/basemenu/intel/QIntelInfo.py @@ -32,7 +32,7 @@ class QIntelInfo(QFrame): ).present.items(): if count: task_type = unit_type.dcs_unit_type.task_default.name - units_by_task[task_type][unit_type.name] += count + units_by_task[task_type][unit_type.variant_id] += count units_by_task = { task: units_by_task[task] for task in sorted(units_by_task.keys()) @@ -41,7 +41,7 @@ class QIntelInfo(QFrame): front_line_units = defaultdict(int) for unit_type, count in self.cp.base.armor.items(): if count: - front_line_units[unit_type.name] += count + front_line_units[unit_type.variant_id] += count units_by_task["Front line units"] = front_line_units for task, unit_types in units_by_task.items(): diff --git a/qt_ui/windows/groundobject/QGroundObjectBuyMenu.py b/qt_ui/windows/groundobject/QGroundObjectBuyMenu.py index b0dc5505..c790287b 100644 --- a/qt_ui/windows/groundobject/QGroundObjectBuyMenu.py +++ b/qt_ui/windows/groundobject/QGroundObjectBuyMenu.py @@ -76,7 +76,7 @@ class QTgoLayoutGroupRow(QWidget): # Add all possible units with the price for unit_type in force_group.unit_types_for_group(group): self.unit_selector.addItem( - f"{unit_type.name} [${unit_type.price}M]", + f"{unit_type.variant_id} [${unit_type.price}M]", userData=(unit_type.dcs_unit_type, unit_type.price), ) # Add all possible statics with price = 0 diff --git a/qt_ui/windows/intel.py b/qt_ui/windows/intel.py index cb8e3b27..d8742273 100644 --- a/qt_ui/windows/intel.py +++ b/qt_ui/windows/intel.py @@ -88,11 +88,11 @@ class AircraftIntelLayout(IntelTableLayout): continue self.add_header(f"{control_point.name} ({base_total})") - for airframe in sorted(allocation.present, key=lambda k: k.name): + for airframe in sorted(allocation.present, key=lambda k: k.variant_id): count = allocation.present[airframe] if not count: continue - self.add_row(f" {airframe.name}", count) + self.add_row(f" {airframe.variant_id}", count) self.add_row("") self.add_row("Total", total) @@ -117,11 +117,11 @@ class ArmyIntelLayout(IntelTableLayout): continue self.add_header(f"{control_point.name} ({base.total_armor})") - for vehicle in sorted(base.armor, key=lambda k: k.name): + for vehicle in sorted(base.armor, key=lambda k: k.variant_id): count = base.armor[vehicle] if not count: continue - self.add_row(f" {vehicle.name}", count) + self.add_row(f" {vehicle.variant_id}", count) self.add_row("") self.add_row("Total", total)