Skip to content

Commit

Permalink
Merge branch 'saveformat' into 'master'
Browse files Browse the repository at this point in the history
Drop support for old saves

See merge request OpenMW/openmw!3629
  • Loading branch information
jvoisin committed Dec 6, 2023
2 parents 0ff9831 + 659d7fe commit b67d89f
Show file tree
Hide file tree
Showing 24 changed files with 75 additions and 159 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
Feature #6447: Add LOD support to Object Paging
Feature #6491: Add support for Qt6
Feature #6556: Lua API for sounds
Feature #6624: Drop support for old saves
Feature #6726: Lua API for creating new objects
Feature #6864: Lua file access API
Feature #6922: Improve launcher appearance
Expand Down
32 changes: 13 additions & 19 deletions apps/openmw/mwclass/creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -779,32 +779,26 @@ namespace MWClass

const ESM::CreatureState& creatureState = state.asCreatureState();

if (state.mVersion > 0)
if (!ptr.getRefData().getCustomData())
{
if (!ptr.getRefData().getCustomData())
if (creatureState.mCreatureStats.mMissingACDT)
ensureCustomData(ptr);
else
{
if (creatureState.mCreatureStats.mMissingACDT)
ensureCustomData(ptr);
else
{
// Create a CustomData, but don't fill it from ESM records (not needed)
auto data = std::make_unique<CreatureCustomData>();
// Create a CustomData, but don't fill it from ESM records (not needed)
auto data = std::make_unique<CreatureCustomData>();

if (hasInventoryStore(ptr))
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
else
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();
if (hasInventoryStore(ptr))
data->mContainerStore = std::make_unique<MWWorld::InventoryStore>();
else
data->mContainerStore = std::make_unique<MWWorld::ContainerStore>();

MWBase::Environment::get().getWorldModel()->registerPtr(ptr);
data->mContainerStore->setPtr(ptr);
MWBase::Environment::get().getWorldModel()->registerPtr(ptr);
data->mContainerStore->setPtr(ptr);

ptr.getRefData().setCustomData(std::move(data));
}
ptr.getRefData().setCustomData(std::move(data));
}
}
else
ensureCustomData(
ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.

CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();

Expand Down
24 changes: 9 additions & 15 deletions apps/openmw/mwclass/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1333,25 +1333,19 @@ namespace MWClass

const ESM::NpcState& npcState = state.asNpcState();

if (state.mVersion > 0)
if (!ptr.getRefData().getCustomData())
{
if (!ptr.getRefData().getCustomData())
if (npcState.mCreatureStats.mMissingACDT)
ensureCustomData(ptr);
else
{
if (npcState.mCreatureStats.mMissingACDT)
ensureCustomData(ptr);
else
{
// Create a CustomData, but don't fill it from ESM records (not needed)
auto data = std::make_unique<NpcCustomData>();
MWBase::Environment::get().getWorldModel()->registerPtr(ptr);
data->mInventoryStore.setPtr(ptr);
ptr.getRefData().setCustomData(std::move(data));
}
// Create a CustomData, but don't fill it from ESM records (not needed)
auto data = std::make_unique<NpcCustomData>();
MWBase::Environment::get().getWorldModel()->registerPtr(ptr);
data->mInventoryStore.setPtr(ptr);
ptr.getRefData().setCustomData(std::move(data));
}
}
else
ensureCustomData(
ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.

NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();

Expand Down
2 changes: 1 addition & 1 deletion apps/openmw/mwdialogue/journalimp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ namespace MWDialogue

void Journal::readRecord(ESM::ESMReader& reader, uint32_t type)
{
if (type == ESM::REC_JOUR || type == ESM::REC_JOUR_LEGACY)
if (type == ESM::REC_JOUR)
{
ESM::JournalEntry record;
record.load(reader);
Expand Down
30 changes: 20 additions & 10 deletions apps/openmw/mwstate/statemanagerimp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,10 +411,25 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
ESM::ESMReader reader;
reader.open(filepath);

if (reader.getFormatVersion() > ESM::CurrentSaveGameFormatVersion)
throw VersionMismatchError(
"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.");
ESM::FormatVersion version = reader.getFormatVersion();
if (version > ESM::CurrentSaveGameFormatVersion)
throw VersionMismatchError("#{OMWEngine:LoadingRequiresNewVersionError}");
else if (version < ESM::MinSupportedSaveGameFormatVersion)
{
const char* release;
// Report the last version still capable of reading this save
if (version <= ESM::OpenMW0_48SaveGameFormatVersion)
release = "OpenMW 0.48.0";
else
{
// Insert additional else if statements above to cover future releases
static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion);
release = "OpenMW 0.49.0";
}
auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine");
std::string message = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release });
throw VersionMismatchError(message);
}

std::map<int, int> contentFileMap = buildContentFileIndexMap(reader);
reader.setContentFileMapping(&contentFileMap);
Expand Down Expand Up @@ -457,7 +472,6 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
break;

case ESM::REC_JOUR:
case ESM::REC_JOUR_LEGACY:
case ESM::REC_QUES:

MWBase::Environment::get().getJournal()->readRecord(reader, n.toInt());
Expand Down Expand Up @@ -607,11 +621,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file
std::vector<std::string> buttons;
buttons.emplace_back("#{Interface:OK}");

std::string error;
if (typeid(e) == typeid(VersionMismatchError))
error = "#{OMWEngine:LoadingFailed}: #{OMWEngine:LoadingRequiresNewVersionError}";
else
error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what());
std::string error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what());

MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error, buttons);
}
Expand Down
6 changes: 3 additions & 3 deletions apps/openmw/mwworld/cellstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,16 +252,16 @@ namespace
if (!record)
return;

if (state.mVersion < 15)
if (state.mVersion <= ESM::MaxOldRestockingFormatVersion)
fixRestocking(record, state);
if (state.mVersion < 17)
if (state.mVersion <= ESM::MaxClearModifiersFormatVersion)
{
if constexpr (std::is_same_v<T, ESM::Creature>)
MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory);
else if constexpr (std::is_same_v<T, ESM::NPC>)
MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats);
}
else if (state.mVersion < 20)
else if (state.mVersion <= ESM::MaxOldCreatureStatsFormatVersion)
{
if constexpr (std::is_same_v<T, ESM::Creature> || std::is_same_v<T, ESM::NPC>)
MWWorld::convertStats(state.mCreatureStats);
Expand Down
7 changes: 1 addition & 6 deletions apps/openmw/mwworld/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,12 +334,7 @@ namespace MWWorld

if (player.mObject.mNpcStats.mIsWerewolf)
{
if (player.mObject.mNpcStats.mWerewolfDeprecatedData)
{
saveStats();
setWerewolfStats();
}
else if (reader.getFormatVersion() <= ESM::MaxOldSkillsAndAttributesFormatVersion)
if (reader.getFormatVersion() <= ESM::MaxOldSkillsAndAttributesFormatVersion)
{
setWerewolfStats();
if (player.mSetWerewolfAcrobatics)
Expand Down
4 changes: 2 additions & 2 deletions apps/openmw_test_suite/mwworld/test_store.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ namespace
ESM::CurrentContentFormatVersion,
ESM::MaxOldWeatherFormatVersion,
ESM::MaxOldDeathAnimationFormatVersion,
ESM::MaxOldForOfWarFormatVersion,
ESM::MaxWerewolfDeprecatedDataFormatVersion,
ESM::MaxOldFogOfWarFormatVersion,
ESM::MaxUnoptimizedCharacterDataFormatVersion,
ESM::MaxOldTimeLeftFormatVersion,
ESM::MaxIntFallbackFormatVersion,
ESM::MaxClearModifiersFormatVersion,
Expand Down
2 changes: 0 additions & 2 deletions components/esm/defs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,6 @@ namespace ESM

// format 0 - saved games
REC_SAVE = esm3Recname("SAVE"),
REC_JOUR_LEGACY = esm3Recname("\xa4UOR"), // "\xa4UOR", rather than "JOUR", little oversight when magic numbers
// were calculated by hand, needs to be supported for older files now
REC_JOUR = esm3Recname("JOUR"),
REC_QUES = esm3Recname("QUES"),
REC_GSCR = esm3Recname("GSCR"),
Expand Down
11 changes: 2 additions & 9 deletions components/esm3/creaturestats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,17 @@ namespace ESM
mHitRecovery = false;
mBlock = false;
mRecalcDynamicStats = false;
if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion)
if (esm.getFormatVersion() <= MaxUnoptimizedCharacterDataFormatVersion)
{
esm.getHNOT(mDead, "DEAD");
esm.getHNOT(mDeathAnimationFinished, "DFNT");
if (esm.getFormatVersion() <= MaxOldDeathAnimationFormatVersion && mDead)
mDeathAnimationFinished = true;
esm.getHNOT(mDied, "DIED");
esm.getHNOT(mMurdered, "MURD");
if (esm.isNextSub("FRHT"))
esm.skipHSub(); // Friendly hits, no longer used
esm.getHNOT(mTalkedTo, "TALK");
esm.getHNOT(mAlarmed, "ALRM");
esm.getHNOT(mAttacked, "ATKD");
if (esm.isNextSub("HOST"))
esm.skipHSub(); // Hostile, no longer used
if (esm.isNextSub("ATCK"))
esm.skipHSub(); // attackingOrSpell, no longer used
esm.getHNOT(mKnockdown, "KNCK");
Expand Down Expand Up @@ -82,17 +78,14 @@ namespace ESM
mMovementFlags = 0;
esm.getHNOT(mMovementFlags, "MOVE");

if (esm.isNextSub("ASTR"))
esm.skipHSub(); // attackStrength, no longer used

mFallHeight = 0;
esm.getHNOT(mFallHeight, "FALL");

mLastHitObject = esm.getHNORefId("LHIT");

mLastHitAttemptObject = esm.getHNORefId("LHAT");

if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion)
if (esm.getFormatVersion() <= MaxUnoptimizedCharacterDataFormatVersion)
esm.getHNOT(mRecalcDynamicStats, "CALC");

mDrawState = 0;
Expand Down
8 changes: 0 additions & 8 deletions components/esm3/dialoguestate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ namespace ESM
esm.getHNT(reaction, "INTV");
mChangedFactionReaction[faction][faction2] = reaction;
}

// no longer used
while (esm.isNextSub("REAC"))
{
esm.skipHSub();
esm.getSubName();
esm.skipHSub();
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion components/esm3/fogstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace ESM
tex.mImageData.resize(imageSize);
esm.getExact(tex.mImageData.data(), imageSize);

if (dataFormat <= MaxOldForOfWarFormatVersion)
if (dataFormat <= MaxOldFogOfWarFormatVersion)
convertFogOfWar(tex.mImageData);

mFogTextures.push_back(tex);
Expand Down
9 changes: 7 additions & 2 deletions components/esm3/formatversion.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ namespace ESM
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 MaxOldFogOfWarFormatVersion = 6;
inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7;
inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8;
inline constexpr FormatVersion MaxIntFallbackFormatVersion = 10;
inline constexpr FormatVersion MaxOldRestockingFormatVersion = 14;
inline constexpr FormatVersion MaxClearModifiersFormatVersion = 16;
inline constexpr FormatVersion MaxOldAiPackageFormatVersion = 17;
inline constexpr FormatVersion MaxOldSkillsAndAttributesFormatVersion = 18;
Expand All @@ -26,6 +27,10 @@ namespace ESM
inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26;
inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27;
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29;

inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 1;
inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21;
inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion;
}

#endif
8 changes: 0 additions & 8 deletions components/esm3/inventorystate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ namespace ESM

ObjectState state;

// obsolete
if (esm.isNextSub("SLOT"))
{
int32_t slot;
esm.getHT(slot);
mEquipmentSlots[index] = slot;
}

state.mRef.loadId(esm, true);
state.load(esm);

Expand Down
57 changes: 0 additions & 57 deletions components/esm3/npcstats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,46 +41,6 @@ namespace ESM
for (auto& skill : mSkills)
skill.load(esm, intFallback);

mWerewolfDeprecatedData = false;
if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion && esm.peekNextSub("STBA"))
{
// we have deprecated werewolf skills, stored interleaved
// Load into one big vector, then remove every 2nd value
mWerewolfDeprecatedData = true;
std::vector<StatState<float>> skills(mSkills.begin(), mSkills.end());

for (size_t i = 0; i < std::size(mSkills); ++i)
{
StatState<float> skill;
skill.load(esm, intFallback);
skills.push_back(skill);
}

int i = 0;
for (std::vector<StatState<float>>::iterator it = skills.begin(); it != skills.end(); ++i)
{
if (i % 2 == 1)
it = skills.erase(it);
else
++it;
}
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.begin());
}

