Merge commit 'pull/upstream/678' into HEAD

Conflicts:
	apps/esmtool/record.cpp
	apps/opencs/model/doc/savingstages.cpp
	apps/opencs/model/world/land.cpp
	apps/opencs/model/world/land.hpp
	apps/opencs/model/world/landtexture.cpp
	apps/openmw/mwworld/store.cpp
	components/esm/loadland.cpp
	components/esm/loadland.hpp
pull/805/head
scrawl 9 years ago
commit b8e11cf197

@ -27,7 +27,8 @@ struct ESMData
std::vector<ESM::Header::MasterData> masters;
std::deque<EsmTool::RecordBase *> mRecords;
std::map<ESM::Cell *, std::deque<ESM::CellRef> > mCellRefs;
// Value: (Reference, Deleted flag)
std::map<ESM::Cell *, std::deque<std::pair<ESM::CellRef, bool> > > mCellRefs;
std::map<int, int> mRecordStats;
static const std::set<int> sLabeledRec;
@ -255,7 +256,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info)
while(cell.getNextRef(esm, ref, deleted))
{
if (save) {
info.data.mCellRefs[&cell].push_back(ref);
info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted));
}
if(quiet) continue;
@ -352,61 +353,58 @@ int load(Arguments& info)
uint32_t flags;
esm.getRecHeader(flags);
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
if (record == 0)
{
if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end())
{
std::cout << "Skipping " << n.toString() << " records." << std::endl;
skipped.push_back(n.val);
}
esm.skipRecord();
if (quiet) break;
std::cout << " Skipping\n";
continue;
}
record->setFlags(static_cast<int>(flags));
record->setPrintPlain(info.plain_given);
record->load(esm);
// Is the user interested in this record type?
bool interested = true;
if (!info.types.empty())
{
std::vector<std::string>::iterator match;
match = std::find(info.types.begin(), info.types.end(),
n.toString());
match = std::find(info.types.begin(), info.types.end(), n.toString());
if (match == info.types.end()) interested = false;
}
std::string id = esm.getHNOString("NAME");
if (id.empty())
id = esm.getHNOString("INAM");
if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, id))
if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId()))
interested = false;
if(!quiet && interested)
std::cout << "\nRecord: " << n.toString()
<< " '" << id << "'\n";
EsmTool::RecordBase *record = EsmTool::RecordBase::create(n);
if (record == 0) {
if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end())
{
std::cout << "Skipping " << n.toString() << " records." << std::endl;
skipped.push_back(n.val);
}
{
std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n";
record->print();
}
esm.skipRecord();
if (quiet) break;
std::cout << " Skipping\n";
} else {
if (record->getType().val == ESM::REC_GMST) {
// preset id for GameSetting record
record->cast<ESM::GameSetting>()->get().mId = id;
}
record->setId(id);
record->setFlags((int) flags);
record->setPrintPlain(info.plain_given);
record->load(esm);
if (!quiet && interested) record->print();
if (record->getType().val == ESM::REC_CELL && loadCells && interested) {
loadCell(record->cast<ESM::Cell>()->get(), esm, info);
}
if (record->getType().val == ESM::REC_CELL && loadCells && interested)
{
loadCell(record->cast<ESM::Cell>()->get(), esm, info);
}
if (save) {
info.data.mRecords.push_back(record);
} else {
delete record;
}
++info.data.mRecordStats[n.val];
if (save)
{
info.data.mRecords.push_back(record);
}
else
{
delete record;
}
++info.data.mRecordStats[n.val];
}
} catch(std::exception &e) {
@ -493,28 +491,19 @@ int clone(Arguments& info)
for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it)
{
EsmTool::RecordBase *record = *it;
name.val = record->getType().val;
esm.startRecord(name.toString(), record->getFlags());
// TODO wrap this with std::set
if (ESMData::sLabeledRec.count(name.val) > 0) {
esm.writeHNCString("NAME", record->getId());
} else {
esm.writeHNOString("NAME", record->getId());
}
record->save(esm);
if (name.val == ESM::REC_CELL) {
ESM::Cell *ptr = &record->cast<ESM::Cell>()->get();
if (!info.data.mCellRefs[ptr].empty()) {
typedef std::deque<ESM::CellRef> RefList;
typedef std::deque<std::pair<ESM::CellRef, bool> > RefList;
RefList &refs = info.data.mCellRefs[ptr];
for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt)
{
refIt->save(esm);
refIt->first.save(esm, refIt->second);
}
}
}

@ -405,6 +405,7 @@ void Record<ESM::Activator>::print()
std::cout << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -419,6 +420,7 @@ void Record<ESM::Potion>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl;
printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -447,6 +449,7 @@ void Record<ESM::Armor>::print()
if (pit->mFemale != "")
std::cout << " Female Name: " << pit->mFemale << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -461,6 +464,7 @@ void Record<ESM::Apparatus>::print()
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -474,6 +478,7 @@ void Record<ESM::BodyPart>::print()
std::cout << " Part: " << meshPartLabel(mData.mData.mPart)
<< " (" << (int)mData.mData.mPart << ")" << std::endl;
std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -502,6 +507,7 @@ void Record<ESM::Book>::print()
{
std::cout << " Text: [skipped]" << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -513,6 +519,7 @@ void Record<ESM::BirthSign>::print()
std::vector<std::string>::iterator pit;
for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit)
std::cout << " Power: " << *pit << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -541,6 +548,7 @@ void Record<ESM::Cell>::print()
std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl;
std::cout << " Water Level Int: " << mData.mWaterInt << std::endl;
std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
@ -563,6 +571,7 @@ void Record<ESM::Class>::print()
for (int i = 0; i != 5; i++)
std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1])
<< " (" << mData.mData.mSkills[i][1] << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -589,6 +598,7 @@ void Record<ESM::Clothing>::print()
if (pit->mFemale != "")
std::cout << " Female Name: " << pit->mFemale << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -604,6 +614,7 @@ void Record<ESM::Container>::print()
for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit)
std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount
<< " Item: " << cit->mItem.toString() << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -670,6 +681,7 @@ void Record<ESM::Creature>::print()
std::vector<ESM::AIPackage>::iterator pit;
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
printAIPackage(*pit);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -677,6 +689,7 @@ void Record<ESM::Dialogue>::print()
{
std::cout << " Type: " << dialogTypeLabel(mData.mType)
<< " (" << (int)mData.mType << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
// Sadly, there are no DialInfos, because the loader dumps as it
// loads, rather than loading and then dumping. :-( Anyone mind if
// I change this?
@ -693,6 +706,7 @@ void Record<ESM::Door>::print()
std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " OpenSound: " << mData.mOpenSound << std::endl;
std::cout << " CloseSound: " << mData.mCloseSound << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -704,6 +718,7 @@ void Record<ESM::Enchantment>::print()
std::cout << " Charge: " << mData.mData.mCharge << std::endl;
std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl;
printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -737,12 +752,14 @@ void Record<ESM::Faction>::print()
std::map<std::string, int>::iterator rit;
for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit)
std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
void Record<ESM::Global>::print()
{
std::cout << " " << mData.mValue << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -809,6 +826,7 @@ void Record<ESM::DialInfo>::print()
std::cout << " Result Script: [skipped]" << std::endl;
}
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -832,6 +850,7 @@ void Record<ESM::Ingredient>::print()
std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i])
<< " (" << mData.mData.mAttributes[i] << ")" << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -848,6 +867,8 @@ void Record<ESM::Land>::print()
std::cout << " Unknown1: " << data->mUnk1 << std::endl;
std::cout << " Unknown2: " << data->mUnk2 << std::endl;
}
if (!wasLoaded) mData.unloadData();
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -860,6 +881,7 @@ void Record<ESM::CreatureLevList>::print()
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
std::cout << " Creature: Level: " << iit->mLevel
<< " Creature: " << iit->mId << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -872,6 +894,7 @@ void Record<ESM::ItemLevList>::print()
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
std::cout << " Inventory: Level: " << iit->mLevel
<< " Item: " << iit->mId << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -892,6 +915,7 @@ void Record<ESM::Light>::print()
std::cout << " Duration: " << mData.mData.mTime << std::endl;
std::cout << " Radius: " << mData.mData.mRadius << std::endl;
std::cout << " Color: " << mData.mData.mColor << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -906,6 +930,7 @@ void Record<ESM::Lockpick>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -920,6 +945,7 @@ void Record<ESM::Probe>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -934,6 +960,7 @@ void Record<ESM::Repair>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -942,6 +969,7 @@ void Record<ESM::LandTexture>::print()
std::cout << " Id: " << mData.mId << std::endl;
std::cout << " Index: " << mData.mIndex << std::endl;
std::cout << " Texture: " << mData.mTexture << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -992,6 +1020,7 @@ void Record<ESM::Miscellaneous>::print()
std::cout << " Weight: " << mData.mData.mWeight << std::endl;
std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Is Key: " << mData.mData.mIsKey << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1077,6 +1106,8 @@ void Record<ESM::NPC>::print()
std::vector<ESM::AIPackage>::iterator pit;
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
printAIPackage(*pit);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1111,6 +1142,8 @@ void Record<ESM::Pathgrid>::print()
std::cout << " BAD POINT IN EDGE!" << std::endl;
i++;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1151,6 +1184,8 @@ void Record<ESM::Race>::print()
std::vector<std::string>::iterator sit;
for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit)
std::cout << " Power: " << *sit << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1210,6 +1245,8 @@ void Record<ESM::Script>::print()
{
std::cout << " Script: [skipped]" << std::endl;
}
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1233,6 +1270,7 @@ void Record<ESM::SoundGenerator>::print()
std::cout << " Sound: " << mData.mSound << std::endl;
std::cout << " Type: " << soundTypeLabel(mData.mType)
<< " (" << mData.mType << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1243,6 +1281,7 @@ void Record<ESM::Sound>::print()
if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0)
std::cout << " Range: " << (int)mData.mData.mMinRange << " - "
<< (int)mData.mData.mMaxRange << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1254,13 +1293,15 @@ void Record<ESM::Spell>::print()
std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl;
std::cout << " Cost: " << mData.mData.mCost << std::endl;
printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
void Record<ESM::StartScript>::print()
{
std::cout << "Start Script: " << mData.mId << std::endl;
std::cout << "Start Data: " << mData.mData << std::endl;
std::cout << " Start Script: " << mData.mId << std::endl;
std::cout << " Start Data: " << mData.mData << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
@ -1301,6 +1342,37 @@ void Record<ESM::Weapon>::print()
if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0)
std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-"
<< (int)mData.mData.mThrust[1] << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
}
template<>
std::string Record<ESM::Cell>::getId() const
{
return mData.mName;
}
template<>
std::string Record<ESM::Land>::getId() const
{
return ""; // No ID for Land record
}
template<>
std::string Record<ESM::MagicEffect>::getId() const
{
return ""; // No ID for MagicEffect record
}
template<>
std::string Record<ESM::Pathgrid>::getId() const
{
return ""; // No ID for Pathgrid record
}
template<>
std::string Record<ESM::Skill>::getId() const
{
return ""; // No ID for Skill record
}
} // end namespace

@ -32,13 +32,7 @@ namespace EsmTool
virtual ~RecordBase() {}
const std::string &getId() const {
return mId;
}
void setId(const std::string &id) {
mId = id;
}
virtual std::string getId() const = 0;
uint32_t getFlags() const {
return mFlags;
@ -73,22 +67,37 @@ namespace EsmTool
class Record : public RecordBase
{
T mData;
bool mIsDeleted;
public:
Record()
: mIsDeleted(false)
{}
std::string getId() const {
return mData.mId;
}
T &get() {
return mData;
}
void save(ESM::ESMWriter &esm) {
mData.save(esm);
mData.save(esm, mIsDeleted);
}
void load(ESM::ESMReader &esm) {
mData.load(esm);
mData.load(esm, mIsDeleted);
}
void print();
};
template<> std::string Record<ESM::Cell>::getId() const;
template<> std::string Record<ESM::Land>::getId() const;
template<> std::string Record<ESM::MagicEffect>::getId() const;
template<> std::string Record<ESM::Pathgrid>::getId() const;
template<> std::string Record<ESM::Skill>::getId() const;
template<> void Record<ESM::Activator>::print();
template<> void Record<ESM::Potion>::print();

@ -158,9 +158,9 @@ namespace ESSImport
void ConvertCell::read(ESM::ESMReader &esm)
{
ESM::Cell cell;
std::string id = esm.getHNString("NAME");
cell.mName = id;
cell.load(esm, false);
bool isDeleted = false;
cell.load(esm, isDeleted, false);
// I wonder what 0x40 does?
if (cell.isExterior() && cell.mData.mFlags & 0x20)
@ -169,7 +169,7 @@ namespace ESSImport
}
// note if the player is in a nameless exterior cell, we will assign the cellId later based on player position
if (id == mContext->mPlayerCellName)
if (cell.mName == mContext->mPlayerCellName)
{
mContext->mPlayer.mCellId = cell.getCellId();
}
@ -277,7 +277,7 @@ namespace ESSImport
if (cell.isExterior())
mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell;
else
mIntCells[id] = newcell;
mIntCells[cell.mName] = newcell;
}
void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm)

