fix save compat

This commit is contained in:
Eclipse/Druss99 2025-09-23 16:36:27 -04:00 committed by Raffson
parent 31c80dfd02
commit 9f10ecc884
21 changed files with 122 additions and 56 deletions

View File

@ -105,6 +105,15 @@ class Coalition:
return state
def __setstate__(self, state: dict[str, Any]) -> None:
# Migration: Convert old boolean player values to Player enum
if "player" in state and isinstance(state["player"], bool):
from game.theater.player import Player
if state["player"]:
state["player"] = Player.BLUE
else:
state["player"] = Player.RED
self.__dict__.update(state)
# Regenerate any state that was not persisted.
self.on_load()

View File

@ -56,7 +56,7 @@ https://en.wikipedia.org/wiki/Hierarchical_task_network
from __future__ import annotations
from datetime import datetime
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from game.ato.starttype import StartType
from game.commander.tasks.compound.nextaction import PlanNextAction
@ -80,6 +80,18 @@ class TheaterCommander(Planner[TheaterState, TheaterCommanderTask]):
self.game = game
self.player = player
def __setstate__(self, state: dict[str, Any]) -> None:
# Migration: Convert old boolean player values to Player enum
if "player" in state and isinstance(state["player"], bool):
from game.theater.player import Player
if state["player"]:
state["player"] = Player.BLUE
else:
state["player"] = Player.RED
self.__dict__.update(state)
def plan_missions(self, now: datetime, tracer: MultiEventTracer) -> None:
state = TheaterState.from_game(self.game, self.player, now, tracer)
while True:

View File

@ -321,7 +321,7 @@ class MissionResultsProcessor:
settings = cp.coalition.game.settings
factor = (
settings.frontline_reserves_factor
if cp.captured
if cp.captured.is_blue
else settings.frontline_reserves_factor_red
)

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import itertools
from collections import defaultdict
from typing import Iterator, Optional, Sequence, TYPE_CHECKING
from typing import Iterator, Optional, Sequence, TYPE_CHECKING, Any
from game.ato.closestairfields import ObjectiveDistanceCache
from game.dcs.aircrafttype import AircraftType
@ -27,6 +27,18 @@ class AirWing:
self.squadron_def_generator = SquadronDefGenerator(faction)
self.settings = game.settings
def __setstate__(self, state: dict[str, Any]) -> None:
# Migration: Convert old boolean player values to Player enum
if "player" in state and isinstance(state["player"], bool):
from game.theater.player import Player
if state["player"]:
state["player"] = Player.BLUE
else:
state["player"] = Player.RED
self.__dict__.update(state)
def unclaim_squadron_def(self, squadron: Squadron) -> None:
if squadron.aircraft in self.squadron_defs:
for squadron_def in self.squadron_defs[squadron.aircraft]:

View File

@ -15,6 +15,7 @@ from faker import Faker
from game.ato import Flight, FlightType, Package
from game.settings import AutoAtoBehavior, Settings
from game.theater import ParkingType
from game.theater.player import Player
from .pilot import Pilot, PilotStatus
from ..db.database import Database
from ..radio.radios import RadioFrequency
@ -24,7 +25,7 @@ if TYPE_CHECKING:
from game import Game
from game.coalition import Coalition
from game.dcs.aircrafttype import AircraftType
from game.theater import ControlPoint, MissionTarget, Player
from game.theater import ControlPoint, MissionTarget
from .operatingbases import OperatingBases
from .squadrondef import SquadronDef

View File