// No longer used
bool hasWerewolfAttributes = false;
esm.getHNOT(hasWerewolfAttributes, "HWAT");
if (hasWerewolfAttributes)
{
StatState<int32_t> dummy;
for (int i = 0; i < ESM::Attribute::Length; ++i)
dummy.load(esm, intFallback);
mWerewolfDeprecatedData = true;
}

mIsWerewolf = false;
esm.getHNOT(mIsWerewolf, "WOLF");

Expand All @@ -93,14 +53,6 @@ namespace ESM
mWerewolfKills = 0;
esm.getHNOT(mWerewolfKills, "WKIL");

// No longer used
if (esm.isNextSub("PROF"))
esm.skipHSub(); // int profit

// No longer used
if (esm.isNextSub("ASTR"))
esm.skipHSub(); // attackStrength

mLevelProgress = 0;
esm.getHNOT(mLevelProgress, "LPRO");

Expand All @@ -116,14 +68,6 @@ namespace ESM
mTimeToStartDrowning = 0;
esm.getHNOT(mTimeToStartDrowning, "DRTI");

// No longer used
float lastDrowningHit = 0;
esm.getHNOT(lastDrowningHit, "DRLH");

// No longer used
float levelHealthBonus = 0;
esm.getHNOT(levelHealthBonus, "LVLH");

mCrimeId = -1;
esm.getHNOT(mCrimeId, "CRID");
}
Expand Down Expand Up @@ -195,7 +139,6 @@ namespace ESM

void NpcStats::blank()
{
mWerewolfDeprecatedData = false;
mIsWerewolf = false;
mDisposition = 0;
mBounty = 0;
Expand Down
Loading

0 comments on commit b67d89f

Please sign in to comment.