@ -54,6 +54,8 @@ public:
void setContext(Context& context) { mContext = &context; }
/// @note The load method of ESM records accept the deleted flag as a parameter.
/// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored.
virtual void read(ESM::ESMReader& esm)
{
}
@ -78,10 +80,11 @@ public:
virtual void read(ESM::ESMReader& esm)
{
std::string id = esm.getHNString("NAME");
T record;
record.load(esm);
mRecords[id] = record;
bool isDeleted = false;
record.load(esm, isDeleted);
mRecords[record.mId] = record;
}
virtual void write(ESM::ESMWriter& esm)
@ -89,7 +92,6 @@ public:
for (typename std::map<std::string, T>::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it)
{
esm.startRecord(T::sRecordId);
esm.writeHNString("NAME", it->first);
it->second.save(esm);
esm.endRecord(T::sRecordId);
}
@ -105,14 +107,15 @@ public:
virtual void read(ESM::ESMReader &esm)
{
ESM::NPC npc;
std::string id = esm.getHNString("NAME");
npc.load(esm);
if (id != "player")
bool isDeleted = false;
npc.load(esm, isDeleted);
if (npc.mId != "player")
{
// Handles changes to the NPC struct, but since there is no index here
// it will apply to ALL instances of the class. seems to be the reason for the
// "feature" in MW where changing AI settings of one guard will change it for all guards of that refID.
mContext->mNpcs[Misc::StringUtils::lowerCase(id)] = npc;
mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc;
}
else
{
@ -142,9 +145,10 @@ public:
{
// See comment in ConvertNPC
ESM::Creature creature;
std::string id = esm.getHNString("NAME");
creature.load(esm);
mContext->mCreatures[Misc::StringUtils::lowerCase(id)] = creature;
bool isDeleted = false;
creature.load(esm, isDeleted);
mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature;
}
};
@ -157,18 +161,19 @@ class ConvertGlobal : public DefaultConverter<ESM::Global>
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
ESM::Global global;
global.load(esm);
if (Misc::StringUtils::ciEqual(id, "gamehour"))
bool isDeleted = false;
global.load(esm, isDeleted);
if (Misc::StringUtils::ciEqual(global.mId, "gamehour"))
mContext->mHour = global.mValue.getFloat();
if (Misc::StringUtils::ciEqual(id, "day"))
if (Misc::StringUtils::ciEqual(global.mId, "day"))
mContext->mDay = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(id, "month"))
if (Misc::StringUtils::ciEqual(global.mId, "month"))
mContext->mMonth = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(id, "year"))
if (Misc::StringUtils::ciEqual(global.mId, "year"))
mContext->mYear = global.mValue.getInteger();
mRecords[id] = global;
mRecords[global.mId] = global;
}
};
@ -177,14 +182,14 @@ class ConvertClass : public DefaultConverter<ESM::Class>
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
ESM::Class class_;
class_.load(esm);
bool isDeleted = false;
if (id == "NEWCLASSID_CHARGEN")
class_.load(esm, isDeleted);
if (class_.mId == "NEWCLASSID_CHARGEN")
mContext->mCustomPlayerClassName = class_.mName;
mRecords[id] = class_;
mRecords[class_.mId] = class_;
}
};
@ -193,13 +198,14 @@ class ConvertBook : public DefaultConverter<ESM::Book>
public:
virtual void read(ESM::ESMReader &esm)
{
std::string id = esm.getHNString("NAME");
ESM::Book book;
book.load(esm);
bool isDeleted = false;
book.load(esm, isDeleted);
if (book.mData.mSkillID == -1)
mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id));
mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId));
mRecords[id] = book;
mRecords[book.mId] = book;
}
};
@ -371,11 +377,12 @@ class ConvertFACT : public Converter
public:
virtual void read(ESM::ESMReader& esm)
{
std::string id = esm.getHNString("NAME");
ESM::Faction faction;
faction.load(esm);
bool isDeleted = false;
faction.load(esm, isDeleted);
std::string id = Misc::StringUtils::toLower(faction.mId);
Misc::StringUtils::toLower(id);
for (std::map<std::string, int>::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it)
{
std::string faction2 = Misc::StringUtils::lowerCase(it->first);

@ -32,7 +32,8 @@ namespace ESSImport
if (esm.isNextSub("MNAM"))
esm.skipHSub();
ESM::CellRef::loadData(esm);
bool isDeleted = false;
ESM::CellRef::loadData(esm, isDeleted);
mHasACDT = false;
if (esm.isNextSub("ACDT"))

@ -394,7 +394,7 @@ namespace ESSImport
}
writer.startRecord(ESM::REC_NPC_);
writer.writeHNString("NAME", "player");
context.mPlayerBase.mId = "player";
context.mPlayerBase.save(writer);
writer.endRecord(ESM::REC_NPC_);

@ -32,7 +32,8 @@ namespace ESSImport
item.mSCRI.load(esm);
// for XSOL and XCHG seen so far, but probably others too
item.ESM::CellRef::loadData(esm);
bool isDeleted = false;
item.ESM::CellRef::loadData(esm, isDeleted);
int charge=-1;
esm.getHNOT(charge, "XHLT");

@ -99,95 +99,79 @@ int CSMDoc::WriteDialogueCollectionStage::setup()
void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& messages)
{
ESM::ESMWriter& writer = mState.getWriter();
const CSMWorld::Record<ESM::Dialogue>& topic = mTopics.getRecord (stage);
CSMWorld::RecordBase::State state = topic.mState;
if (state==CSMWorld::RecordBase::State_Deleted)
if (topic.mState == CSMWorld::RecordBase::State_Deleted)
{
// if the topic is deleted, we do not need to bother with INFO records.
/// \todo wrote record with delete flag
ESM::Dialogue dialogue = topic.get();
writer.startRecord(dialogue.sRecordId);
dialogue.save(writer, true);
writer.endRecord(dialogue.sRecordId);
return;
}
// Test, if we need to save anything associated info records.
bool infoModified = false;
CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId);
for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter)
{
CSMWorld::RecordBase::State state = iter->mState;
if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly ||
state==CSMWorld::RecordBase::State_Deleted)
if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted)
{
infoModified = true;
break;
}
}
if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly ||
infoModified)
if (topic.isModified() || infoModified)
{
if (infoModified && state != CSMWorld::RecordBase::State_Modified
&& state != CSMWorld::RecordBase::State_ModifiedOnly)
if (infoModified && topic.mState != CSMWorld::RecordBase::State_Modified
&& topic.mState != CSMWorld::RecordBase::State_ModifiedOnly)
{
mState.getWriter().startRecord (topic.mBase.sRecordId);
mState.getWriter().writeHNCString ("NAME", topic.mBase.mId);
topic.mBase.save (mState.getWriter());
topic.mBase.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted);
mState.getWriter().endRecord (topic.mBase.sRecordId);
}
else
{
mState.getWriter().startRecord (topic.mModified.sRecordId);
mState.getWriter().writeHNCString ("NAME", topic.mModified.mId);
topic.mModified.save (mState.getWriter());
topic.mModified.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted);
mState.getWriter().endRecord (topic.mModified.sRecordId);
}
// write modified selected info records
for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second;
++iter)
for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter)
{
CSMWorld::RecordBase::State infoState = iter->mState;
if (infoState==CSMWorld::RecordBase::State_Deleted)
{
/// \todo wrote record with delete flag
}
else if (infoState==CSMWorld::RecordBase::State_Modified ||
infoState==CSMWorld::RecordBase::State_ModifiedOnly)
if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted)
{
ESM::DialInfo info = iter->get();
info.mId = info.mId.substr (info.mId.find_last_of ('#')+1);
info.mPrev = "";
if (iter!=range.first)
{
CSMWorld::InfoCollection::RecordConstIterator prev = iter;
--prev;
info.mPrev =
prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1);
info.mPrev = prev->get().mId.substr (prev->get().mId.find_last_of ('#')+1);
}
CSMWorld::InfoCollection::RecordConstIterator next = iter;
++next;
info.mNext = "";
if (next!=range.second)
{
info.mNext =
next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1);
info.mNext = next->get().mId.substr (next->get().mId.find_last_of ('#')+1);
}
mState.getWriter().startRecord (info.sRecordId);
mState.getWriter().writeHNCString ("INAM", info.mId);
info.save (mState.getWriter());
mState.getWriter().endRecord (info.sRecordId);
writer.startRecord (info.sRecordId);
info.save (writer, iter->mState == CSMWorld::RecordBase::State_Deleted);
writer.endRecord (info.sRecordId);
}
}
}
@ -235,9 +219,7 @@ void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages)
const CSMWorld::Record<CSMWorld::CellRef>& record =
mDocument.getData().getReferences().getRecord (i);
if (record.mState==CSMWorld::RecordBase::State_Deleted ||
record.mState==CSMWorld::RecordBase::State_Modified ||
record.mState==CSMWorld::RecordBase::State_ModifiedOnly)
if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted)
{
std::string cellId = record.get().mOriginalCell.empty() ?
record.get().mCell : record.get().mOriginalCell;
@ -279,36 +261,34 @@ int CSMDoc::WriteCellCollectionStage::setup()
void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages)
{
const CSMWorld::Record<CSMWorld::Cell>& cell =
mDocument.getData().getCells().getRecord (stage);
ESM::ESMWriter& writer = mState.getWriter();
const CSMWorld::Record<CSMWorld::Cell>& cell = mDocument.getData().getCells().getRecord (stage);
std::map<std::string, std::deque<int> >::const_iterator references =
mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId));
if (cell.mState==CSMWorld::RecordBase::State_Modified ||
cell.mState==CSMWorld::RecordBase::State_ModifiedOnly ||
if (cell.isModified() ||
cell.mState == CSMWorld::RecordBase::State_Deleted ||
references!=mState.getSubRecords().end())
{
bool interior = cell.get().mId.substr (0, 1)!="#";
CSMWorld::Cell cellRecord = cell.get();
bool interior = cellRecord.mId.substr (0, 1)!="#";
// write cell data
mState.getWriter().startRecord (cell.mModified.sRecordId);
mState.getWriter().writeHNOCString ("NAME", cell.get().mName);
ESM::Cell cell2 = cell.get();
writer.startRecord (cellRecord.sRecordId);
if (interior)
cell2.mData.mFlags |= ESM::Cell::Interior;
cellRecord.mData.mFlags |= ESM::Cell::Interior;
else
{
cell2.mData.mFlags &= ~ESM::Cell::Interior;
cellRecord.mData.mFlags &= ~ESM::Cell::Interior;
std::istringstream stream (cell.get().mId.c_str());
std::istringstream stream (cellRecord.mId.c_str());
char ignore;
stream >> ignore >> cell2.mData.mX >> cell2.mData.mY;
stream >> ignore >> cellRecord.mData.mX >> cellRecord.mData.mY;
}
cell2.save (mState.getWriter());
cellRecord.save (writer, cell.mState == CSMWorld::RecordBase::State_Deleted);
// write references
if (references!=mState.getSubRecords().end())
@ -319,24 +299,25 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages)
const CSMWorld::Record<CSMWorld::CellRef>& ref =
mDocument.getData().getReferences().getRecord (*iter);
if (ref.mState==CSMWorld::RecordBase::State_Modified ||
ref.mState==CSMWorld::RecordBase::State_ModifiedOnly)
if (ref.isModified() || ref.mState == CSMWorld::RecordBase::State_Deleted)
{
CSMWorld::CellRef refRecord = ref.get();
// recalculate the ref's cell location
std::ostringstream stream;
if (!interior)
{
std::pair<int, int> index = ref.get().getCellIndex();
std::pair<int, int> index = refRecord.getCellIndex();
stream << "#" << index.first << " " << index.second;
}
// An empty mOriginalCell is meant to indicate that it is the same as
// the current cell. It is possible that a moved ref is moved again.
if ((ref.get().mOriginalCell.empty() ? ref.get().mCell : ref.get().mOriginalCell)
if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell)
!= stream.str() && !interior)
{
ESM::MovedCellRef moved;
moved.mRefNum = ref.get().mRefNum;
moved.mRefNum = refRecord.mRefNum;
// Need to fill mTarget with the ref's new position.
std::istringstream istream (stream.str().c_str());
@ -344,24 +325,16 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages)
char ignore;
istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1];
ref.get().mRefNum.save (mState.getWriter(), false, "MVRF");
mState.getWriter().writeHNT ("CNDT", moved.mTarget, 8);
refRecord.mRefNum.save (writer, false, "MVRF");
writer.writeHNT ("CNDT", moved.mTarget, 8);
}
ref.get().save (mState.getWriter());
}
else if (ref.mState==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
refRecord.save (writer, false, false, ref.mState == CSMWorld::RecordBase::State_Deleted);
}
}
}
mState.getWriter().endRecord (cell.mModified.sRecordId);
}
else if (cell.mState==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
writer.endRecord (cellRecord.sRecordId);
}
}
@ -378,11 +351,11 @@ int CSMDoc::WritePathgridCollectionStage::setup()
void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages)
{
const CSMWorld::Record<CSMWorld::Pathgrid>& pathgrid =
ESM::ESMWriter& writer = mState.getWriter();
const CSMWorld::Record<CSMWorld::Pathgrid>& pathgrid =
mDocument.getData().getPathgrids().getRecord (stage);
if (pathgrid.mState==CSMWorld::RecordBase::State_Modified ||
pathgrid.mState==CSMWorld::RecordBase::State_ModifiedOnly)
if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted)
{
CSMWorld::Pathgrid record = pathgrid.get();
@ -395,15 +368,9 @@ void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& message
else
record.mCell = record.mId;
mState.getWriter().startRecord (record.sRecordId);
record.save (mState.getWriter());
mState.getWriter().endRecord (record.sRecordId);
}
else if (pathgrid.mState==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
writer.startRecord (record.sRecordId);
record.save (writer, pathgrid.mState == CSMWorld::RecordBase::State_Deleted);
writer.endRecord (record.sRecordId);
}
}
@ -420,26 +387,20 @@ int CSMDoc::WriteLandCollectionStage::setup()
void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages)
{
const CSMWorld::Record<CSMWorld::Land>& land =
ESM::ESMWriter& writer = mState.getWriter();
const CSMWorld::Record<CSMWorld::Land>& land =
mDocument.getData().getLand().getRecord (stage);
if (land.mState==CSMWorld::RecordBase::State_Modified ||
land.mState==CSMWorld::RecordBase::State_ModifiedOnly)
if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted)
{
const CSMWorld::Land& record = land.get();
mState.getWriter().startRecord (record.sRecordId);
record.save (mState.getWriter());
CSMWorld::Land record = land.get();
writer.startRecord (record.sRecordId);
record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted);
if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes))
data->save (mState.getWriter());
mState.getWriter().endRecord (record.sRecordId);
}
else if (land.mState==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
writer.endRecord (record.sRecordId);
}
}
@ -456,25 +417,16 @@ int CSMDoc::WriteLandTextureCollectionStage::setup()
void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages)
{
const CSMWorld::Record<CSMWorld::LandTexture>& landTexture =
ESM::ESMWriter& writer = mState.getWriter();
const CSMWorld::Record<CSMWorld::LandTexture>& landTexture =
mDocument.getData().getLandTextures().getRecord (stage);
if (landTexture.mState==CSMWorld::RecordBase::State_Modified ||
landTexture.mState==CSMWorld::RecordBase::State_ModifiedOnly)
if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted)
{
CSMWorld::LandTexture record = landTexture.get();
mState.getWriter().startRecord (record.sRecordId);
mState.getWriter().writeHNString("NAME", record.mId);
record.save (mState.getWriter());
mState.getWriter().endRecord (record.sRecordId);
}
else if (landTexture.mState==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
writer.startRecord (record.sRecordId);
record.save (writer, landTexture.mState == CSMWorld::RecordBase::State_Deleted);
writer.endRecord (record.sRecordId);
}
}

@ -100,26 +100,17 @@ namespace CSMDoc
if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope)
return;
ESM::ESMWriter& writer = mState.getWriter();
CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState;
typename CollectionT::ESXRecord record = mCollection.getRecord (stage).get();
if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly)
if (state == CSMWorld::RecordBase::State_Modified ||
state == CSMWorld::RecordBase::State_ModifiedOnly ||
state == CSMWorld::RecordBase::State_Deleted)
{
// FIXME: A quick Workaround to support records which should not write
// NAME, including SKIL, MGEF and SCPT. If there are many more
// idcollection records that doesn't use NAME then a more generic
// solution may be required.
uint32_t name = mCollection.getRecord (stage).mModified.sRecordId;
mState.getWriter().startRecord (name);
if(name != ESM::REC_SKIL && name != ESM::REC_MGEF && name != ESM::REC_SCPT)
mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage));
mCollection.getRecord (stage).mModified.save (mState.getWriter());
mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId);
}
else if (state==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
writer.startRecord (record.sRecordId);
record.save (writer, state == CSMWorld::RecordBase::State_Deleted);
writer.endRecord (record.sRecordId);
}
}

@ -2,18 +2,15 @@
#include <sstream>
void CSMWorld::Cell::load (ESM::ESMReader &esm)
void CSMWorld::Cell::load (ESM::ESMReader &esm, bool &isDeleted)
{
mName = mId;
ESM::Cell::load (esm, isDeleted, false);
ESM::Cell::load (esm, false);
if (!(mData.mFlags & Interior))
mId = mName;
if (isExterior())
{
std::ostringstream stream;
stream << "#" << mData.mX << " " << mData.mY;
mId = stream.str();
}
}

@ -16,7 +16,7 @@ namespace CSMWorld
{
std::string mId;
void load (ESM::ESMReader &esm);
void load (ESM::ESMReader &esm, bool &isDeleted);
};
}

@ -43,6 +43,12 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
class Collection : public CollectionBase
{
public:
typedef ESXRecordT ESXRecord;
private:
std::vector<Record<ESXRecordT> > mRecords;
std::map<std::string, int> mIndex;
std::vector<Column<ESXRecordT> *> mColumns;

@ -1010,41 +1010,43 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
case ESM::REC_DIAL:
{
std::string id = mReader->getHNOString ("NAME");
ESM::Dialogue record;
record.mId = id;
record.load (*mReader);
bool isDeleted = false;
if (record.mType==ESM::Dialogue::Journal)
{
mJournals.load (record, mBase);
mDialogue = &mJournals.getRecord (id).get();
}
else if (record.mType==ESM::Dialogue::Deleted)
record.load (*mReader, isDeleted);
if (isDeleted)
{
mDialogue = 0; // record vector can be shuffled around which would make pointer
// to record invalid
// record vector can be shuffled around which would make pointer to record invalid
mDialogue = 0;
if (mJournals.tryDelete (id))
if (mJournals.tryDelete (record.mId))
{
/// \todo handle info records
mJournalInfos.removeDialogueInfos(record.mId);
}
else if (mTopics.tryDelete (id))
else if (mTopics.tryDelete (record.mId))
{
/// \todo handle info records
mTopicInfos.removeDialogueInfos(record.mId);
}
else
{
messages.add (UniversalId::Type_None,
"Trying to delete dialogue record " + id + " which does not exist",
"Trying to delete dialogue record " + record.mId + " which does not exist",
"", CSMDoc::Message::Severity_Warning);
}
}
else
{
mTopics.load (record, mBase);
mDialogue = &mTopics.getRecord (id).get();
if (record.mType == ESM::Dialogue::Journal)
{
mJournals.load (record, mBase);
mDialogue = &mJournals.getRecord (record.mId).get();
}
else
{
mTopics.load (record, mBase);
mDialogue = &mTopics.getRecord (record.mId).get();
}
}
break;

@ -11,7 +11,7 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
class IdCollection : public Collection<ESXRecordT, IdAccessorT>
{
virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader);
virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted);
public:
@ -33,77 +33,46 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT>
void IdCollection<ESXRecordT, IdAccessorT>::loadRecord (ESXRecordT& record,
ESM::ESMReader& reader)
ESM::ESMReader& reader,
bool& isDeleted)
{
record.load (reader);
record.load (reader, isDeleted);
}
template<typename ESXRecordT, typename IdAccessorT>
int IdCollection<ESXRecordT, IdAccessorT>::load (ESM::ESMReader& reader, bool base)
{
std::string id = reader.getHNOString ("NAME");
ESXRecordT record;
bool isDeleted = false;
if (reader.isNextSub ("DELE"))
{
int index = Collection<ESXRecordT, IdAccessorT>::searchId (id);
loadRecord (record, reader, isDeleted);
reader.skipRecord();
std::string id = IdAccessorT().getId (record);
int index = this->searchId (id);
if (isDeleted)
{
if (index==-1)
{
// deleting a record that does not exist
// ignore it for now
/// \todo report the problem to the user
}
else if (base)
{
Collection<ESXRecordT, IdAccessorT>::removeRows (index, 1);
}
else
{
Record<ESXRecordT> record = Collection<ESXRecordT, IdAccessorT>::getRecord (index);
record.mState = RecordBase::State_Deleted;
this->setRecord (index, record);
return -1;
}
return -1;
}
else
{
ESXRecordT record;
// Sometimes id (i.e. NAME of the cell) may be different to the id we stored
// earlier. e.g. NAME == "Vivec, Arena" but id == "#-4 11". Sometime NAME is
// missing altogether for scripts or cells.
//
// In such cases the returned index will be -1. We then try updating the
// IdAccessor's id manually (e.g. set mId of the record to "Vivec, Arena")
// and try getting the index once more after loading the record. The mId of the
// record would have changed to "#-4 11" after the load, and searchId() should find
// it (if this is a modify)
int index = this->searchId (id);
if (index==-1)
IdAccessorT().getId (record) = id;
else
{
record = this->getRecord (index).get();
}
loadRecord (record, reader);
if (index==-1)
if (base)
{
std::string newId = IdAccessorT().getId(record);
int newIndex = this->searchId(newId);
if (newIndex != -1 && id != newId)
index = newIndex;
this->removeRows (index, 1);
return -1;
}
return load (record, base, index);
Record<ESXRecordT> baseRecord = this->getRecord (index);
baseRecord.mState = RecordBase::State_Deleted;
this->setRecord (index, baseRecord);
return index;
}
return load (record, base, index);
}
template<typename ESXRecordT, typename IdAccessorT>

@ -106,21 +106,20 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector<int
void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue)
{
std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" +
reader.getHNOString ("INAM");
Info info;
bool isDeleted = false;
if (reader.isNextSub ("DELE"))
info.load (reader, isDeleted);
std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" + info.mId;
if (isDeleted)
{
int index = searchId (id);
reader.skipRecord();
if (index==-1)
{
// deleting a record that does not exist
// ignore it for now
/// \todo report the problem to the user
}
else if (base)
@ -136,12 +135,9 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES
}
else
{
Info record;
record.mTopicId = dialogue.mId;
record.mId = id;
record.load (reader);
load (record, base);
info.mTopicId = dialogue.mId;
info.mId = id;
load (info, base);
}
}
@ -193,3 +189,39 @@ CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const s
return Range (begin, end);
}
void CSMWorld::InfoCollection::removeDialogueInfos(const std::string& dialogueId)
{
std::string id = Misc::StringUtils::lowerCase(dialogueId);
std::vector<int> erasedRecords;
std::map<std::string, int>::const_iterator current = getIdMap().lower_bound(id);
std::map<std::string, int>::const_iterator end = getIdMap().end();
for (; current != end; ++current)
{
Record<Info> record = getRecord(current->second);
if (Misc::StringUtils::ciEqual(dialogueId, record.get().mTopicId))
{
if (record.mState == RecordBase::State_ModifiedOnly)
{
erasedRecords.push_back(current->second);
}
else
{
record.mState = RecordBase::State_Deleted;
setRecord(current->second, record);
}
}
else
{
break;
}
}
while (!erasedRecords.empty())
{
removeRows(erasedRecords.back(), 1);
erasedRecords.pop_back();
}
}