@ -38,7 +38,16 @@ from collections import defaultdict
from dataclasses import dataclass, field
from datetime import datetime
from functools import singledispatchmethod
from typing import Generic, Iterator, List, Optional, Sequence, TYPE_CHECKING, TypeVar
from typing import (
Any,
Generic,
Iterator,
List,
Optional,
Sequence,
TYPE_CHECKING,
TypeVar,
)
from dcs.mapping import Point
@ -246,10 +255,7 @@ class Airlift(Transport):
@property
def player_owned(self) -> bool:
if self.transfer.player.is_blue:
return True
else:
return False
return self.transfer.player.is_blue
def find_escape_route(self) -> Optional[ControlPoint]:
# TODO: Move units to closest base.
@ -395,10 +401,9 @@ class MultiGroupTransport(MissionTarget, Transport):
self.transfers: List[TransferOrder] = []
def is_friendly(self, to_player: Player) -> bool:
if self.origin.captured.is_blue:
if self.origin.captured == to_player:
return True
else:
return False
return False
def add_units(self, transfer: TransferOrder) -> None:
self.transfers.append(transfer)
@ -590,6 +595,18 @@ class PendingTransfers:
self.cargo_ships = CargoShipMap()
self.pending_transfers: List[TransferOrder] = []
def __setstate__(self, state: dict[str, Any]) -> None:
# Migration: Convert old boolean player values to Player enum
if "player" in state and isinstance(state["player"], bool):
from game.theater.player import Player
if state["player"]:
state["player"] = Player.BLUE
else:
state["player"] = Player.RED
self.__dict__.update(state)
def __iter__(self) -> Iterator[TransferOrder]:
yield from self.pending_transfers

View File

@ -377,7 +377,7 @@ class TransferModel(QAbstractListModel):
@property
def transfers(self) -> PendingTransfers:
return self.game_model.game.coalition_for(player=True).transfers
return self.game_model.game.coalition_for(player=Player.BLUE).transfers
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
return self.transfers.pending_transfer_count
@ -433,7 +433,7 @@ class AirWingModel(QAbstractListModel):
SquadronRole = Qt.ItemDataRole.UserRole
def __init__(self, game_model: GameModel, player: bool) -> None:
def __init__(self, game_model: GameModel, player: Player) -> None:
super().__init__()
self.game_model = game_model
self.player = player
@ -548,8 +548,8 @@ class GameModel:
self.game: Optional[Game] = game
self.sim_controller = sim_controller
self.transfer_model = TransferModel(self)
self.blue_air_wing_model = AirWingModel(self, player=True)
self.red_air_wing_model = AirWingModel(self, player=False)
self.blue_air_wing_model = AirWingModel(self, player=Player.BLUE)
self.red_air_wing_model = AirWingModel(self, player=Player.RED)
if self.game is None:
self.ato_model = AtoModel(self, AirTaskingOrder())
self.red_ato_model = AtoModel(self, AirTaskingOrder())

View File

@ -3,6 +3,7 @@ from PySide6.QtWidgets import QHBoxLayout, QGroupBox, QPushButton
import qt_ui.uiconstants as CONST
from game import Game
from game.income import Income
from game.theater.player import Player
from qt_ui.windows.finances.QFinancesMenu import QFinancesMenu
@ -41,7 +42,9 @@ class QBudgetBox(QGroupBox):
return
self.game = game
self.setBudget(self.game.blue.budget, Income(self.game, player=True).total)
self.setBudget(
self.game.blue.budget, Income(self.game, player=Player.BLUE).total
)
self.finances.setEnabled(True)
def openFinances(self):

View File

