#ifndef OPENMW_ESSIMPORT_CONVERTER_H #define OPENMW_ESSIMPORT_CONVERTER_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "importcrec.hpp" #include "importcntc.hpp" #include "importercontext.hpp" #include "importcellref.hpp" #include "importklst.hpp" #include "importgame.hpp" #include "importinfo.hpp" #include "importdial.hpp" #include "importques.hpp" #include "importjour.hpp" #include "importscpt.hpp" #include "convertacdt.hpp" #include "convertnpcc.hpp" #include "convertscpt.hpp" #include "convertplayer.hpp" namespace ESSImport { class Converter { public: /// @return the order for writing this converter's records to the output file, in relation to other converters virtual int getStage() { return 1; } virtual ~Converter() {} 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) { } /// Called after the input file has been read in completely, which may be necessary /// if the conversion process relies on information in other records virtual void write(ESM::ESMWriter& esm) { } protected: Context* mContext; }; /// Default converter: simply reads the record and writes it unmodified to the output template class DefaultConverter : public Converter { public: virtual int getStage() { return 0; } virtual void read(ESM::ESMReader& esm) { T record; bool isDeleted = false; record.load(esm, isDeleted); mRecords[record.mId] = record; } virtual void write(ESM::ESMWriter& esm) { for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) { esm.startRecord(T::sRecordId); it->second.save(esm); esm.endRecord(T::sRecordId); } } protected: std::map mRecords; }; class ConvertNPC : public Converter { public: virtual void read(ESM::ESMReader &esm) { ESM::NPC npc; 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(npc.mId)] = npc; } else { mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel; mContext->mPlayerBase = npc; std::map empty; // FIXME: player start spells and birthsign spells aren't listed here, // need to fix openmw to account for this for (std::vector::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it) mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[*it] = empty; // Clear the list now that we've written it, this prevents issues cropping up with // ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal. mContext->mPlayerBase.mSpells.mList.clear(); // Same with inventory. Actually it's strange this would contain something, since there's already an // inventory list in NPCC. There seems to be a fair amount of redundancy in this format. mContext->mPlayerBase.mInventory.mList.clear(); } } }; class ConvertCREA : public Converter { public: virtual void read(ESM::ESMReader &esm) { // See comment in ConvertNPC ESM::Creature creature; bool isDeleted = false; creature.load(esm, isDeleted); mContext->mCreatures[Misc::StringUtils::lowerCase(creature.mId)] = creature; } }; // Do we need ConvertCONT? // I've seen a CONT record in a certain save file, but the container contents in it // were identical to a corresponding CNTC record. See previous comment about redundancy... class ConvertGlobal : public DefaultConverter { public: virtual void read(ESM::ESMReader &esm) { ESM::Global global; bool isDeleted = false; global.load(esm, isDeleted); if (Misc::StringUtils::ciEqual(global.mId, "gamehour")) mContext->mHour = global.mValue.getFloat(); if (Misc::StringUtils::ciEqual(global.mId, "day")) mContext->mDay = global.mValue.getInteger(); if (Misc::StringUtils::ciEqual(global.mId, "month")) mContext->mMonth = global.mValue.getInteger(); if (Misc::StringUtils::ciEqual(global.mId, "year")) mContext->mYear = global.mValue.getInteger(); mRecords[global.mId] = global; } }; class ConvertClass : public DefaultConverter { public: virtual void read(ESM::ESMReader &esm) { ESM::Class class_; bool isDeleted = false; class_.load(esm, isDeleted); if (class_.mId == "NEWCLASSID_CHARGEN") mContext->mCustomPlayerClassName = class_.mName; mRecords[class_.mId] = class_; } }; class ConvertBook : public DefaultConverter { public: virtual void read(ESM::ESMReader &esm) { ESM::Book book; bool isDeleted = false; 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; } }; class ConvertNPCC : public Converter { public: virtual void read(ESM::ESMReader &esm) { std::string id = esm.getHNString("NAME"); NPCC npcc; npcc.load(esm); if (id == "PlayerSaveGame") { convertNPCC(npcc, mContext->mPlayer.mObject); } else { int index = npcc.mNPDT.mIndex; mContext->mNpcChanges.insert(std::make_pair(std::make_pair(index,id), npcc)); } } }; class ConvertREFR : public Converter { public: virtual void read(ESM::ESMReader &esm) { REFR refr; refr.load(esm); assert(refr.mRefID == "PlayerSaveGame"); mContext->mPlayer.mObject.mPosition = refr.mPos; ESM::CreatureStats& cStats = mContext->mPlayer.mObject.mCreatureStats; convertACDT(refr.mActorData.mACDT, cStats); ESM::NpcStats& npcStats = mContext->mPlayer.mObject.mNpcStats; convertNpcData(refr.mActorData, npcStats); mSelectedSpell = refr.mActorData.mSelectedSpell; if (!refr.mActorData.mSelectedEnchantItem.empty()) { ESM::InventoryState& invState = mContext->mPlayer.mObject.mInventory; for (unsigned int i=0; imPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam); } virtual void write(ESM::ESMWriter &esm) { esm.startRecord(ESM::REC_CAM_); esm.writeHNT("FIRS", mFirstPersonCam); esm.endRecord(ESM::REC_CAM_); } private: bool mFirstPersonCam; }; class ConvertCNTC : public Converter { virtual void read(ESM::ESMReader &esm) { std::string id = esm.getHNString("NAME"); CNTC cntc; cntc.load(esm); mContext->mContainerChanges.insert(std::make_pair(std::make_pair(cntc.mIndex,id), cntc)); } }; class ConvertCREC : public Converter { public: virtual void read(ESM::ESMReader &esm) { std::string id = esm.getHNString("NAME"); CREC crec; crec.load(esm); mContext->mCreatureChanges.insert(std::make_pair(std::make_pair(crec.mIndex,id), crec)); } }; class ConvertFMAP : public Converter { public: virtual void read(ESM::ESMReader &esm); virtual void write(ESM::ESMWriter &esm); private: osg::ref_ptr mGlobalMapImage; }; class ConvertCell : public Converter { public: virtual void read(ESM::ESMReader& esm); virtual void write(ESM::ESMWriter& esm); private: struct Cell { ESM::Cell mCell; std::vector mRefs; std::vector mFogOfWar; }; std::map mIntCells; std::map, Cell> mExtCells; std::vector mMarkers; void writeCell(const Cell& cell, ESM::ESMWriter &esm); }; class ConvertKLST : public Converter { public: virtual void read(ESM::ESMReader& esm) { KLST klst; klst.load(esm); mKillCounter = klst.mKillCounter; mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills; } virtual void write(ESM::ESMWriter &esm) { esm.startRecord(ESM::REC_DCOU); for (std::map::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it) { esm.writeHNString("ID__", it->first); esm.writeHNT ("COUN", it->second); } esm.endRecord(ESM::REC_DCOU); } private: std::map mKillCounter; }; class ConvertFACT : public Converter { public: virtual void read(ESM::ESMReader& esm) { ESM::Faction faction; bool isDeleted = false; faction.load(esm, isDeleted); std::string id = Misc::StringUtils::lowerCase(faction.mId); for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) { std::string faction2 = Misc::StringUtils::lowerCase(it->first); mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second)); } } }; /// Stolen items class ConvertSTLN : public Converter { public: virtual void read(ESM::ESMReader &esm) { std::string itemid = esm.getHNString("NAME"); Misc::StringUtils::lowerCaseInPlace(itemid); while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) { if (esm.retSubName().toString() == "FNAM") { std::string factionid = esm.getHString(); mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(factionid), true)); } else { std::string ownerid = esm.getHString(); mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); } } } virtual void write(ESM::ESMWriter &esm) { ESM::StolenItems items; for (std::map >::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it) { std::map, int> owners; for (std::set::const_iterator ownerIt = it->second.begin(); ownerIt != it->second.end(); ++ownerIt) { owners.insert(std::make_pair(std::make_pair(ownerIt->first, ownerIt->second) // Since OpenMW doesn't suffer from the owner contamination bug, // it needs a count argument. But for legacy savegames, we don't know // this count, so must assume all items of that ID are stolen, // like vanilla MW did. ,std::numeric_limits::max())); } items.mStolenItems.insert(std::make_pair(it->first, owners)); } esm.startRecord(ESM::REC_STLN); items.write(esm); esm.endRecord(ESM::REC_STLN); } private: typedef std::pair Owner; // std::map > mStolenItems; }; /// Seen responses for a dialogue topic? /// Each DIAL record is followed by a number of INFO records, I believe, just like in ESMs /// Dialogue conversion problems: /// - Journal is stored in one continuous HTML markup rather than each entry separately with associated info ID. /// - Seen dialogue responses only store the INFO id, rather than the fulltext. /// - Quest stages only store the INFO id, rather than the journal entry fulltext. class ConvertINFO : public Converter { public: virtual void read(ESM::ESMReader& esm) { INFO info; info.load(esm); } }; class ConvertDIAL : public Converter { public: virtual void read(ESM::ESMReader& esm) { std::string id = esm.getHNString("NAME"); DIAL dial; dial.load(esm); if (dial.mIndex > 0) mDials[id] = dial; } virtual void write(ESM::ESMWriter &esm) { for (std::map::const_iterator it = mDials.begin(); it != mDials.end(); ++it) { esm.startRecord(ESM::REC_QUES); ESM::QuestState state; state.mFinished = 0; state.mState = it->second.mIndex; state.mTopic = Misc::StringUtils::lowerCase(it->first); state.save(esm); esm.endRecord(ESM::REC_QUES); } } private: std::map mDials; }; class ConvertQUES : public Converter { public: virtual void read(ESM::ESMReader& esm) { std::string id = esm.getHNString("NAME"); QUES quest; quest.load(esm); } }; class ConvertJOUR : public Converter { public: virtual void read(ESM::ESMReader& esm) { JOUR journal; journal.load(esm); } }; class ConvertGAME : public Converter { public: ConvertGAME() : mHasGame(false) {} virtual void read(ESM::ESMReader &esm) { mGame.load(esm); mHasGame = true; } int validateWeatherID(int weatherID) { if(weatherID >= -1 && weatherID < 10) { return weatherID; } else { std::stringstream error; error << "Invalid weather ID:" << weatherID << std::endl; throw std::runtime_error(error.str()); } } virtual void write(ESM::ESMWriter &esm) { if (!mHasGame) return; esm.startRecord(ESM::REC_WTHR); ESM::WeatherState weather; weather.mTimePassed = 0.0f; weather.mFastForward = false; weather.mWeatherUpdateTime = mGame.mGMDT.mTimeOfNextTransition - mContext->mHour; weather.mTransitionFactor = 1 - (mGame.mGMDT.mWeatherTransition / 100.0f); weather.mCurrentWeather = validateWeatherID(mGame.mGMDT.mCurrentWeather); weather.mNextWeather = validateWeatherID(mGame.mGMDT.mNextWeather); weather.mQueuedWeather = -1; // TODO: Determine how ModRegion modifiers are saved in Morrowind. weather.save(esm); esm.endRecord(ESM::REC_WTHR); } private: bool mHasGame; GAME mGame; }; /// Running global script class ConvertSCPT : public Converter { public: virtual void read(ESM::ESMReader &esm) { SCPT script; script.load(esm); ESM::GlobalScript out; convertSCPT(script, out); mScripts.push_back(out); } virtual void write(ESM::ESMWriter &esm) { for (std::vector::const_iterator it = mScripts.begin(); it != mScripts.end(); ++it) { esm.startRecord(ESM::REC_GSCR); it->save(esm); esm.endRecord(ESM::REC_GSCR); } } private: std::vector mScripts; }; } #endif