@ -44,6 +44,8 @@ namespace CSMWorld
Range getTopicRange (const std::string& topic) const;
///< Return iterators that point to the beginning and past the end of the range for
/// the given topic.
void removeDialogueInfos(const std::string& dialogueId);
};
}

@ -4,13 +4,12 @@
namespace CSMWorld
{
void Land::load(ESM::ESMReader &esm)
void Land::load(ESM::ESMReader &esm, bool &isDeleted)
{
ESM::Land::load(esm);
ESM::Land::load(esm, isDeleted);
std::ostringstream stream;
stream << "#" << mX << " " << mY;
mId = stream.str();
}
}

@ -10,13 +10,12 @@ namespace CSMWorld
/// \brief Wrapper for Land record. Encodes X and Y cell index in the ID.
///
/// \todo Add worldspace support to the Land record.
/// \todo Add a proper copy constructor (currently worked around using shared_ptr)
struct Land : public ESM::Land
{
std::string mId;
/// Loads the metadata and ID
void load (ESM::ESMReader &esm);
void load (ESM::ESMReader &esm, bool &isDeleted);
};
}

@ -4,10 +4,9 @@
namespace CSMWorld
{
void LandTexture::load(ESM::ESMReader &esm)
void LandTexture::load(ESM::ESMReader &esm, bool &isDeleted)
{
ESM::LandTexture::load(esm);
ESM::LandTexture::load(esm, isDeleted);
mPluginIndex = esm.getIndex();
}

@ -12,7 +12,7 @@ namespace CSMWorld
{
int mPluginIndex;
void load (ESM::ESMReader &esm);
void load (ESM::ESMReader &esm, bool &isDeleted);
};
}

@ -4,33 +4,28 @@
#include <sstream>
void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, const IdCollection<Cell>& cells)
void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection<Cell>& cells)
{
load (esm);
load (esm, isDeleted);
// correct ID
if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1)
{
std::ostringstream stream;
stream << "#" << mData.mX << " " << mData.mY;
mId = stream.str();
}
}
void CSMWorld::Pathgrid::load (ESM::ESMReader &esm)
void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted)
{
ESM::Pathgrid::load (esm);
ESM::Pathgrid::load (esm, isDeleted);
mId = mCell;
if (mCell.empty())
{
std::ostringstream stream;
stream << "#" << mData.mX << " " << mData.mY;
mId = stream.str();
}
else
mId = mCell;
}

@ -20,9 +20,8 @@ namespace CSMWorld
{
std::string mId;
void load (ESM::ESMReader &esm, const IdCollection<Cell>& cells);
void load (ESM::ESMReader &esm);
void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection<Cell>& cells);
void load (ESM::ESMReader &esm, bool &isDeleted);
};
}

@ -19,12 +19,11 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
Cell& cell2 = base ? cell.mBase : cell.mModified;
CellRef ref;
bool deleted = false;
ESM::MovedCellRef mref;
bool isDeleted = false;
// hack to initialise mindex
while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, deleted, true, &mref))
while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref))
{
// Keep mOriginalCell empty when in modified (as an indicator that the
// original cell will always be equal the current cell).
@ -49,17 +48,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
// https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30
ref.mOriginalCell = cell2.mId;
if (deleted)
{
// FIXME: how to mark the record deleted?
CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell,
mCells.getId (cellIndex));
messages.add (id, "Moved reference "+ref.mRefID+" is in DELE state");
continue;
}
// It is not always possibe to ignore moved references sub-record and
// calculate from coordinates. Some mods may place the ref in positions
// outside normal bounds, resulting in non sensical cell id's. This often
@ -91,7 +79,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
break;
}
if (deleted)
if (isDeleted)
{
if (iter==cache.end())
{
@ -99,7 +87,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
mCells.getId (cellIndex));
messages.add (id, "Attempt to delete a non-existing reference");
continue;
}
@ -107,7 +94,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool
Record<CellRef> record = getRecord (index);
if (record.mState==RecordBase::State_BaseOnly)
if (base)
{
removeRows (index, 1);
cache.erase (iter);

@ -837,61 +837,7 @@ const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (int index) con
void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, UniversalId::Type type)
{
std::string id = reader.getHNOString ("NAME");
int index = searchId (id);
if (reader.isNextSub ("DELE"))
{
reader.skipRecord();
if (index==-1)
{
// deleting a record that does not exist
// ignore it for now
/// \todo report the problem to the user
}
else if (base)
{
mData.erase (index, 1);
}
else
{
mData.getRecord (mData.globalToLocalIndex (index)).mState = RecordBase::State_Deleted;
}
}
else
{
if (index==-1)
{
// new record
int index = mData.getAppendIndex (type);
mData.appendRecord (type, id, base);
RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index);
mData.load (localIndex, reader, base);
mData.getRecord (localIndex).mState =
base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly;
}
else
{
// old record
RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index);
if (!base)
if (mData.getRecord (localIndex).mState==RecordBase::State_Erased)
throw std::logic_error ("attempt to access a deleted record");
mData.load (localIndex, reader, base);
if (!base)
mData.getRecord (localIndex).mState = RecordBase::State_Modified;
}
}
mData.load(reader, base, type);
}
int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const

@ -3,10 +3,20 @@
#include <cassert>
#include <memory>
#include <components/misc/stringops.hpp>
CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {}
std::string CSMWorld::RefIdData::getRecordId(const CSMWorld::RefIdData::LocalIndex &index) const
{
std::map<UniversalId::Type, RefIdDataContainerBase *>::const_iterator found =
mRecordContainers.find (index.second);
if (found == mRecordContainers.end())
throw std::logic_error ("invalid local index type");
return found->second->getId(index.first);
}
CSMWorld::RefIdData::RefIdData()
{
mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators));
@ -161,15 +171,27 @@ int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const
return index;
}
void CSMWorld::RefIdData::load (const LocalIndex& index, ESM::ESMReader& reader, bool base)
void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::UniversalId::Type type)
{
std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter =
mRecordContainers.find (index.second);
std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator found =
mRecordContainers.find (type);
if (iter==mRecordContainers.end())
throw std::logic_error ("invalid local index type");
if (found == mRecordContainers.end())
throw std::logic_error ("Invalid Referenceable ID type");
iter->second->load (index.first, reader, base);
int index = found->second->load(reader, base);
if (index != -1)
{
LocalIndex localIndex = LocalIndex(index, type);
if (base && getRecord(localIndex).mState == RecordBase::State_Deleted)
{
erase(localIndex, 1);
}
else
{
mIndex[Misc::StringUtils::lowerCase(getRecordId(localIndex))] = localIndex;
}
}
}
void CSMWorld::RefIdData::erase (const LocalIndex& index, int count)

@ -25,6 +25,8 @@
#include <components/esm/loadmisc.hpp>
#include <components/esm/esmwriter.hpp>
#include <components/misc/stringops.hpp>
#include "record.hpp"
#include "universalid.hpp"
@ -49,7 +51,8 @@ namespace CSMWorld
virtual void insertRecord (RecordBase& record) = 0;
virtual void load (int index, ESM::ESMReader& reader, bool base) = 0;
virtual int load (ESM::ESMReader& reader, bool base) = 0;
///< \return index of a loaded record or -1 if no record was loaded
virtual void erase (int index, int count) = 0;
@ -73,7 +76,8 @@ namespace CSMWorld
virtual void insertRecord (RecordBase& record);
virtual void load (int index, ESM::ESMReader& reader, bool base);
virtual int load (ESM::ESMReader& reader, bool base);
///< \return index of a loaded record or -1 if no record was loaded
virtual void erase (int index, int count);
@ -122,9 +126,58 @@ namespace CSMWorld
}
template<typename RecordT>
void RefIdDataContainer<RecordT>::load (int index, ESM::ESMReader& reader, bool base)
int RefIdDataContainer<RecordT>::load (ESM::ESMReader& reader, bool base)
{
(base ? mContainer.at (index).mBase : mContainer.at (index).mModified).load (reader);
RecordT record;
bool isDeleted = false;
record.load(reader, isDeleted);
int index = 0;
int numRecords = static_cast<int>(mContainer.size());
for (; index < numRecords; ++index)
{
if (Misc::StringUtils::ciEqual(mContainer[index].get().mId, record.mId))
{
break;
}
}
if (isDeleted)
{
if (index == numRecords)
{
// deleting a record that does not exist
// ignore it for now
/// \todo report the problem to the user
return -1;
}
// Flag the record as Deleted even for a base content file.
// RefIdData is responsible for its erasure.
mContainer[index].mState = RecordBase::State_Deleted;
}
else
{
if (index == numRecords)
{
appendRecord(record.mId, base);
if (base)
{
mContainer.back().mBase = record;
}
else
{
mContainer.back().mModified = record;
}
}
else if (!base)
{
mContainer[index].setModified(record);
}
}
return index;
}
template<typename RecordT>
@ -145,19 +198,14 @@ namespace CSMWorld
template<typename RecordT>
void RefIdDataContainer<RecordT>::save (int index, ESM::ESMWriter& writer) const
{
CSMWorld::RecordBase::State state = mContainer.at (index).mState;
Record<RecordT> record = mContainer.at(index);
if (state==CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly)
if (record.isModified() || record.mState == RecordBase::State_Deleted)
{
writer.startRecord (mContainer.at (index).mModified.sRecordId);
writer.writeHNCString ("NAME", getId (index));
mContainer.at (index).mModified.save (writer);
writer.endRecord (mContainer.at (index).mModified.sRecordId);
}
else if (state==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
RecordT esmRecord = record.get();
writer.startRecord(esmRecord.sRecordId);
esmRecord.save(writer, record.mState == RecordBase::State_Deleted);
writer.endRecord(esmRecord.sRecordId);
}
}
@ -198,6 +246,8 @@ namespace CSMWorld
void erase (const LocalIndex& index, int count);
///< Must not spill over into another type.
std::string getRecordId(const LocalIndex &index) const;
public:
RefIdData();
@ -221,7 +271,7 @@ namespace CSMWorld
int getAppendIndex (UniversalId::Type type) const;
void load (const LocalIndex& index, ESM::ESMReader& reader, bool base);
void load (ESM::ESMReader& reader, bool base, UniversalId::Type type);
int getSize() const;

@ -20,7 +20,7 @@ namespace CSMWorld
{
const IdCollection<Cell>& mCells;
virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader);
virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted);
public:
@ -29,9 +29,10 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT>
void SubCellCollection<ESXRecordT, IdAccessorT>::loadRecord (ESXRecordT& record,
ESM::ESMReader& reader)
ESM::ESMReader& reader,
bool& isDeleted)
{
record.load (reader, mCells);
record.load (reader, isDeleted, mCells);
}
template<typename ESXRecordT, typename IdAccessorT>

@ -475,7 +475,7 @@ namespace MWWorld
switch (store.find (ref.mRefID))
{
case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break;
case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break;
case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break;
case ESM::REC_APPA: mAppas.load(ref, deleted, store); break;
case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break;
case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break;

@ -96,33 +96,21 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener)
throw std::runtime_error(error.str());
}
} 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);
// DELE can also occur after the usual subrecords
if (esm.isNextSub("DELE")) {
esm.skipRecord();
it->second->eraseStatic(id);
continue;
RecordId id = it->second->load(esm);
if (id.mIsDeleted)
{
it->second->eraseStatic(id.mId);
continue;
}
if (n.val==ESM::REC_DIAL) {
dialogue = const_cast<ESM::Dialogue*>(mDialogs.find(id));
dialogue = const_cast<ESM::Dialogue*>(mDialogs.find(id.mId));
} else {
dialogue = 0;
}
// Insert the reference into the global lookup
if (!id.empty() && isCacheableRecord(n.val)) {
mIds[Misc::StringUtils::lowerCase (id)] = n.val;
if (!id.mId.empty() && isCacheableRecord(n.val)) {
mIds[Misc::StringUtils::lowerCase (id.mId)] = n.val;
}
}
listener->setProgress(static_cast<size_t>(esm.getFileOffset() / (float)esm.getFileSize() * 1000));
@ -195,13 +183,12 @@ void ESMStore::setUp()
case ESM::REC_LEVC:
{
std::string id = reader.getHNString ("NAME");
mStores[type]->read (reader, id);
RecordId id = mStores[type]->read (reader);
// FIXME: there might be stale dynamic IDs in mIds from an earlier savegame
// that really should be cleared instead of just overwritten
mIds[id] = type;
mIds[id.mId] = type;
}
if (type==ESM::REC_NPC_)

@ -13,7 +13,7 @@ namespace MWWorld
{
Globals::Collection::const_iterator Globals::find (const std::string& name) const
{
Collection::const_iterator iter = mVariables.find (name);
Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name));
if (iter==mVariables.end())
throw std::runtime_error ("unknown global variable: " + name);
@ -23,7 +23,7 @@ namespace MWWorld
Globals::Collection::iterator Globals::find (const std::string& name)
{
Collection::iterator iter = mVariables.find (name);
Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name));
if (iter==mVariables.end())
throw std::runtime_error ("unknown global variable: " + name);
@ -40,28 +40,28 @@ namespace MWWorld
for (MWWorld::Store<ESM::Global>::iterator iter = globals.begin(); iter!=globals.end();
++iter)
{
mVariables.insert (std::make_pair (iter->mId, iter->mValue));
mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (iter->mId), *iter));
}
}
const ESM::Variant& Globals::operator[] (const std::string& name) const
{
return find (name)->second;
return find (Misc::StringUtils::lowerCase (name))->second.mValue;
}
ESM::Variant& Globals::operator[] (const std::string& name)
{
return find (name)->second;
return find (Misc::StringUtils::lowerCase (name))->second.mValue;
}
char Globals::getType (const std::string& name) const
{
Collection::const_iterator iter = mVariables.find (name);
Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name));
if (iter==mVariables.end())
return ' ';
switch (iter->second.getType())
switch (iter->second.mValue.getType())
{
case ESM::VT_Short: return 's';
case ESM::VT_Long: return 'l';
@ -81,8 +81,7 @@ namespace MWWorld
for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter)
{
writer.startRecord (ESM::REC_GLOB);
writer.writeHNString ("NAME", iter->first);
iter->second.write (writer, ESM::Variant::Format_Global);
iter->second.save (writer);
writer.endRecord (ESM::REC_GLOB);
}
}
@ -91,14 +90,17 @@ namespace MWWorld
{
if (type==ESM::REC_GLOB)
{
std::string id = reader.getHNString ("NAME");
ESM::Global global;
bool isDeleted = false;
Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (id));
// This readRecord() method is used when reading a saved game.
// Deleted globals can't appear there, so isDeleted will be ignored here.
global.load(reader, isDeleted);
Misc::StringUtils::toLower(global.mId);
Collection::iterator iter = mVariables.find (global.mId);
if (iter!=mVariables.end())
iter->second.read (reader, ESM::Variant::Format_Global);
else
reader.skipRecord();
iter->second = global;
return true;
}