@ -103,8 +103,8 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
if self.include_airbases:
for cp in self.game.theater.controlpoints:
if (self.include_enemy and not cp.captured) or (
self.include_friendly and cp.captured
if (self.include_enemy and cp.captured.is_red) or (
self.include_friendly and cp.captured.is_blue
):
wpt = FlightWaypoint(
cp.name,
@ -113,7 +113,7 @@ class QPredefinedWaypointSelectionComboBox(QFilteredComboBox):
Distance.from_meters(0),
)
wpt.alt_type = "RADIO"
if cp.captured:
if cp.captured.is_blue:
wpt.description = (
"Position of " + cp.name + " [Friendly Airbase]"
)

View File

@ -24,7 +24,7 @@ from game.factions import Faction
from game.server import EventStream
from game.sim import GameUpdateEvents
from game.squadrons import Squadron
from game.theater import ConflictTheater
from game.theater import ConflictTheater, Player
from qt_ui.delegates import TwoColumnRowDelegate
from qt_ui.models import AirWingModel, AtoModel, GameModel, SquadronModel
from qt_ui.simcontroller import SimController
@ -221,13 +221,17 @@ class AirInventoryView(QWidget):
self.table.setSortingEnabled(True)
def iter_allocated_aircraft(self) -> Iterator[AircraftInventoryData]:
coalition = self.game_model.game.coalition_for(not self.enemy_info)
coalition = self.game_model.game.coalition_for(
Player.BLUE if not self.enemy_info else Player.RED
)
for package in coalition.ato.packages:
for flight in package.flights:
yield from AircraftInventoryData.from_flight(flight)
def iter_unallocated_aircraft(self) -> Iterator[AircraftInventoryData]:
coalition = self.game_model.game.coalition_for(not self.enemy_info)
coalition = self.game_model.game.coalition_for(
Player.BLUE if not self.enemy_info else Player.RED
)
for squadron in coalition.air_wing.iter_squadrons():
yield from AircraftInventoryData.each_untasked_from_squadron(squadron)
@ -273,7 +277,7 @@ class AirWingTabs(QTabWidget):
self.addTab(w, "Cheats")
qfu_ownfor = QFactionUnits(
game_model.game.coalition_for(True).faction,
game_model.game.coalition_for(Player.BLUE).faction,
self,
show_jtac=True,
)
@ -283,7 +287,7 @@ class AirWingTabs(QTabWidget):
"Faction OWNFOR",
)
qfu_opfor = QFactionUnits(
game_model.game.coalition_for(False).faction,
game_model.game.coalition_for(Player.RED).faction,
self,
show_jtac=True,
)
@ -300,12 +304,12 @@ class AirWingTabs(QTabWidget):
self.game_model.ato_model.on_sim_update(events)
def preset_group_updated_ownfor(self, f: Faction) -> None:
self.preset_group_updated(f, player=True)
self.preset_group_updated(f, player=Player.BLUE)
def preset_group_updated_opfor(self, f: Faction) -> None:
self.preset_group_updated(f, player=False)
self.preset_group_updated(f, player=Player.RED)
def preset_group_updated(self, f: Faction, player: bool) -> None:
def preset_group_updated(self, f: Faction, player: Player) -> None:
self.game_model.game.coalition_for(player).armed_forces = ArmedForces(f)

View File

