mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 17:59:56 +00:00
Name all custom ESM format versions and add tests
This commit is contained in:
parent
b385f27f86
commit
080700f8fe
34 changed files with 354 additions and 129 deletions
|
@ -347,7 +347,7 @@ namespace ESSImport
|
||||||
|
|
||||||
ESM::ESMWriter writer;
|
ESM::ESMWriter writer;
|
||||||
|
|
||||||
writer.setFormat(ESM::SavedGame::sCurrentFormat);
|
writer.setFormatVersion(ESM::CurrentSaveGameFormatVersion);
|
||||||
|
|
||||||
std::ofstream stream(mOutFile, std::ios::out | std::ios::binary);
|
std::ofstream stream(mOutFile, std::ios::out | std::ios::binary);
|
||||||
// all unused
|
// all unused
|
||||||
|
|
|
@ -91,7 +91,7 @@ void CSMDoc::WriteHeaderStage::perform(int stage, Messages& messages)
|
||||||
|
|
||||||
// ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs
|
// ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs
|
||||||
// we use the format `0` for compatibility with old versions.
|
// we use the format `0` for compatibility with old versions.
|
||||||
mState.getWriter().setFormat(0);
|
mState.getWriter().setFormatVersion(ESM::DefaultFormatVersion);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -2232,7 +2232,7 @@ namespace CSMWorld
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant get(const Record<ESXRecordT>& record) const override { return record.get().mFormat; }
|
QVariant get(const Record<ESXRecordT>& record) const override { return record.get().mFormatVersion; }
|
||||||
|
|
||||||
bool isEditable() const override { return false; }
|
bool isEditable() const override { return false; }
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,21 +8,21 @@ void CSMWorld::MetaData::blank()
|
||||||
{
|
{
|
||||||
// ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs
|
// ESM::Header::CurrentFormat is `1` but since new records are not yet used in opencs
|
||||||
// we use the format `0` for compatibility with old versions.
|
// we use the format `0` for compatibility with old versions.
|
||||||
mFormat = 0;
|
mFormatVersion = ESM::DefaultFormatVersion;
|
||||||
mAuthor.clear();
|
mAuthor.clear();
|
||||||
mDescription.clear();
|
mDescription.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMWorld::MetaData::load(ESM::ESMReader& esm)
|
void CSMWorld::MetaData::load(ESM::ESMReader& esm)
|
||||||
{
|
{
|
||||||
mFormat = esm.getHeader().mFormat;
|
mFormatVersion = esm.getHeader().mFormatVersion;
|
||||||
mAuthor = esm.getHeader().mData.author;
|
mAuthor = esm.getHeader().mData.author;
|
||||||
mDescription = esm.getHeader().mData.desc;
|
mDescription = esm.getHeader().mData.desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSMWorld::MetaData::save(ESM::ESMWriter& esm) const
|
void CSMWorld::MetaData::save(ESM::ESMWriter& esm) const
|
||||||
{
|
{
|
||||||
esm.setFormat(mFormat);
|
esm.setFormatVersion(mFormatVersion);
|
||||||
esm.setAuthor(mAuthor);
|
esm.setAuthor(mAuthor);
|
||||||
esm.setDescription(mDescription);
|
esm.setDescription(mDescription);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
#define CSM_WOLRD_METADATA_H
|
#define CSM_WOLRD_METADATA_H
|
||||||
|
|
||||||
#include <components/esm/refid.hpp>
|
#include <components/esm/refid.hpp>
|
||||||
|
#include <components/esm3/formatversion.hpp>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
|
@ -16,7 +18,7 @@ namespace CSMWorld
|
||||||
{
|
{
|
||||||
ESM::RefId mId;
|
ESM::RefId mId;
|
||||||
|
|
||||||
int mFormat;
|
ESM::FormatVersion mFormatVersion;
|
||||||
std::string mAuthor;
|
std::string mAuthor;
|
||||||
std::string mDescription;
|
std::string mDescription;
|
||||||
|
|
||||||
|
|
|
@ -250,7 +250,7 @@ void MWState::StateManager::saveGame(const std::string& description, const Slot*
|
||||||
for (const std::string& contentFile : MWBase::Environment::get().getWorld()->getContentFiles())
|
for (const std::string& contentFile : MWBase::Environment::get().getWorld()->getContentFiles())
|
||||||
writer.addMaster(contentFile, 0); // not using the size information anyway -> use value of 0
|
writer.addMaster(contentFile, 0); // not using the size information anyway -> use value of 0
|
||||||
|
|
||||||
writer.setFormat(ESM::SavedGame::sCurrentFormat);
|
writer.setFormatVersion(ESM::CurrentSaveGameFormatVersion);
|
||||||
|
|
||||||
// all unused
|
// all unused
|
||||||
writer.setVersion(0);
|
writer.setVersion(0);
|
||||||
|
@ -400,7 +400,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
|
||||||
ESM::ESMReader reader;
|
ESM::ESMReader reader;
|
||||||
reader.open(filepath);
|
reader.open(filepath);
|
||||||
|
|
||||||
if (reader.getFormat() > ESM::SavedGame::sCurrentFormat)
|
if (reader.getFormatVersion() > ESM::CurrentSaveGameFormatVersion)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade "
|
"This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade "
|
||||||
"to the newest OpenMW version to load this file.");
|
"to the newest OpenMW version to load this file.");
|
||||||
|
|
|
@ -55,7 +55,7 @@ namespace MWWorld
|
||||||
if (!mMasterFileFormat.has_value()
|
if (!mMasterFileFormat.has_value()
|
||||||
&& (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm")
|
&& (Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".esm")
|
||||||
|| Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame")))
|
|| Misc::StringUtils::ciEndsWith(reader->getName().u8string(), u8".omwgame")))
|
||||||
mMasterFileFormat = reader->getFormat();
|
mMasterFileFormat = reader->getFormatVersion();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ESM::Format::Tes4:
|
case ESM::Format::Tes4:
|
||||||
|
|
|
@ -327,10 +327,10 @@ namespace MWWorld
|
||||||
// this is the one object we can not silently drop.
|
// this is the one object we can not silently drop.
|
||||||
throw std::runtime_error("invalid player state record (object state)");
|
throw std::runtime_error("invalid player state record (object state)");
|
||||||
}
|
}
|
||||||
if (reader.getFormat() < 17)
|
if (reader.getFormatVersion() <= ESM::MaxClearModifiersFormatVersion)
|
||||||
convertMagicEffects(
|
convertMagicEffects(
|
||||||
player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats);
|
player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats);
|
||||||
else if (reader.getFormat() < 20)
|
else if (reader.getFormatVersion() <= ESM::MaxOldCreatureStatsFormatVersion)
|
||||||
convertStats(player.mObject.mCreatureStats);
|
convertStats(player.mObject.mCreatureStats);
|
||||||
|
|
||||||
if (!player.mObject.mEnabled)
|
if (!player.mObject.mEnabled)
|
||||||
|
@ -353,7 +353,7 @@ namespace MWWorld
|
||||||
saveStats();
|
saveStats();
|
||||||
setWerewolfStats();
|
setWerewolfStats();
|
||||||
}
|
}
|
||||||
else if (reader.getFormat() < 19)
|
else if (reader.getFormatVersion() <= ESM::MaxOldSkillsAndAttributesFormatVersion)
|
||||||
{
|
{
|
||||||
setWerewolfStats();
|
setWerewolfStats();
|
||||||
if (player.mSetWerewolfAcrobatics)
|
if (player.mSetWerewolfAcrobatics)
|
||||||
|
|
|
@ -921,8 +921,7 @@ namespace MWWorld
|
||||||
{
|
{
|
||||||
if (ESM::REC_WTHR == type)
|
if (ESM::REC_WTHR == type)
|
||||||
{
|
{
|
||||||
static const int oldestCompatibleSaveFormat = 2;
|
if (reader.getFormatVersion() <= ESM::MaxOldWeatherFormatVersion)
|
||||||
if (reader.getFormat() < oldestCompatibleSaveFormat)
|
|
||||||
{
|
{
|
||||||
// Weather state isn't really all that important, so to preserve older save games, we'll just discard
|
// Weather state isn't really all that important, so to preserve older save games, we'll just discard
|
||||||
// the older weather records, rather than fail to handle the record.
|
// the older weather records, rather than fail to handle the record.
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <components/esm3/esmreader.hpp>
|
#include <components/esm3/esmreader.hpp>
|
||||||
#include <components/esm3/esmwriter.hpp>
|
#include <components/esm3/esmwriter.hpp>
|
||||||
|
#include <components/esm3/formatversion.hpp>
|
||||||
#include <components/esm3/readerscache.hpp>
|
#include <components/esm3/readerscache.hpp>
|
||||||
#include <components/lua/configuration.hpp>
|
#include <components/lua/configuration.hpp>
|
||||||
#include <components/lua/serialization.hpp>
|
#include <components/lua/serialization.hpp>
|
||||||
|
@ -152,7 +153,7 @@ namespace
|
||||||
writer.setAuthor("");
|
writer.setAuthor("");
|
||||||
writer.setDescription("");
|
writer.setDescription("");
|
||||||
writer.setRecordCount(1);
|
writer.setRecordCount(1);
|
||||||
writer.setFormat(ESM::Header::CurrentFormat);
|
writer.setFormatVersion(ESM::CurrentContentFormatVersion);
|
||||||
writer.setVersion();
|
writer.setVersion();
|
||||||
writer.addMaster("morrowind.esm", 0);
|
writer.addMaster("morrowind.esm", 0);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
#include <boost/program_options/options_description.hpp>
|
#include <boost/program_options/options_description.hpp>
|
||||||
|
@ -16,6 +17,7 @@
|
||||||
#include <components/esm4/loadstat.hpp>
|
#include <components/esm4/loadstat.hpp>
|
||||||
#include <components/esm4/reader.hpp>
|
#include <components/esm4/reader.hpp>
|
||||||
#include <components/esm4/readerutils.hpp>
|
#include <components/esm4/readerutils.hpp>
|
||||||
|
#include <components/esm4/typetraits.hpp>
|
||||||
#include <components/files/configurationmanager.hpp>
|
#include <components/files/configurationmanager.hpp>
|
||||||
#include <components/files/conversion.hpp>
|
#include <components/files/conversion.hpp>
|
||||||
#include <components/loadinglistener/loadinglistener.hpp>
|
#include <components/loadinglistener/loadinglistener.hpp>
|
||||||
|
@ -238,20 +240,21 @@ TEST_F(ContentFileTest, autocalc_test)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// Base class for tests of ESMStore that do not rely on external content files
|
/// Base class for tests of ESMStore that do not rely on external content files
|
||||||
|
template <class T>
|
||||||
struct StoreTest : public ::testing::Test
|
struct StoreTest : public ::testing::Test
|
||||||
{
|
{
|
||||||
protected:
|
|
||||||
MWWorld::ESMStore mEsmStore;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
TYPED_TEST_SUITE_P(StoreTest);
|
||||||
|
|
||||||
/// Create an ESM file in-memory containing the specified record.
|
/// Create an ESM file in-memory containing the specified record.
|
||||||
/// @param deleted Write record with deleted flag?
|
/// @param deleted Write record with deleted flag?
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::unique_ptr<std::istream> getEsmFile(T record, bool deleted)
|
std::unique_ptr<std::istream> getEsmFile(T record, bool deleted, ESM::FormatVersion formatVersion)
|
||||||
{
|
{
|
||||||
ESM::ESMWriter writer;
|
ESM::ESMWriter writer;
|
||||||
auto stream = std::make_unique<std::stringstream>();
|
auto stream = std::make_unique<std::stringstream>();
|
||||||
writer.setFormat(0);
|
writer.setFormatVersion(formatVersion);
|
||||||
writer.save(*stream);
|
writer.save(*stream);
|
||||||
writer.startRecord(T::sRecordId);
|
writer.startRecord(T::sRecordId);
|
||||||
record.save(writer, deleted);
|
record.save(writer, deleted);
|
||||||
|
@ -260,41 +263,79 @@ std::unique_ptr<std::istream> getEsmFile(T record, bool deleted)
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests deletion of records.
|
namespace
|
||||||
TEST_F(StoreTest, delete_test)
|
|
||||||
{
|
{
|
||||||
const ESM::RefId recordId = ESM::RefId::stringRefId("foobar");
|
constexpr std::array formats = {
|
||||||
|
ESM::DefaultFormatVersion,
|
||||||
|
ESM::CurrentContentFormatVersion,
|
||||||
|
ESM::MaxOldWeatherFormatVersion,
|
||||||
|
ESM::MaxOldDeathAnimationFormatVersion,
|
||||||
|
ESM::MaxOldForOfWarFormatVersion,
|
||||||
|
ESM::MaxWerewolfDeprecatedDataFormatVersion,
|
||||||
|
ESM::MaxOldTimeLeftFormatVersion,
|
||||||
|
ESM::MaxIntFallbackFormatVersion,
|
||||||
|
ESM::MaxClearModifiersFormatVersion,
|
||||||
|
ESM::MaxOldAiPackageFormatVersion,
|
||||||
|
ESM::MaxOldSkillsAndAttributesFormatVersion,
|
||||||
|
ESM::MaxOldCreatureStatsFormatVersion,
|
||||||
|
ESM::CurrentSaveGameFormatVersion,
|
||||||
|
};
|
||||||
|
|
||||||
typedef ESM::Apparatus RecordType;
|
template <class T, class = std::void_t<>>
|
||||||
|
struct HasBlankFunction : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
RecordType record;
|
template <class T>
|
||||||
record.blank();
|
struct HasBlankFunction<T, std::void_t<decltype(std::declval<T>().blank())>> : std::true_type
|
||||||
record.mId = recordId;
|
{
|
||||||
|
};
|
||||||
|
|
||||||
ESM::ESMReader reader;
|
template <class T>
|
||||||
ESM::Dialogue* dialogue = nullptr;
|
constexpr bool hasBlankFunction = HasBlankFunction<T>::value;
|
||||||
|
}
|
||||||
|
|
||||||
// master file inserts a record
|
/// Tests deletion of records.
|
||||||
reader.open(getEsmFile(record, false), "filename");
|
TYPED_TEST_P(StoreTest, delete_test)
|
||||||
mEsmStore.load(reader, &dummyListener, dialogue);
|
{
|
||||||
mEsmStore.setUp();
|
using RecordType = TypeParam;
|
||||||
|
|
||||||
ASSERT_TRUE(mEsmStore.get<RecordType>().getSize() == 1);
|
for (const ESM::FormatVersion formatVersion : formats)
|
||||||
|
{
|
||||||
|
SCOPED_TRACE("FormatVersion: " + std::to_string(formatVersion));
|
||||||
|
const ESM::RefId recordId = ESM::RefId::stringRefId("foobar");
|
||||||
|
|
||||||
// now a plugin deletes it
|
RecordType record;
|
||||||
reader.open(getEsmFile(record, true), "filename");
|
if constexpr (hasBlankFunction<RecordType>)
|
||||||
mEsmStore.load(reader, &dummyListener, dialogue);
|
record.blank();
|
||||||
mEsmStore.setUp();
|
record.mId = recordId;
|
||||||
|
|
||||||
ASSERT_TRUE(mEsmStore.get<RecordType>().getSize() == 0);
|
ESM::ESMReader reader;
|
||||||
|
ESM::Dialogue* dialogue = nullptr;
|
||||||
|
MWWorld::ESMStore esmStore;
|
||||||
|
|
||||||
// now another plugin inserts it again
|
// master file inserts a record
|
||||||
// expected behaviour is the record to reappear rather than staying deleted
|
reader.open(getEsmFile(record, false, formatVersion), "filename");
|
||||||
reader.open(getEsmFile(record, false), "filename");
|
esmStore.load(reader, &dummyListener, dialogue);
|
||||||
mEsmStore.load(reader, &dummyListener, dialogue);
|
esmStore.setUp();
|
||||||
mEsmStore.setUp();
|
|
||||||
|
|
||||||
ASSERT_TRUE(mEsmStore.get<RecordType>().getSize() == 1);
|
EXPECT_EQ(esmStore.get<RecordType>().getSize(), 1);
|
||||||
|
|
||||||
|
// now a plugin deletes it
|
||||||
|
reader.open(getEsmFile(record, true, formatVersion), "filename");
|
||||||
|
esmStore.load(reader, &dummyListener, dialogue);
|
||||||
|
esmStore.setUp();
|
||||||
|
|
||||||
|
EXPECT_EQ(esmStore.get<RecordType>().getSize(), 0);
|
||||||
|
|
||||||
|
// now another plugin inserts it again
|
||||||
|
// expected behaviour is the record to reappear rather than staying deleted
|
||||||
|
reader.open(getEsmFile(record, false, formatVersion), "filename");
|
||||||
|
esmStore.load(reader, &dummyListener, dialogue);
|
||||||
|
esmStore.setUp();
|
||||||
|
|
||||||
|
EXPECT_EQ(esmStore.get<RecordType>().getSize(), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -327,42 +368,204 @@ static void testAllRecNameIntUnique(const MWWorld::ESMStore::StoreTuple& stores)
|
||||||
std::apply([&stores](auto&&... x) { (testRecNameIntCount(x, stores), ...); }, stores);
|
std::apply([&stores](auto&&... x) { (testRecNameIntCount(x, stores), ...); }, stores);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(StoreTest, eachRecordTypeShouldHaveUniqueRecordId)
|
TEST(StoreTest, eachRecordTypeShouldHaveUniqueRecordId)
|
||||||
{
|
{
|
||||||
testAllRecNameIntUnique(MWWorld::ESMStore::StoreTuple());
|
testAllRecNameIntUnique(MWWorld::ESMStore::StoreTuple());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests overwriting of records.
|
/// Tests overwriting of records.
|
||||||
TEST_F(StoreTest, overwrite_test)
|
TYPED_TEST_P(StoreTest, overwrite_test)
|
||||||
{
|
{
|
||||||
const ESM::RefId recordId = ESM::RefId::stringRefId("foobar");
|
using RecordType = TypeParam;
|
||||||
const ESM::RefId recordIdUpper = ESM::RefId::stringRefId("Foobar");
|
|
||||||
|
|
||||||
typedef ESM::Apparatus RecordType;
|
for (const ESM::FormatVersion formatVersion : formats)
|
||||||
|
{
|
||||||
|
SCOPED_TRACE("FormatVersion: " + std::to_string(formatVersion));
|
||||||
|
|
||||||
RecordType record;
|
const ESM::RefId recordId = ESM::RefId::stringRefId("foobar");
|
||||||
record.blank();
|
const ESM::RefId recordIdUpper = ESM::RefId::stringRefId("Foobar");
|
||||||
record.mId = recordId;
|
|
||||||
|
|
||||||
ESM::ESMReader reader;
|
RecordType record;
|
||||||
ESM::Dialogue* dialogue = nullptr;
|
if constexpr (hasBlankFunction<RecordType>)
|
||||||
|
record.blank();
|
||||||
|
record.mId = recordId;
|
||||||
|
|
||||||
// master file inserts a record
|
ESM::ESMReader reader;
|
||||||
reader.open(getEsmFile(record, false), "filename");
|
ESM::Dialogue* dialogue = nullptr;
|
||||||
mEsmStore.load(reader, &dummyListener, dialogue);
|
MWWorld::ESMStore esmStore;
|
||||||
mEsmStore.setUp();
|
|
||||||
|
|
||||||
// now a plugin overwrites it with changed data
|
// master file inserts a record
|
||||||
record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it
|
reader.open(getEsmFile(record, false, formatVersion), "filename");
|
||||||
record.mModel = "the_new_model";
|
esmStore.load(reader, &dummyListener, dialogue);
|
||||||
reader.open(getEsmFile(record, false), "filename");
|
esmStore.setUp();
|
||||||
mEsmStore.load(reader, &dummyListener, dialogue);
|
|
||||||
mEsmStore.setUp();
|
|
||||||
|
|
||||||
// verify that changes were actually applied
|
// now a plugin overwrites it with changed data
|
||||||
const RecordType* overwrittenRec = mEsmStore.get<RecordType>().search(recordId);
|
record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it
|
||||||
|
record.mModel = "the_new_model";
|
||||||
|
reader.open(getEsmFile(record, false, formatVersion), "filename");
|
||||||
|
esmStore.load(reader, &dummyListener, dialogue);
|
||||||
|
esmStore.setUp();
|
||||||
|
|
||||||
ASSERT_TRUE(overwrittenRec != nullptr);
|
// verify that changes were actually applied
|
||||||
|
const RecordType* overwrittenRec = esmStore.get<RecordType>().search(recordId);
|
||||||
|
|
||||||
ASSERT_TRUE(overwrittenRec && overwrittenRec->mModel == "the_new_model");
|
ASSERT_NE(overwrittenRec, nullptr);
|
||||||
|
|
||||||
|
EXPECT_EQ(overwrittenRec->mModel, "the_new_model");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
template <class T>
|
||||||
|
struct StoreSaveLoadTest : public ::testing::Test
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class = std::void_t<>>
|
||||||
|
struct HasIndex : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct HasIndex<T, std::void_t<decltype(T::mIndex)>> : std::true_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
constexpr bool hasIndex = HasIndex<T>::value;
|
||||||
|
|
||||||
|
TYPED_TEST_SUITE_P(StoreSaveLoadTest);
|
||||||
|
|
||||||
|
TYPED_TEST_P(StoreSaveLoadTest, shouldNotChangeRefId)
|
||||||
|
{
|
||||||
|
using RecordType = TypeParam;
|
||||||
|
|
||||||
|
const int index = 3;
|
||||||
|
ESM::RefId refId;
|
||||||
|
if constexpr (hasIndex<RecordType> && !std::is_same_v<RecordType, ESM::LandTexture>)
|
||||||
|
refId = ESM::RefId::stringRefId(RecordType::indexToId(index));
|
||||||
|
else
|
||||||
|
refId = ESM::RefId::stringRefId("foobar");
|
||||||
|
|
||||||
|
for (const ESM::FormatVersion formatVersion : formats)
|
||||||
|
{
|
||||||
|
SCOPED_TRACE("FormatVersion: " + std::to_string(formatVersion));
|
||||||
|
|
||||||
|
RecordType record;
|
||||||
|
|
||||||
|
if constexpr (hasBlankFunction<RecordType>)
|
||||||
|
record.blank();
|
||||||
|
|
||||||
|
record.mId = refId;
|
||||||
|
|
||||||
|
if constexpr (hasIndex<RecordType>)
|
||||||
|
record.mIndex = index;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<RecordType, ESM::Global>)
|
||||||
|
record.mValue = ESM::Variant(42);
|
||||||
|
|
||||||
|
ESM::ESMReader reader;
|
||||||
|
ESM::Dialogue* dialogue = nullptr;
|
||||||
|
MWWorld::ESMStore esmStore;
|
||||||
|
|
||||||
|
reader.open(getEsmFile(record, false, formatVersion), "filename");
|
||||||
|
esmStore.load(reader, &dummyListener, dialogue);
|
||||||
|
esmStore.setUp();
|
||||||
|
|
||||||
|
const RecordType* result = nullptr;
|
||||||
|
if constexpr (std::is_same_v<RecordType, ESM::LandTexture>)
|
||||||
|
result = esmStore.get<RecordType>().search(index, 0);
|
||||||
|
else if constexpr (hasIndex<RecordType>)
|
||||||
|
result = esmStore.get<RecordType>().search(index);
|
||||||
|
else
|
||||||
|
result = esmStore.get<RecordType>().search(refId);
|
||||||
|
|
||||||
|
ASSERT_NE(result, nullptr);
|
||||||
|
EXPECT_EQ(result->mId, refId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(hasIndex<ESM::MagicEffect>);
|
||||||
|
|
||||||
|
template <class T, class = std::void_t<>>
|
||||||
|
struct HasSaveFunction : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct HasSaveFunction<T, std::void_t<decltype(std::declval<T>().save(std::declval<ESM::ESMWriter&>(), bool()))>>
|
||||||
|
: std::true_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Head, class List>
|
||||||
|
struct ConcatTypes;
|
||||||
|
|
||||||
|
template <class Head, class... Ts>
|
||||||
|
struct ConcatTypes<Head, std::tuple<Ts...>>
|
||||||
|
{
|
||||||
|
using Type = std::tuple<Head, Ts...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <template <class...> class Predicate, class Out, class... Ins>
|
||||||
|
struct FilterTypesImpl;
|
||||||
|
|
||||||
|
template <template <class...> class Predicate, class Out, class Head, class... Tail>
|
||||||
|
struct FilterTypesImpl<Predicate, Out, Head, Tail...>
|
||||||
|
{
|
||||||
|
using Type = typename FilterTypesImpl<Predicate,
|
||||||
|
std::conditional_t<Predicate<Head>::value, typename ConcatTypes<Head, Out>::Type, Out>, Tail...>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <template <class...> class Predicate, class Out>
|
||||||
|
struct FilterTypesImpl<Predicate, Out>
|
||||||
|
{
|
||||||
|
using Type = Out;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <template <class...> class Predicate, class List>
|
||||||
|
struct FilterTypes;
|
||||||
|
|
||||||
|
template <template <class...> class Predicate, class... Ts>
|
||||||
|
struct FilterTypes<Predicate, std::tuple<Ts...>>
|
||||||
|
{
|
||||||
|
using Type = typename FilterTypesImpl<Predicate, std::tuple<>, Ts...>::Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class... T>
|
||||||
|
struct ToRecordTypes;
|
||||||
|
|
||||||
|
template <class... T>
|
||||||
|
struct ToRecordTypes<std::tuple<MWWorld::Store<T>...>>
|
||||||
|
{
|
||||||
|
using Type = std::tuple<T...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class... T>
|
||||||
|
struct AsTestingTypes;
|
||||||
|
|
||||||
|
template <class... T>
|
||||||
|
struct AsTestingTypes<std::tuple<T...>>
|
||||||
|
{
|
||||||
|
using Type = testing::Types<T...>;
|
||||||
|
};
|
||||||
|
|
||||||
|
using RecordTypes = typename ToRecordTypes<MWWorld::ESMStore::StoreTuple>::Type;
|
||||||
|
using RecordTypesWithId = typename FilterTypes<ESM4::HasId, RecordTypes>::Type;
|
||||||
|
using RecordTypesWithSave = typename FilterTypes<HasSaveFunction, RecordTypesWithId>::Type;
|
||||||
|
using RecordTypesWithModel = typename FilterTypes<ESM4::HasModel, RecordTypesWithSave>::Type;
|
||||||
|
|
||||||
|
REGISTER_TYPED_TEST_SUITE_P(StoreSaveLoadTest, shouldNotChangeRefId);
|
||||||
|
|
||||||
|
static_assert(std::tuple_size_v<RecordTypesWithSave> == 38);
|
||||||
|
|
||||||
|
INSTANTIATE_TYPED_TEST_SUITE_P(
|
||||||
|
RecordTypesTest, StoreSaveLoadTest, typename AsTestingTypes<RecordTypesWithSave>::Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_TYPED_TEST_SUITE_P(StoreTest, overwrite_test, delete_test);
|
||||||
|
|
||||||
|
static_assert(std::tuple_size_v<RecordTypesWithModel> == 19);
|
||||||
|
|
||||||
|
INSTANTIATE_TYPED_TEST_SUITE_P(RecordTypesTest, StoreTest, typename AsTestingTypes<RecordTypesWithModel>::Type);
|
||||||
|
|
|
@ -479,7 +479,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf
|
||||||
|
|
||||||
file->setAuthor(QString::fromUtf8(fileReader.getAuthor().c_str()));
|
file->setAuthor(QString::fromUtf8(fileReader.getAuthor().c_str()));
|
||||||
file->setDate(info.lastModified());
|
file->setDate(info.lastModified());
|
||||||
file->setFormat(fileReader.getFormat());
|
file->setFormat(fileReader.getFormatVersion());
|
||||||
file->setFilePath(info.absoluteFilePath());
|
file->setFilePath(info.absoluteFilePath());
|
||||||
file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str()));
|
file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str()));
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ QString ContentSelectorModel::EsmFile::sToolTip = QString(
|
||||||
ContentSelectorModel::EsmFile::EsmFile(QString fileName, ModelItem* parent)
|
ContentSelectorModel::EsmFile::EsmFile(QString fileName, ModelItem* parent)
|
||||||
: ModelItem(parent)
|
: ModelItem(parent)
|
||||||
, mFileName(fileName)
|
, mFileName(fileName)
|
||||||
, mFormat(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ void ContentSelectorModel::EsmFile::setDate(const QDateTime& modified)
|
||||||
|
|
||||||
void ContentSelectorModel::EsmFile::setFormat(int format)
|
void ContentSelectorModel::EsmFile::setFormat(int format)
|
||||||
{
|
{
|
||||||
mFormat = format;
|
mVersion = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentSelectorModel::EsmFile::setFilePath(const QString& path)
|
void ContentSelectorModel::EsmFile::setFilePath(const QString& path)
|
||||||
|
@ -59,7 +58,7 @@ QByteArray ContentSelectorModel::EsmFile::encodedData() const
|
||||||
QByteArray encodedData;
|
QByteArray encodedData;
|
||||||
QDataStream stream(&encodedData, QIODevice::WriteOnly);
|
QDataStream stream(&encodedData, QIODevice::WriteOnly);
|
||||||
|
|
||||||
stream << mFileName << mAuthor << QString::number(mFormat) << mModified.toString() << mPath << mDescription
|
stream << mFileName << mAuthor << QString::number(mVersion) << mModified.toString() << mPath << mDescription
|
||||||
<< mGameFiles;
|
<< mGameFiles;
|
||||||
|
|
||||||
return encodedData;
|
return encodedData;
|
||||||
|
@ -85,7 +84,7 @@ QVariant ContentSelectorModel::EsmFile::fileProperty(const FileProperty prop) co
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FileProperty_Format:
|
case FileProperty_Format:
|
||||||
return mFormat;
|
return mVersion;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FileProperty_DateModified:
|
case FileProperty_DateModified:
|
||||||
|
@ -122,7 +121,7 @@ void ContentSelectorModel::EsmFile::setFileProperty(const FileProperty prop, con
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FileProperty_Format:
|
case FileProperty_Format:
|
||||||
mFormat = value.toInt();
|
mVersion = value.toInt();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FileProperty_DateModified:
|
case FileProperty_DateModified:
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
|
#include <components/esm3/formatversion.hpp>
|
||||||
|
|
||||||
#include "modelitem.hpp"
|
#include "modelitem.hpp"
|
||||||
|
|
||||||
class QMimeData;
|
class QMimeData;
|
||||||
|
@ -49,7 +51,7 @@ namespace ContentSelectorModel
|
||||||
inline QString fileName() const { return mFileName; }
|
inline QString fileName() const { return mFileName; }
|
||||||
inline QString author() const { return mAuthor; }
|
inline QString author() const { return mAuthor; }
|
||||||
inline QDateTime modified() const { return mModified; }
|
inline QDateTime modified() const { return mModified; }
|
||||||
inline float format() const { return mFormat; }
|
ESM::FormatVersion formatVersion() const { return mVersion; }
|
||||||
inline QString filePath() const { return mPath; }
|
inline QString filePath() const { return mPath; }
|
||||||
|
|
||||||
/// @note Contains file names, not paths.
|
/// @note Contains file names, not paths.
|
||||||
|
@ -58,7 +60,7 @@ namespace ContentSelectorModel
|
||||||
inline QString toolTip() const
|
inline QString toolTip() const
|
||||||
{
|
{
|
||||||
return sToolTip.arg(mAuthor)
|
return sToolTip.arg(mAuthor)
|
||||||
.arg(mFormat)
|
.arg(mVersion)
|
||||||
.arg(mModified.toString(Qt::ISODate))
|
.arg(mModified.toString(Qt::ISODate))
|
||||||
.arg(mPath)
|
.arg(mPath)
|
||||||
.arg(mDescription)
|
.arg(mDescription)
|
||||||
|
@ -76,7 +78,7 @@ namespace ContentSelectorModel
|
||||||
QString mFileName;
|
QString mFileName;
|
||||||
QString mAuthor;
|
QString mAuthor;
|
||||||
QDateTime mModified;
|
QDateTime mModified;
|
||||||
int mFormat;
|
ESM::FormatVersion mVersion = ESM::DefaultFormatVersion;
|
||||||
QString mPath;
|
QString mPath;
|
||||||
QStringList mGameFiles;
|
QStringList mGameFiles;
|
||||||
QString mDescription;
|
QString mDescription;
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace ESM
|
||||||
|
|
||||||
void loadImpl(ESMReader& esm, std::vector<ActiveSpells::ActiveSpellParams>& spells, NAME tag)
|
void loadImpl(ESMReader& esm, std::vector<ActiveSpells::ActiveSpellParams>& spells, NAME tag)
|
||||||
{
|
{
|
||||||
int format = esm.getFormat();
|
const FormatVersion format = esm.getFormatVersion();
|
||||||
|
|
||||||
while (esm.isNextSub(tag))
|
while (esm.isNextSub(tag))
|
||||||
{
|
{
|
||||||
|
@ -50,7 +50,7 @@ namespace ESM
|
||||||
params.mId = esm.getRefId();
|
params.mId = esm.getRefId();
|
||||||
esm.getHNT(params.mCasterActorId, "CAST");
|
esm.getHNT(params.mCasterActorId, "CAST");
|
||||||
params.mDisplayName = esm.getHNString("DISP");
|
params.mDisplayName = esm.getHNString("DISP");
|
||||||
if (format < 17)
|
if (format <= MaxClearModifiersFormatVersion)
|
||||||
params.mType = ActiveSpells::Type_Temporary;
|
params.mType = ActiveSpells::Type_Temporary;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -77,7 +77,7 @@ namespace ESM
|
||||||
effect.mArg = -1;
|
effect.mArg = -1;
|
||||||
esm.getHNOT(effect.mArg, "ARG_");
|
esm.getHNOT(effect.mArg, "ARG_");
|
||||||
esm.getHNT(effect.mMagnitude, "MAGN");
|
esm.getHNT(effect.mMagnitude, "MAGN");
|
||||||
if (format < 17)
|
if (format <= MaxClearModifiersFormatVersion)
|
||||||
{
|
{
|
||||||
effect.mMinMagnitude = effect.mMagnitude;
|
effect.mMinMagnitude = effect.mMagnitude;
|
||||||
effect.mMaxMagnitude = effect.mMagnitude;
|
effect.mMaxMagnitude = effect.mMagnitude;
|
||||||
|
@ -90,11 +90,11 @@ namespace ESM
|
||||||
esm.getHNT(effect.mDuration, "DURA");
|
esm.getHNT(effect.mDuration, "DURA");
|
||||||
effect.mEffectIndex = -1;
|
effect.mEffectIndex = -1;
|
||||||
esm.getHNOT(effect.mEffectIndex, "EIND");
|
esm.getHNOT(effect.mEffectIndex, "EIND");
|
||||||
if (format < 9)
|
if (format <= MaxOldTimeLeftFormatVersion)
|
||||||
effect.mTimeLeft = effect.mDuration;
|
effect.mTimeLeft = effect.mDuration;
|
||||||
else
|
else
|
||||||
esm.getHNT(effect.mTimeLeft, "LEFT");
|
esm.getHNT(effect.mTimeLeft, "LEFT");
|
||||||
if (format < 17)
|
if (format <= MaxClearModifiersFormatVersion)
|
||||||
effect.mFlags = ActiveEffect::Flag_None;
|
effect.mFlags = ActiveEffect::Flag_None;
|
||||||
else
|
else
|
||||||
esm.getHNT(effect.mFlags, "FLAG");
|
esm.getHNT(effect.mFlags, "FLAG");
|
||||||
|
|
|
@ -57,7 +57,7 @@ namespace ESM
|
||||||
mCellId = esm.getHNOString("CELL");
|
mCellId = esm.getHNOString("CELL");
|
||||||
mRepeat = false;
|
mRepeat = false;
|
||||||
esm.getHNOT(mRepeat, "REPT");
|
esm.getHNOT(mRepeat, "REPT");
|
||||||
if (esm.getFormat() < 18)
|
if (esm.getFormatVersion() <= MaxOldAiPackageFormatVersion)
|
||||||
{
|
{
|
||||||
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
|
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
|
||||||
// The exact value of mDuration only matters for repeating packages.
|
// The exact value of mDuration only matters for repeating packages.
|
||||||
|
@ -94,7 +94,7 @@ namespace ESM
|
||||||
esm.getHNOT(mActive, "ACTV");
|
esm.getHNOT(mActive, "ACTV");
|
||||||
mRepeat = false;
|
mRepeat = false;
|
||||||
esm.getHNOT(mRepeat, "REPT");
|
esm.getHNOT(mRepeat, "REPT");
|
||||||
if (esm.getFormat() < 18)
|
if (esm.getFormatVersion() <= MaxOldAiPackageFormatVersion)
|
||||||
{
|
{
|
||||||
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
|
// mDuration isn't saved in the save file, so just giving it "1" for now if the package has a duration.
|
||||||
// The exact value of mDuration only matters for repeating packages.
|
// The exact value of mDuration only matters for repeating packages.
|
||||||
|
@ -265,7 +265,7 @@ namespace ESM
|
||||||
|
|
||||||
esm.getHNOT(mLastAiPackage, "LAST");
|
esm.getHNOT(mLastAiPackage, "LAST");
|
||||||
|
|
||||||
if (count > 1 && esm.getFormat() < 18)
|
if (count > 1 && esm.getFormatVersion() <= MaxOldAiPackageFormatVersion)
|
||||||
{
|
{
|
||||||
for (auto& pkg : mPackages)
|
for (auto& pkg : mPackages)
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace ESM
|
||||||
|
|
||||||
void CreatureStats::load(ESMReader& esm)
|
void CreatureStats::load(ESMReader& esm)
|
||||||
{
|
{
|
||||||
bool intFallback = esm.getFormat() < 11;
|
const bool intFallback = esm.getFormatVersion() <= MaxIntFallbackFormatVersion;
|
||||||
for (int i = 0; i < 8; ++i)
|
for (int i = 0; i < 8; ++i)
|
||||||
mAttributes[i].load(esm, intFallback);
|
mAttributes[i].load(esm, intFallback);
|
||||||
|
|
||||||
|
@ -37,11 +37,11 @@ namespace ESM
|
||||||
mHitRecovery = false;
|
mHitRecovery = false;
|
||||||
mBlock = false;
|
mBlock = false;
|
||||||
mRecalcDynamicStats = false;
|
mRecalcDynamicStats = false;
|
||||||
if (esm.getFormat() < 8)
|
if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion)
|
||||||
{
|
{
|
||||||
esm.getHNOT(mDead, "DEAD");
|
esm.getHNOT(mDead, "DEAD");
|
||||||
esm.getHNOT(mDeathAnimationFinished, "DFNT");
|
esm.getHNOT(mDeathAnimationFinished, "DFNT");
|
||||||
if (esm.getFormat() < 3 && mDead)
|
if (esm.getFormatVersion() <= MaxOldDeathAnimationFormatVersion && mDead)
|
||||||
mDeathAnimationFinished = true;
|
mDeathAnimationFinished = true;
|
||||||
esm.getHNOT(mDied, "DIED");
|
esm.getHNOT(mDied, "DIED");
|
||||||
esm.getHNOT(mMurdered, "MURD");
|
esm.getHNOT(mMurdered, "MURD");
|
||||||
|
@ -91,7 +91,7 @@ namespace ESM
|
||||||
|
|
||||||
mLastHitAttemptObject = ESM::RefId::stringRefId(esm.getHNOString("LHAT"));
|
mLastHitAttemptObject = ESM::RefId::stringRefId(esm.getHNOString("LHAT"));
|
||||||
|
|
||||||
if (esm.getFormat() < 8)
|
if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion)
|
||||||
esm.getHNOT(mRecalcDynamicStats, "CALC");
|
esm.getHNOT(mRecalcDynamicStats, "CALC");
|
||||||
|
|
||||||
mDrawState = 0;
|
mDrawState = 0;
|
||||||
|
@ -115,7 +115,7 @@ namespace ESM
|
||||||
mAiSequence.load(esm);
|
mAiSequence.load(esm);
|
||||||
mMagicEffects.load(esm);
|
mMagicEffects.load(esm);
|
||||||
|
|
||||||
if (esm.getFormat() < 17)
|
if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion)
|
||||||
{
|
{
|
||||||
while (esm.isNextSub("SUMM"))
|
while (esm.isNextSub("SUMM"))
|
||||||
{
|
{
|
||||||
|
@ -168,7 +168,7 @@ namespace ESM
|
||||||
|
|
||||||
mCorprusSpells[id] = stats;
|
mCorprusSpells[id] = stats;
|
||||||
}
|
}
|
||||||
if (esm.getFormat() <= 18)
|
if (esm.getFormatVersion() <= MaxOldSkillsAndAttributesFormatVersion)
|
||||||
mMissingACDT = mGoldPool == std::numeric_limits<int>::min();
|
mMissingACDT = mGoldPool == std::numeric_limits<int>::min();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace ESM
|
||||||
const std::string& getDesc() const { return mHeader.mData.desc; }
|
const std::string& getDesc() const { return mHeader.mData.desc; }
|
||||||
const std::vector<Header::MasterData>& getGameFiles() const { return mHeader.mMaster; }
|
const std::vector<Header::MasterData>& getGameFiles() const { return mHeader.mMaster; }
|
||||||
const Header& getHeader() const { return mHeader; }
|
const Header& getHeader() const { return mHeader; }
|
||||||
int getFormat() const { return mHeader.mFormat; }
|
FormatVersion getFormatVersion() const { return mHeader.mFormatVersion; }
|
||||||
const NAME& retSubName() const { return mCtx.subName; }
|
const NAME& retSubName() const { return mCtx.subName; }
|
||||||
uint32_t getSubSize() const { return mCtx.leftSub; }
|
uint32_t getSubSize() const { return mCtx.leftSub; }
|
||||||
const std::filesystem::path& getName() const { return mCtx.filename; }
|
const std::filesystem::path& getName() const { return mCtx.filename; }
|
||||||
|
|
|
@ -49,9 +49,9 @@ namespace ESM
|
||||||
mHeader.mData.records = count;
|
mHeader.mData.records = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESMWriter::setFormat(int format)
|
void ESMWriter::setFormatVersion(FormatVersion value)
|
||||||
{
|
{
|
||||||
mHeader.mFormat = format;
|
mHeader.mFormatVersion = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESMWriter::clearMaster()
|
void ESMWriter::clearMaster()
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace ESM
|
||||||
// It is a good idea to compare this with the value you wrote into the header (setRecordCount)
|
// It is a good idea to compare this with the value you wrote into the header (setRecordCount)
|
||||||
// It should be the record count you set + 1 (1 additional record for the TES3 header)
|
// It should be the record count you set + 1 (1 additional record for the TES3 header)
|
||||||
int getRecordCount() { return mRecordCount; }
|
int getRecordCount() { return mRecordCount; }
|
||||||
void setFormat(int format);
|
void setFormatVersion(FormatVersion value);
|
||||||
|
|
||||||
void clearMaster();
|
void clearMaster();
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ namespace ESM
|
||||||
{
|
{
|
||||||
esm.getHNOT(mBounds, "BOUN");
|
esm.getHNOT(mBounds, "BOUN");
|
||||||
esm.getHNOT(mNorthMarkerAngle, "ANGL");
|
esm.getHNOT(mNorthMarkerAngle, "ANGL");
|
||||||
int dataFormat = esm.getFormat();
|
const FormatVersion dataFormat = esm.getFormatVersion();
|
||||||
while (esm.isNextSub("FTEX"))
|
while (esm.isNextSub("FTEX"))
|
||||||
{
|
{
|
||||||
esm.getSubHeader();
|
esm.getSubHeader();
|
||||||
|
@ -73,7 +73,7 @@ namespace ESM
|
||||||
tex.mImageData.resize(imageSize);
|
tex.mImageData.resize(imageSize);
|
||||||
esm.getExact(&tex.mImageData[0], imageSize);
|
esm.getExact(&tex.mImageData[0], imageSize);
|
||||||
|
|
||||||
if (dataFormat < 7)
|
if (dataFormat <= MaxOldForOfWarFormatVersion)
|
||||||
convertFogOfWar(tex.mImageData);
|
convertFogOfWar(tex.mImageData);
|
||||||
|
|
||||||
mFogTextures.push_back(tex);
|
mFogTextures.push_back(tex);
|
||||||
|
|
25
components/esm3/formatversion.hpp
Normal file
25
components/esm3/formatversion.hpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_ESM3_FORMATVERSION_H
|
||||||
|
#define OPENMW_COMPONENTS_ESM3_FORMATVERSION_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace ESM
|
||||||
|
{
|
||||||
|
using FormatVersion = std::uint32_t;
|
||||||
|
|
||||||
|
inline constexpr FormatVersion DefaultFormatVersion = 0;
|
||||||
|
inline constexpr FormatVersion CurrentContentFormatVersion = 1;
|
||||||
|
inline constexpr FormatVersion MaxOldWeatherFormatVersion = 1;
|
||||||
|
inline constexpr FormatVersion MaxOldDeathAnimationFormatVersion = 2;
|
||||||
|
inline constexpr FormatVersion MaxOldForOfWarFormatVersion = 6;
|
||||||
|
inline constexpr FormatVersion MaxWerewolfDeprecatedDataFormatVersion = 7;
|
||||||
|
inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8;
|
||||||
|
inline constexpr FormatVersion MaxIntFallbackFormatVersion = 10;
|
||||||
|
inline constexpr FormatVersion MaxClearModifiersFormatVersion = 16;
|
||||||
|
inline constexpr FormatVersion MaxOldAiPackageFormatVersion = 17;
|
||||||
|
inline constexpr FormatVersion MaxOldSkillsAndAttributesFormatVersion = 18;
|
||||||
|
inline constexpr FormatVersion MaxOldCreatureStatsFormatVersion = 19;
|
||||||
|
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -189,7 +189,7 @@ namespace ESM
|
||||||
mId = ESM::RefId::stringRefId(indexToId(mIndex));
|
mId = ESM::RefId::stringRefId(indexToId(mIndex));
|
||||||
|
|
||||||
esm.getHNTSized<36>(mData, "MEDT");
|
esm.getHNTSized<36>(mData, "MEDT");
|
||||||
if (esm.getFormat() == 0)
|
if (esm.getFormatVersion() == DefaultFormatVersion)
|
||||||
{
|
{
|
||||||
// don't allow mods to change fixed flags in the legacy format
|
// don't allow mods to change fixed flags in the legacy format
|
||||||
mData.mFlags &= (AllowSpellmaking | AllowEnchanting | NegativeLight);
|
mData.mFlags &= (AllowSpellmaking | AllowEnchanting | NegativeLight);
|
||||||
|
|
|
@ -14,20 +14,16 @@ namespace ESM
|
||||||
mData.author.clear();
|
mData.author.clear();
|
||||||
mData.desc.clear();
|
mData.desc.clear();
|
||||||
mData.records = 0;
|
mData.records = 0;
|
||||||
mFormat = CurrentFormat;
|
mFormatVersion = CurrentContentFormatVersion;
|
||||||
mMaster.clear();
|
mMaster.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Header::load(ESMReader& esm)
|
void Header::load(ESMReader& esm)
|
||||||
{
|
{
|
||||||
if (esm.isNextSub("FORM"))
|
if (esm.isNextSub("FORM"))
|
||||||
{
|
esm.getHT(mFormatVersion);
|
||||||
esm.getHT(mFormat);
|
|
||||||
if (mFormat < 0)
|
|
||||||
esm.fail("invalid format code");
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
mFormat = 0;
|
mFormatVersion = DefaultFormatVersion;
|
||||||
|
|
||||||
if (esm.isNextSub("HEDR"))
|
if (esm.isNextSub("HEDR"))
|
||||||
{
|
{
|
||||||
|
@ -69,8 +65,8 @@ namespace ESM
|
||||||
|
|
||||||
void Header::save(ESMWriter& esm)
|
void Header::save(ESMWriter& esm)
|
||||||
{
|
{
|
||||||
if (mFormat > 0)
|
if (mFormatVersion > DefaultFormatVersion)
|
||||||
esm.writeHNT("FORM", mFormat);
|
esm.writeHNT("FORM", mFormatVersion);
|
||||||
|
|
||||||
esm.startSubRecord("HEDR");
|
esm.startSubRecord("HEDR");
|
||||||
esm.writeT(mData.version);
|
esm.writeT(mData.version);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "components/esm/esmcommon.hpp"
|
#include "components/esm/esmcommon.hpp"
|
||||||
|
#include "components/esm3/formatversion.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
@ -42,8 +43,6 @@ namespace ESM
|
||||||
/// \brief File header record
|
/// \brief File header record
|
||||||
struct Header
|
struct Header
|
||||||
{
|
{
|
||||||
static constexpr int CurrentFormat = 1; // most recent known format
|
|
||||||
|
|
||||||
// Defines another files (esm or esp) that this file depends upon.
|
// Defines another files (esm or esp) that this file depends upon.
|
||||||
struct MasterData
|
struct MasterData
|
||||||
{
|
{
|
||||||
|
@ -56,7 +55,7 @@ namespace ESM
|
||||||
std::vector<unsigned char> mSCRS; // Used in .ess savegames only, screenshot
|
std::vector<unsigned char> mSCRS; // Used in .ess savegames only, screenshot
|
||||||
|
|
||||||
Data mData;
|
Data mData;
|
||||||
int mFormat;
|
FormatVersion mFormatVersion;
|
||||||
std::vector<MasterData> mMaster;
|
std::vector<MasterData> mMaster;
|
||||||
|
|
||||||
void blank();
|
void blank();
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace ESM
|
||||||
std::pair<int, float> params;
|
std::pair<int, float> params;
|
||||||
esm.getHT(id);
|
esm.getHT(id);
|
||||||
esm.getHNT(params.first, "BASE");
|
esm.getHNT(params.first, "BASE");
|
||||||
if (esm.getFormat() < 17)
|
if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion)
|
||||||
params.second = 0.f;
|
params.second = 0.f;
|
||||||
else
|
else
|
||||||
esm.getHNT(params.second, "MODI");
|
esm.getHNT(params.second, "MODI");
|
||||||
|
|
|
@ -39,12 +39,12 @@ namespace ESM
|
||||||
mDisposition = 0;
|
mDisposition = 0;
|
||||||
esm.getHNOT(mDisposition, "DISP");
|
esm.getHNOT(mDisposition, "DISP");
|
||||||
|
|
||||||
bool intFallback = esm.getFormat() < 11;
|
const bool intFallback = esm.getFormatVersion() <= MaxIntFallbackFormatVersion;
|
||||||
for (int i = 0; i < 27; ++i)
|
for (int i = 0; i < 27; ++i)
|
||||||
mSkills[i].load(esm, intFallback);
|
mSkills[i].load(esm, intFallback);
|
||||||
|
|
||||||
mWerewolfDeprecatedData = false;
|
mWerewolfDeprecatedData = false;
|
||||||
if (esm.getFormat() < 8 && esm.peekNextSub("STBA"))
|
if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion && esm.peekNextSub("STBA"))
|
||||||
{
|
{
|
||||||
// we have deprecated werewolf skills, stored interleaved
|
// we have deprecated werewolf skills, stored interleaved
|
||||||
// Load into one big vector, then remove every 2nd value
|
// Load into one big vector, then remove every 2nd value
|
||||||
|
@ -66,7 +66,9 @@ namespace ESM
|
||||||
else
|
else
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
assert(skills.size() == 27);
|
if (skills.size() != std::size(mSkills))
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Invalid number of skill for werewolf deprecated data: " + std::to_string(skills.size()));
|
||||||
std::copy(skills.begin(), skills.end(), mSkills);
|
std::copy(skills.begin(), skills.end(), mSkills);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace ESM
|
||||||
|
|
||||||
void ObjectState::load(ESMReader& esm)
|
void ObjectState::load(ESMReader& esm)
|
||||||
{
|
{
|
||||||
mVersion = esm.getFormat();
|
mVersion = esm.getFormatVersion();
|
||||||
|
|
||||||
bool isDeleted;
|
bool isDeleted;
|
||||||
mRef.loadData(esm, isDeleted);
|
mRef.loadData(esm, isDeleted);
|
||||||
|
|
|
@ -4,9 +4,11 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "components/esm/luascripts.hpp"
|
||||||
|
#include "components/esm3/formatversion.hpp"
|
||||||
|
|
||||||
#include "animationstate.hpp"
|
#include "animationstate.hpp"
|
||||||
#include "cellref.hpp"
|
#include "cellref.hpp"
|
||||||
#include "components/esm/luascripts.hpp"
|
|
||||||
#include "locals.hpp"
|
#include "locals.hpp"
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
|
@ -37,7 +39,7 @@ namespace ESM
|
||||||
// Is there any class-specific state following the ObjectState
|
// Is there any class-specific state following the ObjectState
|
||||||
bool mHasCustomState;
|
bool mHasCustomState;
|
||||||
|
|
||||||
unsigned int mVersion;
|
FormatVersion mVersion = DefaultFormatVersion;
|
||||||
|
|
||||||
AnimationState mAnimationState;
|
AnimationState mAnimationState;
|
||||||
|
|
||||||
|
@ -47,7 +49,6 @@ namespace ESM
|
||||||
, mCount(0)
|
, mCount(0)
|
||||||
, mFlags(0)
|
, mFlags(0)
|
||||||
, mHasCustomState(true)
|
, mHasCustomState(true)
|
||||||
, mVersion(0)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,10 +47,11 @@ namespace ESM
|
||||||
checkPrevItems = false;
|
checkPrevItems = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esm.getFormat() < 19)
|
if (esm.getFormatVersion() <= MaxOldSkillsAndAttributesFormatVersion)
|
||||||
{
|
{
|
||||||
bool intFallback = esm.getFormat() < 11;
|
const bool intFallback = esm.getFormatVersion() <= MaxIntFallbackFormatVersion;
|
||||||
bool clearModified = esm.getFormat() < 17 && !mObject.mNpcStats.mIsWerewolf;
|
const bool clearModified
|
||||||
|
= esm.getFormatVersion() <= MaxClearModifiersFormatVersion && !mObject.mNpcStats.mIsWerewolf;
|
||||||
if (esm.hasMoreSubs())
|
if (esm.hasMoreSubs())
|
||||||
{
|
{
|
||||||
for (int i = 0; i < Attribute::Length; ++i)
|
for (int i = 0; i < Attribute::Length; ++i)
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace ESM
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
EffectList().load(esm); // for backwards compatibility
|
EffectList().load(esm); // for backwards compatibility
|
||||||
esm.getHNT(mSpeed, "SPED");
|
esm.getHNT(mSpeed, "SPED");
|
||||||
if (esm.getFormat() < 17)
|
if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion)
|
||||||
mSlot = 0;
|
mSlot = 0;
|
||||||
else
|
else
|
||||||
esm.getHNT(mSlot, "SLOT");
|
esm.getHNT(mSlot, "SLOT");
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
|
||||||
int SavedGame::sCurrentFormat = 22;
|
|
||||||
|
|
||||||
void SavedGame::load(ESMReader& esm)
|
void SavedGame::load(ESMReader& esm)
|
||||||
{
|
{
|
||||||
mPlayerName = esm.getHNString("PLNA");
|
mPlayerName = esm.getHNString("PLNA");
|
||||||
|
|
|
@ -18,8 +18,6 @@ namespace ESM
|
||||||
{
|
{
|
||||||
constexpr static RecNameInts sRecordId = REC_SAVE;
|
constexpr static RecNameInts sRecordId = REC_SAVE;
|
||||||
|
|
||||||
static int sCurrentFormat;
|
|
||||||
|
|
||||||
std::vector<std::string> mContentFiles;
|
std::vector<std::string> mContentFiles;
|
||||||
std::string mPlayerName;
|
std::string mPlayerName;
|
||||||
int mPlayerLevel;
|
int mPlayerLevel;
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace ESM
|
||||||
|
|
||||||
void SpellState::load(ESMReader& esm)
|
void SpellState::load(ESMReader& esm)
|
||||||
{
|
{
|
||||||
if (esm.getFormat() < 17)
|
if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion)
|
||||||
{
|
{
|
||||||
while (esm.isNextSub("SPEL"))
|
while (esm.isNextSub("SPEL"))
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue