diff --git a/CMakeLists.txt b/CMakeLists.txt index fb1ac83cc7..bd01db606e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -119,8 +119,6 @@ else() endif() endif() -set(SOUND_INPUT_LIBRARY ${FFMPEG_LIBRARIES}) - # TinyXML option(USE_SYSTEM_TINYXML "Use system TinyXML library instead of internal." OFF) if(USE_SYSTEM_TINYXML) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index eef970d377..512a348ae4 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -26,7 +26,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; @@ -254,7 +255,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; @@ -351,30 +352,9 @@ int load(Arguments& info) uint32_t flags; esm.getRecHeader(flags); - // 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()); - 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)) - interested = false; - - if(!quiet && interested) - std::cout << "\nRecord: " << n.toString() - << " '" << id << "'\n"; - EsmTool::RecordBase *record = EsmTool::RecordBase::create(n); - - if (record == 0) { + if (record == 0) + { if (std::find(skipped.begin(), skipped.end(), n.val) == skipped.end()) { std::cout << "Skipping " << n.toString() << " records." << std::endl; @@ -384,28 +364,46 @@ int load(Arguments& info) 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 (save) { - info.data.mRecords.push_back(record); - } else { - delete record; - } - ++info.data.mRecordStats[n.val]; + 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()); + if (match == info.types.end()) interested = false; + } + + if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, record->getId())) + interested = false; + + if(!quiet && interested) + { + std::cout << "\nRecord: " << n.toString() << " '" << record->getId() << "'\n"; + record->print(); + } + + 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]; } } catch(std::exception &e) { @@ -492,28 +490,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 5fe6471b0f..d7cbea73b0 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; } + 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 5e03c64dba..dca38409fe 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 91d290f331..b372012f94 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -134,9 +134,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) @@ -145,7 +145,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(); } @@ -253,7 +253,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 5711e6754b..61155c1eab 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -51,6 +51,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) { } @@ -75,10 +77,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) @@ -86,7 +89,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); } @@ -102,14 +104,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 { @@ -139,9 +142,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; } }; @@ -154,18 +158,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; } }; @@ -174,14 +179,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_; } }; @@ -190,13 +195,14 @@ class ConvertBook : public DefaultConverter public: virtual void read(ESM::ESMReader &esm) { - std::string id = esm.getHNString("NAME"); ESM::Book book; - book.load(esm); - if (book.mData.mSkillID == -1) - mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id)); + bool isDeleted = false; - mRecords[id] = book; + book.load(esm, isDeleted); + if (book.mData.mSkillID == -1) + mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(book.mId)); + + mRecords[book.mId] = book; } }; @@ -368,11 +374,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 9d881515dd..1602aa784b 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -18,7 +18,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 b9468ed870..57c4ba4c36 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -348,7 +348,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 d27cd5c8ca..3ec640d3d5 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 3fba2cd85c..db38c4779b 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -99,95 +99,77 @@ 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 +217,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 +259,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 +297,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 +323,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 +349,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 +366,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 +385,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 +415,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 188f22f961..64afd0dd8e 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/settings/usersettings.cpp b/apps/opencs/model/settings/usersettings.cpp index 5cf3bd7a74..87992d0dc4 100644 --- a/apps/opencs/model/settings/usersettings.cpp +++ b/apps/opencs/model/settings/usersettings.cpp @@ -366,6 +366,11 @@ void CSMSettings::UserSettings::buildSettingModelDefaults() delay->setRange (0, 10000); delay->setToolTip ("Delay in milliseconds"); + Setting *errorHeight = createSetting (Type_SpinBox, "error-height", + "Initial height of the error panel"); + errorHeight->setDefaultValue (100); + errorHeight->setRange (100, 10000); + Setting *formatInt = createSetting (Type_LineEdit, "colour-int", "Highlight Colour: Int"); formatInt->setDefaultValues (QStringList() << "Dark magenta"); formatInt->setToolTip ("(Default: Green) Use one of the following formats:" + tooltip); diff --git a/apps/opencs/model/world/cell.cpp b/apps/opencs/model/world/cell.cpp index 91becdb74e..93f3c500d7 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 f393e2cf97..160610874c 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 b0571bbed1..16f5ce51f4 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 dfb2d99d32..22ad0048bf 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -1108,41 +1108,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) - { - mDialogue = 0; // record vector can be shuffled around which would make pointer - // to record invalid + record.load (*mReader, isDeleted); - if (mJournals.tryDelete (id)) + if (isDeleted) + { + // record vector can be shuffled around which would make pointer to record invalid + mDialogue = 0; + + 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 4eafc59bd5..ea6eefb882 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")) + loadRecord (record, reader, isDeleted); + + std::string id = IdAccessorT().getId (record); + int index = this->searchId (id); + + if (isDeleted) { - int index = Collection::searchId (id); - - reader.skipRecord(); - if (index==-1) { // deleting a record that does not exist - // ignore it for now - /// \todo report the problem to the user - } - else if (base) - { - Collection::removeRows (index, 1); - } - else - { - Record record = Collection::getRecord (index); - record.mState = RecordBase::State_Deleted; - this->setRecord (index, record); + return -1; } - return -1; + if (base) + { + this->removeRows (index, 1); + return -1; + } + + Record baseRecord = this->getRecord (index); + baseRecord.mState = RecordBase::State_Deleted; + this->setRecord (index, baseRecord); + return index; } - else - { - ESXRecordT record; - // Sometimes id (i.e. NAME of the cell) may be different to the id we stored - // earlier. e.g. NAME == "Vivec, Arena" but id == "#-4 11". Sometime NAME is - // missing altogether for scripts or cells. - // - // In such cases the returned index will be -1. We then try updating the - // IdAccessor's id manually (e.g. set mId of the record to "Vivec, Arena") - // and try getting the index once more after loading the record. The mId of the - // record would have changed to "#-4 11" after the load, and searchId() should find - // it (if this is a modify) - int index = this->searchId (id); - - if (index==-1) - IdAccessorT().getId (record) = id; - else - { - record = this->getRecord (index).get(); - } - - loadRecord (record, reader); - - if (index==-1) - { - std::string newId = IdAccessorT().getId(record); - int newIndex = this->searchId(newId); - if (newIndex != -1 && id != newId) - index = newIndex; - } - - return load (record, base, index); - } + return load (record, base, index); } template diff --git a/apps/opencs/model/world/infocollection.cpp b/apps/opencs/model/world/infocollection.cpp index 60c6130416..f5ec4d4587 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 6db47373d0..e5a5575c78 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 222f9bc023..80f86c7467 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 22cedb56db..e5f25c1d3d 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 e7772129cd..266377d0f6 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 c0b6eeba9c..91459eee28 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 5c66e7d8ea..c995bd8f09 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 7e7b7c3bb6..22d01b0710 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 f8818807bc..65251a81db 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 a5e8133386..9560539b20 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -848,61 +848,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 7696c3763b..2d8c9ac108 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 8909ae4fb0..59cad6a661 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 df1f8a12ea..496cb06430 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/opencs/view/doc/filewidget.cpp b/apps/opencs/view/doc/filewidget.cpp index 110d561c17..9e9acdfbe6 100644 --- a/apps/opencs/view/doc/filewidget.cpp +++ b/apps/opencs/view/doc/filewidget.cpp @@ -16,7 +16,6 @@ CSVDoc::FileWidget::FileWidget (QWidget *parent) : QWidget (parent), mAddon (fal QHBoxLayout *layout = new QHBoxLayout (this); mInput = new QLineEdit (this); - mInput->setValidator (new QRegExpValidator(QRegExp("^[a-zA-Z0-9_-\\s]*$"))); layout->addWidget (mInput, 1); diff --git a/apps/opencs/view/doc/startup.cpp b/apps/opencs/view/doc/startup.cpp index a9d697f1c2..67ff50dab9 100644 --- a/apps/opencs/view/doc/startup.cpp +++ b/apps/opencs/view/doc/startup.cpp @@ -104,14 +104,16 @@ CSVDoc::StartupDialogue::StartupDialogue() : mWidth (0), mColumn (2) layout->addWidget (createButtons()); layout->addWidget (createTools()); - /// \todo remove this label once loading and saving are fully implemented - QLabel *warning = new QLabel ("WARNING:

