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
openmw-38
scrawl 9 years ago
commit b8e11cf197

@ -27,7 +27,8 @@ struct ESMData
std::vector<ESM::Header::MasterData> masters; std::vector<ESM::Header::MasterData> masters;
std::deque<EsmTool::RecordBase *> mRecords; 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; std::map<int, int> mRecordStats;
static const std::set<int> sLabeledRec; 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)) while(cell.getNextRef(esm, ref, deleted))
{ {
if (save) { if (save) {
info.data.mCellRefs[&cell].push_back(ref); info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted));
} }
if(quiet) continue; if(quiet) continue;
@ -352,61 +353,58 @@ int load(Arguments& info)
uint32_t flags; uint32_t flags;
esm.getRecHeader(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? // Is the user interested in this record type?
bool interested = true; bool interested = true;
if (!info.types.empty()) if (!info.types.empty())
{ {
std::vector<std::string>::iterator match; std::vector<std::string>::iterator match;
match = std::find(info.types.begin(), info.types.end(), match = std::find(info.types.begin(), info.types.end(), n.toString());
n.toString());
if (match == info.types.end()) interested = false; if (match == info.types.end()) interested = false;
} }
std::string id = esm.getHNOString("NAME"); if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId()))
if (id.empty())
id = esm.getHNOString("INAM");
if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, id))
interested = false; interested = false;
if(!quiet && interested) if(!quiet && interested)
std::cout << "\nRecord: " << n.toString() {
<< " '" << id << "'\n"; std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n";
record->print();
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 (record->getType().val == ESM::REC_CELL && loadCells && interested)
if (quiet) break; {
std::cout << " Skipping\n"; loadCell(record->cast<ESM::Cell>()->get(), esm, info);
} 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 (save) { if (save)
info.data.mRecords.push_back(record); {
} else { info.data.mRecords.push_back(record);
delete record; }
} else
++info.data.mRecordStats[n.val]; {
delete record;
} }
++info.data.mRecordStats[n.val];
} }
} catch(std::exception &e) { } catch(std::exception &e) {
@ -493,28 +491,19 @@ int clone(Arguments& info)
for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it)
{ {
EsmTool::RecordBase *record = *it; EsmTool::RecordBase *record = *it;
name.val = record->getType().val; name.val = record->getType().val;
esm.startRecord(name.toString(), record->getFlags()); 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); record->save(esm);
if (name.val == ESM::REC_CELL) { if (name.val == ESM::REC_CELL) {
ESM::Cell *ptr = &record->cast<ESM::Cell>()->get(); ESM::Cell *ptr = &record->cast<ESM::Cell>()->get();
if (!info.data.mCellRefs[ptr].empty()) { 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]; RefList &refs = info.data.mCellRefs[ptr];
for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt) 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 << " Name: " << mData.mName << std::endl;
std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Model: " << mData.mModel << std::endl;
std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -419,6 +420,7 @@ void Record<ESM::Potion>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl;
printEffectList(mData.mEffects); printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -447,6 +449,7 @@ void Record<ESM::Armor>::print()
if (pit->mFemale != "") if (pit->mFemale != "")
std::cout << " Female Name: " << pit->mFemale << std::endl; std::cout << " Female Name: " << pit->mFemale << std::endl;
} }
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -461,6 +464,7 @@ void Record<ESM::Apparatus>::print()
std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl;
std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -474,6 +478,7 @@ void Record<ESM::BodyPart>::print()
std::cout << " Part: " << meshPartLabel(mData.mData.mPart) std::cout << " Part: " << meshPartLabel(mData.mData.mPart)
<< " (" << (int)mData.mData.mPart << ")" << std::endl; << " (" << (int)mData.mData.mPart << ")" << std::endl;
std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -502,6 +507,7 @@ void Record<ESM::Book>::print()
{ {
std::cout << " Text: [skipped]" << std::endl; std::cout << " Text: [skipped]" << std::endl;
} }
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -513,6 +519,7 @@ void Record<ESM::BirthSign>::print()
std::vector<std::string>::iterator pit; std::vector<std::string>::iterator pit;
for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit) for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit)
std::cout << " Power: " << *pit << std::endl; std::cout << " Power: " << *pit << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -541,6 +548,7 @@ void Record<ESM::Cell>::print()
std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl;
std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl;
std::cout << " RefId counter: " << mData.mRefNumCounter << 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++) for (int i = 0; i != 5; i++)
std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1])
<< " (" << mData.mData.mSkills[i][1] << ")" << std::endl; << " (" << mData.mData.mSkills[i][1] << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -589,6 +598,7 @@ void Record<ESM::Clothing>::print()
if (pit->mFemale != "") if (pit->mFemale != "")
std::cout << " Female Name: " << pit->mFemale << std::endl; std::cout << " Female Name: " << pit->mFemale << std::endl;
} }
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -604,6 +614,7 @@ void Record<ESM::Container>::print()
for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit) for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit)
std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount
<< " Item: " << cit->mItem.toString() << std::endl; << " Item: " << cit->mItem.toString() << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -670,6 +681,7 @@ void Record<ESM::Creature>::print()
std::vector<ESM::AIPackage>::iterator pit; std::vector<ESM::AIPackage>::iterator pit;
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
printAIPackage(*pit); printAIPackage(*pit);
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -677,6 +689,7 @@ void Record<ESM::Dialogue>::print()
{ {
std::cout << " Type: " << dialogTypeLabel(mData.mType) std::cout << " Type: " << dialogTypeLabel(mData.mType)
<< " (" << (int)mData.mType << ")" << std::endl; << " (" << (int)mData.mType << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
// Sadly, there are no DialInfos, because the loader dumps as it // Sadly, there are no DialInfos, because the loader dumps as it
// loads, rather than loading and then dumping. :-( Anyone mind if // loads, rather than loading and then dumping. :-( Anyone mind if
// I change this? // I change this?
@ -693,6 +706,7 @@ void Record<ESM::Door>::print()
std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Script: " << mData.mScript << std::endl;
std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl;
std::cout << " CloseSound: " << mData.mCloseSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -704,6 +718,7 @@ void Record<ESM::Enchantment>::print()
std::cout << " Charge: " << mData.mData.mCharge << std::endl; std::cout << " Charge: " << mData.mData.mCharge << std::endl;
std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl;
printEffectList(mData.mEffects); printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -737,12 +752,14 @@ void Record<ESM::Faction>::print()
std::map<std::string, int>::iterator rit; std::map<std::string, int>::iterator rit;
for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit) for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit)
std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl; std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
void Record<ESM::Global>::print() void Record<ESM::Global>::print()
{ {
std::cout << " " << mData.mValue << std::endl; std::cout << " " << mData.mValue << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -809,6 +826,7 @@ void Record<ESM::DialInfo>::print()
std::cout << " Result Script: [skipped]" << std::endl; std::cout << " Result Script: [skipped]" << std::endl;
} }
} }
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -832,6 +850,7 @@ void Record<ESM::Ingredient>::print()
std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i])
<< " (" << mData.mData.mAttributes[i] << ")" << std::endl; << " (" << mData.mData.mAttributes[i] << ")" << std::endl;
} }
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -848,6 +867,8 @@ void Record<ESM::Land>::print()
std::cout << " Unknown1: " << data->mUnk1 << std::endl; std::cout << " Unknown1: " << data->mUnk1 << std::endl;
std::cout << " Unknown2: " << data->mUnk2 << std::endl; std::cout << " Unknown2: " << data->mUnk2 << std::endl;
} }
if (!wasLoaded) mData.unloadData();
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -860,6 +881,7 @@ void Record<ESM::CreatureLevList>::print()
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
std::cout << " Creature: Level: " << iit->mLevel std::cout << " Creature: Level: " << iit->mLevel
<< " Creature: " << iit->mId << std::endl; << " Creature: " << iit->mId << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -872,6 +894,7 @@ void Record<ESM::ItemLevList>::print()
for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit)
std::cout << " Inventory: Level: " << iit->mLevel std::cout << " Inventory: Level: " << iit->mLevel
<< " Item: " << iit->mId << std::endl; << " Item: " << iit->mId << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -892,6 +915,7 @@ void Record<ESM::Light>::print()
std::cout << " Duration: " << mData.mData.mTime << std::endl; std::cout << " Duration: " << mData.mData.mTime << std::endl;
std::cout << " Radius: " << mData.mData.mRadius << std::endl; std::cout << " Radius: " << mData.mData.mRadius << std::endl;
std::cout << " Color: " << mData.mData.mColor << std::endl; std::cout << " Color: " << mData.mData.mColor << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -906,6 +930,7 @@ void Record<ESM::Lockpick>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -920,6 +945,7 @@ void Record<ESM::Probe>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -934,6 +960,7 @@ void Record<ESM::Repair>::print()
std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl;
std::cout << " Uses: " << mData.mData.mUses << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -942,6 +969,7 @@ void Record<ESM::LandTexture>::print()
std::cout << " Id: " << mData.mId << std::endl; std::cout << " Id: " << mData.mId << std::endl;
std::cout << " Index: " << mData.mIndex << std::endl; std::cout << " Index: " << mData.mIndex << std::endl;
std::cout << " Texture: " << mData.mTexture << std::endl; std::cout << " Texture: " << mData.mTexture << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -992,6 +1020,7 @@ void Record<ESM::Miscellaneous>::print()
std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl;
std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl;
std::cout << " Is Key: " << mData.mData.mIsKey << std::endl; std::cout << " Is Key: " << mData.mData.mIsKey << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -1077,6 +1106,8 @@ void Record<ESM::NPC>::print()
std::vector<ESM::AIPackage>::iterator pit; std::vector<ESM::AIPackage>::iterator pit;
for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit)
printAIPackage(*pit); printAIPackage(*pit);
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -1111,6 +1142,8 @@ void Record<ESM::Pathgrid>::print()
std::cout << " BAD POINT IN EDGE!" << std::endl; std::cout << " BAD POINT IN EDGE!" << std::endl;
i++; i++;
} }
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -1151,6 +1184,8 @@ void Record<ESM::Race>::print()
std::vector<std::string>::iterator sit; std::vector<std::string>::iterator sit;
for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit) for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit)
std::cout << " Power: " << *sit << std::endl; std::cout << " Power: " << *sit << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -1210,6 +1245,8 @@ void Record<ESM::Script>::print()
{ {
std::cout << " Script: [skipped]" << std::endl; std::cout << " Script: [skipped]" << std::endl;
} }
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -1233,6 +1270,7 @@ void Record<ESM::SoundGenerator>::print()
std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Sound: " << mData.mSound << std::endl;
std::cout << " Type: " << soundTypeLabel(mData.mType) std::cout << " Type: " << soundTypeLabel(mData.mType)
<< " (" << mData.mType << ")" << std::endl; << " (" << mData.mType << ")" << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -1243,6 +1281,7 @@ void Record<ESM::Sound>::print()
if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0)
std::cout << " Range: " << (int)mData.mData.mMinRange << " - " std::cout << " Range: " << (int)mData.mData.mMinRange << " - "
<< (int)mData.mData.mMaxRange << std::endl; << (int)mData.mData.mMaxRange << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -1254,13 +1293,15 @@ void Record<ESM::Spell>::print()
std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl;
std::cout << " Cost: " << mData.mData.mCost << std::endl; std::cout << " Cost: " << mData.mData.mCost << std::endl;
printEffectList(mData.mEffects); printEffectList(mData.mEffects);
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
void Record<ESM::StartScript>::print() void Record<ESM::StartScript>::print()
{ {
std::cout << "Start Script: " << mData.mId << std::endl; std::cout << " Start Script: " << mData.mId << std::endl;
std::cout << "Start Data: " << mData.mData << std::endl; std::cout << " Start Data: " << mData.mData << std::endl;
std::cout << " Deleted: " << mIsDeleted << std::endl;
} }
template<> template<>
@ -1301,6 +1342,37 @@ void Record<ESM::Weapon>::print()
if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0)
std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-"
<< (int)mData.mData.mThrust[1] << std::endl; << (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 } // end namespace

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