@ -8,7 +8,7 @@
#include <stdint.h>
#include <components/interpreter/types.hpp>
#include <components/esm/variant.hpp>
#include <components/esm/loadglob.hpp>
namespace ESM
{
@ -29,7 +29,7 @@ namespace MWWorld
{
private:
typedef std::map<std::string, ESM::Variant> Collection;
typedef std::map<std::string, ESM::Global> Collection;
Collection mVariables; // type, value

@ -42,6 +42,10 @@ namespace
namespace MWWorld
{
RecordId::RecordId(const std::string &id, bool isDeleted)
: mId(id), mIsDeleted(isDeleted)
{}
template<typename T>
IndexedStore<T>::IndexedStore()
{
@ -60,7 +64,9 @@ namespace MWWorld
void IndexedStore<T>::load(ESM::ESMReader &esm)
{
T record;
record.load(esm);
bool isDeleted = false;
record.load(esm, isDeleted);
// Try to overwrite existing record
std::pair<typename Static::iterator, bool> ret = mStatic.insert(std::make_pair(record.mIndex, record));
@ -178,16 +184,21 @@ namespace MWWorld
return ptr;
}
template<typename T>
void Store<T>::load(ESM::ESMReader &esm, const std::string &id)
RecordId Store<T>::load(ESM::ESMReader &esm)
{
std::string idLower = Misc::StringUtils::lowerCase(id);
T record;
bool isDeleted = false;
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(idLower, T()));
record.load(esm, isDeleted);
Misc::StringUtils::toLower(record.mId);
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(record.mId, record));
if (inserted.second)
mShared.push_back(&inserted.first->second);
else
inserted.first->second = record;
inserted.first->second.mId = idLower;
inserted.first->second.load(esm);
return RecordId(record.mId, isDeleted);
}
template<typename T>
void Store<T>::setUp()
@ -309,20 +320,21 @@ namespace MWWorld
++iter)
{
writer.startRecord (T::sRecordId);
writer.writeHNString ("NAME", iter->second.mId);
iter->second.save (writer);
writer.endRecord (T::sRecordId);
}
}
template<typename T>
void Store<T>::read(ESM::ESMReader& reader, const std::string& id)
RecordId Store<T>::read(ESM::ESMReader& reader)
{
T record;
record.mId = id;
record.load (reader);
bool isDeleted = false;
record.load (reader, isDeleted);
insert (record);
}
return RecordId(record.mId, isDeleted);
}
// LandTexture
//=========================================================================
@ -361,11 +373,12 @@ namespace MWWorld
assert(plugin < mStatic.size());
return mStatic[plugin].size();
}
void Store<ESM::LandTexture>::load(ESM::ESMReader &esm, const std::string &id, size_t plugin)
RecordId Store<ESM::LandTexture>::load(ESM::ESMReader &esm, size_t plugin)
{
ESM::LandTexture lt;
lt.load(esm);
lt.mId = id;
bool isDeleted = false;
lt.load(esm, isDeleted);
// Make sure we have room for the structure
if (plugin >= mStatic.size()) {
@ -377,10 +390,12 @@ namespace MWWorld
// Store it
ltexl[lt.mIndex] = lt;
return RecordId(lt.mId, isDeleted);
}
void Store<ESM::LandTexture>::load(ESM::ESMReader &esm, const std::string &id)
RecordId Store<ESM::LandTexture>::load(ESM::ESMReader &esm)
{
load(esm, id, esm.getIndex());
return load(esm, esm.getIndex());
}
Store<ESM::LandTexture>::iterator Store<ESM::LandTexture>::begin(size_t plugin) const
{
@ -392,7 +407,6 @@ namespace MWWorld
assert(plugin < mStatic.size());
return mStatic[plugin].end();
}
// Land
//=========================================================================
@ -440,10 +454,12 @@ namespace MWWorld
}
return ptr;
}
void Store<ESM::Land>::load(ESM::ESMReader &esm, const std::string &id)
RecordId Store<ESM::Land>::load(ESM::ESMReader &esm)
{
ESM::Land *ptr = new ESM::Land();
ptr->load(esm);
bool isDeleted = false;
ptr->load(esm, isDeleted);
// Same area defined in multiple plugins? -> last plugin wins
// Can't use search() because we aren't sorted yet - is there any other way to speed this up?
@ -458,6 +474,8 @@ namespace MWWorld
}
mStatic.push_back(ptr);
return RecordId("", isDeleted);
}
void Store<ESM::Land>::setUp()
{
@ -600,7 +618,7 @@ namespace MWWorld
mSharedExt.push_back(&(it->second));
}
}
void Store<ESM::Cell>::load(ESM::ESMReader &esm, const std::string &id)
RecordId Store<ESM::Cell>::load(ESM::ESMReader &esm)
{
// 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,
@ -608,13 +626,13 @@ namespace MWWorld
// are not available until both cells have been loaded at least partially!
// All cells have a name record, even nameless exterior cells.
std::string idLower = Misc::StringUtils::lowerCase(id);
ESM::Cell cell;
cell.mName = id;
bool isDeleted = false;
// Load the (x,y) coordinates of the cell, if it is an exterior cell,
// Load the (x,y) coordinates of the cell, if it is an exterior cell,
// so we can find the cell we need to merge with
cell.loadData(esm);
cell.loadNameAndData(esm, isDeleted);
std::string idLower = Misc::StringUtils::lowerCase(cell.mName);
if(cell.mData.mFlags & ESM::Cell::Interior)
{
@ -682,6 +700,8 @@ namespace MWWorld
mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell;
}
}
return RecordId(cell.mName, isDeleted);
}
Store<ESM::Cell>::iterator Store<ESM::Cell>::intBegin() const
{
@ -837,10 +857,12 @@ namespace MWWorld
{
mCells = &cells;
}
void Store<ESM::Pathgrid>::load(ESM::ESMReader &esm, const std::string &id)
RecordId Store<ESM::Pathgrid>::load(ESM::ESMReader &esm)
{
ESM::Pathgrid pathgrid;
pathgrid.load(esm);
bool isDeleted = false;
pathgrid.load(esm, isDeleted);
// Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell.
// For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name.
@ -862,6 +884,8 @@ namespace MWWorld
if (!ret.second)
ret.first->second = pathgrid;
}
return RecordId("", isDeleted);
}
size_t Store<ESM::Pathgrid>::getSize() const
{
@ -1013,51 +1037,29 @@ namespace MWWorld
}
template <>
void Store<ESM::Dialogue>::load(ESM::ESMReader &esm, const std::string &id) {
std::string idLower = Misc::StringUtils::lowerCase(id);
std::map<std::string, ESM::Dialogue>::iterator it = mStatic.find(idLower);
if (it == mStatic.end()) {
it = mStatic.insert( std::make_pair( idLower, ESM::Dialogue() ) ).first;
it->second.mId = id; // don't smash case here, as this line is printed
}
it->second.load(esm);
}
inline RecordId Store<ESM::Dialogue>::load(ESM::ESMReader &esm) {
// The original letter case of a dialogue ID is saved, because it's printed
ESM::Dialogue dialogue;
bool isDeleted = false;
dialogue.loadId(esm);
// Script
//=========================================================================
template <>
void Store<ESM::Script>::load(ESM::ESMReader &esm, const std::string &id) {
ESM::Script scpt;
scpt.load(esm);
Misc::StringUtils::toLower(scpt.mId);
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(scpt.mId, scpt));
if (inserted.second)
mShared.push_back(&inserted.first->second);
std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId);
std::map<std::string, ESM::Dialogue>::iterator found = mStatic.find(idLower);
if (found == mStatic.end())
{
dialogue.loadData(esm, isDeleted);
mStatic.insert(std::make_pair(idLower, dialogue));
}
else
inserted.first->second = scpt;
}
// StartScript
//=========================================================================
{
found->second.loadData(esm, isDeleted);
dialogue = found->second;
}
template <>
void Store<ESM::StartScript>::load(ESM::ESMReader &esm, const std::string &id)
{
ESM::StartScript s;
s.load(esm);
s.mId = Misc::StringUtils::toLower(s.mId);
std::pair<typename Static::iterator, bool> inserted = mStatic.insert(std::make_pair(s.mId, s));
if (inserted.second)
mShared.push_back(&inserted.first->second);
else
inserted.first->second = s;
return RecordId(dialogue.mId, isDeleted);
}
}
template class MWWorld::Store<ESM::Activator>;
@ -1082,7 +1084,7 @@ template class MWWorld::Store<ESM::Global>;
template class MWWorld::Store<ESM::Ingredient>;
template class MWWorld::Store<ESM::ItemLevList>;
//template class MWWorld::Store<ESM::Land>;
template class MWWorld::Store<ESM::LandTexture>;
//template class MWWorld::Store<ESM::LandTexture>;
template class MWWorld::Store<ESM::Light>;
template class MWWorld::Store<ESM::Lockpick>;
//template class MWWorld::Store<ESM::MagicEffect>;

@ -19,6 +19,14 @@ namespace Loading
namespace MWWorld
{
struct RecordId
{
std::string mId;
bool mIsDeleted;
RecordId(const std::string &id = "", bool isDeleted = false);
};
struct StoreBase
{
virtual ~StoreBase() {}
@ -28,14 +36,14 @@ namespace MWWorld
virtual size_t getSize() const = 0;
virtual int getDynamicSize() const { return 0; }
virtual void load(ESM::ESMReader &esm, const std::string &id) = 0;
virtual RecordId load(ESM::ESMReader &esm) = 0;
virtual bool eraseStatic(const std::string &id) {return false;}
virtual void clearDynamic() {}
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {}
virtual void read (ESM::ESMReader& reader, const std::string& id) {}
virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); }
///< Read into dynamic storage
};
@ -180,9 +188,9 @@ namespace MWWorld
bool erase(const std::string &id);
bool erase(const T &item);
void load(ESM::ESMReader &esm, const std::string &id);
RecordId load(ESM::ESMReader &esm);
void write(ESM::ESMWriter& writer, Loading::Listener& progress) const;
void read(ESM::ESMReader& reader, const std::string& id);
RecordId read(ESM::ESMReader& reader);
};
template <>
@ -205,8 +213,8 @@ namespace MWWorld
size_t getSize() const;
size_t getSize(size_t plugin) const;
void load(ESM::ESMReader &esm, const std::string &id, size_t plugin);
void load(ESM::ESMReader &esm, const std::string &id);
RecordId load(ESM::ESMReader &esm, size_t plugin);
RecordId load(ESM::ESMReader &esm);
iterator begin(size_t plugin) const;
iterator end(size_t plugin) const;
@ -231,7 +239,7 @@ namespace MWWorld
ESM::Land *search(int x, int y) const;
ESM::Land *find(int x, int y) const;
void load(ESM::ESMReader &esm, const std::string &id);
RecordId load(ESM::ESMReader &esm);
void setUp();
};
@ -281,7 +289,7 @@ namespace MWWorld
void setUp();
void load(ESM::ESMReader &esm, const std::string &id);
RecordId load(ESM::ESMReader &esm);
iterator intBegin() const;
iterator intEnd() const;
@ -323,7 +331,7 @@ namespace MWWorld
Store();
void setCells(Store<ESM::Cell>& cells);
void load(ESM::ESMReader &esm, const std::string &id);
RecordId load(ESM::ESMReader &esm);
size_t getSize() const;
void setUp();

