diff --git a/changelog.md b/changelog.md
index 33a77730..59ee06e0 100644
--- a/changelog.md
+++ b/changelog.md
@@ -6,6 +6,7 @@ Saves from 2.5 are not compatible with 2.6.
* **[Campaign]** Ground units can now be transferred by road. See https://github.com/Khopa/dcs_liberation/wiki/Unit-Transfers for more information.
* **[Campaign]** Ground units can no longer be sold. To move units to a new location, transfer them.
+* **[UI]** Campaigns generated for an older or newer version of the game will now be marked as incompatible. They can still be played, but bugs may be present.
## Fixes
diff --git a/game/theater/conflicttheater.py b/game/theater/conflicttheater.py
index 80194189..07c17aa9 100644
--- a/game/theater/conflicttheater.py
+++ b/game/theater/conflicttheater.py
@@ -84,6 +84,15 @@ def pairwise(iterable):
class MizCampaignLoader:
+ #: The latest version of the campaign format. Increment this version whenever all
+ #: existing campaigns should be flagged as incompatible in the UI. We will still
+ #: attempt to load old campaigns, but this provides a warning to the user that the
+ #: campaign may not work correctly.
+ #:
+ #: There is no verification that the campaign author updated their campaign
+ #: correctly, this is just a UI hint.
+ VERSION = 1
+
BLUE_COUNTRY = CombinedJointTaskForcesBlue()
RED_COUNTRY = CombinedJointTaskForcesRed()
diff --git a/qt_ui/windows/newgame/QCampaignList.py b/qt_ui/windows/newgame/QCampaignList.py
index b97f2ef0..44ed7175 100644
--- a/qt_ui/windows/newgame/QCampaignList.py
+++ b/qt_ui/windows/newgame/QCampaignList.py
@@ -4,7 +4,7 @@ import json
import logging
from dataclasses import dataclass
from pathlib import Path
-from typing import Any, Dict, List, Union
+from typing import Any, Dict, List, Optional, Union
from PySide2 import QtGui
from PySide2.QtCore import QItemSelectionModel
@@ -12,7 +12,7 @@ from PySide2.QtGui import QStandardItem, QStandardItemModel
from PySide2.QtWidgets import QAbstractItemView, QListView
import qt_ui.uiconstants as CONST
-from game.theater import ConflictTheater
+from game.theater import ConflictTheater, MizCampaignLoader
PERF_FRIENDLY = 0
PERF_MEDIUM = 1
@@ -26,6 +26,12 @@ class Campaign:
icon_name: str
authors: str
description: str
+
+ #: 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
+ #: selecting a campaign that is not up to date.
+ version: int
+
recommended_player_faction: str
recommended_enemy_faction: str
performance: Union[PERF_FRIENDLY, PERF_MEDIUM, PERF_HARD, PERF_NASA]
@@ -43,6 +49,7 @@ class Campaign:
f"Terrain_{sanitized_theater}",
data.get("authors", "???"),
data.get("description", ""),
+ data.get("version", 0),
data.get("recommended_player_faction", "USA 2005"),
data.get("recommended_enemy_faction", "Russia 1990"),
data.get("performance", 0),
@@ -53,6 +60,27 @@ class Campaign:
def load_theater(self) -> ConflictTheater:
return ConflictTheater.from_json(self.path.parent, self.data)
+ @property
+ def is_out_of_date(self) -> bool:
+ """Returns True if this campaign is not up to date with the latest format."""
+ return self.version < MizCampaignLoader.VERSION
+
+ @property
+ def is_from_future(self) -> bool:
+ """Returns True if this campaign is newer than the supported format."""
+ return self.version > MizCampaignLoader.VERSION
+
+ @property
+ def is_compatible(self) -> bool:
+ """Returns True is this campaign was built for this version of the game."""
+ if not self.version:
+ return False
+ if self.is_out_of_date:
+ return False
+ if self.is_from_future:
+ return False
+ return True
+
def load_campaigns() -> List[Campaign]:
campaign_dir = Path("resources\\campaigns")
@@ -73,7 +101,11 @@ class QCampaignItem(QStandardItem):
super(QCampaignItem, self).__init__()
self.setIcon(QtGui.QIcon(CONST.ICONS[campaign.icon_name]))
self.setEditable(False)
- self.setText(campaign.name)
+ if campaign.is_compatible:
+ name = campaign.name
+ else:
+ name = f"[INCOMPATIBLE] {campaign.name}"
+ self.setText(name)
class QCampaignList(QListView):
diff --git a/qt_ui/windows/newgame/QNewGameWizard.py b/qt_ui/windows/newgame/QNewGameWizard.py
index 0ff28d59..c3cac473 100644
--- a/qt_ui/windows/newgame/QNewGameWizard.py
+++ b/qt_ui/windows/newgame/QNewGameWizard.py
@@ -321,7 +321,7 @@ class TheaterConfiguration(QtWidgets.QWizardPage):
# Faction description
self.campaignMapDescription = QTextEdit("")
self.campaignMapDescription.setReadOnly(True)
- self.campaignMapDescription.setMaximumHeight(100)
+ self.campaignMapDescription.setMaximumHeight(200)
self.performanceText = QTextEdit("")
self.performanceText.setReadOnly(True)
diff --git a/resources/ui/templates/campaigntemplate_EN.j2 b/resources/ui/templates/campaigntemplate_EN.j2
index 2ec97f6e..6cd913c7 100644
--- a/resources/ui/templates/campaigntemplate_EN.j2
+++ b/resources/ui/templates/campaigntemplate_EN.j2
@@ -1,8 +1,24 @@
-Author(s): {{ campaign.authors }}
-
+
Author(s): {{ campaign.authors }}
-Default factions: - {{campaign.recommended_player_faction}} VS {{campaign.recommended_enemy_faction}} -This campaign was created for an unknown version +of the game.
+You can still attempt to play this campaign but there may be game breaking +{% elif campaign.is_out_of_date %} +
This campaign was created for an older version +of the game.
+You can still attempt to play this campaign but there may be game breaking +bugs.
+{% elif campaign.is_from_future %} +This campaign was created for a newer version +of the game.
+You can still attempt to play this campaign but there may be game breaking +bugs.
+{% else %} +This campaign is up to date.
+{% endif %} -{{ campaign.description|safe }} +Default factions:
+{{campaign.recommended_player_faction}} VS {{campaign.recommended_enemy_faction}}
+ +{{ campaign.description|safe }} \ No newline at end of file