@ -158,9 +158,9 @@ namespace ESSImport
void ConvertCell::read(ESM::ESMReader &esm) void ConvertCell::read(ESM::ESMReader &esm)
{ {
ESM::Cell cell; ESM::Cell cell;
std::string id = esm.getHNString("NAME"); bool isDeleted = false;
cell.mName = id;
cell.load(esm, false); cell.load(esm, isDeleted, false);
// I wonder what 0x40 does? // I wonder what 0x40 does?
if (cell.isExterior() && cell.mData.mFlags & 0x20) 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 // 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(); mContext->mPlayer.mCellId = cell.getCellId();
} }
@ -277,7 +277,7 @@ namespace ESSImport
if (cell.isExterior()) if (cell.isExterior())
mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell;
else else
mIntCells[id] = newcell; mIntCells[cell.mName] = newcell;
} }
void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm)

@ -54,6 +54,8 @@ public:
void setContext(Context& context) { mContext = &context; } 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) virtual void read(ESM::ESMReader& esm)
{ {
} }
@ -78,10 +80,11 @@ public:
virtual void read(ESM::ESMReader& esm) virtual void read(ESM::ESMReader& esm)
{ {
std::string id = esm.getHNString("NAME");
T record; T record;
record.load(esm); bool isDeleted = false;
mRecords[id] = record;
record.load(esm, isDeleted);
mRecords[record.mId] = record;
} }
virtual void write(ESM::ESMWriter& esm) 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) for (typename std::map<std::string, T>::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it)
{ {
esm.startRecord(T::sRecordId); esm.startRecord(T::sRecordId);
esm.writeHNString("NAME", it->first);
it->second.save(esm); it->second.save(esm);
esm.endRecord(T::sRecordId); esm.endRecord(T::sRecordId);
} }
@ -105,14 +107,15 @@ public:
virtual void read(ESM::ESMReader &esm) virtual void read(ESM::ESMReader &esm)
{ {
ESM::NPC npc; ESM::NPC npc;
std::string id = esm.getHNString("NAME"); bool isDeleted = false;
npc.load(esm);
if (id != "player") npc.load(esm, isDeleted);
if (npc.mId != "player")
{ {
// Handles changes to the NPC struct, but since there is no index here // 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 // 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. // "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 else
{ {
@ -142,9 +145,10 @@ public:
{ {
// See comment in ConvertNPC // See comment in ConvertNPC
ESM::Creature creature; ESM::Creature creature;
std::string id = esm.getHNString("NAME"); bool isDeleted = false;
creature.load(esm);
mContext->mCreatures[Misc::StringUtils::lowerCase(id)] = creature; creature.load(esm, isDeleted);
mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature;
} }
}; };
@ -157,18 +161,19 @@ class ConvertGlobal : public DefaultConverter<ESM::Global>
public: public:
virtual void read(ESM::ESMReader &esm) virtual void read(ESM::ESMReader &esm)
{ {
std::string id = esm.getHNString("NAME");
ESM::Global global; ESM::Global global;
global.load(esm); bool isDeleted = false;
if (Misc::StringUtils::ciEqual(id, "gamehour"))
global.load(esm, isDeleted);
if (Misc::StringUtils::ciEqual(global.mId, "gamehour"))
mContext->mHour = global.mValue.getFloat(); mContext->mHour = global.mValue.getFloat();
if (Misc::StringUtils::ciEqual(id, "day")) if (Misc::StringUtils::ciEqual(global.mId, "day"))
mContext->mDay = global.mValue.getInteger(); mContext->mDay = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(id, "month")) if (Misc::StringUtils::ciEqual(global.mId, "month"))
mContext->mMonth = global.mValue.getInteger(); mContext->mMonth = global.mValue.getInteger();
if (Misc::StringUtils::ciEqual(id, "year")) if (Misc::StringUtils::ciEqual(global.mId, "year"))
mContext->mYear = global.mValue.getInteger(); mContext->mYear = global.mValue.getInteger();
mRecords[id] = global; mRecords[global.mId] = global;
} }
}; };
@ -177,14 +182,14 @@ class ConvertClass : public DefaultConverter<ESM::Class>
public: public:
virtual void read(ESM::ESMReader &esm) virtual void read(ESM::ESMReader &esm)
{ {
std::string id = esm.getHNString("NAME");
ESM::Class class_; 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; mContext->mCustomPlayerClassName = class_.mName;
mRecords[id] = class_; mRecords[class_.mId] = class_;
} }
}; };
@ -193,13 +198,14 @@ class ConvertBook : public DefaultConverter<ESM::Book>
public: public:
virtual void read(ESM::ESMReader &esm) virtual void read(ESM::ESMReader &esm)
{ {
std::string id = esm.getHNString("NAME");
ESM::Book book; ESM::Book book;
book.load(esm); bool isDeleted = false;
book.load(esm, isDeleted);
if (book.mData.mSkillID == -1) 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: public:
virtual void read(ESM::ESMReader& esm) virtual void read(ESM::ESMReader& esm)
{ {
std::string id = esm.getHNString("NAME");
ESM::Faction faction; 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) 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); std::string faction2 = Misc::StringUtils::lowerCase(it->first);

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

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

@ -32,7 +32,8 @@ namespace ESSImport
item.mSCRI.load(esm); item.mSCRI.load(esm);
// for XSOL and XCHG seen so far, but probably others too // 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; int charge=-1;
esm.getHNOT(charge, "XHLT"); esm.getHNOT(charge, "XHLT");

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

@ -100,26 +100,17 @@ namespace CSMDoc
if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope) if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope)
return; return;
ESM::ESMWriter& writer = mState.getWriter();
CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState;
typename CollectionT::ESXRecord record = mCollection.getRecord (stage).get();
if (state==CSMWorld::RecordBase::State_Modified || if (state == CSMWorld::RecordBase::State_Modified ||
state==CSMWorld::RecordBase::State_ModifiedOnly) state == CSMWorld::RecordBase::State_ModifiedOnly ||
state == CSMWorld::RecordBase::State_Deleted)
{ {
// FIXME: A quick Workaround to support records which should not write writer.startRecord (record.sRecordId);
// NAME, including SKIL, MGEF and SCPT. If there are many more record.save (writer, state == CSMWorld::RecordBase::State_Deleted);
// idcollection records that doesn't use NAME then a more generic writer.endRecord (record.sRecordId);
// 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
} }
} }

