2013-11-19 15:38:26 +01:00
|
|
|
#include "character.hpp"
|
|
|
|
|
2019-02-22 23:16:47 +04:00
|
|
|
#include <cctype>
|
2022-06-08 23:25:50 +02:00
|
|
|
#include <filesystem>
|
2013-11-19 15:38:26 +01:00
|
|
|
#include <sstream>
|
2022-06-08 23:25:50 +02:00
|
|
|
#include <utility>
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2013-11-21 12:24:24 +01:00
|
|
|
#include <components/esm/defs.hpp>
|
2022-06-08 23:25:50 +02:00
|
|
|
#include <components/esm3/esmreader.hpp>
|
2021-11-09 21:45:16 +01:00
|
|
|
#include <components/misc/utf8stream.hpp>
|
|
|
|
|
2022-08-03 00:00:54 +02:00
|
|
|
#include <components/misc/strings/algorithm.hpp>
|
|
|
|
|
2013-11-19 15:38:26 +01:00
|
|
|
bool MWState::operator< (const Slot& left, const Slot& right)
|
|
|
|
{
|
|
|
|
return left.mTimeStamp<right.mTimeStamp;
|
|
|
|
}
|
|
|
|
|
2021-10-17 10:29:10 +02:00
|
|
|
std::string MWState::getFirstGameFile(const std::vector<std::string>& contentFiles)
|
|
|
|
{
|
|
|
|
for (const std::string& c : contentFiles)
|
|
|
|
{
|
|
|
|
if (Misc::StringUtils::ciEndsWith(c, ".esm") || Misc::StringUtils::ciEndsWith(c, ".omwgame"))
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
return "";
|
|
|
|
}
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2022-06-08 23:25:50 +02:00
|
|
|
void MWState::Character::addSlot (const std::filesystem::path& path, const std::string& game)
|
2013-11-19 15:38:26 +01:00
|
|
|
{
|
|
|
|
Slot slot;
|
|
|
|
slot.mPath = path;
|
2022-06-11 23:38:09 +02:00
|
|
|
slot.mTimeStamp = std::filesystem::last_write_time (path);
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2013-11-21 12:24:24 +01:00
|
|
|
ESM::ESMReader reader;
|
2022-06-19 13:28:33 +02:00
|
|
|
reader.open (slot.mPath);
|
2013-11-21 12:24:24 +01:00
|
|
|
|
|
|
|
if (reader.getRecName()!=ESM::REC_SAVE)
|
|
|
|
return; // invalid save file -> ignore
|
|
|
|
|
|
|
|
reader.getRecHeader();
|
|
|
|
|
|
|
|
slot.mProfile.load (reader);
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2021-10-17 10:29:10 +02:00
|
|
|
if (!Misc::StringUtils::ciEqual(getFirstGameFile(slot.mProfile.mContentFiles), game))
|
2013-11-25 13:00:05 +01:00
|
|
|
return; // this file is for a different game -> ignore
|
|
|
|
|
2013-11-19 15:38:26 +01:00
|
|
|
mSlots.push_back (slot);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MWState::Character::addSlot (const ESM::SavedGame& profile)
|
|
|
|
{
|
|
|
|
Slot slot;
|
|
|
|
|
|
|
|
std::ostringstream stream;
|
2014-06-10 00:22:00 +02:00
|
|
|
|
|
|
|
// The profile description is user-supplied, so we need to escape the path
|
2021-11-09 21:45:16 +01:00
|
|
|
Utf8Stream description(profile.mDescription);
|
|
|
|
while(!description.eof())
|
2014-06-10 00:22:00 +02:00
|
|
|
{
|
2021-11-09 21:45:16 +01:00
|
|
|
auto c = description.consume();
|
2021-11-10 16:54:52 +01:00
|
|
|
if(c <= 0x7F && std::isalnum(c)) // Ignore multibyte characters and non alphanumeric characters
|
2021-11-09 21:45:16 +01:00
|
|
|
stream << static_cast<char>(c);
|
2014-06-10 00:22:00 +02:00
|
|
|
else
|
2021-11-09 21:45:16 +01:00
|
|
|
stream << '_';
|
2014-06-10 00:22:00 +02:00
|
|
|
}
|
2013-11-19 15:38:26 +01:00
|
|
|
|
2015-01-07 15:06:39 +01:00
|
|
|
const std::string ext = ".omwsave";
|
|
|
|
slot.mPath = mPath / (stream.str() + ext);
|
2014-06-10 00:22:00 +02:00
|
|
|
|
|
|
|
// Append an index if necessary to ensure a unique file
|
|
|
|
int i=0;
|
2022-06-08 23:25:50 +02:00
|
|
|
while (std::filesystem::exists(slot.mPath))
|
2014-06-10 00:22:00 +02:00
|
|
|
{
|
2019-01-07 17:47:39 +04:00
|
|
|
const std::string test = stream.str() + " - " + std::to_string(++i);
|
|
|
|
slot.mPath = mPath / (test + ext);
|
2014-06-10 00:22:00 +02:00
|
|
|
}
|
|
|
|
|
2013-11-19 15:38:26 +01:00
|
|
|
slot.mProfile = profile;
|
2022-06-11 23:38:09 +02:00
|
|
|
slot.mTimeStamp = std::filesystem::file_time_type ();
|
2013-11-19 15:38:26 +01:00
|
|
|
|
|
|
|
mSlots.push_back (slot);
|
|
|
|
}
|
|
|
|
|
2022-06-08 23:25:50 +02:00
|
|
|
MWState::Character::Character (std::filesystem::path saves, const std::string& game)
|
|
|
|
: mPath (std::move(saves))
|
2013-11-19 15:38:26 +01:00
|
|
|
{
|
2022-06-08 23:25:50 +02:00
|
|
|
if (!std::filesystem::is_directory (mPath))
|
2013-11-19 15:38:26 +01:00
|
|
|
{
|
2022-06-08 23:25:50 +02:00
|
|
|
std::filesystem::create_directories (mPath);
|
2013-11-19 15:38:26 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-06-19 13:28:33 +02:00
|
|
|
for (const auto& iter : std::filesystem::directory_iterator (mPath))
|
2013-11-19 15:38:26 +01:00
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2022-06-19 13:28:33 +02:00
|
|
|
addSlot (iter, game);
|
2013-11-19 15:38:26 +01:00
|
|
|
}
|
|
|
|
catch (...) {} // ignoring bad saved game files for now
|
|
|
|
}
|
|
|
|
|
|
|
|
std::sort (mSlots.begin(), mSlots.end());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-28 20:57:45 +02:00
|
|
|
void MWState::Character::cleanup()
|
|
|
|
{
|
2022-06-08 23:25:50 +02:00
|
|
|
if (mSlots.empty())
|
2014-04-28 20:57:45 +02:00
|
|
|
{
|
|
|
|
// All slots are gone, no need to keep the empty directory
|
2022-06-08 23:25:50 +02:00
|
|
|
if (std::filesystem::is_directory (mPath))
|
2014-04-28 20:57:45 +02:00
|
|
|
{
|
|
|
|
// Extra safety check to make sure the directory is empty (e.g. slots failed to parse header)
|
2022-06-08 23:25:50 +02:00
|
|
|
std::filesystem::directory_iterator it(mPath);
|
|
|
|
if (it == std::filesystem::directory_iterator())
|
|
|
|
std::filesystem::remove_all(mPath);
|
2014-04-28 20:57:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-19 15:38:26 +01:00
|
|
|
const MWState::Slot *MWState::Character::createSlot (const ESM::SavedGame& profile)
|
|
|
|
{
|
|
|
|
addSlot (profile);
|
|
|
|
|
|
|
|
return &mSlots.back();
|
|
|
|
}
|
|
|
|
|
2014-04-28 20:57:45 +02:00
|
|
|
void MWState::Character::deleteSlot (const Slot *slot)
|
|
|
|
{
|
2022-09-03 16:41:35 +01:00
|
|
|
int index = slot - mSlots.data();
|
2014-04-28 20:57:45 +02:00
|
|
|
|
|
|
|
if (index<0 || index>=static_cast<int> (mSlots.size()))
|
|
|
|
{
|
|
|
|
// sanity check; not entirely reliable
|
|
|
|
throw std::logic_error ("slot not found");
|
|
|
|
}
|
|
|
|
|
2022-06-08 23:25:50 +02:00
|
|
|
std::filesystem::remove(slot->mPath);
|
2014-04-28 20:57:45 +02:00
|
|
|
|
|
|
|
mSlots.erase (mSlots.begin()+index);
|
|
|
|
}
|
|
|
|
|
2013-11-19 15:38:26 +01:00
|
|
|
const MWState::Slot *MWState::Character::updateSlot (const Slot *slot, const ESM::SavedGame& profile)
|
|
|
|
{
|
2022-09-03 16:41:35 +01:00
|
|
|
int index = slot - mSlots.data();
|
2013-11-19 15:38:26 +01:00
|
|
|
|
|
|
|
if (index<0 || index>=static_cast<int> (mSlots.size()))
|
|
|
|
{
|
|
|
|
// sanity check; not entirely reliable
|
|
|
|
throw std::logic_error ("slot not found");
|
|
|
|
}
|
|
|
|
|
|
|
|
Slot newSlot = *slot;
|
|
|
|
newSlot.mProfile = profile;
|
2022-06-11 23:38:09 +02:00
|
|
|
newSlot.mTimeStamp = std::filesystem::file_time_type ();
|
2013-11-19 15:38:26 +01:00
|
|
|
|
|
|
|
mSlots.erase (mSlots.begin()+index);
|
|
|
|
|
|
|
|
mSlots.push_back (newSlot);
|
|
|
|
|
|
|
|
return &mSlots.back();
|
2013-11-21 11:10:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
MWState::Character::SlotIterator MWState::Character::begin() const
|
|
|
|
{
|
|
|
|
return mSlots.rbegin();
|
|
|
|
}
|
|
|
|
|
|
|
|
MWState::Character::SlotIterator MWState::Character::end() const
|
|
|
|
{
|
|
|
|
return mSlots.rend();
|
2013-11-21 11:18:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ESM::SavedGame MWState::Character::getSignature() const
|
|
|
|
{
|
|
|
|
if (mSlots.empty())
|
|
|
|
throw std::logic_error ("character signature not available");
|
|
|
|
|
|
|
|
std::vector<Slot>::const_iterator iter (mSlots.begin());
|
|
|
|
|
|
|
|
Slot slot = *iter;
|
|
|
|
|
|
|
|
for (++iter; iter!=mSlots.end(); ++iter)
|
|
|
|
if (iter->mProfile.mPlayerLevel>slot.mProfile.mPlayerLevel)
|
|
|
|
slot = *iter;
|
|
|
|
else if (iter->mProfile.mPlayerLevel==slot.mProfile.mPlayerLevel &&
|
|
|
|
iter->mTimeStamp>slot.mTimeStamp)
|
|
|
|
slot = *iter;
|
|
|
|
|
|
|
|
return slot.mProfile;
|
2014-04-28 20:57:45 +02:00
|
|
|
}
|
2015-01-07 03:03:56 +01:00
|
|
|
|
2022-06-08 23:25:50 +02:00
|
|
|
const std::filesystem::path& MWState::Character::getPath() const
|
2015-01-07 03:03:56 +01:00
|
|
|
{
|
|
|
|
return mPath;
|
|
|
|
}
|