mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Add an engine "compatible" version
When a patch version is released, it must declare compatibility with the
previous patch versions of the same main release. This allows players to
keep replaying their games and to keep playing online with users of
other patches of the same main release.
This should have anticipated for dae7a8c394
This commit is contained in:
parent
50f6da2a13
commit
866d6f0527
17 changed files with 81 additions and 34 deletions
|
|
@ -24,7 +24,8 @@ class PersistentMatchSettings
|
|||
Engine.FileExists(this.filename) &&
|
||||
Engine.ReadJSONFile(this.filename);
|
||||
|
||||
const persistedSettings = data?.engine_info?.engine_version == this.engineInfo.engine_version &&
|
||||
const persistedSettings = data?.engine_info?.engine_serialization_version &&
|
||||
data.engine_info.engine_serialization_version == this.engineInfo.engine_serialization_version &&
|
||||
hasSameMods(data?.engine_info?.mods, this.engineInfo.mods) &&
|
||||
data.attributes || {};
|
||||
|
||||
|
|
|
|||
|
|
@ -174,8 +174,8 @@ class SavegameList
|
|||
isCompatibleSavegame(metadata, engineInfo)
|
||||
{
|
||||
return engineInfo &&
|
||||
metadata.engine_version &&
|
||||
metadata.engine_version == engineInfo.engine_version &&
|
||||
metadata.engine_serialization_version &&
|
||||
metadata.engine_serialization_version == engineInfo.engine_serialization_version &&
|
||||
hasSameMods(metadata.mods, engineInfo.mods);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ class SavegameLoader
|
|||
// Check compatibility before really loading it
|
||||
const engineInfo = Engine.GetEngineInfo();
|
||||
const sameMods = hasSameMods(metadata.mods, engineInfo.mods);
|
||||
const sameEngineVersion = metadata.engine_version && metadata.engine_version == engineInfo.engine_version;
|
||||
const compatibleEngineVersions = metadata.engine_serialization_version && metadata.engine_serialization_version == engineInfo.engine_serialization_version;
|
||||
|
||||
if (sameEngineVersion && sameMods)
|
||||
if (compatibleEngineVersions && sameMods)
|
||||
{
|
||||
this.closePageCallback(gameId);
|
||||
return;
|
||||
|
|
@ -35,14 +35,17 @@ class SavegameLoader
|
|||
// Version not compatible ... ask for confirmation
|
||||
let message = "";
|
||||
|
||||
if (!sameEngineVersion)
|
||||
if (metadata.engine_version)
|
||||
message += sprintf(translate("This savegame needs 0 A.D. version %(requiredVersion)s, while you are running version %(currentVersion)s."), {
|
||||
"requiredVersion": metadata.engine_version,
|
||||
"currentVersion": engineInfo.engine_version
|
||||
if (!compatibleEngineVersions)
|
||||
{
|
||||
if (metadata.engine_serialization_version)
|
||||
message += sprintf(translate("This savegame needs 0 A.D. version %(requiredCompatibleVersion)s or compatible. You are running version %(currentVersion)s, compatible down to %(compatibleVersion)s."), {
|
||||
"requiredCompatibleVersion": metadata.engine_serialization_version,
|
||||
"currentVersion": engineInfo.engine_version,
|
||||
"compatibleVersion": engineInfo.engine_serialization_version,
|
||||
}) + "\n";
|
||||
else
|
||||
message += translate("This savegame needs an older version of 0 A.D.") + "\n";
|
||||
}
|
||||
|
||||
if (!sameMods)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ function reallyStartVisualReplay(replayDirectory)
|
|||
function displayReplayCompatibilityError(replay)
|
||||
{
|
||||
var errMsg;
|
||||
if (replayHasSameEngineVersion(replay))
|
||||
if (replayHasCompatibleEngineVersion(replay))
|
||||
{
|
||||
const gameMods = replay.attribs.mods || [];
|
||||
errMsg = translate("This replay needs a different sequence of mods:") + "\n" +
|
||||
|
|
@ -93,8 +93,9 @@ function displayReplayCompatibilityError(replay)
|
|||
else
|
||||
{
|
||||
errMsg = translate("This replay is not compatible with your version of the game!") + "\n";
|
||||
errMsg += sprintf(translate("Your version: %(version)s"), { "version": g_EngineInfo.engine_version }) + "\n";
|
||||
errMsg += sprintf(translate("Required version: %(version)s"), { "version": replay.attribs.engine_version });
|
||||
errMsg += sprintf(translate("Your version: %(version)s, compatible down to %(compatibleVersion)s"), { "version": g_EngineInfo.engine_version, "compatibleVersion": g_EngineInfo.engine_serialization_version }) + "\n";
|
||||
if (replay.attribs.engine_serialization_version)
|
||||
errMsg += sprintf(translate("Replay version: %(version)s"), { "version": replay.attribs.engine_serialization_version });
|
||||
}
|
||||
|
||||
messageBox(500, 200, errMsg, translate("Incompatible replay"));
|
||||
|
|
|
|||
|
|
@ -360,13 +360,13 @@ function getReplayDuration(replay)
|
|||
*/
|
||||
function isReplayCompatible(replay)
|
||||
{
|
||||
return replayHasSameEngineVersion(replay) && hasSameMods(replay.attribs.mods, g_EngineInfo.mods);
|
||||
return replayHasCompatibleEngineVersion(replay) && hasSameMods(replay.attribs.mods, g_EngineInfo.mods);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if we can start the given replay with the currently loaded mods.
|
||||
*/
|
||||
function replayHasSameEngineVersion(replay)
|
||||
function replayHasCompatibleEngineVersion(replay)
|
||||
{
|
||||
return replay.attribs.engine_version && replay.attribs.engine_version == g_EngineInfo.engine_version;
|
||||
return replay.attribs.engine_serialization_version && replay.attribs.engine_serialization_version == g_EngineInfo.engine_serialization_version;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,46 @@
|
|||
#ifndef INCLUDED_BUILDVERSION
|
||||
#define INCLUDED_BUILDVERSION
|
||||
|
||||
#define PYROGENESIS_VERSION "0.28.0"
|
||||
#define PYROGENESIS_VERSION_WORD 0,28,0,0
|
||||
/*
|
||||
* The version of the game is built as MAJOR.MINOR.PATCH, where
|
||||
* - MAJOR is 0
|
||||
* - MINOR is incremented for each main release
|
||||
* - PATCH is incremented whenever we release a bugfix patch release
|
||||
*
|
||||
* TODO: This does not respect semver.
|
||||
*/
|
||||
#define PS_VERSION_MAJOR 0
|
||||
#define PS_VERSION_MINOR 28
|
||||
#define PS_VERSION_PATCH 0
|
||||
|
||||
/*
|
||||
* When a patch version is released, it should stay simulation-compatible with
|
||||
* all the previous patch releases of the same main release. This allows players
|
||||
* to keep replaying their games and playing online against other versions of the
|
||||
* same main release.
|
||||
*
|
||||
* The "compatible patch version" is the earliest patch version with which the
|
||||
* current version is compatible. It should ideally stay at 0 all the time.
|
||||
* If the compatible patch version is bumped:
|
||||
* - a new lobby room must be opened
|
||||
* - the version of the public '0ad' mod must be bumped
|
||||
* - incompatible replays and savegames will be rotated as new folders are created
|
||||
*
|
||||
* This does not describe compatibility with the modding API. Modders are advised
|
||||
* to rely on the actual engine version.
|
||||
*/
|
||||
#define PS_SERIALIZATION_COMPATIBLE_PATCH 0
|
||||
|
||||
#define STR(ver) #ver
|
||||
#define DOTCONCAT(v1, v2, v3) STR(v1) "." STR(v2) "." STR(v3)
|
||||
|
||||
// See definition of PS_VERSION_* for details
|
||||
#define PS_VERSION DOTCONCAT(PS_VERSION_MAJOR, PS_VERSION_MINOR, PS_VERSION_PATCH)
|
||||
// Used in Windows .rc file
|
||||
#define PS_VERSION_WORD PS_VERSION_MAJOR,PS_VERSION_MINOR,PS_VERSION_PATCH,0
|
||||
// See definition of PS_SERIALIZATION_COMPATIBLE_PATCH for details
|
||||
#define PS_SERIALIZATION_VERSION DOTCONCAT(PS_VERSION_MAJOR, PS_VERSION_MINOR, PS_SERIALIZATION_COMPATIBLE_PATCH)
|
||||
|
||||
extern wchar_t build_version[];
|
||||
|
||||
#endif // INCLUDED_BUILDVERSION
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
#endif
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION PYROGENESIS_VERSION_WORD
|
||||
PRODUCTVERSION PYROGENESIS_VERSION_WORD
|
||||
FILEVERSION PS_VERSION_WORD
|
||||
PRODUCTVERSION PS_VERSION_WORD
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
FILEFLAGS VER_DEBUG
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
|
|
@ -23,12 +23,12 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Wildfire Games"
|
||||
VALUE "FileDescription", "Pyrogenesis engine"
|
||||
VALUE "FileVersion", PYROGENESIS_VERSION
|
||||
VALUE "FileVersion", PS_VERSION
|
||||
VALUE "InternalName", "pyrogenesis.rc"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2025 Wildfire Games"
|
||||
VALUE "OriginalFilename", "pyrogenesis.rc"
|
||||
VALUE "ProductName", "Pyrogenesis"
|
||||
VALUE "ProductVersion", PYROGENESIS_VERSION
|
||||
VALUE "ProductVersion", PS_VERSION
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ XmppClient::XmppClient(const ScriptInterface* scriptInterface, const std::string
|
|||
|
||||
m_client->registerConnectionListener(this);
|
||||
m_client->setPresence(gloox::Presence::Available, -1);
|
||||
m_client->disco()->setVersion("Pyrogenesis", PYROGENESIS_VERSION);
|
||||
m_client->disco()->setVersion("Pyrogenesis", PS_SERIALIZATION_VERSION);
|
||||
m_client->disco()->setIdentity("client", "bot");
|
||||
m_client->setCompression(false);
|
||||
|
||||
|
|
|
|||
|
|
@ -561,7 +561,9 @@ static void RunGameOrAtlas(const std::span<const char* const> argv)
|
|||
|
||||
if (args.Has("version"))
|
||||
{
|
||||
debug_printf("Pyrogenesis %s\n", PYROGENESIS_VERSION);
|
||||
debug_printf("Pyrogenesis %s\n", PS_VERSION);
|
||||
if (std::strcmp(PS_VERSION, PS_SERIALIZATION_VERSION) != 0)
|
||||
debug_printf("Compatible down to patch %s\n", PS_SERIALIZATION_VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ Message CreateHandshake() {
|
|||
handshake.m_Magic = PS_PROTOCOL_MAGIC;
|
||||
|
||||
handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
|
||||
handshake.m_EngineVersion = PYROGENESIS_VERSION;
|
||||
handshake.m_EngineVersion = PS_SERIALIZATION_VERSION;
|
||||
|
||||
for (const Mod::ModData* mod : Mod::Instance().GetEnabledModsData())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ void StartNetworkHost(const CStrW& playerName, const u16 serverPort, const CStr&
|
|||
* TODO: it should be possible to implement SRP or something along those lines to completely protect from this,
|
||||
* but the cost/benefit ratio is probably not worth it.
|
||||
*/
|
||||
CStr hashedPass = HashCryptographically(password, hostJID + password + PYROGENESIS_VERSION);
|
||||
CStr hashedPass = HashCryptographically(password, hostJID + password + PS_SERIALIZATION_VERSION);
|
||||
g_NetServer->SetPassword(hashedPass);
|
||||
g_NetClient->SetHostJID(hostJID);
|
||||
g_NetClient->SetGamePassword(hashedPass);
|
||||
|
|
@ -176,7 +176,7 @@ void StartNetworkJoinLobby(const CStrW& playerName, const CStr& hostJID, const C
|
|||
ENSURE(!g_NetServer);
|
||||
ENSURE(!g_Game);
|
||||
|
||||
CStr hashedPass = HashCryptographically(password, hostJID + password + PYROGENESIS_VERSION);
|
||||
CStr hashedPass = HashCryptographically(password, hostJID + password + PS_SERIALIZATION_VERSION);
|
||||
g_Game = new CGame(true);
|
||||
g_NetClient = new CNetClient(g_Game);
|
||||
g_NetClient->SetUserName(playerName);
|
||||
|
|
|
|||
|
|
@ -77,8 +77,8 @@ CLogger::CLogger(std::ostream& mainLog, std::ostream& interestingLog, const bool
|
|||
m_InterestingLog{interestingLog},
|
||||
m_UseDebugPrintf{useDebugPrintf}
|
||||
{
|
||||
m_MainLog << html_header0 << PYROGENESIS_VERSION << ") Main log" << html_header1;
|
||||
m_InterestingLog << html_header0 << PYROGENESIS_VERSION << ") Main log (warnings and errors only)" << html_header1;
|
||||
m_MainLog << html_header0 << PS_VERSION << ") Main log" << html_header1;
|
||||
m_InterestingLog << html_header0 << PS_VERSION << ") Main log (warnings and errors only)" << html_header1;
|
||||
}
|
||||
|
||||
CLogger::~CLogger()
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ static void AppendAsciiFile(FILE* out, const OsPath& pathname)
|
|||
void psBundleLogs(FILE* f)
|
||||
{
|
||||
fwprintf(f, L"Build Version: %ls\n\n", build_version);
|
||||
fwprintf(f, L"Engine Version: %hs\n\n", PYROGENESIS_VERSION);
|
||||
fwprintf(f, L"Engine Version: %hs\n\n", PS_VERSION);
|
||||
|
||||
fwprintf(f, L"System info:\n\n");
|
||||
OsPath path1 = psLogDir()/"system_info.txt";
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ void CReplayLogger::StartGame(JS::MutableHandleValue attribs)
|
|||
Script::SetProperty(rq, attribs, "timestamp", (double)std::time(nullptr));
|
||||
|
||||
// Add engine version and currently loaded mods for sanity checks when replaying
|
||||
Script::SetProperty(rq, attribs, "engine_version", PYROGENESIS_VERSION);
|
||||
Script::SetProperty(rq, attribs, "engine_serialization_version", PS_SERIALIZATION_VERSION);
|
||||
JS::RootedValue mods(rq.cx);
|
||||
Script::ToJSVal(rq, &mods, g_Mods.GetEnabledModsData());
|
||||
Script::SetProperty(rq, attribs, "mods", mods);
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ Status SavedGames::Save(const CStrW& name, const CStrW& description, CSimulation
|
|||
Script::CreateObject(
|
||||
rq,
|
||||
&metadata,
|
||||
"engine_version", PYROGENESIS_VERSION,
|
||||
"engine_version", PS_VERSION,
|
||||
"engine_serialization_version", PS_SERIALIZATION_VERSION,
|
||||
"time", static_cast<double>(now),
|
||||
"playerID", g_Game->GetPlayerID(),
|
||||
"mods", mods,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ const u8 minimumReplayDuration = 3;
|
|||
|
||||
OsPath VisualReplay::GetDirectoryPath()
|
||||
{
|
||||
return Paths(g_CmdLineArgs).UserData() / "replays" / PYROGENESIS_VERSION;
|
||||
return Paths(g_CmdLineArgs).UserData() / "replays" / PS_SERIALIZATION_VERSION;
|
||||
}
|
||||
|
||||
OsPath VisualReplay::GetCacheFilePath()
|
||||
|
|
|
|||
|
|
@ -135,7 +135,8 @@ JS::Value GetEngineInfo(const ScriptInterface& scriptInterface)
|
|||
Script::CreateObject(
|
||||
rq,
|
||||
&metainfo,
|
||||
"engine_version", PYROGENESIS_VERSION,
|
||||
"engine_version", PS_VERSION,
|
||||
"engine_serialization_version", PS_SERIALIZATION_VERSION,
|
||||
"mods", mods);
|
||||
|
||||
Script::DeepFreezeObject(rq, metainfo);
|
||||
|
|
|
|||
Loading…
Reference in a new issue