Merge remote-tracking branch 'mark76/multiple_esm_esp' into next

actorid
Marc Zinnschlag 12 years ago
commit f19fbaa293

@ -209,7 +209,10 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
bool save = (info.mode == "clone");
// Skip back to the beginning of the reference list
cell.restore(esm);
// FIXME: Changes to the references backend required to support multiple plugins have
// almost certainly broken this following line. I'll leave it as is for now, so that
// the compiler does not complain.
cell.restore(esm, 0);
// Loop through all the references
ESM::CellRef ref;

@ -210,18 +210,31 @@ void OMW::Engine::setCell (const std::string& cellName)
// Set master file (esm)
// - If the given name does not have an extension, ".esm" is added automatically
// - Currently OpenMW only supports one master at the same time.
void OMW::Engine::addMaster (const std::string& master)
{
assert (mMaster.empty());
mMaster = master;
mMaster.push_back(master);
std::string &str = mMaster.back();
// Append .esm if not already there
std::string::size_type sep = mMaster.find_last_of (".");
std::string::size_type sep = str.find_last_of (".");
if (sep == std::string::npos)
{
str += ".esm";
}
}
// Add plugin file (esp)
void OMW::Engine::addPlugin (const std::string& plugin)
{
mPlugins.push_back(plugin);
std::string &str = mPlugins.back();
// Append .esp if not already there
std::string::size_type sep = str.find_last_of (".");
if (sep == std::string::npos)
{
mMaster += ".esm";
str += ".esp";
}
}
@ -326,13 +339,13 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
MWGui::CursorReplace replacer;
// Create the world
mEnvironment.setWorld (new MWWorld::World (*mOgre, mFileCollections, mMaster,
mEnvironment.setWorld( new MWWorld::World (*mOgre, mFileCollections, mMaster, mPlugins,
mResDir, mCfgMgr.getCachePath(), mNewGame, mEncoder, mFallbackMap,
mActivationDistanceOverride));
//Load translation data
mTranslationDataStorage.setEncoder(mEncoder);
mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster);
mTranslationDataStorage.loadTranslationData(mFileCollections, mMaster[0]);
// Create window manager - this manages all the MW-specific GUI windows
MWScript::registerExtensions (mExtensions);

@ -67,7 +67,8 @@ namespace OMW
boost::filesystem::path mResDir;
OEngine::Render::OgreRenderer *mOgre;
std::string mCellName;
std::string mMaster;
std::vector<std::string> mMaster;
std::vector<std::string> mPlugins;
int mFpsLevel;
bool mDebug;
bool mVerboseScripts;
@ -132,9 +133,12 @@ namespace OMW
/// Set master file (esm)
/// - If the given name does not have an extension, ".esm" is added automatically
/// - Currently OpenMW only supports one master at the same time.
void addMaster(const std::string& master);
/// Same as "addMaster", but for plugin files (esp)
/// - If the given name does not have an extension, ".esp" is added automatically
void addPlugin(const std::string& plugin);
/// Enable fps counter
void showFPS(int level);

@ -211,18 +211,21 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat
master.push_back("Morrowind");
}
if (master.size() > 1)
StringsVector plugin = variables["plugin"].as<StringsVector>();
// Removed check for 255 files, which would be the hard-coded limit in Morrowind.
// I'll keep the following variable in, maybe we can use it for something different.
// Say, a feedback like "loading file x/cnt".
// Commenting this out for now to silence compiler warning.
//int cnt = master.size() + plugin.size();
// Prepare loading master/plugin files (i.e. send filenames to engine)
for (std::vector<std::string>::size_type i = 0; i < master.size(); i++)
{
std::cout
<< "Ignoring all but the first master file (multiple master files not yet supported)."
<< std::endl;
engine.addMaster(master[i]);
}
engine.addMaster(master[0]);
StringsVector plugin = variables["plugin"].as<StringsVector>();
if (!plugin.empty())
for (std::vector<std::string>::size_type i = 0; i < plugin.size(); i++)
{
std::cout << "Ignoring plugin files (plugins not yet supported)." << std::endl;
engine.addPlugin(plugin[i]);
}
// startup-settings

@ -105,7 +105,7 @@ namespace MWBase
virtual const MWWorld::ESMStore& getStore() const = 0;
virtual ESM::ESMReader& getEsmReader() = 0;
virtual std::vector<ESM::ESMReader>& getEsmReader() = 0;
virtual MWWorld::LocalScripts& getLocalScripts() = 0;

@ -144,7 +144,7 @@ namespace MWRender
std::map<uint16_t, int> indexes;
initTerrainTextures(&terrainData, cellX, cellY,
x * numTextures, y * numTextures,
numTextures, indexes);
numTextures, indexes, land->mPlugin);
if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL)
{
@ -213,8 +213,13 @@ namespace MWRender
void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData,
int cellX, int cellY,
int fromX, int fromY, int size,
std::map<uint16_t, int>& indexes)
std::map<uint16_t, int>& indexes, size_t plugin)
{
// FIXME: In a multiple esm configuration, we have multiple palettes. Since this code
// crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need
// to adopt the following code for a dynamic palette. And this is evil - the current design
// does not work well for this task...
assert(terrainData != NULL && "Must have valid terrain data");
assert(fromX >= 0 && fromY >= 0 &&
"Can't get a terrain texture on terrain outside the current cell");
@ -227,12 +232,20 @@ namespace MWRender
//
//If we don't sort the ltex indexes, the splatting order may differ between
//cells which may lead to inconsistent results when shading between cells
int num = MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>().getSize(plugin);
std::set<uint16_t> ltexIndexes;
for ( int y = fromY - 1; y < fromY + size + 1; y++ )
{
for ( int x = fromX - 1; x < fromX + size + 1; x++ )
{
ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y));
int idx = getLtexIndexAt(cellX, cellY, x, y);
// This is a quick hack to prevent the program from trying to fetch textures
// from a neighboring cell, which might originate from a different plugin,
// and use a separate texture palette. Right now, we simply cast it to the
// default texture (i.e. 0).
if (idx > num)
idx = 0;
ltexIndexes.insert(idx);
}
}
@ -244,7 +257,7 @@ namespace MWRender
iter != ltexIndexes.end();
++iter )
{
const uint16_t ltexIndex = *iter;
uint16_t ltexIndex = *iter;
//this is the base texture, so we can ignore this at present
if ( ltexIndex == baseTexture )
{
@ -260,8 +273,11 @@ namespace MWRender
const MWWorld::Store<ESM::LandTexture> &ltexStore =
MWBase::Environment::get().getWorld()->getStore().get<ESM::LandTexture>();
assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 &&
"LAND.VTEX must be within the bounds of the LTEX array");
// NOTE: using the quick hack above, we should no longer end up with textures indices
// that are out of bounds. However, I haven't updated the test to a multi-palette
// system yet. We probably need more work here, so we skip it for now.
//assert( (int)ltexStore.getSize() >= (int)ltexIndex - 1 &&
//"LAND.VTEX must be within the bounds of the LTEX array");
std::string texture;
if ( ltexIndex == 0 )
@ -270,7 +286,7 @@ namespace MWRender
}
else
{
texture = ltexStore.search(ltexIndex-1)->mTexture;
texture = ltexStore.search(ltexIndex-1, plugin)->mTexture;
//TODO this is needed due to MWs messed up texture handling
texture = texture.substr(0, texture.rfind(".")) + ".dds";
}

