2013-07-06 15:03:18 +00:00
|
|
|
#include "refcollection.hpp"
|
|
|
|
|
2022-01-22 14:58:41 +00:00
|
|
|
#include <components/esm3/loadcell.hpp>
|
2014-05-20 07:02:22 +00:00
|
|
|
|
2013-07-06 15:03:18 +00:00
|
|
|
#include "cell.hpp"
|
2014-01-21 20:37:21 +00:00
|
|
|
#include "record.hpp"
|
2013-07-06 15:03:18 +00:00
|
|
|
#include "ref.hpp"
|
2014-01-21 20:37:21 +00:00
|
|
|
#include "universalid.hpp"
|
2013-07-06 15:03:18 +00:00
|
|
|
|
2022-08-19 17:19:42 +00:00
|
|
|
#include "../doc/messages.hpp"
|
|
|
|
|
2021-09-04 16:07:23 +00:00
|
|
|
#include <string_view>
|
|
|
|
|
2021-07-23 09:07:56 +00:00
|
|
|
namespace CSMWorld
|
|
|
|
{
|
|
|
|
template <>
|
|
|
|
void Collection<CellRef, IdAccessor<CellRef>>::removeRows(int index, int count)
|
|
|
|
{
|
|
|
|
mRecords.erase(mRecords.begin() + index, mRecords.begin() + index + count);
|
|
|
|
|
|
|
|
// index map is updated in RefCollection::removeRows()
|
|
|
|
}
|
|
|
|
|
|
|
|
template <>
|
|
|
|
void Collection<CellRef, IdAccessor<CellRef>>::insertRecord(
|
|
|
|
std::unique_ptr<RecordBase> record, int index, UniversalId::Type type)
|
|
|
|
{
|
|
|
|
int size = static_cast<int>(mRecords.size());
|
|
|
|
if (index < 0 || index > size)
|
|
|
|
throw std::runtime_error("index out of range");
|
|
|
|
|
|
|
|
std::unique_ptr<Record<CellRef>> record2(static_cast<Record<CellRef>*>(record.release()));
|
|
|
|
|
|
|
|
if (index == size)
|
|
|
|
mRecords.push_back(std::move(record2));
|
|
|
|
else
|
|
|
|
mRecords.insert(mRecords.begin() + index, std::move(record2));
|
|
|
|
|
|
|
|
// index map is updated in RefCollection::insertRecord()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-20 07:02:22 +00:00
|
|
|
void CSMWorld::RefCollection::load(ESM::ESMReader& reader, int cellIndex, bool base,
|
2021-07-23 09:07:56 +00:00
|
|
|
std::map<unsigned int, unsigned int>& cache, CSMDoc::Messages& messages)
|
2013-07-06 15:03:18 +00:00
|
|
|
{
|
|
|
|
Record<Cell> cell = mCells.getRecord(cellIndex);
|
|
|
|
|
|
|
|
Cell& cell2 = base ? cell.mBase : cell.mModified;
|
|
|
|
|
|
|
|
CellRef ref;
|
2016-03-24 10:12:05 +00:00
|
|
|
ref.mNew = false;
|
2015-04-25 07:20:02 +00:00
|
|
|
ESM::MovedCellRef mref;
|
2020-04-06 05:43:05 +00:00
|
|
|
mref.mRefNum.mIndex = 0;
|
2015-07-21 17:25:43 +00:00
|
|
|
bool isDeleted = false;
|
2021-07-12 15:30:39 +00:00
|
|
|
bool isMoved = false;
|
2014-05-20 07:02:22 +00:00
|
|
|
|
2021-07-12 15:30:39 +00:00
|
|
|
while (ESM::Cell::getNextRef(reader, ref, isDeleted, mref, isMoved))
|
2013-07-06 15:03:18 +00:00
|
|
|
{
|
2015-01-10 11:35:59 +00:00
|
|
|
// Keep mOriginalCell empty when in modified (as an indicator that the
|
|
|
|
// original cell will always be equal the current cell).
|
|
|
|
ref.mOriginalCell = base ? cell2.mId : "";
|
2013-07-06 15:03:18 +00:00
|
|
|
|
2015-01-09 11:05:16 +00:00
|
|
|
if (cell.get().isExterior())
|
|
|
|
{
|
2018-12-23 16:05:00 +00:00
|
|
|
// Autocalculate the cell index from coordinates first
|
2015-01-09 11:05:16 +00:00
|
|
|
std::pair<int, int> index = ref.getCellIndex();
|
|
|
|
|
2018-12-23 16:05:00 +00:00
|
|
|
ref.mCell = "#" + std::to_string(index.first) + " " + std::to_string(index.second);
|
2015-04-26 01:35:46 +00:00
|
|
|
|
2018-12-23 16:05:00 +00:00
|
|
|
// Handle non-base moved references
|
2021-07-23 08:12:09 +00:00
|
|
|
if (!base && isMoved)
|
2015-04-25 07:20:02 +00:00
|
|
|
{
|
2018-12-23 16:05:00 +00:00
|
|
|
// Moved references must have a link back to their original cell
|
|
|
|
// See discussion: https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30
|
2015-04-26 01:35:46 +00:00
|
|
|
ref.mOriginalCell = cell2.mId;
|
|
|
|
|
2018-12-23 16:05:00 +00:00
|
|
|
// Some mods may move references outside of the bounds, which often happens they are deleted.
|
|
|
|
// This results in nonsensical autocalculated cell IDs, so we must use the record target cell.
|
|
|
|
|
|
|
|
// Log a warning if the record target cell is different
|
2015-04-26 04:55:40 +00:00
|
|
|
if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1])
|
2015-04-26 01:35:46 +00:00
|
|
|
{
|
2018-12-23 16:05:00 +00:00
|
|
|
std::string indexCell = ref.mCell;
|
|
|
|
ref.mCell = "#" + std::to_string(mref.mTarget[0]) + " " + std::to_string(mref.mTarget[1]);
|
|
|
|
|
|
|
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex));
|
|
|
|
messages.add(id, "The position of the moved reference " + ref.mRefID + " (cell " + indexCell + ")"
|
|
|
|
" does not match the target cell (" + ref.mCell + ")",
|
|
|
|
std::string(), CSMDoc::Message::Severity_Warning);
|
2015-04-26 01:35:46 +00:00
|
|
|
}
|
2015-04-25 07:20:02 +00:00
|
|
|
}
|
2015-01-09 11:05:16 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
ref.mCell = cell2.mId;
|
2013-07-06 15:03:18 +00:00
|
|
|
|
2021-08-28 04:09:55 +00:00
|
|
|
if (ref.mRefNum.mContentFile != -1 && !base)
|
|
|
|
{
|
|
|
|
ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24;
|
|
|
|
ref.mRefNum.mIndex &= 0x00ffffff;
|
|
|
|
}
|
|
|
|
|
2021-07-23 05:30:33 +00:00
|
|
|
unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff)
|
|
|
|
| (ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24;
|
2020-12-08 22:10:58 +00:00
|
|
|
|
2021-07-23 09:07:56 +00:00
|
|
|
std::map<unsigned int, unsigned int>::iterator iter = cache.find(refNum);
|
2021-07-23 05:30:33 +00:00
|
|
|
|
2021-08-28 04:09:55 +00:00
|
|
|
if (isMoved)
|
|
|
|
{
|
|
|
|
if (iter == cache.end())
|
|
|
|
{
|
|
|
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex));
|
|
|
|
|
|
|
|
messages.add(id,
|
|
|
|
"Attempt to move a non-existent reference - RefNum index " + std::to_string(ref.mRefNum.mIndex)
|
|
|
|
+ ", refID " + ref.mRefID + ", content file index " + std::to_string(ref.mRefNum.mContentFile),
|
|
|
|
/*hint*/ "", CSMDoc::Message::Severity_Warning);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
int index = getIntIndex(iter->second);
|
|
|
|
|
|
|
|
// ensure we have the same record id for setRecord()
|
|
|
|
ref.mId = getRecord(index).get().mId;
|
|
|
|
ref.mIdNum = extractIdNum(ref.mId);
|
|
|
|
|
2022-05-29 11:25:17 +00:00
|
|
|
auto record = std::make_unique<Record<CellRef>>();
|
2021-08-28 04:09:55 +00:00
|
|
|
// TODO: check whether a base record be moved
|
|
|
|
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
|
|
|
|
(base ? record->mBase : record->mModified) = std::move(ref);
|
|
|
|
|
|
|
|
// overwrite original record
|
|
|
|
setRecord(index, std::move(record));
|
|
|
|
|
|
|
|
continue; // NOTE: assumed moved references are not deleted at the same time
|
|
|
|
}
|
2014-05-20 07:28:18 +00:00
|
|
|
|
2015-07-21 17:25:43 +00:00
|
|
|
if (isDeleted)
|
2014-05-20 07:02:22 +00:00
|
|
|
{
|
2015-04-26 04:55:40 +00:00
|
|
|
if (iter == cache.end())
|
2014-05-20 07:28:18 +00:00
|
|
|
{
|
|
|
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex));
|
2022-09-22 18:26:05 +00:00
|
|
|
|
2021-07-23 09:07:56 +00:00
|
|
|
messages.add(id,
|
|
|
|
"Attempt to delete a non-existent reference - RefNum index " + std::to_string(ref.mRefNum.mIndex)
|
|
|
|
+ ", refID " + ref.mRefID + ", content file index " + std::to_string(ref.mRefNum.mContentFile),
|
|
|
|
/*hint*/ "", CSMDoc::Message::Severity_Warning);
|
2014-05-20 07:28:18 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-07-23 09:07:56 +00:00
|
|
|
int index = getIntIndex(iter->second);
|
2014-05-20 07:28:18 +00:00
|
|
|
|
2015-07-15 17:53:08 +00:00
|
|
|
if (base)
|
2014-05-20 07:28:18 +00:00
|
|
|
{
|
|
|
|
removeRows(index, 1);
|
2015-04-26 04:55:40 +00:00
|
|
|
cache.erase(iter);
|
2014-05-20 07:28:18 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-05-29 11:25:17 +00:00
|
|
|
auto record = std::make_unique<Record<CellRef>>(getRecord(index));
|
2021-07-23 23:17:48 +00:00
|
|
|
record->mState = RecordBase::State_Deleted;
|
|
|
|
setRecord(index, std::move(record));
|
2014-05-20 07:28:18 +00:00
|
|
|
}
|
2013-07-06 15:03:18 +00:00
|
|
|
|
2014-05-20 07:02:22 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-04-26 04:55:40 +00:00
|
|
|
if (iter == cache.end())
|
2014-05-20 07:02:22 +00:00
|
|
|
{
|
|
|
|
// new reference
|
2021-07-23 09:07:56 +00:00
|
|
|
ref.mIdNum = mNextId; // FIXME: fragile
|
2014-05-20 07:02:22 +00:00
|
|
|
ref.mId = getNewId();
|
|
|
|
|
2021-07-23 09:07:56 +00:00
|
|
|
cache.emplace(refNum, ref.mIdNum);
|
2021-07-23 05:30:33 +00:00
|
|
|
|
2022-05-29 11:25:17 +00:00
|
|
|
auto record = std::make_unique<Record<CellRef>>();
|
2021-07-23 04:21:21 +00:00
|
|
|
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
|
|
|
|
(base ? record->mBase : record->mModified) = std::move(ref);
|
2013-07-06 15:03:18 +00:00
|
|
|
|
2021-07-23 04:21:21 +00:00
|
|
|
appendRecord(std::move(record));
|
2014-05-20 07:02:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// old reference -> merge
|
2021-07-23 09:07:56 +00:00
|
|
|
int index = getIntIndex(iter->second);
|
|
|
|
#if 0
|
|
|
|
// ref.mRefNum.mIndex : the key
|
|
|
|
// iter->second : previously cached idNum for the key
|
|
|
|
// index : position of the record for that idNum
|
|
|
|
// getRecord(index).get() : record in the index position
|
|
|
|
assert(iter->second != getRecord(index).get().mIdNum); // sanity check
|
2014-05-20 07:02:22 +00:00
|
|
|
|
2021-07-23 09:07:56 +00:00
|
|
|
// check if the plugin used the same RefNum index for a different record
|
|
|
|
if (ref.mRefID != getRecord(index).get().mRefID)
|
|
|
|
{
|
|
|
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex));
|
|
|
|
messages.add(id,
|
|
|
|
"RefNum renamed from RefID \"" + getRecord(index).get().mRefID + "\" to \""
|
|
|
|
+ ref.mRefID + "\" (RefNum index " + std::to_string(ref.mRefNum.mIndex) + ")",
|
|
|
|
/*hint*/"",
|
|
|
|
CSMDoc::Message::Severity_Info);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
ref.mId = getRecord(index).get().mId;
|
|
|
|
ref.mIdNum = extractIdNum(ref.mId);
|
2014-05-20 07:02:22 +00:00
|
|
|
|
2022-05-29 11:25:17 +00:00
|
|
|
auto record = std::make_unique<Record<CellRef>>(getRecord(index));
|
2021-07-23 04:21:21 +00:00
|
|
|
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified;
|
|
|
|
(base ? record->mBase : record->mModified) = std::move(ref);
|
2014-05-20 07:02:22 +00:00
|
|
|
|
2021-07-23 04:21:21 +00:00
|
|
|
setRecord(index, std::move(record));
|
2014-05-20 07:02:22 +00:00
|
|
|
}
|
|
|
|
}
|
2013-07-30 10:53:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string CSMWorld::RefCollection::getNewId()
|
|
|
|
{
|
2018-12-23 16:05:00 +00:00
|
|
|
return "ref#" + std::to_string(mNextId++);
|
2014-01-21 20:37:21 +00:00
|
|
|
}
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2021-08-03 22:23:22 +00:00
|
|
|
unsigned int CSMWorld::RefCollection::extractIdNum(std::string_view id) const
|
2021-07-23 09:07:56 +00:00
|
|
|
{
|
2021-08-03 20:47:14 +00:00
|
|
|
std::string::size_type separator = id.find_last_of('#');
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2021-08-03 20:47:14 +00:00
|
|
|
if (separator == std::string::npos)
|
2021-08-03 22:23:22 +00:00
|
|
|
throw std::runtime_error("invalid ref ID: " + std::string(id));
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2021-08-03 22:23:22 +00:00
|
|
|
return static_cast<unsigned int>(std::stoi(std::string(id.substr(separator + 1))));
|
2021-07-23 09:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int CSMWorld::RefCollection::getIntIndex(unsigned int id) const
|
|
|
|
{
|
|
|
|
int index = searchId(id);
|
|
|
|
|
|
|
|
if (index == -1)
|
|
|
|
throw std::runtime_error("invalid RefNum: " + std::to_string(id));
|
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
int CSMWorld::RefCollection::searchId(unsigned int id) const
|
|
|
|
{
|
|
|
|
std::map<unsigned int, int>::const_iterator iter = mRefIndex.find(id);
|
|
|
|
|
|
|
|
if (iter == mRefIndex.end())
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return iter->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSMWorld::RefCollection::removeRows(int index, int count)
|
|
|
|
{
|
|
|
|
Collection<CellRef, IdAccessor<CellRef>>::removeRows(index, count); // erase records only
|
|
|
|
|
|
|
|
std::map<unsigned int, int>::iterator iter = mRefIndex.begin();
|
|
|
|
while (iter != mRefIndex.end())
|
|
|
|
{
|
|
|
|
if (iter->second >= index)
|
|
|
|
{
|
|
|
|
if (iter->second >= index + count)
|
|
|
|
{
|
|
|
|
iter->second -= count;
|
|
|
|
++iter;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
mRefIndex.erase(iter++);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
++iter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSMWorld::RefCollection::appendBlankRecord(const std::string& id, UniversalId::Type type)
|
|
|
|
{
|
2022-05-29 11:25:17 +00:00
|
|
|
auto record = std::make_unique<Record<CellRef>>();
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2021-07-23 23:17:48 +00:00
|
|
|
record->mState = Record<CellRef>::State_ModifiedOnly;
|
|
|
|
record->mModified.blank();
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2021-07-23 23:17:48 +00:00
|
|
|
record->get().mId = id;
|
|
|
|
record->get().mIdNum = extractIdNum(id);
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2021-07-23 23:17:48 +00:00
|
|
|
Collection<CellRef, IdAccessor<CellRef>>::appendRecord(std::move(record));
|
2021-07-23 09:07:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CSMWorld::RefCollection::cloneRecord(
|
|
|
|
const std::string& origin, const std::string& destination, const UniversalId::Type type)
|
|
|
|
{
|
2022-05-29 11:25:17 +00:00
|
|
|
auto copy = std::make_unique<Record<CellRef>>();
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2022-05-29 11:25:17 +00:00
|
|
|
copy->mModified = getRecord(origin).get();
|
|
|
|
copy->mState = RecordBase::State_ModifiedOnly;
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2022-05-29 11:25:17 +00:00
|
|
|
copy->get().mId = destination;
|
|
|
|
copy->get().mIdNum = extractIdNum(destination);
|
2021-07-23 09:07:56 +00:00
|
|
|
|
2022-05-29 11:25:17 +00:00
|
|
|
insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord()
|
2021-07-23 09:07:56 +00:00
|
|
|
}
|
|
|
|
|
2021-09-04 16:07:23 +00:00
|
|
|
int CSMWorld::RefCollection::searchId(std::string_view id) const
|
2021-07-23 09:07:56 +00:00
|
|
|
{
|
|
|
|
return searchId(extractIdNum(id));
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSMWorld::RefCollection::appendRecord(std::unique_ptr<RecordBase> record, UniversalId::Type type)
|
|
|
|
{
|
|
|
|
int index = getAppendIndex(/*id*/ "", type); // for CellRef records id is ignored
|
|
|
|
|
|
|
|
mRefIndex.insert(std::make_pair(static_cast<Record<CellRef>*>(record.get())->get().mIdNum, index));
|
|
|
|
|
|
|
|
Collection<CellRef, IdAccessor<CellRef>>::insertRecord(std::move(record), index, type); // add records only
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSMWorld::RefCollection::insertRecord(std::unique_ptr<RecordBase> record, int index, UniversalId::Type type)
|
|
|
|
{
|
|
|
|
int size = getAppendIndex(/*id*/ "", type); // for CellRef records id is ignored
|
|
|
|
unsigned int idNum = static_cast<Record<CellRef>*>(record.get())->get().mIdNum;
|
|
|
|
|
|
|
|
Collection<CellRef, IdAccessor<CellRef>>::insertRecord(std::move(record), index, type); // add records only
|
|
|
|
|
|
|
|
if (index < size - 1)
|
|
|
|
{
|
|
|
|
for (std::map<unsigned int, int>::iterator iter(mRefIndex.begin()); iter != mRefIndex.end(); ++iter)
|
|
|
|
{
|
|
|
|
if (iter->second >= index)
|
|
|
|
++(iter->second);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mRefIndex.insert(std::make_pair(idNum, index));
|
|
|
|
}
|