@ -114,7 +114,7 @@ class PendingTransfersDialog(QDialog):
def can_cancel(self, index: QModelIndex) -> bool:
if not index.isValid():
return False
return self.transfer_model.transfer_at_index(index).player
return self.transfer_model.transfer_at_index(index).player.is_blue
def on_selection_changed(
self, selected: QItemSelection, _deselected: QItemSelection

View File

@ -174,7 +174,9 @@ class ScrollingUnitTransferGrid(QFrame):
scroll_content = QWidget()
task_box_layout = QGridLayout()
unit_types = set(self.game_model.game.faction_for(player=True).ground_units)
unit_types = set(
self.game_model.game.faction_for(player=Player.BLUE).ground_units
)
sorted_units = sorted(
{u for u in unit_types if self.cp.base.total_units_of_type(u)},
key=lambda u: u.display_name,

View File

@ -51,7 +51,7 @@ class QBaseMenu2(QDialog):
self.game_model = game_model
self.objectName = "menuDialogue"
if self.cp.captured:
if self.cp.captured.is_blue:
self.deliveryEvent = None
self.setWindowIcon(EVENT_ICONS["capture"])
@ -146,14 +146,14 @@ class QBaseMenu2(QDialog):
bottom_row = QHBoxLayout()
main_layout.addLayout(bottom_row)
if FlightType.OCA_RUNWAY in self.cp.mission_types(for_player=True):
if FlightType.OCA_RUNWAY in self.cp.mission_types(for_player=Player.BLUE):
runway_attack_button = QPushButton("Attack airfield")
bottom_row.addWidget(runway_attack_button)
runway_attack_button.setProperty("style", "btn-danger")
runway_attack_button.clicked.connect(self.new_package)
if self.cp.captured and self.has_transfer_destinations:
if self.cp.captured.is_blue and self.has_transfer_destinations:
transfer_button = QPushButton("Transfer Units")
transfer_button.setProperty("style", "btn-success")
bottom_row.addWidget(transfer_button)
@ -194,7 +194,7 @@ class QBaseMenu2(QDialog):
u.revive(events)
else:
self.cp.capture(
self.game_model.game, events, for_player=not self.cp.captured
self.game_model.game, events, for_player=self.cp.captured.opponent
)
mrp = MissionResultsProcessor(self.game_model.game)
mrp.redeploy_units(self.cp)
@ -232,7 +232,7 @@ class QBaseMenu2(QDialog):
@property
def can_repair_runway(self) -> bool:
return self.cp.captured and self.cp.runway_can_be_repaired
return self.cp.captured.is_blue and self.cp.runway_can_be_repaired
@property
def can_afford_runway_repair(self) -> bool:
@ -266,7 +266,7 @@ class QBaseMenu2(QDialog):
def update_repair_button(self) -> None:
self.repair_button.setVisible(True)
turns_remaining = self.cp.runway_status.repair_turns_remaining
if self.cp.captured and turns_remaining is not None:
if self.cp.captured.is_blue and turns_remaining is not None:
self.repair_button.setText("Repairing...")
self.repair_button.setDisabled(True)
return

View File

@ -12,7 +12,7 @@ class QBaseMenuTabs(QTabWidget):
def __init__(self, cp: ControlPoint, game_model: GameModel):
super(QBaseMenuTabs, self).__init__()
if not cp.captured:
if cp.captured.is_red:
self.intel = QIntelInfo(cp)
self.addTab(self.intel, "Intel")

View File

@ -4,6 +4,7 @@ from PySide6.QtWidgets import QGridLayout, QScrollArea, QVBoxLayout, QWidget
from game.dcs.groundunittype import GroundUnitType
from game.purchaseadapter import GroundUnitPurchaseAdapter
from game.theater import ControlPoint
from game.theater.player import Player
from qt_ui.models import GameModel
from qt_ui.windows.basemenu.UnitTransactionFrame import UnitTransactionFrame
@ -30,7 +31,7 @@ class QArmorRecruitmentMenu(UnitTransactionFrame[GroundUnitType]):
row = 0
unit_types = list(
set(self.game_model.game.faction_for(player=True).ground_units)
set(self.game_model.game.faction_for(player=Player.BLUE).ground_units)
)
unit_types.sort(key=lambda u: u.display_name)
for row, unit_type in enumerate(unit_types):

View File

@ -30,7 +30,7 @@ class QGroundForcesStrategy(QGroupBox):
layout = QVBoxLayout()
for enemy_cp in self.cp.connected_points:
if not enemy_cp.captured:
if enemy_cp.captured.is_red:
layout.addWidget(QLabel(enemy_cp.name))
layout.addWidget(QGroundForcesStrategySelector(self.cp, enemy_cp))
if self.game.settings.enable_frontline_cheats:

View File

@ -12,7 +12,7 @@ from PySide6.QtWidgets import (
import qt_ui.uiconstants as CONST
from game.game import Game
from game.income import BuildingIncome, Income
from game.theater import ControlPoint
from game.theater import ControlPoint, Player
class QHorizontalSeparationLine(QFrame):
@ -26,7 +26,7 @@ class QHorizontalSeparationLine(QFrame):
class FinancesLayout(QGridLayout):
def __init__(self, game: Game, player: bool, total_at_top: bool = False) -> None:
def __init__(self, game: Game, player: Player, total_at_top: bool = False) -> None:
super().__init__()
self.row = itertools.count(0)
@ -112,4 +112,4 @@ class QFinancesMenu(QDialog):
self.setWindowIcon(CONST.ICONS["Money"])
self.setMinimumSize(450, 200)
self.setLayout(FinancesLayout(game, player=True))
self.setLayout(FinancesLayout(game, player=Player.BLUE))

View File

@ -17,7 +17,7 @@ from PySide6.QtWidgets import (
)
from game.game import Game
from game.theater import ParkingType
from game.theater import ParkingType, Player
from qt_ui.uiconstants import ICONS
from qt_ui.windows.finances.QFinancesMenu import FinancesLayout
@ -45,7 +45,7 @@ class ScrollingFrame(QFrame):
class EconomyIntelTab(ScrollingFrame):
def __init__(self, game: Game, player: bool) -> None:
def __init__(self, game: Game, player: Player) -> None:
super().__init__()
self.addLayout(FinancesLayout(game, player=player, total_at_top=True))
@ -75,7 +75,7 @@ class IntelTableLayout(QGridLayout):
class AircraftIntelLayout(IntelTableLayout):
def __init__(self, game: Game, player: bool) -> None:
def __init__(self, game: Game, player: Player) -> None:
super().__init__()
total = 0
@ -102,13 +102,13 @@ class AircraftIntelLayout(IntelTableLayout):
class AircraftIntelTab(ScrollingFrame):
def __init__(self, game: Game, player: bool) -> None:
def __init__(self, game: Game, player: Player) -> None:
super().__init__()
self.addLayout(AircraftIntelLayout(game, player=player))
class ArmyIntelLayout(IntelTableLayout):
def __init__(self, game: Game, player: bool) -> None:
def __init__(self, game: Game, player: Player) -> None:
super().__init__()
total = 0
@ -131,13 +131,13 @@ class ArmyIntelLayout(IntelTableLayout):
class ArmyIntelTab(ScrollingFrame):
def __init__(self, game: Game, player: bool) -> None:
def __init__(self, game: Game, player: Player) -> None:
super().__init__()
self.addLayout(ArmyIntelLayout(game, player=player))
class IntelTabs(QTabWidget):
def __init__(self, game: Game, player: bool):
def __init__(self, game: Game, player: Player):
super().__init__()
self.addTab(EconomyIntelTab(game, player), "Economy")
@ -150,7 +150,7 @@ class IntelWindow(QDialog):
super().__init__()
self.game = game
self.player = True
self.player = Player.BLUE
self.setModal(True)
self.setWindowTitle("Intelligence")
self.setWindowIcon(ICONS["Statistics"])
@ -162,7 +162,7 @@ class IntelWindow(QDialog):
self.refresh_layout()
def on_faction_changed(self) -> None:
self.player = not self.player
self.player = self.player.opponent
self.refresh_layout()
def refresh_layout(self) -> None:
@ -174,7 +174,7 @@ class IntelWindow(QDialog):
# Add the new layout
own_faction = QCheckBox("Enemy Info")
own_faction.setChecked(not self.player)
own_faction.setChecked(self.player.is_red)
own_faction.stateChanged.connect(self.on_faction_changed)
intel_tabs = IntelTabs(self.game, self.player)

View File

@ -78,7 +78,11 @@ class QFlightCreator(QDialog):
layout.addLayout(QLabeledWidget("Squadron:", self.squadron_selector))
self.divert = QArrivalAirfieldSelector(
[cp for cp in game.theater.controlpoints if cp.captured == is_ownfor],
[
cp
for cp in game.theater.controlpoints
if cp.captured.is_blue == is_ownfor
],
self.aircraft_selector.currentData(),
"None",
)

View File

@ -84,7 +84,7 @@ class FlightPlanPropertiesGroup(QGroupBox):
layout.addLayout(QLabeledWidget("Arrival:", arrival_label))
self.divert = QArrivalAirfieldSelector(
[cp for cp in game.theater.controlpoints if cp.captured],
[cp for cp in game.theater.controlpoints if cp.captured.is_blue],
flight.unit_type,
"None",
)

View File

@ -22,6 +22,7 @@ from game.ato.flighttype import FlightType
from game.ato.flightwaypoint import FlightWaypoint
from game.ato.loadouts import Loadout
from game.ato.package import Package
from game.theater import Player
from qt_ui.windows.mission.flight.waypoints.QFlightWaypointList import (
QFlightWaypointList,
)
@ -36,7 +37,7 @@ class QFlightWaypointTab(QFrame):
def __init__(self, game: Game, package: Package, flight: Flight):
super(QFlightWaypointTab, self).__init__()
self.game = game
self.coalition = game.coalition_for(player=True)
self.coalition = game.coalition_for(player=Player.BLUE)
self.package = package
self.flight = flight
@ -61,7 +62,7 @@ class QFlightWaypointTab(QFrame):
rlayout.addWidget(QLabel("<small>AI compatible</small>"))
self.recreate_buttons.clear()
for task in self.package.target.mission_types(for_player=True):
for task in self.package.target.mission_types(for_player=Player.BLUE):
if task == FlightType.AIR_ASSAULT and not self.game.settings.plugin_option(
"ctld"
):