@ -2,18 +2,15 @@
#include <sstream> #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); mId = mName;
if (isExterior())
if (!(mData.mFlags & Interior))
{ {
std::ostringstream stream; std::ostringstream stream;
stream << "#" << mData.mX << " " << mData.mY; stream << "#" << mData.mX << " " << mData.mY;
mId = stream.str(); mId = stream.str();
} }
} }

@ -16,7 +16,7 @@ namespace CSMWorld
{ {
std::string mId; 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> > template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
class Collection : public CollectionBase class Collection : public CollectionBase
{ {
public:
typedef ESXRecordT ESXRecord;
private:
std::vector<Record<ESXRecordT> > mRecords; std::vector<Record<ESXRecordT> > mRecords;
std::map<std::string, int> mIndex; std::map<std::string, int> mIndex;
std::vector<Column<ESXRecordT> *> mColumns; std::vector<Column<ESXRecordT> *> mColumns;

@ -1010,41 +1010,43 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
case ESM::REC_DIAL: case ESM::REC_DIAL:
{ {
std::string id = mReader->getHNOString ("NAME");
ESM::Dialogue record; ESM::Dialogue record;
record.mId = id; bool isDeleted = false;
record.load (*mReader);
if (record.mType==ESM::Dialogue::Journal) record.load (*mReader, isDeleted);
{
mJournals.load (record, mBase); if (isDeleted)
mDialogue = &mJournals.getRecord (id).get();
}
else if (record.mType==ESM::Dialogue::Deleted)
{ {
mDialogue = 0; // record vector can be shuffled around which would make pointer // record vector can be shuffled around which would make pointer to record invalid
// 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 else
{ {
messages.add (UniversalId::Type_None, 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); "", CSMDoc::Message::Severity_Warning);
} }
} }
else else
{ {
mTopics.load (record, mBase); if (record.mType == ESM::Dialogue::Journal)
mDialogue = &mTopics.getRecord (id).get(); {
mJournals.load (record, mBase);
mDialogue = &mJournals.getRecord (record.mId).get();
}
else
{
mTopics.load (record, mBase);
mDialogue = &mTopics.getRecord (record.mId).get();
}
} }
break; break;

@ -11,7 +11,7 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> > template<typename ESXRecordT, typename IdAccessorT = IdAccessor<ESXRecordT> >
class IdCollection : public Collection<ESXRecordT, IdAccessorT> 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: public:
@ -33,77 +33,46 @@ namespace CSMWorld
template<typename ESXRecordT, typename IdAccessorT> template<typename ESXRecordT, typename IdAccessorT>
void IdCollection<ESXRecordT, IdAccessorT>::loadRecord (ESXRecordT& record, 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> template<typename ESXRecordT, typename IdAccessorT>
int IdCollection<ESXRecordT, IdAccessorT>::load (ESM::ESMReader& reader, bool base) 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")) loadRecord (record, reader, isDeleted);
{
int index = Collection<ESXRecordT, IdAccessorT>::searchId (id);
reader.skipRecord(); std::string id = IdAccessorT().getId (record);
int index = this->searchId (id);
if (isDeleted)
{
if (index==-1) if (index==-1)
{ {
// deleting a record that does not exist // deleting a record that does not exist
// ignore it for now // ignore it for now
/// \todo report the problem to the user /// \todo report the problem to the user
} return -1;
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; if (base)
}
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)
{ {
std::string newId = IdAccessorT().getId(record); this->removeRows (index, 1);
int newIndex = this->searchId(newId); return -1;
if (newIndex != -1 && id != newId)
index = newIndex;
} }
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> 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) void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ESM::Dialogue& dialogue)
{ {
std::string id = Misc::StringUtils::lowerCase (dialogue.mId) + "#" + Info info;
reader.getHNOString ("INAM"); 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); int index = searchId (id);
reader.skipRecord();
if (index==-1) if (index==-1)
{ {
// deleting a record that does not exist // deleting a record that does not exist
// ignore it for now // ignore it for now
/// \todo report the problem to the user /// \todo report the problem to the user
} }
else if (base) else if (base)
@ -136,12 +135,9 @@ void CSMWorld::InfoCollection::load (ESM::ESMReader& reader, bool base, const ES
} }
else else
{ {
Info record; info.mTopicId = dialogue.mId;
record.mTopicId = dialogue.mId; info.mId = id;
record.mId = id; load (info, base);
record.load (reader);
load (record, base);
} }
} }
@ -193,3 +189,39 @@ CSMWorld::InfoCollection::Range CSMWorld::InfoCollection::getTopicRange (const s
return Range (begin, end); 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; Range getTopicRange (const std::string& topic) const;
///< Return iterators that point to the beginning and past the end of the range for ///< Return iterators that point to the beginning and past the end of the range for
/// the given topic. /// the given topic.
void removeDialogueInfos(const std::string& dialogueId);
}; };
} }

@ -4,13 +4,12 @@
namespace CSMWorld 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; std::ostringstream stream;
stream << "#" << mX << " " << mY; stream << "#" << mX << " " << mY;
mId = stream.str(); mId = stream.str();
} }
} }

@ -10,13 +10,12 @@ namespace CSMWorld
/// \brief Wrapper for Land record. Encodes X and Y cell index in the ID. /// \brief Wrapper for Land record. Encodes X and Y cell index in the ID.
/// ///
/// \todo Add worldspace support to the Land record. /// \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 struct Land : public ESM::Land
{ {
std::string mId; std::string mId;
/// Loads the metadata and ID /// Loads the metadata and ID
void load (ESM::ESMReader &esm); void load (ESM::ESMReader &esm, bool &isDeleted);
}; };
} }

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

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

@ -4,33 +4,28 @@
#include <sstream> #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 // correct ID
if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1) if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1)
{ {
std::ostringstream stream; std::ostringstream stream;
stream << "#" << mData.mX << " " << mData.mY; stream << "#" << mData.mX << " " << mData.mY;
mId = stream.str(); 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()) if (mCell.empty())
{ {
std::ostringstream stream; std::ostringstream stream;
stream << "#" << mData.mX << " " << mData.mY; stream << "#" << mData.mX << " " << mData.mY;
mId = stream.str(); mId = stream.str();
} }
else
mId = mCell;
} }

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

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

@ -3,10 +3,20 @@
#include <cassert> #include <cassert>
#include <memory> #include <memory>
#include <components/misc/stringops.hpp>
CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} 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() CSMWorld::RefIdData::RefIdData()
{ {
mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators)); mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators));
@ -161,15 +171,27 @@ int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const
return index; 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 = std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator found =
mRecordContainers.find (index.second); mRecordContainers.find (type);
if (iter==mRecordContainers.end()) if (found == mRecordContainers.end())
throw std::logic_error ("invalid local index type"); 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) void CSMWorld::RefIdData::erase (const LocalIndex& index, int count)