@ -24,10 +24,10 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const
}
void ESM::CellRef::load (ESMReader& esm, bool wideRefNum)
void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum)
{
loadId(esm, wideRefNum);
loadData(esm);
loadData(esm, isDeleted);
}
void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum)
@ -39,71 +39,98 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum)
if (esm.isNextSub ("NAM0"))
esm.skipHSub();
blank();
mRefNum.load (esm, wideRefNum);
mRefID = esm.getHNString ("NAME");
}
void ESM::CellRef::loadData(ESMReader &esm)
void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted)
{
// Again, UNAM sometimes appears after NAME and sometimes later.
// Or perhaps this UNAM means something different?
mReferenceBlocked = -1;
esm.getHNOT (mReferenceBlocked, "UNAM");
mScale = 1.0;
esm.getHNOT (mScale, "XSCL");
mOwner = esm.getHNOString ("ANAM");
mGlobalVariable = esm.getHNOString ("BNAM");
mSoul = esm.getHNOString ("XSOL");
mFaction = esm.getHNOString ("CNAM");
mFactionRank = -2;
esm.getHNOT (mFactionRank, "INDX");
mGoldValue = 1;
mChargeInt = -1;
mEnchantmentCharge = -1;
esm.getHNOT (mEnchantmentCharge, "XCHG");
esm.getHNOT (mChargeInt, "INTV");
esm.getHNOT (mGoldValue, "NAM9");
isDeleted = false;
// Present for doors that teleport you to another cell.
if (esm.isNextSub ("DODT"))
bool isLoaded = false;
while (!isLoaded && esm.hasMoreSubs())
{
mTeleport = true;
esm.getHT (mDoorDest);
mDestCell = esm.getHNOString ("DNAM");
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'U','N','A','M'>::value:
esm.getHT(mReferenceBlocked);
break;
case ESM::FourCC<'X','S','C','L'>::value:
esm.getHT(mScale);
break;
case ESM::FourCC<'A','N','A','M'>::value:
mOwner = esm.getHString();
break;
case ESM::FourCC<'B','N','A','M'>::value:
mGlobalVariable = esm.getHString();
break;
case ESM::FourCC<'X','S','O','L'>::value:
mSoul = esm.getHString();
break;
case ESM::FourCC<'C','N','A','M'>::value:
mFaction = esm.getHString();
break;
case ESM::FourCC<'I','N','D','X'>::value:
esm.getHT(mFactionRank);
break;
case ESM::FourCC<'X','C','H','G'>::value:
esm.getHT(mEnchantmentCharge);
break;
case ESM::FourCC<'I','N','T','V'>::value:
esm.getHT(mChargeInt);
break;
case ESM::FourCC<'N','A','M','9'>::value:
esm.getHT(mGoldValue);
break;
case ESM::FourCC<'D','O','D','T'>::value:
esm.getHT(mDoorDest);
mTeleport = true;
break;
case ESM::FourCC<'D','N','A','M'>::value:
mDestCell = esm.getHString();
break;
case ESM::FourCC<'F','L','T','V'>::value:
esm.getHT(mLockLevel);
break;
case ESM::FourCC<'K','N','A','M'>::value:
mKey = esm.getHString();
break;
case ESM::FourCC<'T','N','A','M'>::value:
mTrap = esm.getHString();
break;
case ESM::FourCC<'D','A','T','A'>::value:
esm.getHT(mPos, 24);
break;
case ESM::FourCC<'N','A','M','0'>::value:
esm.skipHSub();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.cacheSubName();
isLoaded = true;
break;
}
}
else
mTeleport = false;
mLockLevel = 0; //Set to 0 to indicate no lock
esm.getHNOT (mLockLevel, "FLTV");
mKey = esm.getHNOString ("KNAM");
mTrap = esm.getHNOString ("TNAM");
esm.getHNOT (mReferenceBlocked, "UNAM");
if (esm.isNextSub("FLTV")) // no longer used
esm.skipHSub();
esm.getHNOT(mPos, "DATA", 24);
if (esm.isNextSub("NAM0"))
esm.skipHSub();
}
void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const
void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const
{
mRefNum.save (esm, wideRefNum);
esm.writeHNCString("NAME", mRefID);
if (isDeleted) {
esm.writeHNCString("DELE", "");
return;
}
if (mScale != 1.0) {
esm.writeHNT("XSCL", mScale);
}
@ -134,7 +161,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons
}
if (!inInventory && mLockLevel != 0) {
esm.writeHNT("FLTV", mLockLevel);
esm.writeHNT("FLTV", mLockLevel);
}
if (!inInventory)
@ -153,7 +180,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons
void ESM::CellRef::blank()
{
mRefNum.unset();
mRefID.clear();
mRefID.clear();
mScale = 1;
mOwner.clear();
mGlobalVariable.clear();
@ -169,7 +196,7 @@ void ESM::CellRef::blank()
mTrap.clear();
mReferenceBlocked = -1;
mTeleport = false;
for (int i=0; i<3; ++i)
{
mDoorDest.pos[i] = 0;

@ -34,7 +34,6 @@ namespace ESM
class CellRef
{
public:
// Reference number
// Note: Currently unused for items in containers
RefNum mRefNum;
@ -100,14 +99,14 @@ namespace ESM
Position mPos;
/// Calls loadId and loadData
void load (ESMReader& esm, bool wideRefNum = false);
void load (ESMReader& esm, bool &isDeleted, bool wideRefNum = false);
void loadId (ESMReader& esm, bool wideRefNum = false);
/// Implicitly called by load
void loadData (ESMReader& esm);
void loadData (ESMReader& esm, bool &isDeleted);
void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const;
void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false, bool isDeleted = false) const;
void blank();
};

@ -6,15 +6,48 @@
unsigned int ESM::DebugProfile::sRecordId = REC_DBGP;
void ESM::DebugProfile::load (ESMReader& esm)
void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted)
{
mDescription = esm.getHNString ("DESC");
mScriptText = esm.getHNString ("SCRP");
esm.getHNT (mFlags, "FLAG");
isDeleted = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
break;
case ESM::FourCC<'D','E','S','C'>::value:
mDescription = esm.getHString();
break;
case ESM::FourCC<'S','C','R','P'>::value:
mScriptText = esm.getHString();
break;
case ESM::FourCC<'F','L','A','G'>::value:
esm.getHT(mFlags);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
}
void ESM::DebugProfile::save (ESMWriter& esm) const
void ESM::DebugProfile::save (ESMWriter& esm, bool isDeleted) const
{
esm.writeHNCString ("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString ("DESC", mDescription);
esm.writeHNCString ("SCRP", mScriptText);
esm.writeHNT ("FLAG", mFlags);

@ -27,8 +27,8 @@ namespace ESM
unsigned int mFlags;
void load (ESMReader& esm);
void save (ESMWriter& esm) const;
void load (ESMReader& esm, bool &isDeleted);
void save (ESMWriter& esm, bool isDeleted = false) const;
/// Set record to default state (does not touch the ID).
void blank();

@ -187,6 +187,11 @@ bool ESMReader::peekNextSub(const char *name)
return mCtx.subName == name;
}
void ESMReader::cacheSubName()
{
mCtx.subCached = true;
}
// Read subrecord name. This gets called a LOT, so I've optimized it
// slightly.
void ESMReader::getSubName()
@ -276,6 +281,7 @@ void ESMReader::skipRecord()
{
skip(mCtx.leftRec);
mCtx.leftRec = 0;
mCtx.subCached = false;
}
void ESMReader::getRecHeader(uint32_t &flags)

@ -185,6 +185,9 @@ public:
bool peekNextSub(const char* name);
// Store the current subrecord name for the next call of getSubName()
void cacheSubName();
// Read subrecord name. This gets called a LOT, so I've optimized it
// slightly.
void getSubName();

@ -6,14 +6,46 @@
unsigned int ESM::Filter::sRecordId = REC_FILT;
void ESM::Filter::load (ESMReader& esm)
void ESM::Filter::load (ESMReader& esm, bool &isDeleted)
{
mFilter = esm.getHNString ("FILT");
mDescription = esm.getHNString ("DESC");
isDeleted = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
break;
case ESM::FourCC<'F','I','L','T'>::value:
mFilter = esm.getHString();
break;
case ESM::FourCC<'D','E','S','C'>::value:
mDescription = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
}
void ESM::Filter::save (ESMWriter& esm) const
void ESM::Filter::save (ESMWriter& esm, bool isDeleted) const
{
esm.writeHNCString ("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString ("FILT", mFilter);
esm.writeHNCString ("DESC", mDescription);
}

@ -18,8 +18,8 @@ namespace ESM
std::string mFilter;
void load (ESMReader& esm);
void save (ESMWriter& esm) const;
void load (ESMReader& esm, bool &isDeleted);
void save (ESMWriter& esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,14 +8,20 @@ namespace ESM
{
unsigned int Activator::sRecordId = REC_ACTI;
void Activator::load(ESMReader &esm)
void Activator::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
bool hasName = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -25,13 +31,29 @@ namespace ESM
case ESM::FourCC<'S','C','R','I'>::value:
mScript = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasName)
esm.fail("Missing NAME subrecord");
}
void Activator::save(ESMWriter &esm) const
void Activator::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("SCRI", mScript);

@ -17,8 +17,8 @@ struct Activator
std::string mId, mName, mScript, mModel;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,16 +8,23 @@ namespace ESM
{
unsigned int Potion::sRecordId = REC_ALCH;
void Potion::load(ESMReader &esm)
void Potion::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
mEffects.mList.clear();
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -37,15 +44,31 @@ namespace ESM
case ESM::FourCC<'E','N','A','M'>::value:
mEffects.add(esm);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
esm.fail("Missing ALDT");
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing ALDT subrecord");
}
void Potion::save(ESMWriter &esm) const
void Potion::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("TEXT", mIcon);
esm.writeHNOCString("SCRI", mScript);

@ -33,8 +33,8 @@ struct Potion
std::string mId, mName, mModel, mIcon, mScript;
EffectList mEffects;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,47 +8,69 @@ namespace ESM
{
unsigned int Apparatus::sRecordId = REC_APPA;
void Apparatus::load(ESMReader &esm)
{
bool hasData = false;
while (esm.hasMoreSubs())
void Apparatus::load(ESMReader &esm, bool &isDeleted)
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
isDeleted = false;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
case ESM::FourCC<'F','N','A','M'>::value:
mName = esm.getHString();
break;
case ESM::FourCC<'A','A','D','T'>::value:
esm.getHT(mData);
hasData = true;
break;
case ESM::FourCC<'S','C','R','I'>::value:
mScript = esm.getHString();
break;
case ESM::FourCC<'I','T','E','X'>::value:
mIcon = esm.getHString();
break;
default:
esm.fail("Unknown subrecord");
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
case ESM::FourCC<'F','N','A','M'>::value:
mName = esm.getHString();
break;
case ESM::FourCC<'A','A','D','T'>::value:
esm.getHT(mData);
hasData = true;
break;
case ESM::FourCC<'S','C','R','I'>::value:
mScript = esm.getHString();
break;
case ESM::FourCC<'I','T','E','X'>::value:
mIcon = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing AADT subrecord");
}
if (!hasData)
esm.fail("Missing AADT");
}
void Apparatus::save(ESMWriter &esm) const
{
esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mName);
esm.writeHNT("AADT", mData, 16);
esm.writeHNOCString("SCRI", mScript);
esm.writeHNCString("ITEX", mIcon);
}
void Apparatus::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNCString("FNAM", mName);
esm.writeHNT("AADT", mData, 16);
esm.writeHNOCString("SCRI", mScript);
esm.writeHNCString("ITEX", mIcon);
}
void Apparatus::blank()
{

@ -38,8 +38,8 @@ struct Apparatus
AADTstruct mData;
std::string mId, mModel, mIcon, mScript, mName;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -38,16 +38,23 @@ namespace ESM
unsigned int Armor::sRecordId = REC_ARMO;
void Armor::load(ESMReader &esm)
void Armor::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
mParts.mParts.clear();
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -70,16 +77,32 @@ namespace ESM
case ESM::FourCC<'I','N','D','X'>::value:
mParts.add(esm);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing CTDT subrecord");
}
void Armor::save(ESMWriter &esm) const
void Armor::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("SCRI", mScript);

@ -96,8 +96,8 @@ struct Armor
std::string mId, mName, mModel, mIcon, mScript, mEnchant;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,40 +8,61 @@ namespace ESM
{
unsigned int BodyPart::sRecordId = REC_BODY;
void BodyPart::load(ESMReader &esm)
{
bool hasData = false;
while (esm.hasMoreSubs())
void BodyPart::load(ESMReader &esm, bool &isDeleted)
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
isDeleted = false;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
case ESM::FourCC<'F','N','A','M'>::value:
mRace = esm.getHString();
break;
case ESM::FourCC<'B','Y','D','T'>::value:
esm.getHT(mData, 4);
hasData = true;
break;
default:
esm.fail("Unknown subrecord");
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
case ESM::FourCC<'F','N','A','M'>::value:
mRace = esm.getHString();
break;
case ESM::FourCC<'B','Y','D','T'>::value:
esm.getHT(mData, 4);
hasData = true;
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing BYDT subrecord");
}
if (!hasData)
esm.fail("Missing BYDT subrecord");
}
void BodyPart::save(ESMWriter &esm) const
{
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mRace);
esm.writeHNT("BYDT", mData, 4);
}
void BodyPart::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mRace);
esm.writeHNT("BYDT", mData, 4);
}
void BodyPart::blank()
{

@ -60,8 +60,8 @@ struct BodyPart
BYDTstruct mData;
std::string mId, mModel, mRace;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,15 +8,21 @@ namespace ESM
{
unsigned int Book::sRecordId = REC_BOOK;
void Book::load(ESMReader &esm)
void Book::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -39,15 +45,31 @@ namespace ESM
case ESM::FourCC<'T','E','X','T'>::value:
mText = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing BKDT subrecord");
}
void Book::save(ESMWriter &esm) const
void Book::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNT("BKDT", mData, 20);

@ -28,8 +28,8 @@ struct Book
std::string mName, mModel, mIcon, mScript, mEnchant, mText;
std::string mId;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,41 +8,63 @@ namespace ESM
{
unsigned int BirthSign::sRecordId = REC_BSGN;
void BirthSign::load(ESMReader &esm)
{
mPowers.mList.clear();
while (esm.hasMoreSubs())
void BirthSign::load(ESMReader &esm, bool &isDeleted)
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
isDeleted = false;
mPowers.mList.clear();
bool hasName = false;
while (esm.hasMoreSubs())
{
case ESM::FourCC<'F','N','A','M'>::value:
mName = esm.getHString();
break;
case ESM::FourCC<'T','N','A','M'>::value:
mTexture = esm.getHString();
break;
case ESM::FourCC<'D','E','S','C'>::value:
mDescription = esm.getHString();
break;
case ESM::FourCC<'N','P','C','S'>::value:
mPowers.add(esm);
break;
default:
esm.fail("Unknown subrecord");
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'F','N','A','M'>::value:
mName = esm.getHString();
break;
case ESM::FourCC<'T','N','A','M'>::value:
mTexture = esm.getHString();
break;
case ESM::FourCC<'D','E','S','C'>::value:
mDescription = esm.getHString();
break;
case ESM::FourCC<'N','P','C','S'>::value:
mPowers.add(esm);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasName)
esm.fail("Missing NAME subrecord");
}
}
void BirthSign::save(ESMWriter &esm) const
{
esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("TNAM", mTexture);
esm.writeHNOCString("DESC", mDescription);
void BirthSign::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
mPowers.save(esm);
}
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("TNAM", mTexture);
esm.writeHNOCString("DESC", mDescription);
mPowers.save(esm);
}
void BirthSign::blank()
{

@ -22,8 +22,8 @@ struct BirthSign
// List of powers and abilities that come with this birth sign.
SpellList mPowers;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID/index).