@ -75,7 +75,7 @@ namespace MWRender{
void initTerrainTextures(Ogre::Terrain::ImportData* terrainData,
int cellX, int cellY,
int fromX, int fromY, int size,
std::map<uint16_t, int>& indexes);
std::map<uint16_t, int>& indexes, size_t plugin);
/**
* Creates the blend (splatting maps) for the given terrain from the ltex data.

@ -84,7 +84,7 @@ MWWorld::Ptr MWWorld::Cells::getPtrAndCache (const std::string& name, Ptr::CellS
return ptr;
}
MWWorld::Cells::Cells (const MWWorld::ESMStore& store, ESM::ESMReader& reader)
MWWorld::Cells::Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader)
: mStore (store), mReader (reader),
mIdCache (20, std::pair<std::string, Ptr::CellStore *> ("", (Ptr::CellStore*)0)), /// \todo make cache size configurable
mIdCacheIndex (0)
@ -119,6 +119,7 @@ MWWorld::Ptr::CellStore *MWWorld::Cells::getExterior (int x, int y)
if (result->second.mState!=Ptr::CellStore::State_Loaded)
{
// Multiple plugin support for landscape data is much easier than for references. The last plugin wins.
result->second.load (mStore, mReader);
fillContainers (result->second);
}

@ -2,6 +2,7 @@
#define GAME_MWWORLD_CELLS_H
#include <map>
#include <list>
#include <string>
#include "ptr.hpp"
@ -19,7 +20,7 @@ namespace MWWorld
class Cells
{
const MWWorld::ESMStore& mStore;
ESM::ESMReader& mReader;
std::vector<ESM::ESMReader>& mReader;
std::map<std::string, CellStore> mInteriors;
std::map<std::pair<int, int>, CellStore> mExteriors;
std::vector<std::pair<std::string, CellStore *> > mIdCache;
@ -36,7 +37,7 @@ namespace MWWorld
public:
Cells (const MWWorld::ESMStore& store, ESM::ESMReader& reader);
Cells (const MWWorld::ESMStore& store, std::vector<ESM::ESMReader>& reader);
///< \todo pass the dynamic part of the ESMStore isntead (once it is written) of the whole
/// world

@ -10,13 +10,48 @@
namespace MWWorld
{
template <typename X>
void CellRefList<X>::load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore)
{
// Get existing reference, in case we need to overwrite it.
typename std::list<LiveRef>::iterator iter = std::find(mList.begin(), mList.end(), ref.mRefnum);
// Skip this when reference was deleted.
// TODO: Support respawning references, in this case, we need to track it somehow.
if (ref.mDeleted) {
mList.erase(iter);
return;
}
// for throwing exception on unhandled record type
const MWWorld::Store<X> &store = esmStore.get<X>();
const X *ptr = store.search(ref.mRefID);
/// \note no longer redundant - changed to Store<X>::search(), don't throw
/// an exception on miss, try to continue (that's how MW does it, anyway)
if (ptr == NULL) {
std::cout << "Warning: could not resolve cell reference " << ref.mRefID << ", trying to continue anyway" << std::endl;
} else {
if (iter != mList.end())
*iter = LiveRef(ref, ptr);
else
mList.push_back(LiveRef(ref, ptr));
}
}
template<typename X> bool operator==(const LiveCellRef<X>& ref, int pRefnum)
{
return (ref.mRef.mRefnum == pRefnum);
}
CellStore::CellStore (const ESM::Cell *cell)
: mCell (cell), mState (State_Unloaded)
{
mWaterLevel = cell->mWater;
}
void CellStore::load (const MWWorld::ESMStore &store, ESM::ESMReader &esm)
void CellStore::load (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{
if (mState!=State_Loaded)
{
@ -31,7 +66,7 @@ namespace MWWorld
}
}
void CellStore::preload (const MWWorld::ESMStore &store, ESM::ESMReader &esm)
void CellStore::preload (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{
if (mState==State_Unloaded)
{
@ -41,57 +76,126 @@ namespace MWWorld
}
}
void CellStore::listRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm)
void CellStore::listRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{
assert (mCell);
if (mCell->mContext.filename.empty())
if (mCell->mContextList.size() == 0)
return; // this is a dynamically generated cell -> skipping.
// Reopen the ESM reader and seek to the right position.
mCell->restore (esm);
ESM::CellRef ref;
// Get each reference in turn
while (mCell->getNextRef (esm, ref))
// Load references from all plugins that do something with this cell.
for (size_t i = 0; i < mCell->mContextList.size(); i++)
{
std::string lowerCase = Misc::StringUtils::lowerCase (ref.mRefID);
// Reopen the ESM reader and seek to the right position.
int index = mCell->mContextList.at(i).index;
mCell->restore (esm[index], i);
ESM::CellRef ref;
mIds.push_back (lowerCase);
// Get each reference in turn
while (mCell->getNextRef (esm[index], ref))
{
std::string lowerCase = Misc::StringUtils::lowerCase (ref.mRefID);
if (ref.mDeleted) {
// Right now, don't do anything. Where is "listRefs" actually used, anyway?
// Skipping for now...
continue;
}
mIds.push_back (lowerCase);
}
}
std::sort (mIds.begin(), mIds.end());
}
void CellStore::loadRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm)
void CellStore::loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm)
{
assert (mCell);
if (mCell->mContext.filename.empty())
if (mCell->mContextList.size() == 0)
return; // this is a dynamically generated cell -> skipping.
// Reopen the ESM reader and seek to the right position.
mCell->restore(esm);
// Load references from all plugins that do something with this cell.
for (size_t i = 0; i < mCell->mContextList.size(); i++)
{
// Reopen the ESM reader and seek to the right position.
int index = mCell->mContextList.at(i).index;
mCell->restore (esm[index], i);
ESM::CellRef ref;
// Get each reference in turn
while(mCell->getNextRef(esm[index], ref))
{
// Don't load reference if it was moved to a different cell.
std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID);
ESM::MovedCellRefTracker::const_iterator iter = std::find(mCell->mMovedRefs.begin(), mCell->mMovedRefs.end(), ref.mRefnum);
if (iter != mCell->mMovedRefs.end()) {
continue;
}
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;
ESM::CellRef ref;
case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break;
default:
std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n";
}
}
}
// Get each reference in turn
while(mCell->getNextRef(esm, ref))
// Load moved references, from separately tracked list.
for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); it++)
{
std::string lowerCase = Misc::StringUtils::lowerCase(ref.mRefID);
// Doesn't seem to work in one line... huh? Too sleepy to check...
ESM::CellRef &ref = const_cast<ESM::CellRef&>(*it);
//ESM::CellRef &ref = const_cast<ESM::CellRef&>(it->second);
int rec = store.find(ref.mRefID);
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)
{
/* 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;
@ -113,10 +217,11 @@ namespace MWWorld
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";
}
case 0: std::cout << "Cell reference " + ref.mRefID + " not found!\n"; break;
default:
std::cout << "WARNING: Ignoring reference '" << ref.mRefID << "' of unhandled type\n";
}
}
}
}

@ -3,17 +3,18 @@
#include <components/esm/records.hpp>
#include <list>
#include <deque>
#include <algorithm>
#include "refdata.hpp"
#include "esmstore.hpp"
struct C;
namespace MWWorld
{
class Ptr;
class ESMStore;
/// A reference to one object (of any type) in a cell.
///
/// Constructing this with a CellRef instance in the constructor means that
@ -42,6 +43,8 @@ namespace MWWorld
/// runtime-data
RefData mData;
};
template<typename X> bool operator==(const LiveCellRef<X>& ref, int pRefnum);
/// A list of cell references
template <typename X>
@ -51,21 +54,14 @@ namespace MWWorld
typedef std::list<LiveRef> List;
List mList;
/// Searches for reference of appropriate type in given ESMStore.
/// If reference exists, loads it into container, throws an exception
/// on miss
void load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore)
{
// for throwing exception on unhandled record type
const MWWorld::Store<X> &store = esmStore.get<X>();
const X *ptr = store.find(ref.mRefID);
/// \note redundant because Store<X>::find() throws exception on miss
if (ptr == NULL) {
throw std::runtime_error("Error resolving cell reference " + ref.mRefID);
}
mList.push_back(LiveRef(ref, ptr));
}
// Search for the given reference in the given reclist from
// ESMStore. Insert the reference into the list if a match is
// found. If not, throw an exception.
// Moved to cpp file, as we require a custom compare operator for it,
// and the build will fail with an ugly three-way cyclic header dependence
// so we need to pass the instantiation of the method to the lnker, when
// all methods are known.
void load(ESM::CellRef &ref, const MWWorld::ESMStore &esmStore);
LiveRef *find (const std::string& name)
{
@ -124,9 +120,9 @@ namespace MWWorld
CellRefList<ESM::Static> mStatics;
CellRefList<ESM::Weapon> mWeapons;
void load (const MWWorld::ESMStore &store, ESM::ESMReader &esm);
void load (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
void preload (const MWWorld::ESMStore &store, ESM::ESMReader &esm);
void preload (const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
/// Call functor (ref) for each reference. functor must return a bool. Returning
/// false will abort the iteration.
@ -185,9 +181,9 @@ namespace MWWorld
}
/// Run through references and store IDs
void listRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm);
void listRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
void loadRefs(const MWWorld::ESMStore &store, ESM::ESMReader &esm);
void loadRefs(const MWWorld::ESMStore &store, std::vector<ESM::ESMReader> &esm);
};
}

@ -3,6 +3,8 @@
#include <set>
#include <iostream>
#include <boost/filesystem/v3/operations.hpp>
namespace MWWorld
{
@ -25,6 +27,33 @@ void ESMStore::load(ESM::ESMReader &esm)
ESM::Dialogue *dialogue = 0;
// Cache parent esX files by tracking their indices in the global list of
// all files/readers used by the engine. This will greaty accelerate
// refnumber mangling, as required for handling moved references.
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 (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) {
index = i;
break;
}
}
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
+ ", but it has not been loaded yet. Please check your load order.";
esm.fail(fstring);
}
mast.index = index;
}
// Loop through all records
while(esm.hasMoreRecs())
{
@ -55,12 +84,21 @@ void ESMStore::load(ESM::ESMReader &esm)
} else {
// Load it
std::string id = esm.getHNOString("NAME");
// ... unless it got deleted! This means that the following record
// has been deleted, and trying to load it using standard assumptions
// on the structure will (probably) fail.
if (esm.isNextSub("DELE")) {
esm.skipRecord();
it->second->eraseStatic(id);
continue;
}
it->second->load(esm, id);
if (n.val==ESM::REC_DIAL) {
// dirty hack, but it is better than non-const search()
// or friends
dialogue = &mDialogs.mStatic.back();
//dialogue = &mDialogs.mStatic.back();
dialogue = const_cast<ESM::Dialogue*>(mDialogs.find(id));
assert (dialogue->mId == id);
} else {
dialogue = 0;
@ -84,7 +122,6 @@ void ESMStore::load(ESM::ESMReader &esm)
cout << *it << " ";
cout << endl;
*/
setUp();
}
void ESMStore::setUp()
@ -100,12 +137,11 @@ void ESMStore::setUp()
ESM::NPC item;
item.mId = "player";
std::vector<ESM::NPC>::iterator pIt =
std::lower_bound(mNpcs.mStatic.begin(), mNpcs.mStatic.end(), item, RecordCmp());
assert(pIt != mNpcs.mStatic.end() && pIt->mId == "player");
const ESM::NPC *pIt = mNpcs.find("player");
assert(pIt != NULL);
mNpcs.insert(*pIt);
mNpcs.mStatic.erase(pIt);
mNpcs.eraseStatic(pIt->mId);
}
} // end namespace