@ -25,6 +25,8 @@
#include <components/esm/loadmisc.hpp> #include <components/esm/loadmisc.hpp>
#include <components/esm/esmwriter.hpp> #include <components/esm/esmwriter.hpp>
#include <components/misc/stringops.hpp>
#include "record.hpp" #include "record.hpp"
#include "universalid.hpp" #include "universalid.hpp"
@ -49,7 +51,8 @@ namespace CSMWorld
virtual void insertRecord (RecordBase& record) = 0; 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; virtual void erase (int index, int count) = 0;
@ -73,7 +76,8 @@ namespace CSMWorld
virtual void insertRecord (RecordBase& record); 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); virtual void erase (int index, int count);
@ -122,9 +126,58 @@ namespace CSMWorld
} }
template<typename RecordT> 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> template<typename RecordT>
@ -145,19 +198,14 @@ namespace CSMWorld
template<typename RecordT> template<typename RecordT>
void RefIdDataContainer<RecordT>::save (int index, ESM::ESMWriter& writer) const 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 || if (record.isModified() || record.mState == RecordBase::State_Deleted)
state==CSMWorld::RecordBase::State_ModifiedOnly)
{ {
writer.startRecord (mContainer.at (index).mModified.sRecordId); RecordT esmRecord = record.get();
writer.writeHNCString ("NAME", getId (index)); writer.startRecord(esmRecord.sRecordId);
mContainer.at (index).mModified.save (writer); esmRecord.save(writer, record.mState == RecordBase::State_Deleted);
writer.endRecord (mContainer.at (index).mModified.sRecordId); writer.endRecord(esmRecord.sRecordId);
}
else if (state==CSMWorld::RecordBase::State_Deleted)
{
/// \todo write record with delete flag
} }
} }
@ -198,6 +246,8 @@ namespace CSMWorld
void erase (const LocalIndex& index, int count); void erase (const LocalIndex& index, int count);
///< Must not spill over into another type. ///< Must not spill over into another type.
std::string getRecordId(const LocalIndex &index) const;
public: public:
RefIdData(); RefIdData();
@ -221,7 +271,7 @@ namespace CSMWorld
int getAppendIndex (UniversalId::Type type) const; 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; int getSize() const;

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

@ -475,7 +475,7 @@ namespace MWWorld
switch (store.find (ref.mRefID)) switch (store.find (ref.mRefID))
{ {
case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; 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_APPA: mAppas.load(ref, deleted, store); break;
case ESM::REC_ARMO: mArmors.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; 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()); throw std::runtime_error(error.str());
} }
} else { } else {
// Load it RecordId id = it->second->load(esm);
std::string id = esm.getHNOString("NAME"); if (id.mIsDeleted)
// ... unless it got deleted! This means that the following record {
// has been deleted, and trying to load it using standard assumptions it->second->eraseStatic(id.mId);
// on the structure will (probably) fail. continue;
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;
} }
if (n.val==ESM::REC_DIAL) { if (n.val==ESM::REC_DIAL) {
dialogue = const_cast<ESM::Dialogue*>(mDialogs.find(id)); dialogue = const_cast<ESM::Dialogue*>(mDialogs.find(id.mId));
} else { } else {
dialogue = 0; dialogue = 0;
} }
// Insert the reference into the global lookup // Insert the reference into the global lookup
if (!id.empty() && isCacheableRecord(n.val)) { if (!id.mId.empty() && isCacheableRecord(n.val)) {
mIds[Misc::StringUtils::lowerCase (id)] = n.val; mIds[Misc::StringUtils::lowerCase (id.mId)] = n.val;
} }
} }
listener->setProgress(static_cast<size_t>(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); listener->setProgress(static_cast<size_t>(esm.getFileOffset() / (float)esm.getFileSize() * 1000));
@ -195,13 +183,12 @@ void ESMStore::setUp()
case ESM::REC_LEVC: case ESM::REC_LEVC:
{ {
std::string id = reader.getHNString ("NAME"); RecordId id = mStores[type]->read (reader);
mStores[type]->read (reader, id);
// FIXME: there might be stale dynamic IDs in mIds from an earlier savegame // FIXME: there might be stale dynamic IDs in mIds from an earlier savegame
// that really should be cleared instead of just overwritten // that really should be cleared instead of just overwritten
mIds[id] = type; mIds[id.mId] = type;
} }
if (type==ESM::REC_NPC_) if (type==ESM::REC_NPC_)

@ -13,7 +13,7 @@ namespace MWWorld
{ {
Globals::Collection::const_iterator Globals::find (const std::string& name) const 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()) if (iter==mVariables.end())
throw std::runtime_error ("unknown global variable: " + name); throw std::runtime_error ("unknown global variable: " + name);
@ -23,7 +23,7 @@ namespace MWWorld
Globals::Collection::iterator Globals::find (const std::string& name) 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()) if (iter==mVariables.end())
throw std::runtime_error ("unknown global variable: " + name); 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(); for (MWWorld::Store<ESM::Global>::iterator iter = globals.begin(); iter!=globals.end();
++iter) ++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 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) 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 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()) if (iter==mVariables.end())
return ' '; return ' ';
switch (iter->second.getType()) switch (iter->second.mValue.getType())
{ {
case ESM::VT_Short: return 's'; case ESM::VT_Short: return 's';
case ESM::VT_Long: return 'l'; case ESM::VT_Long: return 'l';
@ -81,8 +81,7 @@ namespace MWWorld
for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter)
{ {
writer.startRecord (ESM::REC_GLOB); writer.startRecord (ESM::REC_GLOB);
writer.writeHNString ("NAME", iter->first); iter->second.save (writer);
iter->second.write (writer, ESM::Variant::Format_Global);
writer.endRecord (ESM::REC_GLOB); writer.endRecord (ESM::REC_GLOB);
} }
} }
@ -91,14 +90,17 @@ namespace MWWorld
{ {
if (type==ESM::REC_GLOB) 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()) if (iter!=mVariables.end())
iter->second.read (reader, ESM::Variant::Format_Global); iter->second = global;
else
reader.skipRecord();
return true; return true;
} }

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

