From a8e02779b26e0e7b21761b3489ae58917b28181d Mon Sep 17 00:00:00 2001 From: Mark Siewert Date: Sat, 19 Jan 2013 23:33:18 +0100 Subject: [PATCH] - Add support for multiple plugins trying to modify the same reference - Fix a small signed/unsigned warning --- apps/openmw/mwworld/cellstore.cpp | 56 +++++++++++++++++++++++ apps/openmw/mwworld/esmstore.cpp | 6 +-- apps/openmw/mwworld/esmstore.hpp | 3 ++ apps/openmw/mwworld/store.hpp | 45 ++++++++++++++++--- components/esm/loadcell.cpp | 74 ++++++++++++++++++++++++------- components/esm/loadcell.hpp | 46 ++++++++++++++++++- 6 files changed, 203 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index d007ff981..ecc1bc306 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -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(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"; + } + + } } } diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 54050b38c..38fadca9e 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -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 *allPlugins = esm.getGlobalReaderList(); for (size_t j = 0; j < masters.size(); j++) { ESM::MasterData &mast = const_cast(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 diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index bd8e003f4..2039a00db 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -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; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 77c9c7357..c36e84813 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -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 iterator; Store() @@ -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 key(x, y); + std::map, 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(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 { diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index aafe629e6..76a1e4f95 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -2,11 +2,15 @@ #include #include -#include +#include +#include #include "esmreader.hpp" #include "esmwriter.hpp" +#include +#include + 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 &cStore = const_cast&>(store.get()); + ESM::Cell *cellAlt = const_cast(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; +} + } diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index dff5a3338..6862dbc5c 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -6,6 +6,14 @@ #include "esmcommon.hpp" #include "defs.hpp" +#include + +/* +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 MovedCellRefTracker; +typedef std::map 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