OpenCS is in alpha stage.
The code for loading and saving is incomplete.
This version of OpenCS is only a preview.
Do NOT use it for real editing!
You will lose records both on loading and on saving.

Please note:
If you lose data and come to the OpenMW forum to complain,
we will mock you.
"); + /// \todo remove this label once we are feature complete and convinced that this thing is + /// working properly. + QLabel *warning = new QLabel ("WARNING: OpenMW-CS is in alpha stage.

The editor is not feature complete and not sufficiently tested.
In theory your data should be safe. But we strongly advice to make backups regularly if you are working with live data.
"); QFont font; font.setPointSize (12); font.setBold (true); warning->setFont (font); + warning->setWordWrap (true); layout->addWidget (warning, 1); diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 41fe70c011..11a08faa6d 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -36,9 +36,7 @@ namespace CSVRender return ltex; } - std::stringstream error; - error << "Can't find LandTexture " << index << " from plugin " << plugin; - throw std::runtime_error(error.str()); + return NULL; } void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) diff --git a/apps/opencs/view/world/scriptsubview.cpp b/apps/opencs/view/world/scriptsubview.cpp index eb0c706564..bd66f2bf6b 100644 --- a/apps/opencs/view/world/scriptsubview.cpp +++ b/apps/opencs/view/world/scriptsubview.cpp @@ -54,6 +54,7 @@ void CSVWorld::ScriptSubView::updateDeletedState() if (isDeleted()) { mErrors->clear(); + adjustSplitter(); mEditor->setEnabled (false); } else @@ -63,9 +64,32 @@ void CSVWorld::ScriptSubView::updateDeletedState() } } +void CSVWorld::ScriptSubView::adjustSplitter() +{ + QList sizes; + + if (mErrors->rowCount()) + { + if (mErrors->height()) + return; // keep old height if the error panel was already open + + sizes << (mMain->height()-mErrorHeight-mMain->handleWidth()) << mErrorHeight; + } + else + { + if (mErrors->height()) + mErrorHeight = mErrors->height(); + + sizes << 1 << 0; + } + + mMain->setSizes (sizes); +} + CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document) : SubView (id), mDocument (document), mColumn (-1), mBottom(0), mButtons (0), - mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())) + mCommandDispatcher (document, CSMWorld::UniversalId::getParentType (id.getType())), + mErrorHeight (CSMSettings::UserSettings::instance().setting ("script-editor/error-height").toInt()) { std::vector selection (1, id.getId()); mCommandDispatcher.setSelection (selection); @@ -81,6 +105,10 @@ CSVWorld::ScriptSubView::ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc: mErrors = new ScriptErrorTable (document, this); mMain->addWidget (mErrors); + QList sizes; + sizes << 1 << 0; + mMain->setSizes (sizes); + QWidget *widget = new QWidget (this);; widget->setLayout (&mLayout); setWidget (widget); @@ -347,4 +375,6 @@ void CSVWorld::ScriptSubView::updateRequest() QString source = mModel->data (index).toString(); mErrors->update (source.toUtf8().constData()); + + adjustSplitter(); } diff --git a/apps/opencs/view/world/scriptsubview.hpp b/apps/opencs/view/world/scriptsubview.hpp index 907dc7958b..179430ef90 100644 --- a/apps/opencs/view/world/scriptsubview.hpp +++ b/apps/opencs/view/world/scriptsubview.hpp @@ -47,6 +47,7 @@ namespace CSVWorld QSplitter *mMain; ScriptErrorTable *mErrors; QTimer *mCompileDelay; + int mErrorHeight; private: @@ -58,6 +59,8 @@ namespace CSVWorld void updateDeletedState(); + void adjustSplitter(); + public: ScriptSubView (const CSMWorld::UniversalId& id, CSMDoc::Document& document); diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index d541521006..ffa52042ca 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -108,7 +108,9 @@ endif () # Sound stuff - here so CMake doesn't stupidly recompile EVERYTHING # when we change the backend. -include_directories(${SOUND_INPUT_INCLUDES}) +include_directories( + ${FFMPEG_INCLUDE_DIRS} +) target_link_libraries(openmw ${OENGINE_LIBRARY} @@ -123,6 +125,9 @@ target_link_libraries(openmw ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_WAVE_LIBRARY} + ${OPENAL_LIBRARY} + ${FFMPEG_LIBRARIES} + ${BULLET_LIBRARIES} ${MYGUI_LIBRARIES} ${SDL2_LIBRARY} ${MYGUI_PLATFORM_LIBRARIES} diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 2808187a10..d2601361d0 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -68,7 +68,7 @@ namespace MWRender { const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); - return esmStore.get().find(index, plugin); + return esmStore.get().search(index, plugin); } } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 7da7c187d9..67de77ceb6 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -545,7 +545,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 5d9beecb65..2a302e0825 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -95,33 +95,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)); @@ -194,13 +182,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 dcd7924a22..4a406613de 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 bb4ab13d92..3468c2e719 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 cdcc00b4d3..767a33b135 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -35,7 +35,7 @@ void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) } } -void Store::load(ESM::ESMReader &esm, const std::string &id) +void 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, @@ -43,9 +43,9 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) // 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; + cell.loadName(esm); + std::string idLower = Misc::StringUtils::lowerCase(cell.mName); // 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 @@ -119,9 +119,9 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) } } -void Store::load(ESM::ESMReader &esm, const std::string &id) +void Store::load(ESM::ESMReader &esm) { - load(esm, id, esm.getIndex()); + load(esm, esm.getIndex()); } } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index ab09782b14..27771f4ea8 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -10,6 +10,7 @@ #include #include +#include #include @@ -26,15 +27,19 @@ 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 void 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 void read (ESM::ESMReader& reader) {} ///< Read into dynamic storage + + virtual std::string getLastAddedRecordId() const { return ""; } + ///< Returns the last loaded/read ID or empty string if a loaded record has no ID + virtual bool isLastAddedRecordDeleted() const { return false; } }; template @@ -126,6 +131,7 @@ namespace MWWorld } }; + T mLastAddedRecord; friend class ESMStore; @@ -208,15 +214,16 @@ namespace MWWorld return ptr; } - void load(ESM::ESMReader &esm, const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); + void load(ESM::ESMReader &esm) { + T record; + record.load(esm); + Misc::StringUtils::toLower(record.mId); - std::pair inserted = mStatic.insert(std::make_pair(idLower, T())); + std::pair inserted = mStatic.insert(std::make_pair(record.mId, record)); if (inserted.second) mShared.push_back(&inserted.first->second); - inserted.first->second.mId = idLower; - inserted.first->second.load(esm); + mLastAddedRecord = record; } void setUp() { @@ -325,58 +332,81 @@ namespace MWWorld ++iter) { writer.startRecord (T::sRecordId); - writer.writeHNString ("NAME", iter->second.mId); iter->second.save (writer); writer.endRecord (T::sRecordId); } } - void read (ESM::ESMReader& reader, const std::string& id) + void read (ESM::ESMReader& reader) { T record; - record.mId = id; record.load (reader); insert (record); + + mLastAddedRecord = record; + } + + std::string getLastAddedRecordId() const + { + return ESM::getRecordId(mLastAddedRecord); + } + + bool isLastAddedRecordDeleted() const + { + return ESM::isRecordDeleted(mLastAddedRecord); } }; template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); + inline void Store::load(ESM::ESMReader &esm) { + // The original letter case of a dialogue ID is saved, because it's printed + ESM::Dialogue dialogue; + dialogue.load(esm); - 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 + std::string idLower = Misc::StringUtils::lowerCase(dialogue.mId); + std::map::iterator found = mStatic.find(idLower); + if (found == mStatic.end()) + { + mStatic.insert(std::make_pair(idLower, dialogue)); } - - it->second.load(esm); + else + { + found->second.mIsDeleted = dialogue.mIsDeleted; + found->second.mType = dialogue.mType; + } + + mLastAddedRecord = dialogue; } template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - ESM::Script scpt; - scpt.load(esm); - Misc::StringUtils::toLower(scpt.mId); + inline void Store::load(ESM::ESMReader &esm) { + ESM::Script script; + script.load(esm); + Misc::StringUtils::toLower(script.mId); - std::pair inserted = mStatic.insert(std::make_pair(scpt.mId, scpt)); + std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); if (inserted.second) mShared.push_back(&inserted.first->second); else - inserted.first->second = scpt; + inserted.first->second = script; + + mLastAddedRecord = script; } template <> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) + inline void Store::load(ESM::ESMReader &esm) { - ESM::StartScript s; - s.load(esm); - s.mId = Misc::StringUtils::toLower(s.mId); - std::pair inserted = mStatic.insert(std::make_pair(s.mId, s)); + ESM::StartScript script; + script.load(esm); + Misc::StringUtils::toLower(script.mId); + + std::pair inserted = mStatic.insert(std::make_pair(script.mId, script)); if (inserted.second) mShared.push_back(&inserted.first->second); else - inserted.first->second = s; + inserted.first->second = script; + + mLastAddedRecord = script; } template <> @@ -385,6 +415,7 @@ namespace MWWorld // For multiple ESM/ESP files we need one list per file. typedef std::vector LandTextureList; std::vector mStatic; + ESM::LandTexture mLastLoadedTexture; public: Store() { @@ -426,10 +457,9 @@ namespace MWWorld return mStatic[plugin].size(); } - void load(ESM::ESMReader &esm, const std::string &id, size_t plugin) { + void load(ESM::ESMReader &esm, size_t plugin) { ESM::LandTexture lt; lt.load(esm); - lt.mId = id; // Make sure we have room for the structure if (plugin >= mStatic.size()) { @@ -443,7 +473,7 @@ namespace MWWorld ltexl[lt.mIndex] = lt; } - void load(ESM::ESMReader &esm, const std::string &id); + void load(ESM::ESMReader &esm); iterator begin(size_t plugin) const { assert(plugin < mStatic.size()); @@ -454,6 +484,16 @@ namespace MWWorld assert(plugin < mStatic.size()); return mStatic[plugin].end(); } + + std::string getLastAddedRecordId() const + { + return ESM::getRecordId(mLastLoadedTexture); + } + + bool isLastAddedRecordDeleted() const + { + return ESM::isRecordDeleted(mLastLoadedTexture); + } }; template <> @@ -521,7 +561,7 @@ namespace MWWorld return ptr; } - void load(ESM::ESMReader &esm, const std::string &id) { + void load(ESM::ESMReader &esm) { ESM::Land *ptr = new ESM::Land(); ptr->load(esm); @@ -688,7 +728,7 @@ namespace MWWorld // errors related to the compare operator used in std::find for ESM::MovedCellRefTracker::find. // There some nasty three-way cyclic header dependency involved, which I could only fix by moving // this method. - void load(ESM::ESMReader &esm, const std::string &id); + void load(ESM::ESMReader &esm); iterator intBegin() const { return iterator(mSharedInt.begin()); @@ -857,7 +897,7 @@ namespace MWWorld mCells = &cells; } - void load(ESM::ESMReader &esm, const std::string &id) { + void load(ESM::ESMReader &esm) { ESM::Pathgrid pathgrid; pathgrid.load(esm); diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 2ffb7ffa0e..2300f97a36 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -4,8 +4,11 @@ if (GTEST_FOUND) include_directories(${GTEST_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES - components/misc/test_*.cpp - mwdialogue/test_*.cpp + ../openmw/mwworld/store.cpp + ../openmw/mwworld/esmstore.cpp + mwworld/test_store.cpp + + mwdialogue/test_keywordsearch.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/components/misc/test_stringops.cpp b/apps/openmw_test_suite/components/misc/test_stringops.cpp deleted file mode 100644 index 55fe0e0c27..0000000000 --- a/apps/openmw_test_suite/components/misc/test_stringops.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include -#include "components/misc/stringops.hpp" - -struct StringOpsTest : public ::testing::Test -{ - protected: - virtual void SetUp() - { - } - - virtual void TearDown() - { - } -}; diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp new file mode 100644 index 0000000000..ac21470ded --- /dev/null +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -0,0 +1,315 @@ +#include + +#include + +#include +#include +#include +#include + +#include "apps/openmw/mwworld/esmstore.hpp" + +static Loading::Listener dummyListener; + +/// Base class for tests of ESMStore that rely on external content files to produce the test results +struct ContentFileTest : public ::testing::Test +{ + protected: + + virtual void SetUp() + { + readContentFiles(); + + // load the content files + std::vector readerList; + readerList.resize(mContentFiles.size()); + + int index=0; + for (std::vector::const_iterator it = mContentFiles.begin(); it != mContentFiles.end(); ++it) + { + ESM::ESMReader lEsm; + lEsm.setEncoder(NULL); + lEsm.setIndex(index); + lEsm.setGlobalReaderList(&readerList); + lEsm.open(it->string()); + readerList[index] = lEsm; + mEsmStore.load(readerList[index], &dummyListener); + + ++index; + } + + mEsmStore.setUp(); + } + + virtual void TearDown() + { + } + + // read absolute path to content files from openmw.cfg + void readContentFiles() + { + boost::program_options::variables_map variables; + + boost::program_options::options_description desc("Allowed options"); + desc.add_options() + ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) + ("content", boost::program_options::value >()->default_value(std::vector(), "") + ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") + ("data-local", boost::program_options::value()->default_value("")); + + boost::program_options::notify(variables); + + mConfigurationManager.readConfiguration(variables, desc, true); + + Files::PathContainer dataDirs, dataLocal; + if (!variables["data"].empty()) { + dataDirs = Files::PathContainer(variables["data"].as()); + } + + std::string local = variables["data-local"].as(); + if (!local.empty()) { + dataLocal.push_back(Files::PathContainer::value_type(local)); + } + + mConfigurationManager.processPaths (dataDirs); + mConfigurationManager.processPaths (dataLocal, true); + + if (!dataLocal.empty()) + dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end()); + + Files::Collections collections (dataDirs, true); + + std::vector contentFiles = variables["content"].as >(); + for (std::vector::iterator it = contentFiles.begin(); it != contentFiles.end(); ++it) + mContentFiles.push_back(collections.getPath(*it)); + } + +protected: + Files::ConfigurationManager mConfigurationManager; + MWWorld::ESMStore mEsmStore; + std::vector mContentFiles; +}; + +/// Print results of the dialogue merging process, i.e. the resulting linked list. +TEST_F(ContentFileTest, dialogue_merging_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + const std::string file = "test_dialogue_merging.txt"; + + boost::filesystem::ofstream stream; + stream.open(file); + + const MWWorld::Store& dialStore = mEsmStore.get(); + for (MWWorld::Store::iterator it = dialStore.begin(); it != dialStore.end(); ++it) + { + const ESM::Dialogue& dial = *it; + stream << "Dialogue: " << dial.mId << std::endl; + + for (ESM::Dialogue::InfoContainer::const_iterator infoIt = dial.mInfo.begin(); infoIt != dial.mInfo.end(); ++infoIt) + { + const ESM::DialInfo& info = *infoIt; + stream << info.mId << std::endl; + } + stream << std::endl; + } + + std::cout << "dialogue_merging_test successful, results printed to " << file << std::endl; +} + +// Note: here we don't test records that don't use string names (e.g. Land, Pathgrid, Cell) +#define RUN_TEST_FOR_TYPES(func, arg1, arg2) \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); \ + func(arg1, arg2); + +template +void printRecords(MWWorld::ESMStore& esmStore, std::ostream& outStream) +{ + const MWWorld::Store& store = esmStore.get(); + outStream << store.getSize() << " " << T::getRecordType() << " records" << std::endl; + + for (typename MWWorld::Store::iterator it = store.begin(); it != store.end(); ++it) + { + const T& record = *it; + outStream << record.mId << std::endl; + } + + outStream << std::endl; +} + +/// Print some basic diagnostics about the loaded content files, e.g. number of records and names of those records +/// Also used to test the iteration order of records +TEST_F(ContentFileTest, content_diagnostics_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + const std::string file = "test_content_diagnostics.txt"; + + boost::filesystem::ofstream stream; + stream.open(file); + + RUN_TEST_FOR_TYPES(printRecords, mEsmStore, stream); + + std::cout << "diagnostics_test successful, results printed to " << file << std::endl; +} + +// TODO: +/// Print results of autocalculated NPC spell lists. Also serves as test for attribute/skill autocalculation which the spell autocalculation heavily relies on +/// - even incorrect rounding modes can completely change the resulting spell lists. +/* +TEST_F(ContentFileTest, autocalc_test) +{ + if (mContentFiles.empty()) + { + std::cout << "No content files found, skipping test" << std::endl; + return; + } + + +} +*/ + +/// Base class for tests of ESMStore that do not rely on external content files +struct StoreTest : public ::testing::Test +{ +protected: + MWWorld::ESMStore mEsmStore; +}; + + +/// Create an ESM file in-memory containing the specified record. +/// @param deleted Write record with deleted flag? +template +Files::IStreamPtr getEsmFile(T record, bool deleted) +{ + ESM::ESMWriter writer; + std::stringstream* stream = new std::stringstream; + writer.setFormat(0); + writer.save(*stream); + writer.startRecord(T::sRecordId); + record.save(writer, deleted); + writer.endRecord(T::sRecordId); + + return Files::IStreamPtr(stream); +} + +/// Tests deletion of records. +TEST_F(StoreTest, delete_test) +{ + const std::string recordId = "foobar"; + + typedef ESM::Apparatus RecordType; + + RecordType record; + record.blank(); + record.mId = recordId; + + ESM::ESMReader reader; + std::vector readerList; + readerList.push_back(reader); + reader.setGlobalReaderList(&readerList); + + // master file inserts a record + Files::IStreamPtr file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 1); + + // now a plugin deletes it + file = getEsmFile(record, true); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 0); + + // now another plugin inserts it again + // expected behaviour is the record to reappear rather than staying deleted + file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + ASSERT_TRUE (mEsmStore.get().getSize() == 1); +} + +/// Tests overwriting of records. +TEST_F(StoreTest, overwrite_test) +{ + const std::string recordId = "foobar"; + const std::string recordIdUpper = "Foobar"; + + typedef ESM::Apparatus RecordType; + + RecordType record; + record.blank(); + record.mId = recordId; + + ESM::ESMReader reader; + std::vector readerList; + readerList.push_back(reader); + reader.setGlobalReaderList(&readerList); + + // master file inserts a record + Files::IStreamPtr file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + // now a plugin overwrites it with changed data + record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it + record.mModel = "the_new_model"; + file = getEsmFile(record, false); + reader.open(file, "filename"); + mEsmStore.load(reader, &dummyListener); + mEsmStore.setUp(); + + // verify that changes were actually applied + const RecordType* overwrittenRec = mEsmStore.get().search(recordId); + + ASSERT_TRUE (overwrittenRec != NULL); + + ASSERT_TRUE (overwrittenRec && overwrittenRec->mModel == "the_new_model"); +} diff --git a/components/compiler/errorhandler.cpp b/components/compiler/errorhandler.cpp index a987a86da2..7f02255db2 100644 --- a/components/compiler/errorhandler.cpp +++ b/components/compiler/errorhandler.cpp @@ -32,7 +32,10 @@ namespace Compiler void ErrorHandler::warning (const std::string& message, const TokenLoc& loc) { - if (mWarningsMode==1) + if (mWarningsMode==1 || + // temporarily change from mode 2 to mode 1 if error downgrading is enabled to + // avoid infinite recursion + (mWarningsMode==2 && mDowngradeErrors)) { ++mWarnings; report (message, loc, WarningMessage); diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 8478959785..fe867febae 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -37,7 +37,6 @@ namespace Compiler float mPutbackFloat; std::string mPutbackName; TokenLoc mPutbackLoc; - bool mNameStartingWithDigit; public: diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 33ac4a91e8..76a82fe232 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"); + isDeleted = false; - mScale = 1.0; - esm.getHNOT (mScale, "XSCL"); - - mOwner = esm.getHNOString ("ANAM"); - mGlobalVariable = esm.getHNOString ("BNAM"); - mSoul = esm.getHNOString ("XSOL"); - - mFaction = esm.getHNOString ("CNAM"); - mFactionRank = -2; - esm.getHNOT (mFactionRank, "INDX"); - - mGoldValue = 1; - mChargeInt = -1; - mEnchantmentCharge = -1; - - esm.getHNOT (mEnchantmentCharge, "XCHG"); - - esm.getHNOT (mChargeInt, "INTV"); - - esm.getHNOT (mGoldValue, "NAM9"); - - // Present for doors that teleport you to another cell. - 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::SREC_DELE: + 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 e9959611b3..c371a4f015 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 9d605a6af9..66e338d0c4 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::SREC_NAME: + 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::SREC_DELE: + 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 b54e8ff5fc..c056750a88 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/defs.hpp b/components/esm/defs.hpp index 7ef8102c29..9dc088596a 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -124,5 +124,12 @@ enum RecNameInts REC_DBGP = FourCC<'D','B','G','P'>::value ///< only used in project files }; +/// Common subrecords +enum SubRecNameInts +{ + SREC_DELE = ESM::FourCC<'D','E','L','E'>::value, + SREC_NAME = ESM::FourCC<'N','A','M','E'>::value +}; + } #endif diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 77ac0ae32c..400ead4e27 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -185,6 +185,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() @@ -274,6 +279,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 4e92b7e5f3..66ef981306 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -187,6 +187,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 5bc768f725..c3658f1520 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::SREC_NAME: + 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::SREC_DELE: + 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 bc3dd7bdcb..b1c511ebba 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 b5adce5509..b9934d3d39 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::SREC_NAME: + 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::SREC_DELE: + 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 d9a55023bc..4cc72d5283 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 18db512c0c..2049045024 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::SREC_NAME: + 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::SREC_DELE: + 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 b90a7c448d..9ef390ebd9 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 f2c82aacfb..490881fae7 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) - { - 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"); - } - } - if (!hasData) - esm.fail("Missing AADT"); -} + isDeleted = false; -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); -} + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + 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::SREC_DELE: + 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"); + } + + 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 f18b046486..0590d33edf 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 066551d6fe..f2526554af 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::SREC_NAME: + 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::SREC_DELE: + 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 54416fd318..ef3bb734c0 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 ed24ded57b..09113e8d1b 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::SREC_NAME: + 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::SREC_DELE: + 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 c48c313056..bf320330ff 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 47f52fc31a..617040be42 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::SREC_NAME: + 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::SREC_DELE: + 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 6211b3e457..3d50356aea 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 e0cd83ea07..9a4d98bb7b 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::SREC_NAME: + 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::SREC_DELE: + 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 f91f91c974..24d27a7f85 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 94f4b0b6e7..8455daeac3 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; - - if (mData.mFlags & Interior) + void Cell::load(ESMReader &esm, bool &isDeleted, bool saveContext) { - // Interior cells - if (esm.isNextSub("INTV")) + loadNameAndData(esm, isDeleted); + loadCell(esm, saveContext); + } + + void Cell::loadNameAndData(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; + + blank(); + + bool hasData = false; + bool isLoaded = false; + while (!isLoaded && esm.hasMoreSubs()) { - int waterl; - esm.getHT(waterl); - mWater = (float) waterl; - mWaterInt = true; - } - else if (esm.isNextSub("WHGT")) - { - esm.getHT(mWater); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mName = esm.getHString(); + break; + case ESM::FourCC<'D','A','T','A'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::SREC_DELE: + 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) + 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.writeHNOCString("RGNN", mRegion); - else - esm.writeHNT("AMBI", mAmbi, 16); - } - else - { - esm.writeHNOCString("RGNN", mRegion); - if (mMapColor != 0) - esm.writeHNT("NAM5", mMapColor); + if (mMapColor != 0) + esm.writeHNT("NAM5", mMapColor); + } + + if (mRefNumCounter != 0) + esm.writeHNT("NAM0", mRefNumCounter); } - if (mRefNumCounter != 0) - esm.writeHNT("NAM0", mRefNumCounter); -} - -void Cell::restore(ESMReader &esm, int iCtx) const -{ - esm.restoreContext(mContextList.at (iCtx)); -} - -std::string Cell::getDescription() const -{ - if (mData.mFlags & Interior) + void Cell::restore(ESMReader &esm, int iCtx) const { - return mName; + esm.restoreContext(mContextList.at (iCtx)); } - else - { - std::ostringstream stream; - stream << mData.mX << ", " << mData.mY; - return stream.str(); - } -} -bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool& deleted, bool ignoreMoves, MovedCellRef *mref) -{ - // TODO: Try and document reference numbering, I don't think this has been done anywhere else. - if (!esm.hasMoreSubs()) - return false; - - // NOTE: We should not need this check. It is a safety check until we have checked - // 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 + std::ostringstream stream; + stream << mData.mX << ", " << mData.mY; + return stream.str(); + } + } + + bool Cell::getNextRef(ESMReader &esm, CellRef &ref, bool &isDeleted, bool ignoreMoves, MovedCellRef *mref) + { + isDeleted = false; + + // 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")) + { + 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; + } } + + if (esm.peekNextSub("FRMR")) + { + ref.load (esm, isDeleted); + + // Identify references belonging to a parent file and adapt the ID accordingly. + adjustRefNum (ref.mRefNum, esm); + return true; + } + return false; } - ref.load (esm); - - // Identify references belonging to a parent file and adapt the ID accordingly. - adjustRefNum (ref.mRefNum, esm); - - if (esm.isNextSub("DELE")) + bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) { - esm.skipHSub(); - deleted = true; + esm.getHT(mref.mRefNum.mIndex); + esm.getHNOT(mref.mTarget, "CNDT"); + + adjustRefNum (mref.mRefNum, esm); + + return true; } - else - deleted = false; - - return true; -} - -bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) -{ - esm.getHT(mref.mRefNum.mIndex); - esm.getHNOT(mref.mTarget, "CNDT"); - - adjustRefNum (mref.mRefNum, esm); - - return true; -} void Cell::blank() { diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 12fb8c0684..2a7a78a54a 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 66acaea721..61960b87db 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::SREC_NAME: + 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::SREC_DELE: + 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 972b48e889..833dd6757d 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 5f49b5e709..2ef69e5e91 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::SREC_NAME: + 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::SREC_DELE: + 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 6945f224a4..39e5ea6729 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 3481189c37..739b0d7db6 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::SREC_NAME: + 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::SREC_DELE: + 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 ab587f9353..4c847f4e2e 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 fb235e6b3d..b517820896 100644 --- a/components/esm/loadcrea.cpp +++ b/components/esm/loadcrea.cpp @@ -1,5 +1,7 @@ #include "loadcrea.hpp" +#include + #include "esmreader.hpp" #include "esmwriter.hpp" #include "defs.hpp" @@ -8,8 +10,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 +23,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::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; case ESM::FourCC<'M','O','D','L'>::value: mModel = esm.getHString(); break; @@ -72,18 +81,40 @@ namespace ESM { case AI_CNDT: mAiPackage.add(esm); break; + case ESM::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + case ESM::FourCC<'I','N','D','X'>::value: + // seems to occur only in .ESS files, unsure of purpose + int index; + esm.getHT(index); + std::cerr << "Creature::load: Unhandled INDX " << index << std::endl; + 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 47e5954a5f..a5147619cf 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 f2da8f3775..bc87c4f57d 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::loadData(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; -void Dialogue::readInfo(ESMReader &esm, bool merge) -{ - const std::string& id = esm.getHNOString("INAM"); + 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::SREC_DELE: + esm.skipHSub(); + mType = Unknown; + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + } - if (!merge || mInfo.empty()) + void Dialogue::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + } + else + { + esm.writeHNT("DATA", mType); + } + } + + void Dialogue::blank() + { + mInfo.clear(); + } + + void Dialogue::readInfo(ESMReader &esm, bool merge) { ESM::DialInfo info; - info.mId = id; - info.load(esm); - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; - } + info.loadId(esm); - ESM::Dialogue::InfoContainer::iterator it = mInfo.end(); + bool isDeleted = false; + if (!merge || mInfo.empty()) + { + info.loadData(esm, isDeleted); + mLookup[info.mId] = std::make_pair(mInfo.insert(mInfo.end(), info), isDeleted); - std::map::iterator lookup; + return; + } - lookup = mLookup.find(id); + InfoContainer::iterator it = mInfo.end(); - ESM::DialInfo info; - if (lookup != mLookup.end()) - { - it = lookup->second; + LookupMap::iterator lookup; + lookup = mLookup.find(info.mId); - // Merge with existing record. Only the subrecords that are present in - // the new record will be overwritten. - it->load(esm); - info = *it; + if (lookup != mLookup.end()) + { + it = lookup->second.first; - // Since the record merging may have changed the next/prev linked list connection, we need to re-insert the record - mInfo.erase(it); - mLookup.erase(lookup); - } - else - { - info.mId = id; - info.load(esm); - } + // Merge with existing record. Only the subrecords that are present in + // the new record will be overwritten. + it->loadData(esm, isDeleted); + info = *it; - if (info.mNext.empty()) - { - mLookup[id] = mInfo.insert(mInfo.end(), info); - return; - } - if (info.mPrev.empty()) - { - mLookup[id] = mInfo.insert(mInfo.begin(), info); - return; - } - - lookup = mLookup.find(info.mPrev); - if (lookup != mLookup.end()) - { - it = lookup->second; - - mLookup[id] = mInfo.insert(++it, info); - return; - } - - lookup = mLookup.find(info.mNext); - if (lookup != mLookup.end()) - { - it = lookup->second; - - mLookup[id] = mInfo.insert(it, info); - return; - } - - std::cerr << "Failed to insert info " << id << std::endl; -} - -void Dialogue::clearDeletedInfos() -{ - for (InfoContainer::iterator it = mInfo.begin(); it != mInfo.end(); ) - { - if (it->mQuestStatus == DialInfo::QS_Deleted) - it = mInfo.erase(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 58598d3536..b80cbd74c3 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 f446eed611..6d8c0978c8 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::SREC_NAME: + 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::SREC_DELE: + 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 3073f4e9de..3afe5d5e4b 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 54690d9a0b..ae83c63f70 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) - { - 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; - } - } - if (!hasData) - esm.fail("Missing ENDT subrecord"); -} + isDeleted = false; + mEffects.mList.clear(); -void Enchantment::save(ESMWriter &esm) const -{ - esm.writeHNT("ENDT", mData, 16); - mEffects.save(esm); -} + bool hasName = false; + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + 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::SREC_DELE: + 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"); + } + + 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 cfcdd4edce..7b93b519c3 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 006ca0ce00..75c482d201 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::SREC_NAME: + 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::SREC_DELE: + 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"); + } + + void Faction::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNOCString("FNAM", mName); + + 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); } } - if (!hasData) - esm.fail("Missing FADT subrecord"); -} -void Faction::save(ESMWriter &esm) const -{ - esm.writeHNOCString("FNAM", mName); - - 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 8645e23fd8..cc715d2660 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 a78ed1a1be..72ecce503c 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 cc5dbbdcfc..0533cc95ea 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 21d66339a9..1ebb002e68 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 d9d9048b61..73a723e810 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 a2bade1c5e..246baf0265 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); + void DialInfo::load(ESMReader &esm, bool &isDeleted) + { + loadId(esm); + loadData(esm, isDeleted); } - 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::loadId(ESMReader &esm) { - mActor = esm.getHString(); - if (esm.isEmptyOrGetName()) + mId = esm.getHNString("INAM"); + } + + void DialInfo::loadData(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; + + 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(); + + 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::SREC_NAME: + 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::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + } + + void DialInfo::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("INAM", mId); + esm.writeHNCString("PNAM", mPrev); + esm.writeHNCString("NNAM", mNext); + + 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; + } } - if (subName.val == REC_RNAM) - { - mRace = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - if (subName.val == REC_CNAM) - { - mClass = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - - if (subName.val == REC_FNAM) - { - 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; - } - - while (subName.val == REC_SCVR) - { - SelectStruct ss; - - ss.mSelectRule = esm.getHString(); - - ss.mValue.read (esm, Variant::Format_Info); - - mSelects.push_back(ss); - - if (esm.isEmptyOrGetName()) - return; - } - - if (subName.val == REC_BNAM) - { - mResultScript = esm.getHString(); - if (esm.isEmptyOrGetName()) - return; - } - - 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) - { - 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; - case QS_Deleted: esm.writeHNT("DELE", '\1'); break; - default: break; - } -} void DialInfo::blank() { diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 54003b0d96..8123a9ace8 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 7e0cc3168d..e00e73ab0b 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::SREC_NAME: + 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::SREC_DELE: + 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 5846a97809..c0f4450238 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 784cfd4078..e7be033219 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; + 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; + 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; - } - } - 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)); - } -} -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++]; -} + 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); -Land::Land() - : mFlags(0) - , mX(0) - , mY(0) - , mPlugin(0) - , mEsm(NULL) - , mDataTypes(0) - , mDataLoaded(false) - , mLandData(NULL) -{ -} - -Land::~Land() -{ - delete mLandData; -} - -void Land::load(ESMReader &esm) -{ - mEsm = &esm; - mPlugin = mEsm->getIndex(); - - // Get the grid location - esm.getSubNameIs("INTV"); - esm.getSubHeaderIs(8); - esm.getT(mX); - esm.getT(mY); - - esm.getHNT(mFlags, "DATA"); - - // Store the file position - mContext = esm.getContext(); - - // 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; -} - -void Land::save(ESMWriter &esm) const -{ - esm.startSubRecord("INTV"); - esm.writeT(mX); - esm.writeT(mY); - esm.endRecord("INTV"); - - esm.writeHNT("DATA", mFlags); -} - -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("VNML")) { - condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); - } - - 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]; - - 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; + prevX = mHeights[number]; + ++number; } } - mLandData->mUnk1 = vhgt.mUnk1; - mLandData->mUnk2 = vhgt.mUnk2; + 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)); } } - if (mEsm->isNextSub("WNAM")) { - condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); + Land::Land() + : mFlags(0) + , mX(0) + , mY(0) + , mPlugin(0) + , mEsm(NULL) + , mDataTypes(0) + , mDataLoaded(false) + , mLandData(NULL) + { } - 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::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; - mLandData = NULL; + } + + void Land::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; + + 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::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.cacheSubName(); + isLoaded = true; + break; + } + } + + if (!hasLocation) + esm.fail("Missing INTV subrecord"); + + 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; + } + } + mDataLoaded = 0; + mLandData = NULL; } -} -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; + void Land::save(ESMWriter &esm, bool isDeleted) const + { + esm.startSubRecord("INTV"); + esm.writeT(mX); + esm.writeT(mY); + esm.endRecord("INTV"); + + esm.writeHNT("DATA", mFlags); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + if (mLandData) + { + mLandData->save(esm); + } } - mEsm->skipHSubSize(size); - return false; -} -bool Land::isDataLoaded(int flags) const -{ - return (mDataLoaded & flags) == (flags & mDataTypes); -} + 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("VNML")) { + condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + } + + 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]; + + 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; + } + } + + 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) + { + 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; + } + mEsm->skipHSubSize(size); + return false; + } + + 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 8ec4f74ea0..65ac88cda3 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 ca5c5d74d8..8c0d503244 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")) + bool hasName = false; + bool hasList = false; + while (esm.hasMoreSubs()) { - int len; - esm.getHT(len); - mList.resize(len); + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + 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. + 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::SREC_DELE: + 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; + } + } } - else + + if (!hasName) + esm.fail("Missing NAME subrecord"); + } + + void LevelledListBase::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) { - // Original engine ignores rest of the record, even if there are items following - mList.clear(); - esm.skipRecord(); + esm.writeHNCString("DELE", ""); return; } - // 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"); - } - } - void LevelledListBase::save(ESMWriter &esm) const - { 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 dc6fcda5ec..ed4131c165 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 26d70d964a..20c700b238 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::SREC_NAME: + 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::SREC_DELE: + 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 ed8c36665c..8509c64b6d 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 2747a6f787..fc6af86990 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::SREC_NAME: + 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::SREC_DELE: + 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 0d678cd64c..9db41af978 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 c3e2d50ff5..a42ae7c5bf 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::SREC_NAME: + 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::SREC_DELE: + 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 50a7881054..2cb5abf0c8 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 6f859ab3cd..eef58aa2f1 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 eeb4268c2c..a52ed797f9 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 81c094f2bc..199b4e3b2c 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::SREC_NAME: + 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::SREC_DELE: + 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 6e0b4e01b8..e7a3239042 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 67a437176c..e524e6a7cc 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::SREC_NAME: + 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::SREC_DELE: + 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 9bda9560e1..5b89f40554 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 fc0974c9d8..69ee60eeb8 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"); - - mPoints.clear(); - mEdges.clear(); - - // keep track of total connections so we can reserve edge vector size - int edgeCount = 0; - - if (esm.isNextSub("PGRP")) + void Pathgrid::load(ESMReader &esm, bool &isDeleted) { - 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; - } - } - } + isDeleted = false; - if (esm.isNextSub("PGRC")) - { - 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); - } + mPoints.clear(); + mEdges.clear(); - 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) + // keep track of total connections so we can reserve edge vector size + int edgeCount = 0; + + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) { - 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); + case ESM::SREC_NAME: + 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::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; } } - } -} -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) - { - esm.writeT(*it); - } - esm.endRecord("PGRP"); + if (!hasData) + esm.fail("Missing DATA subrecord"); } - if (!mEdges.empty()) + void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { - esm.startSubRecord("PGRC"); - for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + esm.writeHNCString("NAME", mCell); + esm.writeHNT("DATA", mData, 12); + + if (isDeleted) { - esm.writeT(it->mV1); + esm.writeHNCString("DELE", ""); + return; + } + + if (!mPoints.empty()) + { + esm.startSubRecord("PGRP"); + for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + { + esm.writeT(*it); + } + esm.endRecord("PGRP"); + } + + if (!mEdges.empty()) + { + 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 f33ccbedf9..d1003eb865 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 c5f80c5844..baf466490b 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::SREC_NAME: + 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::SREC_DELE: + 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 c737757aa3..da203b456b 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 e88454d4c1..a371751446 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(); - - bool hasData = false; - while (esm.hasMoreSubs()) + void Race::load(ESMReader &esm, bool &isDeleted) { - esm.getSubName(); - uint32_t name = esm.retSubName().val; - switch (name) + isDeleted = false; + + mPowers.mList.clear(); + + 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','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.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + 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::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + } } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + if (!hasData && !isDeleted) + esm.fail("Missing RADT subrecord"); + } + void Race::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + 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 553d2e68b1..bf0573075c 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 1b08b72172..e5382f50b6 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"); + void Region::load(ESMReader &esm, bool &isDeleted) + { + isDeleted = false; - esm.getSubNameIs("WEAT"); - 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 + 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::SREC_NAME: + 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::SREC_DELE: + esm.skipHSub(); + isDeleted = true; + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + + if (!hasName) + esm.fail("Missing NAME subrecord"); + } + + void Region::save(ESMWriter &esm, bool isDeleted) const + { + esm.writeHNCString("NAME", mId); + + if (isDeleted) + { + esm.writeHNCString("DELE", ""); + return; + } + + esm.writeHNOCString("FNAM", mName); + + if (esm.getVersion() == VER_12) + esm.writeHNT("WEAT", mData, sizeof(mData) - 2); + else + esm.writeHNT("WEAT", mData); + + esm.writeHNOCString("BNAM", mSleepList); + + esm.writeHNT("CNAM", mMapColor); + for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) + { + esm.writeHNT("SNAM", *it); } } - else - esm.fail("Don't know what to do in this version"); - - mSleepList = esm.getHNOString("BNAM"); - - esm.getHNT(mMapColor, "CNAM"); - - 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); - - if (esm.getVersion() == VER_12) - esm.writeHNT("WEAT", mData, sizeof(mData) - 2); - else - esm.writeHNT("WEAT", mData); - - esm.writeHNOCString("BNAM", mSleepList); - - 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 1e241fffbe..3d914bd17b 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 f90f9e39dc..e4af3d9378 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::SREC_NAME: + 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::SREC_DELE: + 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 e765bc93a5..2537c53cb6 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 60b4a3304e..af716cab67 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -6,7 +6,6 @@ namespace ESM { - unsigned int Script::sRecordId = REC_SCPT; void Script::loadSCVR(ESMReader &esm) @@ -56,21 +55,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); @@ -88,13 +91,21 @@ namespace ESM case ESM::FourCC<'S','C','T','X'>::value: mScriptText = esm.getHString(); break; + case ESM::SREC_DELE: + 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()) @@ -109,6 +120,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 56390f3841..b8a06406dd 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 7883b8a1a9..c520897910 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 e001842970..5430b422d1 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 5ee6f5245c..d84fe624d0 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::SREC_NAME: + 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::SREC_DELE: + 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 056958f0a8..70b221e98c 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 690c1b4484..82f169e54f 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::SREC_NAME: + 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::SREC_DELE: + 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 ff2202ca7d..937e22be88 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 96c048e0a9..728b7bc2a7 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::SREC_NAME: + 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::SREC_DELE: + 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 491da1d179..1763d0991c 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 7380dd0a7f..ab4c09750d 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::SREC_NAME: + 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::SREC_DELE: + 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 dc7ad6a42a..ce2ff49e77 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 ed90b04751..62d495ee34 100644 --- a/components/esm/loadstat.cpp +++ b/components/esm/loadstat.cpp @@ -8,15 +8,47 @@ namespace ESM { unsigned int Static::sRecordId = REC_STAT; - void Static::load(ESMReader &esm) + void Static::load(ESMReader &esm, bool &isDeleted) { - mPersistent = (esm.getRecordFlags() & 0x0400) != 0; + isDeleted = false; - mModel = esm.getHNString("MODL"); + bool hasName = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().val) + { + case ESM::SREC_NAME: + mId = esm.getHString(); + hasName = true; + break; + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::SREC_DELE: + 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 21a9e66e88..930cdb8491 100644 --- a/components/esm/loadstat.hpp +++ b/components/esm/loadstat.hpp @@ -28,10 +28,8 @@ struct Static std::string mId, mModel; - bool mPersistent; - - 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 981a5815a2..880a26bcb3 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::SREC_NAME: + 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::SREC_DELE: + 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 f66e9f3a68..eddcaee4f1 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 62aa0452a8..cf1db32ff2 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"); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 83fa858900..9df4d6890b 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -1,5 +1,8 @@ #include "storage.hpp" +#include +#include + #include #include #include @@ -32,19 +35,22 @@ namespace ESMTerrain Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); + int cellX = static_cast(std::floor(origin.x)); + int cellY = static_cast(std::floor(origin.y)); - int cellX = static_cast(origin.x); - int cellY = static_cast(origin.y); + int startRow = (origin.x - cellX) * ESM::Land::LAND_SIZE; + int startColumn = (origin.y - cellY) * ESM::Land::LAND_SIZE; + + int endRow = startRow + size * (ESM::Land::LAND_SIZE-1) + 1; + int endColumn = startColumn + size * (ESM::Land::LAND_SIZE-1) + 1; if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VHGT)) { min = std::numeric_limits::max(); max = -std::numeric_limits::max(); - for (int row=0; rowmHeights[col*ESM::Land::LAND_SIZE+row]; if (h > max) @@ -141,17 +147,15 @@ namespace ESMTerrain size_t increment = 1 << lodLevel; Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); - assert(origin.x == (int) origin.x); - assert(origin.y == (int) origin.y); - int startX = static_cast(origin.x); - int startY = static_cast(origin.y); + int startCellX = static_cast(std::floor(origin.x)); + int startCellY = static_cast(std::floor(origin.y)); size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); - colours.resize(numVerts*numVerts*4); positions.resize(numVerts*numVerts*3); normals.resize(numVerts*numVerts*3); + colours.resize(numVerts*numVerts*4); Ogre::Vector3 normal; Ogre::ColourValue color; @@ -160,10 +164,10 @@ namespace ESMTerrain float vertX = 0; float vertY_ = 0; // of current cell corner - for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { float vertX_ = 0; // of current cell corner - for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT); const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML); @@ -173,20 +177,33 @@ namespace ESMTerrain int colStart = 0; // Skip the first row / column unless we're at a chunk edge, // since this row / column is already contained in a previous cell + // This is only relevant if we're creating a chunk spanning multiple cells if (colStart == 0 && vertY_ != 0) colStart += increment; if (rowStart == 0 && vertX_ != 0) rowStart += increment; + // Only relevant for chunks smaller than (contained in) one cell + rowStart += (origin.x - startCellX) * ESM::Land::LAND_SIZE; + colStart += (origin.y - startCellY) * ESM::Land::LAND_SIZE; + int rowEnd = rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + int colEnd = colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1; + vertY = vertY_; - for (int col=colStart; col(vertX*numVerts * 3 + vertY * 3)] = ((vertX / float(numVerts - 1) - 0.5f) * size * 8192); positions[static_cast(vertX*numVerts * 3 + vertY * 3 + 1)] = ((vertY / float(numVerts - 1) - 0.5f) * size * 8192); + assert(row >= 0 && row < ESM::Land::LAND_SIZE); + assert(col >= 0 && col < ESM::Land::LAND_SIZE); + + assert (vertX < numVerts); + assert (vertY < numVerts); + float height = -2048; if (heightData) height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; @@ -284,13 +301,19 @@ namespace ESMTerrain std::string Storage::getTextureName(UniqueTextureId id) { + static const std::string defaultTexture = "textures\\_land_default.dds"; if (id.first == 0) - return "textures\\_land_default.dds"; // Not sure if the default texture really is hardcoded? + return defaultTexture; // Not sure if the default texture really is hardcoded? // NB: All vtex ids are +1 compared to the ltex ids const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + if (!ltex) + { + std::cerr << "Unable to find land texture index " << id.first-1 << " in plugin " << id.second << ", using default texture instead" << std::endl; + return defaultTexture; + } - //TODO this is needed due to MWs messed up texture handling + // this is needed due to MWs messed up texture handling std::string texture = Misc::ResourceHelpers::correctTexturePath(ltex->mTexture); return texture; @@ -320,8 +343,19 @@ namespace ESMTerrain // and interpolate the rest of the cell by hand? :/ Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); - int cellX = static_cast(origin.x); - int cellY = static_cast(origin.y); + int cellX = static_cast(std::floor(origin.x)); + int cellY = static_cast(std::floor(origin.y)); + + int realTextureSize = ESM::Land::LAND_TEXTURE_SIZE+1; // add 1 to wrap around next cell + + int rowStart = (origin.x - cellX) * realTextureSize; + int colStart = (origin.y - cellY) * realTextureSize; + int rowEnd = rowStart + chunkSize * (realTextureSize-1) + 1; + int colEnd = colStart + chunkSize * (realTextureSize-1) + 1; + + assert (rowStart >= 0 && colStart >= 0); + assert (rowEnd <= realTextureSize); + assert (colEnd <= realTextureSize); // Save the used texture indices so we know the total number of textures // and number of required blend maps @@ -332,8 +366,8 @@ namespace ESMTerrain // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. textureIndices.insert(std::make_pair(0,0)); - for (int y=0; ysecond; int blendIndex = (pack ? static_cast(std::floor((layerIndex - 1) / 4.f)) : layerIndex - 1); int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; diff --git a/components/loadinglistener/loadinglistener.hpp b/components/loadinglistener/loadinglistener.hpp index 47962015c2..558111b342 100644 --- a/components/loadinglistener/loadinglistener.hpp +++ b/components/loadinglistener/loadinglistener.hpp @@ -6,18 +6,18 @@ namespace Loading class Listener { public: - virtual void setLabel (const std::string& label) = 0; + virtual void setLabel (const std::string& label) {} // Use ScopedLoad instead of using these directly - virtual void loadingOn() = 0; - virtual void loadingOff() = 0; + virtual void loadingOn() {} + virtual void loadingOff() {} /// Indicate that some progress has been made, without specifying how much - virtual void indicateProgress () = 0; + virtual void indicateProgress () {} - virtual void setProgressRange (size_t range) = 0; - virtual void setProgress (size_t value) = 0; - virtual void increaseProgress (size_t increase = 1) = 0; + virtual void setProgressRange (size_t range) {} + virtual void setProgress (size_t value) {} + virtual void increaseProgress (size_t increase = 1) {} }; // Used for stopping a loading sequence when the object goes out of scope diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp deleted file mode 100644 index 723c1c1e0b..0000000000 --- a/components/misc/stringops.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "stringops.hpp" - -namespace Misc -{ - -std::locale StringUtils::mLocale = std::locale::classic(); - -} diff --git a/components/misc/stringops.hpp b/components/misc/stringops.hpp index 04dedb0721..d6bc190695 100644 --- a/components/misc/stringops.hpp +++ b/components/misc/stringops.hpp @@ -4,18 +4,15 @@ #include #include #include -#include namespace Misc { class StringUtils { - - static std::locale mLocale; struct ci { bool operator()(char x, char y) const { - return std::tolower(x, StringUtils::mLocale) < std::tolower(y, StringUtils::mLocale); + return tolower(x) < tolower(y); } }; @@ -31,7 +28,7 @@ public: std::string::const_iterator xit = x.begin(); std::string::const_iterator yit = y.begin(); for (; xit != x.end(); ++xit, ++yit) { - if (std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) { + if (tolower(*xit) != tolower(*yit)) { return false; } } @@ -45,7 +42,7 @@ public: for(;xit != x.end() && yit != y.end() && len > 0;++xit,++yit,--len) { int res = *xit - *yit; - if(res != 0 && std::tolower(*xit, mLocale) != std::tolower(*yit, mLocale)) + if(res != 0 && tolower(*xit) != tolower(*yit)) return (res > 0) ? 1 : -1; } if(len > 0) @@ -61,7 +58,7 @@ public: /// Transforms input string to lower case w/o copy static std::string &toLower(std::string &inout) { for (unsigned int i=0; i