@ -42,6 +42,10 @@ namespace
namespace MWWorld namespace MWWorld
{ {
RecordId::RecordId(const std::string &id, bool isDeleted)
: mId(id), mIsDeleted(isDeleted)
{}
template<typename T> template<typename T>
IndexedStore<T>::IndexedStore() IndexedStore<T>::IndexedStore()
{ {
@ -60,7 +64,9 @@ namespace MWWorld
void IndexedStore<T>::load(ESM::ESMReader &esm) void IndexedStore<T>::load(ESM::ESMReader &esm)
{ {
T record; T record;
record.load(esm); bool isDeleted = false;
record.load(esm, isDeleted);
// Try to overwrite existing record // Try to overwrite existing record
std::pair<typename Static::iterator, bool> ret = mStatic.insert(std::make_pair(record.mIndex, record)); std::pair<typename Static::iterator, bool> ret = mStatic.insert(std::make_pair(record.mIndex, record));
@ -178,16 +184,21 @@ namespace MWWorld
return ptr; return ptr;
} }
template<typename T> 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) if (inserted.second)
mShared.push_back(&inserted.first->second); mShared.push_back(&inserted.first->second);
else
inserted.first->second = record;
inserted.first->second.mId = idLower; return RecordId(record.mId, isDeleted);
inserted.first->second.load(esm);
} }
template<typename T> template<typename T>
void Store<T>::setUp() void Store<T>::setUp()
@ -309,20 +320,21 @@ namespace MWWorld
++iter) ++iter)
{ {
writer.startRecord (T::sRecordId); writer.startRecord (T::sRecordId);
writer.writeHNString ("NAME", iter->second.mId);
iter->second.save (writer); iter->second.save (writer);
writer.endRecord (T::sRecordId); writer.endRecord (T::sRecordId);
} }
} }
template<typename T> template<typename T>
void Store<T>::read(ESM::ESMReader& reader, const std::string& id) RecordId Store<T>::read(ESM::ESMReader& reader)
{ {
T record; T record;
record.mId = id; bool isDeleted = false;
record.load (reader);
record.load (reader, isDeleted);
insert (record); insert (record);
}
return RecordId(record.mId, isDeleted);
}
// LandTexture // LandTexture
//========================================================================= //=========================================================================
@ -361,11 +373,12 @@ namespace MWWorld
assert(plugin < mStatic.size()); assert(plugin < mStatic.size());
return mStatic[plugin].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; ESM::LandTexture lt;
lt.load(esm); bool isDeleted = false;
lt.mId = id;
lt.load(esm, isDeleted);
// Make sure we have room for the structure // Make sure we have room for the structure
if (plugin >= mStatic.size()) { if (plugin >= mStatic.size()) {
@ -377,10 +390,12 @@ namespace MWWorld
// Store it // Store it
ltexl[lt.mIndex] = lt; 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 Store<ESM::LandTexture>::iterator Store<ESM::LandTexture>::begin(size_t plugin) const
{ {
@ -392,7 +407,6 @@ namespace MWWorld
assert(plugin < mStatic.size()); assert(plugin < mStatic.size());
return mStatic[plugin].end(); return mStatic[plugin].end();
} }
// Land // Land
//========================================================================= //=========================================================================
@ -440,10 +454,12 @@ namespace MWWorld
} }
return ptr; 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(); 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 // 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? // 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); mStatic.push_back(ptr);
return RecordId("", isDeleted);
} }
void Store<ESM::Land>::setUp() void Store<ESM::Land>::setUp()
{ {
@ -600,7 +618,7 @@ namespace MWWorld
mSharedExt.push_back(&(it->second)); 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, // 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, // 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! // are not available until both cells have been loaded at least partially!
// All cells have a name record, even nameless exterior cells. // All cells have a name record, even nameless exterior cells.
std::string idLower = Misc::StringUtils::lowerCase(id);
ESM::Cell cell; 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 // 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) if(cell.mData.mFlags & ESM::Cell::Interior)
{ {
@ -682,6 +700,8 @@ namespace MWWorld
mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; 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 Store<ESM::Cell>::iterator Store<ESM::Cell>::intBegin() const
{ {
@ -837,10 +857,12 @@ namespace MWWorld
{ {
mCells = &cells; 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; 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. // 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. // 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) if (!ret.second)
ret.first->second = pathgrid; ret.first->second = pathgrid;
} }
return RecordId("", isDeleted);
} }
size_t Store<ESM::Pathgrid>::getSize() const size_t Store<ESM::Pathgrid>::getSize() const
{ {
@ -1013,51 +1037,29 @@ namespace MWWorld
} }
template <> template <>
void Store<ESM::Dialogue>::load(ESM::ESMReader &esm, const std::string &id) { inline RecordId Store<ESM::Dialogue>::load(ESM::ESMReader &esm) {
std::string idLower = Misc::StringUtils::lowerCase(id); // The original letter case of a dialogue ID is saved, because it's printed
ESM::Dialogue dialogue;
std::map<std::string, ESM::Dialogue>::iterator it = mStatic.find(idLower); bool isDeleted = false;
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);
}
dialogue.loadId(esm);
// Script std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId);
//========================================================================= std::map<std::string, ESM::Dialogue>::iterator found = mStatic.find(idLower);
if (found == mStatic.end())
template <> {
void Store<ESM::Script>::load(ESM::ESMReader &esm, const std::string &id) { dialogue.loadData(esm, isDeleted);
ESM::Script scpt; mStatic.insert(std::make_pair(idLower, dialogue));
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);
else else
inserted.first->second = scpt; {
} found->second.loadData(esm, isDeleted);
dialogue = found->second;
}
// StartScript
//=========================================================================
template <> return RecordId(dialogue.mId, isDeleted);
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;
} }
} }
template class MWWorld::Store<ESM::Activator>; 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::Ingredient>;
template class MWWorld::Store<ESM::ItemLevList>; template class MWWorld::Store<ESM::ItemLevList>;
//template class MWWorld::Store<ESM::Land>; //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::Light>;
template class MWWorld::Store<ESM::Lockpick>; template class MWWorld::Store<ESM::Lockpick>;
//template class MWWorld::Store<ESM::MagicEffect>; //template class MWWorld::Store<ESM::MagicEffect>;

