Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
13 kB · 353 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354#include "load.hpp"#include "esmdata.hpp"#include "lessbyid.hpp"#include "record.hpp"
#include <components/debug/debuglog.hpp>#include <components/esm/defs.hpp>#include <components/esm/typetraits.hpp>#include <components/esm3/esmreader.hpp>#include <components/esm3/loadacti.hpp>#include <components/esm3/loadcell.hpp>#include <components/esm3/loadcont.hpp>#include <components/esm3/loaddoor.hpp>#include <components/esm3/loadgmst.hpp>#include <components/esm3/loadland.hpp>#include <components/esm3/loadstat.hpp>#include <components/esm3/readerscache.hpp>#include <components/files/collections.hpp>#include <components/files/conversion.hpp>#include <components/files/multidircollection.hpp>#include <components/loadinglistener/loadinglistener.hpp>#include <components/misc/pathhelpers.hpp>#include <components/misc/resourcehelpers.hpp>#include <components/misc/strings/lower.hpp>
#include <algorithm>#include <cstddef>#include <filesystem>#include <map>#include <set>#include <sstream>#include <string>#include <string_view>#include <utility>#include <vector>
namespace EsmLoader{ namespace { struct GetKey { template <class T> decltype(auto) operator()(const T& v) const { return (v.mId); }
const ESM::RefId& operator()(const ESM::Cell& v) const { return v.mId; }
std::pair<int, int> operator()(const ESM::Land& v) const { return std::pair(v.mX, v.mY); }
template <class T> decltype(auto) operator()(const Record<T>& v) const { return (*this)(v.mValue); } };
struct CellRecords { Records<ESM::Cell> mValues; std::map<std::string, std::size_t> mByName; std::map<std::pair<int, int>, std::size_t> mByPosition; };
template <class T> concept NotHasId = !ESM::HasId<T>;
template <ESM::HasId T> void loadRecord(ESM::ESMReader& reader, Records<T>& records) { T record; bool deleted = false; record.load(reader, deleted); if (Misc::ResourceHelpers::isHiddenMarker(record.mId)) return; records.emplace_back(deleted, std::move(record)); }
template <NotHasId T> void loadRecord(ESM::ESMReader& reader, Records<T>& records) { T record; bool deleted = false; record.load(reader, deleted); records.emplace_back(deleted, std::move(record)); }
void loadRecord(ESM::ESMReader& reader, CellRecords& records) { ESM::Cell record; bool deleted = false; record.loadNameAndData(reader, deleted);
if ((record.mData.mFlags & ESM::Cell::Interior) != 0) { const auto it = records.mByName.find(record.mName); if (it == records.mByName.end()) { record.loadCell(reader, true); records.mByName.emplace_hint(it, record.mName, records.mValues.size()); records.mValues.emplace_back(deleted, std::move(record)); } else { Record<ESM::Cell>& old = records.mValues[it->second]; old.mValue.mData = record.mData; old.mValue.loadCell(reader, true); } } else { const std::pair<int, int> position(record.mData.mX, record.mData.mY); const auto it = records.mByPosition.find(position); if (it == records.mByPosition.end()) { record.loadCell(reader, true); records.mByPosition.emplace_hint(it, position, records.mValues.size()); records.mValues.emplace_back(deleted, std::move(record)); } else { Record<ESM::Cell>& old = records.mValues[it->second]; old.mValue.mData = record.mData; old.mValue.loadCell(reader, true); } } }
struct ShallowContent { Records<ESM::Activator> mActivators; CellRecords mCells; Records<ESM::Container> mContainers; Records<ESM::Door> mDoors; Records<ESM::GameSetting> mGameSettings; Records<ESM::Land> mLands; Records<ESM::Static> mStatics; };
void loadRecord(const Query& query, const ESM::NAME& name, ESM::ESMReader& reader, ShallowContent& content) { switch (name.toInt()) { case ESM::REC_ACTI: if (query.mLoadActivators) return loadRecord(reader, content.mActivators); break; case ESM::REC_CELL: if (query.mLoadCells) return loadRecord(reader, content.mCells); break; case ESM::REC_CONT: if (query.mLoadContainers) return loadRecord(reader, content.mContainers); break; case ESM::REC_DOOR: if (query.mLoadDoors) return loadRecord(reader, content.mDoors); break; case ESM::REC_GMST: if (query.mLoadGameSettings) return loadRecord(reader, content.mGameSettings); break; case ESM::REC_LAND: if (query.mLoadLands) return loadRecord(reader, content.mLands); break; case ESM::REC_STAT: if (query.mLoadStatics) return loadRecord(reader, content.mStatics); break; }
reader.skipRecord(); }
void loadEsm(const Query& query, ESM::ESMReader& reader, ShallowContent& content, Loading::Listener* listener) { Log(Debug::Info) << "Loading ESM file " << reader.getName();
while (reader.hasMoreRecs()) { const ESM::NAME recName = reader.getRecName(); reader.getRecHeader(); if (reader.getRecordFlags() & ESM::FLAG_Ignored) { reader.skipRecord(); continue; } loadRecord(query, recName, reader, content);
if (listener != nullptr) listener->setProgress(fileProgress * reader.getFileOffset() / reader.getFileSize()); } }
ShallowContent shallowLoad(const Query& query, const std::vector<std::string>& contentFiles, const Files::Collections& fileCollections, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { ShallowContent result;
const std::set<std::string_view, Misc::StringUtils::CiComp> supportedFormats{ "esm", "esp", "omwgame", "omwaddon", "project", };
for (std::size_t i = 0; i < contentFiles.size(); ++i) { const std::string& file = contentFiles[i]; const std::string_view extension = Misc::getFileExtension(file);
if (!supportedFormats.contains(extension)) { Log(Debug::Warning) << "Skipping unsupported content file: " << file; continue; }
if (listener != nullptr) { listener->setLabel(file); listener->setProgressRange(fileProgress); }
const Files::MultiDirCollection& collection = fileCollections.getCollection(extension);
const ESM::ReadersCache::BusyItem reader = readers.get(i); reader->setEncoder(encoder); reader->setIndex(static_cast<int>(i)); reader->open(collection.getPath(file)); if (query.mLoadCells) reader->resolveParentFileIndices(readers);
loadEsm(query, *reader, result, listener); }
return result; }
struct WithType { ESM::RecNameInts mType;
template <class T> RefIdWithType operator()(const T& v) const { return { v.mId, mType }; } };
template <class T> void addRefIdsTypes(const std::vector<T>& values, std::vector<RefIdWithType>& refIdsTypes) { std::transform(values.begin(), values.end(), std::back_inserter(refIdsTypes), WithType{ static_cast<ESM::RecNameInts>(T::sRecordId) }); }
void addRefIdsTypes(EsmData& content) { content.mRefIdTypes.reserve(content.mActivators.size() + content.mContainers.size() + content.mDoors.size() + content.mStatics.size());
addRefIdsTypes(content.mActivators, content.mRefIdTypes); addRefIdsTypes(content.mContainers, content.mRefIdTypes); addRefIdsTypes(content.mDoors, content.mRefIdTypes); addRefIdsTypes(content.mStatics, content.mRefIdTypes);
std::sort(content.mRefIdTypes.begin(), content.mRefIdTypes.end(), LessById{}); }
std::vector<ESM::Cell> prepareCellRecords(Records<ESM::Cell>& records) { std::vector<ESM::Cell> result; for (Record<ESM::Cell>& v : records) if (!v.mDeleted) result.emplace_back(std::move(v.mValue)); return result; } }
EsmData loadEsmData(const Query& query, const std::vector<std::string>& contentFiles, const Files::Collections& fileCollections, ESM::ReadersCache& readers, ToUTF8::Utf8Encoder* encoder, Loading::Listener* listener) { Log(Debug::Info) << "Loading ESM data...";
ShallowContent content = shallowLoad(query, contentFiles, fileCollections, readers, encoder, listener);
std::ostringstream loaded;
if (query.mLoadActivators) loaded << ' ' << content.mActivators.size() << " activators,"; if (query.mLoadCells) loaded << ' ' << content.mCells.mValues.size() << " cells,"; if (query.mLoadContainers) loaded << ' ' << content.mContainers.size() << " containers,"; if (query.mLoadDoors) loaded << ' ' << content.mDoors.size() << " doors,"; if (query.mLoadGameSettings) loaded << ' ' << content.mGameSettings.size() << " game settings,"; if (query.mLoadLands) loaded << ' ' << content.mLands.size() << " lands,"; if (query.mLoadStatics) loaded << ' ' << content.mStatics.size() << " statics,";
Log(Debug::Info) << "Loaded" << loaded.str();
EsmData result;
if (query.mLoadActivators) result.mActivators = prepareRecords(content.mActivators, GetKey{}); if (query.mLoadCells) result.mCells = prepareCellRecords(content.mCells.mValues); if (query.mLoadContainers) result.mContainers = prepareRecords(content.mContainers, GetKey{}); if (query.mLoadDoors) result.mDoors = prepareRecords(content.mDoors, GetKey{}); if (query.mLoadGameSettings) result.mGameSettings = prepareRecords(content.mGameSettings, GetKey{}); if (query.mLoadLands) result.mLands = prepareRecords(content.mLands, GetKey{}); if (query.mLoadStatics) result.mStatics = prepareRecords(content.mStatics, GetKey{});
addRefIdsTypes(result);
std::ostringstream prepared;
if (query.mLoadActivators) prepared << ' ' << result.mActivators.size() << " unique activators,"; if (query.mLoadCells) prepared << ' ' << result.mCells.size() << " unique cells,"; if (query.mLoadContainers) prepared << ' ' << result.mContainers.size() << " unique containers,"; if (query.mLoadDoors) prepared << ' ' << result.mDoors.size() << " unique doors,"; if (query.mLoadGameSettings) prepared << ' ' << result.mGameSettings.size() << " unique game settings,"; if (query.mLoadLands) prepared << ' ' << result.mLands.size() << " unique lands,"; if (query.mLoadStatics) prepared << ' ' << result.mStatics.size() << " unique statics,";
Log(Debug::Info) << "Prepared" << prepared.str();
return result; }}