diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 5b6e50b96..be90afec3 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -27,7 +27,8 @@ struct ESMData std::vector masters; std::deque mRecords; - std::map > mCellRefs; + // Value: (Reference, Deleted flag) + std::map > > mCellRefs; std::map mRecordStats; static const std::set sLabeledRec; @@ -255,7 +256,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) while(cell.getNextRef(esm, ref, deleted)) { if (save) { - info.data.mCellRefs[&cell].push_back(ref); + info.data.mCellRefs[&cell].push_back(std::make_pair(ref, deleted)); } if(quiet) continue; @@ -352,61 +353,58 @@ int load(Arguments& info) uint32_t flags; esm.getRecHeader(flags); + EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); + if (record == 0) + { + if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) + { + std::cout << "Skipping " << n.toString() << " records." << std::endl; + skipped.push_back(n.val); + } + + esm.skipRecord(); + if (quiet) break; + std::cout << " Skipping\n"; + + continue; + } + + record->setFlags(static_cast(flags)); + record->setPrintPlain(info.plain_given); + record->load(esm); + // Is the user interested in this record type? bool interested = true; if (!info.types.empty()) { std::vector::iterator match; - match = std::find(info.types.begin(), info.types.end(), - n.toString()); + match = std::find(info.types.begin(), info.types.end(), n.toString()); if (match == info.types.end()) interested = false; } - std::string id = esm.getHNOString("NAME"); - if (id.empty()) - id = esm.getHNOString("INAM"); - - if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, id)) + if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) interested = false; if(!quiet && interested) - std::cout << "\nRecord: " << n.toString() - << " '" << id << "'\n"; - - EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); - - if (record == 0) { - if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) - { - std::cout << "Skipping " << n.toString() << " records." << std::endl; - skipped.push_back(n.val); - } + { + std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n"; + record->print(); + } - esm.skipRecord(); - if (quiet) break; - std::cout << " Skipping\n"; - } else { - if (record->getType().val == ESM::REC_GMST) { - // preset id for GameSetting record - record->cast()->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()->get(), esm, info); - } + if (record->getType().val == ESM::REC_CELL && loadCells && interested) + { + loadCell(record->cast()->get(), esm, info); + } - if (save) { - info.data.mRecords.push_back(record); - } else { - delete record; - } - ++info.data.mRecordStats[n.val]; + if (save) + { + info.data.mRecords.push_back(record); + } + else + { + delete record; } + ++info.data.mRecordStats[n.val]; } } catch(std::exception &e) { @@ -493,28 +491,19 @@ int clone(Arguments& info) for (Records::iterator it = records.begin(); it != records.end() && i > 0; ++it) { EsmTool::RecordBase *record = *it; - name.val = record->getType().val; esm.startRecord(name.toString(), record->getFlags()); - // TODO wrap this with std::set - if (ESMData::sLabeledRec.count(name.val) > 0) { - esm.writeHNCString("NAME", record->getId()); - } else { - esm.writeHNOString("NAME", record->getId()); - } - record->save(esm); - if (name.val == ESM::REC_CELL) { ESM::Cell *ptr = &record->cast()->get(); if (!info.data.mCellRefs[ptr].empty()) { - typedef std::deque RefList; + typedef std::deque > RefList; RefList &refs = info.data.mCellRefs[ptr]; for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt) { - refIt->save(esm); + refIt->first.save(esm, refIt->second); } } } diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 5fe6471b0..0e7769f37 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -405,6 +405,7 @@ void Record::print() std::cout << " Name: " << mData.mName << std::endl; std::cout << " Model: " << mData.mModel << std::endl; std::cout << " Script: " << mData.mScript << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -419,6 +420,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutoCalc << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -447,6 +449,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -461,6 +464,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -474,6 +478,7 @@ void Record::print() std::cout << " Part: " << meshPartLabel(mData.mData.mPart) << " (" << (int)mData.mData.mPart << ")" << std::endl; std::cout << " Vampire: " << (int)mData.mData.mVampire << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -502,6 +507,7 @@ void Record::print() { std::cout << " Text: [skipped]" << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -513,6 +519,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mPowers.mList.begin(); pit != mData.mPowers.mList.end(); ++pit) std::cout << " Power: " << *pit << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -541,6 +548,7 @@ void Record::print() std::cout << " Map Color: " << boost::format("0x%08X") % mData.mMapColor << std::endl; std::cout << " Water Level Int: " << mData.mWaterInt << std::endl; std::cout << " RefId counter: " << mData.mRefNumCounter << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -563,6 +571,7 @@ void Record::print() for (int i = 0; i != 5; i++) std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) << " (" << mData.mData.mSkills[i][1] << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -589,6 +598,7 @@ void Record::print() if (pit->mFemale != "") std::cout << " Female Name: " << pit->mFemale << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -604,6 +614,7 @@ void Record::print() for (cit = mData.mInventory.mList.begin(); cit != mData.mInventory.mList.end(); ++cit) std::cout << " Inventory: Count: " << boost::format("%4d") % cit->mCount << " Item: " << cit->mItem.toString() << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -670,6 +681,7 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -677,6 +689,7 @@ void Record::print() { std::cout << " Type: " << dialogTypeLabel(mData.mType) << " (" << (int)mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; // Sadly, there are no DialInfos, because the loader dumps as it // loads, rather than loading and then dumping. :-( Anyone mind if // I change this? @@ -693,6 +706,7 @@ void Record::print() std::cout << " Script: " << mData.mScript << std::endl; std::cout << " OpenSound: " << mData.mOpenSound << std::endl; std::cout << " CloseSound: " << mData.mCloseSound << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -704,6 +718,7 @@ void Record::print() std::cout << " Charge: " << mData.mData.mCharge << std::endl; std::cout << " AutoCalc: " << mData.mData.mAutocalc << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -737,12 +752,14 @@ void Record::print() std::map::iterator rit; for (rit = mData.mReactions.begin(); rit != mData.mReactions.end(); ++rit) std::cout << " Reaction: " << rit->second << " = " << rit->first << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { std::cout << " " << mData.mValue << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -809,6 +826,7 @@ void Record::print() std::cout << " Result Script: [skipped]" << std::endl; } } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -832,6 +850,7 @@ void Record::print() std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) << " (" << mData.mData.mAttributes[i] << ")" << std::endl; } + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -848,6 +867,8 @@ void Record::print() std::cout << " Unknown1: " << data->mUnk1 << std::endl; std::cout << " Unknown2: " << data->mUnk2 << std::endl; } + if (!wasLoaded) mData.unloadData(); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -860,6 +881,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Creature: Level: " << iit->mLevel << " Creature: " << iit->mId << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -872,6 +894,7 @@ void Record::print() for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Inventory: Level: " << iit->mLevel << " Item: " << iit->mId << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -892,6 +915,7 @@ void Record::print() std::cout << " Duration: " << mData.mData.mTime << std::endl; std::cout << " Radius: " << mData.mData.mRadius << std::endl; std::cout << " Color: " << mData.mData.mColor << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -906,6 +930,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -920,6 +945,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -934,6 +960,7 @@ void Record::print() std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; std::cout << " Uses: " << mData.mData.mUses << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -942,6 +969,7 @@ void Record::print() std::cout << " Id: " << mData.mId << std::endl; std::cout << " Index: " << mData.mIndex << std::endl; std::cout << " Texture: " << mData.mTexture << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -992,6 +1020,7 @@ void Record::print() std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Is Key: " << mData.mData.mIsKey << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1077,6 +1106,8 @@ void Record::print() std::vector::iterator pit; for (pit = mData.mAiPackage.mList.begin(); pit != mData.mAiPackage.mList.end(); ++pit) printAIPackage(*pit); + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1111,6 +1142,8 @@ void Record::print() std::cout << " BAD POINT IN EDGE!" << std::endl; i++; } + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1151,6 +1184,8 @@ void Record::print() std::vector::iterator sit; for (sit = mData.mPowers.mList.begin(); sit != mData.mPowers.mList.end(); ++sit) std::cout << " Power: " << *sit << std::endl; + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1210,6 +1245,8 @@ void Record::print() { std::cout << " Script: [skipped]" << std::endl; } + + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1233,6 +1270,7 @@ void Record::print() std::cout << " Sound: " << mData.mSound << std::endl; std::cout << " Type: " << soundTypeLabel(mData.mType) << " (" << mData.mType << ")" << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1243,6 +1281,7 @@ void Record::print() if (mData.mData.mMinRange != 0 && mData.mData.mMaxRange != 0) std::cout << " Range: " << (int)mData.mData.mMinRange << " - " << (int)mData.mData.mMaxRange << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1254,13 +1293,15 @@ void Record::print() std::cout << " Flags: " << spellFlags(mData.mData.mFlags) << std::endl; std::cout << " Cost: " << mData.mData.mCost << std::endl; printEffectList(mData.mEffects); + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> void Record::print() { - std::cout << "Start Script: " << mData.mId << std::endl; - std::cout << "Start Data: " << mData.mData << std::endl; + std::cout << " Start Script: " << mData.mId << std::endl; + std::cout << " Start Data: " << mData.mData << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; } template<> @@ -1301,6 +1342,37 @@ void Record::print() if (mData.mData.mThrust[0] != 0 && mData.mData.mThrust[1] != 0) std::cout << " Thrust: " << (int)mData.mData.mThrust[0] << "-" << (int)mData.mData.mThrust[1] << std::endl; + std::cout << " Deleted: " << mIsDeleted << std::endl; +} + +template<> +std::string Record::getId() const +{ + return mData.mName; +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Land record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for MagicEffect record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Pathgrid record +} + +template<> +std::string Record::getId() const +{ + return ""; // No ID for Skill record } } // end namespace diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index 5e03c64db..dca38409f 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -32,13 +32,7 @@ namespace EsmTool virtual ~RecordBase() {} - const std::string &getId() const { - return mId; - } - - void setId(const std::string &id) { - mId = id; - } + virtual std::string getId() const = 0; uint32_t getFlags() const { return mFlags; @@ -73,22 +67,37 @@ namespace EsmTool class Record : public RecordBase { T mData; + bool mIsDeleted; public: + Record() + : mIsDeleted(false) + {} + + std::string getId() const { + return mData.mId; + } + T &get() { return mData; } void save(ESM::ESMWriter &esm) { - mData.save(esm); + mData.save(esm, mIsDeleted); } void load(ESM::ESMReader &esm) { - mData.load(esm); + mData.load(esm, mIsDeleted); } void print(); }; + + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; + template<> std::string Record::getId() const; template<> void Record::print(); template<> void Record::print(); diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 6267ef91e..afd0ef131 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -158,9 +158,9 @@ namespace ESSImport void ConvertCell::read(ESM::ESMReader &esm) { ESM::Cell cell; - std::string id = esm.getHNString("NAME"); - cell.mName = id; - cell.load(esm, false); + bool isDeleted = false; + + cell.load(esm, isDeleted, false); // I wonder what 0x40 does? if (cell.isExterior() && cell.mData.mFlags & 0x20) @@ -169,7 +169,7 @@ namespace ESSImport } // note if the player is in a nameless exterior cell, we will assign the cellId later based on player position - if (id == mContext->mPlayerCellName) + if (cell.mName == mContext->mPlayerCellName) { mContext->mPlayer.mCellId = cell.getCellId(); } @@ -277,7 +277,7 @@ namespace ESSImport if (cell.isExterior()) mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; else - mIntCells[id] = newcell; + mIntCells[cell.mName] = newcell; } void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 4bad5e23b..8194f2b43 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -54,6 +54,8 @@ public: void setContext(Context& context) { mContext = &context; } + /// @note The load method of ESM records accept the deleted flag as a parameter. + /// I don't know can the DELE sub-record appear in saved games, so the deleted flag will be ignored. virtual void read(ESM::ESMReader& esm) { } @@ -78,10 +80,11 @@ public: virtual void read(ESM::ESMReader& esm) { - std::string id = esm.getHNString("NAME"); T record; - record.load(esm); - mRecords[id] = record; + bool isDeleted = false; + + record.load(esm, isDeleted); + mRecords[record.mId] = record; } virtual void write(ESM::ESMWriter& esm) @@ -89,7 +92,6 @@ public: for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) { esm.startRecord(T::sRecordId); - esm.writeHNString("NAME", it->first); it->second.save(esm); esm.endRecord(T::sRecordId); } @@ -105,14 +107,15 @@ public: virtual void read(ESM::ESMReader &esm) { ESM::NPC npc; - std::string id = esm.getHNString("NAME"); - npc.load(esm); - if (id != "player") + bool isDeleted = false; + + npc.load(esm, isDeleted); + if (npc.mId != "player") { // Handles changes to the NPC struct, but since there is no index here // it will apply to ALL instances of the class. seems to be the reason for the // "feature" in MW where changing AI settings of one guard will change it for all guards of that refID. - mContext->mNpcs[Misc::StringUtils::lowerCase(id)] = npc; + mContext->mNpcs[Misc::StringUtils::lowerCase(npc.mId)] = npc; } else { @@ -142,9 +145,10 @@ public: { // See comment in ConvertNPC ESM::Creature creature; - std::string id = esm.getHNString("NAME"); - creature.load(esm); - mContext->mCreatures[Misc::StringUtils::lowerCase(id)] = creature; + bool isDeleted = false; + + creature.load(esm, isDeleted); + mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature; } }; @@ -157,18 +161,19 @@ class ConvertGlobal : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Global global; - global.load(esm); - if (Misc::StringUtils::ciEqual(id, "gamehour")) + bool isDeleted = false; + + global.load(esm, isDeleted); + if (Misc::StringUtils::ciEqual(global.mId, "gamehour")) mContext->mHour = global.mValue.getFloat(); - if (Misc::StringUtils::ciEqual(id, "day")) + if (Misc::StringUtils::ciEqual(global.mId, "day")) mContext->mDay = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(id, "month")) + if (Misc::StringUtils::ciEqual(global.mId, "month")) mContext->mMonth = global.mValue.getInteger(); - if (Misc::StringUtils::ciEqual(id, "year")) + if (Misc::StringUtils::ciEqual(global.mId, "year")) mContext->mYear = global.mValue.getInteger(); - mRecords[id] = global; + mRecords[global.mId] = global; } }; @@ -177,14 +182,14 @@ class ConvertClass : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Class class_; - class_.load(esm); + bool isDeleted = false; - if (id == "NEWCLASSID_CHARGEN") + class_.load(esm, isDeleted); + if (class_.mId == "NEWCLASSID_CHARGEN") mContext->mCustomPlayerClassName = class_.mName; - mRecords[id] = class_; + mRecords[class_.mId] = class_; } }; @@ -193,13 +198,14 @@ class ConvertBook : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Book book; - book.load(esm); + bool isDeleted = false; + + book.load(esm, isDeleted); if (book.mData.mSkillID == -1) - mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id)); + mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); - mRecords[id] = book; + mRecords[book.mId] = book; } }; @@ -371,11 +377,12 @@ class ConvertFACT : public Converter public: virtual void read(ESM::ESMReader& esm) { - std::string id = esm.getHNString("NAME"); ESM::Faction faction; - faction.load(esm); + bool isDeleted = false; + + faction.load(esm, isDeleted); + std::string id = Misc::StringUtils::toLower(faction.mId); - Misc::StringUtils::toLower(id); for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) { std::string faction2 = Misc::StringUtils::lowerCase(it->first); diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index a5f9d6eb3..239c46698 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -32,7 +32,8 @@ namespace ESSImport if (esm.isNextSub("MNAM")) esm.skipHSub(); - ESM::CellRef::loadData(esm); + bool isDeleted = false; + ESM::CellRef::loadData(esm, isDeleted); mHasACDT = false; if (esm.isNextSub("ACDT")) diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 624241039..4fbf06217 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -394,7 +394,7 @@ namespace ESSImport } writer.startRecord(ESM::REC_NPC_); - writer.writeHNString("NAME", "player"); + context.mPlayerBase.mId = "player"; context.mPlayerBase.save(writer); writer.endRecord(ESM::REC_NPC_); diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index d27cd5c8c..3ec640d3d 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -32,7 +32,8 @@ namespace ESSImport item.mSCRI.load(esm); // for XSOL and XCHG seen so far, but probably others too - item.ESM::CellRef::loadData(esm); + bool isDeleted = false; + item.ESM::CellRef::loadData(esm, isDeleted); int charge=-1; esm.getHNOT(charge, "XHLT"); diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 3fba2cd85..16a91c865 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -99,95 +99,79 @@ int CSMDoc::WriteDialogueCollectionStage::setup() void CSMDoc::WriteDialogueCollectionStage::perform (int stage, Messages& messages) { + ESM::ESMWriter& writer = mState.getWriter(); const CSMWorld::Record& topic = mTopics.getRecord (stage); - CSMWorld::RecordBase::State state = topic.mState; - - if (state==CSMWorld::RecordBase::State_Deleted) + if (topic.mState == CSMWorld::RecordBase::State_Deleted) { // if the topic is deleted, we do not need to bother with INFO records. - - /// \todo wrote record with delete flag - + ESM::Dialogue dialogue = topic.get(); + writer.startRecord(dialogue.sRecordId); + dialogue.save(writer, true); + writer.endRecord(dialogue.sRecordId); return; } // Test, if we need to save anything associated info records. bool infoModified = false; - CSMWorld::InfoCollection::Range range = mInfos.getTopicRange (topic.get().mId); for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State state = iter->mState; - - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - state==CSMWorld::RecordBase::State_Deleted) + if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { infoModified = true; break; } } - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly || - infoModified) + if (topic.isModified() || infoModified) { - if (infoModified && state != CSMWorld::RecordBase::State_Modified - && state != CSMWorld::RecordBase::State_ModifiedOnly) + if (infoModified && topic.mState != CSMWorld::RecordBase::State_Modified + && topic.mState != CSMWorld::RecordBase::State_ModifiedOnly) { mState.getWriter().startRecord (topic.mBase.sRecordId); mState.getWriter().writeHNCString ("NAME", topic.mBase.mId); - topic.mBase.save (mState.getWriter()); + topic.mBase.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); mState.getWriter().endRecord (topic.mBase.sRecordId); } else { mState.getWriter().startRecord (topic.mModified.sRecordId); mState.getWriter().writeHNCString ("NAME", topic.mModified.mId); - topic.mModified.save (mState.getWriter()); + topic.mModified.save (mState.getWriter(), topic.mState == CSMWorld::RecordBase::State_Deleted); mState.getWriter().endRecord (topic.mModified.sRecordId); } // write modified selected info records - for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; - ++iter) + for (CSMWorld::InfoCollection::RecordConstIterator iter (range.first); iter!=range.second; ++iter) { - CSMWorld::RecordBase::State infoState = iter->mState; - - if (infoState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo wrote record with delete flag - } - else if (infoState==CSMWorld::RecordBase::State_Modified || - infoState==CSMWorld::RecordBase::State_ModifiedOnly) + if (iter->isModified() || iter->mState == CSMWorld::RecordBase::State_Deleted) { ESM::DialInfo info = iter->get(); info.mId = info.mId.substr (info.mId.find_last_of ('#')+1); + info.mPrev = ""; if (iter!=range.first) { CSMWorld::InfoCollection::RecordConstIterator prev = iter; --prev; - info.mPrev = - prev->mModified.mId.substr (prev->mModified.mId.find_last_of ('#')+1); + info.mPrev = prev->get().mId.substr (prev->get().mId.find_last_of ('#')+1); } CSMWorld::InfoCollection::RecordConstIterator next = iter; ++next; + info.mNext = ""; if (next!=range.second) { - info.mNext = - next->mModified.mId.substr (next->mModified.mId.find_last_of ('#')+1); + info.mNext = next->get().mId.substr (next->get().mId.find_last_of ('#')+1); } - mState.getWriter().startRecord (info.sRecordId); - mState.getWriter().writeHNCString ("INAM", info.mId); - info.save (mState.getWriter()); - mState.getWriter().endRecord (info.sRecordId); + writer.startRecord (info.sRecordId); + info.save (writer, iter->mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (info.sRecordId); } } } @@ -235,9 +219,7 @@ void CSMDoc::CollectionReferencesStage::perform (int stage, Messages& messages) const CSMWorld::Record& record = mDocument.getData().getReferences().getRecord (i); - if (record.mState==CSMWorld::RecordBase::State_Deleted || - record.mState==CSMWorld::RecordBase::State_Modified || - record.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (record.isModified() || record.mState == CSMWorld::RecordBase::State_Deleted) { std::string cellId = record.get().mOriginalCell.empty() ? record.get().mCell : record.get().mOriginalCell; @@ -279,36 +261,34 @@ int CSMDoc::WriteCellCollectionStage::setup() void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& cell = - mDocument.getData().getCells().getRecord (stage); + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& cell = mDocument.getData().getCells().getRecord (stage); std::map >::const_iterator references = mState.getSubRecords().find (Misc::StringUtils::lowerCase (cell.get().mId)); - if (cell.mState==CSMWorld::RecordBase::State_Modified || - cell.mState==CSMWorld::RecordBase::State_ModifiedOnly || + if (cell.isModified() || + cell.mState == CSMWorld::RecordBase::State_Deleted || references!=mState.getSubRecords().end()) { - bool interior = cell.get().mId.substr (0, 1)!="#"; + CSMWorld::Cell cellRecord = cell.get(); + bool interior = cellRecord.mId.substr (0, 1)!="#"; // write cell data - mState.getWriter().startRecord (cell.mModified.sRecordId); - - mState.getWriter().writeHNOCString ("NAME", cell.get().mName); - - ESM::Cell cell2 = cell.get(); + writer.startRecord (cellRecord.sRecordId); if (interior) - cell2.mData.mFlags |= ESM::Cell::Interior; + cellRecord.mData.mFlags |= ESM::Cell::Interior; else { - cell2.mData.mFlags &= ~ESM::Cell::Interior; + cellRecord.mData.mFlags &= ~ESM::Cell::Interior; - std::istringstream stream (cell.get().mId.c_str()); + std::istringstream stream (cellRecord.mId.c_str()); char ignore; - stream >> ignore >> cell2.mData.mX >> cell2.mData.mY; + stream >> ignore >> cellRecord.mData.mX >> cellRecord.mData.mY; } - cell2.save (mState.getWriter()); + + cellRecord.save (writer, cell.mState == CSMWorld::RecordBase::State_Deleted); // write references if (references!=mState.getSubRecords().end()) @@ -319,24 +299,25 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) const CSMWorld::Record& ref = mDocument.getData().getReferences().getRecord (*iter); - if (ref.mState==CSMWorld::RecordBase::State_Modified || - ref.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (ref.isModified() || ref.mState == CSMWorld::RecordBase::State_Deleted) { + CSMWorld::CellRef refRecord = ref.get(); + // recalculate the ref's cell location std::ostringstream stream; if (!interior) { - std::pair index = ref.get().getCellIndex(); + std::pair index = refRecord.getCellIndex(); stream << "#" << index.first << " " << index.second; } // An empty mOriginalCell is meant to indicate that it is the same as // the current cell. It is possible that a moved ref is moved again. - if ((ref.get().mOriginalCell.empty() ? ref.get().mCell : ref.get().mOriginalCell) + if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { ESM::MovedCellRef moved; - moved.mRefNum = ref.get().mRefNum; + moved.mRefNum = refRecord.mRefNum; // Need to fill mTarget with the ref's new position. std::istringstream istream (stream.str().c_str()); @@ -344,24 +325,16 @@ void CSMDoc::WriteCellCollectionStage::perform (int stage, Messages& messages) char ignore; istream >> ignore >> moved.mTarget[0] >> moved.mTarget[1]; - ref.get().mRefNum.save (mState.getWriter(), false, "MVRF"); - mState.getWriter().writeHNT ("CNDT", moved.mTarget, 8); + refRecord.mRefNum.save (writer, false, "MVRF"); + writer.writeHNT ("CNDT", moved.mTarget, 8); } - ref.get().save (mState.getWriter()); - } - else if (ref.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + refRecord.save (writer, false, false, ref.mState == CSMWorld::RecordBase::State_Deleted); } } } - mState.getWriter().endRecord (cell.mModified.sRecordId); - } - else if (cell.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.endRecord (cellRecord.sRecordId); } } @@ -378,11 +351,11 @@ int CSMDoc::WritePathgridCollectionStage::setup() void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& pathgrid = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& pathgrid = mDocument.getData().getPathgrids().getRecord (stage); - if (pathgrid.mState==CSMWorld::RecordBase::State_Modified || - pathgrid.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (pathgrid.isModified() || pathgrid.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::Pathgrid record = pathgrid.get(); @@ -395,15 +368,9 @@ void CSMDoc::WritePathgridCollectionStage::perform (int stage, Messages& message else record.mCell = record.mId; - mState.getWriter().startRecord (record.sRecordId); - - record.save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (pathgrid.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, pathgrid.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } @@ -420,26 +387,20 @@ int CSMDoc::WriteLandCollectionStage::setup() void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& land = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& land = mDocument.getData().getLand().getRecord (stage); - if (land.mState==CSMWorld::RecordBase::State_Modified || - land.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (land.isModified() || land.mState == CSMWorld::RecordBase::State_Deleted) { - const CSMWorld::Land& record = land.get(); - - mState.getWriter().startRecord (record.sRecordId); - - record.save (mState.getWriter()); + CSMWorld::Land record = land.get(); + writer.startRecord (record.sRecordId); + record.save (writer, land.mState == CSMWorld::RecordBase::State_Deleted); if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes)) data->save (mState.getWriter()); - mState.getWriter().endRecord (record.sRecordId); - } - else if (land.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.endRecord (record.sRecordId); } } @@ -456,25 +417,16 @@ int CSMDoc::WriteLandTextureCollectionStage::setup() void CSMDoc::WriteLandTextureCollectionStage::perform (int stage, Messages& messages) { - const CSMWorld::Record& landTexture = + ESM::ESMWriter& writer = mState.getWriter(); + const CSMWorld::Record& landTexture = mDocument.getData().getLandTextures().getRecord (stage); - if (landTexture.mState==CSMWorld::RecordBase::State_Modified || - landTexture.mState==CSMWorld::RecordBase::State_ModifiedOnly) + if (landTexture.isModified() || landTexture.mState == CSMWorld::RecordBase::State_Deleted) { CSMWorld::LandTexture record = landTexture.get(); - - mState.getWriter().startRecord (record.sRecordId); - - mState.getWriter().writeHNString("NAME", record.mId); - - record.save (mState.getWriter()); - - mState.getWriter().endRecord (record.sRecordId); - } - else if (landTexture.mState==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, landTexture.mState == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index 188f22f96..64afd0dd8 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -100,26 +100,17 @@ namespace CSMDoc if (CSMWorld::getScopeFromId (mCollection.getRecord (stage).get().mId)!=mScope) return; + ESM::ESMWriter& writer = mState.getWriter(); CSMWorld::RecordBase::State state = mCollection.getRecord (stage).mState; + typename CollectionT::ESXRecord record = mCollection.getRecord (stage).get(); - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (state == CSMWorld::RecordBase::State_Modified || + state == CSMWorld::RecordBase::State_ModifiedOnly || + state == CSMWorld::RecordBase::State_Deleted) { - // FIXME: A quick Workaround to support records which should not write - // NAME, including SKIL, MGEF and SCPT. If there are many more - // idcollection records that doesn't use NAME then a more generic - // solution may be required. - uint32_t name = mCollection.getRecord (stage).mModified.sRecordId; - mState.getWriter().startRecord (name); - - if(name != ESM::REC_SKIL && name != ESM::REC_MGEF && name != ESM::REC_SCPT) - mState.getWriter().writeHNCString ("NAME", mCollection.getId (stage)); - mCollection.getRecord (stage).mModified.save (mState.getWriter()); - mState.getWriter().endRecord (mCollection.getRecord (stage).mModified.sRecordId); - } - else if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + writer.startRecord (record.sRecordId); + record.save (writer, state == CSMWorld::RecordBase::State_Deleted); + writer.endRecord (record.sRecordId); } } diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 91becdb74..93f3c500d 100644 --- a/apps/opencs/model/world/cell.cpp +++ b/apps/opencs/model/world/cell.cpp @@ -2,18 +2,15 @@ #include -void CSMWorld::Cell::load (ESM::ESMReader &esm) +void CSMWorld::Cell::load (ESM::ESMReader &esm, bool &isDeleted) { - mName = mId; + ESM::Cell::load (esm, isDeleted, false); - ESM::Cell::load (esm, false); - - if (!(mData.mFlags & Interior)) + mId = mName; + if (isExterior()) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } } diff --git a/apps/opencs/model/world/cell.hpp b/apps/opencs/model/world/cell.hpp index f393e2cf9..160610874 100644 --- a/apps/opencs/model/world/cell.hpp +++ b/apps/opencs/model/world/cell.hpp @@ -16,7 +16,7 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index b0571bbed..16f5ce51f 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -43,6 +43,12 @@ namespace CSMWorld template > class Collection : public CollectionBase { + public: + + typedef ESXRecordT ESXRecord; + + private: + std::vector > mRecords; std::map mIndex; std::vector *> mColumns; diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index bfdab0675..1f98b2475 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1010,41 +1010,43 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages) case ESM::REC_DIAL: { - std::string id = mReader->getHNOString ("NAME"); - ESM::Dialogue record; - record.mId = id; - record.load (*mReader); + bool isDeleted = false; - if (record.mType==ESM::Dialogue::Journal) - { - mJournals.load (record, mBase); - mDialogue = &mJournals.getRecord (id).get(); - } - else if (record.mType==ESM::Dialogue::Deleted) + record.load (*mReader, isDeleted); + + if (isDeleted) { - mDialogue = 0; // record vector can be shuffled around which would make pointer - // to record invalid + // record vector can be shuffled around which would make pointer to record invalid + mDialogue = 0; - if (mJournals.tryDelete (id)) + if (mJournals.tryDelete (record.mId)) { - /// \todo handle info records + mJournalInfos.removeDialogueInfos(record.mId); } - else if (mTopics.tryDelete (id)) + else if (mTopics.tryDelete (record.mId)) { - /// \todo handle info records + mTopicInfos.removeDialogueInfos(record.mId); } else { messages.add (UniversalId::Type_None, - "Trying to delete dialogue record " + id + " which does not exist", + "Trying to delete dialogue record " + record.mId + " which does not exist", "", CSMDoc::Message::Severity_Warning); } } else { - mTopics.load (record, mBase); - mDialogue = &mTopics.getRecord (id).get(); + if (record.mType == ESM::Dialogue::Journal) + { + mJournals.load (record, mBase); + mDialogue = &mJournals.getRecord (record.mId).get(); + } + else + { + mTopics.load (record, mBase); + mDialogue = &mTopics.getRecord (record.mId).get(); + } } break; diff --git a/apps/opencs/model/world/idcollection.hpp b/apps/opencs/model/world/idcollection.hpp index 4eafc59bd..ea6eefb88 100644 --- a/apps/opencs/model/world/idcollection.hpp +++ b/apps/opencs/model/world/idcollection.hpp @@ -11,7 +11,7 @@ namespace CSMWorld template > class IdCollection : public Collection { - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader); + virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: @@ -33,77 +33,46 @@ namespace CSMWorld template void IdCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader) + ESM::ESMReader& reader, + bool& isDeleted) { - record.load (reader); + record.load (reader, isDeleted); } template int IdCollection::load (ESM::ESMReader& reader, bool base) { - std::string id = reader.getHNOString ("NAME"); + ESXRecordT record; + bool isDeleted = false; - if (reader.isNextSub ("DELE")) - { - int index = Collection::searchId (id); + loadRecord (record, reader, isDeleted); - reader.skipRecord(); + std::string id = IdAccessorT().getId (record); + int index = this->searchId (id); + if (isDeleted) + { if (index==-1) { // deleting a record that does not exist - // ignore it for now - /// \todo report the problem to the user - } - else if (base) - { - Collection::removeRows (index, 1); - } - else - { - Record record = Collection::getRecord (index); - record.mState = RecordBase::State_Deleted; - this->setRecord (index, record); + return -1; } - return -1; - } - else - { - ESXRecordT record; - - // Sometimes id (i.e. NAME of the cell) may be different to the id we stored - // earlier. e.g. NAME == "Vivec, Arena" but id == "#-4 11". Sometime NAME is - // missing altogether for scripts or cells. - // - // In such cases the returned index will be -1. We then try updating the - // IdAccessor's id manually (e.g. set mId of the record to "Vivec, Arena") - // and try getting the index once more after loading the record. The mId of the - // record would have changed to "#-4 11" after the load, and searchId() should find - // it (if this is a modify) - int index = this->searchId (id); - - if (index==-1) - IdAccessorT().getId (record) = id; - else - { - record = this->getRecord (index).get(); - } - - loadRecord (record, reader); - - if (index==-1) + if (base) { - std::string newId = IdAccessorT().getId(record); - int newIndex = this->searchId(newId); - if (newIndex != -1 && id != newId) - index = newIndex; + this->removeRows (index, 1); + return -1; } - return load (record, base, index); + Record baseRecord = this->getRecord (index); + baseRecord.mState = RecordBase::State_Deleted; + this->setRecord (index, baseRecord); + return index; } + + return load (record, base, index); } template diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 60c613041..f5ec4d458 100644 --- a/apps/opencs/model/world/infocollection.cpp +++ b/apps/opencs/model/world/infocollection.cpp @@ -106,21 +106,20 @@ bool CSMWorld::InfoCollection::reorderRows (int baseIndex, const std::vector erasedRecords; + + std::map::const_iterator current = getIdMap().lower_bound(id); + std::map::const_iterator end = getIdMap().end(); + for (; current != end; ++current) + { + Record 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(); + } +} diff --git a/apps/opencs/model/world/infocollection.hpp b/apps/opencs/model/world/infocollection.hpp index 6db47373d..e5a5575c7 100644 --- a/apps/opencs/model/world/infocollection.hpp +++ b/apps/opencs/model/world/infocollection.hpp @@ -44,6 +44,8 @@ namespace CSMWorld Range getTopicRange (const std::string& topic) const; ///< Return iterators that point to the beginning and past the end of the range for /// the given topic. + + void removeDialogueInfos(const std::string& dialogueId); }; } diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index 222f9bc02..80f86c746 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -4,13 +4,12 @@ namespace CSMWorld { - void Land::load(ESM::ESMReader &esm) + void Land::load(ESM::ESMReader &esm, bool &isDeleted) { - ESM::Land::load(esm); + ESM::Land::load(esm, isDeleted); std::ostringstream stream; stream << "#" << mX << " " << mY; - mId = stream.str(); } } diff --git a/apps/opencs/model/world/land.hpp b/apps/opencs/model/world/land.hpp index 22cedb56d..e5f25c1d3 100644 --- a/apps/opencs/model/world/land.hpp +++ b/apps/opencs/model/world/land.hpp @@ -10,13 +10,12 @@ namespace CSMWorld /// \brief Wrapper for Land record. Encodes X and Y cell index in the ID. /// /// \todo Add worldspace support to the Land record. - /// \todo Add a proper copy constructor (currently worked around using shared_ptr) struct Land : public ESM::Land { std::string mId; /// Loads the metadata and ID - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index e7772129c..266377d0f 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -4,10 +4,9 @@ namespace CSMWorld { - - void LandTexture::load(ESM::ESMReader &esm) + void LandTexture::load(ESM::ESMReader &esm, bool &isDeleted) { - ESM::LandTexture::load(esm); + ESM::LandTexture::load(esm, isDeleted); mPluginIndex = esm.getIndex(); } diff --git a/apps/opencs/model/world/landtexture.hpp b/apps/opencs/model/world/landtexture.hpp index c0b6eeba9..91459eee2 100644 --- a/apps/opencs/model/world/landtexture.hpp +++ b/apps/opencs/model/world/landtexture.hpp @@ -12,7 +12,7 @@ namespace CSMWorld { int mPluginIndex; - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/pathgrid.cpp b/apps/opencs/model/world/pathgrid.cpp index 5c66e7d8e..c995bd8f0 100644 --- a/apps/opencs/model/world/pathgrid.cpp +++ b/apps/opencs/model/world/pathgrid.cpp @@ -4,33 +4,28 @@ #include -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, const IdCollection& cells) +void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells) { - load (esm); + load (esm, isDeleted); // correct ID if (!mId.empty() && mId[0]!='#' && cells.searchId (mId)==-1) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } } -void CSMWorld::Pathgrid::load (ESM::ESMReader &esm) +void CSMWorld::Pathgrid::load (ESM::ESMReader &esm, bool &isDeleted) { - ESM::Pathgrid::load (esm); + ESM::Pathgrid::load (esm, isDeleted); + mId = mCell; if (mCell.empty()) { std::ostringstream stream; - stream << "#" << mData.mX << " " << mData.mY; - mId = stream.str(); } - else - mId = mCell; } diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index 7e7b7c3bb..22d01b071 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -20,9 +20,8 @@ namespace CSMWorld { std::string mId; - void load (ESM::ESMReader &esm, const IdCollection& cells); - - void load (ESM::ESMReader &esm); + void load (ESM::ESMReader &esm, bool &isDeleted, const IdCollection& cells); + void load (ESM::ESMReader &esm, bool &isDeleted); }; } diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index f8818807b..65251a81d 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -19,12 +19,11 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Cell& cell2 = base ? cell.mBase : cell.mModified; CellRef ref; - - bool deleted = false; ESM::MovedCellRef mref; + bool isDeleted = false; // hack to initialise mindex - while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, deleted, true, &mref)) + while (!(mref.mRefNum.mIndex = 0) && ESM::Cell::getNextRef(reader, ref, isDeleted, true, &mref)) { // Keep mOriginalCell empty when in modified (as an indicator that the // original cell will always be equal the current cell). @@ -49,17 +48,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool // https://forum.openmw.org/viewtopic.php?f=6&t=577&start=30 ref.mOriginalCell = cell2.mId; - if (deleted) - { - // FIXME: how to mark the record deleted? - CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Cell, - mCells.getId (cellIndex)); - - messages.add (id, "Moved reference "+ref.mRefID+" is in DELE state"); - - continue; - } - // It is not always possibe to ignore moved references sub-record and // calculate from coordinates. Some mods may place the ref in positions // outside normal bounds, resulting in non sensical cell id's. This often @@ -91,7 +79,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool break; } - if (deleted) + if (isDeleted) { if (iter==cache.end()) { @@ -99,7 +87,6 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool mCells.getId (cellIndex)); messages.add (id, "Attempt to delete a non-existing reference"); - continue; } @@ -107,7 +94,7 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool Record record = getRecord (index); - if (record.mState==RecordBase::State_BaseOnly) + if (base) { removeRows (index, 1); cache.erase (iter); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 337580fad..c4c8f8605 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -837,61 +837,7 @@ const CSMWorld::RecordBase& CSMWorld::RefIdCollection::getRecord (int index) con void CSMWorld::RefIdCollection::load (ESM::ESMReader& reader, bool base, UniversalId::Type type) { - std::string id = reader.getHNOString ("NAME"); - - int index = searchId (id); - - if (reader.isNextSub ("DELE")) - { - reader.skipRecord(); - - if (index==-1) - { - // deleting a record that does not exist - - // ignore it for now - - /// \todo report the problem to the user - } - else if (base) - { - mData.erase (index, 1); - } - else - { - mData.getRecord (mData.globalToLocalIndex (index)).mState = RecordBase::State_Deleted; - } - } - else - { - if (index==-1) - { - // new record - int index = mData.getAppendIndex (type); - mData.appendRecord (type, id, base); - - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); - - mData.load (localIndex, reader, base); - - mData.getRecord (localIndex).mState = - base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; - } - else - { - // old record - RefIdData::LocalIndex localIndex = mData.globalToLocalIndex (index); - - if (!base) - if (mData.getRecord (localIndex).mState==RecordBase::State_Erased) - throw std::logic_error ("attempt to access a deleted record"); - - mData.load (localIndex, reader, base); - - if (!base) - mData.getRecord (localIndex).mState = RecordBase::State_Modified; - } - } + mData.load(reader, base, type); } int CSMWorld::RefIdCollection::getAppendIndex (const std::string& id, UniversalId::Type type) const diff --git a/apps/opencs/model/world/refiddata.cpp b/apps/opencs/model/world/refiddata.cpp index 7696c3763..2d8c9ac10 100644 --- a/apps/opencs/model/world/refiddata.cpp +++ b/apps/opencs/model/world/refiddata.cpp @@ -3,10 +3,20 @@ #include #include -#include - CSMWorld::RefIdDataContainerBase::~RefIdDataContainerBase() {} + +std::string CSMWorld::RefIdData::getRecordId(const CSMWorld::RefIdData::LocalIndex &index) const +{ + std::map::const_iterator found = + mRecordContainers.find (index.second); + + if (found == mRecordContainers.end()) + throw std::logic_error ("invalid local index type"); + + return found->second->getId(index.first); +} + CSMWorld::RefIdData::RefIdData() { mRecordContainers.insert (std::make_pair (UniversalId::Type_Activator, &mActivators)); @@ -161,15 +171,27 @@ int CSMWorld::RefIdData::getAppendIndex (UniversalId::Type type) const return index; } -void CSMWorld::RefIdData::load (const LocalIndex& index, ESM::ESMReader& reader, bool base) +void CSMWorld::RefIdData::load (ESM::ESMReader& reader, bool base, CSMWorld::UniversalId::Type type) { - std::map::iterator iter = - mRecordContainers.find (index.second); + std::map::iterator found = + mRecordContainers.find (type); - if (iter==mRecordContainers.end()) - throw std::logic_error ("invalid local index type"); + if (found == mRecordContainers.end()) + throw std::logic_error ("Invalid Referenceable ID type"); - iter->second->load (index.first, reader, base); + int index = found->second->load(reader, base); + if (index != -1) + { + LocalIndex localIndex = LocalIndex(index, type); + if (base && getRecord(localIndex).mState == RecordBase::State_Deleted) + { + erase(localIndex, 1); + } + else + { + mIndex[Misc::StringUtils::lowerCase(getRecordId(localIndex))] = localIndex; + } + } } void CSMWorld::RefIdData::erase (const LocalIndex& index, int count) diff --git a/apps/opencs/model/world/refiddata.hpp b/apps/opencs/model/world/refiddata.hpp index 8909ae4fb..59cad6a66 100644 --- a/apps/opencs/model/world/refiddata.hpp +++ b/apps/opencs/model/world/refiddata.hpp @@ -25,6 +25,8 @@ #include #include +#include + #include "record.hpp" #include "universalid.hpp" @@ -49,7 +51,8 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record) = 0; - virtual void load (int index, ESM::ESMReader& reader, bool base) = 0; + virtual int load (ESM::ESMReader& reader, bool base) = 0; + ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count) = 0; @@ -73,7 +76,8 @@ namespace CSMWorld virtual void insertRecord (RecordBase& record); - virtual void load (int index, ESM::ESMReader& reader, bool base); + virtual int load (ESM::ESMReader& reader, bool base); + ///< \return index of a loaded record or -1 if no record was loaded virtual void erase (int index, int count); @@ -122,9 +126,58 @@ namespace CSMWorld } template - void RefIdDataContainer::load (int index, ESM::ESMReader& reader, bool base) + int RefIdDataContainer::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(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 @@ -145,19 +198,14 @@ namespace CSMWorld template void RefIdDataContainer::save (int index, ESM::ESMWriter& writer) const { - CSMWorld::RecordBase::State state = mContainer.at (index).mState; + Record record = mContainer.at(index); - if (state==CSMWorld::RecordBase::State_Modified || - state==CSMWorld::RecordBase::State_ModifiedOnly) + if (record.isModified() || record.mState == RecordBase::State_Deleted) { - writer.startRecord (mContainer.at (index).mModified.sRecordId); - writer.writeHNCString ("NAME", getId (index)); - mContainer.at (index).mModified.save (writer); - writer.endRecord (mContainer.at (index).mModified.sRecordId); - } - else if (state==CSMWorld::RecordBase::State_Deleted) - { - /// \todo write record with delete flag + RecordT esmRecord = record.get(); + writer.startRecord(esmRecord.sRecordId); + esmRecord.save(writer, record.mState == RecordBase::State_Deleted); + writer.endRecord(esmRecord.sRecordId); } } @@ -198,6 +246,8 @@ namespace CSMWorld void erase (const LocalIndex& index, int count); ///< Must not spill over into another type. + std::string getRecordId(const LocalIndex &index) const; + public: RefIdData(); @@ -221,7 +271,7 @@ namespace CSMWorld int getAppendIndex (UniversalId::Type type) const; - void load (const LocalIndex& index, ESM::ESMReader& reader, bool base); + void load (ESM::ESMReader& reader, bool base, UniversalId::Type type); int getSize() const; diff --git a/apps/opencs/model/world/subcellcollection.hpp b/apps/opencs/model/world/subcellcollection.hpp index df1f8a12e..496cb0643 100644 --- a/apps/opencs/model/world/subcellcollection.hpp +++ b/apps/opencs/model/world/subcellcollection.hpp @@ -20,7 +20,7 @@ namespace CSMWorld { const IdCollection& mCells; - virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader); + virtual void loadRecord (ESXRecordT& record, ESM::ESMReader& reader, bool& isDeleted); public: @@ -29,9 +29,10 @@ namespace CSMWorld template void SubCellCollection::loadRecord (ESXRecordT& record, - ESM::ESMReader& reader) + ESM::ESMReader& reader, + bool& isDeleted) { - record.load (reader, mCells); + record.load (reader, isDeleted, mCells); } template diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index b33a6f8db..e9f9c5cd1 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -475,7 +475,7 @@ namespace MWWorld switch (store.find (ref.mRefID)) { case ESM::REC_ACTI: mActivators.load(ref, deleted, store); break; - case ESM::REC_ALCH: mPotions.load(ref, deleted, store); break; + case ESM::REC_ALCH: mPotions.load(ref, deleted,store); break; case ESM::REC_APPA: mAppas.load(ref, deleted, store); break; case ESM::REC_ARMO: mArmors.load(ref, deleted, store); break; case ESM::REC_BOOK: mBooks.load(ref, deleted, store); break; diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index dea468d22..50324f3e8 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -96,33 +96,21 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) throw std::runtime_error(error.str()); } } else { - // Load it - std::string id = esm.getHNOString("NAME"); - // ... unless it got deleted! This means that the following record - // has been deleted, and trying to load it using standard assumptions - // on the structure will (probably) fail. - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - it->second->eraseStatic(id); - continue; - } - it->second->load(esm, id); - - // DELE can also occur after the usual subrecords - if (esm.isNextSub("DELE")) { - esm.skipRecord(); - it->second->eraseStatic(id); - continue; + RecordId id = it->second->load(esm); + if (id.mIsDeleted) + { + it->second->eraseStatic(id.mId); + continue; } if (n.val==ESM::REC_DIAL) { - dialogue = const_cast(mDialogs.find(id)); + dialogue = const_cast(mDialogs.find(id.mId)); } else { dialogue = 0; } // Insert the reference into the global lookup - if (!id.empty() && isCacheableRecord(n.val)) { - mIds[Misc::StringUtils::lowerCase (id)] = n.val; + if (!id.mId.empty() && isCacheableRecord(n.val)) { + mIds[Misc::StringUtils::lowerCase (id.mId)] = n.val; } } listener->setProgress(static_cast(esm.getFileOffset() / (float)esm.getFileSize() * 1000)); @@ -195,13 +183,12 @@ void ESMStore::setUp() case ESM::REC_LEVC: { - std::string id = reader.getHNString ("NAME"); - mStores[type]->read (reader, id); + RecordId id = mStores[type]->read (reader); // FIXME: there might be stale dynamic IDs in mIds from an earlier savegame // that really should be cleared instead of just overwritten - mIds[id] = type; + mIds[id.mId] = type; } if (type==ESM::REC_NPC_) diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index dcd7924a2..4a406613d 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -13,7 +13,7 @@ namespace MWWorld { Globals::Collection::const_iterator Globals::find (const std::string& name) const { - Collection::const_iterator iter = mVariables.find (name); + Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); @@ -23,7 +23,7 @@ namespace MWWorld Globals::Collection::iterator Globals::find (const std::string& name) { - Collection::iterator iter = mVariables.find (name); + Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) throw std::runtime_error ("unknown global variable: " + name); @@ -40,28 +40,28 @@ namespace MWWorld for (MWWorld::Store::iterator iter = globals.begin(); iter!=globals.end(); ++iter) { - mVariables.insert (std::make_pair (iter->mId, iter->mValue)); + mVariables.insert (std::make_pair (Misc::StringUtils::lowerCase (iter->mId), *iter)); } } const ESM::Variant& Globals::operator[] (const std::string& name) const { - return find (name)->second; + return find (Misc::StringUtils::lowerCase (name))->second.mValue; } ESM::Variant& Globals::operator[] (const std::string& name) { - return find (name)->second; + return find (Misc::StringUtils::lowerCase (name))->second.mValue; } char Globals::getType (const std::string& name) const { - Collection::const_iterator iter = mVariables.find (name); + Collection::const_iterator iter = mVariables.find (Misc::StringUtils::lowerCase (name)); if (iter==mVariables.end()) return ' '; - switch (iter->second.getType()) + switch (iter->second.mValue.getType()) { case ESM::VT_Short: return 's'; case ESM::VT_Long: return 'l'; @@ -81,8 +81,7 @@ namespace MWWorld for (Collection::const_iterator iter (mVariables.begin()); iter!=mVariables.end(); ++iter) { writer.startRecord (ESM::REC_GLOB); - writer.writeHNString ("NAME", iter->first); - iter->second.write (writer, ESM::Variant::Format_Global); + iter->second.save (writer); writer.endRecord (ESM::REC_GLOB); } } @@ -91,14 +90,17 @@ namespace MWWorld { if (type==ESM::REC_GLOB) { - std::string id = reader.getHNString ("NAME"); + ESM::Global global; + bool isDeleted = false; - Collection::iterator iter = mVariables.find (Misc::StringUtils::lowerCase (id)); + // This readRecord() method is used when reading a saved game. + // Deleted globals can't appear there, so isDeleted will be ignored here. + global.load(reader, isDeleted); + Misc::StringUtils::toLower(global.mId); + Collection::iterator iter = mVariables.find (global.mId); if (iter!=mVariables.end()) - iter->second.read (reader, ESM::Variant::Format_Global); - else - reader.skipRecord(); + iter->second = global; return true; } diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index bb4ab13d9..3468c2e71 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include namespace ESM { @@ -29,7 +29,7 @@ namespace MWWorld { private: - typedef std::map Collection; + typedef std::map Collection; Collection mVariables; // type, value diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 7a471eaa4..644c3d0cf 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -42,6 +42,10 @@ namespace namespace MWWorld { + RecordId::RecordId(const std::string &id, bool isDeleted) + : mId(id), mIsDeleted(isDeleted) + {} + template IndexedStore::IndexedStore() { @@ -60,7 +64,9 @@ namespace MWWorld void IndexedStore::load(ESM::ESMReader &esm) { T record; - record.load(esm); + bool isDeleted = false; + + record.load(esm, isDeleted); // Try to overwrite existing record std::pair ret = mStatic.insert(std::make_pair(record.mIndex, record)); @@ -178,16 +184,21 @@ namespace MWWorld return ptr; } template - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { - std::string idLower = Misc::StringUtils::lowerCase(id); + T record; + bool isDeleted = false; - std::pair inserted = mStatic.insert(std::make_pair(idLower, T())); + record.load(esm, isDeleted); + Misc::StringUtils::toLower(record.mId); + + std::pair inserted = mStatic.insert(std::make_pair(record.mId, record)); if (inserted.second) mShared.push_back(&inserted.first->second); + else + inserted.first->second = record; - inserted.first->second.mId = idLower; - inserted.first->second.load(esm); + return RecordId(record.mId, isDeleted); } template void Store::setUp() @@ -309,20 +320,21 @@ namespace MWWorld ++iter) { writer.startRecord (T::sRecordId); - writer.writeHNString ("NAME", iter->second.mId); iter->second.save (writer); writer.endRecord (T::sRecordId); } } template - void Store::read(ESM::ESMReader& reader, const std::string& id) + RecordId Store::read(ESM::ESMReader& reader) { T record; - record.mId = id; - record.load (reader); + bool isDeleted = false; + + record.load (reader, isDeleted); insert (record); - } + return RecordId(record.mId, isDeleted); + } // LandTexture //========================================================================= @@ -361,11 +373,12 @@ namespace MWWorld assert(plugin < mStatic.size()); return mStatic[plugin].size(); } - void Store::load(ESM::ESMReader &esm, const std::string &id, size_t plugin) + RecordId Store::load(ESM::ESMReader &esm, size_t plugin) { ESM::LandTexture lt; - lt.load(esm); - lt.mId = id; + bool isDeleted = false; + + lt.load(esm, isDeleted); // Make sure we have room for the structure if (plugin >= mStatic.size()) { @@ -377,10 +390,12 @@ namespace MWWorld // Store it ltexl[lt.mIndex] = lt; + + return RecordId(lt.mId, isDeleted); } - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { - load(esm, id, esm.getIndex()); + return load(esm, esm.getIndex()); } Store::iterator Store::begin(size_t plugin) const { @@ -392,7 +407,6 @@ namespace MWWorld assert(plugin < mStatic.size()); return mStatic[plugin].end(); } - // Land //========================================================================= @@ -440,10 +454,12 @@ namespace MWWorld } return ptr; } - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { ESM::Land *ptr = new ESM::Land(); - ptr->load(esm); + bool isDeleted = false; + + ptr->load(esm, isDeleted); // Same area defined in multiple plugins? -> last plugin wins // Can't use search() because we aren't sorted yet - is there any other way to speed this up? @@ -458,6 +474,8 @@ namespace MWWorld } mStatic.push_back(ptr); + + return RecordId("", isDeleted); } void Store::setUp() { @@ -600,7 +618,7 @@ namespace MWWorld mSharedExt.push_back(&(it->second)); } } - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { // Don't automatically assume that a new cell must be spawned. Multiple plugins write to the same cell, // and we merge all this data into one Cell object. However, we can't simply search for the cell id, @@ -608,13 +626,13 @@ namespace MWWorld // are not available until both cells have been loaded at least partially! // All cells have a name record, even nameless exterior cells. - std::string idLower = Misc::StringUtils::lowerCase(id); ESM::Cell cell; - cell.mName = id; + bool isDeleted = false; - // Load the (x,y) coordinates of the cell, if it is an exterior cell, + // Load the (x,y) coordinates of the cell, if it is an exterior cell, // so we can find the cell we need to merge with - cell.loadData(esm); + cell.loadNameAndData(esm, isDeleted); + std::string idLower = Misc::StringUtils::lowerCase(cell.mName); if(cell.mData.mFlags & ESM::Cell::Interior) { @@ -682,6 +700,8 @@ namespace MWWorld mExt[std::make_pair(cell.mData.mX, cell.mData.mY)] = cell; } } + + return RecordId(cell.mName, isDeleted); } Store::iterator Store::intBegin() const { @@ -837,10 +857,12 @@ namespace MWWorld { mCells = &cells; } - void Store::load(ESM::ESMReader &esm, const std::string &id) + RecordId Store::load(ESM::ESMReader &esm) { ESM::Pathgrid pathgrid; - pathgrid.load(esm); + bool isDeleted = false; + + pathgrid.load(esm, isDeleted); // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. @@ -862,6 +884,8 @@ namespace MWWorld if (!ret.second) ret.first->second = pathgrid; } + + return RecordId("", isDeleted); } size_t Store::getSize() const { @@ -1013,51 +1037,29 @@ namespace MWWorld } template <> - void Store::load(ESM::ESMReader &esm, const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); - - std::map::iterator it = mStatic.find(idLower); - if (it == mStatic.end()) { - it = mStatic.insert( std::make_pair( idLower, ESM::Dialogue() ) ).first; - it->second.mId = id; // don't smash case here, as this line is printed - } - - it->second.load(esm); - } + inline RecordId Store::load(ESM::ESMReader &esm) { + // The original letter case of a dialogue ID is saved, because it's printed + ESM::Dialogue dialogue; + bool isDeleted = false; + dialogue.loadId(esm); - // Script - //========================================================================= - - template <> - void Store::load(ESM::ESMReader &esm, const std::string &id) { - ESM::Script scpt; - scpt.load(esm); - Misc::StringUtils::toLower(scpt.mId); - - std::pair inserted = mStatic.insert(std::make_pair(scpt.mId, scpt)); - if (inserted.second) - mShared.push_back(&inserted.first->second); + std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); + std::map::iterator found = mStatic.find(idLower); + if (found == mStatic.end()) + { + dialogue.loadData(esm, isDeleted); + mStatic.insert(std::make_pair(idLower, dialogue)); + } else - inserted.first->second = scpt; - } - - - // StartScript - //========================================================================= + { + found->second.loadData(esm, isDeleted); + dialogue = found->second; + } - template <> - void Store::load(ESM::ESMReader &esm, const std::string &id) - { - ESM::StartScript s; - s.load(esm); - s.mId = Misc::StringUtils::toLower(s.mId); - std::pair inserted = mStatic.insert(std::make_pair(s.mId, s)); - if (inserted.second) - mShared.push_back(&inserted.first->second); - else - inserted.first->second = s; + return RecordId(dialogue.mId, isDeleted); } + } template class MWWorld::Store; @@ -1082,7 +1084,7 @@ template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; -template class MWWorld::Store; +//template class MWWorld::Store; template class MWWorld::Store; template class MWWorld::Store; //template class MWWorld::Store; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 47c87f742..ef551e205 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -19,6 +19,14 @@ namespace Loading namespace MWWorld { + struct RecordId + { + std::string mId; + bool mIsDeleted; + + RecordId(const std::string &id = "", bool isDeleted = false); + }; + struct StoreBase { virtual ~StoreBase() {} @@ -28,14 +36,14 @@ namespace MWWorld virtual size_t getSize() const = 0; virtual int getDynamicSize() const { return 0; } - virtual void load(ESM::ESMReader &esm, const std::string &id) = 0; + virtual RecordId load(ESM::ESMReader &esm) = 0; virtual bool eraseStatic(const std::string &id) {return false;} virtual void clearDynamic() {} virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {} - virtual void read (ESM::ESMReader& reader, const std::string& id) {} + virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); } ///< Read into dynamic storage }; @@ -180,9 +188,9 @@ namespace MWWorld bool erase(const std::string &id); bool erase(const T &item); - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm); void write(ESM::ESMWriter& writer, Loading::Listener& progress) const; - void read(ESM::ESMReader& reader, const std::string& id); + RecordId read(ESM::ESMReader& reader); }; template <> @@ -205,8 +213,8 @@ namespace MWWorld size_t getSize() const; size_t getSize(size_t plugin) const; - void load(ESM::ESMReader &esm, const std::string &id, size_t plugin); - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm, size_t plugin); + RecordId load(ESM::ESMReader &esm); iterator begin(size_t plugin) const; iterator end(size_t plugin) const; @@ -231,7 +239,7 @@ namespace MWWorld ESM::Land *search(int x, int y) const; ESM::Land *find(int x, int y) const; - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm); void setUp(); }; @@ -281,7 +289,7 @@ namespace MWWorld void setUp(); - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm); iterator intBegin() const; iterator intEnd() const; @@ -323,7 +331,7 @@ namespace MWWorld Store(); void setCells(Store& cells); - void load(ESM::ESMReader &esm, const std::string &id); + RecordId load(ESM::ESMReader &esm); size_t getSize() const; void setUp(); diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 33ac4a91e..d4ef29e04 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -24,10 +24,10 @@ void ESM::RefNum::save (ESMWriter &esm, bool wide, const std::string& tag) const } -void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) +void ESM::CellRef::load (ESMReader& esm, bool &isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); - loadData(esm); + loadData(esm, isDeleted); } void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) @@ -39,71 +39,98 @@ void ESM::CellRef::loadId (ESMReader& esm, bool wideRefNum) if (esm.isNextSub ("NAM0")) esm.skipHSub(); + blank(); + mRefNum.load (esm, wideRefNum); mRefID = esm.getHNString ("NAME"); } -void ESM::CellRef::loadData(ESMReader &esm) +void ESM::CellRef::loadData(ESMReader &esm, bool &isDeleted) { - // Again, UNAM sometimes appears after NAME and sometimes later. - // Or perhaps this UNAM means something different? - mReferenceBlocked = -1; - esm.getHNOT (mReferenceBlocked, "UNAM"); - - mScale = 1.0; - esm.getHNOT (mScale, "XSCL"); - - mOwner = esm.getHNOString ("ANAM"); - mGlobalVariable = esm.getHNOString ("BNAM"); - mSoul = esm.getHNOString ("XSOL"); - - mFaction = esm.getHNOString ("CNAM"); - mFactionRank = -2; - esm.getHNOT (mFactionRank, "INDX"); - - mGoldValue = 1; - mChargeInt = -1; - mEnchantmentCharge = -1; - - esm.getHNOT (mEnchantmentCharge, "XCHG"); - - esm.getHNOT (mChargeInt, "INTV"); - - esm.getHNOT (mGoldValue, "NAM9"); + isDeleted = false; - // Present for doors that teleport you to another cell. - if (esm.isNextSub ("DODT")) + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - mTeleport = true; - esm.getHT (mDoorDest); - mDestCell = esm.getHNOString ("DNAM"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'U','N','A','M'>::value: + esm.getHT(mReferenceBlocked); + break; + case ESM::FourCC<'X','S','C','L'>::value: + esm.getHT(mScale); + break; + case ESM::FourCC<'A','N','A','M'>::value: + mOwner = esm.getHString(); + break; + case ESM::FourCC<'B','N','A','M'>::value: + mGlobalVariable = esm.getHString(); + break; + case ESM::FourCC<'X','S','O','L'>::value: + mSoul = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mFaction = esm.getHString(); + break; + case ESM::FourCC<'I','N','D','X'>::value: + esm.getHT(mFactionRank); + break; + case ESM::FourCC<'X','C','H','G'>::value: + esm.getHT(mEnchantmentCharge); + break; + case ESM::FourCC<'I','N','T','V'>::value: + esm.getHT(mChargeInt); + break; + case ESM::FourCC<'N','A','M','9'>::value: + esm.getHT(mGoldValue); + break; + case ESM::FourCC<'D','O','D','T'>::value: + esm.getHT(mDoorDest); + mTeleport = true; + break; + case ESM::FourCC<'D','N','A','M'>::value: + mDestCell = esm.getHString(); + break; + case ESM::FourCC<'F','L','T','V'>::value: + esm.getHT(mLockLevel); + break; + case ESM::FourCC<'K','N','A','M'>::value: + mKey = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTrap = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mPos, 24); + break; + case ESM::FourCC<'N','A','M','0'>::value: + esm.skipHSub(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } - else - mTeleport = false; - - mLockLevel = 0; //Set to 0 to indicate no lock - esm.getHNOT (mLockLevel, "FLTV"); - - mKey = esm.getHNOString ("KNAM"); - mTrap = esm.getHNOString ("TNAM"); - - esm.getHNOT (mReferenceBlocked, "UNAM"); - if (esm.isNextSub("FLTV")) // no longer used - esm.skipHSub(); - - esm.getHNOT(mPos, "DATA", 24); - - if (esm.isNextSub("NAM0")) - esm.skipHSub(); } -void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) const +void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool isDeleted) const { mRefNum.save (esm, wideRefNum); esm.writeHNCString("NAME", mRefID); + if (isDeleted) { + esm.writeHNCString("DELE", ""); + return; + } + if (mScale != 1.0) { esm.writeHNT("XSCL", mScale); } @@ -134,7 +161,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons } if (!inInventory && mLockLevel != 0) { - esm.writeHNT("FLTV", mLockLevel); + esm.writeHNT("FLTV", mLockLevel); } if (!inInventory) @@ -153,7 +180,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons void ESM::CellRef::blank() { mRefNum.unset(); - mRefID.clear(); + mRefID.clear(); mScale = 1; mOwner.clear(); mGlobalVariable.clear(); @@ -169,7 +196,7 @@ void ESM::CellRef::blank() mTrap.clear(); mReferenceBlocked = -1; mTeleport = false; - + for (int i=0; i<3; ++i) { mDoorDest.pos[i] = 0; diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index e9959611b..c371a4f01 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -34,7 +34,6 @@ namespace ESM class CellRef { public: - // Reference number // Note: Currently unused for items in containers RefNum mRefNum; @@ -100,14 +99,14 @@ namespace ESM Position mPos; /// Calls loadId and loadData - void load (ESMReader& esm, bool wideRefNum = false); + void load (ESMReader& esm, bool &isDeleted, bool wideRefNum = false); void loadId (ESMReader& esm, bool wideRefNum = false); /// Implicitly called by load - void loadData (ESMReader& esm); + void loadData (ESMReader& esm, bool &isDeleted); - void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const; + void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 9d605a6af..56be91e71 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -6,15 +6,48 @@ unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; -void ESM::DebugProfile::load (ESMReader& esm) +void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) { - mDescription = esm.getHNString ("DESC"); - mScriptText = esm.getHNString ("SCRP"); - esm.getHNT (mFlags, "FLAG"); + isDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','P'>::value: + mScriptText = esm.getHString(); + break; + case ESM::FourCC<'F','L','A','G'>::value: + esm.getHT(mFlags); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } -void ESM::DebugProfile::save (ESMWriter& esm) const +void ESM::DebugProfile::save (ESMWriter& esm, bool isDeleted) const { + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString ("DESC", mDescription); esm.writeHNCString ("SCRP", mScriptText); esm.writeHNT ("FLAG", mFlags); diff --git a/components/esm/debugprofile.hpp b/components/esm/debugprofile.hpp index b54e8ff5f..c056750a8 100644 --- a/components/esm/debugprofile.hpp +++ b/components/esm/debugprofile.hpp @@ -27,8 +27,8 @@ namespace ESM unsigned int mFlags; - void load (ESMReader& esm); - void save (ESMWriter& esm) const; + void load (ESMReader& esm, bool &isDeleted); + void save (ESMWriter& esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID). void blank(); diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 4be334970..6ef14a70e 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -187,6 +187,11 @@ bool ESMReader::peekNextSub(const char *name) return mCtx.subName == name; } +void ESMReader::cacheSubName() +{ + mCtx.subCached = true; +} + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void ESMReader::getSubName() @@ -276,6 +281,7 @@ void ESMReader::skipRecord() { skip(mCtx.leftRec); mCtx.leftRec = 0; + mCtx.subCached = false; } void ESMReader::getRecHeader(uint32_t &flags) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index c3e6bbbd3..4772aeb6f 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -185,6 +185,9 @@ public: bool peekNextSub(const char* name); + // Store the current subrecord name for the next call of getSubName() + void cacheSubName(); + // Read subrecord name. This gets called a LOT, so I've optimized it // slightly. void getSubName(); diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 5bc768f72..5b33c6683 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -6,14 +6,46 @@ unsigned int ESM::Filter::sRecordId = REC_FILT; -void ESM::Filter::load (ESMReader& esm) +void ESM::Filter::load (ESMReader& esm, bool &isDeleted) { - mFilter = esm.getHNString ("FILT"); - mDescription = esm.getHNString ("DESC"); + isDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + break; + case ESM::FourCC<'F','I','L','T'>::value: + mFilter = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } -void ESM::Filter::save (ESMWriter& esm) const +void ESM::Filter::save (ESMWriter& esm, bool isDeleted) const { + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString ("FILT", mFilter); esm.writeHNCString ("DESC", mDescription); } diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index bc3dd7bdc..b1c511ebb 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -18,8 +18,8 @@ namespace ESM std::string mFilter; - void load (ESMReader& esm); - void save (ESMWriter& esm) const; + void load (ESMReader& esm, bool &isDeleted); + void save (ESMWriter& esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadacti.cpp b/components/esm/loadacti.cpp index b5adce550..14db45c34 100644 --- a/components/esm/loadacti.cpp +++ b/components/esm/loadacti.cpp @@ -8,14 +8,20 @@ namespace ESM { unsigned int Activator::sRecordId = REC_ACTI; - void Activator::load(ESMReader &esm) + void Activator::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -25,13 +31,29 @@ namespace ESM case ESM::FourCC<'S','C','R','I'>::value: mScript = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Activator::save(ESMWriter &esm) const + void Activator::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadacti.hpp b/components/esm/loadacti.hpp index d9a55023b..4cc72d528 100644 --- a/components/esm/loadacti.hpp +++ b/components/esm/loadacti.hpp @@ -17,8 +17,8 @@ struct Activator std::string mId, mName, mScript, mModel; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadalch.cpp b/components/esm/loadalch.cpp index 18db512c0..ceff4ba7d 100644 --- a/components/esm/loadalch.cpp +++ b/components/esm/loadalch.cpp @@ -8,16 +8,23 @@ namespace ESM { unsigned int Potion::sRecordId = REC_ALCH; - void Potion::load(ESMReader &esm) + void Potion::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEffects.mList.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -37,15 +44,31 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEffects.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing ALDT"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing ALDT subrecord"); } - void Potion::save(ESMWriter &esm) const + void Potion::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("TEXT", mIcon); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadalch.hpp b/components/esm/loadalch.hpp index b90a7c448..9ef390ebd 100644 --- a/components/esm/loadalch.hpp +++ b/components/esm/loadalch.hpp @@ -33,8 +33,8 @@ struct Potion std::string mId, mName, mModel, mIcon, mScript; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadappa.cpp b/components/esm/loadappa.cpp index f2c82aacf..7a77ba421 100644 --- a/components/esm/loadappa.cpp +++ b/components/esm/loadappa.cpp @@ -8,47 +8,69 @@ namespace ESM { unsigned int Apparatus::sRecordId = REC_APPA; -void Apparatus::load(ESMReader &esm) -{ - bool hasData = false; - while (esm.hasMoreSubs()) + void Apparatus::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'A','A','D','T'>::value: - esm.getHT(mData); - hasData = true; - break; - case ESM::FourCC<'S','C','R','I'>::value: - mScript = esm.getHString(); - break; - case ESM::FourCC<'I','T','E','X'>::value: - mIcon = esm.getHString(); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'A','A','D','T'>::value: + esm.getHT(mData); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing AADT subrecord"); } - if (!hasData) - esm.fail("Missing AADT"); -} -void Apparatus::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNCString("FNAM", mName); - esm.writeHNT("AADT", mData, 16); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNCString("ITEX", mIcon); -} + void Apparatus::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNCString("FNAM", mName); + esm.writeHNT("AADT", mData, 16); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNCString("ITEX", mIcon); + } void Apparatus::blank() { diff --git a/components/esm/loadappa.hpp b/components/esm/loadappa.hpp index f18b04648..0590d33ed 100644 --- a/components/esm/loadappa.hpp +++ b/components/esm/loadappa.hpp @@ -38,8 +38,8 @@ struct Apparatus AADTstruct mData; std::string mId, mModel, mIcon, mScript, mName; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadarmo.cpp b/components/esm/loadarmo.cpp index 066551d6f..600c417be 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -38,16 +38,23 @@ namespace ESM unsigned int Armor::sRecordId = REC_ARMO; - void Armor::load(ESMReader &esm) + void Armor::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mParts.mParts.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -70,16 +77,32 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } - void Armor::save(ESMWriter &esm) const + void Armor::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 54416fd31..ef3bb734c 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -96,8 +96,8 @@ struct Armor std::string mId, mName, mModel, mIcon, mScript, mEnchant; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index ed24ded57..20d6b35cf 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -8,40 +8,61 @@ namespace ESM { unsigned int BodyPart::sRecordId = REC_BODY; - -void BodyPart::load(ESMReader &esm) -{ - bool hasData = false; - while (esm.hasMoreSubs()) + void BodyPart::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mRace = esm.getHString(); - break; - case ESM::FourCC<'B','Y','D','T'>::value: - esm.getHT(mData, 4); - hasData = true; - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'B','Y','D','T'>::value: + esm.getHT(mData, 4); + hasData = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing BYDT subrecord"); } - if (!hasData) - esm.fail("Missing BYDT subrecord"); -} -void BodyPart::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mRace); - esm.writeHNT("BYDT", mData, 4); -} + void BodyPart::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mRace); + esm.writeHNT("BYDT", mData, 4); + } void BodyPart::blank() { diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index c48c31305..bf320330f 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -60,8 +60,8 @@ struct BodyPart BYDTstruct mData; std::string mId, mModel, mRace; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index 47f52fc31..b08b12f50 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Book::sRecordId = REC_BOOK; - void Book::load(ESMReader &esm) + void Book::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -39,15 +45,31 @@ namespace ESM case ESM::FourCC<'T','E','X','T'>::value: mText = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing BKDT subrecord"); } - void Book::save(ESMWriter &esm) const + void Book::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("BKDT", mData, 20); diff --git a/components/esm/loadbook.hpp b/components/esm/loadbook.hpp index 6211b3e45..3d50356ae 100644 --- a/components/esm/loadbook.hpp +++ b/components/esm/loadbook.hpp @@ -28,8 +28,8 @@ struct Book std::string mName, mModel, mIcon, mScript, mEnchant, mText; std::string mId; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index e0cd83ea0..0413a8610 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -8,41 +8,63 @@ namespace ESM { unsigned int BirthSign::sRecordId = REC_BSGN; -void BirthSign::load(ESMReader &esm) -{ - mPowers.mList.clear(); - while (esm.hasMoreSubs()) + void BirthSign::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + mPowers.mList.clear(); + + bool hasName = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'T','N','A','M'>::value: - mTexture = esm.getHString(); - break; - case ESM::FourCC<'D','E','S','C'>::value: - mDescription = esm.getHString(); - break; - case ESM::FourCC<'N','P','C','S'>::value: - mPowers.add(esm); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'T','N','A','M'>::value: + mTexture = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } -} -void BirthSign::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("TNAM", mTexture); - esm.writeHNOCString("DESC", mDescription); + void BirthSign::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); - mPowers.save(esm); -} + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("TNAM", mTexture); + esm.writeHNOCString("DESC", mDescription); + + mPowers.save(esm); + } void BirthSign::blank() { diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index f91f91c97..24d27a7f8 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -22,8 +22,8 @@ struct BirthSign // List of powers and abilities that come with this birth sign. SpellList mPowers; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 94f4b0b6e..703a4f4cb 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -52,173 +52,200 @@ namespace ESM return ref.mRefNum == refNum; } -void Cell::load(ESMReader &esm, bool saveContext) -{ - loadData(esm); - loadCell(esm, saveContext); -} - -void Cell::loadCell(ESMReader &esm, bool saveContext) -{ - mRefNumCounter = 0; + void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext) + { + loadNameAndData(esm, isDeleted); + loadCell(esm, saveContext); + } - if (mData.mFlags & Interior) + void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted) { - // Interior cells - if (esm.isNextSub("INTV")) - { - int waterl; - esm.getHT(waterl); - mWater = (float) waterl; - mWaterInt = true; - } - else if (esm.isNextSub("WHGT")) + isDeleted = false; + + blank(); + + bool hasData = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - esm.getHT(mWater); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } } - // Quasi-exterior cells have a region (which determines the - // weather), pure interior cells have ambient lighting - // instead. - if (mData.mFlags & QuasiEx) - mRegion = esm.getHNOString("RGNN"); - else if (esm.isNextSub("AMBI")) - esm.getHT(mAmbi); + if (!hasData) + esm.fail("Missing DATA subrecord"); } - else + + void Cell::loadCell(ESMReader &esm, bool saveContext) { - // Exterior cells - mRegion = esm.getHNOString("RGNN"); + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'I','N','T','V'>::value: + int waterl; + esm.getHT(waterl); + mWater = static_cast(waterl); + mWaterInt = true; + break; + case ESM::FourCC<'W','H','G','T'>::value: + esm.getHT(mWater); + mWaterInt = false; + break; + case ESM::FourCC<'A','M','B','I'>::value: + esm.getHT(mAmbi); + break; + case ESM::FourCC<'R','G','N','N'>::value: + mRegion = esm.getHString(); + break; + case ESM::FourCC<'N','A','M','5'>::value: + esm.getHT(mMapColor); + break; + case ESM::FourCC<'N','A','M','0'>::value: + esm.getHT(mRefNumCounter); + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } + } - mMapColor = 0; - esm.getHNOT(mMapColor, "NAM5"); - } - if (esm.isNextSub("NAM0")) { - esm.getHT(mRefNumCounter); + if (saveContext) + { + mContextList.push_back(esm.getContext()); + esm.skipRecord(); + } } - if (saveContext) { + void Cell::postLoad(ESMReader &esm) + { + // Save position of the cell references and move on mContextList.push_back(esm.getContext()); esm.skipRecord(); } -} - -void Cell::loadData(ESMReader &esm) -{ - // Ignore this for now, it might mean we should delete the entire - // cell? - // TODO: treat the special case "another plugin moved this ref, but we want to delete it"! - if (esm.isNextSub("DELE")) { - esm.skipHSub(); - } - esm.getHNT(mData, "DATA", 12); -} - -void Cell::postLoad(ESMReader &esm) -{ - // Save position of the cell references and move on - mContextList.push_back(esm.getContext()); - esm.skipRecord(); -} - -void Cell::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mData, 12); - if (mData.mFlags & Interior) + void Cell::save(ESMWriter &esm, bool isDeleted) const { - if (mWaterInt) { - int water = - (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); - esm.writeHNT("INTV", water); - } else { - esm.writeHNT("WHGT", mWater); + esm.writeHNOCString("NAME", mName); + esm.writeHNT("DATA", mData, 12); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; } - if (mData.mFlags & QuasiEx) - esm.writeHNOCString("RGNN", mRegion); + if (mData.mFlags & Interior) + { + if (mWaterInt) { + int water = + (mWater >= 0) ? (int) (mWater + 0.5) : (int) (mWater - 0.5); + esm.writeHNT("INTV", water); + } else { + esm.writeHNT("WHGT", mWater); + } + + if (mData.mFlags & QuasiEx) + esm.writeHNOCString("RGNN", mRegion); + else + esm.writeHNT("AMBI", mAmbi, 16); + } else - esm.writeHNT("AMBI", mAmbi, 16); - } - else - { - esm.writeHNOCString("RGNN", mRegion); - if (mMapColor != 0) - esm.writeHNT("NAM5", mMapColor); - } - - if (mRefNumCounter != 0) - esm.writeHNT("NAM0", mRefNumCounter); -} - -void Cell::restore(ESMReader &esm, int iCtx) const -{ - esm.restoreContext(mContextList.at (iCtx)); -} + { + esm.writeHNOCString("RGNN", mRegion); + if (mMapColor != 0) + esm.writeHNT("NAM5", mMapColor); + } -std::string Cell::getDescription() const -{ - if (mData.mFlags & Interior) - { - return mName; + if (mRefNumCounter != 0) + esm.writeHNT("NAM0", mRefNumCounter); } - else + + void Cell::restore(ESMReader &esm, int iCtx) const { - std::ostringstream stream; - stream << mData.mX << ", " << mData.mY; - return stream.str(); + esm.restoreContext(mContextList.at (iCtx)); } -} - -bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted, bool ignoreMoves, MovedCellRef *mref) -{ - // TODO: Try and document reference numbering, I don't think this has been done anywhere else. - if (!esm.hasMoreSubs()) - return false; - // NOTE: We should not need this check. It is a safety check until we have checked - // more plugins, and how they treat these moved references. - if (esm.isNextSub("MVRF")) + std::string Cell::getDescription() const { - if (ignoreMoves) + if (mData.mFlags & Interior) { - esm.getHT (mref->mRefNum.mIndex); - esm.getHNOT (mref->mTarget, "CNDT"); - adjustRefNum (mref->mRefNum, esm); + return mName; } else { - // skip rest of cell record (moved references), they are handled elsewhere - esm.skipRecord(); // skip MVRF, CNDT - return false; + std::ostringstream stream; + stream << mData.mX << ", " << mData.mY; + return stream.str(); } } - ref.load (esm); + bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool &isDeleted, bool ignoreMoves, MovedCellRef *mref) + { + isDeleted = false; - // Identify references belonging to a parent file and adapt the ID accordingly. - adjustRefNum (ref.mRefNum, esm); + // TODO: Try and document reference numbering, I don't think this has been done anywhere else. + if (!esm.hasMoreSubs()) + return false; - if (esm.isNextSub("DELE")) - { - esm.skipHSub(); - deleted = true; - } - else - deleted = false; + // NOTE: We should not need this check. It is a safety check until we have checked + // more plugins, and how they treat these moved references. + if (esm.isNextSub("MVRF")) + { + if (ignoreMoves) + { + esm.getHT (mref->mRefNum.mIndex); + esm.getHNOT (mref->mTarget, "CNDT"); + adjustRefNum (mref->mRefNum, esm); + } + else + { + // skip rest of cell record (moved references), they are handled elsewhere + esm.skipRecord(); // skip MVRF, CNDT + return false; + } + } - return true; -} + if (esm.peekNextSub("FRMR")) + { + ref.load (esm, isDeleted); -bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) -{ - esm.getHT(mref.mRefNum.mIndex); - esm.getHNOT(mref.mTarget, "CNDT"); + // Identify references belonging to a parent file and adapt the ID accordingly. + adjustRefNum (ref.mRefNum, esm); + return true; + } + return false; + } - adjustRefNum (mref.mRefNum, esm); + bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) + { + esm.getHT(mref.mRefNum.mIndex); + esm.getHNOT(mref.mTarget, "CNDT"); - return true; -} + adjustRefNum (mref.mRefNum, esm); + + return true; + } void Cell::blank() { diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 12fb8c068..2a7a78a54 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -116,11 +116,11 @@ struct Cell // This method is left in for compatibility with esmtool. Parsing moved references currently requires // passing ESMStore, bit it does not know about this parameter, so we do it this way. - void load(ESMReader &esm, bool saveContext = true); // Load everything (except references) - void loadData(ESMReader &esm); // Load DATAstruct only - void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except DATAstruct and references + void load(ESMReader &esm, bool &isDeleted, bool saveContext = true); // Load everything (except references) + void loadNameAndData(ESMReader &esm, bool &isDeleted); // Load NAME and DATAstruct + void loadCell(ESMReader &esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; bool isExterior() const { @@ -159,8 +159,11 @@ struct Cell reuse one memory location without blanking it between calls. */ /// \param ignoreMoves ignore MVRF record and read reference like a regular CellRef. - static bool getNextRef(ESMReader &esm, - CellRef &ref, bool& deleted, bool ignoreMoves = false, MovedCellRef *mref = 0); + static bool getNextRef(ESMReader &esm, + CellRef &ref, + bool &isDeleted, + bool ignoreMoves = false, + MovedCellRef *mref = 0); /* This fetches an MVRF record, which is used to track moved references. * Since they are comparably rare, we use a separate method for this. diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index 66acaea72..2ad14b9f2 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -22,7 +22,6 @@ namespace ESM "sSpecializationStealth" }; - int& Class::CLDTstruct::getSkill (int index, bool major) { if (index<0 || index>=5) @@ -39,15 +38,21 @@ namespace ESM return mSkills[index][major ? 1 : 0]; } - void Class::load(ESMReader &esm) + void Class::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -60,15 +65,31 @@ namespace ESM case ESM::FourCC<'D','E','S','C'>::value: mDescription = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CLDT subrecord"); } - void Class::save(ESMWriter &esm) const + void Class::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); esm.writeHNT("CLDT", mData, 60); esm.writeHNOString("DESC", mDescription); diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 972b48e88..833dd6757 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -73,8 +73,8 @@ struct Class std::string mId, mName, mDescription; CLDTstruct mData; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 5f49b5e70..8a88e6d7a 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -8,16 +8,23 @@ namespace ESM { unsigned int Clothing::sRecordId = REC_CLOT; - void Clothing::load(ESMReader &esm) + void Clothing::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mParts.mParts.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -40,16 +47,32 @@ namespace ESM case ESM::FourCC<'I','N','D','X'>::value: mParts.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing CTDT subrecord"); } - void Clothing::save(ESMWriter &esm) const + void Clothing::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CTDT", mData, 12); diff --git a/components/esm/loadclot.hpp b/components/esm/loadclot.hpp index 6945f224a..39e5ea672 100644 --- a/components/esm/loadclot.hpp +++ b/components/esm/loadclot.hpp @@ -48,8 +48,8 @@ struct Clothing std::string mId, mName, mModel, mIcon, mEnchant, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 3481189c3..372683750 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -24,17 +24,24 @@ namespace ESM unsigned int Container::sRecordId = REC_CONT; - void Container::load(ESMReader &esm) + void Container::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mInventory.mList.clear(); + + bool hasName = false; bool hasWeight = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -59,18 +66,34 @@ namespace ESM case ESM::FourCC<'N','P','C','O'>::value: mInventory.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasWeight) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasWeight && !isDeleted) esm.fail("Missing CNDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void Container::save(ESMWriter &esm) const + void Container::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("CNDT", mWeight, 4); diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index ab587f935..4c847f4e2 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -52,8 +52,8 @@ struct Container int mFlags; InventoryList mInventory; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadcrea.cpp b/components/esm/loadcrea.cpp index fb235e6b3..e593ff540 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -8,8 +8,10 @@ namespace ESM { unsigned int Creature::sRecordId = REC_CREA; - void Creature::load(ESMReader &esm) + void Creature::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mAiPackage.mList.clear(); @@ -19,14 +21,19 @@ namespace ESM { mScale = 1.f; mHasAI = false; + + bool hasName = false; bool hasNpdt = false; bool hasFlags = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -72,18 +79,34 @@ namespace ESM { case AI_CNDT: mAiPackage.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasNpdt) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void Creature::save(ESMWriter &esm) const + void Creature::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("CNAM", mOriginal); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadcrea.hpp b/components/esm/loadcrea.hpp index 47e5954a5..a5147619c 100644 --- a/components/esm/loadcrea.hpp +++ b/components/esm/loadcrea.hpp @@ -91,7 +91,6 @@ struct Creature InventoryList mInventory; SpellList mSpells; - bool mHasAI; AIData mAiData; AIPackageList mAiPackage; @@ -99,8 +98,8 @@ struct Creature const std::vector& getTransport() const; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loaddial.cpp b/components/esm/loaddial.cpp index f2da8f377..30fa3cfef 100644 --- a/components/esm/loaddial.cpp +++ b/components/esm/loaddial.cpp @@ -10,121 +10,150 @@ namespace ESM { unsigned int Dialogue::sRecordId = REC_DIAL; -void Dialogue::load(ESMReader &esm) -{ - esm.getSubNameIs("DATA"); - esm.getSubHeader(); - int si = esm.getSubSize(); - if (si == 1) - esm.getT(mType); - else if (si == 4) + void Dialogue::load(ESMReader &esm, bool &isDeleted) { - // These are just markers, their values are not used. - int i; - esm.getT(i); - esm.getHNT(i, "DELE"); - mType = Deleted; + loadId(esm); + loadData(esm, isDeleted); } - else - esm.fail("Unknown sub record size"); -} -void Dialogue::save(ESMWriter &esm) const -{ - if (mType != Deleted) - esm.writeHNT("DATA", mType); - else + void Dialogue::loadId(ESMReader &esm) { - esm.writeHNT("DATA", (int)1); - esm.writeHNT("DELE", (int)1); + mId = esm.getHNString("NAME"); } -} - -void Dialogue::blank() -{ - mInfo.clear(); -} - -void Dialogue::readInfo(ESMReader &esm, bool merge) -{ - const std::string& id = esm.getHNOString("INAM"); - if (!merge || mInfo.empty()) + void Dialogue::loadData(ESMReader &esm, bool &isDeleted) { - ESM::DialInfo info; - info.mId = id; - info.load(esm); - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; + isDeleted = false; + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'D','A','T','A'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + if (size == 1) + { + esm.getT(mType); + } + else + { + esm.skip(size); + } + break; + } + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + mType = Unknown; + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } - ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); - - std::map::iterator lookup; - - lookup = mLookup.find(id); - - ESM::DialInfo info; - if (lookup != mLookup.end()) - { - it = lookup->second; - - // Merge with existing record. Only the subrecords that are present in - // the new record will be overwritten. - it->load(esm); - info = *it; - - // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record - mInfo.erase(it); - mLookup.erase(lookup); - } - else + void Dialogue::save(ESMWriter &esm, bool isDeleted) const { - info.mId = id; - info.load(esm); + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNT("DATA", mType); + } } - if (info.mNext.empty()) - { - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; - } - if (info.mPrev.empty()) + void Dialogue::blank() { - mLookup[id] = mInfo.insert(mInfo.begin(), info); - return; + mInfo.clear(); } - lookup = mLookup.find(info.mPrev); - if (lookup != mLookup.end()) + void Dialogue::readInfo(ESMReader &esm, bool merge) { - it = lookup->second; + ESM::DialInfo info; + info.loadId(esm); - mLookup[id] = mInfo.insert(++it, info); - return; - } + bool isDeleted = false; + if (!merge || mInfo.empty()) + { + info.loadData(esm, isDeleted); + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); - lookup = mLookup.find(info.mNext); - if (lookup != mLookup.end()) - { - it = lookup->second; + return; + } - mLookup[id] = mInfo.insert(it, info); - return; - } + InfoContainer::iterator it = mInfo.end(); - std::cerr << "Failed to insert info " << id << std::endl; -} + LookupMap::iterator lookup; + lookup = mLookup.find(info.mId); -void Dialogue::clearDeletedInfos() -{ - for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) - { - if (it->mQuestStatus == DialInfo::QS_Deleted) - it = mInfo.erase(it); + if (lookup != mLookup.end()) + { + it = lookup->second.first; + + // Merge with existing record. Only the subrecords that are present in + // the new record will be overwritten. + it->loadData(esm, isDeleted); + info = *it; + + // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record + mInfo.erase(it); + mLookup.erase(lookup); + } else - ++it; + { + info.loadData(esm, isDeleted); + } + + if (info.mNext.empty()) + { + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); + return; + } + if (info.mPrev.empty()) + { + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.begin(), info), isDeleted); + return; + } + + lookup = mLookup.find(info.mPrev); + if (lookup != mLookup.end()) + { + it = lookup->second.first; + + mLookup[info.mId] = std::make_pair(mInfo.insert(++it, info), isDeleted); + return; + } + + lookup = mLookup.find(info.mNext); + if (lookup != mLookup.end()) + { + it = lookup->second.first; + + mLookup[info.mId] = std::make_pair(mInfo.insert(it, info), isDeleted); + return; + } + + std::cerr << "Failed to insert info " << info.mId << std::endl; } -} + void Dialogue::clearDeletedInfos() + { + LookupMap::const_iterator current = mLookup.begin(); + LookupMap::const_iterator end = mLookup.end(); + for (; current != end; ++current) + { + if (current->second.second) + { + mInfo.erase(current->second.first); + } + } + mLookup.clear(); + } } diff --git a/components/esm/loaddial.hpp b/components/esm/loaddial.hpp index 58598d353..b80cbd74c 100644 --- a/components/esm/loaddial.hpp +++ b/components/esm/loaddial.hpp @@ -31,7 +31,7 @@ struct Dialogue Greeting = 2, Persuasion = 3, Journal = 4, - Deleted = -1 + Unknown = -1 // Used for deleted dialogues }; std::string mId; @@ -39,17 +39,24 @@ struct Dialogue typedef std::list InfoContainer; - typedef std::map LookupMap; + // Parameters: Info ID, (Info iterator, Deleted flag) + typedef std::map > LookupMap; InfoContainer mInfo; // This is only used during the loading phase to speed up DialInfo merging. LookupMap mLookup; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Dialogue record + void loadId(ESMReader &esm); + ///< Loads NAME sub-record of Dialogue record + void loadData(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Dialogue record, except NAME sub-record - /// Remove all INFOs marked as QS_Deleted from mInfos. + void save(ESMWriter &esm, bool isDeleted = false) const; + + /// Remove all INFOs that are deleted void clearDeletedInfos(); /// Read the next info record diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index f446eed61..706e938e8 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -8,14 +8,20 @@ namespace ESM { unsigned int Door::sRecordId = REC_DOOR; - void Door::load(ESMReader &esm) + void Door::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -31,14 +37,30 @@ namespace ESM case ESM::FourCC<'A','N','A','M'>::value: mCloseSound = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Door::save(ESMWriter &esm) const + void Door::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("SCRI", mScript); diff --git a/components/esm/loaddoor.hpp b/components/esm/loaddoor.hpp index 3073f4e9d..3afe5d5e4 100644 --- a/components/esm/loaddoor.hpp +++ b/components/esm/loaddoor.hpp @@ -17,8 +17,8 @@ struct Door std::string mId, mName, mModel, mScript, mOpenSound, mCloseSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 54690d9a0..5580ef222 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -8,37 +8,58 @@ namespace ESM { unsigned int Enchantment::sRecordId = REC_ENCH; -void Enchantment::load(ESMReader &esm) -{ - mEffects.mList.clear(); - bool hasData = false; - while (esm.hasMoreSubs()) + void Enchantment::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + mEffects.mList.clear(); + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'E','N','D','T'>::value: - esm.getHT(mData, 16); - hasData = true; - break; - case ESM::FourCC<'E','N','A','M'>::value: - mEffects.add(esm); - break; - default: - esm.fail("Unknown subrecord"); - break; + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'E','N','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEffects.add(esm); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing ENDT subrecord"); } - if (!hasData) - esm.fail("Missing ENDT subrecord"); -} -void Enchantment::save(ESMWriter &esm) const -{ - esm.writeHNT("ENDT", mData, 16); - mEffects.save(esm); -} + void Enchantment::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNT("ENDT", mData, 16); + mEffects.save(esm); + } void Enchantment::blank() { diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index cfcdd4edc..7b93b519c 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -42,8 +42,8 @@ struct Enchantment ENDTstruct mData; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index 006ca0ce0..f550a5538 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -26,69 +26,92 @@ namespace ESM return mSkills[index]; } -void Faction::load(ESMReader &esm) -{ - mReactions.clear(); - for (int i=0;i<10;++i) - mRanks[i].clear(); - - int rankCounter=0; - bool hasData = false; - while (esm.hasMoreSubs()) + void Faction::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + mReactions.clear(); + for (int i=0;i<10;++i) + mRanks[i].clear(); + + int rankCounter = 0; + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','N','A','M'>::value: - if (rankCounter >= 10) - esm.fail("Rank out of range"); - mRanks[rankCounter++] = esm.getHString(); - break; - case ESM::FourCC<'F','A','D','T'>::value: - esm.getHT(mData, 240); - if (mData.mIsHidden > 1) - esm.fail("Unknown flag!"); - hasData = true; - break; - case ESM::FourCC<'A','N','A','M'>::value: + esm.getSubName(); + switch (esm.retSubName().val) { - std::string faction = esm.getHString(); - int reaction; - esm.getHNT(reaction, "INTV"); - mReactions[faction] = reaction; - break; + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + if (rankCounter >= 10) + esm.fail("Rank out of range"); + mRanks[rankCounter++] = esm.getHString(); + break; + case ESM::FourCC<'F','A','D','T'>::value: + esm.getHT(mData, 240); + if (mData.mIsHidden > 1) + esm.fail("Unknown flag!"); + hasData = true; + break; + case ESM::FourCC<'A','N','A','M'>::value: + { + std::string faction = esm.getHString(); + int reaction; + esm.getHNT(reaction, "INTV"); + mReactions[faction] = reaction; + break; + } + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } - default: - esm.fail("Unknown subrecord"); } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing FADT subrecord"); } - if (!hasData) - esm.fail("Missing FADT subrecord"); -} -void Faction::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - for (int i = 0; i < 10; i++) + void Faction::save(ESMWriter &esm, bool isDeleted) const { - if (mRanks[i].empty()) - break; + esm.writeHNCString("NAME", mId); - esm.writeHNString("RNAM", mRanks[i], 32); - } + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } - esm.writeHNT("FADT", mData, 240); + esm.writeHNOCString("FNAM", mName); - for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) - { - esm.writeHNString("ANAM", it->first); - esm.writeHNT("INTV", it->second); + for (int i = 0; i < 10; i++) + { + if (mRanks[i].empty()) + break; + + esm.writeHNString("RNAM", mRanks[i], 32); + } + + esm.writeHNT("FADT", mData, 240); + + for (std::map::const_iterator it = mReactions.begin(); it != mReactions.end(); ++it) + { + esm.writeHNString("ANAM", it->first); + esm.writeHNT("INTV", it->second); + } } -} void Faction::blank() { diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 8645e23fd..cc715d266 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -62,8 +62,8 @@ struct Faction // Name of faction ranks (may be empty for NPC factions) std::string mRanks[10]; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index a78ed1a1b..72ecce503 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -1,19 +1,42 @@ #include "loadglob.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int Global::sRecordId = REC_GLOB; - void Global::load (ESMReader &esm) + void Global::load (ESMReader &esm, bool &isDeleted) { - mValue.read (esm, ESM::Variant::Format_Global); + isDeleted = false; + + mId = esm.getHNString ("NAME"); + + if (esm.isNextSub ("DELE")) + { + esm.skipHSub(); + isDeleted = true; + } + else + { + mValue.read (esm, ESM::Variant::Format_Global); + } } - void Global::save (ESMWriter &esm) const + void Global::save (ESMWriter &esm, bool isDeleted) const { - mValue.write (esm, ESM::Variant::Format_Global); + esm.writeHNCString ("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString ("DELE", ""); + } + else + { + mValue.write (esm, ESM::Variant::Format_Global); + } } void Global::blank() diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index cc5dbbdcf..0533cc95e 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -24,8 +24,8 @@ struct Global std::string mId; Variant mValue; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index 21d66339a..1ebb002e6 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -1,18 +1,24 @@ #include "loadgmst.hpp" +#include "esmreader.hpp" +#include "esmwriter.hpp" #include "defs.hpp" namespace ESM { unsigned int GameSetting::sRecordId = REC_GMST; - void GameSetting::load (ESMReader &esm) + void GameSetting::load (ESMReader &esm, bool &isDeleted) { + isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future) + + mId = esm.getHNString("NAME"); mValue.read (esm, ESM::Variant::Format_Gmst); } - void GameSetting::save (ESMWriter &esm) const + void GameSetting::save (ESMWriter &esm, bool /*isDeleted*/) const { + esm.writeHNCString("NAME", mId); mValue.write (esm, ESM::Variant::Format_Gmst); } diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index d9d9048b6..73a723e81 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -26,7 +26,7 @@ struct GameSetting Variant mValue; - void load(ESMReader &esm); + void load(ESMReader &esm, bool &isDeleted); /// \todo remove the get* functions (redundant, since mValue has equivalent functions now). @@ -39,7 +39,7 @@ struct GameSetting std::string getString() const; ///< Throwns an exception if GMST is not of type string. - void save(ESMWriter &esm) const; + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadinfo.cpp b/components/esm/loadinfo.cpp index a2bade1c5..d1f20a3d4 100644 --- a/components/esm/loadinfo.cpp +++ b/components/esm/loadinfo.cpp @@ -8,156 +8,140 @@ namespace ESM { unsigned int DialInfo::sRecordId = REC_INFO; -void DialInfo::load(ESMReader &esm) -{ - mQuestStatus = QS_None; - mFactionLess = false; - - mPrev = esm.getHNString("PNAM"); - mNext = esm.getHNString("NNAM"); - - // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings - mSelects.clear(); - - // Not present if deleted - if (esm.isNextSub("DATA")) { - esm.getHT(mData, 12); - } - - if (!esm.hasMoreSubs()) - return; - - // What follows is somewhat spaghetti-ish, but it's worth if for - // an extra speedup. INFO is by far the most common record type. - - // subName is a reference to the original, so it changes whenever - // a new sub name is read. esm.isEmptyOrGetName() will get the - // next name for us, or return true if there are no more records. - esm.getSubName(); - const NAME &subName = esm.retSubName(); - - if (subName.val == REC_ONAM) + void DialInfo::load(ESMReader &esm, bool &isDeleted) { - mActor = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_RNAM) - { - mRace = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_CNAM) - { - mClass = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; + loadId(esm); + loadData(esm, isDeleted); } - if (subName.val == REC_FNAM) + void DialInfo::loadId(ESMReader &esm) { - mFaction = esm.getHString(); - if (mFaction == "FFFF") - mFactionLess = true; - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_ANAM) - { - mCell = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_DNAM) - { - mPcFaction = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_SNAM) - { - mSound = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_NAME) - { - mResponse = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; + mId = esm.getHNString("INAM"); } - while (subName.val == REC_SCVR) + void DialInfo::loadData(ESMReader &esm, bool &isDeleted) { - SelectStruct ss; + isDeleted = false; - ss.mSelectRule = esm.getHString(); - - ss.mValue.read (esm, Variant::Format_Info); + mQuestStatus = QS_None; + mFactionLess = false; - mSelects.push_back(ss); + mPrev = esm.getHNString("PNAM"); + mNext = esm.getHNString("NNAM"); - if (esm.isEmptyOrGetName()) - return; - } + // Since there's no way to mark selects as "deleted", we have to clear the SelectStructs from all previous loadings + mSelects.clear(); - if (subName.val == REC_BNAM) - { - mResultScript = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + break; + case ESM::FourCC<'O','N','A','M'>::value: + mActor = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mClass = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + { + mFaction = esm.getHString(); + if (mFaction == "FFFF") + { + mFactionLess = true; + } + break; + } + case ESM::FourCC<'A','N','A','M'>::value: + mCell = esm.getHString(); + break; + case ESM::FourCC<'D','N','A','M'>::value: + mPcFaction = esm.getHString(); + break; + case ESM::FourCC<'S','N','A','M'>::value: + mSound = esm.getHString(); + break; + case ESM::FourCC<'N','A','M','E'>::value: + mResponse = esm.getHString(); + break; + case ESM::FourCC<'S','C','V','R'>::value: + { + SelectStruct ss; + ss.mSelectRule = esm.getHString(); + ss.mValue.read(esm, Variant::Format_Info); + mSelects.push_back(ss); + break; + } + case ESM::FourCC<'B','N','A','M'>::value: + mResultScript = esm.getHString(); + break; + case ESM::FourCC<'Q','S','T','N'>::value: + mQuestStatus = QS_Name; + esm.skipRecord(); + break; + case ESM::FourCC<'Q','S','T','F'>::value: + mQuestStatus = QS_Finished; + esm.skipRecord(); + break; + case ESM::FourCC<'Q','S','T','R'>::value: + mQuestStatus = QS_Restart; + esm.skipRecord(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } } - if (subName.val == REC_QSTN) - mQuestStatus = QS_Name; - else if (subName.val == REC_QSTF) - mQuestStatus = QS_Finished; - else if (subName.val == REC_QSTR) - mQuestStatus = QS_Restart; - else if (subName.val == REC_DELE) - mQuestStatus = QS_Deleted; - else - esm.fail( - "Don't know what to do with " + subName.toString() - + " in INFO " + mId); - - if (mQuestStatus != QS_None) - // Skip rest of record - esm.skipRecord(); -} - -void DialInfo::save(ESMWriter &esm) const -{ - esm.writeHNCString("PNAM", mPrev); - esm.writeHNCString("NNAM", mNext); - esm.writeHNT("DATA", mData, 12); - esm.writeHNOCString("ONAM", mActor); - esm.writeHNOCString("RNAM", mRace); - esm.writeHNOCString("CNAM", mClass); - esm.writeHNOCString("FNAM", mFaction); - esm.writeHNOCString("ANAM", mCell); - esm.writeHNOCString("DNAM", mPcFaction); - esm.writeHNOCString("SNAM", mSound); - esm.writeHNOString("NAME", mResponse); - - for (std::vector::const_iterator it = mSelects.begin(); it != mSelects.end(); ++it) + void DialInfo::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNString("SCVR", it->mSelectRule); - it->mValue.write (esm, Variant::Format_Info); - } - - esm.writeHNOString("BNAM", mResultScript); + esm.writeHNCString("INAM", mId); + esm.writeHNCString("PNAM", mPrev); + esm.writeHNCString("NNAM", mNext); - switch(mQuestStatus) - { - case QS_Name: esm.writeHNT("QSTN",'\1'); break; - case QS_Finished: esm.writeHNT("QSTF", '\1'); break; - case QS_Restart: esm.writeHNT("QSTR", '\1'); break; - case QS_Deleted: esm.writeHNT("DELE", '\1'); break; - default: break; + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNT("DATA", mData, 12); + esm.writeHNOCString("ONAM", mActor); + esm.writeHNOCString("RNAM", mRace); + esm.writeHNOCString("CNAM", mClass); + esm.writeHNOCString("FNAM", mFaction); + esm.writeHNOCString("ANAM", mCell); + esm.writeHNOCString("DNAM", mPcFaction); + esm.writeHNOCString("SNAM", mSound); + esm.writeHNOString("NAME", mResponse); + + for (std::vector::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() { diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 54003b0d9..8123a9ace 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -59,8 +59,7 @@ struct DialInfo QS_None = 0, QS_Name = 1, QS_Finished = 2, - QS_Restart = 3, - QS_Deleted + QS_Restart = 3 }; // Rules for when to include this item in the final list of options @@ -106,8 +105,14 @@ struct DialInfo REC_DELE = 0x454c4544 }; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Info record + void loadId(ESMReader &esm); + ///< Loads only Id of Info record (INAM sub-record) + void loadData(ESMReader &esm, bool &isDeleted); + ///< Loads all sub-records of Info record, except INAM sub-record + + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadingr.cpp b/components/esm/loadingr.cpp index 7e0cc3168..a481d5b79 100644 --- a/components/esm/loadingr.cpp +++ b/components/esm/loadingr.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Ingredient::sRecordId = REC_INGR; - void Ingredient::load(ESMReader &esm) + void Ingredient::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,12 +39,19 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing IRDT subrecord"); // horrible hack to fix broken data in records @@ -65,8 +78,16 @@ namespace ESM } } - void Ingredient::save(ESMWriter &esm) const + void Ingredient::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("IRDT", mData, 56); diff --git a/components/esm/loadingr.hpp b/components/esm/loadingr.hpp index 5846a9780..c0f445023 100644 --- a/components/esm/loadingr.hpp +++ b/components/esm/loadingr.hpp @@ -31,8 +31,8 @@ struct Ingredient IRDTstruct mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 784cfd407..9db58fc5b 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -10,212 +10,251 @@ namespace ESM { unsigned int Land::sRecordId = REC_LAND; -void Land::LandData::save(ESMWriter &esm) const -{ - if (mDataTypes & Land::DATA_VNML) { - esm.writeHNT("VNML", mNormals, sizeof(mNormals)); - } - if (mDataTypes & Land::DATA_VHGT) { - VHGT offsets; - offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; - offsets.mUnk1 = mUnk1; - offsets.mUnk2 = mUnk2; - - float prevY = mHeights[0]; - int number = 0; // avoid multiplication - for (int i = 0; i < LAND_SIZE; ++i) { - float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; - offsets.mHeightData[number] = - (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - - float prevX = prevY = mHeights[number]; - ++number; - - for (int j = 1; j < LAND_SIZE; ++j) { - diff = (mHeights[number] - prevX) / HEIGHT_SCALE; + void Land::LandData::save(ESMWriter &esm) const + { + if (mDataTypes & Land::DATA_VNML) { + esm.writeHNT("VNML", mNormals, sizeof(mNormals)); + } + if (mDataTypes & Land::DATA_VHGT) { + VHGT offsets; + offsets.mHeightOffset = mHeights[0] / HEIGHT_SCALE; + offsets.mUnk1 = mUnk1; + offsets.mUnk2 = mUnk2; + + float prevY = mHeights[0]; + int number = 0; // avoid multiplication + for (int i = 0; i < LAND_SIZE; ++i) { + float diff = (mHeights[number] - prevY) / HEIGHT_SCALE; offsets.mHeightData[number] = (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); - prevX = mHeights[number]; + float prevX = prevY = mHeights[number]; ++number; + + for (int j = 1; j < LAND_SIZE; ++j) { + diff = (mHeights[number] - prevX) / HEIGHT_SCALE; + offsets.mHeightData[number] = + (diff >= 0) ? (int8_t) (diff + 0.5) : (int8_t) (diff - 0.5); + + prevX = mHeights[number]; + ++number; + } } + esm.writeHNT("VHGT", offsets, sizeof(VHGT)); + } + if (mDataTypes & Land::DATA_WNAM) { + esm.writeHNT("WNAM", mWnam, 81); + } + if (mDataTypes & Land::DATA_VCLR) { + esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS); + } + if (mDataTypes & Land::DATA_VTEX) { + static uint16_t vtex[LAND_NUM_TEXTURES]; + transposeTextureData(mTextures, vtex); + esm.writeHNT("VTEX", vtex, sizeof(vtex)); } - esm.writeHNT("VHGT", offsets, sizeof(VHGT)); - } - if (mDataTypes & Land::DATA_WNAM) { - esm.writeHNT("WNAM", mWnam, 81); } - if (mDataTypes & Land::DATA_VCLR) { - esm.writeHNT("VCLR", mColours, 3*LAND_NUM_VERTS); + + Land::Land() + : mFlags(0) + , mX(0) + , mY(0) + , mPlugin(0) + , mEsm(NULL) + , mDataTypes(0) + , mDataLoaded(false) + , mLandData(NULL) + { } - if (mDataTypes & Land::DATA_VTEX) { - static uint16_t vtex[LAND_NUM_TEXTURES]; - transposeTextureData(mTextures, vtex); - esm.writeHNT("VTEX", vtex, sizeof(vtex)); + + void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) + { + int readPos = 0; //bit ugly, but it works + for ( int y1 = 0; y1 < 4; y1++ ) + for ( int x1 = 0; x1 < 4; x1++ ) + for ( int y2 = 0; y2 < 4; y2++) + for ( int x2 = 0; x2 < 4; x2++ ) + out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; } -} -void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out) -{ - int readPos = 0; //bit ugly, but it works - for ( int y1 = 0; y1 < 4; y1++ ) - for ( int x1 = 0; x1 < 4; x1++ ) - for ( int y2 = 0; y2 < 4; y2++) - for ( int x2 = 0; x2 < 4; x2++ ) - out[(y1*4+y2)*16+(x1*4+x2)] = in[readPos++]; -} + Land::~Land() + { + delete mLandData; + } -Land::Land() - : mFlags(0) - , mX(0) - , mY(0) - , mPlugin(0) - , mEsm(NULL) - , mDataTypes(0) - , mDataLoaded(false) - , mLandData(NULL) -{ -} + void Land::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; -Land::~Land() -{ - delete mLandData; -} + mEsm = &esm; + mPlugin = mEsm->getIndex(); -void Land::load(ESMReader &esm) -{ - mEsm = &esm; - mPlugin = mEsm->getIndex(); + bool hasLocation = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'I','N','T','V'>::value: + esm.getSubHeaderIs(8); + esm.getT(mX); + esm.getT(mY); + hasLocation = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mFlags); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } + } - // Get the grid location - esm.getSubNameIs("INTV"); - esm.getSubHeaderIs(8); - esm.getT(mX); - esm.getT(mY); + if (!hasLocation) + esm.fail("Missing INTV subrecord"); - esm.getHNT(mFlags, "DATA"); + mContext = esm.getContext(); - // Store the file position - mContext = esm.getContext(); + // Skip the land data here. Load it when the cell is loaded. + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'V','N','M','L'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VNML; + break; + case ESM::FourCC<'V','H','G','T'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VHGT; + break; + case ESM::FourCC<'W','N','A','M'>::value: + esm.skipHSub(); + mDataTypes |= DATA_WNAM; + break; + case ESM::FourCC<'V','C','L','R'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VCLR; + break; + case ESM::FourCC<'V','T','E','X'>::value: + esm.skipHSub(); + mDataTypes |= DATA_VTEX; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } - // Skip these here. Load the actual data when the cell is loaded. - if (esm.isNextSub("VNML")) - { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VNML; - } - if (esm.isNextSub("VHGT")) - { - esm.skipHSubSize(4232); - mDataTypes |= DATA_VHGT; - } - if (esm.isNextSub("WNAM")) - { - esm.skipHSubSize(81); - mDataTypes |= DATA_WNAM; - } - if (esm.isNextSub("VCLR")) - { - esm.skipHSubSize(12675); - mDataTypes |= DATA_VCLR; - } - if (esm.isNextSub("VTEX")) - { - esm.skipHSubSize(512); - mDataTypes |= DATA_VTEX; + mDataLoaded = 0; + mLandData = NULL; } - mDataLoaded = 0; - mLandData = NULL; -} + void Land::save(ESMWriter &esm, bool isDeleted) const + { + esm.startSubRecord("INTV"); + esm.writeT(mX); + esm.writeT(mY); + esm.endRecord("INTV"); -void Land::save(ESMWriter &esm) const -{ - esm.startSubRecord("INTV"); - esm.writeT(mX); - esm.writeT(mY); - esm.endRecord("INTV"); + esm.writeHNT("DATA", mFlags); - esm.writeHNT("DATA", mFlags); -} + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } -void Land::loadData(int flags) const -{ - // Try to load only available data - flags = flags & mDataTypes; - // Return if all required data is loaded - if ((mDataLoaded & flags) == flags) { - return; - } - // Create storage if nothing is loaded - if (mLandData == NULL) { - mLandData = new LandData; - mLandData->mDataTypes = mDataTypes; + if (mLandData) + { + mLandData->save(esm); + } } - mEsm->restoreContext(mContext); - if (mEsm->isNextSub("VNML")) { - condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); - } + void Land::loadData(int flags) const + { + // Try to load only available data + flags = flags & mDataTypes; + // Return if all required data is loaded + if ((mDataLoaded & flags) == flags) { + return; + } + // Create storage if nothing is loaded + if (mLandData == NULL) { + mLandData = new LandData; + mLandData->mDataTypes = mDataTypes; + } + mEsm->restoreContext(mContext); - if (mEsm->isNextSub("VHGT")) { - static VHGT vhgt; - if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { - float rowOffset = vhgt.mHeightOffset; - for (int y = 0; y < LAND_SIZE; y++) { - rowOffset += vhgt.mHeightData[y * LAND_SIZE]; + if (mEsm->isNextSub("VNML")) { + condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + } - mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + if (mEsm->isNextSub("VHGT")) { + static VHGT vhgt; + if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { + float rowOffset = vhgt.mHeightOffset; + for (int y = 0; y < LAND_SIZE; y++) { + rowOffset += vhgt.mHeightData[y * LAND_SIZE]; - float colOffset = rowOffset; - for (int x = 1; x < LAND_SIZE; x++) { - colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; - mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + mLandData->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE; + + float colOffset = rowOffset; + for (int x = 1; x < LAND_SIZE; x++) { + colOffset += vhgt.mHeightData[y * LAND_SIZE + x]; + mLandData->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE; + } } + mLandData->mUnk1 = vhgt.mUnk1; + mLandData->mUnk2 = vhgt.mUnk2; } - mLandData->mUnk1 = vhgt.mUnk1; - mLandData->mUnk2 = vhgt.mUnk2; } - } - if (mEsm->isNextSub("WNAM")) { - condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); - } - if (mEsm->isNextSub("VCLR")) - condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); - if (mEsm->isNextSub("VTEX")) { - static uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { - LandData::transposeTextureData(vtex, mLandData->mTextures); + if (mEsm->isNextSub("WNAM")) { + condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); + } + if (mEsm->isNextSub("VCLR")) + condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); + if (mEsm->isNextSub("VTEX")) { + static uint16_t vtex[LAND_NUM_TEXTURES]; + if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { + LandData::transposeTextureData(vtex, mLandData->mTextures); + } } } -} -void Land::unloadData() -{ - if (mDataLoaded) + void Land::unloadData() { - delete mLandData; - mLandData = NULL; - mDataLoaded = 0; + if (mDataLoaded) + { + delete mLandData; + mLandData = NULL; + mDataLoaded = 0; + } } -} -bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const -{ - if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { - mEsm->getHExact(ptr, size); - mDataLoaded |= dataFlag; - return true; + bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const + { + if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) { + mEsm->getHExact(ptr, size); + mDataLoaded |= dataFlag; + return true; + } + mEsm->skipHSubSize(size); + return false; } - mEsm->skipHSubSize(size); - return false; -} -bool Land::isDataLoaded(int flags) const -{ - return (mDataLoaded & flags) == (flags & mDataTypes); -} + bool Land::isDataLoaded(int flags) const + { + return (mDataLoaded & flags) == (flags & mDataTypes); + } Land::Land (const Land& land) : mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin), diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 8ec4f74ea..65ac88cda 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -106,8 +106,8 @@ struct Land static void transposeTextureData(const uint16_t *in, uint16_t *out); }; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank() {} diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index ca5c5d74d..6245ec856 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -6,42 +6,84 @@ namespace ESM { - - void LevelledListBase::load(ESMReader &esm) + void LevelledListBase::load(ESMReader &esm, bool &isDeleted) { - esm.getHNT(mFlags, "DATA"); - esm.getHNT(mChanceNone, "NNAM"); + isDeleted = false; - if (esm.isNextSub("INDX")) - { - int len; - esm.getHT(len); - mList.resize(len); - } - else + bool hasName = false; + bool hasList = false; + while (esm.hasMoreSubs()) { - // Original engine ignores rest of the record, even if there are items following - mList.clear(); - esm.skipRecord(); - return; - } + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mFlags); + break; + case ESM::FourCC<'N','N','A','M'>::value: + esm.getHT(mChanceNone); + break; + case ESM::FourCC<'I','N','D','X'>::value: + { + int length = 0; + esm.getHT(length); + mList.resize(length); - // If this levelled list was already loaded by a previous content file, - // we overwrite the list. Merging lists should probably be left to external tools, - // with the limited amount of information there is in the records, all merging methods - // will be flawed in some way. For a proper fix the ESM format would have to be changed - // to actually track list changes instead of including the whole list for every file - // that does something with that list. + // If this levelled list was already loaded by a previous content file, + // we overwrite the list. Merging lists should probably be left to external tools, + // with the limited amount of information there is in the records, all merging methods + // will be flawed in some way. For a proper fix the ESM format would have to be changed + // to actually track list changes instead of including the whole list for every file + // that does something with that list. + for (size_t i = 0; i < mList.size(); i++) + { + LevelItem &li = mList[i]; + li.mId = esm.getHNString(mRecName); + esm.getHNT(li.mLevel, "INTV"); + } - for (size_t i = 0; i < mList.size(); i++) - { - LevelItem &li = mList[i]; - li.mId = esm.getHNString(mRecName); - esm.getHNT(li.mLevel, "INTV"); + hasList = true; + break; + } + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + { + if (!hasList) + { + // Original engine ignores rest of the record, even if there are items following + mList.clear(); + esm.skipRecord(); + } + else + { + esm.fail("Unknown subrecord"); + } + break; + } + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void LevelledListBase::save(ESMWriter &esm) const + + void LevelledListBase::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); esm.writeHNT("INDX", mList.size()); diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index dc6fcda5e..ed4131c16 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -36,8 +36,8 @@ struct LevelledListBase std::vector mList; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadligh.cpp b/components/esm/loadligh.cpp index 26d70d964..a0fedc3ad 100644 --- a/components/esm/loadligh.cpp +++ b/components/esm/loadligh.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Light::sRecordId = REC_LIGH; - void Light::load(ESMReader &esm) + void Light::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -36,15 +42,31 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing LHDT subrecord"); } - void Light::save(ESMWriter &esm) const + void Light::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNOCString("ITEX", mIcon); diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index ed8c36665..8509c64b6 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -47,8 +47,8 @@ struct Light std::string mSound, mScript, mModel, mIcon, mName, mId; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadlock.cpp b/components/esm/loadlock.cpp index 2747a6f78..be93eaa0e 100644 --- a/components/esm/loadlock.cpp +++ b/components/esm/loadlock.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Lockpick::sRecordId = REC_LOCK; - void Lockpick::load(ESMReader &esm) + void Lockpick::load(ESMReader &esm, bool &isDeleted) { - bool hasData = true; + isDeleted = false; + + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,16 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing LKDT subrecord"); } - void Lockpick::save(ESMWriter &esm) const + void Lockpick::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadlock.hpp b/components/esm/loadlock.hpp index 0d678cd64..9db41af97 100644 --- a/components/esm/loadlock.hpp +++ b/components/esm/loadlock.hpp @@ -27,8 +27,8 @@ struct Lockpick Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadltex.cpp b/components/esm/loadltex.cpp index c3e2d50ff..e9cd4578d 100644 --- a/components/esm/loadltex.cpp +++ b/components/esm/loadltex.cpp @@ -8,21 +8,58 @@ namespace ESM { unsigned int LandTexture::sRecordId = REC_LTEX; -void LandTexture::load(ESMReader &esm) -{ - esm.getHNT(mIndex, "INTV"); - mTexture = esm.getHNString("DATA"); -} -void LandTexture::save(ESMWriter &esm) const -{ - esm.writeHNT("INTV", mIndex); - esm.writeHNCString("DATA", mTexture); -} + void LandTexture::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; -void LandTexture::blank() -{ - mTexture.clear(); - mIndex = -1; -} + bool hasName = false; + bool hasIndex = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'I','N','T','V'>::value: + esm.getHT(mIndex); + hasIndex = true; + break; + case ESM::FourCC<'D','A','T','A'>::value: + mTexture = esm.getHString(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasIndex) + esm.fail("Missing INTV subrecord"); + } + void LandTexture::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + esm.writeHNT("INTV", mIndex); + esm.writeHNCString("DATA", mTexture); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + } + void LandTexture::blank() + { + mTexture.clear(); + mIndex = -1; + } } diff --git a/components/esm/loadltex.hpp b/components/esm/loadltex.hpp index 50a788105..2cb5abf0c 100644 --- a/components/esm/loadltex.hpp +++ b/components/esm/loadltex.hpp @@ -34,11 +34,11 @@ struct LandTexture std::string mId, mTexture; int mIndex; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; + void blank(); ///< Set record to default state (does not touch the ID). - - void load(ESMReader &esm); - void save(ESMWriter &esm) const; }; } #endif diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 6f859ab3c..eef58aa2f 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -189,8 +189,10 @@ namespace ESM { unsigned int MagicEffect::sRecordId = REC_MGEF; -void MagicEffect::load(ESMReader &esm) +void MagicEffect::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) + esm.getHNT(mIndex, "INDX"); mId = indexToId (mIndex); @@ -209,8 +211,7 @@ void MagicEffect::load(ESMReader &esm) while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); @@ -250,7 +251,7 @@ void MagicEffect::load(ESMReader &esm) } } } -void MagicEffect::save(ESMWriter &esm) const +void MagicEffect::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index eeb4268c2..a52ed797f 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -96,8 +96,8 @@ struct MagicEffect // sMagicCreature04ID/05ID. int mIndex; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; /// Set record to default state (does not touch the ID/index). void blank(); diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 81c094f2b..f03cb9a85 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Miscellaneous::sRecordId = REC_MISC; - void Miscellaneous::load(ESMReader &esm) + void Miscellaneous::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,14 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing MCDT subrecord"); } - void Miscellaneous::save(ESMWriter &esm) const + void Miscellaneous::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("MCDT", mData, 12); diff --git a/components/esm/loadmisc.hpp b/components/esm/loadmisc.hpp index 6e0b4e01b..e7a323904 100644 --- a/components/esm/loadmisc.hpp +++ b/components/esm/loadmisc.hpp @@ -32,8 +32,8 @@ struct Miscellaneous std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index 67a437176..72333add3 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -8,24 +8,30 @@ namespace ESM { unsigned int NPC::sRecordId = REC_NPC_; - void NPC::load(ESMReader &esm) + void NPC::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mPersistent = (esm.getRecordFlags() & 0x0400) != 0; mSpells.mList.clear(); mInventory.mList.clear(); mTransport.mList.clear(); mAiPackage.mList.clear(); + mHasAI = false; + bool hasName = false; bool hasNpdt = false; bool hasFlags = false; - mHasAI = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -92,17 +98,33 @@ namespace ESM case AI_CNDT: mAiPackage.add(esm); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasNpdt) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasNpdt && !isDeleted) esm.fail("Missing NPDT subrecord"); - if (!hasFlags) + if (!hasFlags && !isDeleted) esm.fail("Missing FLAG subrecord"); } - void NPC::save(ESMWriter &esm) const + void NPC::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNCString("RNAM", mRace); diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 9bda9560e..5b89f4055 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -130,8 +130,8 @@ struct NPC // body parts std::string mHair, mHead; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; bool isMale() const; diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index fc0974c9d..95e6a906f 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -32,98 +32,130 @@ namespace ESM { } -void Pathgrid::load(ESMReader &esm) -{ - esm.getHNT(mData, "DATA", 12); - mCell = esm.getHNString("NAME"); + void Pathgrid::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; - mPoints.clear(); - mEdges.clear(); + mPoints.clear(); + mEdges.clear(); - // keep track of total connections so we can reserve edge vector size - int edgeCount = 0; + // keep track of total connections so we can reserve edge vector size + int edgeCount = 0; - if (esm.isNextSub("PGRP")) - { - esm.getSubHeader(); - int size = esm.getSubSize(); - // Check that the sizes match up. Size = 16 * s2 (path points) - if (size != static_cast (sizeof(Point) * mData.mS2)) - esm.fail("Path point subrecord size mismatch"); - else + bool hasData = false; + while (esm.hasMoreSubs()) { - int pointCount = mData.mS2; - mPoints.reserve(pointCount); - for (int i = 0; i < pointCount; ++i) + esm.getSubName(); + switch (esm.retSubName().val) { - Point p; - esm.getExact(&p, sizeof(Point)); - mPoints.push_back(p); - edgeCount += p.mConnectionNum; + case ESM::FourCC<'N','A','M','E'>::value: + mCell = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'P','G','R','P'>::value: + { + esm.getSubHeader(); + int size = esm.getSubSize(); + // Check that the sizes match up. Size = 16 * s2 (path points) + if (size != static_cast (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 rawConnections; + rawConnections.reserve(rawConnNum); + for (int i = 0; i < rawConnNum; ++i) + { + int currentValue; + esm.getT(currentValue); + rawConnections.push_back(currentValue); + } + + std::vector::const_iterator rawIt = rawConnections.begin(); + int pointIndex = 0; + mEdges.reserve(edgeCount); + for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) + { + unsigned char connectionNum = (*it).mConnectionNum; + for (int i = 0; i < connectionNum; ++i) { + Edge edge; + edge.mV0 = pointIndex; + edge.mV1 = *rawIt; + ++rawIt; + mEdges.push_back(edge); + } + } + } + break; + } + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } + + if (!hasData) + esm.fail("Missing DATA subrecord"); } - if (esm.isNextSub("PGRC")) + void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { - esm.getSubHeader(); - int size = esm.getSubSize(); - if (size % sizeof(int) != 0) - esm.fail("PGRC size not a multiple of 4"); - else - { - int rawConnNum = size / sizeof(int); - std::vector rawConnections; - rawConnections.reserve(rawConnNum); - for (int i = 0; i < rawConnNum; ++i) - { - int currentValue; - esm.getT(currentValue); - rawConnections.push_back(currentValue); - } + esm.writeHNCString("NAME", mCell); + esm.writeHNT("DATA", mData, 12); - std::vector::const_iterator rawIt = rawConnections.begin(); - int pointIndex = 0; - mEdges.reserve(edgeCount); - for(PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it, ++pointIndex) - { - unsigned char connectionNum = (*it).mConnectionNum; - for (int i = 0; i < connectionNum; ++i) { - Edge edge; - edge.mV0 = pointIndex; - edge.mV1 = *rawIt; - ++rawIt; - mEdges.push_back(edge); - } - } + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; } - } -} -void Pathgrid::save(ESMWriter &esm) const -{ - esm.writeHNT("DATA", mData, 12); - esm.writeHNCString("NAME", mCell); - if (!mPoints.empty()) - { - esm.startSubRecord("PGRP"); - for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + if (!mPoints.empty()) { - esm.writeT(*it); + esm.startSubRecord("PGRP"); + for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + { + esm.writeT(*it); + } + esm.endRecord("PGRP"); } - esm.endRecord("PGRP"); - } - if (!mEdges.empty()) - { - esm.startSubRecord("PGRC"); - for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + if (!mEdges.empty()) { - esm.writeT(it->mV1); + esm.startSubRecord("PGRC"); + for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + { + esm.writeT(it->mV1); + } + esm.endRecord("PGRC"); } - esm.endRecord("PGRC"); } -} void Pathgrid::blank() { diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index f33ccbedf..d1003eb86 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -53,8 +53,8 @@ struct Pathgrid typedef std::vector EdgeList; EdgeList mEdges; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadprob.cpp b/components/esm/loadprob.cpp index c5f80c584..31caeff41 100644 --- a/components/esm/loadprob.cpp +++ b/components/esm/loadprob.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Probe::sRecordId = REC_PROB; - void Probe::load(ESMReader &esm) + void Probe::load(ESMReader &esm, bool &isDeleted) { - bool hasData = true; + isDeleted = false; + + bool hasName = false; + bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -33,16 +39,32 @@ namespace ESM case ESM::FourCC<'I','T','E','X'>::value: mIcon = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing PBDT subrecord"); } - void Probe::save(ESMWriter &esm) const + void Probe::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); diff --git a/components/esm/loadprob.hpp b/components/esm/loadprob.hpp index c737757aa..da203b456 100644 --- a/components/esm/loadprob.hpp +++ b/components/esm/loadprob.hpp @@ -27,8 +27,8 @@ struct Probe Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index e88454d4c..d5172a133 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -18,44 +18,65 @@ namespace ESM return static_cast(male ? mMale : mFemale); } -void Race::load(ESMReader &esm) -{ - mPowers.mList.clear(); + void Race::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; + + mPowers.mList.clear(); + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','A','D','T'>::value: + esm.getHT(mData, 140); + hasData = true; + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + } + } - bool hasData = false; - while (esm.hasMoreSubs()) + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing RADT subrecord"); + } + void Race::save(ESMWriter &esm, bool isDeleted) const { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + esm.writeHNCString("NAME", mId); + + if (isDeleted) { - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','A','D','T'>::value: - esm.getHT(mData, 140); - hasData = true; - break; - case ESM::FourCC<'D','E','S','C'>::value: - mDescription = esm.getHString(); - break; - case ESM::FourCC<'N','P','C','S'>::value: - mPowers.add(esm); - break; - default: - esm.fail("Unknown subrecord"); + esm.writeHNCString("DELE", ""); + return; } + + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("RADT", mData, 140); + mPowers.save(esm); + esm.writeHNOString("DESC", mDescription); } - if (!hasData) - esm.fail("Missing RADT subrecord"); -} -void Race::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RADT", mData, 140); - mPowers.save(esm); - esm.writeHNOString("DESC", mDescription); -} void Race::blank() { diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index 553d2e68b..bf0573075 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -68,8 +68,8 @@ struct Race std::string mId, mName, mDescription; SpellList mPowers; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 1b08b7217..9f3089763 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -8,62 +8,102 @@ namespace ESM { unsigned int Region::sRecordId = REC_REGN; -void Region::load(ESMReader &esm) -{ - mName = esm.getHNOString("FNAM"); - - esm.getSubNameIs("WEAT"); - esm.getSubHeader(); - if (esm.getVer() == VER_12) + void Region::load(ESMReader &esm, bool &isDeleted) { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData) - 2); - } - else if (esm.getVer() == VER_13) - { - // May include the additional two bytes (but not necessarily) - if (esm.getSubSize() == sizeof(mData)) - esm.getExact(&mData, sizeof(mData)); - else + isDeleted = false; + + bool hasName = false; + while (esm.hasMoreSubs()) { - mData.mA = 0; - mData.mB = 0; - esm.getExact(&mData, sizeof(mData)-2); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'W','E','A','T'>::value: + { + esm.getSubHeader(); + if (esm.getVer() == VER_12) + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData) - 2); + } + else if (esm.getVer() == VER_13) + { + // May include the additional two bytes (but not necessarily) + if (esm.getSubSize() == sizeof(mData)) + { + esm.getExact(&mData, sizeof(mData)); + } + else + { + mData.mA = 0; + mData.mB = 0; + esm.getExact(&mData, sizeof(mData)-2); + } + } + else + { + esm.fail("Don't know what to do in this version"); + } + break; + } + case ESM::FourCC<'B','N','A','M'>::value: + mSleepList = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + esm.getHT(mMapColor); + break; + case ESM::FourCC<'S','N','A','M'>::value: + SoundRef sr; + esm.getHT(sr, 33); + mSoundList.push_back(sr); + 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"); } - else - esm.fail("Don't know what to do in this version"); - mSleepList = esm.getHNOString("BNAM"); + void Region::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); - esm.getHNT(mMapColor, "CNAM"); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } - mSoundList.clear(); - while (esm.hasMoreSubs()) - { - SoundRef sr; - esm.getHNT(sr, "SNAM", 33); - mSoundList.push_back(sr); - } -} -void Region::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("FNAM", mName); - if (esm.getVersion() == VER_12) - esm.writeHNT("WEAT", mData, sizeof(mData) - 2); - else - esm.writeHNT("WEAT", mData); + if (esm.getVersion() == VER_12) + esm.writeHNT("WEAT", mData, sizeof(mData) - 2); + else + esm.writeHNT("WEAT", mData); - esm.writeHNOCString("BNAM", mSleepList); + esm.writeHNOCString("BNAM", mSleepList); - esm.writeHNT("CNAM", mMapColor); - for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) - { - esm.writeHNT("SNAM", *it); + esm.writeHNT("CNAM", mMapColor); + for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) + { + esm.writeHNT("SNAM", *it); + } } -} void Region::blank() { diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 1e241fffb..3d914bd17 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -51,8 +51,8 @@ struct Region std::vector mSoundList; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadrepa.cpp b/components/esm/loadrepa.cpp index f90f9e39d..00d2ebf08 100644 --- a/components/esm/loadrepa.cpp +++ b/components/esm/loadrepa.cpp @@ -8,48 +8,70 @@ namespace ESM { unsigned int Repair::sRecordId = REC_REPA; -void Repair::load(ESMReader &esm) -{ - bool hasData = true; - while (esm.hasMoreSubs()) + void Repair::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) { - case ESM::FourCC<'M','O','D','L'>::value: - mModel = esm.getHString(); - break; - case ESM::FourCC<'F','N','A','M'>::value: - mName = esm.getHString(); - break; - case ESM::FourCC<'R','I','D','T'>::value: - esm.getHT(mData, 16); - hasData = true; - break; - case ESM::FourCC<'S','C','R','I'>::value: - mScript = esm.getHString(); - break; - case ESM::FourCC<'I','T','E','X'>::value: - mIcon = esm.getHString(); - break; - default: - esm.fail("Unknown subrecord"); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','I','D','T'>::value: + esm.getHT(mData, 16); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing RIDT subrecord"); } - if (!hasData) - esm.fail("Missing RIDT subrecord"); -} -void Repair::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); + void Repair::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); - esm.writeHNT("RIDT", mData, 16); - esm.writeHNOString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); -} + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + + esm.writeHNT("RIDT", mData, 16); + esm.writeHNOString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + } void Repair::blank() { diff --git a/components/esm/loadrepa.hpp b/components/esm/loadrepa.hpp index e765bc93a..2537c53cb 100644 --- a/components/esm/loadrepa.hpp +++ b/components/esm/loadrepa.hpp @@ -27,8 +27,8 @@ struct Repair Data mData; std::string mId, mName, mModel, mIcon, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index fd807ddd3..2a0542138 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -8,7 +8,6 @@ namespace ESM { - unsigned int Script::sRecordId = REC_SCPT; void Script::loadSCVR(ESMReader &esm) @@ -58,21 +57,25 @@ namespace ESM } } - void Script::load(ESMReader &esm) + void Script::load(ESMReader &esm, bool &isDeleted) { - SCHD data; - esm.getHNT(data, "SCHD", 52); - mData = data.mData; - mId = data.mName.toString(); + isDeleted = false; mVarNames.clear(); + bool hasHeader = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'S','C','H','D'>::value: + SCHD data; + esm.getHT(data, 52); + mData = data.mData; + mId = data.mName.toString(); + hasHeader = true; + break; case ESM::FourCC<'S','C','V','R'>::value: // list of local variables loadSCVR(esm); @@ -85,13 +88,21 @@ namespace ESM case ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } + + if (!hasHeader) + esm.fail("Missing SCHD subrecord"); } - void Script::save(ESMWriter &esm) const + void Script::save(ESMWriter &esm, bool isDeleted) const { std::string varNameString; if (!mVarNames.empty()) @@ -106,6 +117,12 @@ namespace ESM esm.writeHNT("SCHD", data, 52); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + if (!mVarNames.empty()) { esm.startSubRecord("SCVR"); diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 56390f384..b8a06406d 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -50,8 +50,8 @@ public: /// Script source code std::string mScriptText; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 7883b8a1a..c52089791 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -129,15 +129,16 @@ namespace ESM unsigned int Skill::sRecordId = REC_SKIL; - void Skill::load(ESMReader &esm) + void Skill::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; // Skill record can't be deleted now (may be changed in the future) + bool hasIndex = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { case ESM::FourCC<'I','N','D','X'>::value: esm.getHT(mIndex); @@ -164,7 +165,7 @@ namespace ESM mId = indexToId (mIndex); } - void Skill::save(ESMWriter &esm) const + void Skill::save(ESMWriter &esm, bool /*isDeleted*/) const { esm.writeHNT("INDX", mIndex); esm.writeHNT("SKDT", mData, 24); diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index e00184297..5430b422d 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -78,8 +78,8 @@ struct Skill static const std::string sIconNames[Length]; static const boost::array sSkillIds; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index 5ee6f5245..189cc97df 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int SoundGenerator::sRecordId = REC_SNDG; - void SoundGenerator::load(ESMReader &esm) + void SoundGenerator::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'D','A','T','A'>::value: esm.getHT(mType, 4); hasData = true; @@ -27,18 +33,35 @@ namespace ESM case ESM::FourCC<'S','N','A','M'>::value: mSound = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); + break; } } - if (!hasData) - esm.fail("Missing DATA"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA subrecord"); } - void SoundGenerator::save(ESMWriter &esm) const + void SoundGenerator::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNT("DATA", mType, 4); esm.writeHNOCString("CNAM", mCreature); esm.writeHNOCString("SNAM", mSound); + } void SoundGenerator::blank() diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 056958f0a..70b221e98 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -36,8 +36,8 @@ struct SoundGenerator std::string mId, mCreature, mSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index 690c1b448..d3a82e198 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Sound::sRecordId = REC_SOUN; - void Sound::load(ESMReader &esm) + void Sound::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mSound = esm.getHString(); break; @@ -24,16 +30,32 @@ namespace ESM esm.getHT(mData, 3); hasData = true; 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"); + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA subrecord"); } - void Sound::save(ESMWriter &esm) const + void Sound::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mSound); esm.writeHNT("DATA", mData, 3); } diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index ff2202ca7..937e22be8 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -23,8 +23,8 @@ struct Sound SOUNstruct mData; std::string mId, mSound; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 96c048e0a..16ffb63ff 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -8,17 +8,23 @@ namespace ESM { unsigned int Spell::sRecordId = REC_SPEL; - void Spell::load(ESMReader &esm) + void Spell::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + mEffects.mList.clear(); + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t val = esm.retSubName().val; - - switch (val) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'F','N','A','M'>::value: mName = esm.getHString(); break; @@ -31,14 +37,32 @@ namespace ESM esm.getHT(s, 24); mEffects.mList.push_back(s); 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 SPDT subrecord"); } - void Spell::save(ESMWriter &esm) const + void Spell::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNOCString("FNAM", mName); esm.writeHNT("SPDT", mData, 12); mEffects.save(esm); @@ -51,7 +75,6 @@ namespace ESM mData.mFlags = 0; mName.clear(); - mEffects.mList.clear(); } } diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 491da1d17..1763d0991 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -45,8 +45,8 @@ struct Spell std::string mId, mName; EffectList mEffects; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID/index). diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index 7380dd0a7..a211a99bf 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -8,37 +8,51 @@ namespace ESM { unsigned int StartScript::sRecordId = REC_SSCR; - void StartScript::load(ESMReader &esm) + void StartScript::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + bool hasData = false; bool hasName = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'D','A','T','A'>::value: mData = esm.getHString(); hasData = true; break; - case ESM::FourCC<'N','A','M','E'>::value: - mId = esm.getHString(); - hasName = true; + 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"); + if (!hasName) esm.fail("Missing NAME"); + if (!hasData && !isDeleted) + esm.fail("Missing DATA"); } - void StartScript::save(ESMWriter &esm) const + void StartScript::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNString("DATA", mData); - esm.writeHNString("NAME", mId); + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNString("DATA", mData); + } } void StartScript::blank() diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index dc7ad6a42..ce2ff49e7 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -27,8 +27,8 @@ struct StartScript std::string mId; // Load a record and add it to the list - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); }; diff --git a/components/esm/loadstat.cpp b/components/esm/loadstat.cpp index b0ab89bed..eee7a50f5 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -8,13 +8,47 @@ namespace ESM { unsigned int Static::sRecordId = REC_STAT; - void Static::load(ESMReader &esm) + void Static::load(ESMReader &esm, bool &isDeleted) { - mModel = esm.getHNString("MODL"); + isDeleted = false; + + bool hasName = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); } - void Static::save(ESMWriter &esm) const + void Static::save(ESMWriter &esm, bool isDeleted) const { - esm.writeHNCString("MODL", mModel); + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNCString("MODL", mModel); + } } void Static::blank() diff --git a/components/esm/loadstat.hpp b/components/esm/loadstat.hpp index aa4fe67b8..930cdb849 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,8 +28,8 @@ struct Static std::string mId, mModel; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 981a5815a..b0bc1dad6 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -8,15 +8,21 @@ namespace ESM { unsigned int Weapon::sRecordId = REC_WEAP; - void Weapon::load(ESMReader &esm) + void Weapon::load(ESMReader &esm, bool &isDeleted) { + isDeleted = false; + + bool hasName = false; bool hasData = false; while (esm.hasMoreSubs()) { esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + switch (esm.retSubName().val) { + case ESM::FourCC<'N','A','M','E'>::value: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -36,15 +42,30 @@ namespace ESM case ESM::FourCC<'E','N','A','M'>::value: mEnchant = esm.getHString(); break; + case ESM::FourCC<'D','E','L','E'>::value: + esm.skipHSub(); + isDeleted = true; + break; default: esm.fail("Unknown subrecord"); } } - if (!hasData) + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) esm.fail("Missing WPDT subrecord"); } - void Weapon::save(ESMWriter &esm) const + void Weapon::save(ESMWriter &esm, bool isDeleted) const { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); esm.writeHNT("WPDT", mData, 32); diff --git a/components/esm/loadweap.hpp b/components/esm/loadweap.hpp index f66e9f3a6..eddcaee4f 100644 --- a/components/esm/loadweap.hpp +++ b/components/esm/loadweap.hpp @@ -69,8 +69,8 @@ struct Weapon std::string mId, mName, mModel, mIcon, mEnchant, mScript; - void load(ESMReader &esm); - void save(ESMWriter &esm) const; + void load(ESMReader &esm, bool &isDeleted); + void save(ESMWriter &esm, bool isDeleted = false) const; void blank(); ///< Set record to default state (does not touch the ID). diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index 4fd4245c7..d736bab66 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -7,7 +7,8 @@ void ESM::ObjectState::load (ESMReader &esm) { mVersion = esm.getFormat(); - mRef.loadData(esm); + bool isDeleted; + mRef.loadData(esm, isDeleted); mHasLocals = 0; esm.getHNOT (mHasLocals, "HLOC");