@ -52,173 +52,200 @@ namespace ESM
return ref.mRefNum == refNum;
}
void Cell::load(ESMReader &esm, bool saveContext)
{
loadData(esm);
loadCell(esm, saveContext);
}
void Cell::loadCell(ESMReader &esm, bool saveContext)
{
mRefNumCounter = 0;
void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext)
{
loadNameAndData(esm, isDeleted);
loadCell(esm, saveContext);
}
if (mData.mFlags & Interior)
void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted)
{
// Interior cells
if (esm.isNextSub("INTV"))
{
int waterl;
esm.getHT(waterl);
mWater = (float) waterl;
mWaterInt = true;
}
else if (esm.isNextSub("WHGT"))
isDeleted = false;
blank();
bool hasData = false;
bool isLoaded = false;
while (!isLoaded && esm.hasMoreSubs())
{
esm.getHT(mWater);
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mName = esm.getHString();
break;
case ESM::FourCC<'D','A','T','A'>::value:
esm.getHT(mData, 12);
hasData = true;
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.cacheSubName();
isLoaded = true;
break;
}
}
// Quasi-exterior cells have a region (which determines the
// weather), pure interior cells have ambient lighting
// instead.
if (mData.mFlags & QuasiEx)
mRegion = esm.getHNOString("RGNN");
else if (esm.isNextSub("AMBI"))
esm.getHT(mAmbi);
if (!hasData)
esm.fail("Missing DATA subrecord");
}
else
void Cell::loadCell(ESMReader &esm, bool saveContext)
{
// Exterior cells
mRegion = esm.getHNOString("RGNN");
bool isLoaded = false;
while (!isLoaded && esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'I','N','T','V'>::value:
int waterl;
esm.getHT(waterl);
mWater = static_cast<float>(waterl);
mWaterInt = true;
break;
case ESM::FourCC<'W','H','G','T'>::value:
esm.getHT(mWater);
mWaterInt = false;
break;
case ESM::FourCC<'A','M','B','I'>::value:
esm.getHT(mAmbi);
break;
case ESM::FourCC<'R','G','N','N'>::value:
mRegion = esm.getHString();
break;
case ESM::FourCC<'N','A','M','5'>::value:
esm.getHT(mMapColor);
break;
case ESM::FourCC<'N','A','M','0'>::value:
esm.getHT(mRefNumCounter);
break;
default:
esm.cacheSubName();
isLoaded = true;
break;
}
}
mMapColor = 0;
esm.getHNOT(mMapColor, "NAM5");
}
if (esm.isNextSub("NAM0")) {
esm.getHT(mRefNumCounter);
if (saveContext)
{
mContextList.push_back(esm.getContext());
esm.skipRecord();
}
}
if (saveContext) {
void Cell::postLoad(ESMReader &esm)
{
// Save position of the cell references and move on
mContextList.push_back(esm.getContext());
esm.skipRecord();
}
}
void Cell::loadData(ESMReader &esm)
{
// 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();
}
esm.getHNT(mData, "DATA", 12);
}
void Cell::postLoad(ESMReader &esm)
{
// Save position of the cell references and move on
mContextList.push_back(esm.getContext());
esm.skipRecord();
}
void Cell::save(ESMWriter &esm) const
{
esm.writeHNT("DATA", mData, 12);
if (mData.mFlags & Interior)
void Cell::save(ESMWriter &esm, bool isDeleted) const
{
if (mWaterInt) {
int water =
(mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
esm.writeHNT("INTV", water);
} else {
esm.writeHNT("WHGT", mWater);
esm.writeHNOCString("NAME", mName);
esm.writeHNT("DATA", mData, 12);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
if (mData.mFlags & QuasiEx)
esm.writeHNOCString("RGNN", mRegion);
if (mData.mFlags & Interior)
{
if (mWaterInt) {
int water =
(mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
esm.writeHNT("INTV", water);
} else {
esm.writeHNT("WHGT", mWater);
}
if (mData.mFlags & QuasiEx)
esm.writeHNOCString("RGNN", mRegion);
else
esm.writeHNT("AMBI", mAmbi, 16);
}
else
esm.writeHNT("AMBI", mAmbi, 16);
}
else
{
esm.writeHNOCString("RGNN", mRegion);
if (mMapColor != 0)
esm.writeHNT("NAM5", mMapColor);
}
if (mRefNumCounter != 0)
esm.writeHNT("NAM0", mRefNumCounter);
}
void Cell::restore(ESMReader &esm, int iCtx) const
{
esm.restoreContext(mContextList.at (iCtx));
}
{
esm.writeHNOCString("RGNN", mRegion);
if (mMapColor != 0)
esm.writeHNT("NAM5", mMapColor);
}
std::string Cell::getDescription() const
{
if (mData.mFlags & Interior)
{
return mName;
if (mRefNumCounter != 0)
esm.writeHNT("NAM0", mRefNumCounter);
}
else
void Cell::restore(ESMReader &esm, int iCtx) const
{
std::ostringstream stream;
stream << mData.mX << ", " << mData.mY;
return stream.str();
esm.restoreContext(mContextList.at (iCtx));
}
}
bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted, bool ignoreMoves, MovedCellRef *mref)
{
// 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"))
std::string Cell::getDescription() const
{
if (ignoreMoves)
if (mData.mFlags & Interior)
{
esm.getHT (mref->mRefNum.mIndex);
esm.getHNOT (mref->mTarget, "CNDT");
adjustRefNum (mref->mRefNum, esm);
return mName;
}
else
{
// skip rest of cell record (moved references), they are handled elsewhere
esm.skipRecord(); // skip MVRF, CNDT
return false;
std::ostringstream stream;
stream << mData.mX << ", " << mData.mY;
return stream.str();
}
}
ref.load (esm);
bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool &isDeleted, bool ignoreMoves, MovedCellRef *mref)
{
isDeleted = false;
// Identify references belonging to a parent file and adapt the ID accordingly.
adjustRefNum (ref.mRefNum, esm);
// TODO: Try and document reference numbering, I don't think this has been done anywhere else.
if (!esm.hasMoreSubs())
return false;
if (esm.isNextSub("DELE"))
{
esm.skipHSub();
deleted = true;
}
else
deleted = 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 (ignoreMoves)
{
esm.getHT (mref->mRefNum.mIndex);
esm.getHNOT (mref->mTarget, "CNDT");
adjustRefNum (mref->mRefNum, esm);
}
else
{
// skip rest of cell record (moved references), they are handled elsewhere
esm.skipRecord(); // skip MVRF, CNDT
return false;
}
}
return true;
}
if (esm.peekNextSub("FRMR"))
{
ref.load (esm, isDeleted);
bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
{
esm.getHT(mref.mRefNum.mIndex);
esm.getHNOT(mref.mTarget, "CNDT");
// Identify references belonging to a parent file and adapt the ID accordingly.
adjustRefNum (ref.mRefNum, esm);
return true;
}
return false;
}
adjustRefNum (mref.mRefNum, esm);
bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref)
{
esm.getHT(mref.mRefNum.mIndex);
esm.getHNOT(mref.mTarget, "CNDT");
return true;
}
adjustRefNum (mref.mRefNum, esm);
return true;
}
void Cell::blank()
{

@ -116,11 +116,11 @@ struct Cell
// 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, bool saveContext = true); // Load everything (except references)
void loadData(ESMReader &esm); // Load DATAstruct only
void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except DATAstruct and references
void load(ESMReader &esm, bool &isDeleted, bool saveContext = true); // Load everything (except references)
void loadNameAndData(ESMReader &esm, bool &isDeleted); // Load NAME and DATAstruct
void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references
void save(ESMWriter &esm) const;
void save(ESMWriter &esm, bool isDeleted = false) const;
bool isExterior() const
{
@ -159,8 +159,11 @@ struct Cell
reuse one memory location without blanking it between calls.
*/
/// \param ignoreMoves ignore MVRF record and read reference like a regular CellRef.
static bool getNextRef(ESMReader &esm,
CellRef &ref, bool& deleted, bool ignoreMoves = false, MovedCellRef *mref = 0);
static bool getNextRef(ESMReader &esm,
CellRef &ref,
bool &isDeleted,
bool ignoreMoves = false,
MovedCellRef *mref = 0);
/* This fetches an MVRF record, which is used to track moved references.
* Since they are comparably rare, we use a separate method for this.

@ -22,7 +22,6 @@ namespace ESM
"sSpecializationStealth"
};
int& Class::CLDTstruct::getSkill (int index, bool major)
{
if (index<0 || index>=5)
@ -39,15 +38,21 @@ namespace ESM
return mSkills[index][major ? 1 : 0];
}
void Class::load(ESMReader &esm)
void Class::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'F','N','A','M'>::value:
mName = esm.getHString();
break;
@ -60,15 +65,31 @@ namespace ESM
case ESM::FourCC<'D','E','S','C'>::value:
mDescription = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing CLDT subrecord");
}
void Class::save(ESMWriter &esm) const
void Class::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNOCString("FNAM", mName);
esm.writeHNT("CLDT", mData, 60);
esm.writeHNOString("DESC", mDescription);

@ -73,8 +73,8 @@ struct Class
std::string mId, mName, mDescription;
CLDTstruct mData;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID/index).

@ -8,16 +8,23 @@ namespace ESM
{
unsigned int Clothing::sRecordId = REC_CLOT;
void Clothing::load(ESMReader &esm)
void Clothing::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
mParts.mParts.clear();
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -40,16 +47,32 @@ namespace ESM
case ESM::FourCC<'I','N','D','X'>::value:
mParts.add(esm);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing CTDT subrecord");
}
void Clothing::save(ESMWriter &esm) const
void Clothing::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNT("CTDT", mData, 12);

@ -48,8 +48,8 @@ struct Clothing
std::string mId, mName, mModel, mIcon, mEnchant, mScript;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -24,17 +24,24 @@ namespace ESM
unsigned int Container::sRecordId = REC_CONT;
void Container::load(ESMReader &esm)
void Container::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
mInventory.mList.clear();
bool hasName = false;
bool hasWeight = false;
bool hasFlags = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -59,18 +66,34 @@ namespace ESM
case ESM::FourCC<'N','P','C','O'>::value:
mInventory.add(esm);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasWeight)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasWeight && !isDeleted)
esm.fail("Missing CNDT subrecord");
if (!hasFlags)
if (!hasFlags && !isDeleted)
esm.fail("Missing FLAG subrecord");
}
void Container::save(ESMWriter &esm) const
void Container::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNT("CNDT", mWeight, 4);

@ -52,8 +52,8 @@ struct Container
int mFlags;
InventoryList mInventory;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,8 +8,10 @@ namespace ESM {
unsigned int Creature::sRecordId = REC_CREA;
void Creature::load(ESMReader &esm)
void Creature::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
mPersistent = (esm.getRecordFlags() & 0x0400) != 0;
mAiPackage.mList.clear();
@ -19,14 +21,19 @@ namespace ESM {
mScale = 1.f;
mHasAI = false;
bool hasName = false;
bool hasNpdt = false;
bool hasFlags = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -72,18 +79,34 @@ namespace ESM {
case AI_CNDT:
mAiPackage.add(esm);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasNpdt)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasNpdt && !isDeleted)
esm.fail("Missing NPDT subrecord");
if (!hasFlags)
if (!hasFlags && !isDeleted)
esm.fail("Missing FLAG subrecord");
}
void Creature::save(ESMWriter &esm) const
void Creature::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("CNAM", mOriginal);
esm.writeHNOCString("FNAM", mName);

@ -91,7 +91,6 @@ struct Creature
InventoryList mInventory;
SpellList mSpells;
bool mHasAI;
AIData mAiData;
AIPackageList mAiPackage;
@ -99,8 +98,8 @@ struct Creature
const std::vector<Transport::Dest>& getTransport() const;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -10,121 +10,150 @@ namespace ESM
{
unsigned int Dialogue::sRecordId = REC_DIAL;
void Dialogue::load(ESMReader &esm)
{
esm.getSubNameIs("DATA");
esm.getSubHeader();
int si = esm.getSubSize();
if (si == 1)
esm.getT(mType);
else if (si == 4)
void Dialogue::load(ESMReader &esm, bool &isDeleted)
{
// These are just markers, their values are not used.
int i;
esm.getT(i);
esm.getHNT(i, "DELE");
mType = Deleted;
loadId(esm);
loadData(esm, isDeleted);
}
else
esm.fail("Unknown sub record size");
}
void Dialogue::save(ESMWriter &esm) const
{
if (mType != Deleted)
esm.writeHNT("DATA", mType);
else
void Dialogue::loadId(ESMReader &esm)
{
esm.writeHNT("DATA", (int)1);
esm.writeHNT("DELE", (int)1);
mId = esm.getHNString("NAME");
}
}
void Dialogue::blank()
{
mInfo.clear();
}
void Dialogue::readInfo(ESMReader &esm, bool merge)
{
const std::string& id = esm.getHNOString("INAM");
if (!merge || mInfo.empty())
void Dialogue::loadData(ESMReader &esm, bool &isDeleted)
{
ESM::DialInfo info;
info.mId = id;
info.load(esm);
mLookup[id] = mInfo.insert(mInfo.end(), info);
return;
isDeleted = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'D','A','T','A'>::value:
{
esm.getSubHeader();
int size = esm.getSubSize();
if (size == 1)
{
esm.getT(mType);
}
else
{
esm.skip(size);
}
break;
}
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
mType = Unknown;
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
}
ESM::Dialogue::InfoContainer::iterator it = mInfo.end();
std::map<std::string, ESM::Dialogue::InfoContainer::iterator>::iterator lookup;
lookup = mLookup.find(id);
ESM::DialInfo info;
if (lookup != mLookup.end())
{
it = lookup->second;
// Merge with existing record. Only the subrecords that are present in
// the new record will be overwritten.
it->load(esm);
info = *it;
// Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record
mInfo.erase(it);
mLookup.erase(lookup);
}
else
void Dialogue::save(ESMWriter &esm, bool isDeleted) const
{
info.mId = id;
info.load(esm);
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
}
else
{
esm.writeHNT("DATA", mType);
}
}
if (info.mNext.empty())
{
mLookup[id] = mInfo.insert(mInfo.end(), info);
return;
}
if (info.mPrev.empty())
void Dialogue::blank()
{
mLookup[id] = mInfo.insert(mInfo.begin(), info);
return;
mInfo.clear();
}
lookup = mLookup.find(info.mPrev);
if (lookup != mLookup.end())
void Dialogue::readInfo(ESMReader &esm, bool merge)
{
it = lookup->second;
ESM::DialInfo info;
info.loadId(esm);
mLookup[id] = mInfo.insert(++it, info);
return;
}
bool isDeleted = false;
if (!merge || mInfo.empty())
{
info.loadData(esm, isDeleted);
mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted);
lookup = mLookup.find(info.mNext);
if (lookup != mLookup.end())
{
it = lookup->second;
return;
}
mLookup[id] = mInfo.insert(it, info);
return;
}
InfoContainer::iterator it = mInfo.end();
std::cerr << "Failed to insert info " << id << std::endl;
}
LookupMap::iterator lookup;
lookup = mLookup.find(info.mId);
void Dialogue::clearDeletedInfos()
{
for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); )
{
if (it->mQuestStatus == DialInfo::QS_Deleted)
it = mInfo.erase(it);
if (lookup != mLookup.end())
{
it = lookup->second.first;
// Merge with existing record. Only the subrecords that are present in
// the new record will be overwritten.
it->loadData(esm, isDeleted);
info = *it;
// Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record
mInfo.erase(it);
mLookup.erase(lookup);
}
else
++it;
{
info.loadData(esm, isDeleted);
}
if (info.mNext.empty())
{
mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted);
return;
}
if (info.mPrev.empty())
{
mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted);
return;
}
lookup = mLookup.find(info.mPrev);
if (lookup != mLookup.end())
{
it = lookup->second.first;
mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted);
return;
}
lookup = mLookup.find(info.mNext);
if (lookup != mLookup.end())
{
it = lookup->second.first;
mLookup[info.mId] = std::make_pair(mInfo.insert(it, info), isDeleted);
return;
}
std::cerr << "Failed to insert info " << info.mId << std::endl;
}
}
void Dialogue::clearDeletedInfos()
{
LookupMap::const_iterator current = mLookup.begin();
LookupMap::const_iterator end = mLookup.end();
for (; current != end; ++current)
{
if (current->second.second)
{
mInfo.erase(current->second.first);
}
}
mLookup.clear();
}
}

@ -31,7 +31,7 @@ struct Dialogue
Greeting = 2,
Persuasion = 3,
Journal = 4,
Deleted = -1
Unknown = -1 // Used for deleted dialogues
};
std::string mId;
@ -39,17 +39,24 @@ struct Dialogue
typedef std::list<DialInfo> InfoContainer;
typedef std::map<std::string, InfoContainer::iterator> LookupMap;
// Parameters: Info ID, (Info iterator, Deleted flag)
typedef std::map<std::string, std::pair<InfoContainer::iterator, bool> > LookupMap;
InfoContainer mInfo;
// This is only used during the loading phase to speed up DialInfo merging.
LookupMap mLookup;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
///< Loads all sub-records of Dialogue record
void loadId(ESMReader &esm);
///< Loads NAME sub-record of Dialogue record
void loadData(ESMReader &esm, bool &isDeleted);
///< Loads all sub-records of Dialogue record, except NAME sub-record
/// Remove all INFOs marked as QS_Deleted from mInfos.
void save(ESMWriter &esm, bool isDeleted = false) const;
/// Remove all INFOs that are deleted
void clearDeletedInfos();
/// Read the next info record

@ -8,14 +8,20 @@ namespace ESM
{
unsigned int Door::sRecordId = REC_DOOR;
void Door::load(ESMReader &esm)
void Door::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
bool hasName = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -31,14 +37,30 @@ namespace ESM
case ESM::FourCC<'A','N','A','M'>::value:
mCloseSound = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasName)
esm.fail("Missing NAME subrecord");
}
void Door::save(ESMWriter &esm) const
void Door::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("SCRI", mScript);

@ -17,8 +17,8 @@ struct Door
std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,37 +8,58 @@ namespace ESM
{
unsigned int Enchantment::sRecordId = REC_ENCH;
void Enchantment::load(ESMReader &esm)
{
mEffects.mList.clear();
bool hasData = false;
while (esm.hasMoreSubs())
void Enchantment::load(ESMReader &esm, bool &isDeleted)
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
isDeleted = false;
mEffects.mList.clear();
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
case ESM::FourCC<'E','N','D','T'>::value:
esm.getHT(mData, 16);
hasData = true;
break;
case ESM::FourCC<'E','N','A','M'>::value:
mEffects.add(esm);
break;
default:
esm.fail("Unknown subrecord");
break;
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'E','N','D','T'>::value:
esm.getHT(mData, 16);
hasData = true;
break;
case ESM::FourCC<'E','N','A','M'>::value:
mEffects.add(esm);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing ENDT subrecord");
}
if (!hasData)
esm.fail("Missing ENDT subrecord");
}
void Enchantment::save(ESMWriter &esm) const
{
esm.writeHNT("ENDT", mData, 16);
mEffects.save(esm);
}
void Enchantment::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNT("ENDT", mData, 16);
mEffects.save(esm);
}
void Enchantment::blank()
{

@ -42,8 +42,8 @@ struct Enchantment
ENDTstruct mData;
EffectList mEffects;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -26,69 +26,92 @@ namespace ESM
return mSkills[index];
}
void Faction::load(ESMReader &esm)
{
mReactions.clear();
for (int i=0;i<10;++i)
mRanks[i].clear();
int rankCounter=0;
bool hasData = false;
while (esm.hasMoreSubs())
void Faction::load(ESMReader &esm, bool &isDeleted)
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
isDeleted = false;
mReactions.clear();
for (int i=0;i<10;++i)
mRanks[i].clear();
int rankCounter = 0;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
case ESM::FourCC<'F','N','A','M'>::value:
mName = esm.getHString();
break;
case ESM::FourCC<'R','N','A','M'>::value:
if (rankCounter >= 10)
esm.fail("Rank out of range");
mRanks[rankCounter++] = esm.getHString();
break;
case ESM::FourCC<'F','A','D','T'>::value:
esm.getHT(mData, 240);
if (mData.mIsHidden > 1)
esm.fail("Unknown flag!");
hasData = true;
break;
case ESM::FourCC<'A','N','A','M'>::value:
esm.getSubName();
switch (esm.retSubName().val)
{
std::string faction = esm.getHString();
int reaction;
esm.getHNT(reaction, "INTV");
mReactions[faction] = reaction;
break;
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'F','N','A','M'>::value:
mName = esm.getHString();
break;
case ESM::FourCC<'R','N','A','M'>::value:
if (rankCounter >= 10)
esm.fail("Rank out of range");
mRanks[rankCounter++] = esm.getHString();
break;
case ESM::FourCC<'F','A','D','T'>::value:
esm.getHT(mData, 240);
if (mData.mIsHidden > 1)
esm.fail("Unknown flag!");
hasData = true;
break;
case ESM::FourCC<'A','N','A','M'>::value:
{
std::string faction = esm.getHString();
int reaction;
esm.getHNT(reaction, "INTV");
mReactions[faction] = reaction;
break;
}
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
default:
esm.fail("Unknown subrecord");
}
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing FADT subrecord");
}
if (!hasData)
esm.fail("Missing FADT subrecord");
}
void Faction::save(ESMWriter &esm) const
{
esm.writeHNOCString("FNAM", mName);
for (int i = 0; i < 10; i++)
void Faction::save(ESMWriter &esm, bool isDeleted) const
{
if (mRanks[i].empty())
break;
esm.writeHNCString("NAME", mId);
esm.writeHNString("RNAM", mRanks[i], 32);
}
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNT("FADT", mData, 240);
esm.writeHNOCString("FNAM", mName);
for (std::map<std::string, int>::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it)
{
esm.writeHNString("ANAM", it->first);
esm.writeHNT("INTV", it->second);
for (int i = 0; i < 10; i++)
{
if (mRanks[i].empty())
break;
esm.writeHNString("RNAM", mRanks[i], 32);
}
esm.writeHNT("FADT", mData, 240);
for (std::map<std::string, int>::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it)
{
esm.writeHNString("ANAM", it->first);
esm.writeHNT("INTV", it->second);
}
}
}
void Faction::blank()
{

@ -62,8 +62,8 @@ struct Faction
// Name of faction ranks (may be empty for NPC factions)
std::string mRanks[10];
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID/index).

@ -1,19 +1,42 @@
#include "loadglob.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "defs.hpp"
namespace ESM
{
unsigned int Global::sRecordId = REC_GLOB;
void Global::load (ESMReader &esm)
void Global::load (ESMReader &esm, bool &isDeleted)
{
mValue.read (esm, ESM::Variant::Format_Global);
isDeleted = false;
mId = esm.getHNString ("NAME");
if (esm.isNextSub ("DELE"))
{
esm.skipHSub();
isDeleted = true;
}
else
{
mValue.read (esm, ESM::Variant::Format_Global);
}
}
void Global::save (ESMWriter &esm) const
void Global::save (ESMWriter &esm, bool isDeleted) const
{
mValue.write (esm, ESM::Variant::Format_Global);
esm.writeHNCString ("NAME", mId);
if (isDeleted)
{
esm.writeHNCString ("DELE", "");
}
else
{
mValue.write (esm, ESM::Variant::Format_Global);
}
}
void Global::blank()

@ -24,8 +24,8 @@ struct Global
std::string mId;
Variant mValue;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -1,18 +1,24 @@
#include "loadgmst.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "defs.hpp"
namespace ESM
{
unsigned int GameSetting::sRecordId = REC_GMST;
void GameSetting::load (ESMReader &esm)
void GameSetting::load (ESMReader &esm, bool &isDeleted)
{
isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future)
mId = esm.getHNString("NAME");
mValue.read (esm, ESM::Variant::Format_Gmst);
}
void GameSetting::save (ESMWriter &esm) const
void GameSetting::save (ESMWriter &esm, bool /*isDeleted*/) const
{
esm.writeHNCString("NAME", mId);
mValue.write (esm, ESM::Variant::Format_Gmst);
}

@ -26,7 +26,7 @@ struct GameSetting
Variant mValue;
void load(ESMReader &esm);
void load(ESMReader &esm, bool &isDeleted);
/// \todo remove the get* functions (redundant, since mValue has equivalent functions now).
@ -39,7 +39,7 @@ struct GameSetting
std::string getString() const;
///< Throwns an exception if GMST is not of type string.
void save(ESMWriter &esm) const;
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,156 +8,140 @@ namespace ESM
{
unsigned int DialInfo::sRecordId = REC_INFO;
void DialInfo::load(ESMReader &esm)
{
mQuestStatus = QS_None;
mFactionLess = false;
mPrev = esm.getHNString("PNAM");
mNext = esm.getHNString("NNAM");
// Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings
mSelects.clear();
// Not present if deleted
if (esm.isNextSub("DATA")) {
esm.getHT(mData, 12);
}
if (!esm.hasMoreSubs())
return;
// What follows is somewhat spaghetti-ish, but it's worth if for
// an extra speedup. INFO is by far the most common record type.
// subName is a reference to the original, so it changes whenever
// a new sub name is read. esm.isEmptyOrGetName() will get the
// next name for us, or return true if there are no more records.
esm.getSubName();
const NAME &subName = esm.retSubName();
if (subName.val == REC_ONAM)
void DialInfo::load(ESMReader &esm, bool &isDeleted)
{
mActor = esm.getHString();
if (esm.isEmptyOrGetName())
return;
}
if (subName.val == REC_RNAM)
{
mRace = esm.getHString();
if (esm.isEmptyOrGetName())
return;
}
if (subName.val == REC_CNAM)
{
mClass = esm.getHString();
if (esm.isEmptyOrGetName())
return;
loadId(esm);
loadData(esm, isDeleted);
}
if (subName.val == REC_FNAM)
void DialInfo::loadId(ESMReader &esm)
{
mFaction = esm.getHString();
if (mFaction == "FFFF")
mFactionLess = true;
if (esm.isEmptyOrGetName())
return;
}
if (subName.val == REC_ANAM)
{
mCell = esm.getHString();
if (esm.isEmptyOrGetName())
return;
}
if (subName.val == REC_DNAM)
{
mPcFaction = esm.getHString();
if (esm.isEmptyOrGetName())
return;
}
if (subName.val == REC_SNAM)
{
mSound = esm.getHString();
if (esm.isEmptyOrGetName())
return;
}
if (subName.val == REC_NAME)
{
mResponse = esm.getHString();
if (esm.isEmptyOrGetName())
return;
mId = esm.getHNString("INAM");
}
while (subName.val == REC_SCVR)
void DialInfo::loadData(ESMReader &esm, bool &isDeleted)
{
SelectStruct ss;
isDeleted = false;
ss.mSelectRule = esm.getHString();
ss.mValue.read (esm, Variant::Format_Info);
mQuestStatus = QS_None;
mFactionLess = false;
mSelects.push_back(ss);
mPrev = esm.getHNString("PNAM");
mNext = esm.getHNString("NNAM");
if (esm.isEmptyOrGetName())
return;
}
// Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings
mSelects.clear();
if (subName.val == REC_BNAM)
{
mResultScript = esm.getHString();
if (esm.isEmptyOrGetName())
return;
while (esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'D','A','T','A'>::value:
esm.getHT(mData, 12);
break;
case ESM::FourCC<'O','N','A','M'>::value:
mActor = esm.getHString();
break;
case ESM::FourCC<'R','N','A','M'>::value:
mRace = esm.getHString();
break;
case ESM::FourCC<'C','N','A','M'>::value:
mClass = esm.getHString();
break;
case ESM::FourCC<'F','N','A','M'>::value:
{
mFaction = esm.getHString();
if (mFaction == "FFFF")
{
mFactionLess = true;
}
break;
}
case ESM::FourCC<'A','N','A','M'>::value:
mCell = esm.getHString();
break;
case ESM::FourCC<'D','N','A','M'>::value:
mPcFaction = esm.getHString();
break;
case ESM::FourCC<'S','N','A','M'>::value:
mSound = esm.getHString();
break;
case ESM::FourCC<'N','A','M','E'>::value:
mResponse = esm.getHString();
break;
case ESM::FourCC<'S','C','V','R'>::value:
{
SelectStruct ss;
ss.mSelectRule = esm.getHString();
ss.mValue.read(esm, Variant::Format_Info);
mSelects.push_back(ss);
break;
}
case ESM::FourCC<'B','N','A','M'>::value:
mResultScript = esm.getHString();
break;
case ESM::FourCC<'Q','S','T','N'>::value:
mQuestStatus = QS_Name;
esm.skipRecord();
break;
case ESM::FourCC<'Q','S','T','F'>::value:
mQuestStatus = QS_Finished;
esm.skipRecord();
break;
case ESM::FourCC<'Q','S','T','R'>::value:
mQuestStatus = QS_Restart;
esm.skipRecord();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
}
if (subName.val == REC_QSTN)
mQuestStatus = QS_Name;
else if (subName.val == REC_QSTF)
mQuestStatus = QS_Finished;
else if (subName.val == REC_QSTR)
mQuestStatus = QS_Restart;
else if (subName.val == REC_DELE)
mQuestStatus = QS_Deleted;
else
esm.fail(
"Don't know what to do with " + subName.toString()
+ " in INFO " + mId);
if (mQuestStatus != QS_None)
// Skip rest of record
esm.skipRecord();
}
void DialInfo::save(ESMWriter &esm) const
{
esm.writeHNCString("PNAM", mPrev);
esm.writeHNCString("NNAM", mNext);
esm.writeHNT("DATA", mData, 12);
esm.writeHNOCString("ONAM", mActor);
esm.writeHNOCString("RNAM", mRace);
esm.writeHNOCString("CNAM", mClass);
esm.writeHNOCString("FNAM", mFaction);
esm.writeHNOCString("ANAM", mCell);
esm.writeHNOCString("DNAM", mPcFaction);
esm.writeHNOCString("SNAM", mSound);
esm.writeHNOString("NAME", mResponse);
for (std::vector<SelectStruct>::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it)
void DialInfo::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNString("SCVR", it->mSelectRule);
it->mValue.write (esm, Variant::Format_Info);
}
esm.writeHNOString("BNAM", mResultScript);
esm.writeHNCString("INAM", mId);
esm.writeHNCString("PNAM", mPrev);
esm.writeHNCString("NNAM", mNext);
switch(mQuestStatus)
{
case QS_Name: esm.writeHNT("QSTN",'\1'); break;
case QS_Finished: esm.writeHNT("QSTF", '\1'); break;
case QS_Restart: esm.writeHNT("QSTR", '\1'); break;
case QS_Deleted: esm.writeHNT("DELE", '\1'); break;
default: break;
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNT("DATA", mData, 12);
esm.writeHNOCString("ONAM", mActor);
esm.writeHNOCString("RNAM", mRace);
esm.writeHNOCString("CNAM", mClass);
esm.writeHNOCString("FNAM", mFaction);
esm.writeHNOCString("ANAM", mCell);
esm.writeHNOCString("DNAM", mPcFaction);
esm.writeHNOCString("SNAM", mSound);
esm.writeHNOString("NAME", mResponse);
for (std::vector<SelectStruct>::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it)
{
esm.writeHNString("SCVR", it->mSelectRule);
it->mValue.write (esm, Variant::Format_Info);
}
esm.writeHNOString("BNAM", mResultScript);
switch(mQuestStatus)
{
case QS_Name: esm.writeHNT("QSTN",'\1'); break;
case QS_Finished: esm.writeHNT("QSTF", '\1'); break;
case QS_Restart: esm.writeHNT("QSTR", '\1'); break;
default: break;
}
}
}
void DialInfo::blank()
{

@ -59,8 +59,7 @@ struct DialInfo
QS_None = 0,
QS_Name = 1,
QS_Finished = 2,
QS_Restart = 3,
QS_Deleted
QS_Restart = 3
};
// Rules for when to include this item in the final list of options
@ -106,8 +105,14 @@ struct DialInfo
REC_DELE = 0x454c4544
};
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
///< Loads all sub-records of Info record
void loadId(ESMReader &esm);
///< Loads only Id of Info record (INAM sub-record)
void loadData(ESMReader &esm, bool &isDeleted);
///< Loads all sub-records of Info record, except INAM sub-record
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,15 +8,21 @@ namespace ESM
{
unsigned int Ingredient::sRecordId = REC_INGR;
void Ingredient::load(ESMReader &esm)
void Ingredient::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -33,12 +39,19 @@ namespace ESM
case ESM::FourCC<'I','T','E','X'>::value:
mIcon = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing IRDT subrecord");
// horrible hack to fix broken data in records
@ -65,8 +78,16 @@ namespace ESM
}
}
void Ingredient::save(ESMWriter &esm) const
void Ingredient::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNT("IRDT", mData, 56);