@ -19,6 +19,14 @@ namespace Loading
namespace MWWorld namespace MWWorld
{ {
struct RecordId
{
std::string mId;
bool mIsDeleted;
RecordId(const std::string &id = "", bool isDeleted = false);
};
struct StoreBase struct StoreBase
{ {
virtual ~StoreBase() {} virtual ~StoreBase() {}
@ -28,14 +36,14 @@ namespace MWWorld
virtual size_t getSize() const = 0; virtual size_t getSize() const = 0;
virtual int getDynamicSize() const { return 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 bool eraseStatic(const std::string &id) {return false;}
virtual void clearDynamic() {} virtual void clearDynamic() {}
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} 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 ///< Read into dynamic storage
}; };
@ -180,9 +188,9 @@ namespace MWWorld
bool erase(const std::string &id); bool erase(const std::string &id);
bool erase(const T &item); 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 write(ESM::ESMWriter& writer, Loading::Listener& progress) const;
void read(ESM::ESMReader& reader, const std::string& id); RecordId read(ESM::ESMReader& reader);
}; };
template <> template <>
@ -205,8 +213,8 @@ namespace MWWorld
size_t getSize() const; size_t getSize() const;
size_t getSize(size_t plugin) const; size_t getSize(size_t plugin) const;
void load(ESM::ESMReader &esm, const std::string &id, size_t plugin); RecordId load(ESM::ESMReader &esm, size_t plugin);
void load(ESM::ESMReader &esm, const std::string &id); RecordId load(ESM::ESMReader &esm);
iterator begin(size_t plugin) const; iterator begin(size_t plugin) const;
iterator end(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 *search(int x, int y) const;
ESM::Land *find(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(); void setUp();
}; };
@ -281,7 +289,7 @@ namespace MWWorld
void setUp(); void setUp();
void load(ESM::ESMReader &esm, const std::string &id); RecordId load(ESM::ESMReader &esm);
iterator intBegin() const; iterator intBegin() const;
iterator intEnd() const; iterator intEnd() const;
@ -323,7 +331,7 @@ namespace MWWorld
Store(); Store();
void setCells(Store<ESM::Cell>& cells); void setCells(Store<ESM::Cell>& cells);
void load(ESM::ESMReader &esm, const std::string &id); RecordId load(ESM::ESMReader &esm);
size_t getSize() const; size_t getSize() const;
void setUp(); 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); loadId(esm, wideRefNum);
loadData(esm); loadData(esm, isDeleted);
} }
void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum)
@ -39,71 +39,98 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum)
if (esm.isNextSub ("NAM0")) if (esm.isNextSub ("NAM0"))
esm.skipHSub(); esm.skipHSub();
blank();
mRefNum.load (esm, wideRefNum); mRefNum.load (esm, wideRefNum);
mRefID = esm.getHNString ("NAME"); 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. isDeleted = false;
// 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");
// Present for doors that teleport you to another cell. bool isLoaded = false;
if (esm.isNextSub ("DODT")) while (!isLoaded && esm.hasMoreSubs())
{ {
mTeleport = true; esm.getSubName();
esm.getHT (mDoorDest); switch (esm.retSubName().val)
mDestCell = esm.getHNOString ("DNAM"); {
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); mRefNum.save (esm, wideRefNum);
esm.writeHNCString("NAME", mRefID); esm.writeHNCString("NAME", mRefID);
if (isDeleted) {
esm.writeHNCString("DELE", "");
return;
}
if (mScale != 1.0) { if (mScale != 1.0) {
esm.writeHNT("XSCL", mScale); esm.writeHNT("XSCL", mScale);
} }
@ -134,7 +161,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons
} }
if (!inInventory && mLockLevel != 0) { if (!inInventory && mLockLevel != 0) {
esm.writeHNT("FLTV", mLockLevel); esm.writeHNT("FLTV", mLockLevel);
} }
if (!inInventory) if (!inInventory)
@ -153,7 +180,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons
void ESM::CellRef::blank() void ESM::CellRef::blank()
{ {
mRefNum.unset(); mRefNum.unset();
mRefID.clear(); mRefID.clear();
mScale = 1; mScale = 1;
mOwner.clear(); mOwner.clear();
mGlobalVariable.clear(); mGlobalVariable.clear();
@ -169,7 +196,7 @@ void ESM::CellRef::blank()
mTrap.clear(); mTrap.clear();
mReferenceBlocked = -1; mReferenceBlocked = -1;
mTeleport = false; mTeleport = false;
for (int i=0; i<3; ++i) for (int i=0; i<3; ++i)
{ {
mDoorDest.pos[i] = 0; mDoorDest.pos[i] = 0;

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

@ -6,15 +6,48 @@
unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; 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"); isDeleted = false;
mScriptText = esm.getHNString ("SCRP");
esm.getHNT (mFlags, "FLAG"); 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 ("DESC", mDescription);
esm.writeHNCString ("SCRP", mScriptText); esm.writeHNCString ("SCRP", mScriptText);
esm.writeHNT ("FLAG", mFlags); esm.writeHNT ("FLAG", mFlags);

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

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

@ -185,6 +185,9 @@ public:
bool peekNextSub(const char* name); 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 // Read subrecord name. This gets called a LOT, so I've optimized it
// slightly. // slightly.
void getSubName(); void getSubName();

@ -6,14 +6,46 @@
unsigned int ESM::Filter::sRecordId = REC_FILT; 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"); isDeleted = false;
mDescription = esm.getHNString ("DESC");
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 ("FILT", mFilter);
esm.writeHNCString ("DESC", mDescription); esm.writeHNCString ("DESC", mDescription);
} }

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

@ -8,14 +8,20 @@ namespace ESM
{ {
unsigned int Activator::sRecordId = REC_ACTI; 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()) while (esm.hasMoreSubs())
{ {
esm.getSubName(); esm.getSubName();
uint32_t name = esm.retSubName().val; switch (esm.retSubName().val)
switch (name)
{ {
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value: case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString(); mModel = esm.getHString();
break; break;
@ -25,13 +31,29 @@ namespace ESM
case ESM::FourCC<'S','C','R','I'>::value: case ESM::FourCC<'S','C','R','I'>::value:
mScript = esm.getHString(); mScript = esm.getHString();
break; break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default: default:
esm.fail("Unknown subrecord"); 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.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("SCRI", mScript);

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -52,173 +52,200 @@ namespace ESM
return ref.mRefNum == refNum; return ref.mRefNum == refNum;
} }
void Cell::load(ESMReader &esm, bool saveContext) void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext)
{ {
loadData(esm); loadNameAndData(esm, isDeleted);
loadCell(esm, saveContext); loadCell(esm, saveContext);
} }
void Cell::loadCell(ESMReader &esm, bool saveContext)
{
mRefNumCounter = 0;
if (mData.mFlags & Interior) void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted)
{ {
// Interior cells isDeleted = false;
if (esm.isNextSub("INTV"))
{ blank();
int waterl;
esm.getHT(waterl); bool hasData = false;
mWater = (float) waterl; bool isLoaded = false;
mWaterInt = true; while (!isLoaded && esm.hasMoreSubs())
}
else if (esm.isNextSub("WHGT"))
{ {
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 if (!hasData)
// weather), pure interior cells have ambient lighting esm.fail("Missing DATA subrecord");
// instead.
if (mData.mFlags & QuasiEx)
mRegion = esm.getHNOString("RGNN");
else if (esm.isNextSub("AMBI"))
esm.getHT(mAmbi);
} }
else
void Cell::loadCell(ESMReader &esm, bool saveContext)
{ {
// Exterior cells bool isLoaded = false;
mRegion = esm.getHNOString("RGNN"); 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; if (saveContext)
esm.getHNOT(mMapColor, "NAM5"); {
} mContextList.push_back(esm.getContext());
if (esm.isNextSub("NAM0")) { esm.skipRecord();
esm.getHT(mRefNumCounter); }
} }
if (saveContext) { void Cell::postLoad(ESMReader &esm)
{
// Save position of the cell references and move on
mContextList.push_back(esm.getContext()); mContextList.push_back(esm.getContext());
esm.skipRecord(); 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::save(ESMWriter &esm, bool isDeleted) const
}
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)
{ {
if (mWaterInt) { esm.writeHNOCString("NAME", mName);
int water = esm.writeHNT("DATA", mData, 12);
(mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5);
esm.writeHNT("INTV", water); if (isDeleted)
} else { {
esm.writeHNT("WHGT", mWater); esm.writeHNCString("DELE", "");
return;
} }
if (mData.mFlags & QuasiEx) if (mData.mFlags & Interior)
esm.writeHNOCString("RGNN", mRegion); {
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 else
esm.writeHNT("AMBI", mAmbi, 16); {
} esm.writeHNOCString("RGNN", mRegion);
else if (mMapColor != 0)
{ esm.writeHNT("NAM5", mMapColor);
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));
}
std::string Cell::getDescription() const if (mRefNumCounter != 0)
{ esm.writeHNT("NAM0", mRefNumCounter);
if (mData.mFlags & Interior)
{
return mName;
} }
else
void Cell::restore(ESMReader &esm, int iCtx) const
{ {
std::ostringstream stream; esm.restoreContext(mContextList.at (iCtx));
stream << mData.mX << ", " << mData.mY;
return stream.str();
} }
}
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 std::string Cell::getDescription() const
// more plugins, and how they treat these moved references.
if (esm.isNextSub("MVRF"))
{ {
if (ignoreMoves) if (mData.mFlags & Interior)
{ {
esm.getHT (mref->mRefNum.mIndex); return mName;
esm.getHNOT (mref->mTarget, "CNDT");
adjustRefNum (mref->mRefNum, esm);
} }
else else
{ {
// skip rest of cell record (moved references), they are handled elsewhere std::ostringstream stream;
esm.skipRecord(); // skip MVRF, CNDT stream << mData.mX << ", " << mData.mY;
return false; 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. // TODO: Try and document reference numbering, I don't think this has been done anywhere else.
adjustRefNum (ref.mRefNum, esm); if (!esm.hasMoreSubs())
return false;
if (esm.isNextSub("DELE")) // 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.
esm.skipHSub(); if (esm.isNextSub("MVRF"))
deleted = true; {
} if (ignoreMoves)
else {
deleted = false; 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) // Identify references belonging to a parent file and adapt the ID accordingly.
{ adjustRefNum (ref.mRefNum, esm);
esm.getHT(mref.mRefNum.mIndex); return true;
esm.getHNOT(mref.mTarget, "CNDT"); }
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() void Cell::blank()
{ {

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

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

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

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

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

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

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

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

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

@ -10,121 +10,150 @@ namespace ESM
{ {
unsigned int Dialogue::sRecordId = REC_DIAL; unsigned int Dialogue::sRecordId = REC_DIAL;
void Dialogue::load(ESMReader &esm) void Dialogue::load(ESMReader &esm, bool &isDeleted)
{
esm.getSubNameIs("DATA");
esm.getSubHeader();
int si = esm.getSubSize();
if (si == 1)
esm.getT(mType);
else if (si == 4)
{ {
// These are just markers, their values are not used. loadId(esm);
int i; loadData(esm, isDeleted);
esm.getT(i);
esm.getHNT(i, "DELE");
mType = Deleted;
} }
else
esm.fail("Unknown sub record size");
}
void Dialogue::save(ESMWriter &esm) const void Dialogue::loadId(ESMReader &esm)
{
if (mType != Deleted)
esm.writeHNT("DATA", mType);
else
{ {
esm.writeHNT("DATA", (int)1); mId = esm.getHNString("NAME");
esm.writeHNT("DELE", (int)1);
} }
}
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; isDeleted = false;
info.mId = id;
info.load(esm); while (esm.hasMoreSubs())
mLookup[id] = mInfo.insert(mInfo.end(), info); {
return; 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(); void Dialogue::save(ESMWriter &esm, bool isDeleted) const
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
{ {
info.mId = id; esm.writeHNCString("NAME", mId);
info.load(esm); if (isDeleted)
{
esm.writeHNCString("DELE", "");
}
else
{
esm.writeHNT("DATA", mType);
}
} }
if (info.mNext.empty()) void Dialogue::blank()
{
mLookup[id] = mInfo.insert(mInfo.end(), info);
return;
}
if (info.mPrev.empty())
{ {
mLookup[id] = mInfo.insert(mInfo.begin(), info); mInfo.clear();
return;
} }
lookup = mLookup.find(info.mPrev); void Dialogue::readInfo(ESMReader &esm, bool merge)
if (lookup != mLookup.end())
{ {
it = lookup->second; ESM::DialInfo info;
info.loadId(esm);
mLookup[id] = mInfo.insert(++it, info); bool isDeleted = false;
return; 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); return;
if (lookup != mLookup.end()) }
{
it = lookup->second;
mLookup[id] = mInfo.insert(it, info); InfoContainer::iterator it = mInfo.end();
return;
}
std::cerr << "Failed to insert info " << id << std::endl; LookupMap::iterator lookup;
} lookup = mLookup.find(info.mId);
void Dialogue::clearDeletedInfos() if (lookup != mLookup.end())
{ {
for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) it = lookup->second.first;
{
if (it->mQuestStatus == DialInfo::QS_Deleted) // Merge with existing record. Only the subrecords that are present in
it = mInfo.erase(it); // 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 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, Greeting = 2,
Persuasion = 3, Persuasion = 3,
Journal = 4, Journal = 4,
Deleted = -1 Unknown = -1 // Used for deleted dialogues
}; };
std::string mId; std::string mId;
@ -39,17 +39,24 @@ struct Dialogue
typedef std::list<DialInfo> InfoContainer; 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; InfoContainer mInfo;
// This is only used during the loading phase to speed up DialInfo merging. // This is only used during the loading phase to speed up DialInfo merging.
LookupMap mLookup; LookupMap mLookup;
void load(ESMReader &esm); void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm) const; ///< 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(); void clearDeletedInfos();
/// Read the next info record /// Read the next info record

@ -8,14 +8,20 @@ namespace ESM
{ {
unsigned int Door::sRecordId = REC_DOOR; 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()) while (esm.hasMoreSubs())
{ {
esm.getSubName(); esm.getSubName();
uint32_t name = esm.retSubName().val; switch (esm.retSubName().val)
switch (name)
{ {
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value: case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString(); mModel = esm.getHString();
break; break;
@ -31,14 +37,30 @@ namespace ESM
case ESM::FourCC<'A','N','A','M'>::value: case ESM::FourCC<'A','N','A','M'>::value:
mCloseSound = esm.getHString(); mCloseSound = esm.getHString();
break; break;
case ESM::FourCC<'D','E','L','E'>::value:
esm.skipHSub();
isDeleted = true;
break;
default: default:
esm.fail("Unknown subrecord"); 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.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNOCString("SCRI", mScript); esm.writeHNOCString("SCRI", mScript);

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

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

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

@ -26,69 +26,92 @@ namespace ESM
return mSkills[index]; return mSkills[index];
} }
void Faction::load(ESMReader &esm) void Faction::load(ESMReader &esm, bool &isDeleted)
{
mReactions.clear();
for (int i=0;i<10;++i)
mRanks[i].clear();
int rankCounter=0;
bool hasData = false;
while (esm.hasMoreSubs())
{ {
esm.getSubName(); isDeleted = false;
uint32_t name = esm.retSubName().val;
switch (name) 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: esm.getSubName();
mName = esm.getHString(); switch (esm.retSubName().val)
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(); case ESM::FourCC<'N','A','M','E'>::value:
int reaction; mId = esm.getHString();
esm.getHNT(reaction, "INTV"); hasName = true;
mReactions[faction] = reaction; break;
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()) esm.writeHNCString("NAME", mId);
break;
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) for (int i = 0; i < 10; i++)
{ {
esm.writeHNString("ANAM", it->first); if (mRanks[i].empty())
esm.writeHNT("INTV", it->second); 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() void Faction::blank()
{ {

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

@ -1,19 +1,42 @@
#include "loadglob.hpp" #include "loadglob.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "defs.hpp" #include "defs.hpp"
namespace ESM namespace ESM
{ {
unsigned int Global::sRecordId = REC_GLOB; 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() void Global::blank()

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

@ -1,18 +1,24 @@
#include "loadgmst.hpp" #include "loadgmst.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "defs.hpp" #include "defs.hpp"
namespace ESM namespace ESM
{ {
unsigned int GameSetting::sRecordId = REC_GMST; 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); 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); mValue.write (esm, ESM::Variant::Format_Gmst);
} }

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

@ -8,156 +8,140 @@ namespace ESM
{ {
unsigned int DialInfo::sRecordId = REC_INFO; unsigned int DialInfo::sRecordId = REC_INFO;
void DialInfo::load(ESMReader &esm) void DialInfo::load(ESMReader &esm, bool &isDeleted)
{
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)
{ {
mActor = esm.getHString(); loadId(esm);
if (esm.isEmptyOrGetName()) loadData(esm, isDeleted);
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;
} }
if (subName.val == REC_FNAM) void DialInfo::loadId(ESMReader &esm)
{ {
mFaction = esm.getHString(); mId = esm.getHNString("INAM");
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;
} }
while (subName.val == REC_SCVR) void DialInfo::loadData(ESMReader &esm, bool &isDeleted)
{ {
SelectStruct ss; isDeleted = false;
ss.mSelectRule = esm.getHString(); mQuestStatus = QS_None;
mFactionLess = false;
ss.mValue.read (esm, Variant::Format_Info);
mSelects.push_back(ss); mPrev = esm.getHNString("PNAM");
mNext = esm.getHNString("NNAM");
if (esm.isEmptyOrGetName()) // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings
return; mSelects.clear();
}
if (subName.val == REC_BNAM) while (esm.hasMoreSubs())
{ {
mResultScript = esm.getHString(); esm.getSubName();
if (esm.isEmptyOrGetName()) switch (esm.retSubName().val)
return; {
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) void DialInfo::save(ESMWriter &esm, bool isDeleted) const
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)
{ {
esm.writeHNString("SCVR", it->mSelectRule); esm.writeHNCString("INAM", mId);
it->mValue.write (esm, Variant::Format_Info); esm.writeHNCString("PNAM", mPrev);
} esm.writeHNCString("NNAM", mNext);
esm.writeHNOString("BNAM", mResultScript);
switch(mQuestStatus) if (isDeleted)
{ {
case QS_Name: esm.writeHNT("QSTN",'\1'); break; esm.writeHNCString("DELE", "");
case QS_Finished: esm.writeHNT("QSTF", '\1'); break; return;
case QS_Restart: esm.writeHNT("QSTR", '\1'); break; }
case QS_Deleted: esm.writeHNT("DELE", '\1'); break;
default: break; 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() void DialInfo::blank()
{ {

@ -59,8 +59,7 @@ struct DialInfo
QS_None = 0, QS_None = 0,
QS_Name = 1, QS_Name = 1,
QS_Finished = 2, QS_Finished = 2,
QS_Restart = 3, QS_Restart = 3
QS_Deleted
}; };
// Rules for when to include this item in the final list of options // Rules for when to include this item in the final list of options
@ -106,8 +105,14 @@ struct DialInfo
REC_DELE = 0x454c4544 REC_DELE = 0x454c4544
}; };
void load(ESMReader &esm); void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm) const; ///< 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(); void blank();
///< Set record to default state (does not touch the ID). ///< Set record to default state (does not touch the ID).

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

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

@ -10,212 +10,251 @@ namespace ESM
{ {
unsigned int Land::sRecordId = REC_LAND; unsigned int Land::sRecordId = REC_LAND;
void Land::LandData::save(ESMWriter &esm) const void Land::LandData::save(ESMWriter &esm) const
{ {
if (mDataTypes & Land::DATA_VNML) { if (mDataTypes & Land::DATA_VNML) {
esm.writeHNT("VNML", mNormals, sizeof(mNormals)); esm.writeHNT("VNML", mNormals, sizeof(mNormals));
} }
if (mDataTypes & Land::DATA_VHGT) { if (mDataTypes & Land::DATA_VHGT) {
VHGT offsets; VHGT offsets;
offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE;
offsets.mUnk1 = mUnk1; offsets.mUnk1 = mUnk1;
offsets.mUnk2 = mUnk2; offsets.mUnk2 = mUnk2;
float prevY = mHeights[0]; float prevY = mHeights[0];
int number = 0; // avoid multiplication int number = 0; // avoid multiplication
for (int i = 0; i < LAND_SIZE; ++i) { for (int i = 0; i < LAND_SIZE; ++i) {
float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; 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;
offsets.mHeightData[number] = offsets.mHeightData[number] =
(diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5);
prevX = mHeights[number]; float prevX = prevY = mHeights[number];
++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]; void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out)
transposeTextureData(mTextures, vtex); {
esm.writeHNT("VTEX", vtex, sizeof(vtex)); 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) Land::~Land()
{ {
int readPos = 0; //bit ugly, but it works delete mLandData;
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() void Land::load(ESMReader &esm, bool &isDeleted)
: mFlags(0) {
, mX(0) isDeleted = false;
, mY(0)
, mPlugin(0)
, mEsm(NULL)
, mDataTypes(0)
, mDataLoaded(false)
, mLandData(NULL)
{
}
Land::~Land() mEsm = &esm;
{ mPlugin = mEsm->getIndex();
delete mLandData;
}
void Land::load(ESMReader &esm) bool hasLocation = false;
{ bool isLoaded = false;
mEsm = &esm; while (!isLoaded && esm.hasMoreSubs())
mPlugin = mEsm->getIndex(); {
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 if (!hasLocation)
esm.getSubNameIs("INTV"); esm.fail("Missing INTV subrecord");
esm.getSubHeaderIs(8);
esm.getT<int>(mX);
esm.getT<int>(mY);
esm.getHNT(mFlags, "DATA"); mContext = esm.getContext();
// Store the file position // Skip the land data here. Load it when the cell is loaded.
mContext = esm.getContext(); 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. mDataLoaded = 0;
if (esm.isNextSub("VNML")) mLandData = NULL;
{
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; void Land::save(ESMWriter &esm, bool isDeleted) const
mLandData = NULL; {
} esm.startSubRecord("INTV");
esm.writeT(mX);
esm.writeT(mY);
esm.endRecord("INTV");
void Land::save(ESMWriter &esm) const esm.writeHNT("DATA", mFlags);
{
esm.startSubRecord("INTV");
esm.writeT(mX);
esm.writeT(mY);
esm.endRecord("INTV");
esm.writeHNT("DATA", mFlags); if (isDeleted)
} {
esm.writeHNCString("DELE", "");
return;
}
void Land::loadData(int flags) const if (mLandData)
{ {
// Try to load only available data mLandData->save(esm);
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("VNML")) { void Land::loadData(int flags) const
condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); {
} // 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")) { if (mEsm->isNextSub("VNML")) {
static VHGT vhgt; condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals));
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];
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; mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE;
for (int x = 1; x < LAND_SIZE; x++) {
colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; float colOffset = rowOffset;
mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; 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")) { if (mEsm->isNextSub("WNAM")) {
condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); condLoad(flags, DATA_WNAM, mLandData->mWnam, 81);
} }
if (mEsm->isNextSub("VCLR")) if (mEsm->isNextSub("VCLR"))
condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS);
if (mEsm->isNextSub("VTEX")) { if (mEsm->isNextSub("VTEX")) {
static uint16_t vtex[LAND_NUM_TEXTURES]; static uint16_t vtex[LAND_NUM_TEXTURES];
if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) {
LandData::transposeTextureData(vtex, mLandData->mTextures); LandData::transposeTextureData(vtex, mLandData->mTextures);
}
} }
} }
}
void Land::unloadData() void Land::unloadData()
{
if (mDataLoaded)
{ {
delete mLandData; if (mDataLoaded)
mLandData = NULL; {
mDataLoaded = 0; delete mLandData;
mLandData = NULL;
mDataLoaded = 0;
}
} }
}
bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const
{ {
if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) {
mEsm->getHExact(ptr, size); mEsm->getHExact(ptr, size);
mDataLoaded |= dataFlag; mDataLoaded |= dataFlag;
return true; return true;
}
mEsm->skipHSubSize(size);
return false;
} }
mEsm->skipHSubSize(size);
return false;
}
bool Land::isDataLoaded(int flags) const bool Land::isDataLoaded(int flags) const
{ {
return (mDataLoaded & flags) == (flags & mDataTypes); return (mDataLoaded & flags) == (flags & mDataTypes);
} }
Land::Land (const Land& land) Land::Land (const Land& land)
: mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), : 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); static void transposeTextureData(const uint16_t *in, uint16_t *out);
}; };
void load(ESMReader &esm); void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm) const; void save(ESMWriter &esm, bool isDeleted = false) const;
void blank() {} void blank() {}

@ -6,42 +6,84 @@
namespace ESM namespace ESM
{ {
void LevelledListBase::load(ESMReader &esm, bool &isDeleted)
void LevelledListBase::load(ESMReader &esm)
{ {
esm.getHNT(mFlags, "DATA"); isDeleted = false;
esm.getHNT(mChanceNone, "NNAM");
if (esm.isNextSub("INDX")) bool hasName = false;
{ bool hasList = false;
int len; while (esm.hasMoreSubs())
esm.getHT(len);
mList.resize(len);
}
else
{ {
// Original engine ignores rest of the record, even if there are items following esm.getSubName();
mList.clear(); switch (esm.retSubName().val)
esm.skipRecord(); {
return; 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, // 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, // 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 // 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 // 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 // to actually track list changes instead of including the whole list for every file
// that does something with that list. // 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++) hasList = true;
{ break;
LevelItem &li = mList[i]; }
li.mId = esm.getHNString(mRecName); case ESM::FourCC<'D','E','L','E'>::value:
esm.getHNT(li.mLevel, "INTV"); 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("DATA", mFlags);
esm.writeHNT("NNAM", mChanceNone); esm.writeHNT("NNAM", mChanceNone);
esm.writeHNT<int>("INDX", mList.size()); esm.writeHNT<int>("INDX", mList.size());

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

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

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

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

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

@ -8,21 +8,58 @@ namespace ESM
{ {
unsigned int LandTexture::sRecordId = REC_LTEX; unsigned int LandTexture::sRecordId = REC_LTEX;
void LandTexture::load(ESMReader &esm) void LandTexture::load(ESMReader &esm, bool &isDeleted)
{ {
esm.getHNT(mIndex, "INTV"); isDeleted = false;
mTexture = esm.getHNString("DATA");
}
void LandTexture::save(ESMWriter &esm) const
{
esm.writeHNT("INTV", mIndex);
esm.writeHNCString("DATA", mTexture);
}
void LandTexture::blank() bool hasName = false;
{ bool hasIndex = false;
mTexture.clear(); while (esm.hasMoreSubs())
mIndex = -1; {
} 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; std::string mId, mTexture;
int mIndex; int mIndex;
void load(ESMReader &esm, bool &isDeleted);
void save(ESMWriter &esm, bool isDeleted = false) const;
void blank(); void blank();
///< Set record to default state (does not touch the ID). ///< Set record to default state (does not touch the ID).
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
}; };
} }
#endif #endif

@ -189,8 +189,10 @@ namespace ESM
{ {
unsigned int MagicEffect::sRecordId = REC_MGEF; 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"); esm.getHNT(mIndex, "INDX");
mId = indexToId (mIndex); mId = indexToId (mIndex);
@ -209,8 +211,7 @@ void MagicEffect::load(ESMReader &esm)
while (esm.hasMoreSubs()) while (esm.hasMoreSubs())
{ {
esm.getSubName(); esm.getSubName();
uint32_t name = esm.retSubName().val; switch (esm.retSubName().val)
switch (name)
{ {
case ESM::FourCC<'I','T','E','X'>::value: case ESM::FourCC<'I','T','E','X'>::value:
mIcon = esm.getHString(); 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); esm.writeHNT("INDX", mIndex);

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

@ -8,15 +8,21 @@ namespace ESM
{ {
unsigned int Miscellaneous::sRecordId = REC_MISC; 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; bool hasData = false;
while (esm.hasMoreSubs()) while (esm.hasMoreSubs())
{ {
esm.getSubName(); esm.getSubName();
uint32_t name = esm.retSubName().val; switch (esm.retSubName().val)
switch (name)
{ {
case ESM::FourCC<'N','A','M','E'>::value:
mId = esm.getHString();
hasName = true;
break;
case ESM::FourCC<'M','O','D','L'>::value: case ESM::FourCC<'M','O','D','L'>::value:
mModel = esm.getHString(); mModel = esm.getHString();
break; break;
@ -33,14 +39,32 @@ namespace ESM
case ESM::FourCC<'I','T','E','X'>::value: case ESM::FourCC<'I','T','E','X'>::value:
mIcon = esm.getHString(); mIcon = esm.getHString();
break; 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"); 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.writeHNCString("MODL", mModel);
esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("FNAM", mName);
esm.writeHNT("MCDT", mData, 12); esm.writeHNT("MCDT", mData, 12);

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

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

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

@ -32,98 +32,130 @@ namespace ESM
{ {
} }
void Pathgrid::load(ESMReader &esm) void Pathgrid::load(ESMReader &esm, bool &isDeleted)
{ {
esm.getHNT(mData, "DATA", 12); isDeleted = false;
mCell = esm.getHNString("NAME");
mPoints.clear(); mPoints.clear();
mEdges.clear(); mEdges.clear();
// keep track of total connections so we can reserve edge vector size // keep track of total connections so we can reserve edge vector size
int edgeCount = 0; int edgeCount = 0;
if (esm.isNextSub("PGRP")) bool hasData = false;
{ while (esm.hasMoreSubs())
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; esm.getSubName();
mPoints.reserve(pointCount); switch (esm.retSubName().val)
for (int i = 0; i < pointCount; ++i)
{ {
Point p; case ESM::FourCC<'N','A','M','E'>::value:
esm.getExact(&p, sizeof(Point)); mCell = esm.getHString();
mPoints.push_back(p); break;
edgeCount += p.mConnectionNum; 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(); esm.writeHNCString("NAME", mCell);
int size = esm.getSubSize(); esm.writeHNT("DATA", mData, 12);
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(); if (isDeleted)
int pointIndex = 0; {
mEdges.reserve(edgeCount); esm.writeHNCString("DELE", "");
for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) return;
{
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);
}
}
} }
}
}
void Pathgrid::save(ESMWriter &esm) const
{
esm.writeHNT("DATA", mData, 12);
esm.writeHNCString("NAME", mCell);
if (!mPoints.empty()) if (!mPoints.empty())
{
esm.startSubRecord("PGRP");
for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it)
{ {
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()) if (!mEdges.empty())
{
esm.startSubRecord("PGRC");
for (std::vector<Edge>::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it)
{ {
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() void Pathgrid::blank()
{ {

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

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

Loading…
Cancel
Save