- Add support for multiple plugins trying to modify the same reference

- Fix a small signed/unsigned warning
pull/16/head
Mark Siewert 12 years ago
parent d6377fb2e3
commit a8e02779b2

@ -96,6 +96,11 @@ namespace MWWorld
// Get each reference in turn
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::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
// all files/readers used by the engine. This will greaty help to accelerate
// parsing of reference IDs.
size_t index = ~0;
int index = ~0;
const ESM::ESMReader::MasterList &masters = esm.getMasters();
std::vector<ESM::ESMReader> *allPlugins = esm.getGlobalReaderList();
for (size_t j = 0; j < masters.size(); j++) {
ESM::MasterData &mast = const_cast<ESM::MasterData&>(masters[j]);
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;
std::string fnamecandidate = boost::filesystem::path(candidate).filename().string();
if (fname == fnamecandidate) {
@ -44,7 +44,7 @@ void ESMStore::load(ESM::ESMReader &esm)
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,
// the launcher should have taken care of this.
std::string fstring = "File " + fname + " asks for parent file " + masters[j].name

@ -94,6 +94,9 @@ namespace MWWorld
ESMStore()
: mDynamicCount(0)
{
// Cell store needs access to this for tracking moved references
mCells.mEsmStore = this;
mStores[ESM::REC_ACTI] = &mActivators;
mStores[ESM::REC_ALCH] = &mPotions;
mStores[ESM::REC_APPA] = &mAppas;

@ -399,8 +399,7 @@ namespace MWWorld
DynamicInt mDynamicInt;
DynamicExt mDynamicExt;
const ESM::Cell *search(const ESM::Cell &cell) const {
if (cell.isExterior()) {
return search(cell.getGridX(), cell.getGridY());
@ -409,6 +408,8 @@ namespace MWWorld
}
public:
ESMStore *mEsmStore;
typedef SharedIterator<ESM::Cell> iterator;
Store<ESM::Cell>()
@ -450,6 +451,30 @@ namespace MWWorld
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 *ptr = search(id);
if (ptr == 0) {
@ -500,7 +525,7 @@ namespace MWWorld
cell->mName = id;
// The cell itself takes care of all the hairy details
cell->load(esm);
cell->load(esm, *mEsmStore);
if(cell->mData.mFlags & ESM::Cell::Interior)
{
@ -515,7 +540,6 @@ namespace MWWorld
*oldcell = *cell;
} else
mInt[idLower] = *cell;
delete cell;
}
else
{
@ -526,12 +550,23 @@ namespace MWWorld
oldcell->mContextList.push_back(cell->mContextList.at(0));
// copy list into new cell
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
*oldcell = *cell;
} else
mExt[std::make_pair(cell->mData.mX, cell->mData.mY)] = *cell;
delete cell;
}
delete cell;
}
iterator intBegin() const {

@ -2,11 +2,15 @@
#include <string>
#include <sstream>
#include <c++/4.6/list>
#include <list>
#include <boost/concept_check.hpp>
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include <apps/openmw/mwworld/store.hpp>
#include <apps/openmw/mwworld/cellstore.hpp>
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
// cell?
// TODO: treat the special case "another plugin moved this ref, but we want to delete it"!
if (esm.isNextSub("DELE")) {
esm.skipHSub();
}
@ -110,6 +115,33 @@ void Cell::load(ESMReader &esm)
if (esm.isNextSub("NAM0")) {
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
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.
if (!esm.hasMoreSubs())
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")) {
// Moved existing reference across cell boundaries, so interpret the blocks correctly.
// FIXME: Right now, we don't do anything with this data. This might result in weird behaviour,
// where a moved reference does not appear because the owning cell (i.e. this cell) is not
// 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.
esm.skipRecord(); // skip MVRF
esm.skipRecord(); // skip CNDT
// That should be it, I haven't seen any other fields yet.
}
// 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");
ref.mRefID = esm.getHNString("NAME");
@ -293,4 +317,20 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
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 "defs.hpp"
#include <apps/openmw/mwbase/world.hpp>
/*
namespace MWWorld {
class ESMStore;
class CellStore;
}
*/
namespace ESM
{
@ -86,6 +94,26 @@ public:
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,
buildings) and landscape (for exterior cells). Cells frequently
also has other associated LAND and PGRD records. Combined, all this
@ -131,8 +159,17 @@ struct Cell
bool mWaterInt;
int mMapColor;
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);
bool isExterior() const
@ -167,6 +204,11 @@ struct Cell
reuse one memory location without blanking it between calls.
*/
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

Loading…
Cancel
Save