@ -31,8 +31,8 @@ struct Ingredient
IRDTstruct mData;
std::string mId, mName, mModel, mIcon, mScript;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -10,212 +10,251 @@ namespace ESM
{
unsigned int Land::sRecordId = REC_LAND;
void Land::LandData::save(ESMWriter &esm) const
{
if (mDataTypes & Land::DATA_VNML) {
esm.writeHNT("VNML", mNormals, sizeof(mNormals));
}
if (mDataTypes & Land::DATA_VHGT) {
VHGT offsets;
offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE;
offsets.mUnk1 = mUnk1;
offsets.mUnk2 = mUnk2;
float prevY = mHeights[0];
int number = 0; // avoid multiplication
for (int i = 0; i < LAND_SIZE; ++i) {
float diff = (mHeights[number] - prevY) / HEIGHT_SCALE;
offsets.mHeightData[number] =
(diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5);
float prevX = prevY = mHeights[number];
++number;
for (int j = 1; j < LAND_SIZE; ++j) {
diff = (mHeights[number] - prevX) / HEIGHT_SCALE;
void Land::LandData::save(ESMWriter &esm) const
{
if (mDataTypes & Land::DATA_VNML) {
esm.writeHNT("VNML", mNormals, sizeof(mNormals));
}
if (mDataTypes & Land::DATA_VHGT) {
VHGT offsets;
offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE;
offsets.mUnk1 = mUnk1;
offsets.mUnk2 = mUnk2;
float prevY = mHeights[0];
int number = 0; // avoid multiplication
for (int i = 0; i < LAND_SIZE; ++i) {
float diff = (mHeights[number] - prevY) / HEIGHT_SCALE;
offsets.mHeightData[number] =
(diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5);
prevX = mHeights[number];
float prevX = prevY = mHeights[number];
++number;
for (int j = 1; j < LAND_SIZE; ++j) {
diff = (mHeights[number] - prevX) / HEIGHT_SCALE;
offsets.mHeightData[number] =
(diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5);
prevX = mHeights[number];
++number;
}
}
esm.writeHNT("VHGT", offsets, sizeof(VHGT));
}
if (mDataTypes & Land::DATA_WNAM) {
esm.writeHNT("WNAM", mWnam, 81);
}
if (mDataTypes & Land::DATA_VCLR) {
esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS);
}
if (mDataTypes & Land::DATA_VTEX) {
static uint16_t vtex[LAND_NUM_TEXTURES];
transposeTextureData(mTextures, vtex);
esm.writeHNT("VTEX", vtex, sizeof(vtex));
}
esm.writeHNT("VHGT", offsets, sizeof(VHGT));
}
if (mDataTypes & Land::DATA_WNAM) {
esm.writeHNT("WNAM", mWnam, 81);
}
if (mDataTypes & Land::DATA_VCLR) {
esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS);
Land::Land()
: mFlags(0)
, mX(0)
, mY(0)
, mPlugin(0)
, mEsm(NULL)
, mDataTypes(0)
, mDataLoaded(false)
, mLandData(NULL)
{
}
if (mDataTypes & Land::DATA_VTEX) {
static uint16_t vtex[LAND_NUM_TEXTURES];
transposeTextureData(mTextures, vtex);
esm.writeHNT("VTEX", vtex, sizeof(vtex));
void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out)
{
int readPos = 0; //bit ugly, but it works
for ( int y1 = 0; y1 < 4; y1++ )
for ( int x1 = 0; x1 < 4; x1++ )
for ( int y2 = 0; y2 < 4; y2++)
for ( int x2 = 0; x2 < 4; x2++ )
out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++];
}
}
void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out)
{
int readPos = 0; //bit ugly, but it works
for ( int y1 = 0; y1 < 4; y1++ )
for ( int x1 = 0; x1 < 4; x1++ )
for ( int y2 = 0; y2 < 4; y2++)
for ( int x2 = 0; x2 < 4; x2++ )
out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++];
}
Land::~Land()
{
delete mLandData;
}
Land::Land()
: mFlags(0)
, mX(0)
, mY(0)
, mPlugin(0)
, mEsm(NULL)
, mDataTypes(0)
, mDataLoaded(false)
, mLandData(NULL)
{
}
void Land::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
Land::~Land()
{
delete mLandData;
}
mEsm = &esm;
mPlugin = mEsm->getIndex();
void Land::load(ESMReader &esm)
{
mEsm = &esm;
mPlugin = mEsm->getIndex();
bool hasLocation = false;
bool isLoaded = false;
while (!isLoaded && esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'I','N','T','V'>::value:
esm.getSubHeaderIs(8);
esm.getT<int>(mX);
esm.getT<int>(mY);
hasLocation = true;
break;
case ESM::FourCC<'D','A','T','A'>::value:
esm.getHT(mFlags);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.cacheSubName();
isLoaded = true;
break;
}
}
// Get the grid location
esm.getSubNameIs("INTV");
esm.getSubHeaderIs(8);
esm.getT<int>(mX);
esm.getT<int>(mY);
if (!hasLocation)
esm.fail("Missing INTV subrecord");
esm.getHNT(mFlags, "DATA");
mContext = esm.getContext();
// Store the file position
mContext = esm.getContext();
// Skip the land data here. Load it when the cell is loaded.
while (esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'V','N','M','L'>::value:
esm.skipHSub();
mDataTypes |= DATA_VNML;
break;
case ESM::FourCC<'V','H','G','T'>::value:
esm.skipHSub();
mDataTypes |= DATA_VHGT;
break;
case ESM::FourCC<'W','N','A','M'>::value:
esm.skipHSub();
mDataTypes |= DATA_WNAM;
break;
case ESM::FourCC<'V','C','L','R'>::value:
esm.skipHSub();
mDataTypes |= DATA_VCLR;
break;
case ESM::FourCC<'V','T','E','X'>::value:
esm.skipHSub();
mDataTypes |= DATA_VTEX;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
// Skip these here. Load the actual data when the cell is loaded.
if (esm.isNextSub("VNML"))
{
esm.skipHSubSize(12675);
mDataTypes |= DATA_VNML;
}
if (esm.isNextSub("VHGT"))
{
esm.skipHSubSize(4232);
mDataTypes |= DATA_VHGT;
}
if (esm.isNextSub("WNAM"))
{
esm.skipHSubSize(81);
mDataTypes |= DATA_WNAM;
}
if (esm.isNextSub("VCLR"))
{
esm.skipHSubSize(12675);
mDataTypes |= DATA_VCLR;
}
if (esm.isNextSub("VTEX"))
{
esm.skipHSubSize(512);
mDataTypes |= DATA_VTEX;
mDataLoaded = 0;
mLandData = NULL;
}
mDataLoaded = 0;
mLandData = NULL;
}
void Land::save(ESMWriter &esm, bool isDeleted) const
{
esm.startSubRecord("INTV");
esm.writeT(mX);
esm.writeT(mY);
esm.endRecord("INTV");
void Land::save(ESMWriter &esm) const
{
esm.startSubRecord("INTV");
esm.writeT(mX);
esm.writeT(mY);
esm.endRecord("INTV");
esm.writeHNT("DATA", mFlags);
esm.writeHNT("DATA", mFlags);
}
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
void Land::loadData(int flags) const
{
// Try to load only available data
flags = flags & mDataTypes;
// Return if all required data is loaded
if ((mDataLoaded & flags) == flags) {
return;
}
// Create storage if nothing is loaded
if (mLandData == NULL) {
mLandData = new LandData;
mLandData->mDataTypes = mDataTypes;
if (mLandData)
{
mLandData->save(esm);
}
}
mEsm->restoreContext(mContext);
if (mEsm->isNextSub("VNML")) {
condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals));
}
void Land::loadData(int flags) const
{
// Try to load only available data
flags = flags & mDataTypes;
// Return if all required data is loaded
if ((mDataLoaded & flags) == flags) {
return;
}
// Create storage if nothing is loaded
if (mLandData == NULL) {
mLandData = new LandData;
mLandData->mDataTypes = mDataTypes;
}
mEsm->restoreContext(mContext);
if (mEsm->isNextSub("VHGT")) {
static VHGT vhgt;
if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) {
float rowOffset = vhgt.mHeightOffset;
for (int y = 0; y < LAND_SIZE; y++) {
rowOffset += vhgt.mHeightData[y * LAND_SIZE];
if (mEsm->isNextSub("VNML")) {
condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals));
}
mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE;
if (mEsm->isNextSub("VHGT")) {
static VHGT vhgt;
if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) {
float rowOffset = vhgt.mHeightOffset;
for (int y = 0; y < LAND_SIZE; y++) {
rowOffset += vhgt.mHeightData[y * LAND_SIZE];
float colOffset = rowOffset;
for (int x = 1; x < LAND_SIZE; x++) {
colOffset += vhgt.mHeightData[y * LAND_SIZE + x];
mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE;
mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE;
float colOffset = rowOffset;
for (int x = 1; x < LAND_SIZE; x++) {
colOffset += vhgt.mHeightData[y * LAND_SIZE + x];
mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE;
}
}
mLandData->mUnk1 = vhgt.mUnk1;
mLandData->mUnk2 = vhgt.mUnk2;
}
mLandData->mUnk1 = vhgt.mUnk1;
mLandData->mUnk2 = vhgt.mUnk2;
}
}
if (mEsm->isNextSub("WNAM")) {
condLoad(flags, DATA_WNAM, mLandData->mWnam, 81);
}
if (mEsm->isNextSub("VCLR"))
condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS);
if (mEsm->isNextSub("VTEX")) {
static uint16_t vtex[LAND_NUM_TEXTURES];
if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) {
LandData::transposeTextureData(vtex, mLandData->mTextures);
if (mEsm->isNextSub("WNAM")) {
condLoad(flags, DATA_WNAM, mLandData->mWnam, 81);
}
if (mEsm->isNextSub("VCLR"))
condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS);
if (mEsm->isNextSub("VTEX")) {
static uint16_t vtex[LAND_NUM_TEXTURES];
if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) {
LandData::transposeTextureData(vtex, mLandData->mTextures);
}
}
}
}
void Land::unloadData()
{
if (mDataLoaded)
void Land::unloadData()
{
delete mLandData;
mLandData = NULL;
mDataLoaded = 0;
if (mDataLoaded)
{
delete mLandData;
mLandData = NULL;
mDataLoaded = 0;
}
}
}
bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const
{
if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) {
mEsm->getHExact(ptr, size);
mDataLoaded |= dataFlag;
return true;
bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const
{
if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) {
mEsm->getHExact(ptr, size);
mDataLoaded |= dataFlag;
return true;
}
mEsm->skipHSubSize(size);
return false;
}
mEsm->skipHSubSize(size);
return false;
}
bool Land::isDataLoaded(int flags) const
{
return (mDataLoaded & flags) == (flags & mDataTypes);
}
bool Land::isDataLoaded(int flags) const
{
return (mDataLoaded & flags) == (flags & mDataTypes);
}
Land::Land (const Land& land)
: mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin),

