mirror of
https://github.com/OpenMW/openmw.git
synced 2026-01-10 20:00:54 +00:00
Slowly moving through the open-cs errors Good progress in openCS Very good progress on openCS Getting closer with openCS OpenCS compiles and runs! Didn't have time to test it all though ix openMW everything compiles on windows?? Fix gcc Fix Clang
354 lines
12 KiB
C++
354 lines
12 KiB
C++
#include "refcollection.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <stdexcept>
|
|
#include <string_view>
|
|
#include <utility>
|
|
|
|
#include <apps/opencs/model/world/collection.hpp>
|
|
|
|
#include <components/esm3/cellref.hpp>
|
|
#include <components/esm3/loadcell.hpp>
|
|
|
|
#include "cell.hpp"
|
|
#include "record.hpp"
|
|
#include "ref.hpp"
|
|
#include "universalid.hpp"
|
|
|
|
#include "../doc/messages.hpp"
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
void CSMWorld::RefCollection::load(ESM::ESMReader& reader, int cellIndex, bool base,
|
|
std::map<unsigned int, unsigned int>& cache, CSMDoc::Messages& messages)
|
|
{
|
|
Record<Cell> cell = mCells.getRecord(cellIndex);
|
|
|
|
Cell& cell2 = base ? cell.mBase : cell.mModified;
|
|
|
|
CellRef ref;
|
|
ref.mNew = false;
|
|
ESM::MovedCellRef mref;
|
|
mref.mRefNum.mIndex = 0;
|
|
bool isDeleted = false;
|
|
bool isMoved = false;
|
|
|
|
while (ESM::Cell::getNextRef(reader, ref, isDeleted, mref, isMoved))
|
|
{
|
|
// 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 : ESM::RefId::sEmpty;
|
|
|
|
if (cell.get().isExterior())
|
|
{
|
|
// Autocalculate the cell index from coordinates first
|
|
std::pair<int, int> index = ref.getCellIndex();
|
|
|
|
ref.mCell = ESM::RefId::stringRefId("#" + std::to_string(index.first) + " " + std::to_string(index.second));
|
|
|
|
// Handle non-base moved references
|
|
if (!base && isMoved)
|
|
{
|
|
// 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
|
|
ref.mOriginalCell = cell2.mId;
|
|
|
|
// 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
|
|
if (index.first != mref.mTarget[0] || index.second != mref.mTarget[1])
|
|
{
|
|
ESM::RefId indexCell = ref.mCell;
|
|
ref.mCell = ESM::RefId::stringRefId("#" + 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.getRefIdString() + " (cell " + indexCell.getRefIdString() + ")"
|
|
" does not match the target cell (" + ref.mCell.getRefIdString() + ")",
|
|
std::string(), CSMDoc::Message::Severity_Warning);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ref.mCell = cell2.mId;
|
|
|
|
if (ref.mRefNum.mContentFile != -1 && !base)
|
|
{
|
|
ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24;
|
|
ref.mRefNum.mIndex &= 0x00ffffff;
|
|
}
|
|
|
|
unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff)
|
|
| (ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24;
|
|
|
|
std::map<unsigned int, unsigned int>::iterator iter = cache.find(refNum);
|
|
|
|
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.getRefIdString() + ", 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.getRefIdString());
|
|
|
|
auto record = std::make_unique<Record<CellRef>>();
|
|
// 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
|
|
}
|
|
|
|
if (isDeleted)
|
|
{
|
|
if (iter == cache.end())
|
|
{
|
|
CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, mCells.getId(cellIndex));
|
|
|
|
messages.add(id,
|
|
"Attempt to delete a non-existent reference - RefNum index " + std::to_string(ref.mRefNum.mIndex)
|
|
+ ", refID " + ref.mRefID.getRefIdString() + ", content file index " + std::to_string(ref.mRefNum.mContentFile),
|
|
/*hint*/ "", CSMDoc::Message::Severity_Warning);
|
|
continue;
|
|
}
|
|
|
|
int index = getIntIndex(iter->second);
|
|
|
|
if (base)
|
|
{
|
|
removeRows(index, 1);
|
|
cache.erase(iter);
|
|
}
|
|
else
|
|
{
|
|
auto record = std::make_unique<Record<CellRef>>(getRecord(index));
|
|
record->mState = RecordBase::State_Deleted;
|
|
setRecord(index, std::move(record));
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (iter == cache.end())
|
|
{
|
|
// new reference
|
|
ref.mIdNum = mNextId; // FIXME: fragile
|
|
ref.mId = ESM::RefId::stringRefId(getNewId());
|
|
|
|
cache.emplace(refNum, ref.mIdNum);
|
|
|
|
auto record = std::make_unique<Record<CellRef>>();
|
|
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
|
|
(base ? record->mBase : record->mModified) = std::move(ref);
|
|
|
|
appendRecord(std::move(record));
|
|
}
|
|
else
|
|
{
|
|
// old reference -> merge
|
|
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
|
|
|
|
// 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.getRefIdString());
|
|
|
|
auto record = std::make_unique<Record<CellRef>>(getRecord(index));
|
|
record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_Modified;
|
|
(base ? record->mBase : record->mModified) = std::move(ref);
|
|
|
|
setRecord(index, std::move(record));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string CSMWorld::RefCollection::getNewId()
|
|
{
|
|
return "ref#" + std::to_string(mNextId++);
|
|
}
|
|
|
|
unsigned int CSMWorld::RefCollection::extractIdNum(std::string_view id) const
|
|
{
|
|
std::string::size_type separator = id.find_last_of('#');
|
|
|
|
if (separator == std::string::npos)
|
|
throw std::runtime_error("invalid ref ID: " + std::string(id));
|
|
|
|
return static_cast<unsigned int>(std::stoi(std::string(id.substr(separator + 1))));
|
|
}
|
|
|
|
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)
|
|
{
|
|
auto record = std::make_unique<Record<CellRef>>();
|
|
|
|
record->mState = Record<CellRef>::State_ModifiedOnly;
|
|
record->mModified.blank();
|
|
|
|
record->get().mId = ESM::RefId::stringRefId(id);
|
|
record->get().mIdNum = extractIdNum(id);
|
|
|
|
Collection<CellRef, IdAccessor<CellRef>>::appendRecord(std::move(record));
|
|
}
|
|
|
|
void CSMWorld::RefCollection::appendBlankRecord(const ESM::RefId& id, UniversalId::Type type)
|
|
{
|
|
auto record = std::make_unique<Record<CellRef>>();
|
|
|
|
record->mState = Record<CellRef>::State_ModifiedOnly;
|
|
record->mModified.blank();
|
|
|
|
record->get().mId = id;
|
|
record->get().mIdNum = extractIdNum(id.getRefIdString());
|
|
|
|
Collection<CellRef, IdAccessor<CellRef>>::appendRecord(std::move(record));
|
|
}
|
|
|
|
void CSMWorld::RefCollection::cloneRecord(
|
|
const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type)
|
|
{
|
|
auto copy = std::make_unique<Record<CellRef>>();
|
|
|
|
copy->mModified = getRecord(origin).get();
|
|
copy->mState = RecordBase::State_ModifiedOnly;
|
|
|
|
copy->get().mId = destination;
|
|
copy->get().mIdNum = extractIdNum(destination.getRefIdString());
|
|
|
|
insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord()
|
|
}
|
|
|
|
int CSMWorld::RefCollection::searchId(std::string_view id) const
|
|
{
|
|
return searchId(extractIdNum(id));
|
|
}
|
|
|
|
int CSMWorld::RefCollection::searchId(const ESM::RefId& id) const
|
|
{
|
|
return searchId(extractIdNum(id.getRefIdString()));
|
|
}
|
|
|
|
void CSMWorld::RefCollection::appendRecord(std::unique_ptr<RecordBase> record, UniversalId::Type type)
|
|
{
|
|
int index = getAppendIndex(/*id*/ ESM::RefId::sEmpty, 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*/ ESM::RefId::sEmpty, 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));
|
|
}
|