@ -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;
@ -168,7 +171,8 @@ namespace MWWorld
return ptr;
}
private:
// This method must be called once, after loading all master/plugin files. This can only be done
// from the outside, so it must be public.
void setUp();
};

@ -0,0 +1,65 @@
#include "store.hpp"
namespace MWWorld {
void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
{
// Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell,
// and we merge all this data into one Cell object. However, we can't simply search for the cell id,
// as many exterior cells do not have a name. Instead, we need to search by (x,y) coordinates - and they
// are not available until both cells have been loaded! So first, proceed as usual.
// All cells have a name record, even nameless exterior cells.
std::string idLower = Misc::StringUtils::lowerCase(id);
ESM::Cell *cell = new ESM::Cell;
cell->mName = id;
// The cell itself takes care of some of the hairy details
cell->load(esm, *mEsmStore);
if(cell->mData.mFlags & ESM::Cell::Interior)
{
// Store interior cell by name, try to merge with existing parent data.
ESM::Cell *oldcell = const_cast<ESM::Cell*>(search(idLower));
if (oldcell) {
// push the new references on the list of references to manage
oldcell->mContextList.push_back(cell->mContextList.at(0));
// copy list into new cell
cell->mContextList = oldcell->mContextList;
// have new cell replace old cell
*oldcell = *cell;
} else
mInt[idLower] = *cell;
}
else
{
// Store exterior cells by grid position, try to merge with existing parent data.
ESM::Cell *oldcell = const_cast<ESM::Cell*>(search(cell->getGridX(), cell->getGridY()));
if (oldcell) {
// push the new references on the list of references to manage
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
ESM::MovedCellRefTracker::iterator itold = std::find(oldcell->mMovedRefs.begin(), oldcell->mMovedRefs.end(), it->mRefnum);
if (itold != oldcell->mMovedRefs.end()) {
ESM::MovedCellRef target0 = *itold;
ESM::Cell *wipecell = const_cast<ESM::Cell*>(search(target0.mTarget[0], target0.mTarget[1]));
ESM::CellRefTracker::iterator it_lease = std::find(wipecell->mLeasedRefs.begin(), wipecell->mLeasedRefs.end(), it->mRefnum);
wipecell->mLeasedRefs.erase(it_lease);
*itold = *it;
}
}
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;
}
}

@ -19,6 +19,8 @@ namespace MWWorld
virtual int getSize() const = 0;
virtual void load(ESM::ESMReader &esm, const std::string &id) = 0;
virtual bool eraseStatic(const std::string &id) {return false;}
};
template <class T>
@ -85,7 +87,7 @@ namespace MWWorld
template <class T>
class Store : public StoreBase
{
std::vector<T> mStatic;
std::map<std::string, T> mStatic;
std::vector<T *> mShared;
std::map<std::string, T> mDynamic;
@ -107,11 +109,10 @@ namespace MWWorld
T item;
item.mId = Misc::StringUtils::lowerCase(id);
typename std::vector<T>::const_iterator it =
std::lower_bound(mStatic.begin(), mStatic.end(), item, RecordCmp());
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->mId, id)) {
return &(*it);
typename std::map<std::string, T>::const_iterator it = mStatic.find(item.mId);
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) {
return &(it->second);
}
typename Dynamic::const_iterator dit = mDynamic.find(item.mId);
@ -133,18 +134,19 @@ namespace MWWorld
}
void load(ESM::ESMReader &esm, const std::string &id) {
mStatic.push_back(T());
mStatic.back().mId = Misc::StringUtils::lowerCase(id);
mStatic.back().load(esm);
std::string idLower = Misc::StringUtils::lowerCase(id);
mStatic[idLower] = T();
mStatic[idLower].mId = idLower;
mStatic[idLower].load(esm);
}
void setUp() {
std::sort(mStatic.begin(), mStatic.end(), RecordCmp());
//std::sort(mStatic.begin(), mStatic.end(), RecordCmp());
mShared.reserve(mStatic.size());
typename std::vector<T>::iterator it = mStatic.begin();
typename std::map<std::string, T>::iterator it = mStatic.begin();
for (; it != mStatic.end(); ++it) {
mShared.push_back(&(*it));
mShared.push_back(&(it->second));
}
}
@ -181,6 +183,19 @@ namespace MWWorld
return ptr;
}
bool eraseStatic(const std::string &id) {
T item;
item.mId = Misc::StringUtils::lowerCase(id);
typename std::map<std::string, T>::iterator it = mStatic.find(item.mId);
if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) {
mStatic.erase(it);
}
return true;
}
bool erase(const std::string &id) {
std::string key = Misc::StringUtils::lowerCase(id);
typename Dynamic::iterator it = mDynamic.find(key);
@ -204,39 +219,48 @@ namespace MWWorld
template <>
inline void Store<ESM::Dialogue>::load(ESM::ESMReader &esm, const std::string &id) {
mStatic.push_back(ESM::Dialogue());
mStatic.back().mId = id;
mStatic.back().load(esm);
std::string idLower = Misc::StringUtils::lowerCase(id);
mStatic[idLower] = ESM::Dialogue();
mStatic[idLower].mId = id; // don't smash case here, as this line is printed... I think
mStatic[idLower].load(esm);
}
template <>
inline void Store<ESM::Script>::load(ESM::ESMReader &esm, const std::string &id) {
mStatic.push_back(ESM::Script());
mStatic.back().load(esm);
Misc::StringUtils::toLower(mStatic.back().mId);
ESM::Script scpt;
scpt.load(esm);
Misc::StringUtils::toLower(scpt.mId);
mStatic[scpt.mId] = scpt;
}
template <>
class Store<ESM::LandTexture> : public StoreBase
{
std::vector<ESM::LandTexture> mStatic;
// For multiple ESM/ESP files we need one list per file.
typedef std::vector<ESM::LandTexture> LandTextureList;
std::vector<LandTextureList> mStatic;
public:
Store<ESM::LandTexture>() {
mStatic.reserve(128);
mStatic.push_back(LandTextureList());
LandTextureList &ltexl = mStatic[0];
// More than enough to hold Morrowind.esm. Extra lists for plugins will we
// added on-the-fly in a different method.
ltexl.reserve(128);
}
typedef std::vector<ESM::LandTexture>::const_iterator iterator;
const ESM::LandTexture *search(size_t index) const {
if (index < mStatic.size()) {
return &mStatic.at(index);
}
return 0;
const ESM::LandTexture *search(size_t index, size_t plugin) const {
assert(plugin < mStatic.size());
const LandTextureList &ltexl = mStatic[plugin];
assert(index < ltexl.size());
return &ltexl.at(index);
}
const ESM::LandTexture *find(size_t index) const {
const ESM::LandTexture *ptr = search(index);
const ESM::LandTexture *find(size_t index, size_t plugin) const {
const ESM::LandTexture *ptr = search(index, plugin);
if (ptr == 0) {
std::ostringstream msg;
msg << "Land texture with index " << index << " not found";
@ -249,23 +273,40 @@ namespace MWWorld
return mStatic.size();
}
void load(ESM::ESMReader &esm, const std::string &id) {
ESM::LandTexture ltex;
ltex.load(esm);
int getSize(size_t plugin) const {
assert(plugin < mStatic.size());
return mStatic[plugin].size();
}
void load(ESM::ESMReader &esm, const std::string &id, size_t plugin) {
ESM::LandTexture lt;
lt.load(esm);
lt.mId = id;
if (ltex.mIndex >= (int) mStatic.size()) {
mStatic.resize(ltex.mIndex + 1);
// Make sure we have room for the structure
if (plugin >= mStatic.size()) {
mStatic.resize(plugin+1);
}
mStatic[ltex.mIndex] = ltex;
mStatic[ltex.mIndex].mId = id;
LandTextureList &ltexl = mStatic[plugin];
if(lt.mIndex + 1 > (int)ltexl.size())
ltexl.resize(lt.mIndex+1);
// Store it
ltexl[lt.mIndex] = lt;
}
iterator begin() const {
return mStatic.begin();
void load(ESM::ESMReader &esm, const std::string &id) {
load(esm, id, esm.getIndex());
}
iterator end() const {
return mStatic.end();
iterator begin(size_t plugin) const {
assert(plugin < mStatic.size());
return mStatic[plugin].begin();
}
iterator end(size_t plugin) const {
assert(plugin < mStatic.size());
return mStatic[plugin].end();
}
};
@ -357,19 +398,18 @@ namespace MWWorld
}
};
std::vector<ESM::Cell> mInt;
std::vector<ESM::Cell> mExt;
typedef std::map<std::string, ESM::Cell> DynamicInt;
typedef std::map<std::pair<int, int>, ESM::Cell> DynamicExt;
DynamicInt mInt;
DynamicExt mExt;
std::vector<ESM::Cell *> mSharedInt;
std::vector<ESM::Cell *> mSharedExt;
typedef std::map<std::string, ESM::Cell> DynamicInt;
typedef std::map<std::pair<int, int>, ESM::Cell> DynamicExt;
DynamicInt mDynamicInt;
DynamicExt mDynamicExt;
const ESM::Cell *search(const ESM::Cell &cell) const {
if (cell.isExterior()) {
return search(cell.getGridX(), cell.getGridY());
@ -378,6 +418,8 @@ namespace MWWorld
}
public:
ESMStore *mEsmStore;
typedef SharedIterator<ESM::Cell> iterator;
Store<ESM::Cell>()
@ -387,11 +429,10 @@ namespace MWWorld
ESM::Cell cell;
cell.mName = Misc::StringUtils::lowerCase(id);
std::vector<ESM::Cell>::const_iterator it =
std::lower_bound(mInt.begin(), mInt.end(), cell, RecordCmp());
std::map<std::string, ESM::Cell>::const_iterator it = mInt.find(cell.mName);
if (it != mInt.end() && Misc::StringUtils::ciEqual(it->mName, id)) {
return &(*it);
if (it != mInt.end() && Misc::StringUtils::ciEqual(it->second.mName, id)) {
return &(it->second);
}
DynamicInt::const_iterator dit = mDynamicInt.find(cell.mName);
@ -406,20 +447,42 @@ namespace MWWorld
ESM::Cell cell;
cell.mData.mX = x, cell.mData.mY = y;
std::vector<ESM::Cell>::const_iterator it =
std::lower_bound(mExt.begin(), mExt.end(), cell, ExtCmp());
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);
}
if (it != mExt.end() && it->mData.mX == x && it->mData.mY == y) {
return &(*it);
DynamicExt::const_iterator dit = mDynamicExt.find(key);
if (dit != mDynamicExt.end()) {
return &dit->second;
}
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;
}
return 0;
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 {
@ -443,33 +506,29 @@ namespace MWWorld
}
void setUp() {
typedef std::vector<ESM::Cell>::iterator Iterator;
//typedef std::vector<ESM::Cell>::iterator Iterator;
typedef std::map<std::pair<int, int>, ESM::Cell>::iterator ExtIterator;
typedef std::map<std::string, ESM::Cell>::iterator IntIterator;
std::sort(mInt.begin(), mInt.end(), RecordCmp());
//std::sort(mInt.begin(), mInt.end(), RecordCmp());
mSharedInt.reserve(mInt.size());
for (Iterator it = mInt.begin(); it != mInt.end(); ++it) {
mSharedInt.push_back(&(*it));
for (IntIterator it = mInt.begin(); it != mInt.end(); ++it) {
mSharedInt.push_back(&(it->second));
}
std::sort(mExt.begin(), mExt.end(), ExtCmp());
//std::sort(mExt.begin(), mExt.end(), ExtCmp());
mSharedExt.reserve(mExt.size());
for (Iterator it = mExt.begin(); it != mExt.end(); ++it) {
mSharedExt.push_back(&(*it));
}
}
void load(ESM::ESMReader &esm, const std::string &id) {
ESM::Cell cell;
cell.mName = id;
cell.load(esm);
if (cell.isExterior()) {
mExt.push_back(cell);
} else {
mInt.push_back(cell);
for (ExtIterator it = mExt.begin(); it != mExt.end(); ++it) {
mSharedExt.push_back(&(it->second));
}
}
// HACK: Method implementation had to be moved to a separate cpp file, as we would otherwise get
// errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find.
// There some nasty three-way cyclic header dependency involved, which I could only fix by moving
// this method.
void load(ESM::ESMReader &esm, const std::string &id);
iterator intBegin() const {
return iterator(mSharedInt.begin());
}

@ -55,7 +55,7 @@ namespace
for (iterator iter (refList.mList.begin()); iter!=refList.mList.end(); ++iter)
{
if(iter->mData.getCount() > 0 && iter->mData.getBaseNode()){
if (iter->mData.getCount() > 0 && iter->mData.getBaseNode()){
if (iter->mData.getHandle()==handle)
{
return &*iter;
@ -171,7 +171,8 @@ namespace MWWorld
World::World (OEngine::Render::OgreRenderer& renderer,
const Files::Collections& fileCollections,
const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame,
const std::vector<std::string>& master, const std::vector<std::string>& plugins,
const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame,
ToUTF8::Utf8Encoder* encoder, std::map<std::string,std::string> fallbackMap, int mActivationDistanceOverride)
: mPlayer (0), mLocalScripts (mStore), mGlobalVariables (0),
mSky (true), mCells (mStore, mEsm),
@ -184,14 +185,42 @@ namespace MWWorld
mWeatherManager = new MWWorld::WeatherManager(mRendering);
boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master));
std::cout << "Loading ESM " << masterPath.string() << "\n";
// This parses the ESM file and loads a sample cell
mEsm.setEncoder(encoder);
mEsm.open (masterPath.string());
mStore.load (mEsm);
int idx = 0;
// NOTE: We might need to reserve one more for the running game / save.
mEsm.resize(master.size() + plugins.size());
for (std::vector<std::string>::size_type i = 0; i < master.size(); i++, idx++)
{
boost::filesystem::path masterPath (fileCollections.getCollection (".esm").getPath (master[i]));
std::cout << "Loading ESM " << masterPath.string() << "\n";
// This parses the ESM file
ESM::ESMReader lEsm;
lEsm.setEncoder(encoder);
lEsm.setIndex(idx);
lEsm.setGlobalReaderList(&mEsm);
lEsm.open (masterPath.string());
mEsm[idx] = lEsm;
mStore.load (mEsm[idx]);
}
for (std::vector<std::string>::size_type i = 0; i < plugins.size(); i++, idx++)
{
boost::filesystem::path pluginPath (fileCollections.getCollection (".esp").getPath (plugins[i]));
std::cout << "Loading ESP " << pluginPath.string() << "\n";
// This parses the ESP file
ESM::ESMReader lEsm;
lEsm.setEncoder(encoder);
lEsm.setIndex(idx);
lEsm.setGlobalReaderList(&mEsm);
lEsm.open (pluginPath.string());
mEsm[idx] = lEsm;
mStore.load (mEsm[idx]);
}
mStore.setUp();
mPlayer = new MWWorld::Player (mStore.get<ESM::NPC>().find ("player"), *this);
mRendering->attachCameraTo(mPlayer->getPlayer());
@ -270,7 +299,7 @@ namespace MWWorld
return mStore;
}
ESM::ESMReader& World::getEsmReader()
std::vector<ESM::ESMReader>& World::getEsmReader()
{
return mEsm;
}
@ -1233,8 +1262,8 @@ namespace MWWorld
std::vector<World::DoorMarker> result;
MWWorld::CellRefList<ESM::Door>& doors = cell->mDoors;
std::list< MWWorld::LiveCellRef<ESM::Door> >& refList = doors.mList;
for (std::list< MWWorld::LiveCellRef<ESM::Door> >::iterator it = refList.begin(); it != refList.end(); ++it)
CellRefList<ESM::Door>::List& refList = doors.mList;
for (CellRefList<ESM::Door>::List::iterator it = refList.begin(); it != refList.end(); ++it)
{
MWWorld::LiveCellRef<ESM::Door>& ref = *it;

@ -54,7 +54,7 @@ namespace MWWorld
MWWorld::Scene *mWorldScene;
MWWorld::Player *mPlayer;
ESM::ESMReader mEsm;
std::vector<ESM::ESMReader> mEsm;
MWWorld::ESMStore mStore;
LocalScripts mLocalScripts;
MWWorld::Globals *mGlobalVariables;
@ -113,7 +113,8 @@ namespace MWWorld
World (OEngine::Render::OgreRenderer& renderer,
const Files::Collections& fileCollections,
const std::string& master, const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame,
const std::vector<std::string>& master, const std::vector<std::string>& plugins,
const boost::filesystem::path& resDir, const boost::filesystem::path& cacheDir, bool newGame,
ToUTF8::Utf8Encoder* encoder, std::map<std::string,std::string> fallbackMap, int mActivationDistanceOverride);
virtual ~World();
@ -143,7 +144,7 @@ namespace MWWorld
virtual const MWWorld::ESMStore& getStore() const;
virtual ESM::ESMReader& getEsmReader();
virtual std::vector<ESM::ESMReader>& getEsmReader();
virtual LocalScripts& getLocalScripts();

@ -89,6 +89,7 @@ struct MasterData
{
std::string name;
uint64_t size;
int index; // Position of the parent file in the global list of loaded files
};
// Data that is only present in save game files
@ -113,6 +114,10 @@ struct ESM_Context
size_t leftFile;
NAME recName, subName;
HEDRstruct header;
// When working with multiple esX files, we will generate lists of all files that
// actually contribute to a specific cell. Therefore, we need to store the index
// of the file belonging to this contest. See CellStore::(list/load)refs for details.
int index;
// True if subName has been read but not used.
bool subCached;

@ -78,6 +78,17 @@ public:
void openRaw(const std::string &file);
// This is a quick hack for multiple esm/esp files. Each plugin introduces its own
// terrain palette, but ESMReader does not pass a reference to the correct plugin
// to the individual load() methods. This hack allows to pass this reference
// indirectly to the load() method.
int mIdx;
void setIndex(const int index) {mIdx = index; mCtx.index = index;}
const int getIndex() {return mIdx;}
void setGlobalReaderList(std::vector<ESMReader> *list) {mGlobalReaderList = list;}
std::vector<ESMReader> *getGlobalReaderList() {return mGlobalReaderList;}
/*************************************************************************
*
* Medium-level reading shortcuts
@ -110,6 +121,14 @@ public:
getHT(x);
}
template <typename X>
void getHNOT(X &x, const char* name, int size)
{
assert(sizeof(X) == size);
if(isNextSub(name))
getHT(x);
}
int64_t getHNLong(const char *name);
// Get data of a given type/size, including subrecord header
@ -251,6 +270,7 @@ private:
SaveData mSaveData;
MasterList mMasters;
std::vector<ESMReader> *mGlobalReaderList;
ToUTF8::Utf8Encoder* mEncoder;
};
}

@ -2,13 +2,29 @@
#include <string>
#include <sstream>
#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
{
/// Some overloaded copare operators.
bool operator==(const MovedCellRef& ref, int pRefnum)
{
return (ref.mRefnum == pRefnum);
}
bool operator==(const CellRef& ref, int pRefnum)
{
return (ref.mRefnum == pRefnum);
}
void CellRef::save(ESMWriter &esm)
{
esm.writeHNT("FRMR", mRefnum);
@ -63,10 +79,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();
}
@ -109,9 +126,37 @@ 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.
// We should not need to test for duplicates, as this part of the code is pre-cell merge.
mMovedRefs.push_back(cMRef);
// But there may be duplicates here!
ESM::CellRefTracker::iterator iter = std::find(cellAlt->mLeasedRefs.begin(), cellAlt->mLeasedRefs.end(), ref.mRefnum);
if (iter == cellAlt->mLeasedRefs.end())
cellAlt->mLeasedRefs.push_back(ref);
else
*iter = ref;
}
// Save position of the cell references and move on
mContext = esm.getContext();
mContextList.push_back(esm.getContext());
esm.skipRecord();
}
@ -146,9 +191,9 @@ void Cell::save(ESMWriter &esm)
esm.writeHNT("NAM0", mNAM0);
}
void Cell::restore(ESMReader &esm) const
void Cell::restore(ESMReader &esm, int iCtx) const
{
esm.restoreContext(mContext);
esm.restoreContext(mContextList[iCtx]);
}
std::string Cell::getDescription() const
@ -167,17 +212,61 @@ std::string Cell::getDescription() const
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")) {
esm.skipRecord(); // skip MVRF
esm.skipRecord(); // skip CNDT
// That should be it, I haven't seen any other fields yet.
}
esm.getHNT(ref.mRefnum, "FRMR");
ref.mRefID = esm.getHNString("NAME");
// Identify references belonging to a parent file and adapt the ID accordingly.
int local = (ref.mRefnum & 0xff000000) >> 24;
size_t global = esm.getIndex() + 1;
if (local)
{
// If the most significant 8 bits are used, then this reference already exists.
// In this case, do not spawn a new reference, but overwrite the old one.
ref.mRefnum &= 0x00ffffff; // delete old plugin ID
const ESM::ESMReader::MasterList &masters = esm.getMasters();
global = masters[local-1].index + 1;
ref.mRefnum |= global << 24; // insert global plugin ID
}
else
{
// This is an addition by the present plugin. Set the corresponding plugin index.
ref.mRefnum |= global << 24; // insert global plugin ID
}
// getHNOT will not change the existing value if the subrecord is
// missing
ref.mScale = 1.0;
esm.getHNOT(ref.mScale, "XSCL");
// TODO: support loading references from saves, there are tons of keys not recognized yet.
// The following is just an incomplete list.
if (esm.isNextSub("ACTN"))
esm.skipHSub();
if (esm.isNextSub("STPR"))
esm.skipHSub();
if (esm.isNextSub("ACDT"))
esm.skipHSub();
if (esm.isNextSub("ACSC"))
esm.skipHSub();
if (esm.isNextSub("ACSL"))
esm.skipHSub();
if (esm.isNextSub("CHRD"))
esm.skipHSub();
else if (esm.isNextSub("CRED")) // ???
esm.skipHSub();
ref.mOwner = esm.getHNOString("ANAM");
ref.mGlob = esm.getHNOString("BNAM");
ref.mSoul = esm.getHNOString("XSOL");
@ -215,17 +304,43 @@ bool Cell::getNextRef(ESMReader &esm, CellRef &ref)
esm.getHNOT(ref.mUnam, "UNAM");
esm.getHNOT(ref.mFltv, "FLTV");
esm.getHNT(ref.mPos, "DATA", 24);
esm.getHNOT(ref.mPos, "DATA", 24);
// Number of references in the cell? Maximum once in each cell,
// but not always at the beginning, and not always right. In other
// words, completely useless.
// Update: Well, maybe not completely useless. This might actually be
// number_of_references + number_of_references_moved_here_Across_boundaries,
// and could be helpful for collecting these weird moved references.
ref.mNam0 = 0;
if (esm.isNextSub("NAM0"))
{
esm.getHT(ref.mNam0);
//esm.getHNOT(NAM0, "NAM0");
}
if (esm.isNextSub("DELE")) {
esm.skipHSub();
ref.mDeleted = 2; // Deleted, will not respawn.
// TODO: find out when references do respawn.
} else
ref.mDeleted = 0;
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;
}

@ -2,9 +2,18 @@
#define OPENMW_ESM_CELL_H
#include <string>
#include <vector>
#include "esmcommon.hpp"
#include "defs.hpp"
#include "apps/openmw/mwbase/world.hpp"
/*
namespace MWWorld {
class ESMStore;
class CellStore;
}
*/
namespace ESM
{
@ -69,6 +78,9 @@ public:
// No idea - occurs ONCE in Morrowind.esm, for an activator
char mUnam;
// Track deleted references. 0 - not deleted, 1 - deleted, but respawns, 2 - deleted and does not respawn.
int mDeleted;
// Occurs in Tribunal.esm, eg. in the cell "Mournhold, Plaza
// Brindisi Dorom", where it has the value 100. Also only for
@ -82,6 +94,31 @@ 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.
Unfortunately, we need to implement this here.
*/
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.
};
/// Overloaded copare operator used to search inside a list of cell refs.
bool operator==(const MovedCellRef& ref, int pRefnum);
bool operator==(const CellRef& ref, int pRefnum);
typedef std::list<MovedCellRef> MovedCellRefTracker;
typedef std::list<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
@ -120,15 +157,24 @@ struct Cell
// Optional region name for exterior and quasi-exterior cells.
std::string mRegion;
ESM_Context mContext; // File position
std::vector<ESM_Context> mContextList; // File position; multiple positions for multiple plugin support
DATAstruct mData;
AMBIstruct mAmbi;
float mWater; // Water level
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
@ -151,7 +197,7 @@ struct Cell
// somewhere other than the file system, you need to pre-open the
// ESMReader, and the filename must match the stored filename
// exactly.
void restore(ESMReader &esm) const;
void restore(ESMReader &esm, int iCtx) const;
std::string getDescription() const;
///< Return a short string describing the cell (mostly used for debugging/logging purpose)
@ -163,6 +209,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

@ -81,6 +81,7 @@ Land::~Land()
void Land::load(ESMReader &esm)
{
mEsm = &esm;
mPlugin = mEsm->getIndex();
// Get the grid location
esm.getSubNameIs("INTV");

@ -23,6 +23,7 @@ struct Land
int mFlags; // Only first four bits seem to be used, don't know what
// they mean.
int mX, mY; // Map coordinates.
int mPlugin; // Plugin index, used to reference the correct material palette.
// File context. This allows the ESM reader to be 'reset' to this
// location later when we are ready to load the full data set.

@ -50,7 +50,7 @@ namespace sh
{
assert(mCurrentLanguage != Language_None);
bool anyShaderDirty = false;
bool removeBinaryCache = false;
if (boost::filesystem::exists (mPlatform->getCacheFolder () + "/lastModified.txt"))
{
@ -182,57 +182,33 @@ namespace sh
}
}
std::string sourceFile = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue();
std::string sourceAbsolute = mPlatform->getBasePath() + "/" + it->second->findChild("source")->getValue();
std::string sourceRelative = it->second->findChild("source")->getValue();
ShaderSet newSet (it->second->findChild("type")->getValue(), cg_profile, hlsl_profile,
sourceFile,
sourceAbsolute,
mPlatform->getBasePath(),
it->first,
&mGlobalSettings);
int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceFile));
mShadersLastModifiedNew[sourceFile] = lastModified;
if (mShadersLastModified.find(sourceFile) != mShadersLastModified.end()
&& mShadersLastModified[sourceFile] != lastModified)
int lastModified = boost::filesystem::last_write_time (boost::filesystem::path(sourceAbsolute));
mShadersLastModifiedNew[sourceRelative] = lastModified;
if (mShadersLastModified.find(sourceRelative) != mShadersLastModified.end())
{
// delete any outdated shaders based on this shader set.
if ( boost::filesystem::exists(mPlatform->getCacheFolder())
&& boost::filesystem::is_directory(mPlatform->getCacheFolder()))
if (mShadersLastModified[sourceRelative] != lastModified)
{
boost::filesystem::directory_iterator end_iter;
for( boost::filesystem::directory_iterator dir_iter(mPlatform->getCacheFolder()) ; dir_iter != end_iter ; ++dir_iter)
{
if (boost::filesystem::is_regular_file(dir_iter->status()) )
{
boost::filesystem::path file = dir_iter->path();
std::string pathname = file.filename().string();
// get first part of filename, e.g. main_fragment_546457654 -> main_fragment
// there is probably a better method for this...
std::vector<std::string> tokens;
boost::split(tokens, pathname, boost::is_any_of("_"));
tokens.erase(--tokens.end());
std::string shaderName;
for (std::vector<std::string>::const_iterator vector_iter = tokens.begin(); vector_iter != tokens.end();)
{
shaderName += *(vector_iter++);
if (vector_iter != tokens.end())
shaderName += "_";
}
if (shaderName == it->first)
{
boost::filesystem::remove(file);
std::cout << "Removing outdated file: " << file << std::endl;
}
}
}
// delete any outdated shaders based on this shader set
removeCache (it->first);
// remove the whole binary cache (removing only the individual shaders does not seem to be possible at this point with OGRE)
removeBinaryCache = true;
}
anyShaderDirty = true;
}
else
{
// if we get here, this is either the first run or a new shader file was added
// in both cases we can safely delete
removeCache (it->first);
}
mShaderSets.insert(std::make_pair(it->first, newSet));
}
}
@ -326,7 +302,7 @@ namespace sh
}
}
if (mPlatform->supportsShaderSerialization () && mReadMicrocodeCache && !anyShaderDirty)
if (mPlatform->supportsShaderSerialization () && mReadMicrocodeCache && !removeBinaryCache)
{
std::string file = mPlatform->getCacheFolder () + "/shShaderCache.txt";
if (boost::filesystem::exists(file))
@ -613,4 +589,41 @@ namespace sh
assert(m);
m->createForConfiguration (configuration, 0);
}
void Factory::removeCache(const std::string& pattern)
{
if ( boost::filesystem::exists(mPlatform->getCacheFolder())
&& boost::filesystem::is_directory(mPlatform->getCacheFolder()))
{
boost::filesystem::directory_iterator end_iter;
for( boost::filesystem::directory_iterator dir_iter(mPlatform->getCacheFolder()) ; dir_iter != end_iter ; ++dir_iter)
{
if (boost::filesystem::is_regular_file(dir_iter->status()) )
{
boost::filesystem::path file = dir_iter->path();
std::string pathname = file.filename().string();
// get first part of filename, e.g. main_fragment_546457654 -> main_fragment
// there is probably a better method for this...
std::vector<std::string> tokens;
boost::split(tokens, pathname, boost::is_any_of("_"));
tokens.erase(--tokens.end());
std::string shaderName;
for (std::vector<std::string>::const_iterator vector_iter = tokens.begin(); vector_iter != tokens.end();)
{
shaderName += *(vector_iter++);
if (vector_iter != tokens.end())
shaderName += "_";
}
if (shaderName == pattern)
{
boost::filesystem::remove(file);
std::cout << "Removing outdated shader: " << file << std::endl;
}
}
}
}
}
}

@ -202,6 +202,8 @@ namespace sh
MaterialInstance* findInstance (const std::string& name);
MaterialInstance* searchInstance (const std::string& name);
void removeCache (const std::string& pattern);
};
}

Loading…
Cancel
Save