@ -106,8 +106,8 @@ struct Land
static void transposeTextureData(const uint16_t *in, uint16_t *out);
};
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank() {}

@ -6,42 +6,84 @@
namespace ESM
{
void LevelledListBase::load(ESMReader &esm)
void LevelledListBase::load(ESMReader &esm, bool &isDeleted)
{
esm.getHNT(mFlags, "DATA");
esm.getHNT(mChanceNone, "NNAM");
isDeleted = false;
if (esm.isNextSub("INDX"))
{
int len;
esm.getHT(len);
mList.resize(len);
}
else
bool hasName = false;
bool hasList = false;
while (esm.hasMoreSubs())
{
// Original engine ignores rest of the record, even if there are items following
mList.clear();
esm.skipRecord();
return;
}
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'D','A','T','A'>::value:
esm.getHT(mFlags);
break;
case ESM::FourCC<'N','N','A','M'>::value:
esm.getHT(mChanceNone);
break;
case ESM::FourCC<'I','N','D','X'>::value:
{
int length = 0;
esm.getHT(length);
mList.resize(length);
// If this levelled list was already loaded by a previous content file,
// we overwrite the list. Merging lists should probably be left to external tools,
// with the limited amount of information there is in the records, all merging methods
// will be flawed in some way. For a proper fix the ESM format would have to be changed
// to actually track list changes instead of including the whole list for every file
// that does something with that list.
// If this levelled list was already loaded by a previous content file,
// we overwrite the list. Merging lists should probably be left to external tools,
// with the limited amount of information there is in the records, all merging methods
// will be flawed in some way. For a proper fix the ESM format would have to be changed
// to actually track list changes instead of including the whole list for every file
// that does something with that list.
for (size_t i = 0; i < mList.size(); i++)
{
LevelItem &li = mList[i];
li.mId = esm.getHNString(mRecName);
esm.getHNT(li.mLevel, "INTV");
}
for (size_t i = 0; i < mList.size(); i++)
{
LevelItem &li = mList[i];
li.mId = esm.getHNString(mRecName);
esm.getHNT(li.mLevel, "INTV");
hasList = true;
break;
}
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
{
if (!hasList)
{
// Original engine ignores rest of the record, even if there are items following
mList.clear();
esm.skipRecord();
}
else
{
esm.fail("Unknown subrecord");
}
break;
}
}
}
if (!hasName)
esm.fail("Missing NAME subrecord");
}
void LevelledListBase::save(ESMWriter &esm) const
void LevelledListBase::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNT("DATA", mFlags);
esm.writeHNT("NNAM", mChanceNone);
esm.writeHNT<int>("INDX", mList.size());

@ -36,8 +36,8 @@ struct LevelledListBase
std::vector<LevelItem> mList;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,15 +8,21 @@ namespace ESM
{
unsigned int Light::sRecordId = REC_LIGH;
void Light::load(ESMReader &esm)
void Light::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -36,15 +42,31 @@ namespace ESM
case ESM::FourCC<'S','N','A','M'>::value:
mSound = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing LHDT subrecord");
}
void Light::save(ESMWriter &esm) const
void Light::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("ITEX", mIcon);

@ -47,8 +47,8 @@ struct Light
std::string mSound, mScript, mModel, mIcon, mName, mId;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,15 +8,21 @@ namespace ESM
{
unsigned int Lockpick::sRecordId = REC_LOCK;
void Lockpick::load(ESMReader &esm)
void Lockpick::load(ESMReader &esm, bool &isDeleted)
{
bool hasData = true;
isDeleted = false;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -33,16 +39,32 @@ namespace ESM
case ESM::FourCC<'I','T','E','X'>::value:
mIcon = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing LKDT subrecord");
}
void Lockpick::save(ESMWriter &esm) const
void Lockpick::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);

@ -27,8 +27,8 @@ struct Lockpick
Data mData;
std::string mId, mName, mModel, mIcon, mScript;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,21 +8,58 @@ namespace ESM
{
unsigned int LandTexture::sRecordId = REC_LTEX;
void LandTexture::load(ESMReader &esm)
{
esm.getHNT(mIndex, "INTV");
mTexture = esm.getHNString("DATA");
}
void LandTexture::save(ESMWriter &esm) const
{
esm.writeHNT("INTV", mIndex);
esm.writeHNCString("DATA", mTexture);
}
void LandTexture::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
void LandTexture::blank()
{
mTexture.clear();
mIndex = -1;
}
bool hasName = false;
bool hasIndex = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'I','N','T','V'>::value:
esm.getHT(mIndex);
hasIndex = true;
break;
case ESM::FourCC<'D','A','T','A'>::value:
mTexture = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasIndex)
esm.fail("Missing INTV subrecord");
}
void LandTexture::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
esm.writeHNT("INTV", mIndex);
esm.writeHNCString("DATA", mTexture);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
}
}
void LandTexture::blank()
{
mTexture.clear();
mIndex = -1;
}
}

@ -34,11 +34,11 @@ struct LandTexture
std::string mId, mTexture;
int mIndex;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
};
}
#endif

@ -189,8 +189,10 @@ namespace ESM
{
unsigned int MagicEffect::sRecordId = REC_MGEF;
void MagicEffect::load(ESMReader &esm)
void MagicEffect::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future)
esm.getHNT(mIndex, "INDX");
mId = indexToId (mIndex);
@ -209,8 +211,7 @@ void MagicEffect::load(ESMReader &esm)
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'I','T','E','X'>::value:
mIcon = esm.getHString();
@ -250,7 +251,7 @@ void MagicEffect::load(ESMReader &esm)
}
}
}
void MagicEffect::save(ESMWriter &esm) const
void MagicEffect::save(ESMWriter &esm, bool /*isDeleted*/) const
{
esm.writeHNT("INDX", mIndex);

@ -96,8 +96,8 @@ struct MagicEffect
// sMagicCreature04ID/05ID.
int mIndex;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
/// Set record to default state (does not touch the ID/index).
void blank();

@ -8,15 +8,21 @@ namespace ESM
{
unsigned int Miscellaneous::sRecordId = REC_MISC;
void Miscellaneous::load(ESMReader &esm)
void Miscellaneous::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
bool hasName = false;
bool hasData = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -33,14 +39,32 @@ namespace ESM
case ESM::FourCC<'I','T','E','X'>::value:
mIcon = esm.getHString();
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasData && !isDeleted)
esm.fail("Missing MCDT subrecord");
}
void Miscellaneous::save(ESMWriter &esm) const
void Miscellaneous::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNT("MCDT", mData, 12);

@ -32,8 +32,8 @@ struct Miscellaneous
std::string mId, mName, mModel, mIcon, mScript;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
///< Set record to default state (does not touch the ID).

@ -8,24 +8,30 @@ namespace ESM
{
unsigned int NPC::sRecordId = REC_NPC_;
void NPC::load(ESMReader &esm)
void NPC::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
mPersistent = (esm.getRecordFlags() & 0x0400) != 0;
mSpells.mList.clear();
mInventory.mList.clear();
mTransport.mList.clear();
mAiPackage.mList.clear();
mHasAI = false;
bool hasName = false;
bool hasNpdt = false;
bool hasFlags = false;
mHasAI = false;
while (esm.hasMoreSubs())
{
esm.getSubName();
uint32_t name = esm.retSubName().val;
switch (name)
switch (esm.retSubName().val)
{
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString();
break;
@ -92,17 +98,33 @@ namespace ESM
case AI_CNDT:
mAiPackage.add(esm);
break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasNpdt)
if (!hasName)
esm.fail("Missing NAME subrecord");
if (!hasNpdt && !isDeleted)
esm.fail("Missing NPDT subrecord");
if (!hasFlags)
if (!hasFlags && !isDeleted)
esm.fail("Missing FLAG subrecord");
}
void NPC::save(ESMWriter &esm) const
void NPC::save(ESMWriter &esm, bool isDeleted) const
{
esm.writeHNCString("NAME", mId);
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
esm.writeHNOCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName);
esm.writeHNCString("RNAM", mRace);

@ -130,8 +130,8 @@ struct NPC
// body parts
std::string mHair, mHead;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
bool isMale() const;

@ -32,98 +32,130 @@ namespace ESM
{
}
void Pathgrid::load(ESMReader &esm)
{
esm.getHNT(mData, "DATA", 12);
mCell = esm.getHNString("NAME");
void Pathgrid::load(ESMReader &esm, bool &isDeleted)
{
isDeleted = false;
mPoints.clear();
mEdges.clear();
mPoints.clear();
mEdges.clear();
// keep track of total connections so we can reserve edge vector size
int edgeCount = 0;
// keep track of total connections so we can reserve edge vector size
int edgeCount = 0;
if (esm.isNextSub("PGRP"))
{
esm.getSubHeader();
int size = esm.getSubSize();
// Check that the sizes match up. Size = 16 * s2 (path points)
if (size != static_cast<int> (sizeof(Point) * mData.mS2))
esm.fail("Path point subrecord size mismatch");
else
bool hasData = false;
while (esm.hasMoreSubs())
{
int pointCount = mData.mS2;
mPoints.reserve(pointCount);
for (int i = 0; i < pointCount; ++i)
esm.getSubName();
switch (esm.retSubName().val)
{
Point p;
esm.getExact(&p, sizeof(Point));
mPoints.push_back(p);
edgeCount += p.mConnectionNum;
case ESM::FourCC<'N','A','M','E'>::value:
mCell = esm.getHString();
break;
case ESM::FourCC<'D','A','T','A'>::value:
esm.getHT(mData, 12);
hasData = true;
break;
case ESM::FourCC<'P','G','R','P'>::value:
{
esm.getSubHeader();
int size = esm.getSubSize();
// Check that the sizes match up. Size = 16 * s2 (path points)
if (size != static_cast<int> (sizeof(Point) * mData.mS2))
esm.fail("Path point subrecord size mismatch");
else
{
int pointCount = mData.mS2;
mPoints.reserve(pointCount);
for (int i = 0; i < pointCount; ++i)
{
Point p;
esm.getExact(&p, sizeof(Point));
mPoints.push_back(p);
edgeCount += p.mConnectionNum;
}
}
break;
}
case ESM::FourCC<'P','G','R','C'>::value:
{
esm.getSubHeader();
int size = esm.getSubSize();
if (size % sizeof(int) != 0)
esm.fail("PGRC size not a multiple of 4");
else
{
int rawConnNum = size / sizeof(int);
std::vector<int> rawConnections;
rawConnections.reserve(rawConnNum);
for (int i = 0; i < rawConnNum; ++i)
{
int currentValue;
esm.getT(currentValue);
rawConnections.push_back(currentValue);
}
std::vector<int>::const_iterator rawIt = rawConnections.begin();
int pointIndex = 0;
mEdges.reserve(edgeCount);
for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex)
{
unsigned char connectionNum = (*it).mConnectionNum;
for (int i = 0; i < connectionNum; ++i) {
Edge edge;
edge.mV0 = pointIndex;
edge.mV1 = *rawIt;
++rawIt;
mEdges.push_back(edge);
}
}
}
break;
}
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
if (!hasData)
esm.fail("Missing DATA subrecord");
}
if (esm.isNextSub("PGRC"))
void Pathgrid::save(ESMWriter &esm, bool isDeleted) const
{
esm.getSubHeader();
int size = esm.getSubSize();
if (size % sizeof(int) != 0)
esm.fail("PGRC size not a multiple of 4");
else
{
int rawConnNum = size / sizeof(int);
std::vector<int> rawConnections;
rawConnections.reserve(rawConnNum);
for (int i = 0; i < rawConnNum; ++i)
{
int currentValue;
esm.getT(currentValue);
rawConnections.push_back(currentValue);
}
esm.writeHNCString("NAME", mCell);
esm.writeHNT("DATA", mData, 12);
std::vector<int>::const_iterator rawIt = rawConnections.begin();
int pointIndex = 0;
mEdges.reserve(edgeCount);
for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex)
{
unsigned char connectionNum = (*it).mConnectionNum;
for (int i = 0; i < connectionNum; ++i) {
Edge edge;
edge.mV0 = pointIndex;
edge.mV1 = *rawIt;
++rawIt;
mEdges.push_back(edge);
}
}
if (isDeleted)
{
esm.writeHNCString("DELE", "");
return;
}
}
}
void Pathgrid::save(ESMWriter &esm) const
{
esm.writeHNT("DATA", mData, 12);
esm.writeHNCString("NAME", mCell);
if (!mPoints.empty())
{
esm.startSubRecord("PGRP");
for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it)
if (!mPoints.empty())
{
esm.writeT(*it);
esm.startSubRecord("PGRP");
for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it)
{
esm.writeT(*it);
}
esm.endRecord("PGRP");
}
esm.endRecord("PGRP");
}
if (!mEdges.empty())
{
esm.startSubRecord("PGRC");
for (std::vector<Edge>::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it)
if (!mEdges.empty())
{
esm.writeT(it->mV1);
esm.startSubRecord("PGRC");
for (std::vector<Edge>::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it)
{
esm.writeT(it->mV1);
}
esm.endRecord("PGRC");
}
esm.endRecord("PGRC");
}
}
void Pathgrid::blank()
{

@ -53,8 +53,8 @@ struct Pathgrid
typedef std::vector<Edge> EdgeList;
EdgeList mEdges;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank();
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save