diff --git a/README.md b/README.md index bb59b7e02..7c24a4c81 100644 --- a/README.md +++ b/README.md @@ -41,51 +41,58 @@ Command line options --version print version information and quit --data arg (=data) set data directories (later directories have higher priority) - --data-local arg set local data directory (highest + --data-local arg set local data directory (highest priority) --fallback-archive arg (=fallback-archive) - set fallback BSA archives (later + set fallback BSA archives (later archives have higher priority) --resources arg (=resources) set resources directory - --start arg (=Beshara) set initial cell - --content arg content file(s): esm/esp, or + --start arg set initial cell + --content arg content file(s): esm/esp, or omwgame/omwaddon - --anim-verbose [=arg(=1)] (=0) output animation indices files --no-sound [=arg(=1)] (=0) disable all sounds --script-verbose [=arg(=1)] (=0) verbose script output --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue scripts) at startup - --script-console [=arg(=1)] (=0) enable console-only script + --script-all-dialogue [=arg(=1)] (=0) compile all dialogue scripts at startup + --script-console [=arg(=1)] (=0) enable console-only script functionality - --script-run arg select a file containing a list of - console commands that is executed on + --script-run arg select a file containing a list of + console commands that is executed on startup - --script-warn [=arg(=1)] (=1) handling of warnings when compiling + --script-warn [=arg(=1)] (=1) handling of warnings when compiling scripts 0 - ignore warning 1 - show warning but consider script as correctly compiled anyway 2 - treat warnings as errors + --script-blacklist arg ignore the specified script (if the use + of the blacklist is enabled) + --script-blacklist-use [=arg(=1)] (=1) + enable script blacklisting + --load-savegame arg load a save game file on game startup --skip-menu [=arg(=1)] (=0) skip main menu on game startup - --new-game [=arg(=1)] (=0) run new game sequence (ignored if + --new-game [=arg(=1)] (=0) run new game sequence (ignored if skip-menu=0) - --fs-strict [=arg(=1)] (=0) strict file system handling (no case + --fs-strict [=arg(=1)] (=0) strict file system handling (no case folding) - --encoding arg (=win1252) Character encoding used in OpenMW game + --encoding arg (=win1252) Character encoding used in OpenMW game messages: - - win1250 - Central and Eastern European - such as Polish, Czech, Slovak, - Hungarian, Slovene, Bosnian, Croatian, - Serbian (Latin script), Romanian and + + win1250 - Central and Eastern European + such as Polish, Czech, Slovak, + Hungarian, Slovene, Bosnian, Croatian, + Serbian (Latin script), Romanian and Albanian languages - - win1251 - Cyrillic alphabet such as - Russian, Bulgarian, Serbian Cyrillic + + win1251 - Cyrillic alphabet such as + Russian, Bulgarian, Serbian Cyrillic and other languages - - win1252 - Western European (Latin) + + win1252 - Western European (Latin) alphabet, used by default --fallback arg fallback values --no-grab Don't grab mouse cursor + --export-fonts [=arg(=1)] (=0) Export Morrowind .fnt fonts to PNG + image and XML file in current directory --activate-dist arg (=-1) activation distance override diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a18736bf2..b23ba72f9 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -261,7 +261,7 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) std::cout << " Faction: '" << ref.mFaction << "'" << std::endl; std::cout << " Faction rank: '" << ref.mFactionRank << "'" << std::endl; std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n"; - std::cout << " Uses/health: '" << ref.mCharge << "'\n"; + std::cout << " Uses/health: '" << ref.mChargeInt << "'\n"; std::cout << " Gold value: '" << ref.mGoldValue << "'\n"; std::cout << " Blocked: '" << static_cast(ref.mReferenceBlocked) << "'" << std::endl; std::cout << " Deleted: " << deleted << std::endl; diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt index cf3218454..8d1af714d 100644 --- a/apps/essimporter/CMakeLists.txt +++ b/apps/essimporter/CMakeLists.txt @@ -8,10 +8,14 @@ set(ESSIMPORTER_FILES importacdt.cpp importinventory.cpp importklst.cpp + importcntc.cpp importercontext.cpp converter.cpp convertacdt.cpp convertnpcc.cpp + convertinventory.cpp + convertcrec.cpp + convertcntc.cpp ) add_executable(openmw-essimporter diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp index 81ab61084..496eab9e9 100644 --- a/apps/essimporter/convertacdt.cpp +++ b/apps/essimporter/convertacdt.cpp @@ -27,6 +27,8 @@ namespace ESSImport cStats.mAttributes[i].mMod = acdt.mAttributes[i][0]; cStats.mAttributes[i].mCurrent = acdt.mAttributes[i][0]; } + cStats.mGoldPool = acdt.mGoldPool; + cStats.mTalkedTo = acdt.mFlags & TalkedToPlayer; } void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats) @@ -37,6 +39,8 @@ namespace ESSImport npcStats.mSkills[i].mRegular.mCurrent = actorData.mSkills[i][1]; npcStats.mSkills[i].mRegular.mBase = actorData.mSkills[i][0]; } + + npcStats.mTimeToStartDrowning = actorData.mACDT.mBreathMeter; } } diff --git a/apps/essimporter/convertcntc.cpp b/apps/essimporter/convertcntc.cpp new file mode 100644 index 000000000..426ef4496 --- /dev/null +++ b/apps/essimporter/convertcntc.cpp @@ -0,0 +1,13 @@ +#include "convertcntc.hpp" + +#include "convertinventory.hpp" + +namespace ESSImport +{ + + void convertCNTC(const CNTC &cntc, ESM::ContainerState &state) + { + convertInventory(cntc.mInventory, state.mInventory); + } + +} diff --git a/apps/essimporter/convertcntc.hpp b/apps/essimporter/convertcntc.hpp new file mode 100644 index 000000000..c299d87a1 --- /dev/null +++ b/apps/essimporter/convertcntc.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTCNTC_H +#define OPENMW_ESSIMPORT_CONVERTCNTC_H + +#include "importcntc.hpp" + +#include + +namespace ESSImport +{ + + void convertCNTC(const CNTC& cntc, ESM::ContainerState& state); + +} + +#endif diff --git a/apps/essimporter/convertcrec.cpp b/apps/essimporter/convertcrec.cpp new file mode 100644 index 000000000..34e1c0070 --- /dev/null +++ b/apps/essimporter/convertcrec.cpp @@ -0,0 +1,13 @@ +#include "convertcrec.hpp" + +#include "convertinventory.hpp" + +namespace ESSImport +{ + + void convertCREC(const CREC &crec, ESM::CreatureState &state) + { + convertInventory(crec.mInventory, state.mInventory); + } + +} diff --git a/apps/essimporter/convertcrec.hpp b/apps/essimporter/convertcrec.hpp new file mode 100644 index 000000000..7d317f03e --- /dev/null +++ b/apps/essimporter/convertcrec.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTCREC_H +#define OPENMW_ESSIMPORT_CONVERTCREC_H + +#include "importcrec.hpp" + +#include + +namespace ESSImport +{ + + void convertCREC(const CREC& crec, ESM::CreatureState& state); + +} + +#endif diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index ce3cea675..099987762 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -1,8 +1,14 @@ #include "converter.hpp" +#include + #include #include +#include + +#include "convertcrec.hpp" +#include "convertcntc.hpp" namespace { @@ -15,6 +21,24 @@ namespace screenshot.save(out); } + + void convertCellRef(const ESSImport::CellRef& cellref, ESM::ObjectState& objstate) + { + objstate.mEnabled = cellref.mEnabled; + objstate.mPosition = cellref.mPos; + objstate.mRef.mRefNum = cellref.mRefNum; + } + + bool isIndexedRefId(const std::string& indexedRefId) + { + if (indexedRefId.size() <= 8) + return false; + + std::string index = indexedRefId.substr(indexedRefId.size()-8); + if(index.find_first_not_of("0123456789ABCDEF") == std::string::npos ) + return true; + return false; + } } namespace ESSImport @@ -114,8 +138,9 @@ namespace ESSImport ref.load (esm); if (esm.isNextSub("DELE")) { + // TODO // strangely this can be e.g. 52 instead of just 1, - std::cout << "deleted ref " << ref.mIndexedRefId << std::endl; + //std::cout << "deleted ref " << ref.mIndexedRefId << std::endl; esm.skipHSub(); } cellrefs.push_back(ref); @@ -153,83 +178,128 @@ namespace ESSImport newcell.mRefs = cellrefs; - // FIXME: map by ID for exterior cells - mCells[id] = newcell; + + if (cell.isExterior()) + mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; + else + mIntCells[id] = newcell; } - void ConvertCell::write(ESM::ESMWriter &esm) + void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) { - for (std::map::const_iterator it = mCells.begin(); it != mCells.end(); ++it) + ESM::Cell esmcell = cell.mCell; + esm.startRecord(ESM::REC_CSTA); + ESM::CellState csta; + csta.mHasFogOfWar = 0; + csta.mId = esmcell.getCellId(); + csta.mId.save(esm); + // TODO csta.mLastRespawn; + // shouldn't be needed if we respawn on global schedule like in original MW + csta.mWaterLevel = esmcell.mWater; + csta.save(esm); + + for (std::vector::const_iterator refIt = cell.mRefs.begin(); refIt != cell.mRefs.end(); ++refIt) { - const ESM::Cell& cell = it->second.mCell; - esm.startRecord(ESM::REC_CSTA); - ESM::CellState csta; - csta.mHasFogOfWar = 0; - csta.mId = cell.getCellId(); - csta.mId.save(esm); - // TODO csta.mLastRespawn; - // shouldn't be needed if we respawn on global schedule like in original MW - csta.mWaterLevel = cell.mWater; - csta.save(esm); - - for (std::vector::const_iterator refIt = it->second.mRefs.begin(); refIt != it->second.mRefs.end(); ++refIt) - { - const CellRef& cellref = *refIt; - ESM::CellRef out; - out.blank(); + const CellRef& cellref = *refIt; + ESM::CellRef out; + out.blank(); - if (cellref.mIndexedRefId.size() < 8) - { - std::cerr << "CellRef with no index?" << std::endl; - continue; - } + if (!isIndexedRefId(cellref.mIndexedRefId)) + { + // non-indexed RefNum, i.e. no CREC/NPCC/CNTC record associated with it + // this could be any type of object really (even creatures/npcs too) + out.mRefID = cellref.mIndexedRefId; + std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); + + ESM::ObjectState objstate; + objstate.blank(); + objstate.mRef = out; + objstate.mRef.mRefID = idLower; + objstate.mHasCustomState = false; + convertCellRef(cellref, objstate); + esm.writeHNT ("OBJE", 0); + objstate.save(esm); + continue; + } + else + { std::stringstream stream; - stream << cellref.mIndexedRefId.substr(cellref.mIndexedRefId.size()-8,8); + stream << std::hex << cellref.mIndexedRefId.substr(cellref.mIndexedRefId.size()-8,8); int refIndex; stream >> refIndex; out.mRefID = cellref.mIndexedRefId.substr(0,cellref.mIndexedRefId.size()-8); + std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); - std::map, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find( + std::map, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find( std::make_pair(refIndex, out.mRefID)); - if (crecIt != mContext->mCreatureChanges.end()) + if (npccIt != mContext->mNpcChanges.end()) { - ESM::CreatureState objstate; + ESM::NpcState objstate; objstate.blank(); + objstate.mRef = out; + objstate.mRef.mRefID = idLower; + // probably need more micromanagement here so we don't overwrite values + // from the ESM with default values convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats); - objstate.mEnabled = cellref.mEnabled; - objstate.mPosition = cellref.mPos; + convertNpcData(cellref.mActorData, objstate.mNpcStats); + convertNPCC(npccIt->second, objstate); + convertCellRef(cellref, objstate); + esm.writeHNT ("OBJE", ESM::REC_NPC_); + objstate.save(esm); + continue; + } + + std::map, CNTC>::const_iterator cntcIt = mContext->mContainerChanges.find( + std::make_pair(refIndex, out.mRefID)); + if (cntcIt != mContext->mContainerChanges.end()) + { + ESM::ContainerState objstate; + objstate.blank(); objstate.mRef = out; - objstate.mRef.mRefNum = cellref.mRefNum; - // FIXME: change save format to not require object type, instead look up it up using the RefId - esm.writeHNT ("OBJE", ESM::REC_CREA); + objstate.mRef.mRefID = idLower; + convertCNTC(cntcIt->second, objstate); + convertCellRef(cellref, objstate); + esm.writeHNT ("OBJE", ESM::REC_CONT); objstate.save(esm); continue; } - std::map, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find( + std::map, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find( std::make_pair(refIndex, out.mRefID)); - if (npccIt != mContext->mNpcChanges.end()) + if (crecIt != mContext->mCreatureChanges.end()) { - ESM::NpcState objstate; + ESM::CreatureState objstate; objstate.blank(); - convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats); - convertNpcData(cellref.mActorData, objstate.mNpcStats); - objstate.mEnabled = cellref.mEnabled; - objstate.mPosition = cellref.mPos; objstate.mRef = out; - objstate.mRef.mRefNum = cellref.mRefNum; - esm.writeHNT ("OBJE", ESM::REC_NPC_); + objstate.mRef.mRefID = idLower; + convertACDT(cellref.mActorData.mACDT, objstate.mCreatureStats); + // probably need more micromanagement here so we don't overwrite values + // from the ESM with default values + convertCREC(crecIt->second, objstate); + convertCellRef(cellref, objstate); + esm.writeHNT ("OBJE", ESM::REC_CREA); objstate.save(esm); continue; } - std::cerr << "Can't find type for " << refIndex << " " << out.mRefID << std::endl; + std::stringstream error; + error << "Can't find type for " << cellref.mIndexedRefId << std::endl; + throw std::runtime_error(error.str()); } - - esm.endRecord(ESM::REC_CSTA); } + esm.endRecord(ESM::REC_CSTA); + } + + void ConvertCell::write(ESM::ESMWriter &esm) + { + for (std::map::const_iterator it = mIntCells.begin(); it != mIntCells.end(); ++it) + writeCell(it->second, esm); + + for (std::map, Cell>::const_iterator it = mExtCells.begin(); it != mExtCells.end(); ++it) + writeCell(it->second, esm); + for (std::vector::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it) { esm.startRecord(ESM::REC_MARK); diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index aee60f2e7..b68f9a218 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -9,9 +9,12 @@ #include #include #include +#include +#include #include #include "importcrec.hpp" +#include "importcntc.hpp" #include "importercontext.hpp" #include "importcellref.hpp" @@ -94,7 +97,8 @@ public: mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel; mContext->mPlayerBase = npc; std::map empty; - // FIXME: not working? + // FIXME: player start spells, racial 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; } @@ -165,7 +169,10 @@ public: convertNPCC(npcc, mContext->mPlayer.mObject); } else - mContext->mNpcChanges.insert(std::make_pair(std::make_pair(npcc.mIndex,id), npcc)); + { + int index = npcc.mNPDT.mIndex; + mContext->mNpcChanges.insert(std::make_pair(std::make_pair(index,id), npcc)).second; + } } }; @@ -205,10 +212,30 @@ public: faction.mReputation = it->mReputation; mContext->mPlayer.mObject.mNpcStats.mFactions[it->mFactionName.toString()] = faction; } + for (int i=0; i<8; ++i) + mContext->mPlayer.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i]; + mContext->mPlayer.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress; + + for (std::vector::const_iterator it = pcdt.mKnownDialogueTopics.begin(); + it != pcdt.mKnownDialogueTopics.end(); ++it) + { + mContext->mDialogueState.mKnownTopics.push_back(Misc::StringUtils::lowerCase(*it)); + } } }; +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: @@ -217,7 +244,6 @@ public: std::string id = esm.getHNString("NAME"); CREC crec; crec.load(esm); - mContext->mCreatureChanges.insert(std::make_pair(std::make_pair(crec.mIndex,id), crec)); } }; @@ -242,9 +268,12 @@ private: std::vector mFogOfWar; }; - std::map mCells; + std::map mIntCells; + std::map, Cell> mExtCells; std::vector mMarkers; + + void writeCell(const Cell& cell, ESM::ESMWriter &esm); }; class ConvertKLST : public Converter @@ -274,6 +303,48 @@ private: std::map mKillCounter; }; +class ConvertFACT : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm) + { + std::string id = esm.getHNString("NAME"); + ESM::Faction faction; + faction.load(esm); + + 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); + 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"); + + while (esm.isNextSub("ONAM")) + { + std::string ownerid = esm.getHString(); + mStolenItems.insert(std::make_pair(itemid, ownerid)); + } + while (esm.isNextSub("FNAM")) + { + std::string factionid = esm.getHString(); + mFactionStolenItems.insert(std::make_pair(itemid, factionid)); + } + } +private: + std::multimap mStolenItems; + std::multimap mFactionStolenItems; +}; + } #endif diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp new file mode 100644 index 000000000..31272bf5a --- /dev/null +++ b/apps/essimporter/convertinventory.cpp @@ -0,0 +1,23 @@ +#include "convertinventory.hpp" + +#include + +namespace ESSImport +{ + + void convertInventory(const Inventory &inventory, ESM::InventoryState &state) + { + for (std::vector::const_iterator it = inventory.mItems.begin(); + it != inventory.mItems.end(); ++it) + { + ESM::ObjectState objstate; + objstate.blank(); + objstate.mRef = *it; + objstate.mRef.mRefID = Misc::StringUtils::lowerCase(it->mId); + objstate.mCount = std::abs(it->mCount); // restocking items have negative count in the savefile + // openmw handles them differently, so no need to set any flags + state.mItems.push_back(std::make_pair(objstate, it->mRelativeEquipmentSlot)); + } + } + +} diff --git a/apps/essimporter/convertinventory.hpp b/apps/essimporter/convertinventory.hpp new file mode 100644 index 000000000..8abe85a44 --- /dev/null +++ b/apps/essimporter/convertinventory.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTINVENTORY_H +#define OPENMW_ESSIMPORT_CONVERTINVENTORY_H + +#include "importinventory.hpp" + +#include + +namespace ESSImport +{ + + void convertInventory (const Inventory& inventory, ESM::InventoryState& state); + +} + +#endif diff --git a/apps/essimporter/convertnpcc.cpp b/apps/essimporter/convertnpcc.cpp index fdf96c15a..48d3d9232 100644 --- a/apps/essimporter/convertnpcc.cpp +++ b/apps/essimporter/convertnpcc.cpp @@ -1,10 +1,15 @@ #include "convertnpcc.hpp" +#include "convertinventory.hpp" + namespace ESSImport { void convertNPCC(const NPCC &npcc, ESM::NpcState &npcState) { + npcState.mNpcStats.mDisposition = npcc.mNPDT.mDisposition; npcState.mNpcStats.mReputation = npcc.mNPDT.mReputation; + + convertInventory(npcc.mInventory, npcState.mInventory); } } diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp index d961720a7..84c800897 100644 --- a/apps/essimporter/importacdt.cpp +++ b/apps/essimporter/importacdt.cpp @@ -22,7 +22,7 @@ namespace ESSImport ESM::CellRef bla; bla.ESM::CellRef::loadData(esm); - // FIXME: actually should be required for all actors?, but ActorData is currently in base CellRef + // FIXME: not all actors have this, add flag esm.getHNOT(mACDT, "ACDT"); ACSC acsc; @@ -76,6 +76,7 @@ namespace ESSImport esm.skipHSub(); // 4 byte, 0 } + // FIXME: not all actors have this, add flag if (esm.isNextSub("CHRD")) // npc only esm.getHExact(mSkills, 27*2*sizeof(int)); diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index 6efae9dc5..79f67d2c2 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -11,16 +11,31 @@ namespace ESM namespace ESSImport { + enum ACDTFlags + { + TalkedToPlayer = 0x4 + }; /// Actor data, shared by (at least) REFR and CellRef +#pragma pack(push) +#pragma pack(1) struct ACDT { - unsigned char mUnknown1[40]; + // Note, not stored at *all*: + // - Level changes are lost on reload, except for the player (there it's in the NPC record). + unsigned char mUnknown[12]; + unsigned char mFlags; // ACDTFlags + unsigned char mUnknown1[3]; + float mBreathMeter; // Seconds left before drowning + unsigned char mUnknown2[20]; float mDynamic[3][2]; - unsigned char mUnknown2[16]; + unsigned char mUnknown3[16]; float mAttributes[8][2]; - unsigned char mUnknown3[120]; + unsigned char mUnknown4[112]; + unsigned int mGoldPool; + unsigned char mUnknown5[4]; }; +#pragma pack(pop) struct ActorData { diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index 8c2b8f36d..fd58d5bc1 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -18,8 +18,13 @@ namespace ESSImport mIndexedRefId = esm.getHNString("NAME"); if (esm.isNextSub("LVCR")) - esm.skipHSub(); - + { + // occurs on leveled creature spawner references + // probably some identifier for the the creature that has been spawned? + unsigned char lvcr; + esm.getHT(lvcr); + //std::cout << "LVCR: " << (int)lvcr << std::endl; + } mActorData.load(esm); mEnabled = true; diff --git a/apps/essimporter/importcntc.cpp b/apps/essimporter/importcntc.cpp new file mode 100644 index 000000000..a492aef5a --- /dev/null +++ b/apps/essimporter/importcntc.cpp @@ -0,0 +1,16 @@ +#include "importcntc.hpp" + +#include + +namespace ESSImport +{ + + void CNTC::load(ESM::ESMReader &esm) + { + mIndex = 0; + esm.getHNT(mIndex, "INDX"); + + mInventory.load(esm); + } + +} diff --git a/apps/essimporter/importcntc.hpp b/apps/essimporter/importcntc.hpp new file mode 100644 index 000000000..1bc7d94bd --- /dev/null +++ b/apps/essimporter/importcntc.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTCNTC_H +#define OPENMW_ESSIMPORT_IMPORTCNTC_H + +#include "importinventory.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + /// Changed container contents + struct CNTC + { + int mIndex; + + Inventory mInventory; + + void load(ESM::ESMReader& esm); + }; + +} +#endif diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp index 4cf41a861..c2092ea4d 100644 --- a/apps/essimporter/importer.cpp +++ b/apps/essimporter/importer.cpp @@ -104,6 +104,11 @@ namespace ESSImport blacklist.insert(std::make_pair("GLOB", "FLTV")); // gamehour blacklist.insert(std::make_pair("REFR", "DATA")); // player position blacklist.insert(std::make_pair("CELL", "NAM8")); // fog of war + blacklist.insert(std::make_pair("GAME", "GMDT")); // weather data, current time always changes + + // this changes way too often, name suggests some renderer internal data? + blacklist.insert(std::make_pair("CELL", "ND3D")); + blacklist.insert(std::make_pair("REFR", "ND3D")); File file1; read(mEssFile, file1); @@ -181,6 +186,8 @@ namespace ESSImport Ogre::LogManager logman; Ogre::Root root; + // TODO: set up encoding on ESMReader based on openmw.cfg / --encoding switch + ESM::ESMReader esm; esm.open(mEssFile); @@ -193,6 +200,7 @@ namespace ESSImport const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value; const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value; const unsigned int recKLST = ESM::FourCC<'K','L','S','T'>::value; + const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value; std::map > converters; converters[ESM::REC_GLOB] = boost::shared_ptr(new ConvertGlobal()); @@ -200,10 +208,11 @@ namespace ESSImport converters[ESM::REC_NPC_] = boost::shared_ptr(new ConvertNPC()); converters[ESM::REC_NPCC] = boost::shared_ptr(new ConvertNPCC()); converters[ESM::REC_CREC] = boost::shared_ptr(new ConvertCREC()); - converters[recREFR] = boost::shared_ptr(new ConvertREFR()); - converters[recPCDT] = boost::shared_ptr(new ConvertPCDT()); - converters[recFMAP] = boost::shared_ptr(new ConvertFMAP()); - converters[recKLST] = boost::shared_ptr(new ConvertKLST()); + converters[recREFR ] = boost::shared_ptr(new ConvertREFR()); + converters[recPCDT ] = boost::shared_ptr(new ConvertPCDT()); + converters[recFMAP ] = boost::shared_ptr(new ConvertFMAP()); + converters[recKLST ] = boost::shared_ptr(new ConvertKLST()); + converters[recSTLN ] = boost::shared_ptr(new ConvertSTLN()); converters[ESM::REC_CELL] = boost::shared_ptr(new ConvertCell()); converters[ESM::REC_ALCH] = boost::shared_ptr(new DefaultConverter()); converters[ESM::REC_CLAS] = boost::shared_ptr(new ConvertClass()); @@ -215,6 +224,8 @@ namespace ESSImport converters[ESM::REC_WEAP] = boost::shared_ptr(new DefaultConverter()); converters[ESM::REC_LEVC] = boost::shared_ptr(new DefaultConverter()); converters[ESM::REC_LEVI] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_CNTC] = boost::shared_ptr(new ConvertCNTC()); + converters[ESM::REC_FACT] = boost::shared_ptr(new ConvertFACT()); std::set unknownRecords; @@ -321,6 +332,10 @@ namespace ESSImport } context.mPlayer.save(writer); writer.endRecord(ESM::REC_PLAY); + + writer.startRecord (ESM::REC_DIAS); + context.mDialogueState.save(writer); + writer.endRecord(ESM::REC_DIAS); } diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp index 9183e6179..02585799a 100644 --- a/apps/essimporter/importercontext.hpp +++ b/apps/essimporter/importercontext.hpp @@ -5,9 +5,11 @@ #include #include +#include #include "importnpcc.hpp" #include "importcrec.hpp" +#include "importcntc.hpp" #include "importplayer.hpp" @@ -24,12 +26,15 @@ namespace ESSImport ESM::NPC mPlayerBase; std::string mCustomPlayerClassName; + ESM::DialogueState mDialogueState; + int mDay, mMonth, mYear; float mHour; // key std::map, CREC> mCreatureChanges; std::map, NPCC> mNpcChanges; + std::map, CNTC> mContainerChanges; Context() { diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index d132fd76d..a20584ff5 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -1,7 +1,11 @@ #include "importinventory.hpp" +#include + #include +#include + namespace ESSImport { @@ -9,37 +13,66 @@ namespace ESSImport { while (esm.isNextSub("NPCO")) { - InventoryItem item; - item.mId = esm.getHString(); + ESM::ContItem contItem; + esm.getHT(contItem); - if (esm.isNextSub("XIDX")) - esm.skipHSub(); + InventoryItem item; + item.mId = contItem.mItem.toString(); + item.mCount = contItem.mCount; + item.mRelativeEquipmentSlot = -1; - std::string script = esm.getHNOString("SCRI"); - // script variables? - // unsure if before or after ESM::CellRef - if (!script.empty()) + // seems that a stack of items can have a set of subrecords for each item? rings0000.ess + // doesn't make any sense to me, if the values were different then the items shouldn't stack in the first place? + // I guess we should double check the stacking logic in OpenMW + for (int i=0;i= int(mItems.size())) + esm.fail("equipment item index out of range"); + + // appears to be a relative index for only the *possible* slots this item can be equipped in, + // i.e. 0 most of the time + int slotIndex; + esm.getT(slotIndex); + + mItems[itemIndex].mRelativeEquipmentSlot = slotIndex; } } diff --git a/apps/essimporter/importinventory.hpp b/apps/essimporter/importinventory.hpp index e31cab76a..99fed9b20 100644 --- a/apps/essimporter/importinventory.hpp +++ b/apps/essimporter/importinventory.hpp @@ -19,7 +19,8 @@ namespace ESSImport struct InventoryItem : public ESM::CellRef { std::string mId; - int mCondition; + int mCount; + int mRelativeEquipmentSlot; }; std::vector mItems; diff --git a/apps/essimporter/importnpcc.cpp b/apps/essimporter/importnpcc.cpp index 8400a9e4a..c389ede7f 100644 --- a/apps/essimporter/importnpcc.cpp +++ b/apps/essimporter/importnpcc.cpp @@ -7,9 +7,6 @@ namespace ESSImport void NPCC::load(ESM::ESMReader &esm) { - mIndex = 0; - esm.getHNOT(mIndex, "INDX"); - esm.getHNT(mNPDT, "NPDT"); if (esm.isNextSub("AI_E")) diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp index f3c4e24c7..c69fa3e03 100644 --- a/apps/essimporter/importnpcc.hpp +++ b/apps/essimporter/importnpcc.hpp @@ -19,15 +19,15 @@ namespace ESSImport { struct NPDT { - unsigned char unknown[2]; + unsigned char mDisposition; + unsigned char unknown; unsigned char mReputation; - unsigned char unknown2[5]; + unsigned char unknown2; + int mIndex; } mNPDT; Inventory mInventory; - int mIndex; - void load(ESM::ESMReader &esm); }; diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index 8a57ae588..285605068 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -20,12 +20,11 @@ namespace ESSImport { while (esm.isNextSub("DNAM")) { - // TODO: deal with encoding? mKnownDialogueTopics.push_back(esm.getHString()); } - if (esm.isNextSub("PNAM")) - esm.skipHSub(); + esm.getHNT(mPNAM, "PNAM"); + if (esm.isNextSub("SNAM")) esm.skipHSub(); if (esm.isNextSub("NAM9")) diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp index af8625fa6..64ceddfd7 100644 --- a/apps/essimporter/importplayer.hpp +++ b/apps/essimporter/importplayer.hpp @@ -38,6 +38,8 @@ struct PCDT std::vector mKnownDialogueTopics; +#pragma pack(push) +#pragma pack(1) struct FNAM { unsigned char mRank; @@ -47,7 +49,18 @@ struct PCDT unsigned char mUnknown2[3]; ESM::NAME32 mFactionName; }; + struct PNAM + { + unsigned char mUnknown1[4]; + unsigned char mLevelProgress; + unsigned char mUnknown2[111]; + unsigned char mSkillIncreases[8]; // number of skill increases for each attribute + unsigned char mUnknown3[88]; + }; +#pragma pack(pop) + std::vector mFactions; + PNAM mPNAM; void load(ESM::ESMReader& esm); }; diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index eba73cf0b..da14bb495 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1053,13 +1053,13 @@ namespace CSMWorld virtual QVariant get (const Record& record) const { - return record.get().mCharge; + return record.get().mChargeInt; } virtual void set (Record& record, const QVariant& data) { ESXRecordT record2 = record.get(); - record2.mCharge = data.toInt(); + record2.mChargeInt = data.toInt(); record.setModified (record2); } diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index d0e64b23c..d35b5950f 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -74,6 +74,8 @@ namespace MWBase /// Changes faction1's opinion of faction2 by \a diff. virtual void modFactionReaction (const std::string& faction1, const std::string& faction2, int diff) = 0; + virtual void setFactionReaction (const std::string& faction1, const std::string& faction2, int absolute) = 0; + /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index a3614af96..2e53fe802 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -816,6 +816,9 @@ namespace MWClass void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; + const ESM::CreatureState& state2 = dynamic_cast (state); ensureCustomData(ptr); @@ -844,7 +847,6 @@ namespace MWClass customData.mContainerStore->readState (state2.mInventory); customData.mCreatureStats.readState (state2.mCreatureStats); - } void Creature::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) @@ -852,6 +854,12 @@ namespace MWClass { ESM::CreatureState& state2 = dynamic_cast (state); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + ensureCustomData (ptr); CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 669b8187c..35a21b63e 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -2,7 +2,7 @@ #include "light.hpp" #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -26,27 +26,6 @@ #include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" -namespace -{ - struct LightCustomData : public MWWorld::CustomData - { - float mTime; - ///< Time remaining - - LightCustomData(MWWorld::Ptr ptr) - { - MWWorld::LiveCellRef *ref = ptr.get(); - mTime = ref->mBase->mData.mTime; - } - ///< Constructs this CustomData from the base values for Ptr. - - virtual MWWorld::CustomData *clone() const - { - return new LightCustomData (*this); - } - }; -} - namespace MWClass { std::string Light::getId (const MWWorld::Ptr& ptr) const @@ -219,17 +198,16 @@ namespace MWClass void Light::setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const { - ensureCustomData(ptr); - - float &timeRemaining = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; - timeRemaining = duration; + ptr.getCellRef().setChargeFloat(duration); } float Light::getRemainingUsageTime (const MWWorld::Ptr& ptr) const { - ensureCustomData(ptr); - - return dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + MWWorld::LiveCellRef *ref = ptr.get(); + if (ptr.getCellRef().getCharge() == -1) + return ref->mBase->mData.mTime; + else + return ptr.getCellRef().getChargeFloat(); } MWWorld::Ptr @@ -241,12 +219,6 @@ namespace MWClass return MWWorld::Ptr(&cell.get().insert(*ref), &cell); } - void Light::ensureCustomData (const MWWorld::Ptr& ptr) const - { - if (!ptr.getRefData().getCustomData()) - ptr.getRefData().setCustomData(new LightCustomData(ptr)); - } - bool Light::canSell (const MWWorld::Ptr& item, int npcServices) const { return npcServices & ESM::NPC::Lights; @@ -282,26 +254,6 @@ namespace MWClass return std::make_pair(1,""); } - void Light::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const - { - const ESM::LightState& state2 = dynamic_cast (state); - - ensureCustomData (ptr); - - dynamic_cast (*ptr.getRefData().getCustomData()).mTime = state2.mTime; - } - - void Light::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const - { - ESM::LightState& state2 = dynamic_cast (state); - - ensureCustomData (ptr); - - state2.mTime = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; - } - std::string Light::getSound(const MWWorld::Ptr& ptr) const { return ptr.get()->mBase->mSound; diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index bbca30113..8658375b7 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -10,8 +10,6 @@ namespace MWClass virtual MWWorld::Ptr copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; - void ensureCustomData (const MWWorld::Ptr& ptr) const; - public: /// Return ID of \a ptr @@ -75,14 +73,6 @@ namespace MWClass std::pair canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const; - virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) - const; - ///< Read additional state from \a state into \a ptr. - - virtual void writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const; - ///< Write additional state from \a ptr into \a state. - virtual std::string getSound(const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index ac4182734..8f62cc1e8 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1275,6 +1275,9 @@ namespace MWClass void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; + const ESM::NpcState& state2 = dynamic_cast (state); ensureCustomData(ptr); @@ -1302,6 +1305,12 @@ namespace MWClass { ESM::NpcState& state2 = dynamic_cast (state); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + ensureCustomData (ptr); NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index dd0fa21fa..0ee7dfe94 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -640,7 +640,7 @@ namespace MWDialogue if (iter->second) state.mKnownTopics.push_back (iter->first); - state.mModFactionReaction = mModFactionReaction; + state.mChangedFactionReaction = mChangedFactionReaction; writer.startRecord (ESM::REC_DIAS); state.save (writer); @@ -661,7 +661,7 @@ namespace MWDialogue if (store.get().search (*iter)) mKnownTopics.insert (std::make_pair (*iter, true)); - mModFactionReaction = state.mModFactionReaction; + mChangedFactionReaction = state.mChangedFactionReaction; } } @@ -674,10 +674,23 @@ namespace MWDialogue MWBase::Environment::get().getWorld()->getStore().get().find(fact1); MWBase::Environment::get().getWorld()->getStore().get().find(fact2); - std::map& map = mModFactionReaction[fact1]; - if (map.find(fact2) == map.end()) - map[fact2] = 0; - map[fact2] += diff; + int newValue = getFactionReaction(faction1, faction2) + diff; + + std::map& map = mChangedFactionReaction[fact1]; + map[fact2] = newValue; + } + + void DialogueManager::setFactionReaction(const std::string &faction1, const std::string &faction2, int absolute) + { + std::string fact1 = Misc::StringUtils::lowerCase(faction1); + std::string fact2 = Misc::StringUtils::lowerCase(faction2); + + // Make sure the factions exist + MWBase::Environment::get().getWorld()->getStore().get().find(fact1); + MWBase::Environment::get().getWorld()->getStore().get().find(fact2); + + std::map& map = mChangedFactionReaction[fact1]; + map[fact2] = absolute; } int DialogueManager::getFactionReaction(const std::string &faction1, const std::string &faction2) const @@ -685,10 +698,9 @@ namespace MWDialogue std::string fact1 = Misc::StringUtils::lowerCase(faction1); std::string fact2 = Misc::StringUtils::lowerCase(faction2); - ModFactionReactionMap::const_iterator map = mModFactionReaction.find(fact1); - int diff = 0; - if (map != mModFactionReaction.end() && map->second.find(fact2) != map->second.end()) - diff = map->second.at(fact2); + ModFactionReactionMap::const_iterator map = mChangedFactionReaction.find(fact1); + if (map != mChangedFactionReaction.end() && map->second.find(fact2) != map->second.end()) + return map->second.at(fact2); const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(fact1); @@ -696,9 +708,9 @@ namespace MWDialogue for (; it != faction->mReactions.end(); ++it) { if (Misc::StringUtils::ciEqual(it->first, fact2)) - return it->second + diff; + return it->second; } - return diff; + return 0; } void DialogueManager::clearInfoActor(const MWWorld::Ptr &actor) const diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 9c3d0d54b..c06299736 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -27,7 +27,7 @@ namespace MWDialogue // Modified faction reactions. > typedef std::map > ModFactionReactionMap; - ModFactionReactionMap mModFactionReaction; + ModFactionReactionMap mChangedFactionReaction; std::list mActorKnownTopics; @@ -97,6 +97,8 @@ namespace MWDialogue /// Changes faction1's opinion of faction2 by \a diff. virtual void modFactionReaction (const std::string& faction1, const std::string& faction2, int diff); + virtual void setFactionReaction (const std::string& faction1, const std::string& faction2, int absolute); + /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const; diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index 4594492e1..9e543bb79 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -22,27 +22,27 @@ namespace MWMechanics class NpcStats : public CreatureStats { - /// NPCs other than the player can only have one faction. But for the sake of consistency - /// we use the same data structure for the PC and the NPCs. - /// \note the faction key must be in lowercase - std::map mFactionRank; - int mDisposition; SkillValue mSkill[ESM::Skill::Length]; SkillValue mWerewolfSkill[ESM::Skill::Length]; - int mBounty; - std::set mExpelled; - std::map mFactionReputation; int mReputation; int mCrimeId; - int mWerewolfKills; + int mProfit; + // ----- used by the player only, maybe should be moved at some point ------- + int mBounty; + int mWerewolfKills; + /// NPCs other than the player can only have one faction. But for the sake of consistency + /// we use the same data structure for the PC and the NPCs. + /// \note the faction key must be in lowercase + std::map mFactionRank; + std::set mExpelled; + std::map mFactionReputation; int mLevelProgress; // 0-10 - std::vector mSkillIncreases; // number of skill increases for each attribute - std::set mUsedIds; + // --------------------------------------------------------------------------- /// Countdown to getting damage while underwater float mTimeToStartDrowning; diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 563a9dde3..1d82b8418 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -236,6 +236,25 @@ namespace MWScript } }; + class OpSetFactionReaction : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string faction1 = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + int newValue = runtime[0].mInteger; + runtime.pop(); + + MWBase::Environment::get().getDialogueManager()->setFactionReaction(faction1, faction2, newValue); + } + }; + template class OpClearInfoActor : public Interpreter::Opcode0 { @@ -268,6 +287,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFaction, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); + interpreter.installSegment5 (Compiler::Dialogue::opcodeSetFactionReaction, new OpSetFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActor, new OpClearInfoActor); interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActorExplicit, new OpClearInfoActor); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index 172e1b528..b139d6138 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -442,5 +442,6 @@ op 0x20002fb: AddToLevCreature op 0x20002fc: RemoveFromLevCreature op 0x20002fd: AddToLevItem op 0x20002fe: RemoveFromLevItem +op 0x20002ff: SetFactionReaction -opcodes 0x20002ff-0x3ffffff unused +opcodes 0x2000300-0x3ffffff unused diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index aa6627de5..744e0e27a 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -81,15 +81,29 @@ namespace MWWorld int CellRef::getCharge() const { - return mCellRef.mCharge; + return mCellRef.mChargeInt; } void CellRef::setCharge(int charge) { - if (charge != mCellRef.mCharge) + if (charge != mCellRef.mChargeInt) { mChanged = true; - mCellRef.mCharge = charge; + mCellRef.mChargeInt = charge; + } + } + + float CellRef::getChargeFloat() const + { + return mCellRef.mChargeFloat; + } + + void CellRef::setChargeFloat(float charge) + { + if (charge != mCellRef.mChargeFloat) + { + mChanged = true; + mCellRef.mChargeFloat = charge; } } diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index a7ffbe08b..054fd7fc6 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -60,8 +60,11 @@ namespace MWWorld // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. + // If this returns int(-1) it means full health. int getCharge() const; + float getChargeFloat() const; // Implemented as union with int charge void setCharge(int charge); + void setChargeFloat(float charge); // The NPC that owns this object (and will get angry if you steal it) std::string getOwner() const; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 1c0e7e385..545bbd4b3 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -86,7 +85,9 @@ namespace RecordType state; iter->save (state); + // recordId currently unused writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId); + state.save (writer); } } @@ -94,12 +95,13 @@ namespace template void readReferenceCollection (ESM::ESMReader& reader, - MWWorld::CellRefList& collection, const std::map& contentFileMap) + MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); RecordType state; - state.load (reader); + state.mRef = cref; + state.load(reader); // If the reference came from a content file, make sure this content file is loaded if (state.mRef.mRefNum.hasContentFile()) @@ -464,7 +466,7 @@ namespace MWWorld // List moved references, from separately tracked list. for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) { - ESM::CellRef &ref = const_cast(*it); + const ESM::CellRef &ref = *it; mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); } @@ -624,7 +626,7 @@ namespace MWWorld writeReferenceCollection (writer, mIngreds); writeReferenceCollection (writer, mCreatureLists); writeReferenceCollection (writer, mItemLists); - writeReferenceCollection (writer, mLights); + writeReferenceCollection (writer, mLights); writeReferenceCollection (writer, mLockpicks); writeReferenceCollection (writer, mMiscItems); writeReferenceCollection (writer, mNpcs); @@ -641,109 +643,121 @@ namespace MWWorld while (reader.isNextSub ("OBJE")) { - unsigned int id = 0; - reader.getHT (id); + unsigned int unused; + reader.getHT (unused); + + // load the RefID first so we know what type of object it is + ESM::CellRef cref; + cref.loadId(reader, true); + + int type = MWBase::Environment::get().getWorld()->getStore().find(cref.mRefID); + if (type == 0) + { + std::cerr << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)" << std::endl; + reader.skipHSubUntil("OBJE"); + continue; + } - switch (id) + switch (type) { case ESM::REC_ACTI: - readReferenceCollection (reader, mActivators, contentFileMap); + readReferenceCollection (reader, mActivators, cref, contentFileMap); break; case ESM::REC_ALCH: - readReferenceCollection (reader, mPotions, contentFileMap); + readReferenceCollection (reader, mPotions, cref, contentFileMap); break; case ESM::REC_APPA: - readReferenceCollection (reader, mAppas, contentFileMap); + readReferenceCollection (reader, mAppas, cref, contentFileMap); break; case ESM::REC_ARMO: - readReferenceCollection (reader, mArmors, contentFileMap); + readReferenceCollection (reader, mArmors, cref, contentFileMap); break; case ESM::REC_BOOK: - readReferenceCollection (reader, mBooks, contentFileMap); + readReferenceCollection (reader, mBooks, cref, contentFileMap); break; case ESM::REC_CLOT: - readReferenceCollection (reader, mClothes, contentFileMap); + readReferenceCollection (reader, mClothes, cref, contentFileMap); break; case ESM::REC_CONT: - readReferenceCollection (reader, mContainers, contentFileMap); + readReferenceCollection (reader, mContainers, cref, contentFileMap); break; case ESM::REC_CREA: - readReferenceCollection (reader, mCreatures, contentFileMap); + readReferenceCollection (reader, mCreatures, cref, contentFileMap); break; case ESM::REC_DOOR: - readReferenceCollection (reader, mDoors, contentFileMap); + readReferenceCollection (reader, mDoors, cref, contentFileMap); break; case ESM::REC_INGR: - readReferenceCollection (reader, mIngreds, contentFileMap); + readReferenceCollection (reader, mIngreds, cref, contentFileMap); break; case ESM::REC_LEVC: - readReferenceCollection (reader, mCreatureLists, contentFileMap); + readReferenceCollection (reader, mCreatureLists, cref, contentFileMap); break; case ESM::REC_LEVI: - readReferenceCollection (reader, mItemLists, contentFileMap); + readReferenceCollection (reader, mItemLists, cref, contentFileMap); break; case ESM::REC_LIGH: - readReferenceCollection (reader, mLights, contentFileMap); + readReferenceCollection (reader, mLights, cref, contentFileMap); break; case ESM::REC_LOCK: - readReferenceCollection (reader, mLockpicks, contentFileMap); + readReferenceCollection (reader, mLockpicks, cref, contentFileMap); break; case ESM::REC_MISC: - readReferenceCollection (reader, mMiscItems, contentFileMap); + readReferenceCollection (reader, mMiscItems, cref, contentFileMap); break; case ESM::REC_NPC_: - readReferenceCollection (reader, mNpcs, contentFileMap); + readReferenceCollection (reader, mNpcs, cref, contentFileMap); break; case ESM::REC_PROB: - readReferenceCollection (reader, mProbes, contentFileMap); + readReferenceCollection (reader, mProbes, cref, contentFileMap); break; case ESM::REC_REPA: - readReferenceCollection (reader, mRepairs, contentFileMap); + readReferenceCollection (reader, mRepairs, cref, contentFileMap); break; case ESM::REC_STAT: - readReferenceCollection (reader, mStatics, contentFileMap); + readReferenceCollection (reader, mStatics, cref, contentFileMap); break; case ESM::REC_WEAP: - readReferenceCollection (reader, mWeapons, contentFileMap); + readReferenceCollection (reader, mWeapons, cref, contentFileMap); break; default: diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index e9c968a07..5911b45f7 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -85,28 +85,29 @@ void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::Object ref.save (state); } +/// \todo make this method const once const-correct ContainerStoreIterators are available template -void MWWorld::ContainerStore::storeStates (const CellRefList& collection, - std::vector > >& states, bool equipable) const +void MWWorld::ContainerStore::storeStates (CellRefList& collection, + std::vector >& states, bool equipable) { - for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); + for (typename CellRefList::List::iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) { if (iter->mData.getCount() == 0) continue; ESM::ObjectState state; storeState (*iter, state); - int slot = equipable ? getSlot (*iter) : -1; - states.push_back (std::make_pair (state, std::make_pair (T::sRecordId, slot))); + int slot = equipable ? getRelativeSlot (MWWorld::ContainerStoreIterator(this, iter)) : -1; + states.push_back (std::make_pair (state, slot)); } } -int MWWorld::ContainerStore::getSlot (const MWWorld::LiveCellRefBase& ref) const +int MWWorld::ContainerStore::getRelativeSlot (const MWWorld::ContainerStoreIterator& iter) const { return -1; } -void MWWorld::ContainerStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot) {} +void MWWorld::ContainerStore::setRelativeSlot (const MWWorld::ContainerStoreIterator& iter, int slot) {} const std::string MWWorld::ContainerStore::sGoldId = "gold_001"; @@ -641,7 +642,7 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) return Ptr(); } -void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const +void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) { state.mItems.clear(); @@ -656,55 +657,45 @@ void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const storeStates (probes, state.mItems, true); storeStates (repairs, state.mItems); storeStates (weapons, state.mItems, true); - - state.mLights.clear(); + storeStates (lights, state.mItems, true); state.mLevelledItemMap = mLevelledItemMap; - - for (MWWorld::CellRefList::List::const_iterator iter (lights.mList.begin()); - iter!=lights.mList.end(); ++iter) - { - ESM::LightState objectState; - storeState (*iter, objectState); - state.mLights.push_back (std::make_pair (objectState, getSlot (*iter))); - } } void MWWorld::ContainerStore::readState (const ESM::InventoryState& state) { clear(); - for (std::vector > >::const_iterator + for (std::vector >::const_iterator iter (state.mItems.begin()); iter!=state.mItems.end(); ++iter) { - int slot = iter->second.second; + int slot = iter->second; + + const ESM::ObjectState& state = iter->first; - switch (iter->second.first) + int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID); + + switch (type) { case ESM::REC_ALCH: getState (potions, iter->first); break; case ESM::REC_APPA: getState (appas, iter->first); break; - case ESM::REC_ARMO: setSlot (getState (armors, iter->first), slot); break; + case ESM::REC_ARMO: setRelativeSlot (getState (armors, iter->first), slot); break; case ESM::REC_BOOK: getState (books, iter->first); break; - case ESM::REC_CLOT: setSlot (getState (clothes, iter->first), slot); break; + case ESM::REC_CLOT: setRelativeSlot (getState (clothes, iter->first), slot); break; case ESM::REC_INGR: getState (ingreds, iter->first); break; - case ESM::REC_LOCK: setSlot (getState (lockpicks, iter->first), slot); break; + case ESM::REC_LOCK: setRelativeSlot (getState (lockpicks, iter->first), slot); break; case ESM::REC_MISC: getState (miscItems, iter->first); break; - case ESM::REC_PROB: setSlot (getState (probes, iter->first), slot); break; + case ESM::REC_PROB: setRelativeSlot (getState (probes, iter->first), slot); break; case ESM::REC_REPA: getState (repairs, iter->first); break; - case ESM::REC_WEAP: setSlot (getState (weapons, iter->first), slot); break; + case ESM::REC_WEAP: setRelativeSlot (getState (weapons, iter->first), slot); break; + case ESM::REC_LIGH: setRelativeSlot (getState (lights, iter->first), slot); break; default: - - std::cerr << "invalid item type in inventory state" << std::endl; + std::cerr << "invalid item type in inventory state, refid " << state.mRef.mRefID << std::endl; + break; } } - for (std::vector >::const_iterator iter (state.mLights.begin()); - iter!=state.mLights.end(); ++iter) - { - int slot = iter->second; - setSlot (getState (lights, iter->first), slot); - } mLevelledItemMap = state.mLevelledItemMap; } diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 2849463c9..4a95606f7 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -84,15 +84,16 @@ namespace MWWorld template void storeState (const LiveCellRef& ref, ESM::ObjectState& state) const; + /// \todo make this method const once const-correct ContainerStoreIterators are available template - void storeStates (const CellRefList& collection, - std::vector > >& states, - bool equipable = false) const; + void storeStates (CellRefList& collection, + std::vector >& states, + bool equipable = false); - virtual int getSlot (const MWWorld::LiveCellRefBase& ref) const; + virtual int getRelativeSlot (const MWWorld::ContainerStoreIterator& iter) const; ///< Return inventory slot that \a ref is in or -1 (if \a ref is not in a slot). - virtual void setSlot (const MWWorld::ContainerStoreIterator& iter, int slot); + virtual void setRelativeSlot (const MWWorld::ContainerStoreIterator& iter, int slot); ///< Set slot for \a iter. Ignored if \a iter is an end iterator or if slot==-1. public: @@ -171,7 +172,8 @@ namespace MWWorld Ptr search (const std::string& id); - virtual void writeState (ESM::InventoryState& state) const; + /// \todo make this method const once const-correct ContainerStoreIterators are available + virtual void writeState (ESM::InventoryState& state); virtual void readState (const ESM::InventoryState& state); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 445b42d8d..8f272ca49 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -49,19 +49,40 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots_) slots_.push_back (end()); } -int MWWorld::InventoryStore::getSlot (const MWWorld::LiveCellRefBase& ref) const +int MWWorld::InventoryStore::getRelativeSlot (const MWWorld::ContainerStoreIterator& iter) { for (int i = 0; i (mSlots.size()); ++i) - if (mSlots[i].getType()!=-1 && mSlots[i]->getBase()==&ref) - return i; + if (mSlots[i].getType()!=-1 && mSlots[i] == iter) + { + // linear complexity, but allowedSlots is most of the time just 1 anyway + std::vector allowedSlots = iter->getClass().getEquipmentSlots(*iter).first; + std::vector::iterator found = std::find(allowedSlots.begin(),allowedSlots.end(),i); + if (found == allowedSlots.end()) + return -1; + else + return std::distance(allowedSlots.begin(), found); + } return -1; } -void MWWorld::InventoryStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot) +void MWWorld::InventoryStore::setRelativeSlot (const MWWorld::ContainerStoreIterator& iter, int relativeSlot) { - if (iter!=end() && slot>=0 && slot, bool> allowedSlots = iter->getClass().getEquipmentSlots(*iter); + relativeSlot = std::min(int(allowedSlots.first.size()-1), relativeSlot); + + // unstack if required + if (!allowedSlots.second && iter->getRefData().getCount() > 1) + { + MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, 1); + iter->getRefData().setCount(iter->getRefData().getCount()-1); + mSlots[allowedSlots.first[relativeSlot]] = newIter; + } + else + mSlots[allowedSlots.first[relativeSlot]] = iter; } MWWorld::InventoryStore::InventoryStore() @@ -703,7 +724,7 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::Ptr &item) return false; } -void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const +void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) { MWWorld::ContainerStore::writeState(state); diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 9fd18c54b..ae2ced2b8 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -113,10 +113,10 @@ namespace MWWorld void fireEquipmentChangedEvent(); - virtual int getSlot (const MWWorld::LiveCellRefBase& ref) const; + virtual int getRelativeSlot (const MWWorld::ContainerStoreIterator& iter); ///< Return inventory slot that \a ref is in or -1 (if \a ref is not in a slot). - virtual void setSlot (const MWWorld::ContainerStoreIterator& iter, int slot); + virtual void setRelativeSlot (const MWWorld::ContainerStoreIterator& iter, int relativeSlot); ///< Set slot for \a iter. Ignored if \a iter is an end iterator or if slot==-1. public: @@ -209,7 +209,7 @@ namespace MWWorld virtual void clear(); ///< Empty container. - virtual void writeState (ESM::InventoryState& state) const; + virtual void writeState (ESM::InventoryState& state); virtual void readState (const ESM::InventoryState& state); }; diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 4eb93543b..f32b07471 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -28,7 +28,7 @@ namespace MWWorld cellRef.mRefID = name; cellRef.mScale = 1; cellRef.mFactionRank = 0; - cellRef.mCharge = -1; + cellRef.mChargeInt = -1; cellRef.mGoldValue = 1; cellRef.mEnchantmentCharge = -1; cellRef.mTeleport = false; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 104605e65..f7d5d9616 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2031,7 +2031,7 @@ namespace MWWorld bool World::isOnGround(const MWWorld::Ptr &ptr) const { RefData &refdata = ptr.getRefData(); - const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); + OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); if(!physactor) return false; @@ -2049,7 +2049,7 @@ namespace MWWorld mPhysEngine); if(tracer.mFraction < 1.0f) // collision, must be close to something below { - const_cast (physactor)->setOnGround(true); + physactor->setOnGround(true); return true; } else diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 0578a44b0..a0500127c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -57,11 +57,11 @@ add_component_dir (to_utf8 add_component_dir (esm attr defs esmcommon esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell - loadclas loadclot loadcont loadcrea loadcrec loaddial loaddoor loadench loadfact loadglob loadgmst + loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc loadnpcc loadnpc loadpgrd loadrace loadregn loadscpt loadskil loadsndg loadsoun loadspel loadsscr loadstat loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter - savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate + savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects util custommarkerstate ) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 234c5b12d..a5cc0da62 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -192,6 +192,7 @@ namespace Compiler extensions.registerFunction("samefaction", 'l', "", opcodeSameFaction, opcodeSameFactionExplicit); extensions.registerInstruction("modfactionreaction", "ccl", opcodeModFactionReaction); + extensions.registerInstruction("setfactionreaction", "ccl", opcodeSetFactionReaction); extensions.registerFunction("getfactionreaction", 'l', "ccX", opcodeGetFactionReaction); extensions.registerInstruction("clearinfoactor", "", opcodeClearInfoActor, opcodeClearInfoActorExplicit); } diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index bbafd6b13..04666e976 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -167,6 +167,7 @@ namespace Compiler const int opcodeSameFaction = 0x20001b5; const int opcodeSameFactionExplicit = 0x20001b6; const int opcodeModFactionReaction = 0x2000242; + const int opcodeSetFactionReaction = 0x20002ff; const int opcodeGetFactionReaction = 0x2000243; const int opcodeClearInfoActor = 0x2000245; const int opcodeClearInfoActorExplicit = 0x2000246; diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 43b1f4d29..dfc3052ee 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -5,6 +5,12 @@ #include "esmwriter.hpp" void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) +{ + loadId(esm, wideRefNum); + loadData(esm); +} + +void ESM::CellRef::loadId(ESMReader &esm, bool wideRefNum) { // According to Hrnchamd, this does not belong to the actual ref. Instead, it is a marker indicating that // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. @@ -19,8 +25,6 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) esm.getHNT (mRefNum.mIndex, "FRMR"); mRefID = esm.getHNString ("NAME"); - - loadData(esm); } void ESM::CellRef::loadData(ESMReader &esm) @@ -42,12 +46,12 @@ void ESM::CellRef::loadData(ESMReader &esm) esm.getHNOT (mFactionRank, "INDX"); mGoldValue = 1; - mCharge = -1; + mChargeInt = -1; mEnchantmentCharge = -1; esm.getHNOT (mEnchantmentCharge, "XCHG"); - esm.getHNOT (mCharge, "INTV"); + esm.getHNOT (mChargeInt, "INTV"); esm.getHNOT (mGoldValue, "NAM9"); @@ -102,8 +106,8 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (mEnchantmentCharge != -1) esm.writeHNT("XCHG", mEnchantmentCharge); - if (mCharge != -1) - esm.writeHNT("INTV", mCharge); + if (mChargeInt != -1) + esm.writeHNT("INTV", mChargeInt); if (mGoldValue != 1) { esm.writeHNT("NAM9", mGoldValue); @@ -142,8 +146,8 @@ void ESM::CellRef::blank() mSoul.clear(); mFaction.clear(); mFactionRank = -2; - mCharge = 0; - mEnchantmentCharge = 0; + mChargeInt = -1; + mEnchantmentCharge = -1; mGoldValue = 0; mDestCell.clear(); mLockLevel = 0; diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index b7b00a654..56932aa4d 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -59,7 +59,11 @@ namespace ESM // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. - int mCharge; + union + { + int mChargeInt; + float mChargeFloat; + }; // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float mEnchantmentCharge; @@ -89,8 +93,11 @@ namespace ESM // Position and rotation of this object within the cell Position mPos; + /// Calls loadId and loadData void load (ESMReader& esm, bool wideRefNum = false); + void loadId (ESMReader& esm, bool wideRefNum = false); + /// Implicitly called by load void loadData (ESMReader& esm); diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp index c2838f78d..c15becd98 100644 --- a/components/esm/creaturestate.cpp +++ b/components/esm/creaturestate.cpp @@ -5,18 +5,24 @@ void ESM::CreatureState::load (ESMReader &esm) { ObjectState::load (esm); - mInventory.load (esm); + if (mHasCustomState) + { + mInventory.load (esm); - mCreatureStats.load (esm); + mCreatureStats.load (esm); + } } void ESM::CreatureState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); - mInventory.save (esm); + if (mHasCustomState) + { + mInventory.save (esm); - mCreatureStats.save (esm); + mCreatureStats.save (esm); + } } void ESM::CreatureState::blank() diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index a1ef7eb0e..cac5cc0a6 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -230,7 +230,7 @@ void ESM::CreatureStats::blank() mTradeTime.mHour = 0; mTradeTime.mDay = 0; mGoldPool = 0; - mActorId = 0; + mActorId = -1; mHasAiSettings = false; mDead = false; mDied = false; diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp index 14301ac19..f546462a3 100644 --- a/components/esm/dialoguestate.cpp +++ b/components/esm/dialoguestate.cpp @@ -13,13 +13,20 @@ void ESM::DialogueState::load (ESMReader &esm) { std::string faction = esm.getHString(); - while (esm.isNextSub ("REAC")) + while (esm.isNextSub("REA2")) { std::string faction2 = esm.getHString(); int reaction; esm.getHNT(reaction, "INTV"); + mChangedFactionReaction[faction][faction2] = reaction; + } - mModFactionReaction[faction][faction2] = reaction; + // no longer used + while (esm.isNextSub ("REAC")) + { + esm.skipHSub(); + esm.getSubHeader(); + esm.skipHSub(); } } } @@ -32,15 +39,15 @@ void ESM::DialogueState::save (ESMWriter &esm) const esm.writeHNString ("TOPI", *iter); } - for (std::map >::const_iterator iter = mModFactionReaction.begin(); - iter != mModFactionReaction.end(); ++iter) + for (std::map >::const_iterator iter = mChangedFactionReaction.begin(); + iter != mChangedFactionReaction.end(); ++iter) { esm.writeHNString ("FACT", iter->first); for (std::map::const_iterator reactIter = iter->second.begin(); reactIter != iter->second.end(); ++reactIter) { - esm.writeHNString ("REAC", reactIter->first); + esm.writeHNString ("REA2", reactIter->first); esm.writeHNT ("INTV", reactIter->second); } } diff --git a/components/esm/dialoguestate.hpp b/components/esm/dialoguestate.hpp index 5e5f602a3..d7cdb941c 100644 --- a/components/esm/dialoguestate.hpp +++ b/components/esm/dialoguestate.hpp @@ -14,9 +14,11 @@ namespace ESM struct DialogueState { + // must be lower case topic IDs std::vector mKnownTopics; - std::map > mModFactionReaction; + // must be lower case faction IDs + std::map > mChangedFactionReaction; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 7cf0de1a9..3c43d067f 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -134,7 +134,11 @@ void ESMReader::getHExact(void*p, int size) { getSubHeader(); if (size != static_cast (mCtx.leftSub)) - fail("getHExact() size mismatch"); + { + std::stringstream error; + error << "getHExact(): size mismatch (requested " << size << ", got " << mCtx.leftSub << ")"; + fail(error.str()); + } getExact(p, size); } @@ -210,6 +214,17 @@ void ESMReader::skipHSubSize(int size) fail("skipHSubSize() mismatch"); } +void ESMReader::skipHSubUntil(const char *name) +{ + while (hasMoreSubs() && !isNextSub(name)) + { + mCtx.subCached = false; + skipHSub(); + } + if (hasMoreSubs()) + mCtx.subCached = true; +} + void ESMReader::getSubHeader() { if (mCtx.leftRec < 4) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 642ec4168..e7721bb8f 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -137,7 +137,11 @@ public: { getSubHeader(); if (mCtx.leftSub != sizeof(X)) - fail("getHT(): subrecord size mismatch"); + { + std::stringstream error; + error << "getHT(): subrecord size mismatch (requested " << sizeof(X) << ", got " << mCtx.leftSub << ")"; + fail(error.str()); + } getT(x); } @@ -195,6 +199,9 @@ public: // Skip sub record and check its size void skipHSubSize(int size); + // Skip all subrecords until the given subrecord or no more subrecords remaining + void skipHSubUntil(const char* name); + /* Sub-record header. This updates leftRec beyond the current sub-record as well. leftSub contains size of current sub-record. */ diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index a2d49b144..b946f9501 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -11,12 +11,14 @@ namespace slot = -1; esm.getHNOT (slot, "SLOT"); + state.mRef.loadId(esm, true); state.load (esm); } - void write (ESM::ESMWriter &esm, const ESM::ObjectState& state, unsigned int type, int slot) + void write (ESM::ESMWriter &esm, const ESM::ObjectState& state, int slot) { - esm.writeHNT ("IOBJ", type); + int unused = 0; + esm.writeHNT ("IOBJ", unused); if (slot!=-1) esm.writeHNT ("SLOT", slot); @@ -29,27 +31,15 @@ void ESM::InventoryState::load (ESMReader &esm) { while (esm.isNextSub ("IOBJ")) { - unsigned int id = 0; - esm.getHT (id); - - if (id==ESM::REC_LIGH) - { - LightState state; - int slot; - read (esm, state, slot); - if (state.mCount == 0) - continue; - mLights.push_back (std::make_pair (state, slot)); - } - else - { - ObjectState state; - int slot; - read (esm, state, slot); - if (state.mCount == 0) - continue; - mItems.push_back (std::make_pair (state, std::make_pair (id, slot))); - } + int unused; // no longer used + esm.getHT(unused); + + ObjectState state; + int slot; + read (esm, state, slot); + if (state.mCount == 0) + continue; + mItems.push_back (std::make_pair (state, slot)); } while (esm.isNextSub("LEVM")) @@ -78,12 +68,8 @@ void ESM::InventoryState::load (ESMReader &esm) void ESM::InventoryState::save (ESMWriter &esm) const { - for (std::vector > >::const_iterator iter (mItems.begin()); iter!=mItems.end(); ++iter) - write (esm, iter->first, iter->second.first, iter->second.second); - - for (std::vector >::const_iterator iter (mLights.begin()); - iter!=mLights.end(); ++iter) - write (esm, iter->first, ESM::REC_LIGH, iter->second); + for (std::vector >::const_iterator iter (mItems.begin()); iter!=mItems.end(); ++iter) + write (esm, iter->first, iter->second); for (std::map::const_iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) { diff --git a/components/esm/inventorystate.hpp b/components/esm/inventorystate.hpp index bd0b46a22..13af28e30 100644 --- a/components/esm/inventorystate.hpp +++ b/components/esm/inventorystate.hpp @@ -4,7 +4,6 @@ #include #include "objectstate.hpp" -#include "lightstate.hpp" namespace ESM { @@ -16,11 +15,8 @@ namespace ESM /// \brief State for inventories and containers struct InventoryState { - // anything but lights (type, slot) - std::vector > > mItems; - - // lights (slot) - std::vector > mLights; + /// + std::vector > mItems; std::map mLevelledItemMap; diff --git a/components/esm/lightstate.cpp b/components/esm/lightstate.cpp deleted file mode 100644 index 1ef040823..000000000 --- a/components/esm/lightstate.cpp +++ /dev/null @@ -1,21 +0,0 @@ - -#include "lightstate.hpp" - -#include "esmreader.hpp" -#include "esmwriter.hpp" - -void ESM::LightState::load (ESMReader &esm) -{ - ObjectState::load (esm); - - mTime = 0; - esm.getHNOT (mTime, "LTIM"); -} - -void ESM::LightState::save (ESMWriter &esm, bool inInventory) const -{ - ObjectState::save (esm, inInventory); - - if (mTime) - esm.writeHNT ("LTIM", mTime); -} \ No newline at end of file diff --git a/components/esm/lightstate.hpp b/components/esm/lightstate.hpp deleted file mode 100644 index a22735e07..000000000 --- a/components/esm/lightstate.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef OPENMW_ESM_LIGHTSTATE_H -#define OPENMW_ESM_LIGHTSTATE_H - -#include "objectstate.hpp" - -namespace ESM -{ - // format 0, saved games only - - struct LightState : public ObjectState - { - float mTime; - - virtual void load (ESMReader &esm); - virtual void save (ESMWriter &esm, bool inInventory = false) const; - }; -} - -#endif diff --git a/components/esm/loadcrec.hpp b/components/esm/loadcrec.hpp deleted file mode 100644 index 280739aca..000000000 --- a/components/esm/loadcrec.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef OPENMW_ESM_CREC_H -#define OPENMW_ESM_CREC_H - -#include - -// TODO create implementation files and remove this one -#include "esmreader.hpp" - -namespace ESM { - -class ESMReader; -class ESMWriter; - -/* These two are only used in save games. They are not decoded yet. - */ - -/// Changes a creature -struct LoadCREC -{ - static unsigned int sRecordId; - - std::string mId; - - void load(ESMReader &esm) - { - esm.skipRecord(); - } - - void save(ESMWriter &esm) const - { - } -}; - -/// Changes an item list / container -struct LoadCNTC -{ - std::string mId; - - void load(ESMReader &esm) - { - esm.skipRecord(); - } - - void save(ESMWriter &esm) const - { - } -}; -} -#endif diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp index 6134193c2..724d67326 100644 --- a/components/esm/npcstate.cpp +++ b/components/esm/npcstate.cpp @@ -5,22 +5,28 @@ void ESM::NpcState::load (ESMReader &esm) { ObjectState::load (esm); - mInventory.load (esm); + if (mHasCustomState) + { + mInventory.load (esm); - mNpcStats.load (esm); + mNpcStats.load (esm); - mCreatureStats.load (esm); + mCreatureStats.load (esm); + } } void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); - mInventory.save (esm); + if (mHasCustomState) + { + mInventory.save (esm); - mNpcStats.save (esm); + mNpcStats.save (esm); - mCreatureStats.save (esm); + mCreatureStats.save (esm); + } } void ESM::NpcState::blank() @@ -28,4 +34,5 @@ void ESM::NpcState::blank() ObjectState::blank(); mNpcStats.blank(); mCreatureStats.blank(); + mHasCustomState = true; } diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index f7755f8cb..9ef1ccf80 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -6,7 +6,7 @@ void ESM::ObjectState::load (ESMReader &esm) { - mRef.load (esm, true); + mRef.loadData(esm); mHasLocals = 0; esm.getHNOT (mHasLocals, "HLOC"); @@ -23,6 +23,14 @@ void ESM::ObjectState::load (ESMReader &esm) esm.getHNOT (mPosition, "POS_", 24); esm.getHNOT (mLocalRotation, "LROT", 12); + + // obsolete + int unused; + esm.getHNOT(unused, "LTIM"); + + // FIXME: assuming "false" as default would make more sense, but also break compatibility with older save files + mHasCustomState = true; + esm.getHNOT (mHasCustomState, "HCUS"); } void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const @@ -46,6 +54,9 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const esm.writeHNT ("POS_", mPosition, 24); esm.writeHNT ("LROT", mLocalRotation, 12); } + + if (!mHasCustomState) + esm.writeHNT ("HCUS", false); } void ESM::ObjectState::blank() @@ -60,6 +71,7 @@ void ESM::ObjectState::blank() mPosition.rot[i] = 0; mLocalRotation[i] = 0; } + mHasCustomState = true; } ESM::ObjectState::~ObjectState() {} diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index 5b05e0949..d1077733a 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -26,7 +26,15 @@ namespace ESM ESM::Position mPosition; float mLocalRotation[3]; + // Is there any class-specific state following the ObjectState + bool mHasCustomState; + + ObjectState() : mHasCustomState(true) + {} + + /// @note Does not load the CellRef ID, it should already be loaded before calling this method virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; /// Initialize to default state diff --git a/components/esm/player.cpp b/components/esm/player.cpp index 52b44c945..70c4b79e2 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -6,6 +6,7 @@ void ESM::Player::load (ESMReader &esm) { + mObject.mRef.loadId(esm, true); mObject.load (esm); mCellId.load (esm); diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 7a0452eb3..c01c89d57 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -14,7 +14,6 @@ #include "loadclot.hpp" #include "loadcont.hpp" #include "loadcrea.hpp" -#include "loadcrec.hpp" #include "loadinfo.hpp" #include "loaddial.hpp" #include "loaddoor.hpp" diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 3c76cc3b4..a66193f97 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -402,8 +402,8 @@ namespace ESMTerrain int endX = startX + 1; int endY = startY + 1; - assert(endX < ESM::Land::LAND_SIZE); - assert(endY < ESM::Land::LAND_SIZE); + endX = std::min(endX, ESM::Land::LAND_SIZE-1); + endY = std::min(endY, ESM::Land::LAND_SIZE-1); // now get points in terrain space (effectively rounding them to boundaries) float startXTS = startX * invFactor;