mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-03-30 09:36:43 +00:00
- Add support for multiple plugins trying to modify the same reference
- Fix a small signed/unsigned warning
This commit is contained in:
parent
d6377fb2e3
commit
a8e02779b2
6 changed files with 203 additions and 27 deletions
|
@ -96,6 +96,11 @@ namespace MWWorld
|
||||||
// Get each reference in turn
|
// Get each reference in turn
|
||||||
while(mCell->getNextRef(esm[index], ref))
|
while(mCell->getNextRef(esm[index], ref))
|
||||||
{
|
{
|
||||||
|
// Don't load reference if it was moved to a different cell.
|
||||||
|
if (mCell->mMovedRefs.find(ref.mRefnum) != mCell->mMovedRefs.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
std::string lowerCase;
|
std::string lowerCase;
|
||||||
|
|
||||||
std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase),
|
std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase),
|
||||||
|
@ -139,5 +144,56 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load moved references, from separately tracked list.
|
||||||
|
for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); it++)
|
||||||
|
{
|
||||||
|
// Doesn't seem to work in one line... huh? Too sleepy to check...
|
||||||
|
//const ESM::CellRef &ref0 = it->second;
|
||||||
|
ESM::CellRef &ref = const_cast<ESM::CellRef&>(it->second);
|
||||||
|
|
||||||
|
std::string lowerCase;
|
||||||
|
|
||||||
|
std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase),
|
||||||
|
(int(*)(int)) std::tolower);
|
||||||
|
|
||||||
|
int rec = store.find(ref.mRefID);
|
||||||
|
|
||||||
|
ref.mRefID = lowerCase;
|
||||||
|
|
||||||
|
/* We can optimize this further by storing the pointer to the
|
||||||
|
record itself in store.all, so that we don't need to look it
|
||||||
|
up again here. However, never optimize. There are infinite
|
||||||
|
opportunities to do that later.
|
||||||
|
*/
|
||||||
|
switch(rec)
|
||||||
|
{
|
||||||
|
case ESM::REC_ACTI: mActivators.load(ref, store); break;
|
||||||
|
case ESM::REC_ALCH: mPotions.load(ref, store); break;
|
||||||
|
case ESM::REC_APPA: mAppas.load(ref, store); break;
|
||||||
|
case ESM::REC_ARMO: mArmors.load(ref, store); break;
|
||||||
|
case ESM::REC_BOOK: mBooks.load(ref, store); break;
|
||||||
|
case ESM::REC_CLOT: mClothes.load(ref, store); break;
|
||||||
|
case ESM::REC_CONT: mContainers.load(ref, store); break;
|
||||||
|
case ESM::REC_CREA: mCreatures.load(ref, store); break;
|
||||||
|
case ESM::REC_DOOR: mDoors.load(ref, store); break;
|
||||||
|
case ESM::REC_INGR: mIngreds.load(ref, store); break;
|
||||||
|
case ESM::REC_LEVC: mCreatureLists.load(ref, store); break;
|
||||||
|
case ESM::REC_LEVI: mItemLists.load(ref, store); break;
|
||||||
|
case ESM::REC_LIGH: mLights.load(ref, store); break;
|
||||||
|
case ESM::REC_LOCK: mLockpicks.load(ref, store); break;
|
||||||
|
case ESM::REC_MISC: mMiscItems.load(ref, store); break;
|
||||||
|
case ESM::REC_NPC_: mNpcs.load(ref, store); break;
|
||||||
|
case ESM::REC_PROB: mProbes.load(ref, store); break;
|
||||||
|
case ESM::REC_REPA: mRepairs.load(ref, store); break;
|
||||||
|
case ESM::REC_STAT: mStatics.load(ref, store); break;
|
||||||
|
case ESM::REC_WEAP: mWeapons.load(ref, store); break;
|
||||||
|
|
||||||
|
case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break;
|
||||||
|
default:
|
||||||
|
std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,13 +30,13 @@ void ESMStore::load(ESM::ESMReader &esm)
|
||||||
// Cache parent esX files by tracking their indices in the global list of
|
// Cache parent esX files by tracking their indices in the global list of
|
||||||
// all files/readers used by the engine. This will greaty help to accelerate
|
// all files/readers used by the engine. This will greaty help to accelerate
|
||||||
// parsing of reference IDs.
|
// parsing of reference IDs.
|
||||||
size_t index = ~0;
|
int index = ~0;
|
||||||
const ESM::ESMReader::MasterList &masters = esm.getMasters();
|
const ESM::ESMReader::MasterList &masters = esm.getMasters();
|
||||||
std::vector<ESM::ESMReader> *allPlugins = esm.getGlobalReaderList();
|
std::vector<ESM::ESMReader> *allPlugins = esm.getGlobalReaderList();
|
||||||
for (size_t j = 0; j < masters.size(); j++) {
|
for (size_t j = 0; j < masters.size(); j++) {
|
||||||
ESM::MasterData &mast = const_cast<ESM::MasterData&>(masters[j]);
|
ESM::MasterData &mast = const_cast<ESM::MasterData&>(masters[j]);
|
||||||
std::string fname = mast.name;
|
std::string fname = mast.name;
|
||||||
for (size_t i = 0; i < esm.getIndex(); i++) {
|
for (int i = 0; i < esm.getIndex(); i++) {
|
||||||
const std::string &candidate = allPlugins->at(i).getContext().filename;
|
const std::string &candidate = allPlugins->at(i).getContext().filename;
|
||||||
std::string fnamecandidate = boost::filesystem::path(candidate).filename().string();
|
std::string fnamecandidate = boost::filesystem::path(candidate).filename().string();
|
||||||
if (fname == fnamecandidate) {
|
if (fname == fnamecandidate) {
|
||||||
|
@ -44,7 +44,7 @@ void ESMStore::load(ESM::ESMReader &esm)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (index == (size_t)~0) {
|
if (index == (int)~0) {
|
||||||
// Tried to load a parent file that has not been loaded yet. This is bad,
|
// Tried to load a parent file that has not been loaded yet. This is bad,
|
||||||
// the launcher should have taken care of this.
|
// the launcher should have taken care of this.
|
||||||
std::string fstring = "File " + fname + " asks for parent file " + masters[j].name
|
std::string fstring = "File " + fname + " asks for parent file " + masters[j].name
|
||||||
|
|
|
@ -94,6 +94,9 @@ namespace MWWorld
|
||||||
ESMStore()
|
ESMStore()
|
||||||
: mDynamicCount(0)
|
: mDynamicCount(0)
|
||||||
{
|
{
|
||||||
|
// Cell store needs access to this for tracking moved references
|
||||||
|
mCells.mEsmStore = this;
|
||||||
|
|
||||||
mStores[ESM::REC_ACTI] = &mActivators;
|
mStores[ESM::REC_ACTI] = &mActivators;
|
||||||
mStores[ESM::REC_ALCH] = &mPotions;
|
mStores[ESM::REC_ALCH] = &mPotions;
|
||||||
mStores[ESM::REC_APPA] = &mAppas;
|
mStores[ESM::REC_APPA] = &mAppas;
|
||||||
|
|
|
@ -399,8 +399,7 @@ namespace MWWorld
|
||||||
|
|
||||||
DynamicInt mDynamicInt;
|
DynamicInt mDynamicInt;
|
||||||
DynamicExt mDynamicExt;
|
DynamicExt mDynamicExt;
|
||||||
|
|
||||||
|
|
||||||
const ESM::Cell *search(const ESM::Cell &cell) const {
|
const ESM::Cell *search(const ESM::Cell &cell) const {
|
||||||
if (cell.isExterior()) {
|
if (cell.isExterior()) {
|
||||||
return search(cell.getGridX(), cell.getGridY());
|
return search(cell.getGridX(), cell.getGridY());
|
||||||
|
@ -409,6 +408,8 @@ namespace MWWorld
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
ESMStore *mEsmStore;
|
||||||
|
|
||||||
typedef SharedIterator<ESM::Cell> iterator;
|
typedef SharedIterator<ESM::Cell> iterator;
|
||||||
|
|
||||||
Store<ESM::Cell>()
|
Store<ESM::Cell>()
|
||||||
|
@ -450,6 +451,30 @@ namespace MWWorld
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ESM::Cell *searchOrCreate(int x, int y) {
|
||||||
|
ESM::Cell cell;
|
||||||
|
cell.mData.mX = x, cell.mData.mY = y;
|
||||||
|
|
||||||
|
std::pair<int, int> key(x, y);
|
||||||
|
std::map<std::pair<int, int>, ESM::Cell>::const_iterator it = mExt.find(key);
|
||||||
|
if (it != mExt.end()) {
|
||||||
|
return &(it->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
DynamicExt::const_iterator dit = mDynamicExt.find(key);
|
||||||
|
if (dit != mDynamicExt.end()) {
|
||||||
|
return &dit->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESM::Cell *newCell = new ESM::Cell;
|
||||||
|
newCell->mData.mX = x;
|
||||||
|
newCell->mData.mY = y;
|
||||||
|
mExt[std::make_pair(x, y)] = *newCell;
|
||||||
|
delete newCell;
|
||||||
|
|
||||||
|
return &mExt[std::make_pair(x, y)];
|
||||||
|
}
|
||||||
|
|
||||||
const ESM::Cell *find(const std::string &id) const {
|
const ESM::Cell *find(const std::string &id) const {
|
||||||
const ESM::Cell *ptr = search(id);
|
const ESM::Cell *ptr = search(id);
|
||||||
if (ptr == 0) {
|
if (ptr == 0) {
|
||||||
|
@ -500,7 +525,7 @@ namespace MWWorld
|
||||||
cell->mName = id;
|
cell->mName = id;
|
||||||
|
|
||||||
// The cell itself takes care of all the hairy details
|
// The cell itself takes care of all the hairy details
|
||||||
cell->load(esm);
|
cell->load(esm, *mEsmStore);
|
||||||
|
|
||||||
if(cell->mData.mFlags & ESM::Cell::Interior)
|
if(cell->mData.mFlags & ESM::Cell::Interior)
|
||||||
{
|
{
|
||||||
|
@ -515,7 +540,6 @@ namespace MWWorld
|
||||||
*oldcell = *cell;
|
*oldcell = *cell;
|
||||||
} else
|
} else
|
||||||
mInt[idLower] = *cell;
|
mInt[idLower] = *cell;
|
||||||
delete cell;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -526,12 +550,23 @@ namespace MWWorld
|
||||||
oldcell->mContextList.push_back(cell->mContextList.at(0));
|
oldcell->mContextList.push_back(cell->mContextList.at(0));
|
||||||
// copy list into new cell
|
// copy list into new cell
|
||||||
cell->mContextList = oldcell->mContextList;
|
cell->mContextList = oldcell->mContextList;
|
||||||
|
// merge lists of leased references, use newer data in case of conflict
|
||||||
|
for (ESM::MovedCellRefTracker::const_iterator it = cell->mMovedRefs.begin(); it != cell->mMovedRefs.end(); it++) {
|
||||||
|
// remove reference from current leased ref tracker and add it to new cell
|
||||||
|
if (oldcell->mMovedRefs.find(it->second.mRefnum) != oldcell->mMovedRefs.end()) {
|
||||||
|
ESM::MovedCellRef target0 = oldcell->mMovedRefs[it->second.mRefnum];
|
||||||
|
ESM::Cell *wipecell = const_cast<ESM::Cell*>(search(target0.mTarget[0], target0.mTarget[1]));
|
||||||
|
wipecell->mLeasedRefs.erase(it->second.mRefnum);
|
||||||
|
}
|
||||||
|
oldcell->mMovedRefs[it->second.mRefnum] = it->second;
|
||||||
|
}
|
||||||
|
cell->mMovedRefs = oldcell->mMovedRefs;
|
||||||
// have new cell replace old cell
|
// have new cell replace old cell
|
||||||
*oldcell = *cell;
|
*oldcell = *cell;
|
||||||
} else
|
} else
|
||||||
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
|
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
|
||||||
delete cell;
|
|
||||||
}
|
}
|
||||||
|
delete cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator intBegin() const {
|
iterator intBegin() const {
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <c++/4.6/list>
|
#include <list>
|
||||||
|
#include <boost/concept_check.hpp>
|
||||||
|
|
||||||
#include "esmreader.hpp"
|
#include "esmreader.hpp"
|
||||||
#include "esmwriter.hpp"
|
#include "esmwriter.hpp"
|
||||||
|
|
||||||
|
#include <apps/openmw/mwworld/store.hpp>
|
||||||
|
#include <apps/openmw/mwworld/cellstore.hpp>
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -64,10 +68,11 @@ void CellRef::save(ESMWriter &esm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Cell::load(ESMReader &esm)
|
void Cell::load(ESMReader &esm, MWWorld::ESMStore &store)
|
||||||
{
|
{
|
||||||
// Ignore this for now, it might mean we should delete the entire
|
// Ignore this for now, it might mean we should delete the entire
|
||||||
// cell?
|
// cell?
|
||||||
|
// TODO: treat the special case "another plugin moved this ref, but we want to delete it"!
|
||||||
if (esm.isNextSub("DELE")) {
|
if (esm.isNextSub("DELE")) {
|
||||||
esm.skipHSub();
|
esm.skipHSub();
|
||||||
}
|
}
|
||||||
|
@ -110,6 +115,33 @@ void Cell::load(ESMReader &esm)
|
||||||
if (esm.isNextSub("NAM0")) {
|
if (esm.isNextSub("NAM0")) {
|
||||||
esm.getHT(mNAM0);
|
esm.getHT(mNAM0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preload moved references
|
||||||
|
while (esm.isNextSub("MVRF")) {
|
||||||
|
CellRef ref;
|
||||||
|
MovedCellRef cMRef;
|
||||||
|
getNextMVRF(esm, cMRef);
|
||||||
|
|
||||||
|
MWWorld::Store<ESM::Cell> &cStore = const_cast<MWWorld::Store<ESM::Cell>&>(store.get<ESM::Cell>());
|
||||||
|
ESM::Cell *cellAlt = const_cast<ESM::Cell*>(cStore.searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1]));
|
||||||
|
|
||||||
|
// Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following
|
||||||
|
// implementation when the oher implementation works as well.
|
||||||
|
getNextRef(esm, ref);
|
||||||
|
std::string lowerCase;
|
||||||
|
|
||||||
|
std::transform (ref.mRefID.begin(), ref.mRefID.end(), std::back_inserter (lowerCase),
|
||||||
|
(int(*)(int)) std::tolower);
|
||||||
|
|
||||||
|
// Add data required to make reference appear in the correct cell.
|
||||||
|
/*
|
||||||
|
std::cout << "Moving refnumber! First cell: " << mData.mX << " " << mData.mY << std::endl;
|
||||||
|
std::cout << " New cell: " << cMRef.mTarget[0] << " " << cMRef.mTarget[0] << std::endl;
|
||||||
|
std::cout << "Refnumber (MVRF): " << cMRef.mRefnum << " (FRMR) " << ref.mRefnum << std::endl;
|
||||||
|
*/
|
||||||
|
mMovedRefs[cMRef.mRefnum] = cMRef;
|
||||||
|
cellAlt->mLeasedRefs[ref.mRefnum] = ref;
|
||||||
|
}
|
||||||
|
|
||||||
// Save position of the cell references and move on
|
// Save position of the cell references and move on
|
||||||
mContextList.push_back(esm.getContext());
|
mContextList.push_back(esm.getContext());
|
||||||
|
@ -171,23 +203,15 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
|
||||||
// TODO: Try and document reference numbering, I don't think this has been done anywhere else.
|
// TODO: Try and document reference numbering, I don't think this has been done anywhere else.
|
||||||
if (!esm.hasMoreSubs())
|
if (!esm.hasMoreSubs())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// NOTE: We should not need this check. It is a safety check until we have checked
|
||||||
|
// more plugins, and how they treat these moved references.
|
||||||
if (esm.isNextSub("MVRF")) {
|
if (esm.isNextSub("MVRF")) {
|
||||||
// Moved existing reference across cell boundaries, so interpret the blocks correctly.
|
esm.skipRecord(); // skip MVRF
|
||||||
// FIXME: Right now, we don't do anything with this data. This might result in weird behaviour,
|
esm.skipRecord(); // skip CNDT
|
||||||
// where a moved reference does not appear because the owning cell (i.e. this cell) is not
|
// That should be it, I haven't seen any other fields yet.
|
||||||
// loaded in memory.
|
|
||||||
int movedRefnum = 0;
|
|
||||||
int destCell[2];
|
|
||||||
esm.getHT(movedRefnum);
|
|
||||||
esm.getHNT(destCell, "CNDT");
|
|
||||||
// TODO: Figure out what happens when a reference has moved into an interior cell. This might
|
|
||||||
// be required for NPCs following the player.
|
|
||||||
}
|
}
|
||||||
// If we have just parsed a MVRF entry, there should be a regular FRMR entry following right there.
|
|
||||||
// With the exception that this bock technically belongs to a different cell than this one.
|
|
||||||
// TODO: Figure out a way to handle these weird references that do not belong to this cell.
|
|
||||||
// This may require some not-so-small behing-the-scenes updates.
|
|
||||||
esm.getHNT(ref.mRefnum, "FRMR");
|
esm.getHNT(ref.mRefnum, "FRMR");
|
||||||
ref.mRefID = esm.getHNString("NAME");
|
ref.mRefID = esm.getHNString("NAME");
|
||||||
|
|
||||||
|
@ -293,4 +317,20 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
|
||||||
|
{
|
||||||
|
esm.getHT(mref.mRefnum);
|
||||||
|
esm.getHNOT(mref.mTarget, "CNDT");
|
||||||
|
|
||||||
|
// Identify references belonging to a parent file and adapt the ID accordingly.
|
||||||
|
int local = (mref.mRefnum & 0xff000000) >> 24;
|
||||||
|
size_t global = esm.getIndex() + 1;
|
||||||
|
mref.mRefnum &= 0x00ffffff; // delete old plugin ID
|
||||||
|
const ESM::ESMReader::MasterList &masters = esm.getMasters();
|
||||||
|
global = masters[local-1].index + 1;
|
||||||
|
mref.mRefnum |= global << 24; // insert global plugin ID
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,14 @@
|
||||||
|
|
||||||
#include "esmcommon.hpp"
|
#include "esmcommon.hpp"
|
||||||
#include "defs.hpp"
|
#include "defs.hpp"
|
||||||
|
#include <apps/openmw/mwbase/world.hpp>
|
||||||
|
|
||||||
|
/*
|
||||||
|
namespace MWWorld {
|
||||||
|
class ESMStore;
|
||||||
|
class CellStore;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
|
@ -86,6 +94,26 @@ public:
|
||||||
void save(ESMWriter &esm);
|
void save(ESMWriter &esm);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Moved cell reference tracking object. This mainly stores the target cell
|
||||||
|
of the reference, so we can easily know where it has been moved when another
|
||||||
|
plugin tries to move it independently.
|
||||||
|
*/
|
||||||
|
class MovedCellRef
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int mRefnum;
|
||||||
|
|
||||||
|
// Target cell (if exterior)
|
||||||
|
int mTarget[2];
|
||||||
|
|
||||||
|
// TODO: Support moving references between exterior and interior cells!
|
||||||
|
// This may happen in saves, when an NPC follows the player. Tribunal
|
||||||
|
// introduces a henchman (which no one uses), so we may need this as well.
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::map<int, MovedCellRef> MovedCellRefTracker;
|
||||||
|
typedef std::map<int, CellRef> CellRefTracker;
|
||||||
|
|
||||||
/* Cells hold data about objects, creatures, statics (rocks, walls,
|
/* Cells hold data about objects, creatures, statics (rocks, walls,
|
||||||
buildings) and landscape (for exterior cells). Cells frequently
|
buildings) and landscape (for exterior cells). Cells frequently
|
||||||
also has other associated LAND and PGRD records. Combined, all this
|
also has other associated LAND and PGRD records. Combined, all this
|
||||||
|
@ -131,8 +159,17 @@ struct Cell
|
||||||
bool mWaterInt;
|
bool mWaterInt;
|
||||||
int mMapColor;
|
int mMapColor;
|
||||||
int mNAM0;
|
int mNAM0;
|
||||||
|
|
||||||
void load(ESMReader &esm);
|
// References "leased" from another cell (i.e. a different cell
|
||||||
|
// introduced this ref, and it has been moved here by a plugin)
|
||||||
|
CellRefTracker mLeasedRefs;
|
||||||
|
MovedCellRefTracker mMovedRefs;
|
||||||
|
|
||||||
|
void load(ESMReader &esm, MWWorld::ESMStore &store);
|
||||||
|
|
||||||
|
// This method is left in for compatibility with esmtool. Parsing moved references currently requires
|
||||||
|
// passing ESMStore, bit it does not know about this parameter, so we do it this way.
|
||||||
|
void load(ESMReader &esm) {};
|
||||||
void save(ESMWriter &esm);
|
void save(ESMWriter &esm);
|
||||||
|
|
||||||
bool isExterior() const
|
bool isExterior() const
|
||||||
|
@ -167,6 +204,11 @@ struct Cell
|
||||||
reuse one memory location without blanking it between calls.
|
reuse one memory location without blanking it between calls.
|
||||||
*/
|
*/
|
||||||
static bool getNextRef(ESMReader &esm, CellRef &ref);
|
static bool getNextRef(ESMReader &esm, CellRef &ref);
|
||||||
|
|
||||||
|
/* This fetches an MVRF record, which is used to track moved references.
|
||||||
|
* Since they are comparably rare, we use a separate method for this.
|
||||||
|
*/
|
||||||
|
static bool getNextMVRF(ESMReader &esm, MovedCellRef &mref);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue