Add major/minor versioning of the campaign schema.

Many of the schema version increases are just to add new features that
don't render old campaigns obsolete. Convert the version number to a
major/minor format so we can detect the difference between changes that
render old campaigns obsolete (major versions) and new features that
will not work on older builds of Liberation (minor versions).
This commit is contained in:
Dan Albert 2021-05-17 23:49:08 -07:00
parent f63d218aae
commit 8076206a90
3 changed files with 25 additions and 7 deletions

View File

@ -53,4 +53,4 @@ VERSION = _build_version_string()
#: by a blue circular TriggerZone, campaign creation will fail. Blue circular #: by a blue circular TriggerZone, campaign creation will fail. Blue circular
#: TriggerZones must also have their first property's value field define the type of #: TriggerZones must also have their first property's value field define the type of
#: objective (a valid value for a building TGO category, from `game.db.PRICES`). #: objective (a valid value for a building TGO category, from `game.db.PRICES`).
CAMPAIGN_FORMAT_VERSION = 4 CAMPAIGN_FORMAT_VERSION = (4, 0)

View File

@ -4,15 +4,16 @@ import json
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Union, Tuple
import packaging.version
from PySide2 import QtGui from PySide2 import QtGui
from PySide2.QtCore import QItemSelectionModel from PySide2.QtCore import QItemSelectionModel
from PySide2.QtGui import QStandardItem, QStandardItemModel from PySide2.QtGui import QStandardItem, QStandardItemModel
from PySide2.QtWidgets import QAbstractItemView, QListView from PySide2.QtWidgets import QAbstractItemView, QListView
import qt_ui.uiconstants as CONST import qt_ui.uiconstants as CONST
from game.theater import ConflictTheater, MizCampaignLoader from game.theater import ConflictTheater
from game.version import CAMPAIGN_FORMAT_VERSION from game.version import CAMPAIGN_FORMAT_VERSION
PERF_FRIENDLY = 0 PERF_FRIENDLY = 0
@ -31,7 +32,7 @@ class Campaign:
#: The revision of the campaign format the campaign was built for. We do not attempt #: The revision of the campaign format the campaign was built for. We do not attempt
#: to migrate old campaigns, but this is used to show a warning in the UI when #: to migrate old campaigns, but this is used to show a warning in the UI when
#: selecting a campaign that is not up to date. #: selecting a campaign that is not up to date.
version: int version: Tuple[int, int]
recommended_player_faction: str recommended_player_faction: str
recommended_enemy_faction: str recommended_enemy_faction: str
@ -45,12 +46,20 @@ class Campaign:
data = json.load(campaign_file) data = json.load(campaign_file)
sanitized_theater = data["theater"].replace(" ", "") sanitized_theater = data["theater"].replace(" ", "")
version_field = data.get("version", "0")
try:
version = packaging.version.parse(version_field)
except TypeError:
logging.warning(
f"Non-string campaign version in {path}. Parse may be incorrect."
)
version = packaging.version.parse(str(version_field))
return cls( return cls(
data["name"], data["name"],
f"Terrain_{sanitized_theater}", f"Terrain_{sanitized_theater}",
data.get("authors", "???"), data.get("authors", "???"),
data.get("description", ""), data.get("description", ""),
data.get("version", 0), (version.major, version.minor),
data.get("recommended_player_faction", "USA 2005"), data.get("recommended_player_faction", "USA 2005"),
data.get("recommended_enemy_faction", "Russia 1990"), data.get("recommended_enemy_faction", "Russia 1990"),
data.get("performance", 0), data.get("performance", 0),
@ -63,8 +72,15 @@ class Campaign:
@property @property
def is_out_of_date(self) -> bool: def is_out_of_date(self) -> bool:
"""Returns True if this campaign is not up to date with the latest format.""" """Returns True if this campaign is not up to date with the latest format.
return self.version < CAMPAIGN_FORMAT_VERSION
This is more permissive than is_from_future, which is sensitive to minor version
bumps (the old game definitely doesn't support the minor features added in the
new version, and the campaign may require them. However, the minor version only
indicates *optional* new features, so we do not need to mark out of date
campaigns as incompatible if they are within the same major version.
"""
return self.version[0] < CAMPAIGN_FORMAT_VERSION[0]
@property @property
def is_from_future(self) -> bool: def is_from_future(self) -> bool:

View File

@ -13,12 +13,14 @@ MarkupSafe==1.1.1
mypy==0.812 mypy==0.812
mypy-extensions==0.4.3 mypy-extensions==0.4.3
nodeenv==1.5.0 nodeenv==1.5.0
packaging==20.9
pathspec==0.8.1 pathspec==0.8.1
pefile==2019.4.18 pefile==2019.4.18
Pillow==8.1.1 Pillow==8.1.1
pre-commit==2.10.1 pre-commit==2.10.1
pyinstaller==4.3 pyinstaller==4.3
pyinstaller-hooks-contrib==2021.1 pyinstaller-hooks-contrib==2021.1
pyparsing==2.4.7
pyproj==3.0.1 pyproj==3.0.1
PySide2==5.15.2 PySide2==5.15.2
pywin32-ctypes==0.2.0 pywin32-ctypes==0.2.0