From 7227a83e60153cd05e9d4ef299a4f0ddd8d3d0c7 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 25 Jul 2021 19:53:41 +1000 Subject: [PATCH 001/137] Preserve "blocked" record flags when saving with OpenCS. This will help outputs of OpenCS to be used with vanilla Morrowind. Sample use case: users are using the Morrowind Code Patch feature that allows modders to enable this flag to differentiate editor-made potions from player crafted potions for tooltips. --- apps/opencs/model/doc/savingstages.hpp | 2 +- components/esm/debugprofile.cpp | 1 + components/esm/debugprofile.hpp | 1 + components/esm/filter.cpp | 1 + components/esm/filter.hpp | 1 + components/esm/loadbody.cpp | 1 + components/esm/loadbody.hpp | 1 + components/esm/loadbsgn.cpp | 1 + components/esm/loadbsgn.hpp | 1 + components/esm/loadclas.cpp | 1 + components/esm/loadclas.hpp | 1 + components/esm/loadench.cpp | 1 + components/esm/loadench.hpp | 1 + components/esm/loadfact.cpp | 1 + components/esm/loadfact.hpp | 1 + components/esm/loadglob.cpp | 1 + components/esm/loadglob.hpp | 1 + components/esm/loadgmst.cpp | 1 + components/esm/loadgmst.hpp | 1 + components/esm/loadmgef.cpp | 1 + components/esm/loadmgef.hpp | 1 + components/esm/loadrace.cpp | 1 + components/esm/loadrace.hpp | 1 + components/esm/loadregn.cpp | 1 + components/esm/loadregn.hpp | 1 + components/esm/loadscpt.cpp | 1 + components/esm/loadscpt.hpp | 1 + components/esm/loadskil.cpp | 1 + components/esm/loadskil.hpp | 1 + components/esm/loadsndg.cpp | 1 + components/esm/loadsndg.hpp | 1 + components/esm/loadsoun.cpp | 1 + components/esm/loadsoun.hpp | 1 + components/esm/loadspel.cpp | 1 + components/esm/loadspel.hpp | 1 + components/esm/loadsscr.cpp | 1 + components/esm/loadsscr.hpp | 1 + 37 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/doc/savingstages.hpp b/apps/opencs/model/doc/savingstages.hpp index d59a1efe5e..9ccd1772e1 100644 --- a/apps/opencs/model/doc/savingstages.hpp +++ b/apps/opencs/model/doc/savingstages.hpp @@ -108,7 +108,7 @@ namespace CSMDoc state == CSMWorld::RecordBase::State_ModifiedOnly || state == CSMWorld::RecordBase::State_Deleted) { - writer.startRecord (record.sRecordId); + writer.startRecord (record.sRecordId, record.mRecordFlags); record.save (writer, state == CSMWorld::RecordBase::State_Deleted); writer.endRecord (record.sRecordId); } diff --git a/components/esm/debugprofile.cpp b/components/esm/debugprofile.cpp index 1dcf661fc9..6276258c48 100644 --- a/components/esm/debugprofile.cpp +++ b/components/esm/debugprofile.cpp @@ -9,6 +9,7 @@ unsigned int ESM::DebugProfile::sRecordId = REC_DBGP; void ESM::DebugProfile::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); while (esm.hasMoreSubs()) { diff --git a/components/esm/debugprofile.hpp b/components/esm/debugprofile.hpp index c056750a88..8340404c23 100644 --- a/components/esm/debugprofile.hpp +++ b/components/esm/debugprofile.hpp @@ -19,6 +19,7 @@ namespace ESM Flag_Global = 4 // make available from main menu (i.e. not location specific) }; + unsigned int mRecordFlags; std::string mId; std::string mDescription; diff --git a/components/esm/filter.cpp b/components/esm/filter.cpp index 5bae1ed03e..8d1b755055 100644 --- a/components/esm/filter.cpp +++ b/components/esm/filter.cpp @@ -9,6 +9,7 @@ unsigned int ESM::Filter::sRecordId = REC_FILT; void ESM::Filter::load (ESMReader& esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); while (esm.hasMoreSubs()) { diff --git a/components/esm/filter.hpp b/components/esm/filter.hpp index b1c511ebba..78d51cec00 100644 --- a/components/esm/filter.hpp +++ b/components/esm/filter.hpp @@ -12,6 +12,7 @@ namespace ESM { static unsigned int sRecordId; + unsigned int mRecordFlags; std::string mId; std::string mDescription; diff --git a/components/esm/loadbody.cpp b/components/esm/loadbody.cpp index 4ddefc92c7..239cff7c8b 100644 --- a/components/esm/loadbody.cpp +++ b/components/esm/loadbody.cpp @@ -11,6 +11,7 @@ namespace ESM void BodyPart::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; diff --git a/components/esm/loadbody.hpp b/components/esm/loadbody.hpp index bf320330ff..1be775ffec 100644 --- a/components/esm/loadbody.hpp +++ b/components/esm/loadbody.hpp @@ -58,6 +58,7 @@ struct BodyPart }; BYDTstruct mData; + unsigned int mRecordFlags; std::string mId, mModel, mRace; void load(ESMReader &esm, bool &isDeleted); diff --git a/components/esm/loadbsgn.cpp b/components/esm/loadbsgn.cpp index 1f679af39a..7514f1f85b 100644 --- a/components/esm/loadbsgn.cpp +++ b/components/esm/loadbsgn.cpp @@ -11,6 +11,7 @@ namespace ESM void BirthSign::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mPowers.mList.clear(); diff --git a/components/esm/loadbsgn.hpp b/components/esm/loadbsgn.hpp index 24d27a7f85..806323bf35 100644 --- a/components/esm/loadbsgn.hpp +++ b/components/esm/loadbsgn.hpp @@ -17,6 +17,7 @@ struct BirthSign /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "BirthSign"; } + unsigned int mRecordFlags; std::string mId, mName, mDescription, mTexture; // List of powers and abilities that come with this birth sign. diff --git a/components/esm/loadclas.cpp b/components/esm/loadclas.cpp index b76fc57067..7526fe4f52 100644 --- a/components/esm/loadclas.cpp +++ b/components/esm/loadclas.cpp @@ -41,6 +41,7 @@ namespace ESM void Class::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; diff --git a/components/esm/loadclas.hpp b/components/esm/loadclas.hpp index 833dd6757d..1000879c4c 100644 --- a/components/esm/loadclas.hpp +++ b/components/esm/loadclas.hpp @@ -70,6 +70,7 @@ struct Class ///< Throws an exception for invalid values of \a index. }; // 60 bytes + unsigned int mRecordFlags; std::string mId, mName, mDescription; CLDTstruct mData; diff --git a/components/esm/loadench.cpp b/components/esm/loadench.cpp index 3c1a2f1eda..ed3de90b50 100644 --- a/components/esm/loadench.cpp +++ b/components/esm/loadench.cpp @@ -11,6 +11,7 @@ namespace ESM void Enchantment::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mEffects.mList.clear(); bool hasName = false; diff --git a/components/esm/loadench.hpp b/components/esm/loadench.hpp index b98549ef35..a4e1e8362c 100644 --- a/components/esm/loadench.hpp +++ b/components/esm/loadench.hpp @@ -42,6 +42,7 @@ struct Enchantment int mFlags; }; + unsigned int mRecordFlags; std::string mId; ENDTstruct mData; EffectList mEffects; diff --git a/components/esm/loadfact.cpp b/components/esm/loadfact.cpp index bd0962721b..b71348de44 100644 --- a/components/esm/loadfact.cpp +++ b/components/esm/loadfact.cpp @@ -29,6 +29,7 @@ namespace ESM void Faction::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mReactions.clear(); for (int i=0;i<10;++i) diff --git a/components/esm/loadfact.hpp b/components/esm/loadfact.hpp index 098ed43096..6a42377901 100644 --- a/components/esm/loadfact.hpp +++ b/components/esm/loadfact.hpp @@ -34,6 +34,7 @@ struct Faction /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Faction"; } + unsigned int mRecordFlags; std::string mId, mName; struct FADTstruct diff --git a/components/esm/loadglob.cpp b/components/esm/loadglob.cpp index 72ecce503c..d2226d1738 100644 --- a/components/esm/loadglob.cpp +++ b/components/esm/loadglob.cpp @@ -11,6 +11,7 @@ namespace ESM void Global::load (ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mId = esm.getHNString ("NAME"); diff --git a/components/esm/loadglob.hpp b/components/esm/loadglob.hpp index 0533cc95ea..9dd58e6c67 100644 --- a/components/esm/loadglob.hpp +++ b/components/esm/loadglob.hpp @@ -21,6 +21,7 @@ struct Global /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Global"; } + unsigned int mRecordFlags; std::string mId; Variant mValue; diff --git a/components/esm/loadgmst.cpp b/components/esm/loadgmst.cpp index da8d256e7d..6d4ac1b202 100644 --- a/components/esm/loadgmst.cpp +++ b/components/esm/loadgmst.cpp @@ -11,6 +11,7 @@ namespace ESM void GameSetting::load (ESMReader &esm, bool &isDeleted) { isDeleted = false; // GameSetting record can't be deleted now (may be changed in the future) + mRecordFlags = esm.getRecordFlags(); mId = esm.getHNString("NAME"); mValue.read (esm, ESM::Variant::Format_Gmst); diff --git a/components/esm/loadgmst.hpp b/components/esm/loadgmst.hpp index c40d348fe4..931ee286a4 100644 --- a/components/esm/loadgmst.hpp +++ b/components/esm/loadgmst.hpp @@ -22,6 +22,7 @@ struct GameSetting /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "GameSetting"; } + unsigned int mRecordFlags; std::string mId; Variant mValue; diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 75a94f828a..4bc09920e9 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -189,6 +189,7 @@ namespace ESM void MagicEffect::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) + mRecordFlags = esm.getRecordFlags(); esm.getHNT(mIndex, "INDX"); diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index d718aaccf5..480478d81e 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -16,6 +16,7 @@ struct MagicEffect /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "MagicEffect"; } + unsigned int mRecordFlags; std::string mId; enum Flags diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index ce3cc95bf8..44dbde7742 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -21,6 +21,7 @@ namespace ESM void Race::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mPowers.mList.clear(); diff --git a/components/esm/loadrace.hpp b/components/esm/loadrace.hpp index d014744472..50fa73ad74 100644 --- a/components/esm/loadrace.hpp +++ b/components/esm/loadrace.hpp @@ -65,6 +65,7 @@ struct Race RADTstruct mData; + unsigned int mRecordFlags; std::string mId, mName, mDescription; SpellList mPowers; diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index 91ea92e305..e39887a1b1 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -11,6 +11,7 @@ namespace ESM void Region::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; while (esm.hasMoreSubs()) diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 6f39dc0bff..74f6b123ec 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -45,6 +45,7 @@ struct Region WEATstruct mData; int mMapColor; // RGBA + unsigned int mRecordFlags; // sleepList refers to a leveled list of creatures you can meet if // you sleep outside in this region. std::string mId, mName, mSleepList; diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 19602fef62..8715c83dcb 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -83,6 +83,7 @@ namespace ESM void Script::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mVarNames.clear(); diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index e1ffe1b864..d518a048ff 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -35,6 +35,7 @@ public: Script::SCHDstruct mData; }; + unsigned int mRecordFlags; std::string mId; SCHDstruct mData; diff --git a/components/esm/loadskil.cpp b/components/esm/loadskil.cpp index 61cca7d0d7..9f58176f35 100644 --- a/components/esm/loadskil.cpp +++ b/components/esm/loadskil.cpp @@ -130,6 +130,7 @@ namespace ESM void Skill::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; // Skill record can't be deleted now (may be changed in the future) + mRecordFlags = esm.getRecordFlags(); bool hasIndex = false; bool hasData = false; diff --git a/components/esm/loadskil.hpp b/components/esm/loadskil.hpp index 099264fab7..ae44a51045 100644 --- a/components/esm/loadskil.hpp +++ b/components/esm/loadskil.hpp @@ -22,6 +22,7 @@ struct Skill /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string getRecordType() { return "Skill"; } + unsigned int mRecordFlags; std::string mId; struct SKDTstruct diff --git a/components/esm/loadsndg.cpp b/components/esm/loadsndg.cpp index c6ea930a20..c439d0ca66 100644 --- a/components/esm/loadsndg.cpp +++ b/components/esm/loadsndg.cpp @@ -11,6 +11,7 @@ namespace ESM void SoundGenerator::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; diff --git a/components/esm/loadsndg.hpp b/components/esm/loadsndg.hpp index 70b221e98c..99aae06e0e 100644 --- a/components/esm/loadsndg.hpp +++ b/components/esm/loadsndg.hpp @@ -34,6 +34,7 @@ struct SoundGenerator // Type int mType; + unsigned int mRecordFlags; std::string mId, mCreature, mSound; void load(ESMReader &esm, bool &isDeleted); diff --git a/components/esm/loadsoun.cpp b/components/esm/loadsoun.cpp index ccb5f6fdce..ed8fc519a6 100644 --- a/components/esm/loadsoun.cpp +++ b/components/esm/loadsoun.cpp @@ -11,6 +11,7 @@ namespace ESM void Sound::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasName = false; bool hasData = false; diff --git a/components/esm/loadsoun.hpp b/components/esm/loadsoun.hpp index 937e22be88..14f1178650 100644 --- a/components/esm/loadsoun.hpp +++ b/components/esm/loadsoun.hpp @@ -21,6 +21,7 @@ struct Sound static std::string getRecordType() { return "Sound"; } SOUNstruct mData; + unsigned int mRecordFlags; std::string mId, mSound; void load(ESMReader &esm, bool &isDeleted); diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 34e146501c..5983cdcdf5 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -11,6 +11,7 @@ namespace ESM void Spell::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); mEffects.mList.clear(); diff --git a/components/esm/loadspel.hpp b/components/esm/loadspel.hpp index 1763d0991c..ef74c2c312 100644 --- a/components/esm/loadspel.hpp +++ b/components/esm/loadspel.hpp @@ -42,6 +42,7 @@ struct Spell }; SPDTstruct mData; + unsigned int mRecordFlags; std::string mId, mName; EffectList mEffects; diff --git a/components/esm/loadsscr.cpp b/components/esm/loadsscr.cpp index f436c32a1c..9e060ab1a7 100644 --- a/components/esm/loadsscr.cpp +++ b/components/esm/loadsscr.cpp @@ -11,6 +11,7 @@ namespace ESM void StartScript::load(ESMReader &esm, bool &isDeleted) { isDeleted = false; + mRecordFlags = esm.getRecordFlags(); bool hasData = false; bool hasName = false; diff --git a/components/esm/loadsscr.hpp b/components/esm/loadsscr.hpp index ce2ff49e77..3e84027076 100644 --- a/components/esm/loadsscr.hpp +++ b/components/esm/loadsscr.hpp @@ -24,6 +24,7 @@ struct StartScript static std::string getRecordType() { return "StartScript"; } std::string mData; + unsigned int mRecordFlags; std::string mId; // Load a record and add it to the list From 7264a10c072a9abdb9674de275848d5fd8b5109d Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 28 Aug 2021 09:49:45 +1000 Subject: [PATCH 002/137] Partially revert commit dab1a9e7fbf8c6926405dd09c64e6a309140f92a --- apps/opencs/model/doc/savingstages.cpp | 3 +- apps/opencs/model/world/commanddispatcher.cpp | 73 +++++-------------- 2 files changed, 21 insertions(+), 55 deletions(-) diff --git a/apps/opencs/model/doc/savingstages.cpp b/apps/opencs/model/doc/savingstages.cpp index 9da2c2c144..a410d34b2a 100644 --- a/apps/opencs/model/doc/savingstages.cpp +++ b/apps/opencs/model/doc/savingstages.cpp @@ -285,8 +285,7 @@ void CSMDoc::WriteCellCollectionStage::writeReferences (const std::deque& r { refRecord.mRefNum.mIndex = newRefNum++; } - - if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) + else if ((refRecord.mOriginalCell.empty() ? refRecord.mCell : refRecord.mOriginalCell) != stream.str() && !interior) { // An empty mOriginalCell is meant to indicate that it is the same as diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 3da3e3d22c..36b3ba2e00 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -3,9 +3,6 @@ #include #include -#include -#include - #include #include @@ -142,31 +139,13 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (mLocked) return; - std::unique_ptr clonedData; - std::unique_ptr deleteData; - - std::string newId; - - std::unique_ptr modifyData; std::unique_ptr modifyCell; - QAbstractItemModel* sourceModel = model; - if (IdTableProxyModel* proxy = dynamic_cast (model)) - sourceModel = proxy->sourceModel(); - - CSMWorld::IdTable& table = dynamic_cast(*sourceModel); // for getId() - int stateColumn = table.findColumnIndex(Columns::ColumnId_Modification); - QModelIndex stateIndex = table.getModelIndex(table.getId(index.row()), stateColumn); - RecordBase::State state = static_cast (sourceModel->data(stateIndex).toInt()); + std::unique_ptr modifyDataRefNum; int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); - // This is not guaranteed to be the same as \a model, since a proxy could be used. - IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel(mId)); - - // DeleteCommand triggers a signal to the whole row from IdTable::setData(), so ignore the second call - if (state != RecordBase::State_Deleted && - (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos)) + if (columnId==Columns::ColumnId_PositionXPos || columnId==Columns::ColumnId_PositionYPos) { const float oldPosition = model->data (index).toFloat(); @@ -178,9 +157,12 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons int row = proxy ? proxy->mapToSource (index).row() : index.row(); + // This is not guaranteed to be the same as \a model, since a proxy could be used. + IdTable& model2 = dynamic_cast (*mDocument.getData().getTableModel (mId)); + int cellColumn = model2.searchColumnIndex (Columns::ColumnId_Cell); - if (cellColumn != -1) + if (cellColumn!=-1) { QModelIndex cellIndex = model2.index (row, cellColumn); @@ -188,46 +170,31 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (cellId.find ('#')!=std::string::npos) { - RefCollection& collection = mDocument.getData().getReferences(); - newId = collection.getNewId(); + // Need to recalculate the cell and (if necessary) clear the instance's refNum + modifyCell.reset (new UpdateCellCommand (model2, row)); - clonedData.reset(new CloneCommand(table, - table.getId(row), - newId, - CSMWorld::UniversalId::Type_Reference)); + // Not sure which model this should be applied to + int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum); - deleteData.reset(new DeleteCommand(table, - table.getId(row), - CSMWorld::UniversalId::Type_Reference)); + if (refNumColumn!=-1) + modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0)); } } } } - if (!clonedData.get()) - { - // DeleteCommand will trigger executeModify after setting the state to State_Deleted - // from CommandDelegate::setModelDataImp() - ignore - if (state != RecordBase::State_Deleted) - modifyData.reset(new CSMWorld::ModifyCommand(*model, index, new_)); - } + std::unique_ptr modifyData ( + new CSMWorld::ModifyCommand (*model, index, new_)); - if (clonedData.get()) + if (modifyCell.get()) { CommandMacro macro (mDocument.getUndoStack()); - macro.push(clonedData.release()); - macro.push(deleteData.release()); - - // cannot do these earlier because newIndex is not available until CloneCommand is executed - QModelIndex newIndex = model2.getModelIndex (newId, index.column()); - modifyData.reset (new CSMWorld::ModifyCommand (*model, newIndex, new_)); - macro.push(modifyData.release()); - - // once the data is updated update the cell location - modifyCell.reset(new UpdateCellCommand(model2, newIndex.row())); - macro.push(modifyCell.release()); + macro.push (modifyData.release()); + macro.push (modifyCell.release()); + if (modifyDataRefNum.get()) + macro.push (modifyDataRefNum.release()); } - else if (!clonedData.get() && modifyData.get()) + else mDocument.getUndoStack().push (modifyData.release()); } From b2bd97f283884cc446fa8fbb28df327b3a66b261 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 28 Aug 2021 14:09:55 +1000 Subject: [PATCH 003/137] A different implementation to MR 1051 to fix Issue #6067. This MR doesn't delete the original record, but instead the original record is updated with the moved record when loading. Issue #4752 is no longer addressed in this MR. --- CHANGELOG.md | 1 - apps/opencs/model/world/refcollection.cpp | 38 ++++++++++++++++++++++- apps/opencs/model/world/refcollection.hpp | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17337f7346..296f7d8d52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,6 @@ Bug #4203: Resurrecting an actor should close the loot GUI Bug #4700: Editor: Incorrect command implementation Bug #4744: Invisible particles must still be processed - Bug #4752: UpdateCellCommand doesn't undo properly Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system Bug #5379: Wandering NPCs falling through cantons diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 4782bde6b9..f70e9c9fa2 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -89,12 +89,48 @@ void CSMWorld::RefCollection::load (ESM::ESMReader& reader, int cellIndex, bool else ref.mCell = cell2.mId; + if (ref.mRefNum.mContentFile != -1 && !base) + { + ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; + ref.mRefNum.mIndex &= 0x00ffffff; + } + unsigned int refNum = (ref.mRefNum.mIndex & 0x00ffffff) | (ref.mRefNum.hasContentFile() ? ref.mRefNum.mContentFile : 0xff) << 24; std::map::iterator iter = cache.find(refNum); - if (ref.mRefNum.mContentFile != -1 && !base) ref.mRefNum.mContentFile = ref.mRefNum.mIndex >> 24; + if (isMoved) + { + if (iter == cache.end()) + { + CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Cell, + mCells.getId(cellIndex)); + + messages.add(id, "Attempt to move a non-existent reference - RefNum index " + + std::to_string(ref.mRefNum.mIndex) + ", refID " + ref.mRefID + ", content file index " + + std::to_string(ref.mRefNum.mContentFile), + /*hint*/"", + CSMDoc::Message::Severity_Warning); + continue; + } + + int index = getIntIndex(iter->second); + + // ensure we have the same record id for setRecord() + ref.mId = getRecord(index).get().mId; + ref.mIdNum = extractIdNum(ref.mId); + + std::unique_ptr > record(new Record); + // TODO: check whether a base record be moved + record->mState = base ? RecordBase::State_BaseOnly : RecordBase::State_ModifiedOnly; + (base ? record->mBase : record->mModified) = std::move(ref); + + // overwrite original record + setRecord(index, std::move(record)); + + continue; // NOTE: assumed moved references are not deleted at the same time + } if (isDeleted) { diff --git a/apps/opencs/model/world/refcollection.hpp b/apps/opencs/model/world/refcollection.hpp index e0e88d721f..2031d2be63 100644 --- a/apps/opencs/model/world/refcollection.hpp +++ b/apps/opencs/model/world/refcollection.hpp @@ -25,7 +25,7 @@ namespace CSMWorld class RefCollection : public Collection { Collection& mCells; - std::map mRefIndex; + std::map mRefIndex; // CellRef index keyed by CSMWorld::CellRef::mIdNum int mNextId; From 2eb210f31a29689393aeda67c1dc913ba31e499b Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 28 Aug 2021 14:33:47 +1000 Subject: [PATCH 004/137] Partially undo commit 71be4cdd20657b616c772e1f074f157c65e24c45 so that moved references retain the original refnum. This is consistent with vanilla CS's behaviour. --- apps/opencs/model/world/commanddispatcher.cpp | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/apps/opencs/model/world/commanddispatcher.cpp b/apps/opencs/model/world/commanddispatcher.cpp index 36b3ba2e00..c5d78b6586 100644 --- a/apps/opencs/model/world/commanddispatcher.cpp +++ b/apps/opencs/model/world/commanddispatcher.cpp @@ -141,7 +141,6 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons std::unique_ptr modifyCell; - std::unique_ptr modifyDataRefNum; int columnId = model->data (index, ColumnBase::Role_ColumnId).toInt(); @@ -170,14 +169,8 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons if (cellId.find ('#')!=std::string::npos) { - // Need to recalculate the cell and (if necessary) clear the instance's refNum + // Need to recalculate the cell modifyCell.reset (new UpdateCellCommand (model2, row)); - - // Not sure which model this should be applied to - int refNumColumn = model2.searchColumnIndex (Columns::ColumnId_RefNum); - - if (refNumColumn!=-1) - modifyDataRefNum.reset (new ModifyCommand(*model, model->index(row, refNumColumn), 0)); } } } @@ -191,8 +184,6 @@ void CSMWorld::CommandDispatcher::executeModify (QAbstractItemModel *model, cons CommandMacro macro (mDocument.getUndoStack()); macro.push (modifyData.release()); macro.push (modifyCell.release()); - if (modifyDataRefNum.get()) - macro.push (modifyDataRefNum.release()); } else mDocument.getUndoStack().push (modifyData.release()); From b0d5ca386d043297aa6a1786b2a8610d34ab7cc9 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sat, 28 Aug 2021 16:11:47 +1000 Subject: [PATCH 005/137] Update original cell column and do not modify the refnum when moving a reference to another cell with 3D editing. --- apps/opencs/view/render/object.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index d0e4dbe04a..789fad0587 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -682,18 +682,20 @@ void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) int cellColumn = collection.findColumnIndex (static_cast ( CSMWorld::Columns::ColumnId_Cell)); - int refNumColumn = collection.findColumnIndex (static_cast ( - CSMWorld::Columns::ColumnId_RefNum)); + int origCellColumn = collection.findColumnIndex(static_cast ( + CSMWorld::Columns::ColumnId_OriginalCell)); if (cellIndex != originalIndex) { /// \todo figure out worldspace (not important until multiple worldspaces are supported) + std::string origCellId = CSMWorld::CellCoordinates(originalIndex).getId(""); std::string cellId = CSMWorld::CellCoordinates (cellIndex).getId (""); commands.push (new CSMWorld::ModifyCommand (*model, - model->index (recordIndex, cellColumn), QString::fromUtf8 (cellId.c_str()))); - commands.push (new CSMWorld::ModifyCommand( *model, - model->index (recordIndex, refNumColumn), 0)); + model->index (recordIndex, origCellColumn), QString::fromUtf8 (origCellId.c_str()))); + commands.push(new CSMWorld::ModifyCommand(*model, + model->index(recordIndex, cellColumn), QString::fromUtf8(cellId.c_str()))); + // NOTE: refnum is not modified for moving a reference to another cell } } From 0a5571f19ee88a6f2c3e4905dd46af59433771ba Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 29 Aug 2021 15:27:59 +1000 Subject: [PATCH 006/137] Disable editing for blocked records in both table and dialogue edit widget. --- apps/opencs/model/world/columns.cpp | 1 + apps/opencs/model/world/columns.hpp | 1 + apps/opencs/model/world/idtable.cpp | 8 +++++++ apps/opencs/model/world/refidadapterimp.hpp | 25 +++++++++++++++++++++ apps/opencs/model/world/refidcollection.cpp | 7 ++++-- apps/opencs/view/world/dialoguesubview.cpp | 16 ++++++++++--- 6 files changed, 53 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index cf04d96753..fc39fa8f7d 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -371,6 +371,7 @@ namespace CSMWorld { ColumnId_Skill7, "Skill 7" }, { ColumnId_Persistent, "Persistent" }, + { ColumnId_Blocked, "Blocked" }, { -1, 0 } // end marker }; diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 16482d4898..8cf02b46ac 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -344,6 +344,7 @@ namespace CSMWorld ColumnId_FactionAttrib2 = 312, ColumnId_Persistent = 313, + ColumnId_Blocked = 314, // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 6d3882c015..5b4a9b31bc 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -123,6 +123,14 @@ Qt::ItemFlags CSMWorld::IdTable::flags (const QModelIndex & index) const if (mIdCollection->getColumn (index.column()).isUserEditable()) flags |= Qt::ItemIsEditable; + int blockedColumn = searchColumnIndex(Columns::ColumnId_Blocked); + if (blockedColumn != -1 && blockedColumn != index.column()) + { + bool isBlocked = mIdCollection->getData(index.row(), blockedColumn).toInt(); + if (isBlocked) + flags = Qt::ItemIsSelectable; // not enabled (to grey out) + } + return flags; } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 1d1b5a94a6..84fec5bed0 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -25,6 +25,9 @@ namespace CSMWorld const RefIdColumn *mId; const RefIdColumn *mModified; const RefIdColumn *mType; + const RefIdColumn *mBlocked; + + BaseColumns () : mBlocked(nullptr) {} }; /// \brief Base adapter for all refereceable record types @@ -90,6 +93,9 @@ namespace CSMWorld if (column==mBase.mType) return static_cast (mType); + if (column==mBase.mBlocked) + return (record.get().mRecordFlags & ESM::FLAG_Blocked) != 0; + return QVariant(); } @@ -102,6 +108,17 @@ namespace CSMWorld if (column==mBase.mModified) record.mState = static_cast (value.toInt()); + else if (column==mBase.mBlocked) + { + RecordT record2 = record.get(); + + if (value.toInt() != 0) + record2.mRecordFlags |= ESM::FLAG_Blocked; + else + record2.mRecordFlags &= ~ESM::FLAG_Blocked; + + record.setModified(record2); + } } template @@ -110,6 +127,14 @@ namespace CSMWorld return mType; } + // NOTE: Body Part should not have persistence (but BodyPart is not listed in the Objects + // table at the moment). + // + // Spellmaking - not persistent - currently not part of objects table + // Enchanting - not persistent - currently not part of objects table + // + // Leveled Creature - no model, so not persistent + // Leveled Item - no model, so not persistent struct ModelColumns : public BaseColumns { diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index 177844a31d..6ffb7969a8 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -49,13 +49,16 @@ CSMWorld::RefIdCollection::RefIdCollection() mColumns.emplace_back(Columns::ColumnId_RecordType, ColumnBase::Display_RefRecordType, ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue, false, false); baseColumns.mType = &mColumns.back(); + mColumns.emplace_back(Columns::ColumnId_Blocked, ColumnBase::Display_Boolean, + ColumnBase::Flag_Table | ColumnBase::Flag_Dialogue | ColumnBase::Flag_Dialogue_Refresh); + baseColumns.mBlocked = &mColumns.back(); ModelColumns modelColumns (baseColumns); - mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh); - modelColumns.mModel = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Persistent, ColumnBase::Display_Boolean); modelColumns.mPersistence = &mColumns.back(); + mColumns.emplace_back(Columns::ColumnId_Model, ColumnBase::Display_Mesh); + modelColumns.mModel = &mColumns.back(); NameColumns nameColumns (modelColumns); diff --git a/apps/opencs/view/world/dialoguesubview.cpp b/apps/opencs/view/world/dialoguesubview.cpp index f2360b1378..152472f504 100644 --- a/apps/opencs/view/world/dialoguesubview.cpp +++ b/apps/opencs/view/world/dialoguesubview.cpp @@ -538,6 +538,9 @@ void CSVWorld::EditWidget::remake(int row) mainLayout->addLayout(tablesLayout, QSizePolicy::Preferred); mainLayout->addStretch(1); + int blockedColumn = mTable->searchColumnIndex(CSMWorld::Columns::ColumnId_Blocked); + bool isBlocked = mTable->data(mTable->index(row, blockedColumn)).toInt(); + int unlocked = 0; int locked = 0; const int columns = mTable->columnCount(); @@ -583,6 +586,8 @@ void CSVWorld::EditWidget::remake(int row) NestedTable* table = new NestedTable(mDocument, id, mNestedModels.back(), this, editable, fixedRows); table->resizeColumnsToContents(); + if (isBlocked) + table->setEditTriggers(QAbstractItemView::NoEditTriggers); int rows = mTable->rowCount(mTable->index(row, i)); int rowHeight = (rows == 0) ? table->horizontalHeader()->height() : table->rowHeight(0); @@ -617,7 +622,9 @@ void CSVWorld::EditWidget::remake(int row) label->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); editor->setSizePolicy (QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); - if (! (mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable)) + // HACK: the blocked checkbox needs to keep the same position + // FIXME: unfortunately blocked record displays a little differently to unblocked one + if (!(mTable->flags (mTable->index (row, i)) & Qt::ItemIsEditable) || i == blockedColumn) { lockedLayout->addWidget (label, locked, 0); lockedLayout->addWidget (editor, locked, 1); @@ -639,7 +646,7 @@ void CSVWorld::EditWidget::remake(int row) createEditorContextMenu(editor, display, row); } } - else + else // Flag_Dialogue_List { CSMWorld::IdTree *tree = static_cast(mTable); mNestedTableMapper = new QDataWidgetMapper (this); @@ -686,7 +693,10 @@ void CSVWorld::EditWidget::remake(int row) label->setEnabled(false); } - createEditorContextMenu(editor, display, row); + if (!isBlocked) + createEditorContextMenu(editor, display, row); + else + editor->setEnabled(false); } } mNestedTableMapper->setCurrentModelIndex(tree->index(0, 0, tree->index(row, i))); From 0ada3fa1d85ec4f3c4d0bbdf5a503555dde8409f Mon Sep 17 00:00:00 2001 From: cc9cii Date: Sun, 29 Aug 2021 15:32:45 +1000 Subject: [PATCH 007/137] Update changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17337f7346..ef07fe4eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Feature #6032: Reverse-z depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering + Editor: Preserve the "blocked" record flag for referenceable objects. 0.47.0 ------ From 0bce6c09e1a74e1cd77a8602300201b571af3ee1 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 31 Aug 2021 16:25:45 +0200 Subject: [PATCH 008/137] Change projectile behaviour to be like in vanilla wrt. water plane: - enchanted arrow explode upon hit the water plane - non enchanted arrow disappear (or more accurately, they hit nothingness) - enchanted arrow shot underwater explode immediately - non enchanted arrow disappear immediately Also, solve a bug that occured previously and could theoritically still happens where we use the last tested collision position for instead of the last registered hit: Use the hit position as saved inside Projectile::hit() instead of the last position saved inside the callback. If a projectile collides with several objects (bottom of the sea and water surface for instance), the last collision tested won't necessarily be the impact position as we have no control over the order in which the tests are performed. --- apps/openmw/mwphysics/physicssystem.cpp | 6 ++--- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/projectile.cpp | 24 ++----------------- apps/openmw/mwphysics/projectile.hpp | 23 +++++++++++------- .../mwphysics/projectileconvexcallback.cpp | 4 +--- apps/openmw/mwworld/projectilemanager.cpp | 13 +++++----- apps/openmw/mwworld/worldimp.cpp | 1 + 7 files changed, 29 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5b50962be9..e9ab3864fd 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -643,7 +643,7 @@ namespace MWPhysics mTaskScheduler->convexSweepTest(projectile->getConvexShape(), from_, to_, resultCallback); - const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(resultCallback.m_hitPointWorld); + const auto newpos = projectile->isActive() ? position : Misc::Convert::toOsg(projectile->getHitPosition()); projectile->setPosition(newpos); mTaskScheduler->updateSingleAabb(foundProjectile->second); } @@ -713,7 +713,7 @@ namespace MWPhysics mActors.emplace(ptr, std::move(actor)); } - int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater) + int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius) { osg::ref_ptr shapeInstance = mShapeManager->getInstance(mesh); assert(shapeInstance); @@ -721,7 +721,7 @@ namespace MWPhysics mProjectileId++; - auto projectile = std::make_shared(caster, position, radius, canTraverseWater, mTaskScheduler.get(), this); + auto projectile = std::make_shared(caster, position, radius, mTaskScheduler.get(), this); mProjectiles.emplace(mProjectileId, std::move(projectile)); return mProjectileId; diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index b20c8f88e1..14e4b678c5 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -130,7 +130,7 @@ namespace MWPhysics void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World, bool skipAnimated = false); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); - int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius, bool canTraverseWater); + int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); void setCaster(int projectileId, const MWWorld::Ptr& caster); void updateProjectile(const int projectileId, const osg::Vec3f &position) const; void removeProjectile(const int projectileId); diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index a8bb444956..4efb245149 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -15,12 +15,10 @@ namespace MWPhysics { -Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) - : mCanCrossWaterSurface(canCrossWaterSurface) - , mCrossedWaterSurface(false) +Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem) + : mHitWater(false) , mActive(true) , mHitTarget(nullptr) - , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) { @@ -75,11 +73,6 @@ osg::Vec3f Projectile::getPosition() const return mPosition; } -bool Projectile::canTraverseWater() const -{ - return mCanCrossWaterSurface; -} - void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal) { bool active = true; @@ -143,17 +136,4 @@ bool Projectile::isValidTarget(const btCollisionObject* target) const [target](const btCollisionObject* actor) { return target == actor; }); } -std::optional Projectile::getWaterHitPosition() -{ - return std::exchange(mWaterHitPosition, std::nullopt); -} - -void Projectile::setWaterHitPosition(btVector3 pos) -{ - if (mCrossedWaterSurface) - return; - mCrossedWaterSurface = true; - mWaterHitPosition = pos; -} - } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index dd659b6581..5e4e487c03 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -32,7 +31,7 @@ namespace MWPhysics class Projectile final : public PtrHolder { public: - Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, bool canCrossWaterSurface, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); + Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, float radius, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem); ~Projectile() override; btConvexShape* getConvexShape() const { return mConvexShape; } @@ -56,15 +55,25 @@ namespace MWPhysics return mCasterColObj; } - bool canTraverseWater() const; + void setHitWater() + { + mHitWater = true; + } + + bool getHitWater() const + { + return mHitWater; + } void hit(const btCollisionObject* target, btVector3 pos, btVector3 normal); void setValidTargets(const std::vector& targets); bool isValidTarget(const btCollisionObject* target) const; - std::optional getWaterHitPosition(); - void setWaterHitPosition(btVector3 pos); + btVector3 getHitPosition() const + { + return mHitPosition; + } private: @@ -72,13 +81,11 @@ namespace MWPhysics btConvexShape* mConvexShape; bool mTransformUpdatePending; - bool mCanCrossWaterSurface; - bool mCrossedWaterSurface; + bool mHitWater; std::atomic mActive; MWWorld::Ptr mCaster; const btCollisionObject* mCasterColObj; const btCollisionObject* mHitTarget; - std::optional mWaterHitPosition; osg::Vec3f mPosition; btVector3 mHitPosition; btVector3 mHitNormal; diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index 687253e1cc..6520be787d 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -49,9 +49,7 @@ namespace MWPhysics } case CollisionType_Water: { - mProjectile->setWaterHitPosition(m_hitPointWorld); - if (mProjectile->canTraverseWater()) - return 1.f; + mProjectile->setHitWater(); break; } } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 76c6ca7548..25e7f0c7de 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -317,7 +317,7 @@ namespace MWWorld // in case there are multiple effects, the model is a dummy without geometry. Use the second effect for physics shape if (state.mIdMagic.size() > 1) model = "meshes\\" + MWBase::Environment::get().getWorld()->getStore().get().find(state.mIdMagic[1])->mModel; - state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true, false); + state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; mMagicBolts.push_back(state); } @@ -342,7 +342,7 @@ namespace MWWorld if (!ptr.getClass().getEnchantment(ptr).empty()) SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr)); - state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false, true); + state.mProjectileId = mPhysics->addProjectile(actor, pos, model, false); state.mToDelete = false; mProjectiles.push_back(state); } @@ -493,9 +493,6 @@ namespace MWWorld auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId); - if (const auto hitWaterPos = projectile->getWaterHitPosition()) - mRendering->emitWaterRipple(Misc::Convert::toOsg(*hitWaterPos)); - const auto pos = projectile->getPosition(); projectileState.mNode->setPosition(pos); @@ -519,6 +516,8 @@ namespace MWWorld if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId)) bow = *invIt; } + if (projectile->getHitWater()) + mRendering->emitWaterRipple(pos); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); projectileState.mToDelete = true; @@ -663,7 +662,7 @@ namespace MWWorld int weaponType = ptr.get()->mBase->mData.mType; state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown; - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false, true); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, false); } catch(...) { @@ -716,7 +715,7 @@ namespace MWWorld osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects); createModel(state, model, osg::Vec3f(esm.mPosition), osg::Quat(esm.mOrientation), true, true, lightDiffuseColor, texture); - state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true, false); + state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition), model, true); MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); for (const std::string &soundid : state.mSoundIds) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bc28c2eb7b..3f93aa033a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3145,6 +3145,7 @@ namespace MWWorld bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), worldPos); if (underwater) { + MWMechanics::projectileHit(actor, Ptr(), bow, projectile, worldPos, attackStrength); mRendering->emitWaterRipple(worldPos); return; } From 6e6214bc43ec3dc3c98a7a1aa423d2d24e77fd1e Mon Sep 17 00:00:00 2001 From: Lamoot Date: Thu, 9 Sep 2021 20:46:27 +0200 Subject: [PATCH 009/137] In OpenMW-CS, when creating a new object, sort the entries in the drop-down menu alphabetically. Also have the menu be tall enough to show all of them at once (without scroll bars). --- apps/opencs/view/world/referenceablecreator.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/world/referenceablecreator.cpp b/apps/opencs/view/world/referenceablecreator.cpp index 1a2f2bbaa3..6bc0126b3e 100644 --- a/apps/opencs/view/world/referenceablecreator.cpp +++ b/apps/opencs/view/world/referenceablecreator.cpp @@ -22,7 +22,8 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd std::vector types = CSMWorld::UniversalId::listReferenceableTypes(); mType = new QComboBox (this); - + mType->setMaxVisibleItems(20); + for (std::vector::const_iterator iter (types.begin()); iter!=types.end(); ++iter) { @@ -31,7 +32,9 @@ CSVWorld::ReferenceableCreator::ReferenceableCreator (CSMWorld::Data& data, QUnd mType->addItem (QIcon (id2.getIcon().c_str()), id2.getTypeName().c_str(), static_cast (id2.getType())); } - + + mType->model()->sort(0); + insertBeforeButtons (mType, false); connect (mType, SIGNAL (currentIndexChanged (int)), this, SLOT (setType (int))); From e2d0e860208b6f82f9b7e3d3529597a6e7520e7f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 11 Sep 2021 18:58:42 +0000 Subject: [PATCH 010/137] cellpreloader.cpp unused variable (#3102) --- apps/openmw/mwworld/cellpreloader.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index b2dba1d452..c29d975c24 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -95,7 +95,6 @@ namespace MWWorld { mesh = Misc::ResourceHelpers::correctActorModelPath(mesh, mSceneManager->getVFS()); - bool animated = false; size_t slashpos = mesh.find_last_of("/\\"); if (slashpos != std::string::npos && slashpos != mesh.size()-1) { @@ -107,10 +106,7 @@ namespace MWWorld { kfname.replace(kfname.size()-4, 4, ".kf"); if (mSceneManager->getVFS()->exists(kfname)) - { mPreloadedObjects.insert(mKeyframeManager->get(kfname)); - animated = true; - } } } } From 98a0819d523e1cf7ba40ac8df279e2579896e284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Thu, 29 Jul 2021 15:33:58 +0200 Subject: [PATCH 011/137] Debug terrain chunks --- components/terrain/cellborder.cpp | 19 +++++++++++++------ components/terrain/cellborder.hpp | 3 +++ components/terrain/chunkmanager.cpp | 18 +++++++++++++++++- components/terrain/chunkmanager.hpp | 1 + components/terrain/quadtreeworld.cpp | 2 +- .../reference/modding/settings/terrain.rst | 9 +++++++++ files/settings-default.cfg | 3 +++ 7 files changed, 47 insertions(+), 8 deletions(-) diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index c34ed9ab0a..e391036902 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -9,6 +9,7 @@ #include "../esm/loadland.hpp" #include +#include namespace Terrain { @@ -21,13 +22,14 @@ CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask, { } -void CellBorder::createCellBorderGeometry(int x, int y) +osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask) { const int cellSize = ESM::Land::REAL_SIZE; const int borderSegments = 40; const float offset = 10.0; osg::Vec3 cellCorner = osg::Vec3(x * cellSize,y * cellSize,0); + size *= cellSize; osg::ref_ptr vertices = new osg::Vec3Array; osg::ref_ptr colors = new osg::Vec4Array; @@ -35,16 +37,16 @@ void CellBorder::createCellBorderGeometry(int x, int y) normals->push_back(osg::Vec3(0.0f,-1.0f, 0.0f)); - float borderStep = cellSize / ((float) borderSegments); + float borderStep = size / ((float)borderSegments); for (int i = 0; i <= 2 * borderSegments; ++i) { osg::Vec3f pos = i < borderSegments ? osg::Vec3(i * borderStep,0.0f,0.0f) : - osg::Vec3(cellSize,(i - borderSegments) * borderStep,0.0f); + osg::Vec3(size, (i - borderSegments) * borderStep,0.0f); pos += cellCorner; - pos += osg::Vec3f(0,0,mWorld->getHeightAt(pos) + offset); + pos += osg::Vec3f(0,0, terrain->getHeightAt(pos) + offset); vertices->push_back(pos); @@ -76,10 +78,15 @@ void CellBorder::createCellBorderGeometry(int x, int y) polygonmode->setMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE); stateSet->setAttributeAndModes(polygonmode,osg::StateAttribute::ON); - mSceneManager->recreateShaders(borderGeode, "debug"); + sceneManager->recreateShaders(borderGeode, "debug"); + borderGeode->setNodeMask(mask); - borderGeode->setNodeMask(mBorderMask); + return borderGeode; +} +void CellBorder::createCellBorderGeometry(int x, int y) +{ + auto borderGeode = createBorderGeometry(x, y, 1.f, mWorld->getStorage(), mSceneManager, mBorderMask); mRoot->addChild(borderGeode); mCellBorderNodes[std::make_pair(x,y)] = borderGeode; diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index ee8fd72593..d72241e3dc 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -11,6 +11,7 @@ namespace Resource namespace Terrain { + class Storage; class World; /** @@ -31,6 +32,8 @@ namespace Terrain */ void destroyCellBorderGeometry(); + static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask); + protected: Terrain::World *mWorld; Resource::SceneManager* mSceneManager; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 8809a75bb9..53b743c03a 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -12,12 +13,14 @@ #include #include +#include #include "terraindrawable.hpp" #include "material.hpp" #include "storage.hpp" #include "texturemanager.hpp" #include "compositemaprenderer.hpp" +#include namespace Terrain { @@ -32,6 +35,7 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T , mCompositeMapSize(512) , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) + , mDebugChunks(Settings::Manager::getBool("debug chunks", "Terrain")) { mMultiPassRoot = new osg::StateSet; mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN); @@ -234,7 +238,19 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve } geometry->setNodeMask(mNodeMask); - return geometry; + osg::ref_ptr result(new osg::Group); + result->addChild(geometry); + if (mDebugChunks) + { + auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask()); + osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; + osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); + trans->setDataVariance(osg::Object::STATIC); + trans->addChild(chunkBorder); + result->addChild(trans); + } + + return result; } } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 9b7dbf3ee1..4e030e018c 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -72,6 +72,7 @@ namespace Terrain unsigned int mCompositeMapSize; float mCompositeMapLevel; float mMaxCompGeometrySize; + bool mDebugChunks = false; }; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 6a228a75af..ca28e1cc05 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -367,7 +367,7 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); + osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)->asGroup()->getChild(0))->getWaterBoundingBox(); if (!bb.valid()) continue; osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index e6018a8655..80c085f4c9 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -100,6 +100,15 @@ max composite geometry size Controls the maximum size of simple composite geometry chunk in cell units. With small values there will more draw calls and small textures, but higher values create more overdraw (not every texture layer is used everywhere). +debug chunks +------------ + +:Type: boolean +:Range: True/False +:Default: False + +This debug setting allows you to see the borders of each chunks of the world by drawing lines arround them (as with toggleborder). + object paging ------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 7f57ba529d..08039f5fbe 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -139,6 +139,9 @@ composite map resolution = 512 # Controls the maximum size of composite geometry, should be >= 1.0. With low values there will be many small chunks, with high values - lesser count of bigger chunks. max composite geometry size = 4.0 +# Draw lines arround chunks. +debug chunks = false + # Use object paging for non active cells object paging = true From 4b7d0bba53cb3d4f84da72b53828afce9ce6e80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 6 Sep 2021 21:10:03 +0200 Subject: [PATCH 012/137] Avoid adding redundant osg;;Group in non debug mode --- components/terrain/chunkmanager.cpp | 7 ++++--- components/terrain/quadtreeworld.cpp | 11 ++++++++--- components/terrain/quadtreeworld.hpp | 1 + 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 53b743c03a..1f3279e5c5 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -238,19 +238,20 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve } geometry->setNodeMask(mNodeMask); - osg::ref_ptr result(new osg::Group); - result->addChild(geometry); if (mDebugChunks) { + osg::ref_ptr result(new osg::Group); + result->addChild(geometry); auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask()); osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); trans->setDataVariance(osg::Object::STATIC); trans->addChild(chunkBorder); result->addChild(trans); + return result; } - return result; + return geometry; } } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index ca28e1cc05..bbf212942e 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -252,6 +252,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) , mMinSize(1/8.f) + , mDebugTerrainChunks(Settings::Manager::getBool("debug chunks", "Terrain")) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); @@ -351,7 +352,7 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f } } -void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) +void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld, bool debugTerrainChunk) { if (!(cv->getTraversalMask() & callback->getCullMask())) return; @@ -367,7 +368,11 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)->asGroup()->getChild(0))->getWaterBoundingBox(); + osg::BoundingBox bb; + if(debugTerrainChunk) + bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)->asGroup()->getChild(0))->getWaterBoundingBox(); + else + bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); if (!bb.valid()) continue; osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); @@ -443,7 +448,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) } if (mHeightCullCallback && isCullVisitor) - updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); + updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty(), mDebugTerrainChunks); vd->markUnchanged(); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 3dd96a0b84..f7cbf8097a 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -72,6 +72,7 @@ namespace Terrain int mVertexLodMod; float mViewDistance; float mMinSize; + bool mDebugTerrainChunks; }; } From 080c909c285a3719f1804517ab831005fc47131d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Wed, 8 Sep 2021 19:59:53 +0200 Subject: [PATCH 013/137] Merge the 'debug chunks' and 'object paging debug batches' settings into a single one --- apps/openmw/mwrender/objectpaging.cpp | 2 +- docs/source/reference/modding/settings/terrain.rst | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 907631436f..376c517d59 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -390,7 +390,7 @@ namespace MWRender , mRefTrackerLocked(false) { mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); - mDebugBatches = Settings::Manager::getBool("object paging debug batches", "Terrain"); + mDebugBatches = Settings::Manager::getBool("debug chunks", "Terrain"); mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); diff --git a/docs/source/reference/modding/settings/terrain.rst b/docs/source/reference/modding/settings/terrain.rst index 80c085f4c9..752260fd48 100644 --- a/docs/source/reference/modding/settings/terrain.rst +++ b/docs/source/reference/modding/settings/terrain.rst @@ -107,7 +107,10 @@ debug chunks :Range: True/False :Default: False -This debug setting allows you to see the borders of each chunks of the world by drawing lines arround them (as with toggleborder). +This debug setting allows you to see the borders of each chunks of the world by drawing lines arround them (as with toggleborder). +If object paging is set to true then this debug setting will allows you to see what objects have been merged in the scene +by making them colored randomly. + object paging ------------- @@ -203,12 +206,3 @@ object paging min size cost multiplier This setting adjusts the calculated cost of merging an object used in the mentioned functionality. The larger this value is, the less expensive objects can be before they are discarded. See the formula above to figure out the math. - -object paging debug batches ---------------------------- -:Type: boolean -:Range: True/False -:Default: False - -This debug setting allows you to see what objects have been merged in the scene -by making them colored randomly. From d7352ded36f05330df2c392b06edc3411a762eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 11 Sep 2021 08:25:41 +0200 Subject: [PATCH 014/137] Add configurable color and offset --- components/terrain/cellborder.cpp | 6 +++--- components/terrain/cellborder.hpp | 2 +- components/terrain/chunkmanager.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index e391036902..280a55faca 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -22,11 +22,11 @@ CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask, { } -osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask) +osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, + float offset, osg::Vec4f color) { const int cellSize = ESM::Land::REAL_SIZE; const int borderSegments = 40; - const float offset = 10.0; osg::Vec3 cellCorner = osg::Vec3(x * cellSize,y * cellSize,0); size *= cellSize; @@ -52,7 +52,7 @@ osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, floa osg::Vec4f col = i % 2 == 0 ? osg::Vec4f(0,0,0,1) : - osg::Vec4f(1,1,0,1); + color; colors->push_back(col); } diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index d72241e3dc..785c441b86 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -32,7 +32,7 @@ namespace Terrain */ void destroyCellBorderGeometry(); - static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask); + static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset = 10.0, osg::Vec4f color = { 1,1,0,0 }); protected: Terrain::World *mWorld; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 1f3279e5c5..7a6d4fdb0a 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -242,7 +242,7 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve { osg::ref_ptr result(new osg::Group); result->addChild(geometry); - auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask()); + auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask(), 5.f, { 1, 0, 0, 0 }); osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); trans->setDataVariance(osg::Object::STATIC); From c40f921396927eacbadb24c198692fe4e416acb5 Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 12 Sep 2021 07:56:20 +0000 Subject: [PATCH 015/137] Revert "actoranimation.cpp faster getbonebyname (#3099)" This reverts commit c284d0cf5c6ca0a6a55e92f9de7351469946628d --- apps/openmw/mwrender/actoranimation.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index cb46354590..f556e6891a 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -67,14 +67,17 @@ ActorAnimation::~ActorAnimation() PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor) { + osg::Group* parent = getBoneByName(bonename); + if (!parent) + return nullptr; + + osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); + const NodeMap& nodeMap = getNodeMap(); NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(bonename)); if (found == nodeMap.end()) return PartHolderPtr(); - osg::Group* parent = found->second; - osg::ref_ptr instance = mResourceSystem->getSceneManager()->getInstance(model, parent); - if (enchantedGlow) mGlowUpdater = SceneUtil::addEnchantedGlow(instance, mResourceSystem, *glowColor); @@ -133,9 +136,9 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell) { - osg::Group* foundNode = getBoneByName ("Bip01 AttachShield"); - - if (foundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) + SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); + mObjectRoot->accept(findVisitor); + if (findVisitor.mFoundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson())) { const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); @@ -273,11 +276,10 @@ osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) const if (!mObjectRoot) return nullptr; - const NodeMap& nodeMap = getNodeMap(); - NodeMap::const_iterator found = nodeMap.find(Misc::StringUtils::lowerCase(boneName)); - if (found == nodeMap.end()) - return nullptr; - return found->second; + SceneUtil::FindByNameVisitor findVisitor (boneName); + mObjectRoot->accept(findVisitor); + + return findVisitor.mFoundNode; } std::string ActorAnimation::getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon) From 52a9b4d989d3489e2238ce538516904574edb04d Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 12 Sep 2021 09:21:10 +0000 Subject: [PATCH 016/137] shadowsbin.cpp uniform --- components/sceneutil/shadowsbin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/sceneutil/shadowsbin.cpp b/components/sceneutil/shadowsbin.cpp index 14dbc14178..af62b581c9 100644 --- a/components/sceneutil/shadowsbin.cpp +++ b/components/sceneutil/shadowsbin.cpp @@ -54,6 +54,7 @@ ShadowsBin::ShadowsBin() mShaderAlphaTestStateSet = new osg::StateSet; mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true)); + mShaderAlphaTestStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED | osg::StateAttribute::OVERRIDE); for (size_t i = 0; i < sCastingPrograms.size(); ++i) From 6d12a240a39492f9ea7f3a94709cb641b9697ced Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 12 Sep 2021 09:23:36 +0000 Subject: [PATCH 017/137] shadervisitor.cpp uniform --- components/shader/shadervisitor.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 26511d4654..a72d772afe 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -340,15 +340,6 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } - - if (diffuseMap) - { - if (!writableStateSet) - writableStateSet = getWritableStateSet(node); - // We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out. - // Also it should probably belong to the shader manager or be applied by the shadows bin - writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); - } } const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); From b6f572578e04cf2e7a6fa2b9d09c2ea27e6e42e7 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 13 Sep 2021 10:20:00 +0000 Subject: [PATCH 018/137] Groundcover optimisation (#3101) * groundcover.cpp share state * groundcover.hpp share state --- apps/openmw/mwrender/groundcover.cpp | 54 ++++++++-------------------- apps/openmw/mwrender/groundcover.hpp | 1 + 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 39022709fb..a199201d41 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -1,6 +1,7 @@ #include "groundcover.hpp" #include +#include #include #include @@ -66,18 +67,6 @@ namespace MWRender { } - void apply(osg::Node& node) override - { - osg::ref_ptr ss = node.getStateSet(); - if (ss != nullptr) - { - ss->removeAttribute(osg::StateAttribute::MATERIAL); - removeAlpha(ss); - } - - traverse(node); - } - void apply(osg::Geometry& geom) override { for (unsigned int i = 0; i < geom.getNumPrimitiveSets(); ++i) @@ -110,32 +99,14 @@ namespace MWRender // Display lists do not support instancing in OSG 3.4 geom.setUseDisplayList(false); + geom.setUseVertexBufferObjects(true); geom.setVertexAttribArray(6, transforms.get(), osg::Array::BIND_PER_VERTEX); geom.setVertexAttribArray(7, rotations.get(), osg::Array::BIND_PER_VERTEX); - - osg::ref_ptr ss = geom.getOrCreateStateSet(); - ss->setAttribute(new osg::VertexAttribDivisor(6, 1)); - ss->setAttribute(new osg::VertexAttribDivisor(7, 1)); - - ss->removeAttribute(osg::StateAttribute::MATERIAL); - removeAlpha(ss); - - traverse(geom); } private: std::vector mInstances; osg::Vec3f mChunkPosition; - - void removeAlpha(osg::StateSet* stateset) - { - // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties - stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); - stateset->removeMode(GL_ALPHA_TEST); - stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); - stateset->removeMode(GL_BLEND); - stateset->setRenderBinToInherit(); - } }; class DensityCalculator @@ -199,7 +170,16 @@ namespace MWRender : GenericResourceManager(nullptr) , mSceneManager(sceneManager) , mDensity(density) + , mStateset(new osg::StateSet) { + // MGE uses default alpha settings for groundcover, so we can not rely on alpha properties + // Force a unified alpha handling instead of data from meshes + osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); + mStateset->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + mStateset->setAttributeAndModes(new osg::BlendFunc, osg::StateAttribute::OFF|osg::StateAttribute::OVERRIDE); + mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); + mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); } void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) @@ -255,27 +235,23 @@ namespace MWRender for (auto& pair : instances) { const osg::Node* temp = mSceneManager->getTemplate(pair.first); - osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_ALL&(~osg::CopyOp::DEEP_COPY_TEXTURES))); + osg::ref_ptr node = static_cast(temp->clone(osg::CopyOp::DEEP_COPY_NODES|osg::CopyOp::DEEP_COPY_DRAWABLES|osg::CopyOp::DEEP_COPY_USERDATA|osg::CopyOp::DEEP_COPY_ARRAYS|osg::CopyOp::DEEP_COPY_PRIMITIVES)); // Keep link to original mesh to keep it in cache group->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(temp)); - mSceneManager->reinstateRemovedState(node); - InstancingVisitor visitor(pair.second, worldCenter); node->accept(visitor); group->addChild(node); } - // Force a unified alpha handling instead of data from meshes - osg::ref_ptr alpha = new osg::AlphaFunc(osg::AlphaFunc::GEQUAL, 128.f / 255.f); - group->getOrCreateStateSet()->setAttributeAndModes(alpha.get(), osg::StateAttribute::ON); - group->getBound(); + group->setStateSet(mStateset); group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->setCullCallback(new SceneUtil::LightListCallback); mSceneManager->recreateShaders(group, "groundcover", false, true); - + mSceneManager->shareState(group); + group->getBound(); return group; } diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index 10874b7e8b..4874bea899 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -56,6 +56,7 @@ namespace MWRender private: Resource::SceneManager* mSceneManager; float mDensity; + osg::ref_ptr mStateset; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); From e4eeb9cce9b29702bfde7dc9fdd3b983a8d69249 Mon Sep 17 00:00:00 2001 From: pi03k Date: Tue, 7 Sep 2021 23:10:18 +0200 Subject: [PATCH 019/137] Remove 'no relevant classes' moc warning --- CHANGELOG.md | 1 + CMakeLists.txt | 2 -- apps/launcher/CMakeLists.txt | 21 +++------------------ apps/opencs/CMakeLists.txt | 5 ++++- apps/opencs/view/tools/merge.hpp | 4 ++-- apps/wizard/CMakeLists.txt | 23 ++++------------------- cmake/OpenMWMacros.cmake | 6 ------ components/CMakeLists.txt | 7 +++++-- 8 files changed, 19 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1132a9e68..ffabc0b037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering Feature #6251: OpenMW-CS: Set instance movement based on camera zoom + Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp 0.47.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 52a824d4e9..d14762ef5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,8 +202,6 @@ if (USE_QT) find_package(Qt5Widgets REQUIRED) find_package(Qt5Network REQUIRED) find_package(Qt5OpenGL REQUIRED) - # Instruct CMake to run moc automatically when needed. - #set(CMAKE_AUTOMOC ON) endif() set(USED_OSG_COMPONENTS diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index e9ec906e6a..7e8bd67e94 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -36,23 +36,6 @@ set(LAUNCHER_HEADER ) # Headers that must be pre-processed -set(LAUNCHER_HEADER_MOC - datafilespage.hpp - graphicspage.hpp - maindialog.hpp - playpage.hpp - textslotmsgbox.hpp - settingspage.hpp - advancedpage.hpp - - utils/cellnameloader.hpp - utils/textinputdialog.hpp - utils/profilescombobox.hpp - utils/lineedit.hpp - utils/openalutil.hpp - -) - set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui @@ -74,7 +57,6 @@ if(WIN32) endif(WIN32) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) -QT5_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) QT5_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -109,4 +91,7 @@ if (BUILD_WITH_CODE_COVERAGE) target_link_libraries(openmw-launcher gcov) endif() +if(USE_QT) + set_property(TARGET openmw-launcher PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 4b3b8030e6..60bd0f4ded 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -150,7 +150,6 @@ if(WIN32) endif(WIN32) qt5_wrap_ui(OPENCS_UI_HDR ${OPENCS_UI}) -qt5_wrap_cpp(OPENCS_MOC_SRC ${OPENCS_HDR_QT}) qt5_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) # for compiled .ui files @@ -259,3 +258,7 @@ endif (MSVC) if(APPLE) INSTALL(TARGETS openmw-cs BUNDLE DESTINATION "." COMPONENT Bundle) endif() + +if(USE_QT) + set_property(TARGET openmw-cs PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/apps/opencs/view/tools/merge.hpp b/apps/opencs/view/tools/merge.hpp index d394a431ed..c7c4585979 100644 --- a/apps/opencs/view/tools/merge.hpp +++ b/apps/opencs/view/tools/merge.hpp @@ -1,5 +1,5 @@ -#ifndef CSV_TOOLS_REPORTTABLE_H -#define CSV_TOOLS_REPORTTABLE_H +#ifndef CSV_TOOLS_MERGE_H +#define CSV_TOOLS_MERGE_H #include diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 2558ec81de..03cd63480a 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -1,4 +1,3 @@ - set(WIZARD componentselectionpage.cpp conclusionpage.cpp @@ -34,21 +33,6 @@ set(WIZARD_HEADER utils/componentlistwidget.hpp ) -# Headers that must be pre-processed -set(WIZARD_HEADER_MOC - componentselectionpage.hpp - conclusionpage.hpp - existinginstallationpage.hpp - importpage.hpp - installationtargetpage.hpp - intropage.hpp - languageselectionpage.hpp - mainwizard.hpp - methodselectionpage.hpp - - utils/componentlistwidget.hpp -) - set(WIZARD_UI ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui @@ -63,7 +47,6 @@ set(WIZARD_UI if (OPENMW_USE_UNSHIELD) set (WIZARD ${WIZARD} installationpage.cpp unshield/unshieldworker.cpp) set (WIZARD_HEADER ${WIZARD_HEADER} installationpage.hpp unshield/unshieldworker.hpp) - set (WIZARD_HEADER_MOC ${WIZARD_HEADER_MOC} installationpage.hpp unshield/unshieldworker.hpp) set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui) add_definitions(-DOPENMW_USE_UNSHIELD) endif (OPENMW_USE_UNSHIELD) @@ -80,7 +63,6 @@ if(WIN32) endif(WIN32) QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/wizard/wizard.qrc) -QT5_WRAP_CPP(MOC_SRCS ${WIZARD_HEADER_MOC}) QT5_WRAP_UI(UI_HDRS ${WIZARD_UI}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) @@ -94,7 +76,6 @@ openmw_add_executable(openmw-wizard ${WIZARD} ${WIZARD_HEADER} ${RCC_SRCS} - ${MOC_SRCS} ${UI_HDRS} ) @@ -125,3 +106,7 @@ endif() if (WIN32) INSTALL(TARGETS openmw-wizard RUNTIME DESTINATION ".") endif(WIN32) + +if(USE_QT) + set_property(TARGET openmw-wizard PROPERTY AUTOMOC ON) +endif(USE_QT) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 176a02d507..5bee345344 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -80,10 +80,6 @@ foreach (f ${ALL}) list (APPEND files "${f}") list (APPEND COMPONENT_QT_FILES "${f}") endforeach (f) -file (GLOB MOC_H "${dir}/${u}.hpp") -foreach (fi ${MOC_H}) -list (APPEND COMPONENT_MOC_FILES "${fi}") -endforeach (fi) endforeach (u) source_group ("components\\${dir}" FILES ${files}) endmacro (add_component_qt_dir) @@ -99,7 +95,6 @@ endmacro (add_unit) macro (add_qt_unit project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") endmacro (add_qt_unit) @@ -109,7 +104,6 @@ endmacro (add_hdr) macro (add_qt_hdr project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -add_file (${project} _HDR_QT ${comp} "${dir}/${unit}.hpp") endmacro (add_qt_hdr) macro (opencs_units dir) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7983de6190..5b79a096cd 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -226,7 +226,6 @@ if (USE_QT) ) QT5_WRAP_UI(ESM_UI_HDR ${ESM_UI}) - QT5_WRAP_CPP(MOC_SRCS ${COMPONENT_MOC_FILES}) endif() if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") @@ -280,7 +279,7 @@ if (WIN32) endif() if (USE_QT) - add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) + add_library(components_qt STATIC ${COMPONENT_QT_FILES} ${ESM_UI_HDR}) target_link_libraries(components_qt components Qt5::Widgets Qt5::Core) target_compile_definitions(components_qt PRIVATE OPENMW_DOC_BASEURL="${OPENMW_DOC_BASEURL}") endif() @@ -333,3 +332,7 @@ if(OSG_STATIC) target_link_libraries(components freetype jpeg png) endif() endif(OSG_STATIC) + +if(USE_QT) + set_property(TARGET components_qt PROPERTY AUTOMOC ON) +endif(USE_QT) From 3415f12e128a2de023872d8cfb277de1fa2af275 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 14 Sep 2021 11:30:07 +0000 Subject: [PATCH 020/137] shadervisitor.cpp --- components/shader/shadervisitor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a72d772afe..cb0173c648 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -458,6 +458,9 @@ namespace Shader defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } + if (defineMap["diffuseMap"] == 0) + writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); + defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); From 2cebd194327b285b8a151b81b9f95dd8fbdd03ce Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 14 Sep 2021 11:37:23 +0000 Subject: [PATCH 021/137] shadervisitor.cpp --- components/shader/shadervisitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index cb0173c648..cbd2863e46 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -458,7 +458,7 @@ namespace Shader defineMap[texIt->second + std::string("UV")] = std::to_string(texIt->first); } - if (defineMap["diffuseMap"] == 0) + if (defineMap["diffuseMap"] == "0") writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; From 60298cd66de1bc9a1fefd1cec23218a64c21678b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 14 Sep 2021 16:53:57 +0200 Subject: [PATCH 022/137] Reset rotation when respawning actors --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 1 + apps/openmw/mwclass/npc.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1132a9e68..91602e5408 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop + Bug #6273: Respawning NPCs rotation is inconsistent Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 50dafba39b..54623e6699 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -846,6 +846,7 @@ namespace MWClass // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 41cf3beed5..718b4d972d 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1397,6 +1397,7 @@ namespace MWClass // Reset to original position MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().asVec3()); + MWBase::Environment::get().getWorld()->rotateObject(ptr, ptr.getCellRef().getPosition().asRotationVec3(), MWBase::RotationFlag_none); } } } From d4e26746a32404d620c8e847335ba096668fee17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 6 Sep 2021 21:56:32 +0200 Subject: [PATCH 023/137] Use recurse subdirectory iterator to iterate over the VFS without exposing internal details --- apps/niftest/niftest.cpp | 5 +-- apps/opencs/model/world/resources.cpp | 4 +- apps/openmw/mwgui/loadingscreen.cpp | 23 ++++------ apps/openmw/mwrender/animation.cpp | 36 ++++------------ apps/openmw/mwsound/soundmanagerimp.cpp | 18 ++------ components/fontloader/fontloader.cpp | 18 ++------ components/vfs/manager.cpp | 10 ++--- components/vfs/manager.hpp | 56 +++++++++++++++++++++++-- 8 files changed, 82 insertions(+), 88 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 2e81885c75..cb6205ef5c 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -52,11 +52,8 @@ void readVFS(VFS::Archive* anArchive,std::string archivePath = "") myManager.addArchive(anArchive); myManager.buildIndex(); - std::map files=myManager.getIndex(); - for(auto it=files.begin(); it!=files.end(); ++it) + for(const auto& name : myManager.getRecursiveDirectoryIterator("")) { - std::string name = it->first; - try{ if(isNIF(name)) { diff --git a/apps/opencs/model/world/resources.cpp b/apps/opencs/model/world/resources.cpp index 2544886f3e..cd9f58e848 100644 --- a/apps/opencs/model/world/resources.cpp +++ b/apps/opencs/model/world/resources.cpp @@ -23,10 +23,8 @@ void CSMWorld::Resources::recreate(const VFS::Manager* vfs, const char * const * size_t baseSize = mBaseDirectory.size(); - const std::map& index = vfs->getIndex(); - for (std::map::const_iterator it = index.begin(); it != index.end(); ++it) + for (const auto& filepath : vfs->getRecursiveDirectoryIterator("")) { - std::string filepath = it->first; if (filepath.size()& index = mResourceSystem->getVFS()->getIndex(); std::string pattern = "Splash/"; mResourceSystem->getVFS()->normalizeFilename(pattern); /* priority given to the left */ const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; - auto found = index.lower_bound(pattern); - while (found != index.end()) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(pattern)) { - const std::string& name = found->first; - if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos) + for (auto const& extension : supported_extensions) { - for(auto const& extension: supported_extensions) + if (name.compare(pos, name.size() - pos, extension) == 0) { - if (name.compare(pos, name.size() - pos, extension) == 0) - { - mSplashScreens.push_back(found->first); - break; /* based on priority */ - } + mSplashScreens.push_back(name); + break; /* based on priority */ } } } - else - break; - ++found; } if (mSplashScreens.empty()) Log(Debug::Warning) << "Warning: no splash screens found!"; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index a04724c70a..4eafd05f5f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -591,8 +591,6 @@ namespace MWRender void Animation::loadAllAnimationsInFolder(const std::string &model, const std::string &baseModel) { - const std::map& index = mResourceSystem->getVFS()->getIndex(); - std::string animationPath = model; if (animationPath.find("meshes") == 0) { @@ -602,19 +600,11 @@ namespace MWRender mResourceSystem->getVFS()->normalizeFilename(animationPath); - std::map::const_iterator found = index.lower_bound(animationPath); - while (found != index.end()) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - const std::string& name = found->first; - if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".kf") == 0) - addSingleAnimSource(name, baseModel); - } - else - break; - ++found; + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".kf") == 0) + addSingleAnimSource(name, baseModel); } } @@ -1295,8 +1285,6 @@ namespace MWRender if (model.empty()) return; - const std::map& index = resourceSystem->getVFS()->getIndex(); - std::string animationPath = model; if (animationPath.find("meshes") == 0) { @@ -1306,19 +1294,11 @@ namespace MWRender resourceSystem->getVFS()->normalizeFilename(animationPath); - std::map::const_iterator found = index.lower_bound(animationPath); - while (found != index.end()) + for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - const std::string& name = found->first; - if (name.size() >= animationPath.size() && name.substr(0, animationPath.size()) == animationPath) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".nif") == 0) - loadBonesFromFile(node, name, resourceSystem); - } - else - break; - ++found; + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".nif") == 0) + loadBonesFromFile(node, name, resourceSystem); } } diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index fc9735d576..f5c3231c99 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -294,20 +294,12 @@ namespace MWSound if (mMusicFiles.find(playlist) == mMusicFiles.end()) { std::vector filelist; - const std::map& index = mVFS->getIndex(); std::string pattern = "Music/" + playlist; mVFS->normalizeFilename(pattern); - std::map::const_iterator found = index.lower_bound(pattern); - while (found != index.end()) - { - if (found->first.size() >= pattern.size() && found->first.substr(0, pattern.size()) == pattern) - filelist.push_back(found->first); - else - break; - ++found; - } + for (const auto& name : mVFS->getRecursiveDirectoryIterator(pattern)) + filelist.push_back(name); mMusicFiles[playlist] = filelist; } @@ -327,13 +319,11 @@ namespace MWSound if (mMusicFiles.find("Title") == mMusicFiles.end()) { std::vector filelist; - const std::map& index = mVFS->getIndex(); // Is there an ini setting for this filename or something? std::string filename = "music/special/morrowind title.mp3"; - auto found = index.find(filename); - if (found != index.end()) + if (mVFS->exists(filename)) { - filelist.emplace_back(found->first); + filelist.emplace_back(filename); mMusicFiles["Title"] = filelist; } else diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 487b7bd70b..998e9c0c64 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -191,24 +191,14 @@ namespace Gui void FontLoader::loadBitmapFonts(bool exportToFile) { - const std::map& index = mVFS->getIndex(); - std::string pattern = "Fonts/"; mVFS->normalizeFilename(pattern); - std::map::const_iterator found = index.lower_bound(pattern); - while (found != index.end()) + for (const auto& name : mVFS->getRecursiveDirectoryIterator(pattern)) { - const std::string& name = found->first; - if (name.size() >= pattern.size() && name.substr(0, pattern.size()) == pattern) - { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size()-pos, ".fnt") == 0) - loadBitmapFont(name, exportToFile); - } - else - break; - ++found; + size_t pos = name.find_last_of('.'); + if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".fnt") == 0) + loadBitmapFont(name, exportToFile); } } diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 045fe3cf5c..f799719e31 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -86,11 +86,6 @@ namespace VFS return mIndex.find(normalized) != mIndex.end(); } - const std::map& Manager::getIndex() const - { - return mIndex; - } - void Manager::normalizeFilename(std::string &name) const { normalize_path(name, mStrict); @@ -107,4 +102,9 @@ namespace VFS } return {}; } + + RecursiveDirectoryIterator Manager::getRecursiveDirectoryIterator(const std::string& path) const + { + return RecursiveDirectoryIterator(mIndex, path); + } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 5a09a995eb..8656f3bf10 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -12,6 +12,51 @@ namespace VFS class Archive; class File; + class RecursiveDirectoryIterator; + RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); + + class RecursiveDirectoryIterator + { + public: + RecursiveDirectoryIterator(const std::map& index, const std::string& path) + : mPath(path) + , mIndex(&index) + , mIt(index.lower_bound(path)) + {} + + RecursiveDirectoryIterator(const RecursiveDirectoryIterator&) = default; + + const std::string& operator*() const { return mIt->first; } + const std::string* operator->() const { return &mIt->first; } + + bool operator!=(const RecursiveDirectoryIterator& other) { return mPath != other.mPath || mIt != other.mIt; } + + RecursiveDirectoryIterator& operator++() + { + if (++mIt == mIndex->end() || !starts_with(mIt->first, mPath)) + *this = end(*this); + return *this; + } + + friend RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); + + private: + static bool starts_with(const std::string& text, const std::string& start) { return text.rfind(start, 0) == 0; } + + std::string mPath; + const std::map* mIndex; + std::map::const_iterator mIt; + }; + + inline RecursiveDirectoryIterator begin(RecursiveDirectoryIterator iter) { return iter; } + + inline RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter) + { + RecursiveDirectoryIterator result(iter); + result.mIt = result.mIndex->end(); + return result; + } + /// @brief The main class responsible for loading files from a virtual file system. /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) /// can be registered, and will be merged into a single file tree. If the same filename is @@ -40,10 +85,6 @@ namespace VFS /// @note May be called from any thread once the index has been built. bool exists(const std::string& name) const; - /// Get a complete list of files from all archives - /// @note May be called from any thread once the index has been built. - const std::map& getIndex() const; - /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. /// @note May be called from any thread once the index has been built. void normalizeFilename(std::string& name) const; @@ -59,6 +100,13 @@ namespace VFS Files::IStreamPtr getNormalized(const std::string& normalizedName) const; std::string getArchive(const std::string& name) const; + + /// Recursivly iterate over the elements of the given path + /// In practice it return all files of the VFS starting with the given path + /// @note the path is normalized + /// @note May be called from any thread once the index has been built. + RecursiveDirectoryIterator getRecursiveDirectoryIterator(const std::string& path) const; + private: bool mStrict; From c2df0949e2d739830bf3c3838f644cfdd9bb3371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Mon, 6 Sep 2021 22:01:41 +0200 Subject: [PATCH 024/137] Change normalizeFilename signature --- apps/openmw/mwgui/loadingscreen.cpp | 5 +---- apps/openmw/mwrender/animation.cpp | 4 ---- apps/openmw/mwsound/sound_buffer.cpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 15 +++------------ components/fontloader/fontloader.cpp | 5 +---- components/resource/bulletshapemanager.cpp | 9 +++------ components/resource/imagemanager.cpp | 3 +-- components/resource/keyframemanager.cpp | 3 +-- components/resource/scenemanager.cpp | 14 ++++---------- components/vfs/manager.cpp | 8 +++++--- components/vfs/manager.hpp | 2 +- 11 files changed, 21 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 2d65e4e508..7f454925fd 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -66,13 +66,10 @@ namespace MWGui void LoadingScreen::findSplashScreens() { - std::string pattern = "Splash/"; - mResourceSystem->getVFS()->normalizeFilename(pattern); - /* priority given to the left */ const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; - for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(pattern)) + for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) { size_t pos = name.find_last_of('.'); if (pos != std::string::npos) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 4eafd05f5f..3df902e469 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -598,8 +598,6 @@ namespace MWRender } animationPath.replace(animationPath.size()-3, 3, "/"); - mResourceSystem->getVFS()->normalizeFilename(animationPath); - for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { size_t pos = name.find_last_of('.'); @@ -1292,8 +1290,6 @@ namespace MWRender } animationPath.replace(animationPath.size()-4, 4, "/"); - resourceSystem->getVFS()->normalizeFilename(animationPath); - for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { size_t pos = name.find_last_of('.'); diff --git a/apps/openmw/mwsound/sound_buffer.cpp b/apps/openmw/mwsound/sound_buffer.cpp index cb71cb56d2..e64e89d775 100644 --- a/apps/openmw/mwsound/sound_buffer.cpp +++ b/apps/openmw/mwsound/sound_buffer.cpp @@ -131,7 +131,7 @@ namespace MWSound max = std::max(min, max); Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max); - mVfs->normalizeFilename(sfx.mResourceName); + sfx.mResourceName = mVfs->normalizeFilename(sfx.mResourceName); mBufferNameMap.emplace(soundId, &sfx); return &sfx; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index f5c3231c99..d2422870d7 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -295,10 +295,7 @@ namespace MWSound { std::vector filelist; - std::string pattern = "Music/" + playlist; - mVFS->normalizeFilename(pattern); - - for (const auto& name : mVFS->getRecursiveDirectoryIterator(pattern)) + for (const auto& name : mVFS->getRecursiveDirectoryIterator("Music/" + playlist)) filelist.push_back(name); mMusicFiles[playlist] = filelist; @@ -345,10 +342,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - std::string voicefile = "Sound/"+filename; - - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + DecoderPtr decoder = loadVoice(mVFS->normalizeFilename("Sound/" + filename)); if (!decoder) return; @@ -379,10 +373,7 @@ namespace MWSound if(!mOutput->isInitialized()) return; - std::string voicefile = "Sound/"+filename; - - mVFS->normalizeFilename(voicefile); - DecoderPtr decoder = loadVoice(voicefile); + DecoderPtr decoder = loadVoice(mVFS->normalizeFilename("Sound/" + filename)); if (!decoder) return; diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 998e9c0c64..9ba62f7694 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -191,10 +191,7 @@ namespace Gui void FontLoader::loadBitmapFonts(bool exportToFile) { - std::string pattern = "Fonts/"; - mVFS->normalizeFilename(pattern); - - for (const auto& name : mVFS->getRecursiveDirectoryIterator(pattern)) + for (const auto& name : mVFS->getRecursiveDirectoryIterator("Fonts/")) { size_t pos = name.find_last_of('.'); if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".fnt") == 0) diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index b1b0f74176..98878df152 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -121,8 +121,7 @@ BulletShapeManager::~BulletShapeManager() osg::ref_ptr BulletShapeManager::getShape(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr shape; osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); @@ -180,8 +179,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & osg::ref_ptr BulletShapeManager::cacheInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr instance = createInstance(normalized); if (instance) @@ -191,8 +189,7 @@ osg::ref_ptr BulletShapeManager::cacheInstance(const std::s osg::ref_ptr BulletShapeManager::getInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); if (obj.get()) diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index 37e76359ff..ba46b5cce9 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -83,8 +83,7 @@ namespace Resource osg::ref_ptr ImageManager::getImage(const std::string &filename) { - std::string normalized = filename; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(filename); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 444d2bd7aa..6d431fb3a5 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -133,8 +133,7 @@ namespace Resource osg::ref_ptr KeyframeManager::get(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 33a9ab99a1..ec6bea02d7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -358,10 +358,7 @@ namespace Resource bool SceneManager::checkLoaded(const std::string &name, double timeStamp) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); - - return mCache->checkInObjectCache(normalized, timeStamp); + return mCache->checkInObjectCache(mVFS->normalizeFilename(name), timeStamp); } /// @brief Callback to read image files from the VFS. @@ -533,8 +530,7 @@ namespace Resource osg::ref_ptr SceneManager::getTemplate(const std::string &name, bool compile) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mCache->getRefFromObjectCache(normalized); if (obj) @@ -603,8 +599,7 @@ namespace Resource osg::ref_ptr SceneManager::cacheInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr node = createInstance(normalized); @@ -642,8 +637,7 @@ namespace Resource osg::ref_ptr SceneManager::getInstance(const std::string &name) { - std::string normalized = name; - mVFS->normalizeFilename(normalized); + const std::string normalized = mVFS->normalizeFilename(name); osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); if (obj.get()) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index f799719e31..1cb8745497 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -86,9 +86,11 @@ namespace VFS return mIndex.find(normalized) != mIndex.end(); } - void Manager::normalizeFilename(std::string &name) const + std::string Manager::normalizeFilename(const std::string& name) const { - normalize_path(name, mStrict); + std::string result = name; + normalize_path(result, mStrict); + return result; } std::string Manager::getArchive(const std::string& name) const @@ -105,6 +107,6 @@ namespace VFS RecursiveDirectoryIterator Manager::getRecursiveDirectoryIterator(const std::string& path) const { - return RecursiveDirectoryIterator(mIndex, path); + return RecursiveDirectoryIterator(mIndex, normalizeFilename(path)); } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index 8656f3bf10..b98d8021d4 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -87,7 +87,7 @@ namespace VFS /// Normalize the given filename, making slashes/backslashes consistent, and lower-casing if mStrict is false. /// @note May be called from any thread once the index has been built. - void normalizeFilename(std::string& name) const; + [[nodiscard]] std::string normalizeFilename(const std::string& name) const; /// Retrieve a file by name. /// @note Throws an exception if the file can not be found. From 681728209735520de5daf3657cc5c754783c5028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Sat, 11 Sep 2021 15:49:47 +0200 Subject: [PATCH 025/137] Move getFileExtension to common header and use instead of repeating same code --- apps/openmw/mwgui/loadingscreen.cpp | 21 +++++++-------------- apps/openmw/mwrender/animation.cpp | 7 +++---- components/fontloader/fontloader.cpp | 4 ++-- components/misc/pathhelpers.hpp | 19 +++++++++++++++++++ components/resource/bulletshapemanager.cpp | 8 ++------ components/resource/imagemanager.cpp | 6 ++---- components/resource/keyframemanager.cpp | 4 ++-- components/resource/scenemanager.cpp | 13 +++---------- 8 files changed, 40 insertions(+), 42 deletions(-) create mode 100644 components/misc/pathhelpers.hpp diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 7f454925fd..ef8eea0104 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -66,23 +67,15 @@ namespace MWGui void LoadingScreen::findSplashScreens() { - /* priority given to the left */ - const std::array supported_extensions {{".tga", ".dds", ".ktx", ".png", ".bmp", ".jpeg", ".jpg"}}; + auto isSupportedExtension = [](const std::string_view& ext) { + static const std::array supported_extensions{ {"tga", "dds", "ktx", "png", "bmp", "jpeg", "jpg"} }; + return !ext.empty() && std::find(supported_extensions.begin(), supported_extensions.end(), ext) != supported_extensions.end(); + }; for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator("Splash/")) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos) - { - for (auto const& extension : supported_extensions) - { - if (name.compare(pos, name.size() - pos, extension) == 0) - { - mSplashScreens.push_back(name); - break; /* based on priority */ - } - } - } + if (isSupportedExtension(Misc::getFileExtension(name))) + mSplashScreens.push_back(name); } if (mSplashScreens.empty()) Log(Debug::Warning) << "Warning: no splash screens found!"; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3df902e469..1dc42db47c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -600,8 +601,7 @@ namespace MWRender for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".kf") == 0) + if (Misc::getFileExtension(name) == "kf") addSingleAnimSource(name, baseModel); } } @@ -1292,8 +1292,7 @@ namespace MWRender for (const auto& name : resourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".nif") == 0) + if (Misc::getFileExtension(name) == "nif") loadBonesFromFile(node, name, resourceSystem); } } diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index 9ba62f7694..da43cc38ec 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -17,6 +17,7 @@ #include +#include #include #include @@ -193,8 +194,7 @@ namespace Gui { for (const auto& name : mVFS->getRecursiveDirectoryIterator("Fonts/")) { - size_t pos = name.find_last_of('.'); - if (pos != std::string::npos && name.compare(pos, name.size() - pos, ".fnt") == 0) + if (Misc::getFileExtension(name) == "fnt") loadBitmapFont(name, exportToFile); } } diff --git a/components/misc/pathhelpers.hpp b/components/misc/pathhelpers.hpp new file mode 100644 index 0000000000..88913a1f7b --- /dev/null +++ b/components/misc/pathhelpers.hpp @@ -0,0 +1,19 @@ +#ifndef OPENMW_COMPONENTS_MISC_PATHHELPERS_H +#define OPENMW_COMPONENTS_MISC_PATHHELPERS_H + +#include + +namespace Misc +{ + inline std::string_view getFileExtension(std::string_view file) + { + if (auto extPos = file.find_last_of('.'); extPos != std::string::npos) + { + file.remove_prefix(extPos + 1); + return file; + } + return {}; + } +} + +#endif diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 98878df152..0d0f81962b 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -129,12 +130,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & shape = osg::ref_ptr(static_cast(obj.get())); else { - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); - - if (ext == "nif") + if (Misc::getFileExtension(normalized) == "nif") { NifBullet::BulletNifLoader loader; shape = loader.load(*mNifFileManager->get(normalized)); diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index ba46b5cce9..a544b7b621 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "objectcache.hpp" @@ -102,10 +103,7 @@ namespace Resource return mWarningImage; } - size_t extPos = normalized.find_last_of('.'); - std::string ext; - if (extPos != std::string::npos && extPos+1 < normalized.size()) - ext = normalized.substr(extPos+1); + const std::string ext(Misc::getFileExtension(normalized)); osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); if (!reader) { diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 6d431fb3a5..01a8639aa2 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "animation.hpp" @@ -141,8 +142,7 @@ namespace Resource else { osg::ref_ptr loaded (new SceneUtil::KeyframeHolder); - std::string ext = Resource::getFileExtension(normalized); - if (ext == "kf") + if (Misc::getFileExtension(normalized) == "kf") { NifOsg::Loader::loadKf(Nif::NIFFilePtr(new Nif::NIFFile(mVFS->getNormalized(normalized), normalized)), *loaded.get()); } diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ec6bea02d7..76e73da384 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -388,12 +389,12 @@ namespace Resource osg::ref_ptr load (const std::string& normalizedFilename, const VFS::Manager* vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) { - std::string ext = Resource::getFileExtension(normalizedFilename); + auto ext = Misc::getFileExtension(normalizedFilename); if (ext == "nif") return NifOsg::Loader::load(nifFileManager->get(normalizedFilename), imageManager); else { - osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(ext); + osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext)); if (!reader) { std::stringstream errormsg; @@ -816,12 +817,4 @@ namespace Resource shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } - - std::string getFileExtension(const std::string& file) - { - size_t extPos = file.find_last_of('.'); - if (extPos != std::string::npos && extPos+1 < file.size()) - return file.substr(extPos+1); - return std::string(); - } } From dd6649d519939b157d2f9b2089aa0c1defc2f356 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 14 Sep 2021 22:59:04 +0200 Subject: [PATCH 026/137] Create cmake.yml (#3107) * Create cmake.yml * Update cmake.yml * Update cmake.yml * Update cmake.yml * Update cmake.yml --- .github/workflows/cmake.yml | 53 +++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/cmake.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000000..18221b3cb6 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,53 @@ +name: CMake + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + BUILD_TYPE: RelWithDebInfo + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Add OpenMW PPA Dependancies + run: sudo add-apt-repository ppa:openmw/openmw; sudo apt-get update + + - name: Install Building Dependancies + run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic + + - name: Configure CMake + run: | + mkdir build + mkdir instdir + cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX:PATH=instdir + + - name: Build + run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 + + - name: Test + working-directory: ${{github.workspace}}/build + run: ctest -C ${{env.BUILD_TYPE}} + + - name: Install OpenMW + shell: bash + run: cmake --install . + + - name: Upload OpenMW Artifact + shell: bash + working-directory: instdir + run: | + ls -laR + 7z a ../build_artifact.7z . + + - name: Upload + uses: actions/upload-artifact@v1 + with: + path: ./build_artifact.7z + name: build_artifact.7z From c658acc2b311bb6676597845fe4464d2409314c4 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 15 Sep 2021 16:50:19 +0200 Subject: [PATCH 027/137] pipeline only on pull_requests (#3109) * Create debs * Update cmake.yml * give ccache a try * ccache round 2 * Update cmake.yml --- .github/workflows/cmake.yml | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 18221b3cb6..c220030050 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -1,8 +1,6 @@ name: CMake on: - push: - branches: [ master ] pull_request: branches: [ master ] @@ -22,31 +20,30 @@ jobs: - name: Install Building Dependancies run: sudo CI/install_debian_deps.sh gcc openmw-deps openmw-deps-dynamic - - name: Configure CMake - run: | - mkdir build - mkdir instdir - cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX:PATH=instdir + - name: Prime ccache + uses: hendrikmuhs/ccache-action@v1 + with: + key: ${{ matrix.os }}-${{ env.BUILD_TYPE }} + max-size: 1000M - - name: Build - run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 + - name: Configure + run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - - name: Test - working-directory: ${{github.workspace}}/build - run: ctest -C ${{env.BUILD_TYPE}} + - name: Buil + run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 - - name: Install OpenMW + - name: Install shell: bash run: cmake --install . - - name: Upload OpenMW Artifact + - name: Create Artifact shell: bash - working-directory: instdir + working-directory: install run: | ls -laR 7z a ../build_artifact.7z . - - name: Upload + - name: Upload Artifact uses: actions/upload-artifact@v1 with: path: ./build_artifact.7z From f62adab43a821a75beb9eed0f68ae7af82af81ce Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 16 Sep 2021 20:11:19 +0000 Subject: [PATCH 028/137] Avoid the terrain sync completely in most cases (#3103) We can take elsid's commit 605cb8d further by avoiding the terrain sync completely in most cases. Currently in changeCellGrid we wait for a new preloading task to ensure the getPagedRefnums for the new active cells have been filled in by object paging. This is usually not necessary because we have already completed a preload in the past containing these active cells. With this PR we remember what we preloaded and skip the terrain sync if it is not needed. --- apps/openmw/mwworld/cellpreloader.cpp | 60 ++++++++++++++++--------- apps/openmw/mwworld/cellpreloader.hpp | 4 ++ apps/openmw/mwworld/scene.cpp | 10 ++--- components/resource/resourcemanager.hpp | 1 + 4 files changed, 48 insertions(+), 27 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index c29d975c24..f2f2ebc5a6 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -21,6 +21,29 @@ #include "cellstore.hpp" #include "class.hpp" +namespace +{ + template + bool contains(const std::vector& container, + const Contained& contained, float tolerance=1.f) + { + for (const auto& pos : contained) + { + bool found = false; + for (const auto& pos2 : container) + { + if ((pos.first-pos2.first).length2() < tolerance*tolerance && pos.second == pos2.second) + { + found = true; + break; + } + } + if (!found) return false; + } + return true; + } +} + namespace MWWorld { @@ -224,6 +247,7 @@ namespace MWWorld , mPreloadInstances(true) , mLastResourceCacheUpdate(0.0) , mStoreViewsFailCount(0) + , mLoadedTerrainTimestamp(0.0) { } @@ -369,7 +393,11 @@ namespace MWWorld setTerrainPreloadPositions(std::vector()); } else + { mStoreViewsFailCount = 0; + mLoadedTerrainPositions = mTerrainPreloadPositions; + mLoadedTerrainTimestamp = timestamp; + } mTerrainPreloadItem = nullptr; } } @@ -436,10 +464,8 @@ namespace MWWorld void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid *exceptPos) { - const float resetThreshold = ESM::Land::REAL_SIZE; - for (const auto& pos : mTerrainPreloadPositions) - if (exceptPos && (pos.first-exceptPos->first).length2() < resetThreshold*resetThreshold && pos.second == exceptPos->second) - return; + if (exceptPos && contains(mTerrainPreloadPositions, std::array {*exceptPos}, ESM::Land::REAL_SIZE)) + return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) { mTerrainPreloadItem->abort(); @@ -448,28 +474,13 @@ namespace MWWorld setTerrainPreloadPositions(std::vector()); } - bool contains(const std::vector& container, const std::vector& contained) - { - for (const auto& pos : contained) - { - bool found = false; - for (const auto& pos2 : container) - { - if ((pos.first-pos2.first).length2() < 1 && pos.second == pos2.second) - { - found = true; - break; - } - } - if (!found) return false; - } - return true; - } - void CellPreloader::setTerrainPreloadPositions(const std::vector &positions) { if (positions.empty()) + { mTerrainPreloadPositions.clear(); + mLoadedTerrainPositions.clear(); + } else if (contains(mTerrainPreloadPositions, positions)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) @@ -496,5 +507,10 @@ namespace MWWorld } } } + + bool CellPreloader::isTerrainLoaded(const CellPreloader::PositionCellGrid &position, double referenceTime) const + { + return mLoadedTerrainTimestamp + mResourceSystem->getSceneManager()->getExpiryDelay() > referenceTime && contains(mLoadedTerrainPositions, std::array {position}, ESM::Land::REAL_SIZE); + } } diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index e2eea33146..39436dc5ad 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -79,6 +79,7 @@ namespace MWWorld bool syncTerrainLoad(const std::vector &positions, double timestamp, Loading::Listener& listener); void abortTerrainPreloadExcept(const PositionCellGrid *exceptPos); + bool isTerrainLoaded(const CellPreloader::PositionCellGrid &position, double referenceTime) const; private: Resource::ResourceSystem* mResourceSystem; @@ -119,6 +120,9 @@ namespace MWWorld std::vector mTerrainPreloadPositions; osg::ref_ptr mTerrainPreloadItem; osg::ref_ptr mUpdateCacheItem; + + std::vector mLoadedTerrainPositions; + double mLoadedTerrainTimestamp; }; } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 399d0e6d7f..89349d329a 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -628,7 +628,10 @@ namespace MWWorld osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); mRendering.setActiveGrid(newGrid); - preloadTerrain(pos, true); + if (mRendering.pagingUnlockCache()) + mPreloader->abortTerrainPreloadExcept(nullptr); + if (!mPreloader->isTerrainLoaded(std::make_pair(pos, newGrid), mRendering.getReferenceTime())) + preloadTerrain(pos, true); mPagedRefs.clear(); mRendering.getPagedRefnums(newGrid, mPagedRefs); @@ -1241,10 +1244,7 @@ namespace MWWorld { std::vector vec; vec.emplace_back(pos, gridCenterToBounds(getNewGridCenter(pos))); - if (sync && mRendering.pagingUnlockCache()) - mPreloader->abortTerrainPreloadExcept(nullptr); - else - mPreloader->abortTerrainPreloadExcept(&vec[0]); + mPreloader->abortTerrainPreloadExcept(&vec[0]); mPreloader->setTerrainPreloadPositions(vec); if (!sync) return; diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index ccb065e3bf..b2b71f4635 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -59,6 +59,7 @@ namespace Resource /// How long to keep objects in cache after no longer being referenced. void setExpiryDelay (double expiryDelay) override { mExpiryDelay = expiryDelay; } + float getExpiryDelay() const { return mExpiryDelay; } const VFS::Manager* getVFS() const { return mVFS; } From 4ff5a04e9b3470847dd77efe3261e43c047c2b4f Mon Sep 17 00:00:00 2001 From: Pi03k Date: Thu, 16 Sep 2021 20:45:23 +0200 Subject: [PATCH 029/137] Remove redundant qt-related cmake macros --- apps/opencs/CMakeLists.txt | 28 ++++++++++++++-------------- cmake/OpenMWMacros.cmake | 25 ++----------------------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 60bd0f4ded..0ffa3da559 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -8,11 +8,11 @@ opencs_units (model/doc document operation saving documentmanager loader runner operationholder ) -opencs_units_noqt (model/doc +opencs_units (model/doc stage savingstate savingstages blacklist messages ) -opencs_hdrs_noqt (model/doc +opencs_hdrs (model/doc state ) @@ -23,14 +23,14 @@ opencs_units (model/world ) -opencs_units_noqt (model/world +opencs_units (model/world universalid record commands columnbase columnimp scriptcontext cell refidcollection refidadapter refiddata refidadapterimp ref collectionbase refcollection columns infocollection tablemimedata cellcoordinates cellselection resources resourcesmanager scope pathgrid landtexture land nestedtablewrapper nestedcollection nestedcoladapterimp nestedinfocollection idcompletionmanager metadata defaultgmsts infoselectwrapper commandmacro ) -opencs_hdrs_noqt (model/world +opencs_hdrs (model/world columnimp idcollection collection info subcellcollection ) @@ -39,14 +39,14 @@ opencs_units (model/tools tools reportmodel mergeoperation ) -opencs_units_noqt (model/tools +opencs_units (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck mergestages gmstcheck topicinfocheck journalcheck enchantmentcheck ) -opencs_hdrs_noqt (model/tools +opencs_hdrs (model/tools mergestate ) @@ -57,11 +57,11 @@ opencs_units (view/doc ) -opencs_units_noqt (view/doc +opencs_units (view/doc subviewfactory ) -opencs_hdrs_noqt (view/doc +opencs_hdrs (view/doc subviewfactoryimp ) @@ -74,7 +74,7 @@ opencs_units (view/world bodypartcreator landtexturecreator landcreator ) -opencs_units_noqt (view/world +opencs_units (view/world subviews enumdelegate vartypedelegate recordstatusdelegate idtypedelegate datadisplaydelegate scripthighlighter idvalidator dialoguecreator idcompletiondelegate colordelegate dragdroputils @@ -92,12 +92,12 @@ opencs_units (view/render cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands ) -opencs_units_noqt (view/render +opencs_units (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase cellarrow cellmarker cellborder pathgrid ) -opencs_hdrs_noqt (view/render +opencs_hdrs (view/render mask ) @@ -106,7 +106,7 @@ opencs_units (view/tools reportsubview reporttable searchsubview searchbox merge ) -opencs_units_noqt (view/tools +opencs_units (view/tools subviews ) @@ -119,11 +119,11 @@ opencs_units (model/prefs shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting ) -opencs_units_noqt (model/prefs +opencs_units (model/prefs category ) -opencs_units_noqt (model/filter +opencs_units (model/filter node unarynode narynode leafnode booleannode parser andnode ornode notnode textnode valuenode ) diff --git a/cmake/OpenMWMacros.cmake b/cmake/OpenMWMacros.cmake index 5bee345344..0adc185b38 100644 --- a/cmake/OpenMWMacros.cmake +++ b/cmake/OpenMWMacros.cmake @@ -93,42 +93,21 @@ add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") endmacro (add_unit) -macro (add_qt_unit project dir unit) -add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -add_file (${project} _SRC ${comp} "${dir}/${unit}.cpp") -endmacro (add_qt_unit) - macro (add_hdr project dir unit) add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") endmacro (add_hdr) -macro (add_qt_hdr project dir unit) -add_file (${project} _HDR ${comp} "${dir}/${unit}.hpp") -endmacro (add_qt_hdr) - macro (opencs_units dir) foreach (u ${ARGN}) -add_qt_unit (OPENCS ${dir} ${u}) -endforeach (u) -endmacro (opencs_units) - -macro (opencs_units_noqt dir) -foreach (u ${ARGN}) add_unit (OPENCS ${dir} ${u}) endforeach (u) -endmacro (opencs_units_noqt) +endmacro (opencs_units) macro (opencs_hdrs dir) foreach (u ${ARGN}) -add_qt_hdr (OPENCS ${dir} ${u}) -endforeach (u) -endmacro (opencs_hdrs) - -macro (opencs_hdrs_noqt dir) -foreach (u ${ARGN}) add_hdr (OPENCS ${dir} ${u}) endforeach (u) -endmacro (opencs_hdrs_noqt) +endmacro (opencs_hdrs) include(CMakeParseArguments) From e4648cec48ba9b28e8907b23ffa68d63c9f1e6e8 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Fri, 17 Sep 2021 17:24:11 +0000 Subject: [PATCH 030/137] kill buil (#3118) He was a great fellow. We will forever be in debt for his services. Time to say good-bye. --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index c220030050..f9375a3ba5 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -29,7 +29,7 @@ jobs: - name: Configure run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - - name: Buil + - name: Build run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 - name: Install From b9825afb8a8c97085560f58febd2a9175a14971b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 2 Sep 2021 01:06:27 +0200 Subject: [PATCH 031/137] Fix build with system static OpenSceneGraph * Add dependency to libraries required by OSG but missing when linking with OSG system library. * Use find_package for already defined dependencies. --- CMakeLists.txt | 5 +++++ components/CMakeLists.txt | 14 +++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d14762ef5f..1b8350a04b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,11 @@ if(POLICY CMP0083) cmake_policy(SET CMP0083 NEW) endif() +# to link with freetype library +if(POLICY CMP0079) + cmake_policy(SET CMP0079 NEW) +endif() + option(OPENMW_GL4ES_MANUAL_INIT "Manually initialize gl4es. This is more reliable on platforms without a windowing system. Requires gl4es to be configured with -DNOEGL=ON -DNO_LOADER=ON -DNO_INIT_CONSTRUCTOR=ON." OFF) if(OPENMW_GL4ES_MANUAL_INIT) add_definitions(-DOPENMW_GL4ES_MANUAL_INIT) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5b79a096cd..7ee3d184d8 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -288,6 +288,15 @@ if (GIT_CHECKOUT) add_dependencies (components git-version) endif (GIT_CHECKOUT) +if (OSG_STATIC AND CMAKE_SYSTEM_NAME MATCHES "Linux") + find_package(X11 REQUIRED COMPONENTS Xinerama Xrandr) + target_link_libraries(components ${CMAKE_DL_LIBS} X11::X11 X11::Xinerama X11::Xrandr) + find_package(Fontconfig MODULE) + if(Fontconfig_FOUND) + target_link_libraries(components Fontconfig::Fontconfig) + endif() +endif() + if (WIN32) target_link_libraries(components shlwapi) endif() @@ -329,7 +338,10 @@ if(OSG_STATIC) if(OPENMW_USE_SYSTEM_OSG) # OSG plugin pkgconfig files are missing these dependencies. # https://github.com/openscenegraph/OpenSceneGraph/issues/1052 - target_link_libraries(components freetype jpeg png) + find_package(Freetype REQUIRED) + find_package(JPEG REQUIRED) + find_package(PNG REQUIRED) + target_link_libraries(components Freetype::Freetype JPEG::JPEG PNG::PNG) endif() endif(OSG_STATIC) From 70e210735af9b355f64b3b8cb749b579b1cfc0e5 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 18 Sep 2021 15:57:00 +0300 Subject: [PATCH 032/137] Optimize terrain editing brush drawing performance --- apps/opencs/view/render/brushdraw.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/brushdraw.cpp b/apps/opencs/view/render/brushdraw.cpp index 255a13a12e..6b33e336ea 100644 --- a/apps/opencs/view/render/brushdraw.cpp +++ b/apps/opencs/view/render/brushdraw.cpp @@ -18,6 +18,8 @@ CSVRender::BrushDraw::BrushDraw(osg::ref_ptr parentNode, bool textur mBrushDrawNode = new osg::Group(); mGeometry = new osg::Geometry(); mBrushDrawNode->addChild(mGeometry); + mBrushDrawNode->getOrCreateStateSet()->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + mBrushDrawNode->getOrCreateStateSet()->setRenderBinDetails(11, "RenderBin"); mParentNode->addChild(mBrushDrawNode); if (mTextureMode) mLandSizeFactor = static_cast(ESM::Land::REAL_SIZE) / static_cast(ESM::Land::LAND_TEXTURE_SIZE); @@ -122,7 +124,14 @@ void CSVRender::BrushDraw::buildSquareGeometry(const float& radius, const osg::V const float brushOutlineHeight (1.0f); float diameter = radius * 2; int resolution = static_cast(2.f * diameter / mLandSizeFactor); //half a vertex resolution - float resAdjustedLandSizeFactor = mLandSizeFactor / 2; + float resAdjustedLandSizeFactor = mLandSizeFactor / 2; //128 + + if (resolution > 128) // limit accuracy for performance + { + resolution = 128; + resAdjustedLandSizeFactor = diameter / resolution; + } + osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); for (int i = 0; i < resolution; i++) @@ -215,7 +224,8 @@ void CSVRender::BrushDraw::buildCircleGeometry(const float& radius, const osg::V osg::ref_ptr geom (new osg::Geometry()); osg::ref_ptr vertices (new osg::Vec3Array()); osg::ref_ptr colors (new osg::Vec4Array()); - const int amountOfPoints = (osg::PI * 2.0f) * radius / 20; + + const int amountOfPoints = 128; const float step ((osg::PI * 2.0f) / static_cast(amountOfPoints)); const float brushOutlineHeight (1.0f); osg::Vec4f lineColor(1.0f, 1.0f, 1.0f, 0.6f); From 179f91276af6b766c168900ae5ccc718b6b25e64 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sat, 18 Sep 2021 16:21:11 +0000 Subject: [PATCH 033/137] lightmanager.cpp (#3121) --- components/sceneutil/lightmanager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 63a475566b..50d57fd471 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1204,7 +1204,6 @@ namespace SceneUtil const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) { - bool isReflection = isReflectionCamera(camera); osg::observer_ptr camPtr (camera); auto it = mLightsInViewSpace.find(camPtr); @@ -1212,6 +1211,8 @@ namespace SceneUtil { it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; + bool isReflection = isReflectionCamera(camera); + for (const auto& transform : mLights) { osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix); From 76be2e91e5f69b61077c8287331319097529492c Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sat, 18 Sep 2021 18:25:04 +0200 Subject: [PATCH 034/137] Fixed an issue where keyword search expected the text to be all ASCII characters --- apps/openmw/mwdialogue/keywordsearch.hpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index f296f223fb..3cd8517651 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -68,6 +68,19 @@ public: return false; } + static bool isWhitespaceUTF8(const int utf8Char) + { + if (utf8Char >= 0 && utf8Char <= UCHAR_MAX) + { + //That function has undefined behavior if the character doesn't fit in unsigned char + return std::isspace(utf8Char); + } + else + { + return false; + } + } + static bool sortMatches(const Match& left, const Match& right) { return left.mBeg < right.mBeg; @@ -83,7 +96,7 @@ public: { Point prev = i; --prev; - if(isalpha(*prev)) + if(!isWhitespaceUTF8(*prev)) continue; } From 8d86d90782b495fa6366641b5ad139f278bb780a Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sat, 18 Sep 2021 22:00:26 +0200 Subject: [PATCH 035/137] remove whitespace --- components/sceneutil/lightmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 50d57fd471..1f7d9d2db2 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1212,7 +1212,7 @@ namespace SceneUtil it = mLightsInViewSpace.insert(std::make_pair(camPtr, LightSourceViewBoundCollection())).first; bool isReflection = isReflectionCamera(camera); - + for (const auto& transform : mLights) { osg::Matrixf worldViewMat = transform.mWorldMatrix * (*viewMatrix); From f1e3c55ea3fbab43ae6268b1e2332048fd34b61f Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 19 Sep 2021 10:27:48 +0200 Subject: [PATCH 036/137] Fix deadlock in Lua worker thread (#6286) --- apps/openmw/engine.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 772af3759e..bbe4f25f69 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -875,7 +875,14 @@ public: void join() { if (mThread) + { + { + std::lock_guard lk(mMutex); + mJoinRequest = true; + } + mCV.notify_one(); mThread->join(); + } } private: @@ -891,10 +898,12 @@ private: void threadBody() { - while (!mEngine->mViewer->done() && !mEngine->mEnvironment.getStateManager()->hasQuitRequest()) + while (true) { std::unique_lock lk(mMutex); - mCV.wait(lk, [&]{ return mUpdateRequest; }); + mCV.wait(lk, [&]{ return mUpdateRequest || mJoinRequest; }); + if (mJoinRequest) + break; update(); @@ -908,6 +917,7 @@ private: std::mutex mMutex; std::condition_variable mCV; bool mUpdateRequest = false; + bool mJoinRequest = false; double mDt = 0; bool mIsGuiMode = false; std::optional mThread; From 095f4b2ed58584e77bdbdcb9202e3359bb02b9ba Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Sun, 19 Sep 2021 11:13:28 +0000 Subject: [PATCH 037/137] material.cpp (#3117) --- components/terrain/material.cpp | 59 +++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 2fbd0a7fc0..28297fb456 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -166,6 +166,29 @@ namespace mValue->setSource0_RGB(osg::TexEnvCombine::PREVIOUS); } }; + + class UniformCollection + { + public: + static const UniformCollection& value() + { + static UniformCollection instance; + return instance; + } + + osg::ref_ptr mDiffuseMap; + osg::ref_ptr mBlendMap; + osg::ref_ptr mNormalMap; + osg::ref_ptr mColorMode; + + UniformCollection() + : mDiffuseMap(new osg::Uniform("diffuseMap", 0)) + , mBlendMap(new osg::Uniform("blendMap", 1)) + , mNormalMap(new osg::Uniform("normalMap", 2)) + , mColorMode(new osg::Uniform("colorMode", 2)) + { + } + }; } namespace Terrain @@ -199,32 +222,28 @@ namespace Terrain } } - int texunit = 0; - if (useShaders) { - stateset->setTextureAttributeAndModes(texunit, it->mDiffuseMap); + stateset->setTextureAttributeAndModes(0, it->mDiffuseMap); if (layerTileSize != 1.f) - stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); + stateset->setTextureAttributeAndModes(0, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("diffuseMap", texunit)); + stateset->addUniform(UniformCollection::value().mDiffuseMap); if (!blendmaps.empty()) { - ++texunit; osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); - stateset->setTextureAttributeAndModes(texunit, blendmap.get()); - stateset->setTextureAttributeAndModes(texunit, BlendmapTexMat::value(blendmapScale)); - stateset->addUniform(new osg::Uniform("blendMap", texunit)); + stateset->setTextureAttributeAndModes(1, blendmap.get()); + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + stateset->addUniform(UniformCollection::value().mBlendMap); } if (it->mNormalMap) { - ++texunit; - stateset->setTextureAttributeAndModes(texunit, it->mNormalMap); - stateset->addUniform(new osg::Uniform("normalMap", texunit)); + stateset->setTextureAttributeAndModes(2, it->mNormalMap); + stateset->addUniform(UniformCollection::value().mNormalMap); } Shader::ShaderManager::DefineMap defineMap; @@ -242,31 +261,27 @@ namespace Terrain } stateset->setAttributeAndModes(shaderManager->getProgram(vertexShader, fragmentShader)); - stateset->addUniform(new osg::Uniform("colorMode", 2)); + stateset->addUniform(UniformCollection::value().mColorMode); } else { // Add the actual layer texture osg::ref_ptr tex = it->mDiffuseMap; - stateset->setTextureAttributeAndModes(texunit, tex.get()); + stateset->setTextureAttributeAndModes(0, tex.get()); if (layerTileSize != 1.f) - stateset->setTextureAttributeAndModes(texunit, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); - - ++texunit; + stateset->setTextureAttributeAndModes(0, LayerTexMat::value(layerTileSize), osg::StateAttribute::ON); // Multiply by the alpha map if (!blendmaps.empty()) { osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); - stateset->setTextureAttributeAndModes(texunit, blendmap.get()); + stateset->setTextureAttributeAndModes(1, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. - stateset->setTextureAttributeAndModes(texunit, BlendmapTexMat::value(blendmapScale)); - stateset->setTextureAttributeAndModes(texunit, TexEnvCombine::value(), osg::StateAttribute::ON); - - ++texunit; + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + stateset->setTextureAttributeAndModes(1, TexEnvCombine::value(), osg::StateAttribute::ON); } } From 2f25257a3e1ed8a91328bd1e33cdb30710865628 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 19 Sep 2021 14:38:27 +0200 Subject: [PATCH 038/137] Move LuaState::makeReadOnly(sol::table) out of the class because it doesn't need to access LuaState internals. --- apps/openmw/mwlua/camerabindings.cpp | 2 +- apps/openmw/mwlua/inputbindings.cpp | 10 +++++----- apps/openmw/mwlua/luabindings.cpp | 18 ++++++++--------- apps/openmw/mwlua/settingsbindings.cpp | 2 +- apps/openmw/mwlua/uibindings.cpp | 2 +- apps/openmw_test_suite/lua/test_lua.cpp | 6 +++--- components/lua/luastate.cpp | 26 ++++++++++++++----------- components/lua/luastate.hpp | 11 +++++------ components/lua/scriptscontainer.cpp | 6 +++--- components/lua/scriptscontainer.hpp | 2 +- 10 files changed, 44 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index 68f2331b9e..46bf079d65 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -7,7 +7,7 @@ namespace MWLua { sol::table api(context.mLua->sol(), sol::create); // TODO - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } } diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index f815d9c344..aaa00f3da9 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -48,7 +48,7 @@ namespace MWLua api["getControlSwitch"] = [input](const std::string& key) { return input->getControlSwitch(key); }; api["setControlSwitch"] = [input](const std::string& key, bool v) { input->toggleControlSwitch(key, v); }; - api["ACTION"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + api["ACTION"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "GameMenu", MWInput::A_GameMenu, "Screenshot", MWInput::A_Screenshot, "Inventory", MWInput::A_Inventory, @@ -102,7 +102,7 @@ namespace MWLua "ZoomOut", MWInput::A_ZoomOut )); - api["CONTROL_SWITCH"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + api["CONTROL_SWITCH"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "Controls", "playercontrols", "Fighting", "playerfighting", "Jumping", "playerjumping", @@ -112,7 +112,7 @@ namespace MWLua "VanityMode", "vanitymode" )); - api["CONTROLLER_BUTTON"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + api["CONTROLLER_BUTTON"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "A", SDL_CONTROLLER_BUTTON_A, "B", SDL_CONTROLLER_BUTTON_B, "X", SDL_CONTROLLER_BUTTON_X, @@ -130,7 +130,7 @@ namespace MWLua "DPadRight", SDL_CONTROLLER_BUTTON_DPAD_RIGHT )); - api["CONTROLLER_AXIS"] = context.mLua->makeReadOnly(context.mLua->sol().create_table_with( + api["CONTROLLER_AXIS"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "LeftX", SDL_CONTROLLER_AXIS_LEFTX, "LeftY", SDL_CONTROLLER_AXIS_LEFTY, "RightX", SDL_CONTROLLER_AXIS_RIGHTX, @@ -144,7 +144,7 @@ namespace MWLua "MoveLeftRight", SDL_CONTROLLER_AXIS_MAX + MWInput::A_MoveLeftRight )); - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index ed788eb525..e37241d692 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -18,7 +18,7 @@ namespace MWLua sol::table res(lua.sol(), sol::create); for (const std::string& v : values) res[v] = v; - return lua.makeReadOnly(res); + return LuaUtil::makeReadOnly(res); } sol::table initCorePackage(const Context& context) @@ -43,7 +43,7 @@ namespace MWLua "Activator", "Armor", "Book", "Clothing", "Creature", "Door", "Ingredient", "Light", "Miscellaneous", "NPC", "Player", "Potion", "Static", "Weapon" }); - api["EQUIPMENT_SLOT"] = lua->makeReadOnly(lua->sol().create_table_with( + api["EQUIPMENT_SLOT"] = LuaUtil::makeReadOnly(lua->sol().create_table_with( "Helmet", MWWorld::InventoryStore::Slot_Helmet, "Cuirass", MWWorld::InventoryStore::Slot_Cuirass, "Greaves", MWWorld::InventoryStore::Slot_Greaves, @@ -64,7 +64,7 @@ namespace MWLua "CarriedLeft", MWWorld::InventoryStore::Slot_CarriedLeft, "Ammunition", MWWorld::InventoryStore::Slot_Ammunition )); - return lua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } sol::table initWorldPackage(const Context& context) @@ -107,7 +107,7 @@ namespace MWLua // return GObjectList{worldView->selectObjects(query, false)}; }; // TODO: add world.placeNewObject(recordId, cell, pos, [rot]) - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } sol::table initNearbyPackage(const Context& context) @@ -137,7 +137,7 @@ namespace MWLua // TODO: Maybe use sqlite // return LObjectList{worldView->selectObjects(query, true)}; }; - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } sol::table initQueryPackage(const Context& context) @@ -148,7 +148,7 @@ namespace MWLua query[t] = Queries::Query(std::string(t)); for (const QueryFieldGroup& group : getBasicQueryFieldGroups()) query[group.mName] = initFieldGroup(context, group); - return query; // makeReadonly is applied by LuaState::addCommonPackage + return query; // makeReadOnly is applied by LuaState::addCommonPackage } sol::table initFieldGroup(const Context& context, const QueryFieldGroup& group) @@ -163,12 +163,12 @@ namespace MWLua { const std::string& name = field->path()[i]; if (subgroup[name] == sol::nil) - subgroup[name] = context.mLua->makeReadOnly(context.mLua->newTable()); - subgroup = context.mLua->getMutableFromReadOnly(subgroup[name]); + subgroup[name] = LuaUtil::makeReadOnly(context.mLua->newTable()); + subgroup = LuaUtil::getMutableFromReadOnly(subgroup[name]); } subgroup[field->path().back()] = field; } - return context.mLua->makeReadOnly(res); + return LuaUtil::makeReadOnly(res); } } diff --git a/apps/openmw/mwlua/settingsbindings.cpp b/apps/openmw/mwlua/settingsbindings.cpp index 0e267182ac..12dd69f73a 100644 --- a/apps/openmw/mwlua/settingsbindings.cpp +++ b/apps/openmw/mwlua/settingsbindings.cpp @@ -62,7 +62,7 @@ namespace MWLua else return sol::make_object(lua->sol(), value.getFloat()); }; - return lua->makeReadOnly(config); + return LuaUtil::makeReadOnly(config); } sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, true, false); } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index cb14c41621..4fae84cd40 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -12,7 +12,7 @@ namespace MWLua { luaManager->addUIMessage(message); }; - return context.mLua->makeReadOnly(api); + return LuaUtil::makeReadOnly(api); } } diff --git a/apps/openmw_test_suite/lua/test_lua.cpp b/apps/openmw_test_suite/lua/test_lua.cpp index 9405dba4b2..32d4ea49b8 100644 --- a/apps/openmw_test_suite/lua/test_lua.cpp +++ b/apps/openmw_test_suite/lua/test_lua.cpp @@ -119,7 +119,7 @@ return { EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)"); EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value"); - EXPECT_EQ(mLua.getMutableFromReadOnly(mLua.makeReadOnly(script)), script); + EXPECT_EQ(LuaUtil::getMutableFromReadOnly(LuaUtil::makeReadOnly(script)), script); } TEST_F(LuaStateTest, Print) @@ -150,8 +150,8 @@ return { { LuaUtil::LuaState lua(mVFS.get()); - sol::table api1 = lua.makeReadOnly(lua.sol().create_table_with("name", "api1")); - sol::table api2 = lua.makeReadOnly(lua.sol().create_table_with("name", "api2")); + sol::table api1 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api1")); + sol::table api2 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api2")); sol::table script1 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api1}}); diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index fd42aa7fb2..8e4719dba4 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -67,27 +67,31 @@ namespace LuaUtil mSandboxEnv = sol::nil; } - sol::table LuaState::makeReadOnly(sol::table table) + sol::table makeReadOnly(sol::table table) { + if (table == sol::nil) + return table; if (table.is()) return table; // it is already userdata, no sense to wrap it again + lua_State* lua = table.lua_state(); table[sol::meta_function::index] = table; - sol::stack::push(mLua, std::move(table)); - lua_newuserdata(mLua, 0); - lua_pushvalue(mLua, -2); - lua_setmetatable(mLua, -2); - return sol::stack::pop(mLua); + sol::stack::push(lua, std::move(table)); + lua_newuserdata(lua, 0); + lua_pushvalue(lua, -2); + lua_setmetatable(lua, -2); + return sol::stack::pop(lua); } - sol::table LuaState::getMutableFromReadOnly(const sol::userdata& ro) + sol::table getMutableFromReadOnly(const sol::userdata& ro) { - sol::stack::push(mLua, ro); - int ok = lua_getmetatable(mLua, -1); + lua_State* lua = ro.lua_state(); + sol::stack::push(lua, ro); + int ok = lua_getmetatable(lua, -1); assert(ok); (void)ok; - sol::table res = sol::stack::pop(mLua); - lua_pop(mLua, 1); + sol::table res = sol::stack::pop(lua); + lua_pop(lua, 1); return res; } diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index acaaadea76..8982b49b36 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -3,7 +3,6 @@ #include -#include // missing from sol/sol.hpp #include #include @@ -37,11 +36,6 @@ namespace LuaUtil // A shortcut to create a new Lua table. sol::table newTable() { return sol::table(mLua, sol::create); } - // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. - // Needed to forbid any changes in common resources that can accessed from different sandboxes. - sol::table makeReadOnly(sol::table); - sol::table getMutableFromReadOnly(const sol::userdata&); - // Registers a package that will be available from every sandbox via `require(name)`. // The package can be either a sol::table with an API or a sol::function. If it is a function, // it will be evaluated (once per sandbox) the first time when requested. If the package @@ -106,6 +100,11 @@ namespace LuaUtil // String representation of a Lua object. Should be used for debugging/logging purposes only. std::string toString(const sol::object&); + // Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata. + // Needed to forbid any changes in common resources that can accessed from different sandboxes. + sol::table makeReadOnly(sol::table); + sol::table getMutableFromReadOnly(const sol::userdata&); + } #endif // COMPONENTS_LUA_LUASTATE_H diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index a53b1d0404..6e0a19b120 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -25,7 +25,7 @@ namespace LuaUtil void ScriptsContainer::addPackage(const std::string& packageName, sol::object package) { - API[packageName] = mLua.makeReadOnly(std::move(package)); + API[packageName] = makeReadOnly(std::move(package)); } bool ScriptsContainer::addNewScript(const std::string& path) @@ -63,7 +63,7 @@ namespace LuaUtil if (interfaceName.empty() != (publicInterface == sol::nil)) Log(Debug::Error) << mNamePrefix << "[" << path << "]: 'interfaceName' should always be used together with 'interface'"; else if (!interfaceName.empty()) - script.as()[INTERFACE] = mPublicInterfaces[interfaceName] = mLua.makeReadOnly(publicInterface); + script.as()[INTERFACE] = mPublicInterfaces[interfaceName] = makeReadOnly(publicInterface); mScriptOrder.push_back(path); mScripts[path].mInterface = std::move(script); return true; @@ -329,7 +329,7 @@ namespace LuaUtil mHoursTimersQueue.clear(); mPublicInterfaces.clear(); - // Assigned by mLua.makeReadOnly, but `clear` removes it, so we need to assign it again. + // Assigned by LuaUtil::makeReadOnly, but `clear` removes it, so we need to assign it again. mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces; } diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 7b2b2a7aa9..d3141a2ca3 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -76,7 +76,7 @@ namespace LuaUtil virtual ~ScriptsContainer() {} // Adds package that will be available (via `require`) for all scripts in the container. - // Automatically applies LuaState::makeReadOnly to the package. + // Automatically applies LuaUtil::makeReadOnly to the package. void addPackage(const std::string& packageName, sol::object package); // Finds a file with given path in the virtual file system, starts as a new script, and adds it to the container. From bcb0526268ad529fe51929f4b21af12357b823a7 Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sun, 19 Sep 2021 15:00:51 +0200 Subject: [PATCH 039/137] Added climits, it compiled on VS2019, but not with g++ --- apps/openmw/mwdialogue/keywordsearch.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 3cd8517651..fc6f2a91e8 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -2,6 +2,7 @@ #define GAME_MWDIALOGUE_KEYWORDSEARCH_H #include +#include #include #include #include From c1c501ca3559571c8292cd0f684274e09f07f9ff Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sun, 19 Sep 2021 15:05:48 +0200 Subject: [PATCH 040/137] Added test to make sure keyword search works even with non ascii characters --- .../mwdialogue/test_keywordsearch.cpp | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index 431725be2c..25ea566165 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -65,3 +65,26 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution3) ASSERT_TRUE (matches.size() == 1); ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); } + + +TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) +{ + // We make sure that the search works well even if the character is not ASCII + MWDialogue::KeywordSearch search; + search.seed("tats", 0); + search.seed("rradis", 0); + search.seed("a nous dois", 0); + + + std::string text = "les nations unis ont runis le monde entier, tats units inclus pour parler du problme des gens rradis et a nous dois"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + ASSERT_TRUE (matches.size() == 3); + ASSERT_TRUE(std::string( matches[0].mBeg, matches[0].mEnd) == "tats"); + ASSERT_TRUE(std::string( matches[1].mBeg, matches[1].mEnd) == "rradis"); + ASSERT_TRUE(std::string( matches[2].mBeg, matches[2].mEnd) == "a nous dois"); + + //ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); +} From d879e4aba5db6c13e4a6c10d36dbee1670b374d6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 19 Sep 2021 19:11:27 +0200 Subject: [PATCH 041/137] Use real frame number for axis x --- scripts/osg_stats.py | 51 +++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index f0e6ce4010..8a36eb5963 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -45,20 +45,27 @@ import termtables help='Start processing from this frame.') @click.option('--end_frame', type=int, default=sys.maxsize, help='End processing at this frame.') +@click.option('--frame_number_name', type=str, default='FrameNumber', + help='Frame number metric name.') @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, timeseries_sum, stats_sum, begin_frame, end_frame, path, - commulative_timeseries, commulative_timeseries_sum): + commulative_timeseries, commulative_timeseries_sum, frame_number_name): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} keys = collect_unique_keys(sources) - frames = collect_per_frame(sources=sources, keys=keys, begin_frame=begin_frame, end_frame=end_frame) + frames, begin_frame, end_frame = collect_per_frame( + sources=sources, keys=keys, begin_frame=begin_frame, + end_frame=end_frame, frame_number_name=frame_number_name, + ) if print_keys: for v in keys: print(v) if timeseries: - draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum) + draw_timeseries(sources=frames, keys=timeseries, add_sum=timeseries_sum, + begin_frame=begin_frame, end_frame=end_frame) if commulative_timeseries: - draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum) + draw_commulative_timeseries(sources=frames, keys=commulative_timeseries, add_sum=commulative_timeseries_sum, + begin_frame=begin_frame, end_frame=end_frame) if hist: draw_hists(sources=frames, keys=hist) if hist_ratio: @@ -92,19 +99,26 @@ def read_data(path): frame[key] = to_number(value) -def collect_per_frame(sources, keys, begin_frame, end_frame): +def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): + assert begin_frame < end_frame result = collections.defaultdict(lambda: collections.defaultdict(list)) + begin_frame = max(begin_frame, min(v[0][frame_number_name] for v in sources.values())) + end_frame = min(end_frame, begin_frame + max(len(v) for v in sources.values())) + for name in sources.keys(): + for key in keys: + result[name][key] = [0] * (end_frame - begin_frame) for name, frames in sources.items(): for frame in frames: - for key in keys: - if key in frame: - result[name][key].append(frame[key]) - else: - result[name][key].append(None) - for name, sources in result.items(): - for key, values in sources.items(): - result[name][key] = numpy.array(values[begin_frame:end_frame]) - return result + number = frame[frame_number_name] + if begin_frame <= number < end_frame: + index = number - begin_frame + for key in keys: + if key in frame: + result[name][key][index] = frame[key] + for name in result.keys(): + for key in keys: + result[name][key] = numpy.array(result[name][key]) + return result, begin_frame, end_frame def collect_unique_keys(sources): @@ -116,12 +130,11 @@ def collect_unique_keys(sources): return sorted(result) -def draw_timeseries(sources, keys, add_sum): +def draw_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig, ax = matplotlib.pyplot.subplots() + x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): - x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) for key in keys: - print(key, name) ax.plot(x, frames[key], label=f'{key}:{name}') if add_sum: ax.plot(x, numpy.sum(list(frames[k] for k in keys), axis=0), label=f'sum:{name}') @@ -130,10 +143,10 @@ def draw_timeseries(sources, keys, add_sum): fig.canvas.set_window_title('timeseries') -def draw_commulative_timeseries(sources, keys, add_sum): +def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig, ax = matplotlib.pyplot.subplots() + x = numpy.array(range(begin_frame, end_frame)) for name, frames in sources.items(): - x = numpy.array(range(max(len(v) for k, v in frames.items() if k in keys))) for key in keys: ax.plot(x, numpy.cumsum(frames[key]), label=f'{key}:{name}') if add_sum: From 6d7cb3883480d43c3870be757289fa54727ef5df Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 19 Sep 2021 19:53:38 +0200 Subject: [PATCH 042/137] Remove duplicate GetSquareRoot implementation --- apps/openmw/mwscript/miscextensions.cpp | 3 ++ components/compiler/exprparser.cpp | 40 +++++++---------------- components/compiler/generator.cpp | 10 ------ components/compiler/generator.hpp | 2 -- components/compiler/scanner.cpp | 1 - components/compiler/scanner.hpp | 3 +- components/compiler/stringparser.cpp | 2 +- components/interpreter/docs/vmformat.txt | 2 +- components/interpreter/installopcodes.cpp | 1 - components/interpreter/mathopcodes.hpp | 20 +----------- 10 files changed, 19 insertions(+), 65 deletions(-) diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 869a1d540a..149f9f3f3f 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -864,6 +864,9 @@ namespace MWScript float param = runtime[0].mFloat; runtime.pop(); + if (param < 0) + throw std::runtime_error("square root of negative number (we aren't that imaginary)"); + runtime.push(std::sqrt (param)); } }; diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index fb7ca7aac4..23f15c8bf5 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -421,42 +421,26 @@ namespace Compiler if (mNextOperand) { - if (keyword==Scanner::K_getsquareroot) + // check for custom extensions + if (const Extensions *extensions = getContext().getExtensions()) { start(); - mTokenLoc = loc; - parseArguments ("f", scanner); + char returnType; + std::string argumentType; - Generator::squareRoot (mCode); - mOperands.push_back ('f'); + bool hasExplicit = false; - mNextOperand = false; - return true; - } - else - { - // check for custom extensions - if (const Extensions *extensions = getContext().getExtensions()) + if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit)) { - start(); - - char returnType; - std::string argumentType; + mTokenLoc = loc; + int optionals = parseArguments (argumentType, scanner); - bool hasExplicit = false; + extensions->generateFunctionCode (keyword, mCode, mLiterals, "", optionals); + mOperands.push_back (returnType); - if (extensions->isFunction (keyword, returnType, argumentType, hasExplicit)) - { - mTokenLoc = loc; - int optionals = parseArguments (argumentType, scanner); - - extensions->generateFunctionCode (keyword, mCode, mLiterals, "", optionals); - mOperands.push_back (returnType); - - mNextOperand = false; - return true; - } + mNextOperand = false; + return true; } } } diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index 34da1c4120..3d8b87e69e 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -104,11 +104,6 @@ namespace code.push_back (Compiler::Generator::segment5 (17)); } - void opSquareRoot (Compiler::Generator::CodeContainer& code) - { - code.push_back (Compiler::Generator::segment5 (19)); - } - void opReturn (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (20)); @@ -452,11 +447,6 @@ namespace Compiler::Generator } } - void squareRoot (CodeContainer& code) - { - opSquareRoot (code); - } - void exit (CodeContainer& code) { opReturn (code); diff --git a/components/compiler/generator.hpp b/components/compiler/generator.hpp index 55bba2a75c..00c6e56825 100644 --- a/components/compiler/generator.hpp +++ b/components/compiler/generator.hpp @@ -74,8 +74,6 @@ namespace Compiler void convert (CodeContainer& code, char fromType, char toType); - void squareRoot (CodeContainer& code); - void exit (CodeContainer& code); void message (CodeContainer& code, Literals& literals, const std::string& message, diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 829df35be5..cd607b2e73 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -265,7 +265,6 @@ namespace Compiler "return", "messagebox", "set", "to", - "getsquareroot", nullptr }; diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 15781f9240..7901297325 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -205,8 +205,7 @@ namespace Compiler K_while, K_endwhile, K_return, K_messagebox, - K_set, K_to, - K_getsquareroot + K_set, K_to }; enum special diff --git a/components/compiler/stringparser.cpp b/components/compiler/stringparser.cpp index 8b20377e1f..d9c3c04947 100644 --- a/components/compiler/stringparser.cpp +++ b/components/compiler/stringparser.cpp @@ -63,7 +63,7 @@ namespace Compiler keyword==Scanner::K_elseif || keyword==Scanner::K_while || keyword==Scanner::K_endwhile || keyword==Scanner::K_return || keyword==Scanner::K_messagebox || keyword==Scanner::K_set || - keyword==Scanner::K_to || keyword==Scanner::K_getsquareroot) + keyword==Scanner::K_to) { // pretend this is not a keyword std::string name = loc.mLiteral; diff --git a/components/interpreter/docs/vmformat.txt b/components/interpreter/docs/vmformat.txt index b5c9cf0ae0..7eac8b26e0 100644 --- a/components/interpreter/docs/vmformat.txt +++ b/components/interpreter/docs/vmformat.txt @@ -77,7 +77,7 @@ op 15: div (integer) stack[1] by stack[0], pop twice, push result op 16: div (float) stack[1] by stack[0], pop twice, push result op 17: convert stack[1] from integer to float op 18: convert stack[1] from float to integer -op 19: take square root of stack[0] (float) +opcode 19 unused op 20: return op 21: replace stack[0] with local short stack[0] op 22: replace stack[0] with local long stack[0] diff --git a/components/interpreter/installopcodes.cpp b/components/interpreter/installopcodes.cpp index afee36bc28..b5cb229e84 100644 --- a/components/interpreter/installopcodes.cpp +++ b/components/interpreter/installopcodes.cpp @@ -59,7 +59,6 @@ namespace Interpreter interpreter.installSegment5 (14, new OpMulInt); interpreter.installSegment5 (15, new OpDivInt); interpreter.installSegment5 (16, new OpDivInt); - interpreter.installSegment5 (19, new OpSquareRoot); interpreter.installSegment5 (26, new OpCompare >); interpreter.installSegment5 (27, diff --git a/components/interpreter/mathopcodes.hpp b/components/interpreter/mathopcodes.hpp index 42cb486b9c..bf580c6c2e 100644 --- a/components/interpreter/mathopcodes.hpp +++ b/components/interpreter/mathopcodes.hpp @@ -74,24 +74,6 @@ namespace Interpreter } }; - class OpSquareRoot : public Opcode0 - { - public: - - void execute (Runtime& runtime) override - { - Type_Float value = runtime[0].mFloat; - - if (value<0) - throw std::runtime_error ( - "square root of negative number (we aren't that imaginary)"); - - value = std::sqrt (value); - - runtime[0].mFloat = value; - } - }; - template class OpCompare : public Opcode0 { @@ -105,7 +87,7 @@ namespace Interpreter runtime[0].mInteger = result; } - }; + }; } #endif From 0e06e9b22188e7f23feacd0ddef1cdc286a3f732 Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sun, 19 Sep 2021 20:29:32 +0200 Subject: [PATCH 043/137] Removed useless comment and converted the file to UTF8 to keep special characters --- .../mwdialogue/test_keywordsearch.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index 25ea566165..d633feecd9 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -71,20 +71,18 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) { // We make sure that the search works well even if the character is not ASCII MWDialogue::KeywordSearch search; - search.seed("tats", 0); - search.seed("rradis", 0); - search.seed("a nous dois", 0); + search.seed("états", 0); + search.seed("ïrradiés", 0); + search.seed("ça nous déçois", 0); - std::string text = "les nations unis ont runis le monde entier, tats units inclus pour parler du problme des gens rradis et a nous dois"; + std::string text = "les nations unis ont réunis le monde entier, états units inclus pour parler du problème des gens ïrradiés et ça nous déçois"; std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); ASSERT_TRUE (matches.size() == 3); - ASSERT_TRUE(std::string( matches[0].mBeg, matches[0].mEnd) == "tats"); - ASSERT_TRUE(std::string( matches[1].mBeg, matches[1].mEnd) == "rradis"); - ASSERT_TRUE(std::string( matches[2].mBeg, matches[2].mEnd) == "a nous dois"); - - //ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); + ASSERT_TRUE(std::string( matches[0].mBeg, matches[0].mEnd) == "états"); + ASSERT_TRUE(std::string( matches[1].mBeg, matches[1].mEnd) == "ïrradiés"); + ASSERT_TRUE(std::string( matches[2].mBeg, matches[2].mEnd) == "ça nous déçois"); } From b3d1d106af76e748f695d50e621d57c28f0091a1 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 29 Aug 2021 14:26:17 +0300 Subject: [PATCH 044/137] Collada alpha testing and uniforms --- components/resource/scenemanager.cpp | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 76e73da384..8056e6911b 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -217,7 +218,52 @@ namespace Resource int mMaxAnisotropy; }; + // Check Collada extra descriptions + class ColladaAlphaTrickVisitor : public osg::NodeVisitor + { + public: + ColladaAlphaTrickVisitor() + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + { + } + + osg::AlphaFunc::ComparisonFunction getTestMode(std::string mode) + { + if (mode == "ALWAYS") return osg::AlphaFunc::ALWAYS; + if (mode == "LESS") return osg::AlphaFunc::LESS; + if (mode == "EQUAL") return osg::AlphaFunc::EQUAL; + if (mode == "LEQUAL") return osg::AlphaFunc::LEQUAL; + if (mode == "GREATER") return osg::AlphaFunc::GREATER; + if (mode == "NOTEQUAL") return osg::AlphaFunc::NOTEQUAL; + if (mode == "GEQUAL") return osg::AlphaFunc::GEQUAL; + if (mode == "NEVER") return osg::AlphaFunc::NEVER; + + Log(Debug::Info) << "Unexpected alpha testing mode: " << mode; + return osg::AlphaFunc::LEQUAL; + } + + void apply(osg::Node& node) override + { + std::vector descriptions = node.getDescriptions(); + for (auto description : descriptions) + { + std::vector descriptionParts; + std::istringstream descriptionStringStream(description); + for (std::string part; std::getline(descriptionStringStream, part, ' ');) + descriptionParts.emplace_back(part); + + if (descriptionParts.at(0) == "alphatest") + { + osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); + osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.back()))); + node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + Log(Debug::Info) << "Setting collada alpha test " << description << " for " << node.getName(); + } + } + traverse(node); + } + }; SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) : ResourceManager(vfs) @@ -424,6 +470,18 @@ namespace Resource if (nameFinder.mFoundNode) nameFinder.mFoundNode->setNodeMask(hiddenNodeMask); + if (ext == "dae") + { + // Collada alpha testing + Resource::ColladaAlphaTrickVisitor colladaAlphaTrickVisitor; + result.getNode()->accept(colladaAlphaTrickVisitor); + + result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); + result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1,1,1,1))); + result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); + } + + return result.getNode(); } } From 89982eb9e333d74ed69f57a313096da486b18262 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 29 Aug 2021 14:31:11 +0300 Subject: [PATCH 045/137] Add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 917de55fe2..8a92127dca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ Feature #6032: Reverse-z depth buffer Feature #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly Feature #6199: Support FBO Rendering + Feature #6249: Alpha testing support for Collada Feature #6251: OpenMW-CS: Set instance movement based on camera zoom Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp From 40497d6fe55b68f708cd1063e7a36f7a75ab7389 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 11 Sep 2021 13:55:17 +0300 Subject: [PATCH 046/137] Set depth testing for alpha blend & test, depth writes off for blend. --- components/resource/scenemanager.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 8056e6911b..b4a26f0f35 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -244,6 +244,17 @@ namespace Resource void apply(osg::Node& node) override { + bool osgDepthCreated(false); + + if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + depth->setWriteMask(false); + osgDepthCreated = true; + + node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + std::vector descriptions = node.getDescriptions(); for (auto description : descriptions) { @@ -255,6 +266,12 @@ namespace Resource if (descriptionParts.at(0) == "alphatest") { + if (!osgDepthCreated) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.back()))); node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); From 96f02ab32c4d2fb329f4c382499154673bb56dbb Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 11 Sep 2021 17:03:16 +0300 Subject: [PATCH 047/137] Per-material alpha testing for collada --- components/resource/scenemanager.cpp | 36 ++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b4a26f0f35..80d8f1ecdc 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -255,31 +255,47 @@ namespace Resource node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); } + /* Check if the has + correct format for OpenMW: alphatest mode value MaterialName + e.g alphatest GEQUAL 0.8 MyAlphaTestedMaterial */ std::vector descriptions = node.getDescriptions(); for (auto description : descriptions) { - std::vector descriptionParts; + mDescriptions.emplace_back(description); + } + // Iterate each description, and see if the current node uses the specified material for alpha testing + for (auto description : mDescriptions) + { + std::vector descriptionParts; std::istringstream descriptionStringStream(description); for (std::string part; std::getline(descriptionStringStream, part, ' ');) + { descriptionParts.emplace_back(part); + } - if (descriptionParts.at(0) == "alphatest") + if (descriptionParts.size() > (3) && descriptionParts.at(3) == node.getOrCreateStateSet()->getName()) { - if (!osgDepthCreated) + if (descriptionParts.at(0) == "alphatest") { - osg::ref_ptr depth = SceneUtil::createDepth(); - node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + if (!osgDepthCreated) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + + osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); + osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); + node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + Log(Debug::Info) << "Setting collada alpha test for " << node.getName(); } - - osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); - osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.back()))); - node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - Log(Debug::Info) << "Setting collada alpha test " << description << " for " << node.getName(); } } + traverse(node); } + private: + std::vector mDescriptions; }; SceneManager::SceneManager(const VFS::Manager *vfs, Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager) From ec0b36d21d4035e7a4fbe7b3c222a15f04248523 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 12 Sep 2021 20:31:34 +0300 Subject: [PATCH 048/137] Don't make a new osg::depth to alpha tested node --- components/resource/scenemanager.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 80d8f1ecdc..b9bab66faa 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -244,13 +244,10 @@ namespace Resource void apply(osg::Node& node) override { - bool osgDepthCreated(false); - if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) { osg::ref_ptr depth = SceneUtil::createDepth(); depth->setWriteMask(false); - osgDepthCreated = true; node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); } @@ -278,12 +275,6 @@ namespace Resource { if (descriptionParts.at(0) == "alphatest") { - if (!osgDepthCreated) - { - osg::ref_ptr depth = SceneUtil::createDepth(); - node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - } - osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); From f2a894024a646204c283c969b8acb33db0de255e Mon Sep 17 00:00:00 2001 From: unelsson Date: Sun, 12 Sep 2021 21:08:59 +0300 Subject: [PATCH 049/137] Change debug levels --- components/resource/scenemanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b9bab66faa..3112e3c837 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -238,7 +238,7 @@ namespace Resource if (mode == "GEQUAL") return osg::AlphaFunc::GEQUAL; if (mode == "NEVER") return osg::AlphaFunc::NEVER; - Log(Debug::Info) << "Unexpected alpha testing mode: " << mode; + Log(Debug::Warning) << "Unexpected alpha testing mode: " << mode; return osg::AlphaFunc::LEQUAL; } @@ -278,7 +278,6 @@ namespace Resource osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); - Log(Debug::Info) << "Setting collada alpha test for " << node.getName(); } } } From 67894349a97e9949b2057055fd06933538cfa2e9 Mon Sep 17 00:00:00 2001 From: unelsson Date: Sat, 18 Sep 2021 14:02:28 +0300 Subject: [PATCH 050/137] Add a check for OPAQUE_BIN --- components/resource/scenemanager.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 3112e3c837..b468b430ff 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -251,6 +251,13 @@ namespace Resource node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); } + else if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::OPAQUE_BIN) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + depth->setWriteMask(true); + + node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + } /* Check if the has correct format for OpenMW: alphatest mode value MaterialName From 2d329548887ff91b38a2b757bf216ac1065b20ee Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Mon, 20 Sep 2021 19:56:47 +0200 Subject: [PATCH 051/137] Replaced Assert_true with expect_eq --- .../mwdialogue/test_keywordsearch.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index d633feecd9..62b6f67aae 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -27,9 +27,9 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution) search.highlightKeywords(text.begin(), text.end(), matches); // Should contain: "foo bar", "lock switch" - ASSERT_TRUE (matches.size() == 2); - ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "foo bar"); - ASSERT_TRUE (std::string(matches.rbegin()->mBeg, matches.rbegin()->mEnd) == "lock switch"); + EXPECT_EQ (matches.size() , 2); + EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "foo bar"); + EXPECT_EQ (std::string(matches.rbegin()->mBeg, matches.rbegin()->mEnd) , "lock switch"); } TEST_F(KeywordSearchTest, keyword_test_conflict_resolution2) @@ -43,8 +43,8 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution2) std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); - ASSERT_TRUE (matches.size() == 1); - ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "dwemer language"); + EXPECT_EQ (matches.size() , 1); + EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "dwemer language"); } @@ -62,8 +62,8 @@ TEST_F(KeywordSearchTest, keyword_test_conflict_resolution3) std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); - ASSERT_TRUE (matches.size() == 1); - ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); + EXPECT_EQ (matches.size() , 1); + EXPECT_EQ (std::string(matches.front().mBeg, matches.front().mEnd) , "bar lock"); } @@ -81,8 +81,8 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) std::vector::Match> matches; search.highlightKeywords(text.begin(), text.end(), matches); - ASSERT_TRUE (matches.size() == 3); - ASSERT_TRUE(std::string( matches[0].mBeg, matches[0].mEnd) == "états"); - ASSERT_TRUE(std::string( matches[1].mBeg, matches[1].mEnd) == "ïrradiés"); - ASSERT_TRUE(std::string( matches[2].mBeg, matches[2].mEnd) == "ça nous déçois"); + EXPECT_EQ (matches.size() , 3); + EXPECT_EQ (std::string( matches[0].mBeg, matches[0].mEnd) , "états"); + EXPECT_EQ (std::string( matches[1].mBeg, matches[1].mEnd) , "ïrradiés"); + EXPECT_EQ (std::string( matches[2].mBeg, matches[2].mEnd) , "ça nous déçois"); } From 99df1c695c32ad4bb636501554e2430d3ce87b40 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 20 Sep 2021 18:17:55 +0000 Subject: [PATCH 052/137] fixes wireframe is broken (#3123) * renderingmanager.cpp [ci skip] * groundcover.hpp [ci skip] * groundcover.cpp * renderingmanager.cpp [ci skip] * renderingmanager.hpp --- apps/openmw/mwrender/groundcover.cpp | 30 ------------ apps/openmw/mwrender/groundcover.hpp | 22 --------- apps/openmw/mwrender/renderingmanager.cpp | 56 ++++++++++++++++------- apps/openmw/mwrender/renderingmanager.hpp | 2 - 4 files changed, 39 insertions(+), 71 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index a199201d41..94bd900aea 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -27,36 +27,6 @@ namespace MWRender } } - void GroundcoverUpdater::setWindSpeed(float windSpeed) - { - mWindSpeed = windSpeed; - } - - void GroundcoverUpdater::setPlayerPos(osg::Vec3f playerPos) - { - mPlayerPos = playerPos; - } - - void GroundcoverUpdater::setDefaults(osg::StateSet *stateset) - { - osg::ref_ptr windUniform = new osg::Uniform("windSpeed", 0.0f); - stateset->addUniform(windUniform.get()); - - osg::ref_ptr playerPosUniform = new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f)); - stateset->addUniform(playerPosUniform.get()); - } - - void GroundcoverUpdater::apply(osg::StateSet *stateset, osg::NodeVisitor *nv) - { - osg::ref_ptr windUniform = stateset->getUniform("windSpeed"); - if (windUniform != nullptr) - windUniform->set(mWindSpeed); - - osg::ref_ptr playerPosUniform = stateset->getUniform("playerPos"); - if (playerPosUniform != nullptr) - playerPosUniform->set(mPlayerPos); - } - class InstancingVisitor : public osg::NodeVisitor { public: diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index 4874bea899..0a83976441 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -3,32 +3,10 @@ #include #include -#include #include namespace MWRender { - class GroundcoverUpdater : public SceneUtil::StateSetUpdater - { - public: - GroundcoverUpdater() - : mWindSpeed(0.f) - , mPlayerPos(osg::Vec3f()) - { - } - - void setWindSpeed(float windSpeed); - void setPlayerPos(osg::Vec3f playerPos); - - protected: - void setDefaults(osg::StateSet *stateset) override; - void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; - - private: - float mWindSpeed; - osg::Vec3f mPlayerPos; - }; - typedef std::tuple GroundcoverChunkId; // Center, Size class Groundcover : public Resource::GenericResourceManager, public Terrain::QuadTreeWorld::ChunkManager { diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 56d3ba4a43..19a887fe2e 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -77,10 +77,12 @@ namespace MWRender class SharedUniformStateUpdater : public SceneUtil::StateSetUpdater { public: - SharedUniformStateUpdater() + SharedUniformStateUpdater(bool usePlayerUniforms) : mLinearFac(0.f) , mNear(0.f) , mFar(0.f) + , mUsePlayerUniforms(usePlayerUniforms) + , mWindSpeed(0.f) { } @@ -90,6 +92,11 @@ namespace MWRender stateset->addUniform(new osg::Uniform("linearFac", 0.f)); stateset->addUniform(new osg::Uniform("near", 0.f)); stateset->addUniform(new osg::Uniform("far", 0.f)); + if (mUsePlayerUniforms) + { + stateset->addUniform(new osg::Uniform("windSpeed", 0.0f)); + stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f))); + } } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override @@ -110,6 +117,16 @@ namespace MWRender if (uFar) uFar->set(mFar); + if (mUsePlayerUniforms) + { + auto* windSpeed = stateset->getUniform("windSpeed"); + if (windSpeed) + windSpeed->set(mWindSpeed); + + auto* playerPos = stateset->getUniform("playerPos"); + if (playerPos) + playerPos->set(mPlayerPos); + } } void setProjectionMatrix(const osg::Matrixf& projectionMatrix) @@ -132,11 +149,25 @@ namespace MWRender mFar = far; } + void setWindSpeed(float windSpeed) + { + mWindSpeed = windSpeed; + } + + void setPlayerPos(osg::Vec3f playerPos) + { + mPlayerPos = playerPos; + } + + private: osg::Matrixf mProjectionMatrix; float mLinearFac; float mNear; float mFar; + bool mUsePlayerUniforms; + float mWindSpeed; + osg::Vec3f mPlayerPos; }; class StateUpdater : public SceneUtil::StateSetUpdater @@ -400,16 +431,11 @@ namespace MWRender mTerrain->setTargetFrameRate(Settings::Manager::getFloat("target framerate", "Cells")); mTerrain->setWorkQueue(mWorkQueue.get()); - osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; - if (groundcover) { float density = Settings::Manager::getFloat("density", "Groundcover"); density = std::clamp(density, 0.f, 1.f); - mGroundcoverUpdater = new GroundcoverUpdater; - composite->addController(mGroundcoverUpdater); - mGroundcover.reset(new Groundcover(mResourceSystem->getSceneManager(), density)); static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); @@ -419,10 +445,9 @@ namespace MWRender } mStateUpdater = new StateUpdater; - composite->addController(mStateUpdater); - sceneRoot->addUpdateCallback(composite); + sceneRoot->addUpdateCallback(mStateUpdater); - mSharedUniformStateUpdater = new SharedUniformStateUpdater; + mSharedUniformStateUpdater = new SharedUniformStateUpdater(groundcover); rootNode->addUpdateCallback(mSharedUniformStateUpdater); mPostProcessor = new PostProcessor(*this, viewer, mRootNode); @@ -791,15 +816,12 @@ namespace MWRender mSky->update(dt); mWater->update(dt); - if (mGroundcoverUpdater) - { - const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); - osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); + const MWWorld::Ptr& player = mPlayerAnimation->getPtr(); + osg::Vec3f playerPos(player.getRefData().getPosition().asVec3()); - float windSpeed = mSky->getBaseWindSpeed(); - mGroundcoverUpdater->setWindSpeed(windSpeed); - mGroundcoverUpdater->setPlayerPos(playerPos); - } + float windSpeed = mSky->getBaseWindSpeed(); + mSharedUniformStateUpdater->setWindSpeed(windSpeed); + mSharedUniformStateUpdater->setPlayerPos(playerPos); } updateNavMesh(); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index b991b08efa..bd8bbe4694 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -70,7 +70,6 @@ namespace DetourNavigator namespace MWRender { - class GroundcoverUpdater; class StateUpdater; class SharedUniformStateUpdater; @@ -279,7 +278,6 @@ namespace MWRender std::unique_ptr mTerrainStorage; std::unique_ptr mObjectPaging; std::unique_ptr mGroundcover; - osg::ref_ptr mGroundcoverUpdater; std::unique_ptr mSky; std::unique_ptr mFog; std::unique_ptr mScreenshotManager; From d9c3ba03f4e846128d318bd09d94a8a900d52899 Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Mon, 20 Sep 2021 21:01:28 +0200 Subject: [PATCH 053/137] Uses limits instead of climits --- apps/openmw/mwdialogue/keywordsearch.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index fc6f2a91e8..8ba3349bc8 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -1,9 +1,9 @@ #ifndef GAME_MWDIALOGUE_KEYWORDSEARCH_H #define GAME_MWDIALOGUE_KEYWORDSEARCH_H -#include -#include #include +#include +#include #include #include #include // std::reverse @@ -71,7 +71,7 @@ public: static bool isWhitespaceUTF8(const int utf8Char) { - if (utf8Char >= 0 && utf8Char <= UCHAR_MAX) + if (utf8Char >= 0 && utf8Char <= static_cast( std::numeric_limits::max())) { //That function has undefined behavior if the character doesn't fit in unsigned char return std::isspace(utf8Char); From b22fb7a7bf8e7a3e198d1d40bdc12b15f4fc049a Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 21 Sep 2021 20:39:31 +0000 Subject: [PATCH 054/137] consolidate node mask checks (#3125) * consolidate node mask checks This PR simplifies a few checks against node masks in object paging by using the osg provided `setTraversalMask`. * objectpaging.cpp --- apps/openmw/mwrender/objectpaging.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 376c517d59..47eacbe1a2 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -272,7 +272,7 @@ namespace MWRender : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mCurrentStateSet(nullptr) , mCurrentDistance(0.f) - , mAnalyzeMask(analyzeMask) {} + { setTraversalMask(analyzeMask); } typedef std::unordered_map StateSetCounter; struct Result @@ -283,9 +283,6 @@ namespace MWRender void apply(osg::Node& node) override { - if (!(node.getNodeMask() & mAnalyzeMask)) - return; - if (node.getStateSet()) mCurrentStateSet = node.getStateSet(); @@ -308,9 +305,6 @@ namespace MWRender } void apply(osg::Geometry& geom) override { - if (!(geom.getNodeMask() & mAnalyzeMask)) - return; - if (osg::Array* array = geom.getVertexArray()) mResult.mNumVerts += array->getNumElements(); @@ -345,7 +339,6 @@ namespace MWRender osg::StateSet* mCurrentStateSet; StateSetCounter mGlobalStateSetCounter; float mCurrentDistance; - osg::Node::NodeMask mAnalyzeMask; }; class DebugVisitor : public osg::NodeVisitor From d38c8c6dcbe76b88ed7b1d8b267d0d10734deec8 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 21 Sep 2021 20:41:07 +0000 Subject: [PATCH 055/137] optimise chunk drawing order (#3116) * material.cpp * material.cpp --- components/terrain/material.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 28297fb456..22f507b3a2 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -199,7 +199,6 @@ namespace Terrain std::vector > passes; unsigned int blendmapIndex = 0; - unsigned int passIndex = 0; for (std::vector::const_iterator it = layers.begin(); it != layers.end(); ++it) { bool firstLayer = (it == layers.begin()); @@ -209,7 +208,7 @@ namespace Terrain if (!blendmaps.empty()) { stateset->setMode(GL_BLEND, osg::StateAttribute::ON); - stateset->setRenderBinDetails(passIndex++, "RenderBin"); + stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin"); if (!firstLayer) { stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); From 5893b8840935b86843066cf82d7dd9e7fee469aa Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 22 Sep 2021 01:46:04 +0200 Subject: [PATCH 056/137] Ignore time to destination when giving way (#6296) --- apps/openmw/mwmechanics/actors.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 37b438aefa..f4cb40c2f5 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1807,6 +1807,7 @@ namespace MWMechanics // Standing NPCs give way to moving ones if they are not in combat (or pursue) mode and either // follow player or have a AIWander package with non-empty wander area. bool shouldAvoidCollision = isMoving; + bool shouldGiveWay = false; bool shouldTurnToApproachingActor = !isMoving; MWWorld::Ptr currentTarget; // Combat or pursue target (NPCs should not avoid collision with their targets). const auto& aiSequence = ptr.getClass().getCreatureStats(ptr).getAiSequence(); @@ -1817,7 +1818,7 @@ namespace MWMechanics else if (package->getTypeId() == AiPackageTypeId::Wander && giveWayWhenIdle) { if (!static_cast(package.get())->isStationary()) - shouldAvoidCollision = true; + shouldGiveWay = true; } else if (package->getTypeId() == AiPackageTypeId::Combat || package->getTypeId() == AiPackageTypeId::Pursue) { @@ -1827,7 +1828,7 @@ namespace MWMechanics break; } } - if (!shouldAvoidCollision) + if (!shouldAvoidCollision && !shouldGiveWay) continue; osg::Vec2f baseSpeed = origMovement * maxSpeed; @@ -1836,14 +1837,14 @@ namespace MWMechanics const osg::Vec3f halfExtents = world->getHalfExtents(ptr); float maxDistToCheck = isMoving ? maxDistForPartialAvoiding : maxDistForStrictAvoiding; - float timeToCollision = maxTimeToCheck; + float timeToCheck = maxTimeToCheck; + if (!shouldGiveWay && !aiSequence.isEmpty()) + timeToCheck = std::min(timeToCheck, getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents)); + + float timeToCollision = timeToCheck; osg::Vec2f movementCorrection(0, 0); float angleToApproachingActor = 0; - const float timeToDestination = aiSequence.isEmpty() - ? std::numeric_limits::max() - : getTimeToDestination(**aiSequence.begin(), basePos, maxSpeed, duration, halfExtents); - // Iterate through all other actors and predict collisions. for(PtrActorMap::iterator otherIter(mActors.begin()); otherIter != mActors.end(); ++otherIter) { @@ -1880,7 +1881,7 @@ namespace MWMechanics continue; // No solution; distance is always >= collisionDist. float t = (-vr - std::sqrt(Dh)) / v2; - if (t < 0 || t > timeToCollision || t > timeToDestination) + if (t < 0 || t > timeToCollision) continue; // Check visibility and awareness last as it's expensive. @@ -1900,7 +1901,7 @@ namespace MWMechanics movementCorrection.y() *= 0.5f; } - if (timeToCollision < maxTimeToCheck) + if (timeToCollision < timeToCheck) { // Try to evade the nearest collision. osg::Vec2f newMovement = origMovement + movementCorrection; From e641bea606171542ab52f5c7e099b030d2ed4aac Mon Sep 17 00:00:00 2001 From: Pi03k Date: Sun, 19 Sep 2021 19:07:54 +0200 Subject: [PATCH 057/137] Toggling table columns visibility --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/view/world/scenesubview.hpp | 1 - apps/opencs/view/world/table.cpp | 3 + .../world/tableheadermouseeventhandler.cpp | 64 +++++++++++++++++++ .../world/tableheadermouseeventhandler.hpp | 25 ++++++++ 5 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 apps/opencs/view/world/tableheadermouseeventhandler.cpp create mode 100644 apps/opencs/view/world/tableheadermouseeventhandler.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 0ffa3da559..952bbbdbda 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -71,7 +71,7 @@ opencs_units (view/world cellcreator pathgridcreator referenceablecreator startscriptcreator referencecreator scenesubview infocreator scriptedit dialoguesubview previewsubview regionmap dragrecordtable nestedtable dialoguespinbox recordbuttonbar tableeditidaction scripterrortable extendedcommandconfigurator - bodypartcreator landtexturecreator landcreator + bodypartcreator landtexturecreator landcreator tableheadermouseeventhandler ) opencs_units (view/world diff --git a/apps/opencs/view/world/scenesubview.hpp b/apps/opencs/view/world/scenesubview.hpp index aabb7ca2a7..53cd54e7ac 100644 --- a/apps/opencs/view/world/scenesubview.hpp +++ b/apps/opencs/view/world/scenesubview.hpp @@ -32,7 +32,6 @@ namespace CSVWidget namespace CSVWorld { - class Table; class TableBottomBox; class CreatorFactoryBase; diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 2834159b7f..643396a057 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -28,6 +28,7 @@ #include "../../model/prefs/shortcut.hpp" #include "tableeditidaction.hpp" +#include "tableheadermouseeventhandler.hpp" #include "util.hpp" void CSVWorld::Table::contextMenuEvent (QContextMenuEvent *event) @@ -422,6 +423,8 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id, connect (&CSMPrefs::State::get(), SIGNAL (settingChanged (const CSMPrefs::Setting *)), this, SLOT (settingChanged (const CSMPrefs::Setting *))); CSMPrefs::get()["ID Tables"].update(); + + new TableHeaderMouseEventHandler(this); } void CSVWorld::Table::setEditLock (bool locked) diff --git a/apps/opencs/view/world/tableheadermouseeventhandler.cpp b/apps/opencs/view/world/tableheadermouseeventhandler.cpp new file mode 100644 index 0000000000..866c6149db --- /dev/null +++ b/apps/opencs/view/world/tableheadermouseeventhandler.cpp @@ -0,0 +1,64 @@ +#include "tableheadermouseeventhandler.hpp" +#include "dragrecordtable.hpp" + +#include +#include + +namespace CSVWorld +{ + +TableHeaderMouseEventHandler::TableHeaderMouseEventHandler(DragRecordTable * parent) + : QWidget(parent) + , table(*parent) + , header(*table.horizontalHeader()) +{ + header.setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); + connect( + &header, &QHeaderView::customContextMenuRequested, [=](const QPoint & position) { showContextMenu(position); }); + + header.viewport()->installEventFilter(this); +} + +bool TableHeaderMouseEventHandler::eventFilter(QObject * tableWatched, QEvent * event) +{ + if (event->type() == QEvent::Type::MouseButtonPress) + { + auto & clickEvent = static_cast(*event); + if ((clickEvent.button() == Qt::MiddleButton)) + { + const auto & index = table.indexAt(clickEvent.pos()); + table.setColumnHidden(index.column(), true); + clickEvent.accept(); + return true; + } + } + return false; +} + +void TableHeaderMouseEventHandler::showContextMenu(const QPoint & position) +{ + auto & menu{createContextMenu()}; + menu.popup(header.viewport()->mapToGlobal(position)); +} + +QMenu & TableHeaderMouseEventHandler::createContextMenu() +{ + auto * menu = new QMenu(this); + for (int i = 0; i < table.model()->columnCount(); ++i) + { + const auto & name = table.model()->headerData(i, Qt::Horizontal, Qt::DisplayRole); + QAction * action{new QAction(name.toString(), this)}; + action->setCheckable(true); + action->setChecked(!table.isColumnHidden(i)); + menu->addAction(action); + + connect(action, &QAction::triggered, [=]() { + table.setColumnHidden(i, !action->isChecked()); + action->setChecked(!action->isChecked()); + action->toggle(); + }); + } + return *menu; +} + +} // namespace CSVWorld diff --git a/apps/opencs/view/world/tableheadermouseeventhandler.hpp b/apps/opencs/view/world/tableheadermouseeventhandler.hpp new file mode 100644 index 0000000000..934bc1dbb7 --- /dev/null +++ b/apps/opencs/view/world/tableheadermouseeventhandler.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +namespace CSVWorld +{ +class DragRecordTable; + +class TableHeaderMouseEventHandler : public QWidget +{ +public: + explicit TableHeaderMouseEventHandler(DragRecordTable * parent); + + void showContextMenu(const QPoint &); + +private: + DragRecordTable & table; + QHeaderView & header; + + QMenu & createContextMenu(); + bool eventFilter(QObject *, QEvent *) override; + +}; // class TableHeaderMouseEventHandler +} // namespace CSVWorld From d36595e09efd85bf9d419b97bc50d9e4c37533c2 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 23 Sep 2021 01:47:19 +0200 Subject: [PATCH 058/137] Make sure PathFinder::getClosestPoint is not called with failing precondition Pathgrid should be not nullptr and points should be not empty. --- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4b490d7bc8..eb139c918d 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -348,7 +348,7 @@ namespace MWMechanics bool runFallback = true; - if (pathgrid && !actor.getClass().isPureWaterCreature(actor)) + if (pathgrid != nullptr && !pathgrid->mPoints.empty() && !actor.getClass().isPureWaterCreature(actor)) { ESM::Pathgrid::PointList points; Misc::CoordinateConverter coords(storage.mCell->getCell()); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 19365bcb08..624abe10f9 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -758,6 +758,9 @@ namespace MWMechanics const ESM::Pathgrid *pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*currentCell->getCell()); + if (pathgrid == nullptr || pathgrid->mPoints.empty()) + return; + int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest)); getPathGridGraph(currentCell).getNeighbouringPoints(index, points); From b676d93e03f52645d827da19d87659f6343c6650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Mocquillon?= Date: Thu, 23 Sep 2021 18:47:59 +0200 Subject: [PATCH 059/137] Use a pair of iterator to represents a range for directory listing --- components/vfs/manager.cpp | 19 ++++++++++-- components/vfs/manager.hpp | 63 ++++++++++++++------------------------ 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index 1cb8745497..faebc782aa 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -105,8 +105,23 @@ namespace VFS return {}; } - RecursiveDirectoryIterator Manager::getRecursiveDirectoryIterator(const std::string& path) const + namespace { - return RecursiveDirectoryIterator(mIndex, normalizeFilename(path)); + bool startsWith(std::string_view text, std::string_view start) + { + return text.rfind(start, 0) == 0; + } + } + + Manager::RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(const std::string& path) const + { + if (path.empty()) + return { mIndex.begin(), mIndex.end() }; + auto normalized = normalizeFilename(path); + const auto it = mIndex.lower_bound(normalized); + if (it == mIndex.end() || !startsWith(it->first, normalized)) + return { it, it }; + ++normalized.back(); + return { it, mIndex.lower_bound(normalized) }; } } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index b98d8021d4..8568e8e784 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -12,51 +12,19 @@ namespace VFS class Archive; class File; - class RecursiveDirectoryIterator; - RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); - - class RecursiveDirectoryIterator + template + class IteratorPair { public: - RecursiveDirectoryIterator(const std::map& index, const std::string& path) - : mPath(path) - , mIndex(&index) - , mIt(index.lower_bound(path)) - {} - - RecursiveDirectoryIterator(const RecursiveDirectoryIterator&) = default; - - const std::string& operator*() const { return mIt->first; } - const std::string* operator->() const { return &mIt->first; } - - bool operator!=(const RecursiveDirectoryIterator& other) { return mPath != other.mPath || mIt != other.mIt; } - - RecursiveDirectoryIterator& operator++() - { - if (++mIt == mIndex->end() || !starts_with(mIt->first, mPath)) - *this = end(*this); - return *this; - } - - friend RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter); + IteratorPair(Iterator first, Iterator last) : mFirst(first), mLast(last) {} + Iterator begin() const { return mFirst; } + Iterator end() const { return mLast; } private: - static bool starts_with(const std::string& text, const std::string& start) { return text.rfind(start, 0) == 0; } - - std::string mPath; - const std::map* mIndex; - std::map::const_iterator mIt; + Iterator mFirst; + Iterator mLast; }; - inline RecursiveDirectoryIterator begin(RecursiveDirectoryIterator iter) { return iter; } - - inline RecursiveDirectoryIterator end(const RecursiveDirectoryIterator& iter) - { - RecursiveDirectoryIterator result(iter); - result.mIt = result.mIndex->end(); - return result; - } - /// @brief The main class responsible for loading files from a virtual file system. /// @par Various archive types (e.g. directories on the filesystem, or compressed archives) /// can be registered, and will be merged into a single file tree. If the same filename is @@ -64,6 +32,21 @@ namespace VFS /// @par Most of the methods in this class are considered thread-safe, see each method documentation for details. class Manager { + class RecursiveDirectoryIterator + { + public: + RecursiveDirectoryIterator(std::map::const_iterator it) : mIt(it) {} + const std::string& operator*() const { return mIt->first; } + const std::string* operator->() const { return &mIt->first; } + bool operator!=(const RecursiveDirectoryIterator& other) { return mIt != other.mIt; } + RecursiveDirectoryIterator& operator++() { ++mIt; return *this; } + + private: + std::map::const_iterator mIt; + }; + + using RecursiveDirectoryRange = IteratorPair; + public: /// @param strict Use strict path handling? If enabled, no case folding will /// be done, but slash/backslash conversions are always done. @@ -105,7 +88,7 @@ namespace VFS /// In practice it return all files of the VFS starting with the given path /// @note the path is normalized /// @note May be called from any thread once the index has been built. - RecursiveDirectoryIterator getRecursiveDirectoryIterator(const std::string& path) const; + RecursiveDirectoryRange getRecursiveDirectoryIterator(const std::string& path) const; private: bool mStrict; From 5878b1fce6487206dbabc0dc50ed904ebe33893c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 24 Sep 2021 22:23:55 +0200 Subject: [PATCH 060/137] Use same logic for testing cell as for loading cell Having different branches makes testing less useful. If something fails in regular executing it should fail in testing. To make it possible there should be none differences in the execution paths. --- apps/openmw/mwworld/scene.cpp | 131 ++++++++++++++++------------------ apps/openmw/mwworld/scene.hpp | 10 +-- 2 files changed, 66 insertions(+), 75 deletions(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 89349d329a..e52f5532b4 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -200,13 +200,12 @@ namespace struct InsertVisitor { MWWorld::CellStore& mCell; - Loading::Listener& mLoadingListener; + Loading::Listener* mLoadingListener; bool mOnlyObjects; - bool mTest; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyObjects, bool test); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects); bool operator() (const MWWorld::Ptr& ptr); @@ -214,8 +213,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener& loadingListener, bool onlyObjects, bool test) - : mCell (cell), mLoadingListener (loadingListener), mOnlyObjects(onlyObjects), mTest(test) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects) + : mCell(cell), mLoadingListener(loadingListener), mOnlyObjects(onlyObjects) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -244,8 +243,8 @@ namespace } } - if (!mTest) - mLoadingListener.increaseProgress (1); + if (mLoadingListener != nullptr) + mLoadingListener->increaseProgress(1); } } @@ -325,12 +324,12 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadInactiveCell (CellStore* cell, bool test) + void Scene::unloadInactiveCell (CellStore* cell) { assert(mActiveCells.find(cell) == mActiveCells.end()); assert(mInactiveCells.find(cell) != mInactiveCells.end()); - if (!test) - Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); + + Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); ListObjectsVisitor visitor; @@ -351,13 +350,13 @@ namespace MWWorld mInactiveCells.erase(cell); } - void Scene::deactivateCell(CellStore* cell, bool test) + void Scene::deactivateCell(CellStore* cell) { assert(mInactiveCells.find(cell) != mInactiveCells.end()); if (mActiveCells.find(cell) == mActiveCells.end()) return; - if (!test) - Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); + + Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); ListAndResetObjectsVisitor visitor; @@ -409,7 +408,7 @@ namespace MWWorld mActiveCells.erase(cell); } - void Scene::activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test) + void Scene::activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn) { using DetourNavigator::HeightfieldShape; @@ -417,17 +416,14 @@ namespace MWWorld assert(mInactiveCells.find(cell) != mInactiveCells.end()); mActiveCells.insert(cell); - if (test) - Log(Debug::Info) << "Testing cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); const auto world = MWBase::Environment::get().getWorld(); const int cellX = cell->getCell()->getGridX(); const int cellY = cell->getCell()->getGridY(); - if (!test && cell->getCell()->isExterior()) + if (cell->getCell()->isExterior()) { if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) { @@ -466,69 +462,64 @@ namespace MWWorld if (respawn) cell->respawn(); - insertCell (*cell, loadingListener, false, test); + insertCell(*cell, loadingListener, false); mRendering.addCell(cell); - if (!test) + + MWBase::Environment::get().getWindowManager()->addCell(cell); + bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); + float waterLevel = cell->getWaterLevel(); + mRendering.setWaterEnabled(waterEnabled); + if (waterEnabled) { - MWBase::Environment::get().getWindowManager()->addCell(cell); - bool waterEnabled = cell->getCell()->hasWater() || cell->isExterior(); - float waterLevel = cell->getWaterLevel(); - mRendering.setWaterEnabled(waterEnabled); - if (waterEnabled) - { - mPhysics->enableWater(waterLevel); - mRendering.setWaterHeight(waterLevel); + mPhysics->enableWater(waterLevel); + mRendering.setWaterHeight(waterLevel); - if (cell->getCell()->isExterior()) - { - if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) - { - const btTransform& transform =heightField->getCollisionObject()->getWorldTransform(); - mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, - osg::Vec3f(static_cast(transform.getOrigin().x()), - static_cast(transform.getOrigin().y()), - waterLevel)); - } - } - else + if (cell->getCell()->isExterior()) + { + if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) { - mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), - osg::Vec3f(0, 0, waterLevel)); + const btTransform& transform =heightField->getCollisionObject()->getWorldTransform(); + mNavigator.addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, + osg::Vec3f(static_cast(transform.getOrigin().x()), + static_cast(transform.getOrigin().y()), + waterLevel)); } } else - mPhysics->disableWater(); - - const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - - // The player is loaded before the scene and by default it is grounded, with the scene fully loaded, we validate and correct this. - if (player.mCell == cell) // Only run once, during initial cell load. { - mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); + mNavigator.addWater(osg::Vec2i(cellX, cellY), std::numeric_limits::max(), + osg::Vec3f(0, 0, waterLevel)); } + } + else + mPhysics->disableWater(); - mNavigator.update(player.getRefData().getPosition().asVec3()); + const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) - mRendering.configureAmbient(cell->getCell()); + // The player is loaded before the scene and by default it is grounded, with the scene fully loaded, we validate and correct this. + if (player.mCell == cell) // Only run once, during initial cell load. + { + mPhysics->traceDown(player, player.getRefData().getPosition().asVec3(), 10.f); } + mNavigator.update(player.getRefData().getPosition().asVec3()); + + if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx)) + mRendering.configureAmbient(cell->getCell()); + mPreloader->notifyLoaded(cell); } - void Scene::loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test) + void Scene::loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener) { assert(mActiveCells.find(cell) == mActiveCells.end()); assert(mInactiveCells.find(cell) == mInactiveCells.end()); mInactiveCells.insert(cell); - if (test) - Log(Debug::Info) << "Testing inactive cell " << cell->getCell()->getDescription(); - else - Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); - if (!test && cell->getCell()->isExterior()) + if (cell->getCell()->isExterior()) { float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; @@ -550,7 +541,7 @@ namespace MWWorld } } - insertCell (*cell, loadingListener, true, test); + insertCell(*cell, loadingListener, true); } void Scene::clear() @@ -746,8 +737,8 @@ namespace MWWorld loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadInactiveCell(cell, nullptr); + activateCell(cell, nullptr, false); auto iter = mInactiveCells.begin(); while (iter != mInactiveCells.end()) @@ -755,8 +746,8 @@ namespace MWWorld if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + deactivateCell(*iter); + unloadInactiveCell(*iter); break; } @@ -794,8 +785,8 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadInactiveCell (cell, loadingListener, true); - activateCell (cell, loadingListener, false, true); + loadInactiveCell(cell, nullptr); + activateCell(cell, nullptr, false); auto iter = mInactiveCells.begin(); while (iter != mInactiveCells.end()) @@ -804,8 +795,8 @@ namespace MWWorld if (it->mName == (*iter)->getCell()->mName) { - deactivateCell(*iter, true); - unloadInactiveCell (*iter, true); + deactivateCell(*iter); + unloadInactiveCell(*iter); break; } @@ -988,9 +979,9 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects, bool test) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects) { - InsertVisitor insertVisitor (cell, *loadingListener, onlyObjects, test); + InsertVisitor insertVisitor(cell, loadingListener, onlyObjects); cell.forEach (insertVisitor); insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyObjects); }); if (!onlyObjects) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 75c070dd1e..3f5d7bf2f5 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -100,7 +100,7 @@ namespace MWWorld std::vector> mWorkItems; - void insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects, bool test = false); + void insertCell(CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -116,10 +116,10 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; - void unloadInactiveCell (CellStore* cell, bool test = false); - void deactivateCell (CellStore* cell, bool test = false); - void activateCell (CellStore *cell, Loading::Listener* loadingListener, bool respawn, bool test = false); - void loadInactiveCell (CellStore *cell, Loading::Listener* loadingListener, bool test = false); + void unloadInactiveCell(CellStore* cell); + void deactivateCell(CellStore* cell); + void activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn); + void loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener); public: From e9f253c473e2343f903e325aa26038568a2eed87 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 19 Sep 2021 19:16:39 +0200 Subject: [PATCH 061/137] Support stacked histogram for frames with duration over given threshold --- scripts/osg_stats.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 8a36eb5963..037aafea00 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -38,7 +38,7 @@ import termtables @click.option('--timeseries_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') @click.option('--commulative_timeseries_sum', is_flag=True, - help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') + help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') @click.option('--stats_sum', is_flag=True, help='Add a row to stats table for a sum per frame of all given stats metrics.') @click.option('--begin_frame', type=int, default=0, @@ -47,10 +47,17 @@ import termtables help='End processing at this frame.') @click.option('--frame_number_name', type=str, default='FrameNumber', help='Frame number metric name.') +@click.option('--hist_threshold', type=str, multiple=True, + help='Show a histogram for given metric only for frames with threshold_name metric over threshold_value.') +@click.option('--threshold_name', type=str, default='Frame duration', + help='Frame duration metric name.') +@click.option('--threshold_value', type=float, default=1.05/60, + help='Threshold for hist_over.') @click.argument('path', type=click.Path(), nargs=-1) def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, timeseries_sum, stats_sum, begin_frame, end_frame, path, - commulative_timeseries, commulative_timeseries_sum, frame_number_name): + commulative_timeseries, commulative_timeseries_sum, frame_number_name, + hist_threshold, threshold_name, threshold_value): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} keys = collect_unique_keys(sources) frames, begin_frame, end_frame = collect_per_frame( @@ -76,6 +83,9 @@ def main(print_keys, timeseries, hist, hist_ratio, stdev_hist, plot, stats, draw_plots(sources=frames, plots=plot) if stats: print_stats(sources=frames, keys=stats, stats_sum=stats_sum) + if hist_threshold: + draw_hist_threshold(sources=frames, keys=hist_threshold, begin_frame=begin_frame, + threshold_name=threshold_name, threshold_value=threshold_value) matplotlib.pyplot.show() @@ -240,7 +250,6 @@ def print_stats(sources, keys, stats_sum): if stats_sum: stats.append(make_stats(source=name, key='sum', values=sum_multiple(frames, keys))) metrics = list(stats[0].keys()) - max_key_size = max(len(tuple(v.values())[0]) for v in stats) termtables.print( [list(v.values()) for v in stats], header=metrics, @@ -248,6 +257,27 @@ def print_stats(sources, keys, stats_sum): ) +def draw_hist_threshold(sources, keys, begin_frame, threshold_name, threshold_value): + for name, frames in sources.items(): + indices = [n for n, v in enumerate(frames[threshold_name]) if v > threshold_value] + numbers = [v + begin_frame for v in indices] + x = [v for v in range(0, len(indices))] + fig, ax = matplotlib.pyplot.subplots() + ax.set_title(f'Frames with "{threshold_name}" > {threshold_value} ({len(indices)})') + ax.bar(x, [frames[threshold_name][v] for v in indices], label=threshold_name, color='black', alpha=0.2) + prev = 0 + for key in keys: + values = [frames[key][v] for v in indices] + ax.bar(x, values, bottom=prev, label=key) + prev = values + ax.hlines(threshold_value, x[0] - 1, x[-1] + 1, color='black', label='threshold', linestyles='dashed') + ax.xaxis.set_major_locator(matplotlib.pyplot.FixedLocator(x)) + ax.xaxis.set_major_formatter(matplotlib.pyplot.FixedFormatter(numbers)) + ax.grid(True) + ax.legend() + fig.canvas.set_window_title(f'hist_threshold:{name}') + + def filter_not_none(values): return [v for v in values if v is not None] @@ -282,5 +312,6 @@ def to_number(value): except ValueError: return float(value) + if __name__ == '__main__': main() From 83e0d9aeefd70413d4a04882eed0a8bda03cd19d Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 26 Sep 2021 17:28:51 +0200 Subject: [PATCH 062/137] Minor refactoring: use Misc::normalizeAngle in worldimp.cpp --- apps/openmw/mwworld/worldimp.cpp | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bc28c2eb7b..9d52d7ee77 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -76,21 +77,6 @@ #include "contentloader.hpp" #include "esmloader.hpp" -namespace -{ - -// Wraps a value to (-PI, PI] -void wrap(float& rad) -{ - const float pi = static_cast(osg::PI); - if (rad>0) - rad = std::fmod(rad+pi, 2.0f*pi)-pi; - else - rad = std::fmod(rad-pi, 2.0f*pi)+pi; -} - -} - namespace MWWorld { struct GameContentLoader : public ContentLoader @@ -1290,8 +1276,6 @@ namespace MWWorld void World::rotateObject(const Ptr& ptr, const osg::Vec3f& rot, MWBase::RotationFlags flags) { - const float pi = static_cast(osg::PI); - ESM::Position pos = ptr.getRefData().getPosition(); float *objRot = pos.rot; if (flags & MWBase::RotationFlag_adjust) @@ -1313,13 +1297,9 @@ namespace MWWorld * currently it's done so for rotating the camera, which needs * clamping. */ - const float half_pi = pi/2.f; - - if(objRot[0] < -half_pi) objRot[0] = -half_pi; - else if(objRot[0] > half_pi) objRot[0] = half_pi; - - wrap(objRot[1]); - wrap(objRot[2]); + objRot[0] = osg::clampBetween(objRot[0], -osg::PIf / 2, osg::PIf / 2); + objRot[1] = Misc::normalizeAngle(objRot[1]); + objRot[2] = Misc::normalizeAngle(objRot[2]); } ptr.getRefData().setPosition(pos); From 413ac067ec8ecc0430b19c275b6ea30589bff435 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 1 Sep 2021 01:53:23 +0200 Subject: [PATCH 063/137] Allow creating omwaddons without a dependency on an omwgame --- apps/opencs/view/doc/filedialog.cpp | 2 +- .../contentselector/model/contentmodel.cpp | 24 +++++++------------ .../contentselector/view/contentselector.cpp | 15 ++++++------ .../contentselector/view/contentselector.hpp | 2 +- 4 files changed, 18 insertions(+), 25 deletions(-) diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 69490edca2..8ff063ed31 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -161,7 +161,7 @@ void CSVDoc::FileDialog::slotUpdateAcceptButton(const QString &name, bool) bool isNew = (mAction == ContentAction_New); if (isNew) - success = success && !(name.isEmpty()); + success = !name.isEmpty(); else if (success) { ContentSelectorModel::EsmFile *file = mSelector->selectedFiles().back(); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 141ad13517..690f968142 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -107,34 +107,28 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index // addon can be checked if its gamefile is // ... special case, addon with no dependency can be used with any gamefile. - bool gamefileChecked = (file->gameFiles().count() == 0); + bool gamefileChecked = false; + bool noGameFiles = true; for (const QString &fileName : file->gameFiles()) { for (QListIterator dependencyIter(mFiles); dependencyIter.hasNext(); dependencyIter.next()) { //compare filenames only. Multiple instances //of the filename (with different paths) is not relevant here. - bool depFound = (dependencyIter.peekNext()->fileName().compare(fileName, Qt::CaseInsensitive) == 0); - - if (!depFound) + EsmFile* depFile = dependencyIter.peekNext(); + if (!depFile->isGameFile() || depFile->fileName().compare(fileName, Qt::CaseInsensitive) != 0) continue; - if (!gamefileChecked) + noGameFiles = false; + if (isChecked(depFile->filePath())) { - if (isChecked (dependencyIter.peekNext()->filePath())) - gamefileChecked = (dependencyIter.peekNext()->isGameFile()); - } - - // force it to iterate all files in cases where the current - // dependency is a game file to ensure that a later duplicate - // game file is / is not checked. - // (i.e., break only if it's not a gamefile or the game file has been checked previously) - if (gamefileChecked || !(dependencyIter.peekNext()->isGameFile())) + gamefileChecked = true; break; + } } } - if (gamefileChecked) + if (gamefileChecked || noGameFiles) { returnFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; } diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 6bb8e6e2c7..d7996dfae3 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -29,14 +29,12 @@ void ContentSelectorView::ContentSelector::buildContentModel() void ContentSelectorView::ContentSelector::buildGameFileView() { - ui.gameFileView->setVisible (true); - - ui.gameFileView->setPlaceholderText(QString("Select a game file...")); + ui.gameFileView->addItem(""); + ui.gameFileView->setVisible(true); connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)), this, SLOT (slotCurrentGameFileIndexChanged(int))); - ui.gameFileView->setCurrentIndex(-1); ui.gameFileView->setCurrentIndex(0); } @@ -108,7 +106,7 @@ void ContentSelectorView::ContentSelector::setProfileContent(const QStringList & void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) { - int index = -1; + int index = 0; if (!filename.isEmpty()) { @@ -168,10 +166,11 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) } } - if (ui.gameFileView->currentIndex() != -1) - ui.gameFileView->setCurrentIndex(-1); + if (ui.gameFileView->currentIndex() != 0) + ui.gameFileView->setCurrentIndex(0); mContentModel->uncheckAll(); + mContentModel->checkForLoadOrderErrors(); } void ContentSelectorView::ContentSelector::clearFiles() @@ -183,7 +182,7 @@ QString ContentSelectorView::ContentSelector::currentFile() const { QModelIndex currentIdx = ui.addonView->currentIndex(); - if (!currentIdx.isValid()) + if (!currentIdx.isValid() && ui.gameFileView->currentIndex() > 0) return ui.gameFileView->currentText(); QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex()); diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 1b50f1e5e8..cda68fa1b7 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -40,7 +40,7 @@ namespace ContentSelectorView void setGameFile (const QString &filename = QString("")); bool isGamefileSelected() const - { return ui.gameFileView->currentIndex() != -1; } + { return ui.gameFileView->currentIndex() > 0; } QWidget *uiWidget() const { return ui.contentGroupBox; } From 5620e8f0e275db5b6b91d8b3d9bbf3fe8621b1b7 Mon Sep 17 00:00:00 2001 From: "florent.teppe" Date: Sun, 26 Sep 2021 20:40:11 +0200 Subject: [PATCH 064/137] Added the fix to the changelog, and my name to the authors --- AUTHORS.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 62121a797b..a1f00bca10 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -80,6 +80,7 @@ Programmers Federico Guerra (FedeWar) Fil Krynicki (filkry) Finbar Crago (finbar-crago) + Florent Teppe (Tetramir) Florian Weber (Florianjw) Frédéric Chardon (fr3dz10) Gaëtan Dezeiraud (Brouilles) diff --git a/CHANGELOG.md b/CHANGELOG.md index f81a490b49..80fc4aac02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6273: Respawning NPCs rotation is inconsistent + Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map From c679565893f1480205eb4438cf60aca1514f5cdc Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 16 Sep 2021 18:06:46 +0200 Subject: [PATCH 065/137] Make names starting with digits use normal name parsing code --- CHANGELOG.md | 1 + components/compiler/scanner.cpp | 25 +++---------------------- components/compiler/scanner.hpp | 2 +- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9ea47c56..73588cb749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6273: Respawning NPCs rotation is inconsistent + Bug #6282: Laura craft doesn't follow the player character Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index cd607b2e73..1054e2e269 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -164,8 +164,6 @@ namespace Compiler std::string value; c.appendTo(value); - bool error = false; - while (get (c)) { if (c.isDigit()) @@ -174,16 +172,11 @@ namespace Compiler } else if (!c.isMinusSign() && isStringCharacter (c)) { - error = true; - c.appendTo(value); + /// workaround that allows names to begin with digits + return scanName(c, parser, cont, value); } else if (c=='.') { - if (error) - { - putback (c); - break; - } return scanFloat (value, parser, cont); } else @@ -193,17 +186,6 @@ namespace Compiler } } - if (error) - { - /// workaround that allows names to begin with digits - /// \todo disable - TokenLoc loc (mLoc); - mLoc.mLiteral.clear(); - cont = parser.parseName (value, loc, *this); - return true; -// return false; - } - TokenLoc loc (mLoc); mLoc.mLiteral.clear(); @@ -268,9 +250,8 @@ namespace Compiler nullptr }; - bool Scanner::scanName (MultiChar& c, Parser& parser, bool& cont) + bool Scanner::scanName (MultiChar& c, Parser& parser, bool& cont, std::string name) { - std::string name; c.appendTo(name); if (!scanName (name)) diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 7901297325..8ee2672132 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -236,7 +236,7 @@ namespace Compiler bool scanFloat (const std::string& intValue, Parser& parser, bool& cont); - bool scanName (MultiChar& c, Parser& parser, bool& cont); + bool scanName (MultiChar& c, Parser& parser, bool& cont, std::string name = {}); /// \param name May contain the start of the name (one or more characters) bool scanName (std::string& name); From 34cf66139ab2ef9313bf5f9df31943b2a801eb60 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 16 Sep 2021 16:56:13 +0200 Subject: [PATCH 066/137] Make GetCurrentAIPackage return -1 for non-actors and dead actors --- CHANGELOG.md | 1 + apps/openmw/mwscript/aiextensions.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9ea47c56..aa117463dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6273: Respawning NPCs rotation is inconsistent + Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index d4de8ded5d..c5a4bb6dfc 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -362,7 +362,15 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - const auto value = static_cast(ptr.getClass().getCreatureStats (ptr).getAiSequence().getLastRunTypeId()); + Interpreter::Type_Integer value = -1; + if(ptr.getClass().isActor()) + { + const auto& stats = ptr.getClass().getCreatureStats(ptr); + if(!stats.isDead() || !stats.isDeathAnimationFinished()) + { + value = static_cast(stats.getAiSequence().getLastRunTypeId()); + } + } runtime.push (value); } From c6f7137ee1da615f602c18f365373d5d324481ac Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 27 Sep 2021 18:41:24 +0000 Subject: [PATCH 067/137] fixes bugs with share state (#3111) * optimizer.cpp merge fix * objectpaging.cpp * optimizer.hpp setSharedStateManager * optimizer.cpp shareState * scenemanager.cpp shareState * scenemanager.cpp * optimizer.cpp * optimizer.cpp * scenemanager.cpp * optimizer.cpp --- apps/openmw/mwrender/objectpaging.cpp | 2 +- components/resource/scenemanager.cpp | 12 ++++-------- components/sceneutil/optimizer.cpp | 12 +++++++++++- components/sceneutil/optimizer.hpp | 13 ++++++++++++- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 47eacbe1a2..88c3d4ba02 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -651,7 +651,7 @@ namespace MWRender } optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); unsigned int options = SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS|SceneUtil::Optimizer::REMOVE_REDUNDANT_NODES|SceneUtil::Optimizer::MERGE_GEOMETRY; - mSceneManager->shareState(mergeGroup); + optimizer.optimize(mergeGroup, options); group->addChild(mergeGroup); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b468b430ff..cc38144794 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -659,22 +659,18 @@ namespace Resource osg::ref_ptr shaderVisitor (createShaderVisitor()); loaded->accept(*shaderVisitor); - // share state - // do this before optimizing so the optimizer will be able to combine nodes more aggressively - // note, because StateSets will be shared at this point, StateSets can not be modified inside the optimizer - mSharedStateMutex.lock(); - mSharedStateManager->share(loaded.get()); - mSharedStateMutex.unlock(); - if (canOptimize(normalized)) { SceneUtil::Optimizer optimizer; + optimizer.setSharedStateManager(mSharedStateManager, &mSharedStateMutex); optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback); - static const unsigned int options = getOptimizationOptions(); + static const unsigned int options = getOptimizationOptions()|SceneUtil::Optimizer::SHARE_DUPLICATE_STATE; optimizer.optimize(loaded, options); } + else + shareState(loaded); if (compile && mIncrementalCompileOperation) mIncrementalCompileOperation->add(loaded); diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index 5fbeb681fc..dffbe62ec7 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -30,6 +30,8 @@ #include #include +#include + #include #include #include @@ -84,6 +86,13 @@ void Optimizer::optimize(osg::Node* node, unsigned int options) cstv.removeTransforms(node); } + if (options & SHARE_DUPLICATE_STATE && _sharedStateManager) + { + if (_sharedStateMutex) _sharedStateMutex->lock(); + _sharedStateManager->share(node); + if (_sharedStateMutex) _sharedStateMutex->unlock(); + } + if (options & REMOVE_REDUNDANT_NODES) { OSG_INFO<<"Optimizer::optimize() doing REMOVE_REDUNDANT_NODES"<getNumChildren()==1 && transform->getChild(0)->asTransform()!=0 && transform->getChild(0)->asTransform()->asMatrixTransform()!=0 && - transform->getChild(0)->asTransform()->getDataVariance()==osg::Object::STATIC) + (!transform->getChild(0)->getStateSet() || transform->getChild(0)->getStateSet()->referenceCount()==1) && + transform->getChild(0)->getDataVariance()==osg::Object::STATIC) { // now combine with its child. osg::MatrixTransform* child = transform->getChild(0)->asTransform()->asMatrixTransform(); diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 2d6293e231..7b32bafee2 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -25,6 +25,12 @@ //#include #include +#include + +namespace osgDB +{ + class SharedStateManager; +} //namespace osgUtil { namespace SceneUtil { @@ -65,7 +71,7 @@ class Optimizer public: - Optimizer() : _mergeAlphaBlending(false) {} + Optimizer() : _mergeAlphaBlending(false), _sharedStateManager(nullptr), _sharedStateMutex(nullptr) {} virtual ~Optimizer() {} enum OptimizationOptions @@ -121,6 +127,8 @@ class Optimizer void setMergeAlphaBlending(bool merge) { _mergeAlphaBlending = merge; } void setViewPoint(const osg::Vec3f& viewPoint) { _viewPoint = viewPoint; } + void setSharedStateManager(osgDB::SharedStateManager* sharedStateManager, std::mutex* sharedStateMutex) { _sharedStateMutex = sharedStateMutex; _sharedStateManager = sharedStateManager; } + /** Reset internal data to initial state - the getPermissibleOptionsMap is cleared.*/ void reset(); @@ -258,6 +266,9 @@ class Optimizer osg::Vec3f _viewPoint; bool _mergeAlphaBlending; + osgDB::SharedStateManager* _sharedStateManager; + mutable std::mutex* _sharedStateMutex; + public: /** Flatten Static Transform nodes by applying their transform to the From 449539c0ed66484c9837a2d752de8c9905943bb7 Mon Sep 17 00:00:00 2001 From: psi29a Date: Mon, 27 Sep 2021 19:04:38 +0000 Subject: [PATCH 068/137] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b9ea47c56..cc824b7540 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,8 @@ Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop Bug #6273: Respawning NPCs rotation is inconsistent - Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map From 3f68ddd8f4b44be062e122c7ef73a3f558274a6f Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 27 Sep 2021 19:25:39 +0000 Subject: [PATCH 069/137] alternate debug chunks (#3127) * quadtreeworld.cpp * chunkmanager.cpp * chunkmanager.hpp * quadtreeworld.hpp * chunkmanager.cpp * quadtreeworld.cpp * quadtreeworld.cpp * quadtreeworld.cpp [ci skip] * quadtreeworld.hpp * quadtreeworld.cpp * quadtreeworld.cpp * quadtreeworld.cpp * chunkmanager.cpp * chunkmanager.cpp --- components/terrain/chunkmanager.cpp | 18 -------------- components/terrain/chunkmanager.hpp | 1 - components/terrain/quadtreeworld.cpp | 37 ++++++++++++++++++++++------ components/terrain/quadtreeworld.hpp | 3 +++ 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 7a6d4fdb0a..476805aa3c 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -3,9 +3,7 @@ #include #include -#include #include -#include #include @@ -13,14 +11,12 @@ #include #include -#include #include "terraindrawable.hpp" #include "material.hpp" #include "storage.hpp" #include "texturemanager.hpp" #include "compositemaprenderer.hpp" -#include namespace Terrain { @@ -35,7 +31,6 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T , mCompositeMapSize(512) , mCompositeMapLevel(1.f) , mMaxCompGeometrySize(1.f) - , mDebugChunks(Settings::Manager::getBool("debug chunks", "Terrain")) { mMultiPassRoot = new osg::StateSet; mMultiPassRoot->setRenderingHint(osg::StateSet::OPAQUE_BIN); @@ -238,19 +233,6 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve } geometry->setNodeMask(mNodeMask); - if (mDebugChunks) - { - osg::ref_ptr result(new osg::Group); - result->addChild(geometry); - auto chunkBorder = CellBorder::createBorderGeometry(chunkCenter.x() - chunkSize / 2.f, chunkCenter.y() - chunkSize / 2.f, chunkSize, mStorage, mSceneManager, getNodeMask(), 5.f, { 1, 0, 0, 0 }); - osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; - osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); - trans->setDataVariance(osg::Object::STATIC); - trans->addChild(chunkBorder); - result->addChild(trans); - return result; - } - return geometry; } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 4e030e018c..9b7dbf3ee1 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -72,7 +72,6 @@ namespace Terrain unsigned int mCompositeMapSize; float mCompositeMapLevel; float mMaxCompGeometrySize; - bool mDebugChunks = false; }; } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index bbf212942e..ef37b63ef6 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" @@ -244,6 +245,26 @@ private: osg::ref_ptr mRootNode; }; +class DebugChunkManager : public QuadTreeWorld::ChunkManager +{ +public: + DebugChunkManager(Resource::SceneManager* sceneManager, Storage* storage, unsigned int nodeMask) : mSceneManager(sceneManager), mStorage(storage), mNodeMask(nodeMask) {} + osg::ref_ptr getChunk(float size, const osg::Vec2f& chunkCenter, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + { + osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; + auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size, mStorage, mSceneManager, mNodeMask, 5.f, { 1, 0, 0, 0 }); + osg::ref_ptr trans = new osg::MatrixTransform(osg::Matrixf::translate(-center*Constants::CellSizeInUnits)); + trans->setDataVariance(osg::Object::STATIC); + trans->addChild(chunkBorder); + return trans; + } + unsigned int getNodeMask() { return mNodeMask; } +private: + Resource::SceneManager* mSceneManager; + Storage* mStorage; + unsigned int mNodeMask; +}; + QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) @@ -258,6 +279,12 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour mChunkManager->setCompositeMapLevel(compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); mChunkManagers.push_back(mChunkManager.get()); + + if (mDebugTerrainChunks) + { + mDebugChunkManager = std::unique_ptr(new DebugChunkManager(mResourceSystem->getSceneManager(), mStorage, borderMask)); + addChunkManager(mDebugChunkManager.get()); + } } QuadTreeWorld::~QuadTreeWorld() @@ -352,7 +379,7 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f } } -void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld, bool debugTerrainChunk) +void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil::CullVisitor* cv, float cellworldsize, bool outofworld) { if (!(cv->getTraversalMask() & callback->getCullMask())) return; @@ -368,11 +395,7 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); - osg::BoundingBox bb; - if(debugTerrainChunk) - bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0)->asGroup()->getChild(0))->getWaterBoundingBox(); - else - bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); + osg::BoundingBox bb = static_cast(entry.mRenderingNode->asGroup()->getChild(0))->getWaterBoundingBox(); if (!bb.valid()) continue; osg::Vec3f ofs (entry.mNode->getCenter().x()*cellworldsize, entry.mNode->getCenter().y()*cellworldsize, 0.f); @@ -448,7 +471,7 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) } if (mHeightCullCallback && isCullVisitor) - updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty(), mDebugTerrainChunks); + updateWaterCullingView(mHeightCullCallback, vd, static_cast(&nv), mStorage->getCellWorldSize(), !isGridEmpty()); vd->markUnchanged(); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index f7cbf8097a..cedb0ae9e4 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -5,6 +5,7 @@ #include "terraingrid.hpp" #include +#include namespace osg { @@ -15,6 +16,7 @@ namespace Terrain { class RootNode; class ViewDataMap; + class DebugChunkManager; /// @brief Terrain implementation that loads cells into a Quad Tree, with geometry LOD and texture LOD. class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) @@ -73,6 +75,7 @@ namespace Terrain float mViewDistance; float mMinSize; bool mDebugTerrainChunks; + std::unique_ptr mDebugChunkManager; }; } From 01cc61087b2f8cb23d73699bb76e796a749e63ab Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 27 Sep 2021 19:32:18 +0000 Subject: [PATCH 070/137] improves paging preloader (#3126) * Return check for distance when we try to reuse data * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * cellpreloader.cpp * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * [ci skip] * quadtreeworld.cpp * chunkmanager.cpp * chunkmanager.cpp * cellpreloader.cpp * jvoisin * whitespace * whitespace --- apps/openmw/mwworld/cellpreloader.cpp | 43 ++------- components/terrain/chunkmanager.cpp | 116 +++++++++++++++++-------- components/terrain/chunkmanager.hpp | 3 +- components/terrain/quadtreeworld.cpp | 54 +++++++----- components/terrain/quadtreeworld.hpp | 1 - components/terrain/terraindrawable.cpp | 4 +- components/terrain/terraindrawable.hpp | 2 + components/terrain/viewdata.cpp | 27 ++---- components/terrain/viewdata.hpp | 1 - components/terrain/world.hpp | 4 - 10 files changed, 133 insertions(+), 122 deletions(-) diff --git a/apps/openmw/mwworld/cellpreloader.cpp b/apps/openmw/mwworld/cellpreloader.cpp index f2f2ebc5a6..590e1e9c00 100644 --- a/apps/openmw/mwworld/cellpreloader.cpp +++ b/apps/openmw/mwworld/cellpreloader.cpp @@ -25,7 +25,7 @@ namespace { template bool contains(const std::vector& container, - const Contained& contained, float tolerance=1.f) + const Contained& contained, float tolerance) { for (const auto& pos : contained) { @@ -180,14 +180,6 @@ namespace MWWorld { } - bool storeViews(double referenceTime) - { - for (unsigned int i=0; istoreView(mTerrainViews[i], referenceTime)) - return false; - return true; - } - void doWork() override { for (unsigned int i=0; iisDone()) { - if (!mTerrainPreloadItem->storeViews(timestamp)) - { - if (++mStoreViewsFailCount > 100) - { - OSG_ALWAYS << "paging views are rebuilt every frame, please check for faulty enable/disable scripts." << std::endl; - mStoreViewsFailCount = 0; - } - setTerrainPreloadPositions(std::vector()); - } - else - { - mStoreViewsFailCount = 0; - mLoadedTerrainPositions = mTerrainPreloadPositions; - mLoadedTerrainTimestamp = timestamp; - } - mTerrainPreloadItem = nullptr; + mLoadedTerrainPositions = mTerrainPreloadPositions; + mLoadedTerrainTimestamp = timestamp; } } @@ -443,17 +420,7 @@ namespace MWWorld return true; else if (mTerrainPreloadItem->isDone()) { - if (mTerrainPreloadItem->storeViews(timestamp)) - { - mTerrainPreloadItem = nullptr; - return true; - } - else - { - setTerrainPreloadPositions(std::vector()); - setTerrainPreloadPositions(positions); - return false; - } + return true; } else { @@ -481,7 +448,7 @@ namespace MWWorld mTerrainPreloadPositions.clear(); mLoadedTerrainPositions.clear(); } - else if (contains(mTerrainPreloadPositions, positions)) + else if (contains(mTerrainPreloadPositions, positions, 128.f)) return; if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone()) return; diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 476805aa3c..0c84ddd9b9 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -39,6 +39,17 @@ ChunkManager::ChunkManager(Storage *storage, Resource::SceneManager *sceneMgr, T mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } +struct FindChunkTemplate +{ + void operator() (ChunkId id, osg::Object* obj) + { + if (std::get<0>(id) == std::get<0>(mId) && std::get<1>(id) == std::get<1>(mId)) + mFoundTemplate = obj; + } + ChunkId mId; + osg::ref_ptr mFoundTemplate; +}; + osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { ChunkId id = std::make_tuple(center, lod, lodFlags); @@ -47,7 +58,11 @@ osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& cen return static_cast(obj.get()); else { - osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile); + FindChunkTemplate find; + find.mId = id; + mCache->call(find); + TerrainDrawable* templateGeometry = (find.mFoundTemplate && !mDebugChunks) ? static_cast(find.mFoundTemplate.get()) : nullptr; + osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); mCache->addEntryToObjectCache(id, node.get()); return node; } @@ -165,24 +180,45 @@ std::vector > ChunkManager::createPasses(float chunk return ::Terrain::createPasses(useShaders, &mSceneManager->getShaderManager(), layers, blendmapTextures, blendmapScale, blendmapScale); } -osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile) +osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f &chunkCenter, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry) { - osg::ref_ptr positions (new osg::Vec3Array); - osg::ref_ptr normals (new osg::Vec3Array); - osg::ref_ptr colors (new osg::Vec4ubArray); - colors->setNormalize(true); - - osg::ref_ptr vbo (new osg::VertexBufferObject); - positions->setVertexBufferObject(vbo); - normals->setVertexBufferObject(vbo); - colors->setVertexBufferObject(vbo); - - mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors); - osg::ref_ptr geometry (new TerrainDrawable); - geometry->setVertexArray(positions); - geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); - geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + + if (!templateGeometry) + { + osg::ref_ptr positions (new osg::Vec3Array); + osg::ref_ptr normals (new osg::Vec3Array); + osg::ref_ptr colors (new osg::Vec4ubArray); + colors->setNormalize(true); + + mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors); + + osg::ref_ptr vbo (new osg::VertexBufferObject); + positions->setVertexBufferObject(vbo); + normals->setVertexBufferObject(vbo); + colors->setVertexBufferObject(vbo); + + geometry->setVertexArray(positions); + geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + } + else + { + // Unfortunately we need to copy vertex data because of poor coupling with VertexBufferObject. + osg::ref_ptr positions = static_cast(templateGeometry->getVertexArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); + osg::ref_ptr normals = static_cast(templateGeometry->getNormalArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); + osg::ref_ptr colors = static_cast(templateGeometry->getColorArray()->clone(osg::CopyOp::DEEP_COPY_ALL)); + + osg::ref_ptr vbo (new osg::VertexBufferObject); + positions->setVertexBufferObject(vbo); + normals->setVertexBufferObject(vbo); + colors->setVertexBufferObject(vbo); + + geometry->setVertexArray(positions); + geometry->setNormalArray(normals, osg::Array::BIND_PER_VERTEX); + geometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + } + geometry->setUseDisplayList(false); geometry->setUseVertexBufferObjects(true); @@ -202,32 +238,44 @@ osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Ve geometry->setStateSet(mMultiPassRoot); - if (useCompositeMap) + if (templateGeometry) { - osg::ref_ptr compositeMap = new CompositeMap; - compositeMap->mTexture = createCompositeMapRTT(); + if (templateGeometry->getCompositeMap()) + { + geometry->setCompositeMap(templateGeometry->getCompositeMap()); + geometry->setCompositeMapRenderer(mCompositeMapRenderer); + } + geometry->setPasses(templateGeometry->getPasses()); + } + else + { + if (useCompositeMap) + { + osg::ref_ptr compositeMap = new CompositeMap; + compositeMap->mTexture = createCompositeMapRTT(); - createCompositeMapGeometry(chunkSize, chunkCenter, osg::Vec4f(0,0,1,1), *compositeMap); + createCompositeMapGeometry(chunkSize, chunkCenter, osg::Vec4f(0,0,1,1), *compositeMap); - mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false); + mCompositeMapRenderer->addCompositeMap(compositeMap.get(), false); - geometry->setCompositeMap(compositeMap); - geometry->setCompositeMapRenderer(mCompositeMapRenderer); + geometry->setCompositeMap(compositeMap); + geometry->setCompositeMapRenderer(mCompositeMapRenderer); - TextureLayer layer; - layer.mDiffuseMap = compositeMap->mTexture; - layer.mParallax = false; - layer.mSpecular = false; - geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); - } - else - { - geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); + TextureLayer layer; + layer.mDiffuseMap = compositeMap->mTexture; + layer.mParallax = false; + layer.mSpecular = false; + geometry->setPasses(::Terrain::createPasses(mSceneManager->getForceShaders() || !mSceneManager->getClampLighting(), &mSceneManager->getShaderManager(), std::vector(1, layer), std::vector >(), 1.f, 1.f)); + } + else + { + geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); + } } geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts); - if (compile && mSceneManager->getIncrementalCompileOperation()) + if (!templateGeometry && compile && mSceneManager->getIncrementalCompileOperation()) { mSceneManager->getIncrementalCompileOperation()->add(geometry); } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 9b7dbf3ee1..9b85e81330 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -26,6 +26,7 @@ namespace Terrain class CompositeMapRenderer; class Storage; class CompositeMap; + class TerrainDrawable; typedef std::tuple ChunkId; // Center, Lod, Lod Flags @@ -51,7 +52,7 @@ namespace Terrain void releaseGLObjects(osg::State* state) override; private: - osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool compile); + osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry); osg::ref_ptr createCompositeMapRTT(); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index ef37b63ef6..24f042b941 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -55,11 +55,12 @@ namespace Terrain class DefaultLodCallback : public LodCallback { public: - DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid) + DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid, float distanceModifier=0.f) : mFactor(factor) , mMinSize(minSize) , mViewDistance(viewDistance) , mActiveGrid(grid) + , mDistanceModifier(distanceModifier) { } @@ -78,6 +79,8 @@ public: return Deeper; } + dist = std::max(0.f, dist + mDistanceModifier); + if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded return StopTraversal; @@ -92,6 +95,7 @@ private: float mMinSize; float mViewDistance; osg::Vec4i mActiveGrid; + float mDistanceModifier; }; class RootNode : public QuadTreeNode @@ -370,7 +374,7 @@ void loadRenderingNode(ViewData::Entry& entry, ViewData* vd, int vertexLodMod, f for (QuadTreeWorld::ChunkManager* m : chunkManagers) { - if (m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance*10) + if (m->getViewDistance() && entry.mNode->distance(vd->getViewPoint()) > m->getViewDistance() + reuseDistance) continue; osg::ref_ptr n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), ourLod, entry.mLodFlags, activeGrid, vd->getViewPoint(), compile); if (n) pat->addChild(n); @@ -519,39 +523,43 @@ View* QuadTreeWorld::createView() void QuadTreeWorld::preload(View *view, const osg::Vec3f &viewPoint, const osg::Vec4i &grid, std::atomic &abort, Loading::Reporter& reporter) { ensureQuadTreeBuilt(); + const float cellWorldSize = mStorage->getCellWorldSize(); ViewData* vd = static_cast(view); vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); - DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid); - mRootNode->traverseNodes(vd, viewPoint, &lodCallback); - - std::size_t progressTotal = 0; - for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) - progressTotal += vd->getEntry(i).mNode->getSize(); - - reporter.addTotal(progressTotal); - const float cellWorldSize = mStorage->getCellWorldSize(); - for (unsigned int i=0; igetNumEntries() && !abort; ++i) + for (unsigned int pass=0; pass<3; ++pass) { - ViewData::Entry& entry = vd->getEntry(i); - - + unsigned int startEntry = vd->getNumEntries(); - loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true, mViewDataMap->getReuseDistance()); - reporter.addProgress(entry.mNode->getSize()); + float distanceModifier=0.f; + if (pass == 1) + distanceModifier = 1024; + else if (pass == 2) + distanceModifier = -1024; + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, distanceModifier); + mRootNode->traverseNodes(vd, viewPoint, &lodCallback); + if (pass==0) + { + std::size_t progressTotal = 0; + for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) + progressTotal += vd->getEntry(i).mNode->getSize(); + reporter.addTotal(progressTotal); + } + const float reuseDistance = std::max(mViewDataMap->getReuseDistance(), std::abs(distanceModifier)); + for (unsigned int i=startEntry; igetNumEntries() && !abort; ++i) + { + ViewData::Entry& entry = vd->getEntry(i); + loadRenderingNode(entry, vd, mVertexLodMod, cellWorldSize, grid, mChunkManagers, true, reuseDistance); + if (pass==0) reporter.addProgress(entry.mNode->getSize()); + entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass + } } - vd->markUnchanged(); -} - -bool QuadTreeWorld::storeView(const View* view, double referenceTime) -{ - return mViewDataMap->storeView(static_cast(view), referenceTime); } void QuadTreeWorld::reportStats(unsigned int frameNumber, osg::Stats *stats) diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index cedb0ae9e4..3aabc7233e 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -40,7 +40,6 @@ namespace Terrain View* createView() override; void preload(View* view, const osg::Vec3f& eyePoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) override; - bool storeView(const View* view, double referenceTime) override; void rebuildViews() override; void reportStats(unsigned int frameNumber, osg::Stats* stats) override; diff --git a/components/terrain/terraindrawable.cpp b/components/terrain/terraindrawable.cpp index 746534abb4..231b6f4fed 100644 --- a/components/terrain/terraindrawable.cpp +++ b/components/terrain/terraindrawable.cpp @@ -94,10 +94,10 @@ void TerrainDrawable::cull(osgUtil::CullVisitor *cv) return; } - if (mCompositeMap) + if (mCompositeMap && mCompositeMapRenderer) { mCompositeMapRenderer->setImmediate(mCompositeMap); - mCompositeMap = nullptr; + mCompositeMapRenderer = nullptr; } bool pushedLight = mLightListCallback && mLightListCallback->pushLightState(this, cv); diff --git a/components/terrain/terraindrawable.hpp b/components/terrain/terraindrawable.hpp index dbfdd3c80a..721abe7481 100644 --- a/components/terrain/terraindrawable.hpp +++ b/components/terrain/terraindrawable.hpp @@ -45,6 +45,7 @@ namespace Terrain typedef std::vector > PassVector; void setPasses (const PassVector& passes); + const PassVector& getPasses() const { return mPasses; } void setLightListCallback(SceneUtil::LightListCallback* lightListCallback); @@ -56,6 +57,7 @@ namespace Terrain const osg::BoundingBox& getWaterBoundingBox() const { return mWaterBoundingBox; } void setCompositeMap(CompositeMap* map) { mCompositeMap = map; } + CompositeMap* getCompositeMap() { return mCompositeMap; } void setCompositeMapRenderer(CompositeMapRenderer* renderer) { mCompositeMapRenderer = renderer; } private: diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index e4d043ffc4..c5070fdf0d 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -143,7 +143,7 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - float shortestDist = std::numeric_limits::max(); + float shortestDist = mReuseDistance*mReuseDistance; const ViewData* mostSuitableView = nullptr; for (const ViewData* other : mUsedViews) { @@ -157,31 +157,22 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } } } - if (mostSuitableView && mostSuitableView != vd) + if (mostSuitableView) { vd->copyFrom(*mostSuitableView); return vd; } - } - if (!vd->suitableToUse(activeGrid)) - { - vd->setViewPoint(viewPoint); - vd->setActiveGrid(activeGrid); - needsUpdate = true; + else + { + vd->setViewPoint(viewPoint); + vd->setActiveGrid(activeGrid); + vd->setWorldUpdateRevision(mWorldUpdateRevision); + needsUpdate = true; + } } return vd; } -bool ViewDataMap::storeView(const ViewData* view, double referenceTime) -{ - if (view->getWorldUpdateRevision() < mWorldUpdateRevision) - return false; - ViewData* store = createOrReuseView(); - store->copyFrom(*view); - store->setLastUsageTimeStamp(referenceTime); - return true; -} - ViewData *ViewDataMap::createOrReuseView() { ViewData* vd = nullptr; diff --git a/components/terrain/viewdata.hpp b/components/terrain/viewdata.hpp index 378d07663c..5d814251ea 100644 --- a/components/terrain/viewdata.hpp +++ b/components/terrain/viewdata.hpp @@ -93,7 +93,6 @@ namespace Terrain void clearUnusedViews(double referenceTime); void rebuildViews(); - bool storeView(const ViewData* view, double referenceTime); float getReuseDistance() const { return mReuseDistance; } diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index b62a1cb568..8d60e4c5b6 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -155,10 +155,6 @@ namespace Terrain virtual void preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i &cellgrid, std::atomic& abort, Loading::Reporter& reporter) {} - /// Store a preloaded view into the cache with the intent that the next rendering traversal can use it. - /// @note Not thread safe. - virtual bool storeView(const View* view, double referenceTime) {return true;} - virtual void rebuildViews() {} virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) {} From 9fabf9925057071502d7054e2bd8264d53fcd8af Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 27 Sep 2021 21:38:12 +0200 Subject: [PATCH 071/137] remove mDebugChunks from chunkManager --- components/terrain/chunkmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 0c84ddd9b9..e040cbdd93 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -61,7 +61,7 @@ osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& cen FindChunkTemplate find; find.mId = id; mCache->call(find); - TerrainDrawable* templateGeometry = (find.mFoundTemplate && !mDebugChunks) ? static_cast(find.mFoundTemplate.get()) : nullptr; + TerrainDrawable* templateGeometry = find.mFoundTemplate ? static_cast(find.mFoundTemplate.get()) : nullptr; osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); mCache->addEntryToObjectCache(id, node.get()); return node; From e760a6c7e64dd9a4692a957aa5663ac21b58864b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 27 Sep 2021 19:34:26 +0000 Subject: [PATCH 072/137] Merge branch 'forlua' into 'master' Simple Physics API modification for Lua See merge request OpenMW/openmw!1216 (cherry picked from commit d88494c90b501d0832ae0330a0ca81d8b8e5aa50) 02dd055a Save hitObject in castSphere() just like in castRay() 0793d0bf Allow to override collision mask and group for castSphere() as for castRay() --- apps/openmw/mwphysics/collisiontype.hpp | 3 ++- apps/openmw/mwphysics/physicssystem.cpp | 8 +++++--- apps/openmw/mwphysics/physicssystem.hpp | 5 +++-- apps/openmw/mwphysics/raycasting.hpp | 5 +++-- apps/openmw/mwrender/camera.cpp | 5 +++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwphysics/collisiontype.hpp b/apps/openmw/mwphysics/collisiontype.hpp index 0d6a32fc09..e69534cf68 100644 --- a/apps/openmw/mwphysics/collisiontype.hpp +++ b/apps/openmw/mwphysics/collisiontype.hpp @@ -10,7 +10,8 @@ enum CollisionType { CollisionType_Actor = 1<<2, CollisionType_HeightMap = 1<<3, CollisionType_Projectile = 1<<4, - CollisionType_Water = 1<<5 + CollisionType_Water = 1<<5, + CollisionType_Default = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door }; } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 5b0a180354..8499dc74c1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -348,11 +348,11 @@ namespace MWPhysics return result; } - RayCastingResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius) const + RayCastingResult PhysicsSystem::castSphere(const osg::Vec3f &from, const osg::Vec3f &to, float radius, int mask, int group) const { btCollisionWorld::ClosestConvexResultCallback callback(Misc::Convert::toBullet(from), Misc::Convert::toBullet(to)); - callback.m_collisionFilterGroup = 0xff; - callback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; + callback.m_collisionFilterGroup = group; + callback.m_collisionFilterMask = mask; btSphereShape shape(radius); const btQuaternion btrot = btQuaternion::getIdentity(); @@ -368,6 +368,8 @@ namespace MWPhysics { result.mHitPos = Misc::Convert::toOsg(callback.m_hitPointWorld); result.mHitNormal = Misc::Convert::toOsg(callback.m_hitNormalWorld); + if (auto* ptrHolder = static_cast(callback.m_hitCollisionObject->getUserPointer())) + result.mHitObject = ptrHolder->getPtr(); } return result; } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 5ee99d2d15..52515b563f 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -186,9 +186,10 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), const std::vector& targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const override; + int mask = CollisionType_Default, int group=0xff) const override; - RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override; + RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, + int mask = CollisionType_Default, int group=0xff) const override; /// Return true if actor1 can see actor2. bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const override; diff --git a/apps/openmw/mwphysics/raycasting.hpp b/apps/openmw/mwphysics/raycasting.hpp index e13e745fec..d00f23e2c4 100644 --- a/apps/openmw/mwphysics/raycasting.hpp +++ b/apps/openmw/mwphysics/raycasting.hpp @@ -29,9 +29,10 @@ namespace MWPhysics /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore = MWWorld::ConstPtr(), const std::vector& targets = std::vector(), - int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const = 0; + int mask = CollisionType_Default, int group=0xff) const = 0; - virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0; + virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius, + int mask = CollisionType_Default, int group=0xff) const = 0; /// Return true if actor1 can see actor2. virtual bool getLineOfSight(const MWWorld::ConstPtr& actor1, const MWWorld::ConstPtr& actor2) const = 0; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 3e5d1d0b31..edb017c2a2 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -251,6 +251,7 @@ namespace MWRender const float cameraObstacleLimit = 5.0f; const float focalObstacleLimit = 10.f; + const int collisionType = (MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor); const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); @@ -260,7 +261,7 @@ namespace MWRender float offsetLen = focalOffset.length(); if (offsetLen > 0) { - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, collisionType); if (result.mHit) { double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; @@ -274,7 +275,7 @@ namespace MWRender mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance); osg::Vec3d cameraPos; getPosition(focal, cameraPos); - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, cameraPos, cameraObstacleLimit, collisionType); if (result.mHit) mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); } From 77a23dab099ba69ae0c208e179a981ec4c6d4fcf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 27 Sep 2021 22:23:00 +0200 Subject: [PATCH 073/137] Only add enabled objects to the scene --- CHANGELOG.md | 1 + apps/openmw/mwworld/worldimp.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d98a802a4f..8dc9510f6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Bug #6302: Teleporting disabled actor breaks its disabled state Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5e93aadf4d..da5daed295 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1159,7 +1159,8 @@ namespace MWWorld if (!currCellActive && newCellActive) { newPtr = currCell->moveTo(ptr, newCell); - mWorldScene->addObjectToScene(newPtr); + if(newPtr.getRefData().isEnabled()) + mWorldScene->addObjectToScene(newPtr); std::string script = newPtr.getClass().getScript(newPtr); if (!script.empty()) From d854e247b82373524a50f17c916546d8c127dd15 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 28 Sep 2021 06:25:12 +0000 Subject: [PATCH 074/137] reword warning in settings-default.cfg (#3124) * reword warning in settings-default.cfg The warning at the head of settings-default.cfg is somewhat confusing for users, overly verbose and lacking the most crucial piece of information in its first sentence. Hence, it is unsurprising some users fail to heed the warning, as observed through user support requests on Matrix. * settings-default.cfg --- files/settings-default.cfg | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 08039f5fbe..5ed8109465 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1,15 +1,7 @@ -# WARNING: If this file is named settings-default.cfg or was generated -# from defaults.bin, then editing this file might have no effect, as -# these settings may be overwritten by your user settings.cfg file -# (see documentation for its location). +# WARNING: Users should NOT edit this file. Users should add their personal preferences to the settings.cfg file overriding this file. +# For the location of the settings.cfg file, as well as more detailed settings documentation, refer to: # -# This file provides minimal documentation for each setting, and -# ranges of recommended values. For detailed explanations of the -# significance of each setting, interaction with other settings, hard -# limits on value ranges and more information in general, please read -# the detailed documentation at: -# -# https://openmw.readthedocs.io/en/master/reference/modding/settings/index.html +# https://openmw.readthedocs.io/en/latest/reference/modding/settings/index.html # [Camera] From 5fde6867a2b72d90bdfb5cb888c10cf6be79bc60 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 28 Sep 2021 07:07:49 +0000 Subject: [PATCH 075/137] removes unused code (#3129) * scenemanager.cpp * scenemanager.hpp * scenemanager.cpp * stats.cpp * renderingmanager.cpp --- apps/openmw/mwrender/renderingmanager.cpp | 2 +- components/resource/scenemanager.cpp | 31 +---------------------- components/resource/scenemanager.hpp | 10 -------- components/resource/stats.cpp | 1 - 4 files changed, 2 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 19a887fe2e..74e183693c 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -262,7 +262,7 @@ namespace MWRender try { for (std::vector::const_iterator it = mModels.begin(); it != mModels.end(); ++it) - mResourceSystem->getSceneManager()->cacheInstance(*it); + mResourceSystem->getSceneManager()->getTemplate(*it); for (std::vector::const_iterator it = mTextures.begin(); it != mTextures.end(); ++it) mResourceSystem->getImageManager()->getImage(*it); for (std::vector::const_iterator it = mKeyframes.begin(); it != mKeyframes.end(); ++it) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index cc38144794..68596985ce 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -35,7 +35,6 @@ #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" -#include "multiobjectcache.hpp" namespace { @@ -306,7 +305,6 @@ namespace Resource , mLightingMethod(SceneUtil::LightingMethod::FFP) , mConvertAlphaTestToAlphaToCoverage(false) , mDepthFormat(0) - , mInstanceCache(new MultiObjectCache) , mSharedStateManager(new SharedStateManager) , mImageManager(imageManager) , mNifFileManager(nifFileManager) @@ -682,21 +680,6 @@ namespace Resource } } - osg::ref_ptr SceneManager::cacheInstance(const std::string &name) - { - const std::string normalized = mVFS->normalizeFilename(name); - - osg::ref_ptr node = createInstance(normalized); - - // Note: osg::clone() does not calculate bound volumes. - // Do it immediately, otherwise we will need to update them for all objects - // during first update traversal, what may lead to stuttering during cell transitions - node->getBound(); - - mInstanceCache->addEntryToObjectCache(normalized, node.get()); - return node; - } - osg::ref_ptr SceneManager::createInstance(const std::string& name) { osg::ref_ptr scene = getTemplate(name); @@ -722,14 +705,7 @@ namespace Resource osg::ref_ptr SceneManager::getInstance(const std::string &name) { - const std::string normalized = mVFS->normalizeFilename(name); - - osg::ref_ptr obj = mInstanceCache->takeFromObjectCache(normalized); - if (obj.get()) - return static_cast(obj.get()); - - return createInstance(normalized); - + return createInstance(name); } osg::ref_ptr SceneManager::getInstance(const std::string &name, osg::Group* parentNode) @@ -747,7 +723,6 @@ namespace Resource void SceneManager::releaseGLObjects(osg::State *state) { mCache->releaseGLObjects(state); - mInstanceCache->releaseGLObjects(state); mShaderManager->releaseGLObjects(state); @@ -835,8 +810,6 @@ namespace Resource { ResourceManager::updateCache(referenceTime); - mInstanceCache->removeUnreferencedObjectsInCache(); - mSharedStateMutex.lock(); mSharedStateManager->prune(); mSharedStateMutex.unlock(); @@ -866,7 +839,6 @@ namespace Resource std::lock_guard lock(mSharedStateMutex); mSharedStateManager->clearCache(); - mInstanceCache->clear(); } void SceneManager::reportStats(unsigned int frameNumber, osg::Stats *stats) const @@ -884,7 +856,6 @@ namespace Resource } stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); - stats->setAttribute(frameNumber, "Node Instance", mInstanceCache->getCacheSize()); } Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 9d798177a1..7434a25309 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -65,8 +65,6 @@ namespace Resource std::vector> mObjects; }; - class MultiObjectCache; - /// @brief Handles loading and caching of scenes, e.g. .nif files or .osg files /// @note Some methods of the scene manager can be used from any thread, see the methods documentation for more details. class SceneManager : public ResourceManager @@ -129,12 +127,6 @@ namespace Resource /// @note Thread safe. osg::ref_ptr getTemplate(const std::string& name, bool compile=true); - /// Create an instance of the given scene template and cache it for later use, so that future calls to getInstance() can simply - /// return this cached object instead of creating a new one. - /// @note The returned ref_ptr may be kept around by the caller to ensure that the object stays in cache for as long as needed. - /// @note Thread safe. - osg::ref_ptr cacheInstance(const std::string& name); - osg::ref_ptr createInstance(const std::string& name); osg::ref_ptr createInstance(const osg::Node* base); @@ -207,8 +199,6 @@ namespace Resource bool mConvertAlphaTestToAlphaToCoverage; GLenum mDepthFormat; - osg::ref_ptr mInstanceCache; - osg::ref_ptr mSharedStateManager; mutable std::mutex mSharedStateMutex; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 9881d0458b..b3705f69cc 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -376,7 +376,6 @@ void StatsHandler::setUpScene(osgViewer::ViewerBase *viewer) "Texture", "StateSet", "Node", - "Node Instance", "Shape", "Shape Instance", "Image", From fd251dfe55611ee11d3d082822eaa70527ca3e45 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 28 Sep 2021 09:19:48 +0200 Subject: [PATCH 076/137] remove unused member variable --- apps/openmw/mwworld/cellpreloader.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwworld/cellpreloader.hpp b/apps/openmw/mwworld/cellpreloader.hpp index 39436dc5ad..0c3bcb4f99 100644 --- a/apps/openmw/mwworld/cellpreloader.hpp +++ b/apps/openmw/mwworld/cellpreloader.hpp @@ -94,7 +94,6 @@ namespace MWWorld bool mPreloadInstances; double mLastResourceCacheUpdate; - int mStoreViewsFailCount; struct PreloadEntry { From 0bd1c22e241f78d529019558bb544eb5deaf576b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 28 Aug 2021 10:47:57 +0200 Subject: [PATCH 077/137] Raycasting in Lua --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/luabindings.cpp | 32 +------- apps/openmw/mwlua/luabindings.hpp | 4 +- apps/openmw/mwlua/nearbybindings.cpp | 108 +++++++++++++++++++++++++++ files/lua_api/openmw/nearby.lua | 42 +++++++++++ 5 files changed, 155 insertions(+), 33 deletions(-) create mode 100644 apps/openmw/mwlua/nearbybindings.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 5605ff229e..82a91699a9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp actions object worldview userdataserializer eventqueue query luabindings localscripts objectbindings cellbindings asyncbindings settingsbindings - camerabindings uibindings inputbindings + camerabindings uibindings inputbindings nearbybindings ) add_openmw_dir (mwsound diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index e37241d692..6526a18367 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 5; + api["API_REVISION"] = 6; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); @@ -110,36 +110,6 @@ namespace MWLua return LuaUtil::makeReadOnly(api); } - sol::table initNearbyPackage(const Context& context) - { - sol::table api(context.mLua->sol(), sol::create); - WorldView* worldView = context.mWorldView; - api["activators"] = LObjectList{worldView->getActivatorsInScene()}; - api["actors"] = LObjectList{worldView->getActorsInScene()}; - api["containers"] = LObjectList{worldView->getContainersInScene()}; - api["doors"] = LObjectList{worldView->getDoorsInScene()}; - api["items"] = LObjectList{worldView->getItemsInScene()}; - api["selectObjects"] = [context](const Queries::Query& query) - { - ObjectIdList list; - WorldView* worldView = context.mWorldView; - if (query.mQueryType == "activators") - list = worldView->getActivatorsInScene(); - else if (query.mQueryType == "actors") - list = worldView->getActorsInScene(); - else if (query.mQueryType == "containers") - list = worldView->getContainersInScene(); - else if (query.mQueryType == "doors") - list = worldView->getDoorsInScene(); - else if (query.mQueryType == "items") - list = worldView->getItemsInScene(); - return LObjectList{selectObjectsFromList(query, list, context)}; - // TODO: Maybe use sqlite - // return LObjectList{worldView->selectObjects(query, true)}; - }; - return LuaUtil::makeReadOnly(api); - } - sol::table initQueryPackage(const Context& context) { Queries::registerQueryBindings(context.mLua->sol()); diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index c58b556a3d..d34ff3d727 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -21,11 +21,13 @@ namespace MWLua sol::table initCorePackage(const Context&); sol::table initWorldPackage(const Context&); - sol::table initNearbyPackage(const Context&); sol::table initQueryPackage(const Context&); sol::table initFieldGroup(const Context&, const QueryFieldGroup&); + // Implemented in nearbybindings.cpp + sol::table initNearbyPackage(const Context&); + // Implemented in objectbindings.cpp void initObjectBindingsForLocalScripts(const Context&); void initObjectBindingsForGlobalScripts(const Context&); diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp new file mode 100644 index 0000000000..16e55bd6b1 --- /dev/null +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -0,0 +1,108 @@ +#include "luabindings.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwphysics/raycasting.hpp" + +#include "worldview.hpp" + +namespace sol +{ + template <> + struct is_automagical : std::false_type {}; +} + +namespace MWLua +{ + sol::table initNearbyPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + WorldView* worldView = context.mWorldView; + + sol::usertype rayResult = + context.mLua->sol().new_usertype("RayCastingResult"); + rayResult["hit"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) { return r.mHit; }); + rayResult["hitPos"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional + { + if (r.mHit) + return r.mHitPos; + else + return sol::nullopt; + }); + rayResult["hitNormal"] = sol::readonly_property([](const MWPhysics::RayCastingResult& r) -> sol::optional + { + if (r.mHit) + return r.mHitNormal; + else + return sol::nullopt; + }); + rayResult["hitObject"] = sol::readonly_property([worldView](const MWPhysics::RayCastingResult& r) -> sol::optional + { + if (r.mHitObject.isEmpty()) + return sol::nullopt; + else + return LObject(getId(r.mHitObject), worldView->getObjectRegistry()); + }); + + constexpr int defaultCollisionType = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | + MWPhysics::CollisionType_Actor | MWPhysics::CollisionType_Door; + api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( + "World", MWPhysics::CollisionType_World, + "Door", MWPhysics::CollisionType_Door, + "Actor", MWPhysics::CollisionType_Actor, + "HeightMap", MWPhysics::CollisionType_HeightMap, + "Projectile", MWPhysics::CollisionType_Projectile, + "Water", MWPhysics::CollisionType_Water, + "Default", defaultCollisionType)); + + api["castRay"] = [defaultCollisionType](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) + { + MWWorld::Ptr ignore; + int collisionType = defaultCollisionType; + float radius = 0; + if (options) + { + sol::optional ignoreObj = options->get>("ignore"); + if (ignoreObj) ignore = ignoreObj->ptr(); + collisionType = options->get>("collisionType").value_or(collisionType); + radius = options->get>("radius").value_or(0); + } + const MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + if (radius <= 0) + return rayCasting->castRay(from, to, ignore, std::vector(), collisionType); + else + { + if (!ignore.isEmpty()) throw std::logic_error("Currently castRay doesn't support `ignore` when radius > 0"); + return rayCasting->castSphere(from, to, radius, collisionType); + } + }; + + api["activators"] = LObjectList{worldView->getActivatorsInScene()}; + api["actors"] = LObjectList{worldView->getActorsInScene()}; + api["containers"] = LObjectList{worldView->getContainersInScene()}; + api["doors"] = LObjectList{worldView->getDoorsInScene()}; + api["items"] = LObjectList{worldView->getItemsInScene()}; + api["selectObjects"] = [context](const Queries::Query& query) + { + ObjectIdList list; + WorldView* worldView = context.mWorldView; + if (query.mQueryType == "activators") + list = worldView->getActivatorsInScene(); + else if (query.mQueryType == "actors") + list = worldView->getActorsInScene(); + else if (query.mQueryType == "containers") + list = worldView->getContainersInScene(); + else if (query.mQueryType == "doors") + list = worldView->getDoorsInScene(); + else if (query.mQueryType == "items") + list = worldView->getItemsInScene(); + return LObjectList{selectObjectsFromList(query, list, context)}; + // TODO: Maybe use sqlite + // return LObjectList{worldView->selectObjects(query, true)}; + }; + return LuaUtil::makeReadOnly(api); + } +} diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index c24d5e3d10..f1aeb07b24 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -32,5 +32,47 @@ -- @param openmw.query#Query query -- @return openmw.core#ObjectList +------------------------------------------------------------------------------- +-- @type COLLISION_TYPE +-- @field [parent=#COLLISION_TYPE] #number World +-- @field [parent=#COLLISION_TYPE] #number Door +-- @field [parent=#COLLISION_TYPE] #number Actor +-- @field [parent=#COLLISION_TYPE] #number HeightMap +-- @field [parent=#COLLISION_TYPE] #number Projectile +-- @field [parent=#COLLISION_TYPE] #number Water +-- @field [parent=#COLLISION_TYPE] #number Default Used by deafult: World+Door+Actor+HeightMap + +------------------------------------------------------------------------------- +-- Collision types that are used in `castRay`. +-- Several types can be combined with '+'. +-- @field [parent=#nearby] #COLLISION_TYPE COLLISION_TYPE + +------------------------------------------------------------------------------- +-- Result of raycasing +-- @type RayCastingResult +-- @field [parent=#RayCastingResult] #boolean hit Is there a collision? (true/false) +-- @field [parent=#RayCastingResult] openmw.util#Vector3 hitPos Position of the collision point (nil if no collision) +-- @field [parent=#RayCastingResult] openmw.util#Vector3 hitNormal Normal to the surface in the collision point (nil if no collision) +-- @field [parent=#RayCastingResult] openmw.core#GameObject hitObject The object the ray has collided with (can be nil) + +------------------------------------------------------------------------------- +-- Cast ray from one point to another and return the first collision. +-- @function [parent=#nearby] castRay +-- @param openmw.util#Vector3 from Start point of the ray. +-- @param openmw.util#Vector3 to End point of the ray. +-- @param #table options An optional table with additional optional arguments. Can contain: +-- `ignore` - an object to ignore (specify here the source of the ray); +-- `collisionType` - object types to work with (see @{openmw.nearby#COLLISION_TYPE}), several types can be combined with '+'; +-- `radius` - the radius of the ray (zero by default). If not zero then castRay actually casts a sphere with given radius. +-- NOTE: currently `ignore` is not supported if `radius>0`. +-- @return #RayCastingResult +-- @usage if nearby.castRay(pointA, pointB).hit then print('obstacle between A and B') end +-- @usage local res = nearby.castRay(self.position, enemy.position, {ignore=self}) +-- if res.hitObject and res.hitObject ~= enemy then obstacle = res.hitObject end +-- @usage local res = nearby.castRay(self.position, targetPos, { +-- collisionType=nearby.COLLISION_TYPE.HeightMap + nearby.COLLISION_TYPE.Water, +-- radius = 10, +-- }) + return nil From fb3917fc1ae00e2deb3dd5a3c85c6ed22c344172 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 27 Aug 2021 09:26:38 +0200 Subject: [PATCH 078/137] Lua callbacks --- apps/openmw/mwlua/asyncbindings.cpp | 9 +++++++- apps/openmw/mwlua/luabindings.hpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 15 +++++++++++++ apps/openmw/mwlua/luamanagerimp.hpp | 32 ++++++++++++++++++++++++++++ apps/openmw/mwlua/nearbybindings.cpp | 14 ++++++++++++ components/lua/scriptscontainer.cpp | 12 +++++++++++ components/lua/scriptscontainer.hpp | 4 +++- files/lua_api/openmw/async.lua | 7 ++++++ 8 files changed, 92 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/asyncbindings.cpp b/apps/openmw/mwlua/asyncbindings.cpp index fee6788b89..9fdda53d9d 100644 --- a/apps/openmw/mwlua/asyncbindings.cpp +++ b/apps/openmw/mwlua/asyncbindings.cpp @@ -1,5 +1,7 @@ #include "luabindings.hpp" +#include "luamanagerimp.hpp" + namespace sol { template <> @@ -48,11 +50,16 @@ namespace MWLua asyncId.mContainer->setupUnsavableTimer( TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback)); }; + api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) + { + return Callback{std::move(fn), asyncId.mHiddenData}; + }; auto initializer = [](sol::table hiddenData) { LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY]; - return AsyncPackageId{id.mContainer, id.mPath}; + hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString(); + return AsyncPackageId{id.mContainer, id.mPath, hiddenData}; }; return sol::make_object(context.mLua->sol(), initializer); } diff --git a/apps/openmw/mwlua/luabindings.hpp b/apps/openmw/mwlua/luabindings.hpp index d34ff3d727..d1c62e43e3 100644 --- a/apps/openmw/mwlua/luabindings.hpp +++ b/apps/openmw/mwlua/luabindings.hpp @@ -47,9 +47,9 @@ namespace MWLua // Implemented in asyncbindings.cpp struct AsyncPackageId { - // TODO: add ObjectId mLocalObject; LuaUtil::ScriptsContainer* mContainer; std::string mScript; + sol::table mHiddenData; }; sol::function getAsyncPackageInitializer(const Context&); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 1e009081ff..38055c99b7 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -74,6 +74,16 @@ namespace MWLua mInitialized = true; } + void Callback::operator()(sol::object arg) const + { + if (mHiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY] != sol::nil) + LuaUtil::call(mFunc, std::move(arg)); + else + { + Log(Debug::Debug) << "Ignored callback to removed script " << mHiddenData.get(SCRIPT_NAME_KEY); + } + } + void LuaManager::update(bool paused, float dt) { ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); @@ -126,6 +136,11 @@ namespace MWLua << ". Object not found or has no attached scripts"; } + // Run queued callbacks + for (CallbackWithData& c : mQueuedCallbacks) + c.mCallback(c.mArg); + mQueuedCallbacks.clear(); + // Engine handlers in local scripts PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 8fdfb02701..91f48171f3 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -19,6 +19,19 @@ namespace MWLua { + // Wrapper for a single-argument Lua function. + // Holds information about the script the function belongs to. + // Needed to prevent callback calls if the script was removed. + struct Callback + { + static constexpr std::string_view SCRIPT_NAME_KEY = "name"; + + sol::function mFunc; + sol::table mHiddenData; + + void operator()(sol::object arg) const; + }; + class LuaManager : public MWBase::LuaManager { public: @@ -67,6 +80,18 @@ namespace MWLua // Drops script cache and reloads all scripts. Calls `onSave` and `onLoad` for every script. void reloadAllScripts() override; + // Used to call Lua callbacks from C++ + void queueCallback(Callback callback, sol::object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); } + + // Wraps Lua callback into an std::function. + // NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or + // any other Lua-related function is running. + template + std::function wrapLuaCallback(const Callback& c) + { + return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); }; + } + private: LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr); @@ -100,6 +125,13 @@ namespace MWLua std::vector mInputEvents; std::vector mActorAddedEvents; + struct CallbackWithData + { + Callback mCallback; + sol::object mArg; + }; + std::vector mQueuedCallbacks; + struct LocalEngineEvent { ObjectId mDest; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 16e55bd6b1..8eae46a9b7 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -79,6 +79,20 @@ namespace MWLua return rayCasting->castSphere(from, to, radius, collisionType); } }; + // TODO: async raycasting + /*api["asyncCastRay"] = [luaManager = context.mLuaManager, defaultCollisionType]( + const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) + { + std::function callback = + luaManager->wrapLuaCallback(luaCallback); + MWPhysics::RayCastingInterface* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); + + // Handle options the same way as in `castRay`. + + // NOTE: `callback` is not thread safe. If MWPhysics works in separate thread, it must put results to a queue + // and use this callback from the main thread at the beginning of the next frame processing. + rayCasting->asyncCastRay(callback, from, to, ignore, std::vector(), collisionType); + };*/ api["activators"] = LObjectList{worldView->getActivatorsInScene()}; api["actors"] = LObjectList{worldView->getActorsInScene()}; diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index 6e0a19b120..b6882b0988 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -16,6 +16,15 @@ namespace LuaUtil static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers"; static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers"; + std::string ScriptsContainer::ScriptId::toString() const + { + std::string res = mContainer->mNamePrefix; + res.push_back('['); + res.append(mPath); + res.push_back(']'); + return res; + } + ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua) { registerEngineHandlers({&mUpdateHandlers}); @@ -81,6 +90,7 @@ namespace LuaUtil auto scriptIter = mScripts.find(path); if (scriptIter == mScripts.end()) return false; // no such script + scriptIter->second.mHiddenData[ScriptId::KEY] = sol::nil; sol::object& script = scriptIter->second.mInterface; if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil) { @@ -320,6 +330,8 @@ namespace LuaUtil void ScriptsContainer::removeAllScripts() { + for (auto& [_, script] : mScripts) + script.mHiddenData[ScriptId::KEY] = sol::nil; mScripts.clear(); mScriptOrder.clear(); for (auto& [_, handlers] : mEngineHandlers) diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index d3141a2ca3..0bf50b8793 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -66,6 +66,8 @@ namespace LuaUtil ScriptsContainer* mContainer; std::string mPath; + + std::string toString() const; }; using TimeUnit = ESM::LuaTimer::TimeUnit; @@ -73,7 +75,7 @@ namespace LuaUtil ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix); ScriptsContainer(const ScriptsContainer&) = delete; ScriptsContainer(ScriptsContainer&&) = delete; - virtual ~ScriptsContainer() {} + virtual ~ScriptsContainer() { removeAllScripts(); } // Adds package that will be available (via `require`) for all scripts in the container. // Automatically applies LuaUtil::makeReadOnly to the package. diff --git a/files/lua_api/openmw/async.lua b/files/lua_api/openmw/async.lua index 68f63b5193..cc3a233f8a 100644 --- a/files/lua_api/openmw/async.lua +++ b/files/lua_api/openmw/async.lua @@ -48,5 +48,12 @@ -- @param #number delay -- @param #function func +------------------------------------------------------------------------------- +-- Wraps Lua function with `Callback` object that can be used in async API calls. +-- @function [parent=#async] callback +-- @param self +-- @param #function func +-- @return #Callback + return nil From e41fe7573a90ac649ec6d4c8775c39956103d3da Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 28 Sep 2021 08:17:12 +0000 Subject: [PATCH 079/137] avoids creating empty statesets for collada nodes (#3128) * avoids creating empty statesets for collada nodes With this PR we avoid creating empty statesets for collada nodes which will be detrimental to osg's draw performance. * scenemanager.cpp --- components/resource/scenemanager.cpp | 50 ++++++++++++++++------------ 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 68596985ce..2099d514f7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -243,19 +243,22 @@ namespace Resource void apply(osg::Node& node) override { - if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) + if (osg::StateSet* stateset = node.getStateSet()) { - osg::ref_ptr depth = SceneUtil::createDepth(); - depth->setWriteMask(false); + if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + depth->setWriteMask(false); - node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); - } - else if (node.getOrCreateStateSet()->getRenderingHint() == osg::StateSet::OPAQUE_BIN) - { - osg::ref_ptr depth = SceneUtil::createDepth(); - depth->setWriteMask(true); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + else if (stateset->getRenderingHint() == osg::StateSet::OPAQUE_BIN) + { + osg::ref_ptr depth = SceneUtil::createDepth(); + depth->setWriteMask(true); - node.getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } } /* Check if the has @@ -268,22 +271,25 @@ namespace Resource } // Iterate each description, and see if the current node uses the specified material for alpha testing - for (auto description : mDescriptions) + if (node.getStateSet()) { - std::vector descriptionParts; - std::istringstream descriptionStringStream(description); - for (std::string part; std::getline(descriptionStringStream, part, ' ');) + for (auto description : mDescriptions) { - descriptionParts.emplace_back(part); - } + std::vector descriptionParts; + std::istringstream descriptionStringStream(description); + for (std::string part; std::getline(descriptionStringStream, part, ' ');) + { + descriptionParts.emplace_back(part); + } - if (descriptionParts.size() > (3) && descriptionParts.at(3) == node.getOrCreateStateSet()->getName()) - { - if (descriptionParts.at(0) == "alphatest") + if (descriptionParts.size() > (3) && descriptionParts.at(3) == node.getStateSet()->getName()) { - osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); - osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); - node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + if (descriptionParts.at(0) == "alphatest") + { + osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1)); + osg::ref_ptr alphaFunc (new osg::AlphaFunc(mode, std::stod(descriptionParts.at(2)))); + node.getStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + } } } } From eb2f863b7d6ff5e1340e31ed6430693ce3d939db Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 28 Sep 2021 12:25:37 +0200 Subject: [PATCH 080/137] Resolve `unused-lambda-capture` warnings --- apps/openmw/mwlua/actions.cpp | 4 ++-- apps/openmw/mwlua/nearbybindings.cpp | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp index 1f75760f7c..aad70d1a57 100644 --- a/apps/openmw/mwlua/actions.cpp +++ b/apps/openmw/mwlua/actions.cpp @@ -51,8 +51,8 @@ namespace MWLua std::array usedSlots; std::fill(usedSlots.begin(), usedSlots.end(), false); - constexpr int anySlot = -1; - auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView, anySlot](int slot, const Item& item) -> bool + static constexpr int anySlot = -1; + auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool { auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); MWWorld::Ptr itemPtr; diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index 8eae46a9b7..011d0ae9f3 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -47,8 +47,6 @@ namespace MWLua return LObject(getId(r.mHitObject), worldView->getObjectRegistry()); }); - constexpr int defaultCollisionType = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | - MWPhysics::CollisionType_Actor | MWPhysics::CollisionType_Door; api["COLLISION_TYPE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( "World", MWPhysics::CollisionType_World, "Door", MWPhysics::CollisionType_Door, @@ -56,12 +54,12 @@ namespace MWLua "HeightMap", MWPhysics::CollisionType_HeightMap, "Projectile", MWPhysics::CollisionType_Projectile, "Water", MWPhysics::CollisionType_Water, - "Default", defaultCollisionType)); + "Default", MWPhysics::CollisionType_Default)); - api["castRay"] = [defaultCollisionType](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) + api["castRay"] = [](const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { MWWorld::Ptr ignore; - int collisionType = defaultCollisionType; + int collisionType = MWPhysics::CollisionType_Default; float radius = 0; if (options) { @@ -80,7 +78,7 @@ namespace MWLua } }; // TODO: async raycasting - /*api["asyncCastRay"] = [luaManager = context.mLuaManager, defaultCollisionType]( + /*api["asyncCastRay"] = [luaManager = context.mLuaManager]( const Callback& luaCallback, const osg::Vec3f& from, const osg::Vec3f& to, sol::optional options) { std::function callback = From 48538d5ceff0ca4d809e4606d2cecc94903a1dcf Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 19 Sep 2021 14:43:15 +0200 Subject: [PATCH 081/137] 3D transforms in Lua --- apps/openmw/mwlua/luabindings.cpp | 2 +- .../lua/test_utilpackage.cpp | 121 ++++++++---- components/lua/utilpackage.cpp | 174 +++++++++++++----- components/lua/utilpackage.hpp | 15 +- files/lua_api/openmw/util.lua | 87 ++++++++- 5 files changed, 309 insertions(+), 90 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 6526a18367..aceffc24db 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -25,7 +25,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 6; + api["API_REVISION"] = 7; api["quit"] = [lua]() { std::string traceback = lua->sol()["debug"]["traceback"]().get(); diff --git a/apps/openmw_test_suite/lua/test_utilpackage.cpp b/apps/openmw_test_suite/lua/test_utilpackage.cpp index fb8e48e461..934cbf761a 100644 --- a/apps/openmw_test_suite/lua/test_utilpackage.cpp +++ b/apps/openmw_test_suite/lua/test_utilpackage.cpp @@ -10,30 +10,41 @@ namespace { using namespace testing; + template + T get(sol::state& lua, std::string luaCode) + { + return lua.safe_script("return " + luaCode).get(); + } + + std::string getAsString(sol::state& lua, std::string luaCode) + { + return LuaUtil::toString(lua.safe_script("return " + luaCode)); + } + TEST(LuaUtilPackageTest, Vector2) { sol::state lua; lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector2(3, 4)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), 3); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 4); - EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(3, 4)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v:length()").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return v:length2()").get(), 25); - EXPECT_FALSE(lua.safe_script("return util.vector2(1, 2) == util.vector2(1, 3)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) == util.vector2(2, 4) / 2").get()); - EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) * 2 == util.vector2(2, 4)").get()); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2) * v").get(), 17); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2):dot(v)").get(), 17); + EXPECT_FLOAT_EQ(get(lua, "v.x"), 3); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 4); + EXPECT_EQ(get(lua, "tostring(v)"), "(3, 4)"); + EXPECT_FLOAT_EQ(get(lua, "v:length()"), 5); + EXPECT_FLOAT_EQ(get(lua, "v:length2()"), 25); + EXPECT_FALSE(get(lua, "util.vector2(1, 2) == util.vector2(1, 3)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) == util.vector2(2, 4) / 2")); + EXPECT_TRUE(get(lua, "util.vector2(1, 2) * 2 == util.vector2(2, 4)")); + EXPECT_FLOAT_EQ(get(lua, "util.vector2(3, 2) * v"), 17); + EXPECT_FLOAT_EQ(get(lua, "util.vector2(3, 2):dot(v)"), 17); EXPECT_ERROR(lua.safe_script("v2, len = v.normalize()"), "value is not a valid userdata"); // checks that it doesn't segfault lua.safe_script("v2, len = v:normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 5); - EXPECT_TRUE(lua.safe_script("return v2 == util.vector2(3/5, 4/5)").get()); + EXPECT_FLOAT_EQ(get(lua, "len"), 5); + EXPECT_TRUE(get(lua, "v2 == util.vector2(3/5, 4/5)")); lua.safe_script("_, len = util.vector2(0, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 0); + EXPECT_FLOAT_EQ(get(lua, "len"), 0); } TEST(LuaUtilPackageTest, Vector3) @@ -42,27 +53,59 @@ namespace lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector3(5, 12, 13)"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 12); - EXPECT_FLOAT_EQ(lua.safe_script("return v.z").get(), 13); - EXPECT_EQ(lua.safe_script("return tostring(v)").get(), "(5, 12, 13)"); - EXPECT_EQ(LuaUtil::toString(lua.safe_script("return v")), "(5, 12, 13)"); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length()").get(), 5); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length2()").get(), 25); - EXPECT_FALSE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(1, 3, 2)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2").get()); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)").get()); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1) * v").get(), 5*3 + 12*2 + 13*1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1):dot(v)").get(), 5*3 + 12*2 + 13*1); - EXPECT_TRUE(lua.safe_script("return util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)").get()); + EXPECT_FLOAT_EQ(get(lua, "v.x"), 5); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 12); + EXPECT_FLOAT_EQ(get(lua, "v.z"), 13); + EXPECT_EQ(get(lua, "tostring(v)"), "(5, 12, 13)"); + EXPECT_EQ(getAsString(lua, "v"), "(5, 12, 13)"); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(4, 0, 3):length()"), 5); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(4, 0, 3):length2()"), 25); + EXPECT_FALSE(get(lua, "util.vector3(1, 2, 3) == util.vector3(1, 3, 2)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2")); + EXPECT_TRUE(get(lua, "util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)")); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(3, 2, 1) * v"), 5*3 + 12*2 + 13*1); + EXPECT_FLOAT_EQ(get(lua, "util.vector3(3, 2, 1):dot(v)"), 5*3 + 12*2 + 13*1); + EXPECT_TRUE(get(lua, "util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)")); EXPECT_ERROR(lua.safe_script("v2, len = util.vector3(3, 4, 0).normalize()"), "value is not a valid userdata"); lua.safe_script("v2, len = util.vector3(3, 4, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 5); - EXPECT_TRUE(lua.safe_script("return v2 == util.vector3(3/5, 4/5, 0)").get()); + EXPECT_FLOAT_EQ(get(lua, "len"), 5); + EXPECT_TRUE(get(lua, "v2 == util.vector3(3/5, 4/5, 0)")); lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()"); - EXPECT_FLOAT_EQ(lua.safe_script("return len").get(), 0); + EXPECT_FLOAT_EQ(get(lua, "len"), 0); + } + + TEST(LuaUtilPackageTest, Transform) + { + sol::state lua; + lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); + lua["util"] = LuaUtil::initUtilPackage(lua); + lua["T"] = lua["util"]["transform"]; + lua["v"] = lua["util"]["vector3"]; + EXPECT_ERROR(lua.safe_script("T.identity = nil"), "attempt to index"); + EXPECT_EQ(getAsString(lua, "T.identity * v(3, 4, 5)"), "(3, 4, 5)"); + EXPECT_EQ(getAsString(lua, "T.move(1, 2, 3) * v(3, 4, 5)"), "(4, 6, 8)"); + EXPECT_EQ(getAsString(lua, "T.scale(1, -2, 3) * v(3, 4, 5)"), "(3, -8, 15)"); + EXPECT_EQ(getAsString(lua, "T.scale(v(1, 2, 3)) * v(3, 4, 5)"), "(3, 8, 15)"); + lua.safe_script("moveAndScale = T.move(v(1, 2, 3)) * T.scale(0.5, 1, 0.5) * T.move(10, 20, 30)"); + EXPECT_EQ(getAsString(lua, "moveAndScale * v(0, 0, 0)"), "(6, 22, 18)"); + EXPECT_EQ(getAsString(lua, "moveAndScale * v(300, 200, 100)"), "(156, 222, 68)"); + EXPECT_EQ(getAsString(lua, "moveAndScale"), "TransformM{ move(6, 22, 18) scale(0.5, 1, 0.5) }"); + EXPECT_EQ(getAsString(lua, "T.identity"), "TransformM{ }"); + lua.safe_script("rx = T.rotateX(math.pi / 2)"); + lua.safe_script("ry = T.rotateY(math.pi / 2)"); + lua.safe_script("rz = T.rotateZ(math.pi / 2)"); + EXPECT_LT(get(lua, "(rx * v(1, 2, 3) - v(1, -3, 2)):length()"), 1e-6); + EXPECT_LT(get(lua, "(ry * v(1, 2, 3) - v(3, 2, -1)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rz * v(1, 2, 3) - v(-2, 1, 3)):length()"), 1e-6); + lua.safe_script("rot = T.rotate(math.pi / 2, v(-1, -1, 0)) * T.rotateZ(-math.pi / 4)"); + EXPECT_THAT(getAsString(lua, "rot"), HasSubstr("TransformQ")); + EXPECT_LT(get(lua, "(rot * v(1, 0, 0) - v(0, 0, 1)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rot * rot:inverse() * v(1, 0, 0) - v(1, 0, 0)):length()"), 1e-6); + lua.safe_script("rz_move_rx = rz * T.move(0, 3, 0) * rx"); + EXPECT_LT(get(lua, "(rz_move_rx * v(1, 2, 3) - v(0, 1, 2)):length()"), 1e-6); + EXPECT_LT(get(lua, "(rz_move_rx:inverse() * v(0, 1, 2) - v(1, 2, 3)):length()"), 1e-6); } TEST(LuaUtilPackageTest, UtilityFunctions) @@ -71,12 +114,12 @@ namespace lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); lua["util"] = LuaUtil::initUtilPackage(lua); lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))"); - EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get(), -0.5); - EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get(), 0.86602539); - EXPECT_FLOAT_EQ(lua.safe_script("return util.normalizeAngle(math.pi * 10 + 0.1)").get(), 0.1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(0.1, 0, 1.5)").get(), 0.1); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(-0.1, 0, 1.5)").get(), 0); - EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(2.1, 0, 1.5)").get(), 1.5); + EXPECT_FLOAT_EQ(get(lua, "v.x"), -0.5); + EXPECT_FLOAT_EQ(get(lua, "v.y"), 0.86602539); + EXPECT_FLOAT_EQ(get(lua, "util.normalizeAngle(math.pi * 10 + 0.1)"), 0.1); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(0.1, 0, 1.5)"), 0.1); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(-0.1, 0, 1.5)"), 0); + EXPECT_FLOAT_EQ(get(lua, "util.clamp(2.1, 0, 1.5)"), 1.5); } } diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index abcc6d424e..17cb64461a 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -3,17 +3,23 @@ #include #include -#include - #include +#include "luastate.hpp" + namespace sol { template <> - struct is_automagical : std::false_type {}; + struct is_automagical : std::false_type {}; template <> - struct is_automagical : std::false_type {}; + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; + + template <> + struct is_automagical : std::false_type {}; } namespace LuaUtil @@ -23,70 +29,148 @@ namespace LuaUtil { sol::table util(lua, sol::create); - // TODO: Add bindings for osg::Matrix - - // Lua bindings for osg::Vec2f - util["vector2"] = [](float x, float y) { return osg::Vec2f(x, y); }; - sol::usertype vec2Type = lua.new_usertype("Vec2"); - vec2Type["x"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.x(); } ); - vec2Type["y"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.y(); } ); - vec2Type[sol::meta_function::to_string] = [](const osg::Vec2f& v) { + // Lua bindings for Vec2 + util["vector2"] = [](float x, float y) { return Vec2(x, y); }; + sol::usertype vec2Type = lua.new_usertype("Vec2"); + vec2Type["x"] = sol::readonly_property([](const Vec2& v) -> float { return v.x(); } ); + vec2Type["y"] = sol::readonly_property([](const Vec2& v) -> float { return v.y(); } ); + vec2Type[sol::meta_function::to_string] = [](const Vec2& v) { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ")"; return ss.str(); }; - vec2Type[sol::meta_function::unary_minus] = [](const osg::Vec2f& a) { return -a; }; - vec2Type[sol::meta_function::addition] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a + b; }; - vec2Type[sol::meta_function::subtraction] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a - b; }; - vec2Type[sol::meta_function::equal_to] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a == b; }; + vec2Type[sol::meta_function::unary_minus] = [](const Vec2& a) { return -a; }; + vec2Type[sol::meta_function::addition] = [](const Vec2& a, const Vec2& b) { return a + b; }; + vec2Type[sol::meta_function::subtraction] = [](const Vec2& a, const Vec2& b) { return a - b; }; + vec2Type[sol::meta_function::equal_to] = [](const Vec2& a, const Vec2& b) { return a == b; }; vec2Type[sol::meta_function::multiplication] = sol::overload( - [](const osg::Vec2f& a, float c) { return a * c; }, - [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; }); - vec2Type[sol::meta_function::division] = [](const osg::Vec2f& a, float c) { return a / c; }; - vec2Type["dot"] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; }; - vec2Type["length"] = &osg::Vec2f::length; - vec2Type["length2"] = &osg::Vec2f::length2; - vec2Type["normalize"] = [](const osg::Vec2f& v) { + [](const Vec2& a, float c) { return a * c; }, + [](const Vec2& a, const Vec2& b) { return a * b; }); + vec2Type[sol::meta_function::division] = [](const Vec2& a, float c) { return a / c; }; + vec2Type["dot"] = [](const Vec2& a, const Vec2& b) { return a * b; }; + vec2Type["length"] = &Vec2::length; + vec2Type["length2"] = &Vec2::length2; + vec2Type["normalize"] = [](const Vec2& v) { float len = v.length(); if (len == 0) - return std::make_tuple(osg::Vec2f(), 0.f); + return std::make_tuple(Vec2(), 0.f); else return std::make_tuple(v * (1.f / len), len); }; vec2Type["rotate"] = &Misc::rotateVec2f; - // Lua bindings for osg::Vec3f - util["vector3"] = [](float x, float y, float z) { return osg::Vec3f(x, y, z); }; - sol::usertype vec3Type = lua.new_usertype("Vec3"); - vec3Type["x"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.x(); } ); - vec3Type["y"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.y(); } ); - vec3Type["z"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.z(); } ); - vec3Type[sol::meta_function::to_string] = [](const osg::Vec3f& v) { + // Lua bindings for Vec3 + util["vector3"] = [](float x, float y, float z) { return Vec3(x, y, z); }; + sol::usertype vec3Type = lua.new_usertype("Vec3"); + vec3Type["x"] = sol::readonly_property([](const Vec3& v) -> float { return v.x(); } ); + vec3Type["y"] = sol::readonly_property([](const Vec3& v) -> float { return v.y(); } ); + vec3Type["z"] = sol::readonly_property([](const Vec3& v) -> float { return v.z(); } ); + vec3Type[sol::meta_function::to_string] = [](const Vec3& v) { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; return ss.str(); }; - vec3Type[sol::meta_function::unary_minus] = [](const osg::Vec3f& a) { return -a; }; - vec3Type[sol::meta_function::addition] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a + b; }; - vec3Type[sol::meta_function::subtraction] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a - b; }; - vec3Type[sol::meta_function::equal_to] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a == b; }; + vec3Type[sol::meta_function::unary_minus] = [](const Vec3& a) { return -a; }; + vec3Type[sol::meta_function::addition] = [](const Vec3& a, const Vec3& b) { return a + b; }; + vec3Type[sol::meta_function::subtraction] = [](const Vec3& a, const Vec3& b) { return a - b; }; + vec3Type[sol::meta_function::equal_to] = [](const Vec3& a, const Vec3& b) { return a == b; }; vec3Type[sol::meta_function::multiplication] = sol::overload( - [](const osg::Vec3f& a, float c) { return a * c; }, - [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; }); - vec3Type[sol::meta_function::division] = [](const osg::Vec3f& a, float c) { return a / c; }; - vec3Type[sol::meta_function::involution] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; }; - vec3Type["dot"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; }; - vec3Type["cross"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; }; - vec3Type["length"] = &osg::Vec3f::length; - vec3Type["length2"] = &osg::Vec3f::length2; - vec3Type["normalize"] = [](const osg::Vec3f& v) { + [](const Vec3& a, float c) { return a * c; }, + [](const Vec3& a, const Vec3& b) { return a * b; }); + vec3Type[sol::meta_function::division] = [](const Vec3& a, float c) { return a / c; }; + vec3Type[sol::meta_function::involution] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; + vec3Type["dot"] = [](const Vec3& a, const Vec3& b) { return a * b; }; + vec3Type["cross"] = [](const Vec3& a, const Vec3& b) { return a ^ b; }; + vec3Type["length"] = &Vec3::length; + vec3Type["length2"] = &Vec3::length2; + vec3Type["normalize"] = [](const Vec3& v) { float len = v.length(); if (len == 0) - return std::make_tuple(osg::Vec3f(), 0.f); + return std::make_tuple(Vec3(), 0.f); else return std::make_tuple(v * (1.f / len), len); }; + // Lua bindings for Transform + sol::usertype transMType = lua.new_usertype("TransformM"); + sol::usertype transQType = lua.new_usertype("TransformQ"); + sol::table transforms(lua, sol::create); + util["transform"] = LuaUtil::makeReadOnly(transforms); + + transforms["identity"] = sol::make_object(lua, TransformM{osg::Matrixf::identity()}); + transforms["move"] = sol::overload( + [](const Vec3& v) { return TransformM{osg::Matrixf::translate(v)}; }, + [](float x, float y, float z) { return TransformM{osg::Matrixf::translate(x, y, z)}; }); + transforms["scale"] = sol::overload( + [](const Vec3& v) { return TransformM{osg::Matrixf::scale(v)}; }, + [](float x, float y, float z) { return TransformM{osg::Matrixf::scale(x, y, z)}; }); + transforms["rotate"] = [](float angle, const Vec3& axis) { return TransformQ{osg::Quat(angle, axis)}; }; + transforms["rotateX"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(1, 0, 0))}; }; + transforms["rotateY"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 1, 0))}; }; + transforms["rotateZ"] = [](float angle) { return TransformQ{osg::Quat(angle, Vec3(0, 0, 1))}; }; + + transMType[sol::meta_function::multiplication] = sol::overload( + [](const TransformM& a, const Vec3& b) { return a.mM.preMult(b); }, + [](const TransformM& a, const TransformM& b) { return TransformM{b.mM * a.mM}; }, + [](const TransformM& a, const TransformQ& b) + { + TransformM res{a.mM}; + res.mM.preMultRotate(b.mQ); + return res; + }); + transMType[sol::meta_function::to_string] = [](const TransformM& m) + { + osg::Vec3f trans, scale; + osg::Quat rotation, so; + m.mM.decompose(trans, rotation, scale, so); + osg::Quat::value_type rot_angle, so_angle; + osg::Vec3f rot_axis, so_axis; + rotation.getRotate(rot_angle, rot_axis); + so.getRotate(so_angle, so_axis); + std::stringstream ss; + ss << "TransformM{ "; + if (trans.length2() > 0) + ss << "move(" << trans.x() << ", " << trans.y() << ", " << trans.z() << ") "; + if (rot_angle != 0) + ss << "rotation(angle=" << rot_angle << ", axis=(" + << rot_axis.x() << ", " << rot_axis.y() << ", " << rot_axis.z() << ")) "; + if (scale.x() != 1 || scale.y() != 1 || scale.z() != 1) + ss << "scale(" << scale.x() << ", " << scale.y() << ", " << scale.z() << ") "; + if (so_angle != 0) + ss << "rotation(angle=" << so_angle << ", axis=(" + << so_axis.x() << ", " << so_axis.y() << ", " << so_axis.z() << ")) "; + ss << "}"; + return ss.str(); + }; + transMType["inverse"] = [](const TransformM& m) + { + TransformM res; + if (!res.mM.invert_4x3(m.mM)) + throw std::runtime_error("This Transform is not invertible"); + return res; + }; + + transQType[sol::meta_function::multiplication] = sol::overload( + [](const TransformQ& a, const Vec3& b) { return a.mQ * b; }, + [](const TransformQ& a, const TransformQ& b) { return TransformQ{b.mQ * a.mQ}; }, + [](const TransformQ& a, const TransformM& b) + { + TransformM res{b}; + res.mM.postMultRotate(a.mQ); + return res; + }); + transQType[sol::meta_function::to_string] = [](const TransformQ& q) + { + osg::Quat::value_type angle; + osg::Vec3f axis; + q.mQ.getRotate(angle, axis); + std::stringstream ss; + ss << "TransformQ{ rotation(angle=" << angle << ", axis=(" + << axis.x() << ", " << axis.y() << ", " << axis.z() << ")) }"; + return ss.str(); + }; + transQType["inverse"] = [](const TransformQ& q) { return TransformQ{q.mQ.inverse()}; }; + // Utility functions util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); }; // NOTE: `util["clamp"] = std::clamp` causes error 'AddressSanitizer: stack-use-after-scope' diff --git a/components/lua/utilpackage.hpp b/components/lua/utilpackage.hpp index 9e73723561..d26bfdb027 100644 --- a/components/lua/utilpackage.hpp +++ b/components/lua/utilpackage.hpp @@ -1,11 +1,24 @@ #ifndef COMPONENTS_LUA_UTILPACKAGE_H #define COMPONENTS_LUA_UTILPACKAGE_H -#include // missing from sol/sol.hpp +#include +#include +#include + #include namespace LuaUtil { + using Vec2 = osg::Vec2f; + using Vec3 = osg::Vec3f; + + // For performance reasons "Transform" is implemented as 2 types with the same interface. + // Transform supports only composition, inversion, and applying to a 3d vector. + struct TransformM { osg::Matrixf mM; }; + struct TransformQ { osg::Quat mQ; }; + + inline TransformM asTransform(const osg::Matrixf& m) { return {m}; } + inline TransformQ asTransform(const osg::Quat& q) { return {q}; } sol::table initUtilPackage(sol::state&); diff --git a/files/lua_api/openmw/util.lua b/files/lua_api/openmw/util.lua index 7d31ced408..84e640ff40 100644 --- a/files/lua_api/openmw/util.lua +++ b/files/lua_api/openmw/util.lua @@ -6,7 +6,7 @@ ------------------------------------------------------------------------------- --- Limits given value to the interval [`from`, `to`] +-- Limits given value to the interval [`from`, `to`]. -- @function [parent=#util] clamp -- @param #number value -- @param #number from @@ -14,7 +14,7 @@ -- @return #number min(max(value, from), to) ------------------------------------------------------------------------------- --- Adds `2pi*k` and puts the angle in range `[-pi, pi]` +-- Adds `2pi*k` and puts the angle in range `[-pi, pi]`. -- @function [parent=#util] normalizeAngle -- @param #number angle Angle in radians -- @return #number Angle in range `[-pi, pi]` @@ -48,13 +48,13 @@ -- @return #Vector2. ------------------------------------------------------------------------------- --- Length of the vector +-- Length of the vector. -- @function [parent=#Vector2] length -- @param self -- @return #number ------------------------------------------------------------------------------- --- Square of the length of the vector +-- Square of the length of the vector. -- @function [parent=#Vector2] length2 -- @param self -- @return #number @@ -146,5 +146,84 @@ -- @param #Vector3 v -- @return #Vector3 +------------------------------------------------------------------------------- +-- @type Transform + +------------------------------------------------------------------------------- +-- Returns the inverse transform. +-- @function [parent=#Transform] inverse +-- @param self +-- @return #Transform. + +------------------------------------------------------------------------------- +-- @type TRANSFORM +-- @field [parent=#TRANSFORM] #Transform identity Empty transform. + +------------------------------------------------------------------------------- +-- Movement by given vector. +-- @function [parent=#TRANSFORM] move +-- @param #Vector3 offset. +-- @return #Transform. +-- @usage +-- -- Accepts either 3 numbers or a 3D vector +-- util.transform.move(x, y, z) +-- util.transform.move(util.vector3(x, y, z)) + +------------------------------------------------------------------------------- +-- Scale transform. +-- @function [parent=#TRANSFORM] scale +-- @param #number scaleX. +-- @param #number scaleY. +-- @param #number scaleZ. +-- @return #Transform. +-- @usage +-- -- Accepts either 3 numbers or a 3D vector +-- util.transform.scale(x, y, z) +-- util.transform.scale(util.vector3(x, y, z)) + + +------------------------------------------------------------------------------- +-- Rotation (any axis). +-- @function [parent=#TRANSFORM] rotate +-- @param #number angle +-- @param #Vector3 axis. +-- @return #Transform. + +------------------------------------------------------------------------------- +-- X-axis rotation. +-- @function [parent=#TRANSFORM] rotateX +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- Y-axis rotation. +-- @function [parent=#TRANSFORM] rotateY +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- Z-axis rotation. +-- @function [parent=#TRANSFORM] rotateZ +-- @param #number angle +-- @return #Transform. + +------------------------------------------------------------------------------- +-- 3D transforms (scale/move/rotate) that can be applied to 3D vectors. +-- Several transforms can be combined and applied to a vector using multiplication. +-- Combined transforms apply in reverse order (from right to left). +-- @field [parent=#util] #TRANSFORM transform +-- @usage +-- local util = require('openmw.util') +-- local trans = util.transform +-- local fromActorSpace = trans.move(actor.position) * trans.rotateZ(actor.rotation.z) +-- +-- -- rotation is applied first, movement is second +-- local posBehindActor = fromActorSpace * util.vector3(0, -100, 0) +-- +-- -- equivalent to trans.rotateZ(-actor.rotation.z) * trans.move(-actor.position) +-- local toActorSpace = fromActorSpace:inverse() +-- local relativeTargetPos = toActorSpace * target.position +-- local deltaAngle = math.atan2(relativeTargetPos.y, relativeTargetPos.x) + return nil From 163968f578e3a764596cc90b8ad99f18f9bcdd3e Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 28 Sep 2021 22:42:44 +0200 Subject: [PATCH 082/137] Normalize area cost factor Recastnavigation uses path cost and heuristic based on distance to find shortest path by A* algorithm. Using raw speed values makes cost much lower value than heuristic (1000 times less at maximum). Heuristic is defined as distance from node to target * 0.999 that means area cost should never be less than 0.999 or 1 for simplicity. Use max speed to make lowest possible cost factor equal to 1. --- apps/openmw/mwmechanics/aipackage.cpp | 33 ++++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 46e4729a8a..c358a75b55 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -457,20 +457,31 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); - if (flags & DetourNavigator::Flag_swim) - costs.mWater = divOrMax(costs.mWater, actorClass.getSwimSpeed(actor)); + const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0 + ? 0.0f + : actorClass.getSwimSpeed(actor); - if (flags & DetourNavigator::Flag_walk) + const float walkSpeed = [&] { - float walkCost; + if ((flags & DetourNavigator::Flag_walk) == 0) + return 0.0f; if (getTypeId() == AiPackageTypeId::Wander) - walkCost = divOrMax(1.0, actorClass.getWalkSpeed(actor)); - else - walkCost = divOrMax(1.0, actorClass.getRunSpeed(actor)); - costs.mDoor = costs.mDoor * walkCost; - costs.mPathgrid = costs.mPathgrid * walkCost; - costs.mGround = costs.mGround * walkCost; - } + return actorClass.getWalkSpeed(actor); + return actorClass.getRunSpeed(actor); + } (); + + const float maxSpeed = std::max(swimSpeed, walkSpeed); + + if (maxSpeed == 0) + return costs; + + const float swimFactor = swimSpeed / maxSpeed; + const float walkFactor = walkSpeed / maxSpeed; + + costs.mWater = divOrMax(costs.mWater, swimFactor); + costs.mDoor = divOrMax(costs.mDoor, walkFactor); + costs.mPathgrid = divOrMax(costs.mPathgrid, walkFactor); + costs.mGround = divOrMax(costs.mGround, walkFactor); return costs; } From 9950132a5f3dd750fb357665834820212fae91a1 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 28 Sep 2021 23:16:56 +0200 Subject: [PATCH 083/137] Allow travelling actors find path over pathgrid navmesh area type In addition to other navmesh areas. This makes actors behaviour closer to vanilla when pathgrid is correct. --- apps/openmw/mwmechanics/aipackage.cpp | 4 ++++ apps/openmw/mwmechanics/pathfinding.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index c358a75b55..3e37a4612d 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -443,7 +443,11 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0) + { result |= DetourNavigator::Flag_walk; + if (getTypeId() == AiPackageTypeId::Travel) + result |= DetourNavigator::Flag_usePathgrid; + } if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander) result |= DetourNavigator::Flag_openDoor; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 7e63190edb..4b06993a49 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -397,7 +397,7 @@ namespace MWMechanics mPath.clear(); } - if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty()) + if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty() && (flags & DetourNavigator::Flag_usePathgrid) == 0) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, std::back_inserter(mPath)); From c0ef4417c3bf42fea006efbb4655015953bc4319 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 29 Sep 2021 00:42:29 +0200 Subject: [PATCH 084/137] Check AiTravel destination for other actors presence Use faster aabbTest but without destance filter. To avoid dependency on a specific constant and correctly handle situations when there is a big difference between actors sizes. --- CHANGELOG.md | 1 + apps/openmw/mwbase/world.hpp | 3 +- apps/openmw/mwmechanics/aitravel.cpp | 31 ++++++++++++++----- apps/openmw/mwmechanics/aitravel.hpp | 2 ++ apps/openmw/mwmechanics/aiwander.cpp | 8 ----- apps/openmw/mwmechanics/obstacle.cpp | 11 +++++++ apps/openmw/mwmechanics/obstacle.hpp | 6 ++++ .../mwphysics/hasspherecollisioncallback.hpp | 21 +++++++++---- apps/openmw/mwphysics/physicssystem.cpp | 17 ++++++++-- apps/openmw/mwphysics/physicssystem.hpp | 3 +- apps/openmw/mwworld/worldimp.cpp | 5 +-- apps/openmw/mwworld/worldimp.hpp | 3 +- 12 files changed, 82 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc9510f6e..5bd70b9407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6302: Teleporting disabled actor breaks its disabled state + Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1dbb2b96f6..c8036385db 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -653,7 +653,8 @@ namespace MWBase virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; - virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0; + virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors = nullptr) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 91fe96a2aa..2594adcb34 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,5 +1,7 @@ #include "aitravel.hpp" +#include + #include #include "../mwbase/environment.hpp" @@ -23,6 +25,11 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) return (pos1 - pos2).length2() <= 7168*7168; } + float getActorRadius(const MWWorld::ConstPtr& actor) + { + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + return std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + } } namespace MWMechanics @@ -70,16 +77,24 @@ namespace MWMechanics // Unfortunately, with vanilla assets destination is sometimes blocked by other actor. // If we got close to target, check for actors nearby. If they are, finish AI package. - int destinationTolerance = 64; - if (distance(actorPos, targetPos) <= destinationTolerance) + if (mDestinationCheck.update(duration) == Misc::TimerStatus::Elapsed) { - std::vector targetActors; - std::pair result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors); - - if (!result.first.isEmpty()) + std::vector occupyingActors; + if (isAreaOccupiedByOtherActor(actor, targetPos, &occupyingActors)) { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - return true; + const float actorRadius = getActorRadius(actor); + const float distanceToTarget = distance(actorPos, targetPos); + for (const MWWorld::Ptr& other : occupyingActors) + { + const float otherRadius = getActorRadius(other); + const auto [minRadius, maxRadius] = std::minmax(actorRadius, otherRadius); + constexpr float toleranceFactor = 1.25; + if (minRadius * toleranceFactor + maxRadius > distanceToTarget) + { + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + return true; + } + } } } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 2ea2a8f717..ee4ff9ad4d 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -52,6 +52,8 @@ namespace MWMechanics const float mZ; const bool mHidden; + + AiReactionTimer mDestinationCheck; }; struct AiInternalTravel final : public AiTravel diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 624abe10f9..9c84555404 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -85,14 +85,6 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor); } - bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) - { - const auto world = MWBase::Environment::get().getWorld(); - const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); - const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); - return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor); - } - void stopMovement(const MWWorld::Ptr& actor) { actor.getClass().getMovementSettings(actor).mPosition[0] = 0; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 88325ee7c7..344cc82058 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -4,6 +4,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "movement.hpp" @@ -72,6 +74,15 @@ namespace MWMechanics return MWWorld::Ptr(); // none found } + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + std::vector* occupyingActors) + { + const auto world = MWBase::Environment::get().getWorld(); + const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); + const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor, occupyingActors); + } + ObstacleCheck::ObstacleCheck() : mWalkState(WalkState::Initial) , mStateDuration(0) diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index b574bab67f..24bd5ed1c1 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -3,9 +3,12 @@ #include +#include + namespace MWWorld { class Ptr; + class ConstPtr; } namespace MWMechanics @@ -21,6 +24,9 @@ namespace MWMechanics /** \return Pointer to the door, or empty pointer if none exists **/ const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + std::vector* occupyingActors = nullptr); + class ObstacleCheck { public: diff --git a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp index 275325cf67..fc8725f5f4 100644 --- a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp +++ b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp @@ -22,28 +22,36 @@ namespace MWPhysics return nearest.distance(position) < radius; } + template class HasSphereCollisionCallback final : public btBroadphaseAabbCallback { public: HasSphereCollisionCallback(const btVector3& position, const btScalar radius, btCollisionObject* object, - const int mask, const int group) + const int mask, const int group, OnCollision* onCollision) : mPosition(position), mRadius(radius), mCollisionObject(object), mCollisionFilterMask(mask), - mCollisionFilterGroup(group) + mCollisionFilterGroup(group), + mOnCollision(onCollision) { } bool process(const btBroadphaseProxy* proxy) override { - if (mResult) + if (mResult && mOnCollision == nullptr) return false; const auto collisionObject = static_cast(proxy->m_clientObject); - if (collisionObject == mCollisionObject) + if (collisionObject == mCollisionObject + || !needsCollision(*proxy) + || !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius)) return true; - if (needsCollision(*proxy)) - mResult = testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius); + mResult = true; + if (mOnCollision != nullptr) + { + (*mOnCollision)(collisionObject); + return true; + } return !mResult; } @@ -58,6 +66,7 @@ namespace MWPhysics btCollisionObject* mCollisionObject; int mCollisionFilterMask; int mCollisionFilterGroup; + OnCollision* mOnCollision; bool mResult = false; bool needsCollision(const btBroadphaseProxy& proxy) const diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8499dc74c1..fb363bdac0 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -923,7 +923,8 @@ namespace MWPhysics CollisionType_Actor|CollisionType_Projectile); } - bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const { btCollisionObject* object = nullptr; const auto it = mActors.find(ignore.mRef); @@ -934,7 +935,19 @@ namespace MWPhysics const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); const int mask = MWPhysics::CollisionType_Actor; const int group = 0xff; - HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group); + if (occupyingActors == nullptr) + { + HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group, + static_cast(nullptr)); + mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); + return callback.getResult(); + } + const auto onCollision = [&] (const btCollisionObject* object) + { + if (PtrHolder* holder = static_cast(object->getUserPointer())) + occupyingActors->push_back(holder->getPtr()); + }; + HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group, &onCollision); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 52515b563f..06cfee8e38 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -252,7 +252,8 @@ namespace MWPhysics std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); } - bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const; + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const; void reportStats(unsigned int frameNumber, osg::Stats& stats) const; void reportCollision(const btVector3& position, const btVector3& normal); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index da5daed295..df7357da27 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3935,9 +3935,10 @@ namespace MWWorld return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal); } - bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const { - return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore); + return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore, occupyingActors); } void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 32fc9b101a..4d9841a79a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -735,7 +735,8 @@ namespace MWWorld bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override; - bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const override; + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override; From fc2076db1a3bc57f031f6680a0aad31295530669 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Sep 2021 11:36:05 +0400 Subject: [PATCH 085/137] Fix MSVC warnings about local variables redeclaration (#3130) --- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwphysics/physicssystem.cpp | 50 +++++++++---------- apps/openmw/mwrender/renderingmanager.cpp | 1 - apps/openmw/mwworld/scene.cpp | 6 +-- components/detournavigator/navigatorimpl.cpp | 4 +- .../detournavigator/recastmeshbuilder.cpp | 24 ++++----- components/sceneutil/recastmesh.cpp | 6 +-- 7 files changed, 46 insertions(+), 47 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index f4cb40c2f5..542f185854 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1171,7 +1171,7 @@ namespace MWMechanics creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Undead) { - Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); stat.setModifier(static_cast(effects.get(ESM::MagicEffect::TurnUndead).getMagnitude())); creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8499dc74c1..903e383370 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -514,18 +514,18 @@ namespace MWPhysics void PhysicsSystem::remove(const MWWorld::Ptr &ptr) { - if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { if (mUnrefQueue.get()) - mUnrefQueue->push(found->second->getShapeInstance()); + mUnrefQueue->push(foundObject->second->getShapeInstance()); - mAnimatedObjects.erase(found->second.get()); + mAnimatedObjects.erase(foundObject->second.get()); - mObjects.erase(found); + mObjects.erase(foundObject); } - else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { - mActors.erase(found); + mActors.erase(foundActor); } } @@ -591,16 +591,16 @@ namespace MWPhysics void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr) { - if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { float scale = ptr.getCellRef().getScale(); - found->second->setScale(scale); - mTaskScheduler->updateSingleAabb(found->second); + foundObject->second->setScale(scale); + mTaskScheduler->updateSingleAabb(foundObject->second); } - else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { - found->second->updateScale(); - mTaskScheduler->updateSingleAabb(found->second); + foundActor->second->updateScale(); + mTaskScheduler->updateSingleAabb(foundActor->second); } } @@ -633,32 +633,32 @@ namespace MWPhysics void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr, osg::Quat rotate) { - if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { - found->second->setRotation(rotate); - mTaskScheduler->updateSingleAabb(found->second); + foundObject->second->setRotation(rotate); + mTaskScheduler->updateSingleAabb(foundObject->second); } - else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { - if (!found->second->isRotationallyInvariant()) + if (!foundActor->second->isRotationallyInvariant()) { - found->second->setRotation(rotate); - mTaskScheduler->updateSingleAabb(found->second); + foundActor->second->setRotation(rotate); + mTaskScheduler->updateSingleAabb(foundActor->second); } } } void PhysicsSystem::updatePosition(const MWWorld::Ptr &ptr) { - if (auto found = mObjects.find(ptr.mRef); found != mObjects.end()) + if (auto foundObject = mObjects.find(ptr.mRef); foundObject != mObjects.end()) { - found->second->updatePosition(); - mTaskScheduler->updateSingleAabb(found->second); + foundObject->second->updatePosition(); + mTaskScheduler->updateSingleAabb(foundObject->second); } - else if (auto found = mActors.find(ptr.mRef); found != mActors.end()) + else if (auto foundActor = mActors.find(ptr.mRef); foundActor != mActors.end()) { - found->second->updatePosition(); - mTaskScheduler->updateSingleAabb(found->second, true); + foundActor->second->updatePosition(); + mTaskScheduler->updateSingleAabb(foundActor->second, true); } } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 74e183693c..5661ecfbe7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -440,7 +440,6 @@ namespace MWRender static_cast(mTerrain.get())->addChunkManager(mGroundcover.get()); mResourceSystem->addResourceManager(mGroundcover.get()); - float groundcoverDistance = std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover")); mGroundcover->setViewDistance(groundcoverDistance); } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index e52f5532b4..559ab4aef4 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -917,9 +917,9 @@ namespace MWWorld // unload for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) { - auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell(cell); + auto* cellToUnload = *iter++; + deactivateCell(cellToUnload); + unloadInactiveCell(cellToUnload); } assert(mActiveCells.empty()); assert(mInactiveCells.empty()); diff --git a/components/detournavigator/navigatorimpl.cpp b/components/detournavigator/navigatorimpl.cpp index 5a8488c8d0..750901c73e 100644 --- a/components/detournavigator/navigatorimpl.cpp +++ b/components/detournavigator/navigatorimpl.cpp @@ -39,8 +39,8 @@ namespace DetourNavigator if (const btCollisionShape* const avoidShape = shapes.mShapeInstance->getAvoidCollisionShape()) { const ObjectId avoidId(avoidShape); - CollisionShape collisionShape {shapes.mShapeInstance, *avoidShape}; - if (mNavMeshManager.addObject(avoidId, collisionShape, transform, AreaType_null)) + CollisionShape avoidCollisionShape {shapes.mShapeInstance, *avoidShape}; + if (mNavMeshManager.addObject(avoidId, avoidCollisionShape, transform, AreaType_null)) { updateAvoidShapeId(id, avoidId); result = true; diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 19125e0a9b..73b731c247 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -49,9 +49,9 @@ namespace DetourNavigator std::vector uniqueVertices; uniqueVertices.reserve(3 * triangles.size()); - for (const RecastMeshTriangle& v : triangles) - for (const osg::Vec3f& v : v.mVertices) - uniqueVertices.push_back(v); + for (const RecastMeshTriangle& triangle : triangles) + for (const osg::Vec3f& vertex : triangle.mVertices) + uniqueVertices.push_back(vertex); std::sort(uniqueVertices.begin(), uniqueVertices.end()); uniqueVertices.erase(std::unique(uniqueVertices.begin(), uniqueVertices.end()), uniqueVertices.end()); @@ -61,15 +61,15 @@ namespace DetourNavigator std::vector areaTypes; areaTypes.reserve(triangles.size()); - for (const RecastMeshTriangle& v : triangles) + for (const RecastMeshTriangle& triangle : triangles) { - areaTypes.push_back(v.mAreaType); + areaTypes.push_back(triangle.mAreaType); - for (const osg::Vec3f& v : v.mVertices) + for (const osg::Vec3f& vertex : triangle.mVertices) { - const auto it = std::lower_bound(uniqueVertices.begin(), uniqueVertices.end(), v); + const auto it = std::lower_bound(uniqueVertices.begin(), uniqueVertices.end(), vertex); assert(it != uniqueVertices.end()); - assert(*it == v); + assert(*it == vertex); indices.push_back(static_cast(it - uniqueVertices.begin())); } } @@ -79,11 +79,11 @@ namespace DetourNavigator std::vector vertices; vertices.reserve(3 * uniqueVertices.size()); - for (const osg::Vec3f& v : uniqueVertices) + for (const osg::Vec3f& vertex : uniqueVertices) { - vertices.push_back(v.x() + shift.x()); - vertices.push_back(v.y() + shift.y()); - vertices.push_back(v.z() + shift.z()); + vertices.push_back(vertex.x() + shift.x()); + vertices.push_back(vertex.y() + shift.y()); + vertices.push_back(vertex.z() + shift.z()); } return Mesh(std::move(indices), std::move(vertices), std::move(areaTypes)); diff --git a/components/sceneutil/recastmesh.cpp b/components/sceneutil/recastmesh.cpp index 8d6d47c2b6..97efe010df 100644 --- a/components/sceneutil/recastmesh.cpp +++ b/components/sceneutil/recastmesh.cpp @@ -52,10 +52,10 @@ namespace SceneUtil for (const Heightfield& heightfield : recastMesh.getHeightfields()) { - const Mesh mesh = makeMesh(heightfield); + const Mesh heightfieldMesh = makeMesh(heightfield); const int indexShift = static_cast(vertices.size() / 3); - std::copy(mesh.getVertices().begin(), mesh.getVertices().end(), std::back_inserter(vertices)); - std::transform(mesh.getIndices().begin(), mesh.getIndices().end(), std::back_inserter(indices), + std::copy(heightfieldMesh.getVertices().begin(), heightfieldMesh.getVertices().end(), std::back_inserter(vertices)); + std::transform(heightfieldMesh.getIndices().begin(), heightfieldMesh.getIndices().end(), std::back_inserter(indices), [&] (int index) { return index + indexShift; }); } From 957c25a491b86772b02ab9057679ad1fc970f353 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 29 Sep 2021 07:58:19 +0000 Subject: [PATCH 086/137] avoids creating empty statesets on drawables (#3132) * avoids creating empty statesets on drawables Currently, we attempt to skip creating state on drawable nodes when this state matches the default state. This attempt is incomplete because we still create an avoidable empty stateset in the default case. * renderingmanager.cpp * nifloader.cpp --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/nifosg/nifloader.cpp | 20 +++++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5661ecfbe7..c749d6aa78 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -486,6 +486,7 @@ namespace MWRender defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); mFog.reset(new FogManager()); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0432aef5b4..f05f906c8f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1134,8 +1134,6 @@ namespace NifOsg trans->addChild(toAttach); parentNode->addChild(trans); } - // create partsys stateset in order to pass in ShaderVisitor like all other Drawables - partsys->getOrCreateStateSet(); } void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) @@ -1923,8 +1921,6 @@ namespace NifOsg void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { - osg::StateSet* stateset = node->getOrCreateStateSet(); - // Specular lighting is enabled by default, but there's a quirk... bool specEnabled = true; osg::ref_ptr mat (new osg::Material); @@ -2006,15 +2002,15 @@ namespace NifOsg if (blendFunc->getDestination() == GL_DST_ALPHA) blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); bool noSort = (alphaprop->flags>>13)&1; if (!noSort) - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); else - stateset->setRenderBinToInherit(); + node->getOrCreateStateSet()->setRenderBinToInherit(); } - else + else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); @@ -2025,9 +2021,9 @@ namespace NifOsg { osg::ref_ptr alphaFunc (new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f)); alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } - else + else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); @@ -2083,8 +2079,10 @@ namespace NifOsg mat = shareAttribute(mat); + osg::StateSet* stateset = node->getStateSet(); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); + if (emissiveMult != 1.f) + stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; From 1109cc3ac7fa77ed1e057d893f9b5e5c4f437e8b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Sep 2021 14:08:51 +0400 Subject: [PATCH 087/137] Remove unused data field (#3133) --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/groundcover.hpp | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 94bd900aea..c7113f5193 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -191,7 +191,7 @@ namespace MWRender if (model.empty()) continue; model = "meshes/" + model; - instances[model].emplace_back(std::move(ref), std::move(model)); + instances[model].emplace_back(std::move(ref)); } } } diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index 0a83976441..f2693d6c24 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -24,10 +24,8 @@ namespace MWRender { ESM::Position mPos; float mScale; - std::string mModel; - GroundcoverEntry(const ESM::CellRef& ref, const std::string& model): - mPos(ref.mPos), mScale(ref.mScale), mModel(model) + GroundcoverEntry(const ESM::CellRef& ref) : mPos(ref.mPos), mScale(ref.mScale) {} }; From 83584185556c7cef7071a65dd9c2dcffda781243 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 29 Sep 2021 13:40:37 +0000 Subject: [PATCH 088/137] set the correct program link parameters (#3110) * shadermanager.hpp setProgramTemplate * shadermanager.hpp * shadermanager.cpp setProgramTemplate * shadervisitor.hpp setProgramTemplate * shadervisitor.cpp setProgramTemplate * scenemanager.cpp setProgramTemplate * scenemanager.hpp setProgramTemplate * renderingmanager.cpp * groundcover.cpp setProgramTemplate * groundcover.hpp * groundcover.cpp * shadervisitor.cpp * util.cpp * lightmanager.cpp * scenemanager.cpp * scenemanager.hpp * lightmanager.cpp * lightmanager.cpp * lightmanager.cpp * scenemanager.hpp [ci skip] * water.cpp * groundcover.cpp * shadermanager.hpp --- apps/openmw/mwrender/groundcover.cpp | 7 ++++- apps/openmw/mwrender/groundcover.hpp | 2 ++ apps/openmw/mwrender/renderingmanager.cpp | 1 - apps/openmw/mwrender/water.cpp | 3 --- components/resource/scenemanager.cpp | 11 +++++++- components/resource/scenemanager.hpp | 7 ++++- components/sceneutil/lightmanager.cpp | 13 +++++----- components/sceneutil/util.cpp | 3 +++ components/shader/shadermanager.cpp | 16 +++--------- components/shader/shadermanager.hpp | 31 +++++------------------ components/shader/shadervisitor.cpp | 5 +++- components/shader/shadervisitor.hpp | 5 ++++ 12 files changed, 53 insertions(+), 51 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index c7113f5193..3691c6270b 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwbase/environment.hpp" @@ -150,6 +151,10 @@ namespace MWRender mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); + + mProgramTemplate = mSceneManager->getShaderManager().getProgramTemplate() ? static_cast(mSceneManager->getShaderManager().getProgramTemplate()->clone(osg::CopyOp::SHALLOW_COPY)) : new osg::Program; + mProgramTemplate->addBindAttribLocation("aOffset", 6); + mProgramTemplate->addBindAttribLocation("aRotation", 7); } void Groundcover::collectInstances(InstanceMap& instances, float size, const osg::Vec2f& center) @@ -219,7 +224,7 @@ namespace MWRender group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->setCullCallback(new SceneUtil::LightListCallback); - mSceneManager->recreateShaders(group, "groundcover", false, true); + mSceneManager->recreateShaders(group, "groundcover", false, true, mProgramTemplate); mSceneManager->shareState(group); group->getBound(); return group; diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index f2693d6c24..ed88f7fe24 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace MWRender { @@ -33,6 +34,7 @@ namespace MWRender Resource::SceneManager* mSceneManager; float mDensity; osg::ref_ptr mStateset; + osg::ref_ptr mProgramTemplate; typedef std::map> InstanceMap; osg::ref_ptr createChunk(InstanceMap& instances, const osg::Vec2f& center); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c749d6aa78..fad2c25e20 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -325,7 +325,6 @@ namespace MWRender // Let LightManager choose which backend to use based on our hint. For methods besides legacy lighting, this depends on support for various OpenGL extensions. osg::ref_ptr sceneRoot = new SceneUtil::LightManager(lightingMethod == SceneUtil::LightingMethod::FFP); - resourceSystem->getSceneManager()->getShaderManager().setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setLightingMethod(sceneRoot->getLightingMethod()); resourceSystem->getSceneManager()->setSupportedLightingMethods(sceneRoot->getSupportedLightingMethods()); mMinimumAmbientLuminance = std::clamp(Settings::Manager::getFloat("minimum interior brightness", "Shaders"), 0.f, 1.f); diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 7a8677e43a..88e422fcf3 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -670,9 +670,6 @@ void Water::createShaderWaterStateSet(osg::Node* node, Reflection* reflection, R normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - auto method = mResourceSystem->getSceneManager()->getLightingMethod(); - if (method == SceneUtil::LightingMethod::SingleUBO) - program->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); mRainIntensityUpdater = new RainIntensityUpdater(); node->setUpdateCallback(mRainIntensityUpdater); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 2099d514f7..be32539d40 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -332,10 +333,11 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode, const osg::Program* programTemplate) { osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); shaderVisitor->setAllowedToModifyStateSets(false); + shaderVisitor->setProgramTemplate(programTemplate); if (forceShadersForNode) shaderVisitor->setForceShaders(true); node->accept(*shaderVisitor); @@ -410,6 +412,13 @@ namespace Resource void SceneManager::setLightingMethod(SceneUtil::LightingMethod method) { mLightingMethod = method; + + if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO) + { + osg::ref_ptr program = new osg::Program; + program->addBindUniformBlock("LightBufferBinding", static_cast(UBOBinding::LightBuffer)); + mShaderManager->setProgramTemplate(program); + } } SceneUtil::LightingMethod SceneManager::getLightingMethod() const diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 7434a25309..1443476fd5 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); /// Applying shaders to a node may replace some fixed-function state. /// This restores it. @@ -111,6 +111,11 @@ namespace Resource void setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported); bool isSupportedLightingMethod(SceneUtil::LightingMethod method) const; + enum class UBOBinding + { + // If we add more UBO's, we should probably assign their bindings dynamically according to the current count of UBO's in the programTemplate + LightBuffer + }; void setLightingMethod(SceneUtil::LightingMethod method); SceneUtil::LightingMethod getLightingMethod() const; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 1f7d9d2db2..ba60cb3185 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -294,9 +295,9 @@ namespace SceneUtil osg::ref_ptr ubo = new osg::UniformBufferObject; buffer->getData()->setBufferObject(ubo); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize()); #else - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), ubo, 0, buffer->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), ubo, 0, buffer->getData()->getTotalDataSize()); #endif stateset->setAttributeAndModes(ubb, mode); @@ -676,9 +677,9 @@ namespace SceneUtil auto bo = mLightManager->getLightBuffer(mLastFrameNumber); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); #else - osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Shader::UBOBinding::LightBuffer), bo->getData()->getBufferObject(), 0, bo->getData()->getTotalDataSize()); + osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), bo->getData()->getBufferObject(), 0, bo->getData()->getTotalDataSize()); #endif stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); } @@ -736,7 +737,7 @@ namespace SceneUtil // Needed to query the layout of the buffer object. The layout specifier needed to use the std140 layout is not reliably // available, regardless of extensions, until GLSL 140. mDummyProgram->addShader(new osg::Shader(osg::Shader::VERTEX, dummyVertSource)); - mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Shader::UBOBinding::LightBuffer)); + mDummyProgram->addBindUniformBlock("LightBufferBinding", static_cast(Resource::SceneManager::UBOBinding::LightBuffer)); } LightManagerStateAttribute(const LightManagerStateAttribute& copy, const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY) @@ -751,7 +752,7 @@ namespace SceneUtil void initSharedLayout(osg::GLExtensions* ext, int handle) const { - constexpr std::array index = { static_cast(Shader::UBOBinding::LightBuffer) }; + constexpr std::array index = { static_cast(Resource::SceneManager::UBOBinding::LightBuffer) }; int totalBlockSize = -1; int stride = -1; diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 007a6637a8..442b164981 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -11,6 +11,9 @@ #include #include #include +#include +#include +#include #include #include diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 33f79415f1..e057cfac02 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -17,7 +16,6 @@ namespace Shader { ShaderManager::ShaderManager() - : mLightingMethod(SceneUtil::LightingMethod::FFP) { } @@ -26,11 +24,6 @@ namespace Shader mPath = path; } - void ShaderManager::setLightingMethod(SceneUtil::LightingMethod method) - { - mLightingMethod = method; - } - bool addLineDirectivesAfterConditionalBlocks(std::string& source) { for (size_t position = 0; position < source.length(); ) @@ -345,19 +338,16 @@ namespace Shader return shaderIt->second; } - osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader) + osg::ref_ptr ShaderManager::getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader, const osg::Program* programTemplate) { std::lock_guard lock(mMutex); ProgramMap::iterator found = mPrograms.find(std::make_pair(vertexShader, fragmentShader)); if (found == mPrograms.end()) { - osg::ref_ptr program (new osg::Program); + if (!programTemplate) programTemplate = mProgramTemplate; + osg::ref_ptr program = programTemplate ? static_cast(programTemplate->clone(osg::CopyOp::SHALLOW_COPY)) : new osg::Program; program->addShader(vertexShader); program->addShader(fragmentShader); - program->addBindAttribLocation("aOffset", 6); - program->addBindAttribLocation("aRotation", 7); - if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO) - program->addBindUniformBlock("LightBufferBinding", static_cast(UBOBinding::LightBuffer)); found = mPrograms.insert(std::make_pair(std::make_pair(vertexShader, fragmentShader), program)).first; } return found->second; diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 2450f0d6dc..d0ee069b10 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -8,29 +8,11 @@ #include #include - -#include - -#include - -namespace Resource -{ - class SceneManager; -} - -namespace SceneUtil -{ - enum class LightingMethod; -} +#include namespace Shader { - enum class UBOBinding - { - LightBuffer - }; - /// @brief Reads shader template files and turns them into a concrete shader, based on a list of define's. /// @par Shader templates can get the value of a define with the syntax @define. class ShaderManager @@ -41,8 +23,6 @@ namespace Shader void setShaderPath(const std::string& path); - void setLightingMethod(SceneUtil::LightingMethod method); - typedef std::map DefineMap; /// Create or retrieve a shader instance. @@ -53,7 +33,10 @@ namespace Shader /// @note Thread safe. osg::ref_ptr getShader(const std::string& templateName, const DefineMap& defines, osg::Shader::Type shaderType); - osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader); + osg::ref_ptr getProgram(osg::ref_ptr vertexShader, osg::ref_ptr fragmentShader, const osg::Program* programTemplate=nullptr); + + const osg::Program* getProgramTemplate() const { return mProgramTemplate; } + void setProgramTemplate(const osg::Program* program) { mProgramTemplate = program; } /// Get (a copy of) the DefineMap used to construct all shaders DefineMap getGlobalDefines(); @@ -81,9 +64,9 @@ namespace Shader typedef std::map, osg::ref_ptr >, osg::ref_ptr > ProgramMap; ProgramMap mPrograms; - SceneUtil::LightingMethod mLightingMethod; - std::mutex mMutex; + + osg::ref_ptr mProgramTemplate; }; bool parseFors(std::string& source, const std::string& templateName); diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index cbd2863e46..0d0710853f 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,5 +1,8 @@ #include "shadervisitor.hpp" +#include +#include + #include #include #include @@ -553,7 +556,7 @@ namespace Shader if (vertexShader && fragmentShader) { - auto program = mShaderManager.getProgram(vertexShader, fragmentShader); + auto program = mShaderManager.getProgram(vertexShader, fragmentShader, mProgramTemplate); writableStateSet->setAttributeAndModes(program, osg::StateAttribute::ON); addedState->setAttributeAndModes(program); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index a5add473a6..3cd8b5d85a 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_SHADERVISITOR_H #include +#include namespace Resource { @@ -19,6 +20,8 @@ namespace Shader public: ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultShaderPrefix); + void setProgramTemplate(const osg::Program* programTemplate) { mProgramTemplate = programTemplate; } + /// By default, only bump mapped objects will have a shader added to them. /// Setting force = true will cause all objects to render using shaders, regardless of having a bump map. void setForceShaders(bool force); @@ -109,6 +112,8 @@ namespace Shader void createProgram(const ShaderRequirements& reqs); void ensureFFP(osg::Node& node); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); + + osg::ref_ptr mProgramTemplate; }; class ReinstateRemovedStateVisitor : public osg::NodeVisitor From 803195a05fc87b06385fbf4619faa9de51426c95 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 29 Sep 2021 16:29:10 +0200 Subject: [PATCH 089/137] add back some explicit includes --- apps/openmw/mwrender/groundcover.cpp | 2 +- components/shader/shadervisitor.hpp | 2 +- components/terrain/quadtreeworld.cpp | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 3691c6270b..71bddb1f9d 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -151,7 +151,7 @@ namespace MWRender mStateset->setRenderBinDetails(0, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); mStateset->setAttribute(new osg::VertexAttribDivisor(6, 1)); mStateset->setAttribute(new osg::VertexAttribDivisor(7, 1)); - + mProgramTemplate = mSceneManager->getShaderManager().getProgramTemplate() ? static_cast(mSceneManager->getShaderManager().getProgramTemplate()->clone(osg::CopyOp::SHALLOW_COPY)) : new osg::Program; mProgramTemplate->addBindAttribLocation("aOffset", 6); mProgramTemplate->addBindAttribLocation("aRotation", 7); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 3cd8b5d85a..3380a66cca 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -112,7 +112,7 @@ namespace Shader void createProgram(const ShaderRequirements& reqs); void ensureFFP(osg::Node& node); bool adjustGeometry(osg::Geometry& sourceGeometry, const ShaderRequirements& reqs); - + osg::ref_ptr mProgramTemplate; }; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 24f042b941..c05a964209 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -12,6 +13,7 @@ #include #include #include +#include #include "quadtreenode.hpp" #include "storage.hpp" From d8707a763fa79dcfd0576b5754bfe31eaf0163c9 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 29 Sep 2021 15:10:58 +0000 Subject: [PATCH 090/137] fixes build (#3134) * quadtreeworld.cpp * renderingmanager.cpp [ci skip] * quadtreeworld.hpp * cellborder.hpp * cellborder.cpp --- apps/openmw/mwrender/renderingmanager.cpp | 3 ++- components/terrain/cellborder.cpp | 7 +++---- components/terrain/cellborder.hpp | 2 +- components/terrain/quadtreeworld.cpp | 4 ++-- components/terrain/quadtreeworld.hpp | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index fad2c25e20..be143510b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -414,9 +414,10 @@ namespace MWRender const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); + bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain"); mTerrain.reset(new Terrain::QuadTreeWorld( sceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, - compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize)); + compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks)); if (Settings::Manager::getBool("object paging", "Terrain")) { mObjectPaging.reset(new ObjectPaging(mResourceSystem->getSceneManager())); diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 280a55faca..927530660e 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include "world.hpp" #include "../esm/loadland.hpp" @@ -22,7 +21,7 @@ CellBorder::CellBorder(Terrain::World *world, osg::Group *root, int borderMask, { } -osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, +osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset, osg::Vec4f color) { const int cellSize = ESM::Land::REAL_SIZE; @@ -66,8 +65,8 @@ osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, floa border->addPrimitiveSet(new osg::DrawArrays(GL_LINE_STRIP,0,vertices->size())); - osg::ref_ptr borderGeode = new osg::Geode; - borderGeode->addDrawable(border.get()); + osg::ref_ptr borderGeode = new osg::Group; + borderGeode->addChild(border.get()); osg::StateSet *stateSet = borderGeode->getOrCreateStateSet(); osg::ref_ptr material (new osg::Material); diff --git a/components/terrain/cellborder.hpp b/components/terrain/cellborder.hpp index 785c441b86..4481816a55 100644 --- a/components/terrain/cellborder.hpp +++ b/components/terrain/cellborder.hpp @@ -32,7 +32,7 @@ namespace Terrain */ void destroyCellBorderGeometry(); - static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset = 10.0, osg::Vec4f color = { 1,1,0,0 }); + static osg::ref_ptr createBorderGeometry(float x, float y, float size, Storage* terrain, Resource::SceneManager* sceneManager, int mask, float offset = 10.0, osg::Vec4f color = { 1,1,0,0 }); protected: Terrain::World *mWorld; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index c05a964209..b47896d775 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -271,7 +271,7 @@ private: unsigned int mNodeMask; }; -QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize) +QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resource::ResourceSystem *resourceSystem, Storage *storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, bool debugChunks) : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) @@ -279,7 +279,7 @@ QuadTreeWorld::QuadTreeWorld(osg::Group *parent, osg::Group *compileRoot, Resour , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) , mMinSize(1/8.f) - , mDebugTerrainChunks(Settings::Manager::getBool("debug chunks", "Terrain")) + , mDebugTerrainChunks(debugChunks) { mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapLevel(compMapLevel); diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 3aabc7233e..3bd606d6c6 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -22,7 +22,7 @@ namespace Terrain class QuadTreeWorld : public TerrainGrid // note: derived from TerrainGrid is only to render default cells (see loadCell) { public: - QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize); + QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, bool debugChunks); ~QuadTreeWorld(); From b7c1d9edb0c8a1cea73d52ba79e588f40d8846cd Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 29 Sep 2021 17:13:40 +0200 Subject: [PATCH 091/137] remove unnecessary includes --- components/terrain/quadtreeworld.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index b47896d775..4baf08149d 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -13,7 +12,6 @@ #include #include #include -#include #include "quadtreenode.hpp" #include "storage.hpp" From bec04c66133380d8f36e08193369dd0c485dbe0f Mon Sep 17 00:00:00 2001 From: "Hristos N. Triantafillou" Date: Wed, 29 Sep 2021 11:13:38 -0500 Subject: [PATCH 092/137] Unbreak the formatting broken by !1208 --- docs/source/reference/modding/paths.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/reference/modding/paths.rst b/docs/source/reference/modding/paths.rst index bc955b703d..db5000527b 100644 --- a/docs/source/reference/modding/paths.rst +++ b/docs/source/reference/modding/paths.rst @@ -29,7 +29,7 @@ Savegames +--------------+-----------------------------------------------------------------------------------------------------+ | OS | Location | +==============+=====================================================================================================+ -| Linux | ``$HOME/.local/share/openmw/saves`` | +| Linux | ``$HOME/.local/share/openmw/saves`` | +--------------+-----------------------------------------------------------------------------------------------------+ | Mac | ``$HOME/Library/Application\ Support/openmw/saves`` | +--------------+---------------+-------------------------------------------------------------------------------------+ From e109d864895e472c805fcb090ca03f08ac952562 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Sep 2021 21:01:22 +0400 Subject: [PATCH 093/137] Revert "avoids creating empty statesets on drawables (#3132)" This reverts commit 957c25a491b86772b02ab9057679ad1fc970f353. --- apps/openmw/mwrender/renderingmanager.cpp | 1 - components/nifosg/nifloader.cpp | 20 +++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index be143510b2..07b0418f2b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -486,7 +486,6 @@ namespace MWRender defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); - sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); mFog.reset(new FogManager()); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f05f906c8f..0432aef5b4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1134,6 +1134,8 @@ namespace NifOsg trans->addChild(toAttach); parentNode->addChild(trans); } + // create partsys stateset in order to pass in ShaderVisitor like all other Drawables + partsys->getOrCreateStateSet(); } void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) @@ -1921,6 +1923,8 @@ namespace NifOsg void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { + osg::StateSet* stateset = node->getOrCreateStateSet(); + // Specular lighting is enabled by default, but there's a quirk... bool specEnabled = true; osg::ref_ptr mat (new osg::Material); @@ -2002,15 +2006,15 @@ namespace NifOsg if (blendFunc->getDestination() == GL_DST_ALPHA) blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); - node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); bool noSort = (alphaprop->flags>>13)&1; if (!noSort) - node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); else - node->getOrCreateStateSet()->setRenderBinToInherit(); + stateset->setRenderBinToInherit(); } - else if (osg::StateSet* stateset = node->getStateSet()) + else { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); @@ -2021,9 +2025,9 @@ namespace NifOsg { osg::ref_ptr alphaFunc (new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f)); alphaFunc = shareAttribute(alphaFunc); - node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } - else if (osg::StateSet* stateset = node->getStateSet()) + else { stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); @@ -2079,10 +2083,8 @@ namespace NifOsg mat = shareAttribute(mat); - osg::StateSet* stateset = node->getStateSet(); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - if (emissiveMult != 1.f) - stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); + stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; From dc1fe62dded40110fefb3f26f86e8b0e70b614b9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 27 Aug 2021 20:07:50 +0200 Subject: [PATCH 094/137] Overhaul magic effects to work with onApply and onEnd events --- CHANGELOG.md | 9 + apps/essimporter/converter.hpp | 4 +- apps/openmw/CMakeLists.txt | 4 +- apps/openmw/mwbase/mechanicsmanager.hpp | 2 - apps/openmw/mwbase/world.hpp | 4 +- apps/openmw/mwgui/charactercreation.cpp | 16 - apps/openmw/mwgui/container.cpp | 2 +- apps/openmw/mwgui/jailscreen.cpp | 5 +- apps/openmw/mwgui/spellbuyingwindow.cpp | 8 +- apps/openmw/mwgui/spellcreationdialog.cpp | 4 +- apps/openmw/mwgui/spellicons.cpp | 54 +- apps/openmw/mwgui/spellicons.hpp | 14 - apps/openmw/mwgui/spellmodel.cpp | 3 +- apps/openmw/mwmechanics/activespells.cpp | 569 +++++---- apps/openmw/mwmechanics/activespells.hpp | 129 ++- apps/openmw/mwmechanics/actors.cpp | 824 ++----------- apps/openmw/mwmechanics/actors.hpp | 7 +- apps/openmw/mwmechanics/aicombataction.cpp | 12 +- apps/openmw/mwmechanics/creaturestats.cpp | 44 +- apps/openmw/mwmechanics/creaturestats.hpp | 13 +- apps/openmw/mwmechanics/disease.hpp | 7 +- apps/openmw/mwmechanics/linkedeffects.cpp | 75 -- apps/openmw/mwmechanics/linkedeffects.hpp | 32 - apps/openmw/mwmechanics/magiceffects.cpp | 12 +- apps/openmw/mwmechanics/magiceffects.hpp | 10 - .../mwmechanics/mechanicsmanagerimp.cpp | 22 +- .../mwmechanics/mechanicsmanagerimp.hpp | 2 - apps/openmw/mwmechanics/spellabsorption.cpp | 45 +- apps/openmw/mwmechanics/spellcasting.cpp | 401 ++----- apps/openmw/mwmechanics/spellcasting.hpp | 13 +- apps/openmw/mwmechanics/spelleffects.cpp | 1032 +++++++++++++++++ apps/openmw/mwmechanics/spelleffects.hpp | 20 + apps/openmw/mwmechanics/spelllist.hpp | 6 - apps/openmw/mwmechanics/spellpriority.cpp | 22 +- apps/openmw/mwmechanics/spells.cpp | 234 +--- apps/openmw/mwmechanics/spells.hpp | 26 +- apps/openmw/mwmechanics/summoning.cpp | 112 +- apps/openmw/mwmechanics/summoning.hpp | 23 +- apps/openmw/mwmechanics/tickableeffects.cpp | 216 ---- apps/openmw/mwmechanics/tickableeffects.hpp | 20 - apps/openmw/mwrender/npcanimation.cpp | 28 - apps/openmw/mwrender/npcanimation.hpp | 1 - apps/openmw/mwscript/miscextensions.cpp | 10 +- apps/openmw/mwscript/statsextensions.cpp | 18 +- apps/openmw/mwworld/cellstore.cpp | 8 + apps/openmw/mwworld/esmloader.cpp | 203 ++++ apps/openmw/mwworld/esmloader.hpp | 5 + apps/openmw/mwworld/inventorystore.cpp | 271 +---- apps/openmw/mwworld/inventorystore.hpp | 43 +- apps/openmw/mwworld/player.cpp | 11 +- apps/openmw/mwworld/projectilemanager.cpp | 10 +- apps/openmw/mwworld/projectilemanager.hpp | 3 +- apps/openmw/mwworld/worldimp.cpp | 78 +- apps/openmw/mwworld/worldimp.hpp | 4 +- components/esm/activespells.cpp | 97 +- components/esm/activespells.hpp | 35 +- components/esm/creaturestats.cpp | 52 +- components/esm/creaturestats.hpp | 1 + components/esm/magiceffects.cpp | 18 +- components/esm/magiceffects.hpp | 4 +- components/esm/projectilestate.cpp | 5 + components/esm/projectilestate.hpp | 1 + components/esm/savedgame.cpp | 2 +- components/esm/spellstate.cpp | 69 +- components/esm/spellstate.hpp | 8 +- 65 files changed, 2334 insertions(+), 2708 deletions(-) delete mode 100644 apps/openmw/mwmechanics/linkedeffects.cpp delete mode 100644 apps/openmw/mwmechanics/linkedeffects.hpp create mode 100644 apps/openmw/mwmechanics/spelleffects.cpp create mode 100644 apps/openmw/mwmechanics/spelleffects.hpp delete mode 100644 apps/openmw/mwmechanics/tickableeffects.cpp delete mode 100644 apps/openmw/mwmechanics/tickableeffects.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc9510f6e..ffe687a04c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 0.48.0 ------ + Bug #1751: Birthsign abilities increase modified attribute values instead of base ones Bug #3246: ESSImporter: Most NPCs are dead on save load Bug #3514: Editing a reference's position after loading an esp file makes the reference disappear Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) @@ -16,13 +17,18 @@ Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5508: Engine binary links to Qt without using it + Bug #5596: Effects in constant spells should not be merged + Bug #5621: Drained stats cannot be restored Bug #5755: Active grid object paging - disappearing textures Bug #5788: Texture editing parses the selected indexes wrongly + Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention Bug #5842: GetDisposition adds temporary disposition change from different actors + Bug #5863: GetEffect should return true after the player has teleported Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher Bug #6051: NaN water height in ESM file is not handled gracefully Bug #6066: addtopic "return" does not work from within script. No errors thrown Bug #6067: esp loader fails in for certain subrecord orders + Bug #6087: Bound items added directly to the inventory disappear if their corresponding spell effect ends Bug #6101: Disarming trapped unlocked owned objects isn't considered a crime Bug #6107: Fatigue is incorrectly recalculated when fortify effect is applied or removed Bug #6115: Showmap overzealous matching @@ -36,6 +42,7 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop + Bug #6223: Some Constant Effect Bound Items inconsistencies Bug #6273: Respawning NPCs rotation is inconsistent Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death @@ -45,8 +52,10 @@ Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map + Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4595: Unique object identifier Feature #4737: Handle instance move from one cell to another + Feature #5198: Implement "Magic effect expired" event Feature #5489: MCP: Telekinesis fix for activators Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp index 46e4426dc5..81b9711bbf 100644 --- a/apps/essimporter/converter.hpp +++ b/apps/essimporter/converter.hpp @@ -124,11 +124,9 @@ public: { mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt.mLevel; mContext->mPlayerBase = npc; - ESM::SpellState::SpellParams empty; // FIXME: player start spells and birthsign spells aren't listed here, // need to fix openmw to account for this - for (const auto & spell : npc.mSpells.mList) - mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[spell] = empty; + mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells = npc.mSpells.mList; // 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. diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 82a91699a9..7ad9856a77 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -92,8 +92,8 @@ add_openmw_dir (mwmechanics drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning - character actors objects aistate trading weaponpriority spellpriority weapontype spellutil tickableeffects - spellabsorption linkedeffects + character actors objects aistate trading weaponpriority spellpriority weapontype spellutil + spellabsorption spelleffects ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index b638ecade9..ccc60afc27 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -277,8 +277,6 @@ namespace MWBase virtual float getAngleToPlayer(const MWWorld::Ptr& ptr) const = 0; virtual MWMechanics::GreetingState getGreetingState(const MWWorld::Ptr& ptr) const = 0; virtual bool isTurningToPlayer(const MWWorld::Ptr& ptr) const = 0; - - virtual void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1dbb2b96f6..e9fc6e5c7a 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -539,7 +539,7 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) = 0; - virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) = 0; + virtual void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) = 0; virtual void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) = 0; virtual void updateProjectilesCasters() = 0; @@ -591,7 +591,7 @@ namespace MWBase virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, - const std::string& sourceName, const bool fromProjectile=false) = 0; + const std::string& sourceName, const bool fromProjectile=false, int slot = 0) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 827f87c7d6..43da1ef83f 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -72,13 +72,6 @@ namespace return {question, {r2, r1, r0}, sound}; } } - - void updatePlayerHealth() - { - MWWorld::Ptr player = MWMechanics::getPlayer(); - MWMechanics::NpcStats& npcStats = player.getClass().getNpcStats(player); - npcStats.updateHealth(); - } } namespace MWGui @@ -372,8 +365,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeDialog(mPickClassDialog); mPickClassDialog = nullptr; } - - updatePlayerHealth(); } void CharacterCreation::onPickClassDialogDone(WindowBase* parWindow) @@ -448,8 +439,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeDialog(mRaceDialog); mRaceDialog = nullptr; } - - updatePlayerHealth(); } void CharacterCreation::onRaceDialogBack() @@ -477,8 +466,6 @@ namespace MWGui MWBase::Environment::get().getWindowManager()->removeDialog(mBirthSignDialog); mBirthSignDialog = nullptr; } - - updatePlayerHealth(); } void CharacterCreation::onBirthSignDialogDone(WindowBase* parWindow) @@ -527,7 +514,6 @@ namespace MWGui // Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later mCreateClassDialog->setVisible(false); } - updatePlayerHealth(); } void CharacterCreation::onCreateClassDialogDone(WindowBase* parWindow) @@ -719,8 +705,6 @@ namespace MWGui MWBase::Environment::get().getWorld()->getStore().get().find(mGenerateClass); mPlayerClass = *klass; - - updatePlayerHealth(); } void CharacterCreation::onGenerateClassBack() diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index b649e41b0f..df490943d2 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -262,7 +262,7 @@ namespace MWGui } // Clean up summoned creatures as well - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + auto& creatureMap = creatureStats.getSummonedCreatureMap(); for (const auto& creature : creatureMap) MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(ptr, creature.second); creatureMap.clear(); diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp index cc793073e3..a029fe54b6 100644 --- a/apps/openmw/mwgui/jailscreen.cpp +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -82,10 +82,7 @@ namespace MWGui MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); // We should not worsen corprus when in prison - for (auto& spell : player.getClass().getCreatureStats(player).getCorprusSpells()) - { - spell.second.mNextWorsening += mDays * 24; - } + player.getClass().getCreatureStats(player).getActiveSpells().skipWorsenings(mDays * 24); std::set skills; for (int day=0; day spellsToSort; - for (MWMechanics::Spells::TIterator iter = merchantSpells.begin(); iter!=merchantSpells.end(); ++iter) + for (const ESM::Spell* spell : merchantSpells) { - const ESM::Spell* spell = iter->first; - if (spell->mData.mType!=ESM::Spell::ST_Spell) continue; // don't try to sell diseases, curses or powers @@ -115,10 +113,10 @@ namespace MWGui continue; } - if (playerHasSpell(iter->first->mId)) + if (playerHasSpell(spell->mId)) continue; - spellsToSort.push_back(iter->first); + spellsToSort.push_back(spell); } std::stable_sort(spellsToSort.begin(), spellsToSort.end(), sortSpells); diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 01653d9e6f..8c0ea865a8 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -520,10 +520,8 @@ namespace MWGui std::vector knownEffects; - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; - // only normal spells count if (spell->mData.mType != ESM::Spell::ST_Spell) continue; diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 405abfbae7..4a86503f46 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -24,50 +24,33 @@ namespace MWGui { - - void EffectSourceVisitor::visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime, float totalTime) - { - MagicEffectInfo newEffectSource; - newEffectSource.mKey = key; - newEffectSource.mMagnitude = static_cast(magnitude); - newEffectSource.mPermanent = mIsPermanent; - newEffectSource.mRemainingTime = remainingTime; - newEffectSource.mSource = sourceName; - newEffectSource.mTotalTime = totalTime; - - mEffectSources[key.mId].push_back(newEffectSource); - } - - void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) { - // TODO: Tracking add/remove/expire would be better than force updating every frame - MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - - EffectSourceVisitor visitor; - - // permanent item enchantments & permanent spells - visitor.mIsPermanent = true; - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - store.visitEffectSources(visitor); - stats.getSpells().visitEffectSources(visitor); - - // now add lasting effects - visitor.mIsPermanent = false; - stats.getActiveSpells().visitEffectSources(visitor); - - std::map >& effects = visitor.mEffectSources; + std::map> effects; + for(const auto& params : stats.getActiveSpells()) + { + for(const auto& effect : params.getEffects()) + { + if(!effect.mMagnitude) + continue; + MagicEffectInfo newEffectSource; + newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg); + newEffectSource.mMagnitude = static_cast(effect.mMagnitude); + newEffectSource.mPermanent = effect.mDuration == -1.f; + newEffectSource.mRemainingTime = effect.mTimeLeft; + newEffectSource.mSource = params.getDisplayName(); + newEffectSource.mTotalTime = effect.mDuration; + effects[effect.mEffectId].push_back(newEffectSource); + } + } int w=2; - for (auto& effectInfoPair : effects) + for (const auto& [effectId, effectInfos] : effects) { - const int effectId = effectInfoPair.first; const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld ()->getStore ().get().find(effectId); @@ -78,7 +61,6 @@ namespace MWGui static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->mValue.getFloat(); - std::vector& effectInfos = effectInfoPair.second; bool addNewLine = false; for (const MagicEffectInfo& effectInfo : effectInfos) { diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index b6aa49e69e..9825162a33 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -37,20 +37,6 @@ namespace MWGui bool mPermanent; // the effect is permanent }; - class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor - { - public: - bool mIsPermanent; - - std::map > mEffectSources; - - virtual ~EffectSourceVisitor() {} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override; - }; - class SpellIcons { public: diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index fe18b0f082..61ea9ce93a 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -92,9 +92,8 @@ namespace MWGui std::string filter = Misc::StringUtils::lowerCaseUtf8(mFilter); - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) continue; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 319d797257..6ad6ef369e 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -5,347 +5,442 @@ #include +#include "creaturestats.hpp" +#include "spellcasting.hpp" +#include "spelleffects.hpp" + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/inventorystore.hpp" -namespace MWMechanics +namespace { - void ActiveSpells::update(float duration) const + bool merge(std::vector& present, const std::vector& queued) { - bool rebuild = false; - - // Erase no longer active spells and effects - if (duration > 0) + // Can't merge if we already have an effect with the same effect index + auto problem = std::find_if(queued.begin(), queued.end(), [&] (const auto& qEffect) { - TContainer::iterator iter (mSpells.begin()); - while (iter!=mSpells.end()) - { - if (!timeToExpire (iter)) - { - mSpells.erase (iter++); - rebuild = true; - } - else - { - bool interrupt = false; - std::vector& effects = iter->second.mEffects; - for (std::vector::iterator effectIt = effects.begin(); effectIt != effects.end();) - { - if (effectIt->mTimeLeft <= 0) - { - rebuild = true; - - // Note: it we expire a Corprus effect, we should remove the whole spell. - if (effectIt->mEffectId == ESM::MagicEffect::Corprus) - { - iter = mSpells.erase (iter); - interrupt = true; - break; - } - - effectIt = effects.erase(effectIt); - } - else - { - effectIt->mTimeLeft -= duration; - ++effectIt; - } - } - - if (!interrupt) - ++iter; - } - } - } - - if (mSpellsChanged) - { - mSpellsChanged = false; - rebuild = true; - } - - if (rebuild) - rebuildEffects(); + return std::find_if(present.begin(), present.end(), [&] (const auto& pEffect) { return pEffect.mEffectIndex == qEffect.mEffectIndex; }) != present.end(); + }); + if(problem != queued.end()) + return false; + present.insert(present.end(), queued.begin(), queued.end()); + return true; } - void ActiveSpells::rebuildEffects() const + void addEffects(std::vector& effects, const ESM::EffectList& list, bool ignoreResistances = false) { - mEffects = MagicEffects(); - - for (TIterator iter (begin()); iter!=end(); ++iter) + int currentEffectIndex = 0; + for(const auto& enam : list.mList) { - const std::vector& effects = iter->second.mEffects; - - for (std::vector::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) - { - if (effectIt->mTimeLeft > 0) - mEffects.add(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), MWMechanics::EffectParam(effectIt->mMagnitude)); - } + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mMagnMin; + effect.mMaxMagnitude = enam.mMagnMax; + effect.mEffectIndex = currentEffectIndex++; + effect.mFlags = ESM::ActiveEffect::Flag_None; + if(ignoreResistances) + effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effects.emplace_back(effect); } } +} - ActiveSpells::ActiveSpells() - : mSpellsChanged (false) - {} +namespace MWMechanics +{ + ActiveSpells::IterationGuard::IterationGuard(ActiveSpells& spells) : mActiveSpells(spells) + { + mActiveSpells.mIterating = true; + } - const MagicEffects& ActiveSpells::getMagicEffects() const + ActiveSpells::IterationGuard::~IterationGuard() { - update(0.f); - return mEffects; + mActiveSpells.mIterating = false; } - ActiveSpells::TIterator ActiveSpells::begin() const + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster) + : mId(cast.mId), mDisplayName(cast.mSourceName), mCasterActorId(-1), mSlot(cast.mSlot), mType(cast.mType), mWorsenings(-1) { - return mSpells.begin(); + if(!caster.isEmpty() && caster.getClass().isActor()) + mCasterActorId = caster.getClass().getCreatureStats(caster).getActorId(); } - ActiveSpells::TIterator ActiveSpells::end() const + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances) + : mId(spell->mId), mDisplayName(spell->mName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()), mSlot(0) + , mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability : ESM::ActiveSpells::Type_Permanent), mWorsenings(-1) { - return mSpells.end(); + assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power); + addEffects(mEffects, spell->mEffects, ignoreResistances); } - double ActiveSpells::timeToExpire (const TIterator& iterator) const + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor) + : mId(item.getCellRef().getRefId()), mDisplayName(item.getClass().getName(item)), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId()) + , mSlot(slotIndex), mType(ESM::ActiveSpells::Type_Enchantment), mWorsenings(-1) { - const std::vector& effects = iterator->second.mEffects; + assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect); + addEffects(mEffects, enchantment->mEffects); + } - float duration = 0; + ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params) + : mId(params.mId), mEffects(params.mEffects), mDisplayName(params.mDisplayName), mCasterActorId(params.mCasterActorId) + , mSlot(params.mItem.isSet() ? params.mItem.mIndex : 0) + , mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening}) + {} - for (std::vector::const_iterator iter (effects.begin()); - iter!=effects.end(); ++iter) + ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const + { + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = mId; + params.mEffects = mEffects; + params.mDisplayName = mDisplayName; + params.mCasterActorId = mCasterActorId; + params.mItem.unset(); + if(mSlot) { - if (iter->mTimeLeft > duration) - duration = iter->mTimeLeft; + // Note that we're storing the inventory slot as a RefNum instead of an int as a matter of future proofing + // mSlot needs to be replaced with a RefNum once inventory items get persistent RefNum (#4508 #6148) + params.mItem = { static_cast(mSlot), 0 }; } + params.mType = mType; + params.mWorsenings = mWorsenings; + params.mNextWorsening = mNextWorsening.toEsm(); + return params; + } - if (duration < 0) - return 0; - - return duration; + void ActiveSpells::ActiveSpellParams::worsen() + { + ++mWorsenings; + if(!mWorsenings) + mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp(); + mNextWorsening += CorprusStats::sWorseningPeriod; } - bool ActiveSpells::isSpellActive(const std::string& id) const + bool ActiveSpells::ActiveSpellParams::shouldWorsen() const { - for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter) - { - if (Misc::StringUtils::ciEqual(iter->first, id)) - return true; - } - return false; + return mWorsenings >= 0 && MWBase::Environment::get().getWorld()->getTimeStamp() >= mNextWorsening; } - const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const + void ActiveSpells::ActiveSpellParams::resetWorsenings() { - return mSpells; + mWorsenings = -1; } - void ActiveSpells::addSpell(const std::string &id, bool stack, const std::vector& effects, - const std::string &displayName, int casterActorId) + void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration) { - TContainer::iterator it(mSpells.find(id)); + const auto& creatureStats = ptr.getClass().getCreatureStats(ptr); + assert(&creatureStats.getActiveSpells() == this); + IterationGuard guard{*this}; + // Erase no longer active spells and effects + for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();) + { + if(spellIt->mType != ESM::ActiveSpells::Type_Temporary && spellIt->mType != ESM::ActiveSpells::Type_Consumable) + { + ++spellIt; + continue; + } + bool removedSpell = false; + for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();) + { + if(effectIt->mFlags & ESM::ActiveEffect::Flag_Remove && effectIt->mTimeLeft <= 0.f) + { + auto effect = *effectIt; + effectIt = spellIt->mEffects.erase(effectIt); + onMagicEffectRemoved(ptr, *spellIt, effect); + removedSpell = applyPurges(ptr, &spellIt, &effectIt); + if(removedSpell) + break; + } + else + { + ++effectIt; + } + } + if(removedSpell) + continue; + if(spellIt->mEffects.empty()) + spellIt = mSpells.erase(spellIt); + else + ++spellIt; + } - ActiveSpellParams params; - params.mEffects = effects; - params.mDisplayName = displayName; - params.mCasterActorId = casterActorId; + for(const auto& spell : mQueue) + addToSpells(ptr, spell); + mQueue.clear(); - if (it == end() || stack) + // Vanilla only does this on cell change I think + const auto& spells = creatureStats.getSpells(); + for(const ESM::Spell* spell : spells) { - mSpells.insert(std::make_pair(id, params)); + if(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power && !isSpellActive(spell->mId)) + mSpells.emplace_back(ActiveSpellParams{spell, ptr}); } - else + + if(ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())) { - // addSpell() is called with effects for a range. - // but a spell may have effects with different ranges (e.g. Touch & Target) - // so, if we see new effects for same spell assume additional - // spell effects and add to existing effects of spell - mergeEffects(params.mEffects, it->second.mEffects); - it->second = params; + auto& store = ptr.getClass().getInventoryStore(ptr); + if(store.getInvListener() != nullptr) + { + bool playNonLooping = !store.isFirstEquip(); + const auto world = MWBase::Environment::get().getWorld(); + for(int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++) + { + auto slot = store.getSlot(slotIndex); + if(slot == store.end()) + continue; + const auto& enchantmentId = slot->getClass().getEnchantment(*slot); + if(enchantmentId.empty()) + continue; + const ESM::Enchantment* enchantment = world->getStore().get().find(enchantmentId); + if(enchantment->mData.mType != ESM::Enchantment::ConstantEffect) + continue; + if(std::find_if(mSpells.begin(), mSpells.end(), [&] (const ActiveSpellParams& params) + { + return params.mSlot == slotIndex && params.mType == ESM::ActiveSpells::Type_Enchantment && params.mId == slot->getCellRef().getRefId(); + }) != mSpells.end()) + continue; + ActiveSpellParams params(*slot, enchantment, slotIndex, ptr); + mSpells.emplace_back(params); + for(const auto& effect : params.mEffects) + MWMechanics::playEffects(ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); + } + } } - mSpellsChanged = true; - } - - void ActiveSpells::mergeEffects(std::vector& addTo, const std::vector& from) - { - for (std::vector::const_iterator effect(from.begin()); effect != from.end(); ++effect) + // Update effects + for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - // if effect is not in addTo, add it - bool missing = true; - for (std::vector::const_iterator iter(addTo.begin()); iter != addTo.end(); ++iter) + const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid? + bool removedSpell = false; + for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) { - if ((effect->mEffectId == iter->mEffectId) && (effect->mArg == iter->mArg)) - { - missing = false; + bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration); + if(remove) + it = spellIt->mEffects.erase(it); + else + ++it; + removedSpell = applyPurges(ptr, &spellIt, &it); + if(removedSpell) break; - } } - if (missing) + if(removedSpell) + continue; + + bool remove = false; + if(spellIt->mType == ESM::ActiveSpells::Type_Ability || spellIt->mType == ESM::ActiveSpells::Type_Permanent) + remove = !spells.hasSpell(spellIt->mId); + else if(spellIt->mType == ESM::ActiveSpells::Type_Enchantment) { - addTo.push_back(*effect); + const auto& store = ptr.getClass().getInventoryStore(ptr); + auto slot = store.getSlot(spellIt->mSlot); + remove = slot == store.end() || slot->getCellRef().getRefId() != spellIt->mId; } + if(remove) + { + auto params = *spellIt; + spellIt = mSpells.erase(spellIt); + for(const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); + applyPurges(ptr, &spellIt); + continue; + } + ++spellIt; } } - void ActiveSpells::removeEffects(const std::string &id) + void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) { - for (TContainer::iterator spell = mSpells.begin(); spell != mSpells.end(); ++spell) + if(spell.mType != ESM::ActiveSpells::Type_Consumable) { - if (spell->first == id) + auto found = std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& existing) + { + return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId && spell.mSlot == existing.mSlot; + }); + if(found != mSpells.end()) { - spell->second.mEffects.clear(); - mSpellsChanged = true; + if(merge(found->mEffects, spell.mEffects)) + return; + auto params = *found; + mSpells.erase(found); + for(const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); } } + mSpells.emplace_back(spell); } - void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const + ActiveSpells::ActiveSpells() : mIterating(false) + {} + + ActiveSpells::TIterator ActiveSpells::begin() const { - for (TContainer::const_iterator it = begin(); it != end(); ++it) - { - for (std::vector::const_iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end(); ++effectIt) - { - std::string name = it->second.mDisplayName; + return mSpells.begin(); + } - float magnitude = effectIt->mMagnitude; - if (magnitude) - visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), effectIt->mEffectIndex, name, it->first, it->second.mCasterActorId, magnitude, effectIt->mTimeLeft, effectIt->mDuration); - } - } + ActiveSpells::TIterator ActiveSpells::end() const + { + return mSpells.end(); } - void ActiveSpells::purgeAll(float chance, bool spellOnly) + bool ActiveSpells::isSpellActive(const std::string& id) const { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ) + return std::find_if(mSpells.begin(), mSpells.end(), [&] (const auto& spell) { - const std::string spellId = it->first; + return Misc::StringUtils::ciEqual(spell.mId, id); + }) != mSpells.end(); + } - // if spellOnly is true, dispell only spells. Leave potions, enchanted items etc. - if (spellOnly) - { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); - if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) - { - ++it; - continue; - } - } + void ActiveSpells::addSpell(const ActiveSpellParams& params) + { + mQueue.emplace_back(params); + } - if (Misc::Rng::roll0to99() < chance) - mSpells.erase(it++); - else - ++it; - } - mSpellsChanged = true; + void ActiveSpells::addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor) + { + mQueue.emplace_back(ActiveSpellParams{spell, actor, true}); } - void ActiveSpells::purgeEffect(short effectId) + void ActiveSpells::purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr) { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this); + mPurges.emplace(predicate); + if(!mIterating) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end();) - { - if (effectIt->mEffectId == effectId) - effectIt = it->second.mEffects.erase(effectIt); - else - ++effectIt; - } + IterationGuard guard{*this}; + applyPurges(ptr); } - mSpellsChanged = true; } - void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId, int effectIndex) + void ActiveSpells::purge(EffectPredicate predicate, const MWWorld::Ptr& ptr) { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + assert(&ptr.getClass().getCreatureStats(ptr).getActiveSpells() == this); + mPurges.emplace(predicate); + if(!mIterating) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end();) - { - if (effectIt->mEffectId == effectId && it->first == sourceId && (effectIndex < 0 || effectIndex == effectIt->mEffectIndex)) - effectIt = it->second.mEffects.erase(effectIt); - else - ++effectIt; - } + IterationGuard guard{*this}; + applyPurges(ptr); } - mSpellsChanged = true; } - void ActiveSpells::purge(int casterActorId) + bool ActiveSpells::applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell, std::vector::iterator* currentEffect) { - for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + bool removedCurrentSpell = false; + while(!mPurges.empty()) { - for (std::vector::iterator effectIt = it->second.mEffects.begin(); - effectIt != it->second.mEffects.end();) + auto predicate = mPurges.front(); + mPurges.pop(); + for(auto spellIt = mSpells.begin(); spellIt != mSpells.end();) { - if (it->second.mCasterActorId == casterActorId) - effectIt = it->second.mEffects.erase(effectIt); - else - ++effectIt; + bool isCurrentSpell = currentSpell && *currentSpell == spellIt; + std::visit([&] (auto&& variant) + { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + if(variant(*spellIt)) + { + auto params = *spellIt; + spellIt = mSpells.erase(spellIt); + if(isCurrentSpell) + { + *currentSpell = spellIt; + removedCurrentSpell = true; + } + for(const auto& effect : params.mEffects) + onMagicEffectRemoved(ptr, params, effect); + } + else + ++spellIt; + } + else + { + static_assert(std::is_same_v, "Non-exhaustive visitor"); + for(auto effectIt = spellIt->mEffects.begin(); effectIt != spellIt->mEffects.end();) + { + if(variant(*spellIt, *effectIt)) + { + auto effect = *effectIt; + if(isCurrentSpell && currentEffect) + { + auto distance = std::distance(spellIt->mEffects.begin(), *currentEffect); + if(effectIt <= *currentEffect) + distance--; + effectIt = spellIt->mEffects.erase(effectIt); + *currentEffect = spellIt->mEffects.begin() + distance; + } + else + effectIt = spellIt->mEffects.erase(effectIt); + onMagicEffectRemoved(ptr, *spellIt, effect); + } + else + ++effectIt; + } + ++spellIt; + } + }, predicate); } } - mSpellsChanged = true; + return removedCurrentSpell; } - void ActiveSpells::purgeCorprusDisease() + void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const std::string &id) { - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + purge([=] (const ActiveSpellParams& params) { - bool hasCorprusEffect = false; - for (std::vector::iterator effectIt = iter->second.mEffects.begin(); - effectIt != iter->second.mEffects.end();++effectIt) - { - if (effectIt->mEffectId == ESM::MagicEffect::Corprus) - { - hasCorprusEffect = true; - break; - } - } - - if (hasCorprusEffect) - { - mSpells.erase(iter++); - mSpellsChanged = true; - } - else - ++iter; - } + return params.mId == id; + }, ptr); } - void ActiveSpells::clear() + void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, short effectId) { - mSpells.clear(); - mSpellsChanged = true; + purge([=] (const ActiveSpellParams&, const ESM::ActiveEffect& effect) + { + return effect.mEffectId == effectId; + }, ptr); } - void ActiveSpells::writeState(ESM::ActiveSpells &state) const + void ActiveSpells::purge(const MWWorld::Ptr& ptr, int casterActorId) { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + purge([=] (const ActiveSpellParams& params) { - // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp - ESM::ActiveSpells::ActiveSpellParams params; - params.mEffects = it->second.mEffects; - params.mCasterActorId = it->second.mCasterActorId; - params.mDisplayName = it->second.mDisplayName; + return params.mCasterActorId == casterActorId; + }, ptr); + } - state.mSpells.insert (std::make_pair(it->first, params)); - } + void ActiveSpells::clear(const MWWorld::Ptr& ptr) + { + mQueue.clear(); + purge([] (const ActiveSpellParams& params) { return true; }, ptr); } - void ActiveSpells::readState(const ESM::ActiveSpells &state) + void ActiveSpells::skipWorsenings(double hours) { - for (ESM::ActiveSpells::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + for(auto& spell : mSpells) { - // Stupid copying of almost identical structures. ESM::TimeStamp <-> MWWorld::TimeStamp - ActiveSpellParams params; - params.mEffects = it->second.mEffects; - params.mCasterActorId = it->second.mCasterActorId; - params.mDisplayName = it->second.mDisplayName; - - mSpells.insert (std::make_pair(it->first, params)); - mSpellsChanged = true; + if(spell.mWorsenings >= 0) + spell.mNextWorsening += hours; } } + + void ActiveSpells::writeState(ESM::ActiveSpells &state) const + { + for(const auto& spell : mSpells) + state.mSpells.emplace_back(spell.toEsm()); + for(const auto& spell : mQueue) + state.mQueue.emplace_back(spell.toEsm()); + } + + void ActiveSpells::readState(const ESM::ActiveSpells &state) + { + for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells) + mSpells.emplace_back(ActiveSpellParams{spell}); + for(const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue) + mQueue.emplace_back(ActiveSpellParams{spell}); + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 8bc29aa444..0538d7a8b7 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -1,40 +1,83 @@ #ifndef GAME_MWMECHANICS_ACTIVESPELLS_H #define GAME_MWMECHANICS_ACTIVESPELLS_H -#include -#include +#include +#include +#include #include +#include +#include #include #include "../mwworld/timestamp.hpp" +#include "../mwworld/ptr.hpp" #include "magiceffects.hpp" +#include "spellcasting.hpp" + +namespace ESM +{ + struct Enchantment; + struct Spell; +} namespace MWMechanics { /// \brief Lasting spell effects /// - /// \note The name of this class is slightly misleading, since it also handels lasting potion + /// \note The name of this class is slightly misleading, since it also handles lasting potion /// effects. class ActiveSpells { public: - typedef ESM::ActiveEffect ActiveEffect; - - struct ActiveSpellParams + using ActiveEffect = ESM::ActiveEffect; + class ActiveSpellParams { - std::vector mEffects; - MWWorld::TimeStamp mTimeStamp; - std::string mDisplayName; + std::string mId; + std::vector mEffects; + std::string mDisplayName; + int mCasterActorId; + int mSlot; + ESM::ActiveSpells::EffectType mType; + int mWorsenings; + MWWorld::TimeStamp mNextWorsening; + + ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params); + + ActiveSpellParams(const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances = false); + + ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor); + + ESM::ActiveSpells::ActiveSpellParams toEsm() const; + + friend class ActiveSpells; + public: + ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster); + + const std::string& getId() const { return mId; } + + const std::vector& getEffects() const { return mEffects; } + std::vector& getEffects() { return mEffects; } - // The caster that inflicted this spell on us - int mCasterActorId; + ESM::ActiveSpells::EffectType getType() const { return mType; } + + int getCasterActorId() const { return mCasterActorId; } + + int getWorsenings() const { return mWorsenings; } + + const std::string& getDisplayName() const { return mDisplayName; } + + // Increments worsenings count and sets the next timestamp + void worsen(); + + bool shouldWorsen() const; + + void resetWorsenings(); }; - typedef std::multimap TContainer; - typedef TContainer::const_iterator TIterator; + typedef std::list::const_iterator TIterator; void readState (const ESM::ActiveSpells& state); void writeState (ESM::ActiveSpells& state) const; @@ -43,24 +86,29 @@ namespace MWMechanics TIterator end() const; - void update(float duration) const; + void update(const MWWorld::Ptr& ptr, float duration); private: + using ParamsPredicate = std::function; + using EffectPredicate = std::function; + using Predicate = std::variant; - mutable TContainer mSpells; - mutable MagicEffects mEffects; - mutable bool mSpellsChanged; + struct IterationGuard + { + ActiveSpells& mActiveSpells; - void rebuildEffects() const; + IterationGuard(ActiveSpells& spells); + ~IterationGuard(); + }; - /// Add any effects that are in "from" and not in "addTo" to "addTo" - void mergeEffects(std::vector& addTo, const std::vector& from); + std::list mSpells; + std::vector mQueue; + std::queue mPurges; + bool mIterating; - double timeToExpire (const TIterator& iterator) const; - ///< Returns time (in in-game hours) until the spell pointed to by \a iterator - /// expires. + void addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell); - const TContainer& getActiveSpells() const; + bool applyPurges(const MWWorld::Ptr& ptr, std::list::iterator* currentSpell = nullptr, std::vector::iterator* currentEffect = nullptr); public: @@ -70,40 +118,31 @@ namespace MWMechanics /// /// \brief addSpell /// \param id ID for stacking purposes. - /// \param stack If false, the spell is not added if one with the same ID exists already. - /// \param effects - /// \param displayName Name for display in magic menu. /// - void addSpell (const std::string& id, bool stack, const std::vector& effects, - const std::string& displayName, int casterActorId); + void addSpell (const ActiveSpellParams& params); + + /// Bypasses resistances + void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor); /// Removes the active effects from this spell/potion/.. with \a id - void removeEffects (const std::string& id); + void removeEffects (const MWWorld::Ptr& ptr, const std::string& id); /// Remove all active effects with this effect id - void purgeEffect (short effectId); - - /// Remove all active effects with this effect id and source id - void purgeEffect (short effectId, const std::string& sourceId, int effectIndex=-1); + void purgeEffect (const MWWorld::Ptr& ptr, short effectId); - /// Remove all active effects, if roll succeeds (for each effect) - void purgeAll(float chance, bool spellOnly = false); + void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr); + void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr); - /// Remove all effects with CASTER_LINKED flag that were cast by \a casterActorId - void purge (int casterActorId); + /// Remove all effects that were cast by \a casterActorId + void purge (const MWWorld::Ptr& ptr, int casterActorId); /// Remove all spells - void clear(); + void clear(const MWWorld::Ptr& ptr); bool isSpellActive (const std::string& id) const; ///< case insensitive - void purgeCorprusDisease(); - - const MagicEffects& getMagicEffects() const; - - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - + void skipWorsenings(double hours); }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 542f185854..b1aa246385 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -44,7 +44,6 @@ #include "actor.hpp" #include "summoning.hpp" #include "actorutil.hpp" -#include "tickableeffects.hpp" namespace { @@ -55,67 +54,26 @@ bool isConscious(const MWWorld::Ptr& ptr) return !stats.isDead() && !stats.getKnockedDown(); } -int getBoundItemSlot (const std::string& itemId) +bool isCommanded(const MWWorld::Ptr& actor) { - static std::map boundItemsMap; - if (boundItemsMap.empty()) + const auto& stats = actor.getClass().getCreatureStats(actor); + for(const auto& params : stats.getActiveSpells()) { - const auto& store = MWBase::Environment::get().getWorld()->getStore().get(); - - std::string boundId = store.find("sMagicBoundBootsID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Boots; - - boundId = store.find("sMagicBoundCuirassID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Cuirass; - - boundId = store.find("sMagicBoundLeftGauntletID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_LeftGauntlet; - - boundId = store.find("sMagicBoundRightGauntletID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_RightGauntlet; - - boundId = store.find("sMagicBoundHelmID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_Helmet; - - boundId = store.find("sMagicBoundShieldID")->mValue.getString(); - boundItemsMap[boundId] = MWWorld::InventoryStore::Slot_CarriedLeft; + for(const auto& effect : params.getEffects()) + { + if(((effect.mEffectId == ESM::MagicEffect::CommandHumanoid && actor.getClass().isNpc()) + || (effect.mEffectId == ESM::MagicEffect::CommandCreature && !actor.getClass().isNpc())) + && effect.mMagnitude >= stats.getLevel()) + return true; + } } - - int slot = MWWorld::InventoryStore::Slot_CarriedRight; - std::map::iterator it = boundItemsMap.find(itemId); - if (it != boundItemsMap.end()) - slot = it->second; - - return slot; + return false; } -class CheckActorCommanded : public MWMechanics::EffectSourceVisitor -{ - MWWorld::Ptr mActor; -public: - bool mCommanded; - - CheckActorCommanded(const MWWorld::Ptr& actor) - : mActor(actor) - , mCommanded(false){} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) - || (key.mId == ESM::MagicEffect::CommandCreature && mActor.getTypeName() == typeid(ESM::Creature).name())) - && magnitude >= mActor.getClass().getCreatureStats(mActor).getLevel()) - mCommanded = true; - } -}; - // Check for command effects having ended and remove package if necessary void adjustCommandedActor (const MWWorld::Ptr& actor) { - CheckActorCommanded check(actor); MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - stats.getActiveSpells().visitEffectSources(check); bool hasCommandPackage = false; @@ -130,7 +88,7 @@ void adjustCommandedActor (const MWWorld::Ptr& actor) } } - if (!check.mCommanded && hasCommandPackage) + if (!isCommanded(actor) && hasCommandPackage) stats.getAiSequence().erase(it); } @@ -169,129 +127,43 @@ void forEachFollowingPackage(MWMechanics::Actors::PtrActorMap& actors, const MWW } } -} - -namespace MWMechanics +float getStuntedMagickaDuration(const MWWorld::Ptr& actor) { - static const int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player - static const int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player - static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement - static const float DECELERATE_DISTANCE = 512.f; - - namespace + float remainingTime = 0.f; + for(const auto& params : actor.getClass().getCreatureStats(actor).getActiveSpells()) { - float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents) + for(const auto& effect : params.getEffects()) { - const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length(); - return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed; - } - } - - class GetStuntedMagickaDuration : public MWMechanics::EffectSourceVisitor - { - public: - float mRemainingTime; - - GetStuntedMagickaDuration(const MWWorld::Ptr& actor) - : mRemainingTime(0.f){} - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (mRemainingTime == -1) return; - - if (key.mId == ESM::MagicEffect::StuntedMagicka) + if(effect.mEffectId == ESM::MagicEffect::StuntedMagicka) { - if (totalTime == -1) - { - mRemainingTime = -1; - return; - } - - if (remainingTime > mRemainingTime) - mRemainingTime = remainingTime; + if(effect.mDuration == -1.f) + return -1.f; + remainingTime = std::max(remainingTime, effect.mTimeLeft); } } - }; - - class GetCurrentMagnitudes : public MWMechanics::EffectSourceVisitor - { - std::string mSpellId; - - public: - GetCurrentMagnitudes(const std::string& spellId) - : mSpellId(spellId) - { - } - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (magnitude <= 0) - return; - - if (sourceId != mSpellId) - return; - - mMagnitudes.emplace_back(key, magnitude); - } - - std::vector> mMagnitudes; - }; - - class GetCorprusSpells : public MWMechanics::EffectSourceVisitor - { - - public: - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (key.mId != ESM::MagicEffect::Corprus) - return; - - mSpells.push_back(sourceId); - } - - std::vector mSpells; - }; - - class SoulTrap : public MWMechanics::EffectSourceVisitor - { - MWWorld::Ptr mCreature; - MWWorld::Ptr mActor; - bool mTrapped; - public: - SoulTrap(const MWWorld::Ptr& trappedCreature) - : mCreature(trappedCreature) - , mTrapped(false) - { - } - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override - { - if (mTrapped) - return; - if (key.mId != ESM::MagicEffect::Soultrap) - return; - if (magnitude <= 0) - return; - - MWBase::World* world = MWBase::Environment::get().getWorld(); + } + return remainingTime; +} - MWWorld::Ptr caster = world->searchPtrViaActorId(casterActorId); +void soulTrap(const MWWorld::Ptr& creature) +{ + const auto& stats = creature.getClass().getCreatureStats(creature); + if(!stats.getMagicEffects().get(ESM::MagicEffect::Soultrap).getMagnitude()) + return; + int creatureSoulValue = creature.get()->mBase->mData.mSoul; + if (creatureSoulValue == 0) + return; + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); + for(const auto& params : stats.getActiveSpells()) + { + for(const auto& effect : params.getEffects()) + { + if(effect.mEffectId != ESM::MagicEffect::Soultrap || effect.mMagnitude <= 0.f) + continue; + MWWorld::Ptr caster = world->searchPtrViaActorId(params.getCasterActorId()); if (caster.isEmpty() || !caster.getClass().isActor()) - return; - - static const float fSoulgemMult = world->getStore().get().find("fSoulgemMult")->mValue.getFloat(); - - int creatureSoulValue = mCreature.get()->mBase->mData.mSoul; - if (creatureSoulValue == 0) - return; + continue; // Use the smallest soulgem that is large enough to hold the soul MWWorld::ContainerStore& container = caster.getClass().getContainerStore(caster); @@ -316,126 +188,50 @@ namespace MWMechanics } if (gem == container.end()) - return; + continue; // Set the soul on just one of the gems, not the whole stack gem->getContainerStore()->unstack(*gem, caster); - gem->getCellRef().setSoul(mCreature.getCellRef().getRefId()); + gem->getCellRef().setSoul(creature.getCellRef().getRefId()); // Restack the gem with other gems with the same soul gem->getContainerStore()->restack(*gem); - mTrapped = true; - - if (caster == getPlayer()) + if (caster == MWMechanics::getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sSoultrapSuccess}"); - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + const ESM::Static* fx = world->getStore().get() .search("VFX_Soul_Trap"); if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", mCreature.getRefData().getPosition().asVec3()); + world->spawnEffect("meshes\\" + fx->mModel, "", creature.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getSoundManager()->playSound3D( - mCreature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f - ); + MWBase::Environment::get().getSoundManager()->playSound3D(creature.getRefData().getPosition().asVec3(), "conjuration hit", 1.f, 1.f); + return; //remove to get vanilla behaviour } - }; - - void Actors::addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) - { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - int slot = getBoundItemSlot(itemId); - - if (actor.getClass().getContainerStore(actor).count(itemId) != 0) - return; - - MWWorld::ContainerStoreIterator prevItem = store.getSlot(slot); - - MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); - MWWorld::ActionEquip action(boundPtr); - action.execute(actor); - - if (actor != MWMechanics::getPlayer()) - return; - - MWWorld::Ptr newItem; - auto it = store.getSlot(slot); - // Equip can fail because beast races cannot equip boots/helmets - if(it != store.end()) - newItem = *it; - - if (newItem.isEmpty() || boundPtr != newItem) - return; - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - - // change draw state only if the item is in player's right hand - if (slot == MWWorld::InventoryStore::Slot_CarriedRight) - player.setDrawState(MWMechanics::DrawState_Weapon); - - if (prevItem != store.end()) - player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); } +} +} - void Actors::removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor) - { - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - int slot = getBoundItemSlot(itemId); - - MWWorld::ContainerStoreIterator currentItem = store.getSlot(slot); - - bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId); - - if (actor != MWMechanics::getPlayer()) - { - store.remove(itemId, 1, actor); - - // Equip a replacement - if (!wasEquipped) - return; - - std::string type = currentItem->getTypeName(); - if (type != typeid(ESM::Weapon).name() && type != typeid(ESM::Armor).name() && type != typeid(ESM::Clothing).name()) - return; - - if (actor.getClass().getCreatureStats(actor).isDead()) - return; - - if (!actor.getClass().hasInventoryStore(actor)) - return; - - if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) - return; - - actor.getClass().getInventoryStore(actor).autoEquip(actor); - - return; - } - - MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); - std::string prevItemId = player.getPreviousItem(itemId); - player.erasePreviousItem(itemId); +namespace MWMechanics +{ + static const int GREETING_SHOULD_START = 4; // how many updates should pass before NPC can greet player + static const int GREETING_SHOULD_END = 20; // how many updates should pass before NPC stops turning to player + static const int GREETING_COOLDOWN = 40; // how many updates should pass before NPC can continue movement + static const float DECELERATE_DISTANCE = 512.f; - if (!prevItemId.empty()) + namespace + { + float getTimeToDestination(const AiPackage& package, const osg::Vec3f& position, float speed, float duration, const osg::Vec3f& halfExtents) { - // Find previous item (or its replacement) by id. - // we should equip previous item only if expired bound item was equipped. - MWWorld::Ptr item = store.findReplacement(prevItemId); - if (!item.isEmpty() && wasEquipped) - { - MWWorld::ActionEquip action(item); - action.execute(actor); - } + const auto distanceToNextPathPoint = (package.getNextPathPoint(package.getDestination()) - position).length(); + return (distanceToNextPathPoint - package.getNextPathPointTolerance(speed, duration, halfExtents)) / speed; } - - store.remove(itemId, 1, actor); } void Actors::updateActor (const MWWorld::Ptr& ptr, float duration) { // magic effects - adjustMagicEffects (ptr); + adjustMagicEffects (ptr, duration); if (ptr.getClass().getCreatureStats(ptr).needToRecalcDynamicStats()) calculateDynamicStats (ptr); @@ -793,23 +589,61 @@ namespace MWMechanics } } - void Actors::adjustMagicEffects (const MWWorld::Ptr& creature) + void Actors::adjustMagicEffects (const MWWorld::Ptr& creature, float duration) { - CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); - if (creatureStats.isDeathAnimationFinished()) - return; + CreatureStats& creatureStats = creature.getClass().getCreatureStats (creature); + bool wasDead = creatureStats.isDead(); - MagicEffects now = creatureStats.getSpells().getMagicEffects(); + creatureStats.getActiveSpells().update(creature, duration); - if (creature.getClass().hasInventoryStore(creature)) + if (!wasDead && creatureStats.isDead()) { - MWWorld::InventoryStore& store = creature.getClass().getInventoryStore (creature); - now += store.getMagicEffects(); + // The actor was killed by a magic effect. Figure out if the player was responsible for it. + const ActiveSpells& spells = creatureStats.getActiveSpells(); + MWWorld::Ptr player = getPlayer(); + std::set playerFollowers; + getActorsSidingWith(player, playerFollowers); + + for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + bool actorKilled = false; + + const ActiveSpells::ActiveSpellParams& spell = *it; + MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.getCasterActorId()); + for (const auto& effect : spell.getEffects()) + { + static const std::array damageEffects{ + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, + ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth + }; + + bool isDamageEffect = std::find(damageEffects.begin(), damageEffects.end(), effect.mEffectId) != damageEffects.end(); + + if (isDamageEffect) + { + if (caster.getClass().isNpc() && caster.getClass().getNpcStats(caster).isWerewolf()) + caster.getClass().getNpcStats(caster).addWerewolfKill(); + if (caster == player || playerFollowers.find(caster) != playerFollowers.end()) + { + MWBase::Environment::get().getMechanicsManager()->actorKilled(creature, player); + actorKilled = true; + break; + } + } + } + + if (actorKilled) + break; + } } - now += creatureStats.getActiveSpells().getMagicEffects(); + // updateSummons assumes the actor belongs to a cell. + // This assumption isn't always valid for the player character. + if (!creature.isInCell()) + return; - creatureStats.modifyMagicEffects(now); + if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty()) + updateSummons(creature, mTimerDisposeSummonsCorpses == 0.f); } void Actors::calculateDynamicStats (const MWWorld::Ptr& ptr) @@ -857,22 +691,18 @@ namespace MWMechanics if (stunted) { // Stunted Magicka effect should be taken into account. - GetStuntedMagickaDuration visitor(ptr); - stats.getActiveSpells().visitEffectSources(visitor); - stats.getSpells().visitEffectSources(visitor); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).visitEffectSources(visitor); + float remainingTime = getStuntedMagickaDuration(ptr); // Take a maximum remaining duration of Stunted Magicka effects (-1 is a constant one) in game hours. - if (visitor.mRemainingTime > 0) + if (remainingTime > 0) { double timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); if(timeScale == 0.0) timeScale = 1; - restoreHours = std::max(0.0, hours - visitor.mRemainingTime * timeScale / 3600.f); + restoreHours = std::max(0.0, hours - remainingTime * timeScale / 3600.f); } - else if (visitor.mRemainingTime == -1) + else if (remainingTime == -1) restoreHours = 0; } @@ -933,398 +763,12 @@ namespace MWMechanics stats.setFatigue (fatigue); } - class ExpiryVisitor : public EffectSourceVisitor - { - private: - MWWorld::Ptr mActor; - float mDuration; - - public: - ExpiryVisitor(const MWWorld::Ptr& actor, float duration) - : mActor(actor), mDuration(duration) - { - } - - void visit (MWMechanics::EffectKey key, int /*effectIndex*/, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float magnitude, float remainingTime = -1, float /*totalTime*/ = -1) override - { - if (magnitude > 0 && remainingTime > 0 && remainingTime < mDuration) - { - CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); - if (effectTick(creatureStats, mActor, key, magnitude * remainingTime)) - creatureStats.getMagicEffects().add(key, -magnitude); - } - } - }; - - void Actors::applyCureEffects(const MWWorld::Ptr& actor) - { - CreatureStats &creatureStats = actor.getClass().getCreatureStats(actor); - const MagicEffects &effects = creatureStats.getMagicEffects(); - - if (effects.get(ESM::MagicEffect::CurePoison).getModifier() > 0) - { - creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); - creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Poison); - if (actor.getClass().hasInventoryStore(actor)) - actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison); - } - if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0) - { - creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); - creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze); - if (actor.getClass().hasInventoryStore(actor)) - actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze); - } - if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0) - { - creatureStats.getSpells().purgeCommonDisease(); - } - if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0) - { - creatureStats.getSpells().purgeBlightDisease(); - } - if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0) - { - creatureStats.getActiveSpells().purgeCorprusDisease(); - creatureStats.getSpells().purgeCorprusDisease(); - if (actor.getClass().hasInventoryStore(actor)) - actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true); - } - if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0) - { - creatureStats.getSpells().purgeCurses(); - } - } - void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); - const MagicEffects &effects = creatureStats.getMagicEffects(); - bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - applyCureEffects(ptr); - - bool wasDead = creatureStats.isDead(); - - if (duration > 0) - { - // Apply correct magnitude for tickable effects that have just expired, - // in case duration > remaining time of effect. - // One case where this will happen is when the player uses the rest/wait command - // while there is a tickable effect active that should expire before the end of the rest/wait. - ExpiryVisitor visitor(ptr, duration); - creatureStats.getActiveSpells().visitEffectSources(visitor); - - for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) - { - // tickable effects (i.e. effects having a lasting impact after expiry) - effectTick(creatureStats, ptr, it->first, it->second.getMagnitude() * duration); - - // instant effects are already applied on spell impact in spellcasting.cpp, but may also come from permanent abilities - if (it->second.getMagnitude() > 0) - { - CastSpell cast(ptr, ptr); - if (cast.applyInstantEffect(ptr, ptr, it->first, it->second.getMagnitude())) - { - creatureStats.getSpells().purgeEffect(it->first.mId); - creatureStats.getActiveSpells().purgeEffect(it->first.mId); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first.mId); - } - } - } - } - - // purge levitate effect if levitation is disabled - // check only modifier, because base value can be setted from SetFlying console command. - if (MWBase::Environment::get().getWorld()->isLevitationEnabled() == false && effects.get(ESM::MagicEffect::Levitate).getModifier() > 0) - { - creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Levitate); - creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Levitate); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).purgeEffect(ESM::MagicEffect::Levitate); - - if (ptr == getPlayer()) - { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); - } - } - - // dynamic stats - for (int i = 0; i < 3; ++i) - { - DynamicStat stat = creatureStats.getDynamic(i); - float fortify = effects.get(ESM::MagicEffect::FortifyHealth + i).getMagnitude(); - float drain = 0.f; - if (!godmode) - drain = effects.get(ESM::MagicEffect::DrainHealth + i).getMagnitude(); - stat.setCurrentModifier(fortify - drain, - // Magicka can be decreased below zero due to a fortify effect wearing off - // Fatigue can be decreased below zero meaning the actor will be knocked out - i == 1 || i == 2); - - creatureStats.setDynamic(i, stat); - } - - // attributes - for(int i = 0;i < ESM::Attribute::Length;++i) - { - AttributeValue stat = creatureStats.getAttribute(i); - float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifyAttribute, i)).getMagnitude(); - float drain = 0.f, absorb = 0.f; - if (!godmode) - { - drain = effects.get(EffectKey(ESM::MagicEffect::DrainAttribute, i)).getMagnitude(); - absorb = effects.get(EffectKey(ESM::MagicEffect::AbsorbAttribute, i)).getMagnitude(); - } - stat.setModifier(static_cast(fortify - drain - absorb)); - - creatureStats.setAttribute(i, stat); - } if (creatureStats.needToRecalcDynamicStats()) calculateDynamicStats(ptr); - - if (ptr == getPlayer()) - { - GetCorprusSpells getCorprusSpellsVisitor; - creatureStats.getSpells().visitEffectSources(getCorprusSpellsVisitor); - creatureStats.getActiveSpells().visitEffectSources(getCorprusSpellsVisitor); - ptr.getClass().getInventoryStore(ptr).visitEffectSources(getCorprusSpellsVisitor); - std::vector corprusSpells = getCorprusSpellsVisitor.mSpells; - std::vector corprusSpellsToRemove; - - for (auto it = creatureStats.getCorprusSpells().begin(); it != creatureStats.getCorprusSpells().end(); ++it) - { - if(std::find(corprusSpells.begin(), corprusSpells.end(), it->first) == corprusSpells.end()) - { - // Corprus effect expired, remove entry and restore stats. - MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, it->first); - corprusSpellsToRemove.push_back(it->first); - corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); - continue; - } - - corprusSpells.erase(std::remove(corprusSpells.begin(), corprusSpells.end(), it->first), corprusSpells.end()); - - if (MWBase::Environment::get().getWorld()->getTimeStamp() >= it->second.mNextWorsening) - { - it->second.mNextWorsening += CorprusStats::sWorseningPeriod; - GetCurrentMagnitudes getMagnitudesVisitor (it->first); - creatureStats.getSpells().visitEffectSources(getMagnitudesVisitor); - creatureStats.getActiveSpells().visitEffectSources(getMagnitudesVisitor); - ptr.getClass().getInventoryStore(ptr).visitEffectSources(getMagnitudesVisitor); - for (auto& effectMagnitude : getMagnitudesVisitor.mMagnitudes) - { - if (effectMagnitude.first.mId == ESM::MagicEffect::FortifyAttribute) - { - AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); - attr.damage(-effectMagnitude.second); - creatureStats.setAttribute(effectMagnitude.first.mArg, attr); - it->second.mWorsenings[effectMagnitude.first.mArg] = 0; - } - else if (effectMagnitude.first.mId == ESM::MagicEffect::DrainAttribute) - { - AttributeValue attr = creatureStats.getAttribute(effectMagnitude.first.mArg); - int currentDamage = attr.getDamage(); - if (currentDamage >= 0) - it->second.mWorsenings[effectMagnitude.first.mArg] = std::min(it->second.mWorsenings[effectMagnitude.first.mArg], currentDamage); - - it->second.mWorsenings[effectMagnitude.first.mArg] += effectMagnitude.second; - attr.damage(effectMagnitude.second); - creatureStats.setAttribute(effectMagnitude.first.mArg, attr); - } - } - - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); - } - } - - for (std::string& oldCorprusSpell : corprusSpellsToRemove) - { - creatureStats.removeCorprusSpell(oldCorprusSpell); - } - - for (std::string& newCorprusSpell : corprusSpells) - { - CorprusStats corprus; - for (int i=0; igetTimeStamp() + CorprusStats::sWorseningPeriod; - - creatureStats.addCorprusSpell(newCorprusSpell, corprus); - } - } - - // AI setting modifiers - int creature = !ptr.getClass().isNpc(); - Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Fight); - stat.setModifier(static_cast(effects.get(ESM::MagicEffect::FrenzyHumanoid + creature).getMagnitude() - - effects.get(ESM::MagicEffect::CalmHumanoid+creature).getMagnitude())); - creatureStats.setAiSetting(CreatureStats::AI_Fight, stat); - - stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); - stat.setModifier(static_cast(effects.get(ESM::MagicEffect::DemoralizeHumanoid + creature).getMagnitude() - - effects.get(ESM::MagicEffect::RallyHumanoid+creature).getMagnitude())); - creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); - if (creature && ptr.get()->mBase->mData.mType == ESM::Creature::Undead) - { - stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); - stat.setModifier(static_cast(effects.get(ESM::MagicEffect::TurnUndead).getMagnitude())); - creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); - } - - if (!wasDead && creatureStats.isDead()) - { - // The actor was killed by a magic effect. Figure out if the player was responsible for it. - const ActiveSpells& spells = creatureStats.getActiveSpells(); - MWWorld::Ptr player = getPlayer(); - std::set playerFollowers; - getActorsSidingWith(player, playerFollowers); - - for (ActiveSpells::TIterator it = spells.begin(); it != spells.end(); ++it) - { - bool actorKilled = false; - - const ActiveSpells::ActiveSpellParams& spell = it->second; - MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spell.mCasterActorId); - for (std::vector::const_iterator effectIt = spell.mEffects.begin(); - effectIt != spell.mEffects.end(); ++effectIt) - { - int effectId = effectIt->mEffectId; - bool isDamageEffect = false; - - int damageEffects[] = { - ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison, - ESM::MagicEffect::SunDamage, ESM::MagicEffect::DamageHealth, ESM::MagicEffect::AbsorbHealth - }; - - for (unsigned int i=0; iactorKilled(ptr, player); - actorKilled = true; - break; - } - } - } - - if (actorKilled) - break; - } - } - - // TODO: dirty flag for magic effects to avoid some unnecessary work below? - - // any value of calm > 0 will stop the actor from fighting - if ((effects.get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) - || (effects.get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) - creatureStats.getAiSequence().stopCombat(); - - // Update bound effects - // Note: in vanilla MW multiple bound items of the same type can be created by different spells. - // As these extra copies are kinda useless this may or may not be important. - static std::map boundItemsMap; - if (boundItemsMap.empty()) - { - boundItemsMap[ESM::MagicEffect::BoundBattleAxe] = "sMagicBoundBattleAxeID"; - boundItemsMap[ESM::MagicEffect::BoundBoots] = "sMagicBoundBootsID"; - boundItemsMap[ESM::MagicEffect::BoundCuirass] = "sMagicBoundCuirassID"; - boundItemsMap[ESM::MagicEffect::BoundDagger] = "sMagicBoundDaggerID"; - boundItemsMap[ESM::MagicEffect::BoundGloves] = "sMagicBoundLeftGauntletID"; // Note: needs RightGauntlet variant too (see below) - boundItemsMap[ESM::MagicEffect::BoundHelm] = "sMagicBoundHelmID"; - boundItemsMap[ESM::MagicEffect::BoundLongbow] = "sMagicBoundLongbowID"; - boundItemsMap[ESM::MagicEffect::BoundLongsword] = "sMagicBoundLongswordID"; - boundItemsMap[ESM::MagicEffect::BoundMace] = "sMagicBoundMaceID"; - boundItemsMap[ESM::MagicEffect::BoundShield] = "sMagicBoundShieldID"; - boundItemsMap[ESM::MagicEffect::BoundSpear] = "sMagicBoundSpearID"; - } - - if(ptr.getClass().hasInventoryStore(ptr)) - { - for (const auto& [effect, itemGmst] : boundItemsMap) - { - bool found = creatureStats.mBoundItems.find(effect) != creatureStats.mBoundItems.end(); - float magnitude = effects.get(effect).getMagnitude(); - if (found != (magnitude > 0)) - { - if (magnitude > 0) - creatureStats.mBoundItems.insert(effect); - else - creatureStats.mBoundItems.erase(effect); - - std::string item = MWBase::Environment::get().getWorld()->getStore().get().find( - itemGmst)->mValue.getString(); - - magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); - - if (effect == ESM::MagicEffect::BoundGloves) - { - item = MWBase::Environment::get().getWorld()->getStore().get().find( - "sMagicBoundRightGauntletID")->mValue.getString(); - magnitude > 0 ? addBoundItem(item, ptr) : removeBoundItem(item, ptr); - } - } - } - } - - // Summoned creature update visitor assumes the actor belongs to a cell. - // This assumption isn't always valid for the player character. - if (!ptr.isInCell()) - return; - - bool hasSummonEffect = false; - for (MagicEffects::Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) - { - if (isSummoningEffect(it->first.mId)) - { - hasSummonEffect = true; - break; - } - } - - if (!creatureStats.getSummonedCreatureMap().empty() || !creatureStats.getSummonedCreatureGraveyard().empty() || hasSummonEffect) - { - UpdateSummonedCreatures updateSummonedCreatures(ptr); - creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures); - creatureStats.getSpells().visitEffectSources(updateSummonedCreatures); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures); - updateSummonedCreatures.process(mTimerDisposeSummonsCorpses == 0.f); - } - } - - void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration) - { - NpcStats &npcStats = ptr.getClass().getNpcStats(ptr); - const MagicEffects &effects = npcStats.getMagicEffects(); - bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - // skills - for(int i = 0;i < ESM::Skill::Length;++i) - { - SkillValue& skill = npcStats.getSkill(i); - float fortify = effects.get(EffectKey(ESM::MagicEffect::FortifySkill, i)).getMagnitude(); - float drain = 0.f, absorb = 0.f; - if (!godmode) - { - drain = effects.get(EffectKey(ESM::MagicEffect::DrainSkill, i)).getMagnitude(); - absorb = effects.get(EffectKey(ESM::MagicEffect::AbsorbSkill, i)).getMagnitude(); - } - skill.setModifier(static_cast(fortify - drain - absorb)); - } } bool Actors::isAttackPreparing(const MWWorld::Ptr& ptr) @@ -1979,8 +1423,6 @@ namespace MWMechanics player.getClass().getCreatureStats(player).setHitAttemptActorId(-1); } - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); - const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration); // For dead actors we need to update looping spell particles @@ -1988,7 +1430,7 @@ namespace MWMechanics { // They can be added during the death animation if (!iter->first.getClass().getCreatureStats(iter->first).isDeathAnimationFinished()) - adjustMagicEffects(iter->first); + adjustMagicEffects(iter->first, duration); ctrl->updateContinuousVfx(); } else @@ -2083,8 +1525,6 @@ namespace MWMechanics if (inProcessingRange) updateDrowning(iter->first, duration, ctrl->isKnockedOut(), isPlayer); - calculateNpcStatModifiers(iter->first, duration); - if (timerUpdateEquippedLight == 0) updateEquippedLight(iter->first, updateEquippedLightInterval, showTorches); } @@ -2251,10 +1691,7 @@ namespace MWMechanics // Apply soultrap if (iter->first.getTypeName() == typeid(ESM::Creature).name()) - { - SoulTrap soulTrap (iter->first); - stats.getActiveSpells().visitEffectSources(soulTrap); - } + soulTrap(iter->first); // Magic effects will be reset later, and the magic effect that could kill the actor // needs to be determined now @@ -2270,30 +1707,27 @@ namespace MWMechanics // Reset magic effects and recalculate derived effects // One case where we need this is to make sure bound items are removed upon death - stats.modifyMagicEffects(MWMechanics::MagicEffects()); - stats.getActiveSpells().clear(); + float vampirism = stats.getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude(); + stats.getActiveSpells().clear(iter->first); // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); // Reset dynamic stats, attributes and skills calculateCreatureStatModifiers(iter->first, 0); - if (iter->first.getClass().isNpc()) - calculateNpcStatModifiers(iter->first, 0); + stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism); if (isPlayer) { //player's death animation is over MWBase::Environment::get().getStateManager()->askLoadRecent(); + // Play Death Music if it was the player dying + MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); } else { // NPC death animation is over, disable actor collision MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, false); } - - // Play Death Music if it was the player dying - if(iter->first == getPlayer()) - MWBase::Environment::get().getSoundManager()->streamMusic("Special/MW_Death.mp3"); } } } @@ -2313,7 +1747,7 @@ namespace MWMechanics // Remove the summoned creature's summoned creatures as well MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - std::map& creatureMap = stats.getSummonedCreatureMap(); + auto& creatureMap = stats.getSummonedCreatureMap(); for (const auto& creature : creatureMap) cleanupSummonedCreature(stats, creature.second); creatureMap.clear(); @@ -2334,7 +1768,7 @@ namespace MWMechanics for (PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { MWMechanics::ActiveSpells& spells = iter->first.getClass().getCreatureStats(iter->first).getActiveSpells(); - spells.purge(casterActorId); + spells.purge(iter->first, casterActorId); } } @@ -2352,7 +1786,7 @@ namespace MWMechanics { if (iter->first.getClass().getCreatureStats(iter->first).isDead()) { - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); + adjustMagicEffects (iter->first, duration); continue; } @@ -2363,15 +1797,11 @@ namespace MWMechanics (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange) continue; - adjustMagicEffects (iter->first); + adjustMagicEffects (iter->first, duration); if (iter->first.getClass().getCreatureStats(iter->first).needToRecalcDynamicStats()) calculateDynamicStats (iter->first); calculateCreatureStatModifiers (iter->first, duration); - if (iter->first.getClass().isNpc()) - calculateNpcStatModifiers(iter->first, duration); - - iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(iter->first); if (animation) @@ -2760,10 +2190,8 @@ namespace MWMechanics void Actors::updateMagicEffects(const MWWorld::Ptr &ptr) { - adjustMagicEffects(ptr); + adjustMagicEffects(ptr, 0.f); calculateCreatureStatModifiers(ptr, 0.f); - if (ptr.getClass().isNpc()) - calculateNpcStatModifiers(ptr, 0.f); } bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 171b45e270..3efe28204d 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -41,15 +41,11 @@ namespace MWMechanics { std::map mDeathCount; - void addBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); - void removeBoundItem (const std::string& itemId, const MWWorld::Ptr& actor); - - void adjustMagicEffects (const MWWorld::Ptr& creature); + void adjustMagicEffects (const MWWorld::Ptr& creature, float duration); void calculateDynamicStats (const MWWorld::Ptr& ptr); void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); - void calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateRestoration (const MWWorld::Ptr& ptr, float duration); @@ -208,7 +204,6 @@ namespace MWMechanics private: void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); - void applyCureEffects (const MWWorld::Ptr& actor); PtrActorMap mActors; float mTimerDisposeSummonsCorpses; diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index c26454aab5..e6176c869c 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -208,14 +208,14 @@ namespace MWMechanics } } - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - float rating = rateSpell(it->first, actor, enemy); + float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; - bestAction.reset(new ActionSpell(it->first->mId)); - antiFleeRating = vanillaRateSpell(it->first, actor, enemy); + bestAction.reset(new ActionSpell(spell->mId)); + antiFleeRating = vanillaRateSpell(spell, actor, enemy); } } @@ -266,9 +266,9 @@ namespace MWMechanics } } - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - float rating = rateSpell(it->first, actor, enemy); + float rating = rateSpell(spell, actor, enemy); if (rating > bestActionRating) { bestActionRating = rating; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 8d99664f27..8ca6e8b766 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -545,20 +545,12 @@ namespace MWMechanics mAiSequence.writeState(state.mAiSequence); mMagicEffects.writeState(state.mMagicEffects); - state.mSummonedCreatureMap = mSummonedCreatures; + state.mSummonedCreatures = mSummonedCreatures; state.mSummonGraveyard = mSummonGraveyard; state.mHasAiSettings = true; for (int i=0; i<4; ++i) mAiSettings[i].writeState (state.mAiSettings[i]); - - for (auto it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) - { - for (int i=0; ifirst].mWorsenings[i] = mCorprusSpells.at(it->first).mWorsenings[i]; - - state.mCorprusSpells[it->first].mNextWorsening = mCorprusSpells.at(it->first).mNextWorsening.toEsm(); - } } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -618,7 +610,7 @@ namespace MWMechanics // We can't use mActiveSpells::getMagicEffects here because it doesn't include expired effects auto spell = std::find_if(mActiveSpells.begin(), mActiveSpells.end(), [&] (const auto& spell) { - const auto& effects = spell.second.mEffects; + const auto& effects = spell.getEffects(); return std::find_if(effects.begin(), effects.end(), [&] (const auto& effect) { return effect.mEffectId == effectId; @@ -629,21 +621,12 @@ namespace MWMechanics } } - mSummonedCreatures = state.mSummonedCreatureMap; + mSummonedCreatures = state.mSummonedCreatures; mSummonGraveyard = state.mSummonGraveyard; if (state.mHasAiSettings) for (int i=0; i<4; ++i) mAiSettings[i].readState(state.mAiSettings[i]); - - mCorprusSpells.clear(); - for (auto it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) - { - for (int i=0; ifirst].mWorsenings[i] = state.mCorprusSpells.at(it->first).mWorsenings[i]; - - mCorprusSpells[it->first].mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - } } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) @@ -710,7 +693,7 @@ namespace MWMechanics return mTimeOfDeath; } - std::map& CreatureStats::getSummonedCreatureMap() + std::multimap& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } @@ -719,23 +702,4 @@ namespace MWMechanics { return mSummonGraveyard; } - - std::map &CreatureStats::getCorprusSpells() - { - return mCorprusSpells; - } - - void CreatureStats::addCorprusSpell(const std::string& sourceId, CorprusStats& stats) - { - mCorprusSpells[sourceId] = stats; - } - - void CreatureStats::removeCorprusSpell(const std::string& sourceId) - { - auto corprusIt = mCorprusSpells.find(sourceId); - if (corprusIt != mCorprusSpells.end()) - { - mCorprusSpells.erase(corprusIt); - } - } } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 06d526cc5a..d234d14486 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -1,6 +1,7 @@ #ifndef GAME_MWMECHANICS_CREATURESTATS_H #define GAME_MWMECHANICS_CREATURESTATS_H +#include #include #include #include @@ -87,14 +88,12 @@ namespace MWMechanics float mSideMovementAngle; private: - std::map mSummonedCreatures; // + std::multimap mSummonedCreatures; // // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. // This may be necessary when the creature is in an inactive cell. std::vector mSummonGraveyard; - std::map mCorprusSpells; - protected: int mLevel; @@ -236,7 +235,7 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; - std::map& getSummonedCreatureMap(); // + std::multimap& getSummonedCreatureMap(); // std::vector& getSummonedCreatureGraveyard(); // ActorIds enum Flag @@ -297,12 +296,6 @@ namespace MWMechanics static void cleanup(); - std::map & getCorprusSpells(); - - void addCorprusSpell(const std::string& sourceId, CorprusStats& stats); - - void removeCorprusSpell(const std::string& sourceId); - float getSideMovementAngle() const { return mSideMovementAngle; } void setSideMovementAngle(float angle) { mSideMovementAngle = angle; } }; diff --git a/apps/openmw/mwmechanics/disease.hpp b/apps/openmw/mwmechanics/disease.hpp index 5d4bfb682f..426a66ecf2 100644 --- a/apps/openmw/mwmechanics/disease.hpp +++ b/apps/openmw/mwmechanics/disease.hpp @@ -30,12 +30,11 @@ namespace MWMechanics MWBase::Environment::get().getWorld()->getStore().get().find( "fDiseaseXferChance")->mValue.getFloat(); - MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); + const MagicEffects& actorEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); Spells& spells = carrier.getClass().getCreatureStats(carrier).getSpells(); - for (Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + for (const ESM::Spell* spell : spells) { - const ESM::Spell* spell = it->first; if (actor.getClass().getCreatureStats(actor).getSpells().hasSpell(spell->mId)) continue; @@ -56,7 +55,7 @@ namespace MWMechanics if (Misc::Rng::rollDice(10000) < x) { // Contracted disease! - actor.getClass().getCreatureStats(actor).getSpells().add(it->first); + actor.getClass().getCreatureStats(actor).getSpells().add(spell); MWBase::Environment::get().getWorld()->applyLoopingParticles(actor); std::string msg = "sMagicContractDisease"; diff --git a/apps/openmw/mwmechanics/linkedeffects.cpp b/apps/openmw/mwmechanics/linkedeffects.cpp deleted file mode 100644 index b0defac7d2..0000000000 --- a/apps/openmw/mwmechanics/linkedeffects.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "linkedeffects.hpp" - -#include -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "../mwrender/animation.hpp" - -#include "../mwworld/class.hpp" -#include "../mwworld/esmstore.hpp" - -#include "creaturestats.hpp" - -namespace MWMechanics -{ - - bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects) - { - if (caster.isEmpty() || caster == target || !target.getClass().isActor()) - return false; - - bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; - bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable; - if (!isHarmful || isUnreflectable) - return false; - - float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); - if (Misc::Rng::roll0to99() >= reflect) - return false; - - const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation && !reflectStatic->mModel.empty()) - animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string()); - reflectedEffects.mList.emplace_back(effect); - return true; - } - - void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source) - { - if (caster.isEmpty() || caster == target) - return; - - if (!target.getClass().isActor() || !caster.getClass().isActor()) - return; - - // Make sure callers don't do something weird - if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill) - throw std::runtime_error("invalid absorb stat effect"); - - if (appliedEffect.mMagnitude == 0) - return; - - std::vector absorbEffects; - ActiveSpells::ActiveEffect absorbEffect = appliedEffect; - absorbEffect.mMagnitude *= -1; - absorbEffect.mEffectIndex = appliedEffect.mEffectIndex; - absorbEffects.emplace_back(absorbEffect); - - // Morrowind negates reflected Absorb spells so the original caster won't be harmed. - if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game")) - { - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true, - absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId()); - return; - } - - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true, - absorbEffects, source, target.getClass().getCreatureStats(target).getActorId()); - } -} diff --git a/apps/openmw/mwmechanics/linkedeffects.hpp b/apps/openmw/mwmechanics/linkedeffects.hpp deleted file mode 100644 index a6dea2a3a2..0000000000 --- a/apps/openmw/mwmechanics/linkedeffects.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef MWMECHANICS_LINKEDEFFECTS_H -#define MWMECHANICS_LINKEDEFFECTS_H - -#include - -namespace ESM -{ - struct ActiveEffect; - struct EffectList; - struct ENAMstruct; - struct MagicEffect; - struct Spell; -} - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - - // Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list. - bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects); - - // Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster. - void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect, - const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source); -} - -#endif diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 1a1a44f638..e75361628b 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -193,22 +193,22 @@ namespace MWMechanics void MagicEffects::writeState(ESM::MagicEffects &state) const { - // Don't need to save Modifiers, they are recalculated every frame anyway. - for (Collection::const_iterator iter (begin()); iter!=end(); ++iter) + for (const auto& [key, params] : mCollection) { - if (iter->second.getBase() != 0) + if (params.getBase() != 0 || params.getModifier() != 0.f) { // Don't worry about mArg, never used by magic effect script instructions - state.mEffects.insert(std::make_pair(iter->first.mId, iter->second.getBase())); + state.mEffects[key.mId] = {params.getBase(), params.getModifier()}; } } } void MagicEffects::readState(const ESM::MagicEffects &state) { - for (std::map::const_iterator it = state.mEffects.begin(); it != state.mEffects.end(); ++it) + for (const auto& [key, params] : state.mEffects) { - mCollection[EffectKey(it->first)].setBase(it->second); + mCollection[EffectKey(key)].setBase(params.first); + mCollection[EffectKey(key)].setModifier(params.second); } } } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 12735a87fc..50f4dab050 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -69,16 +69,6 @@ namespace MWMechanics return param -= right; } - // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display - struct EffectSourceVisitor - { - virtual ~EffectSourceVisitor() { } - - virtual void visit (EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) = 0; - }; - /// \brief Effects currently affecting a NPC or creature class MagicEffects { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 24601d79c8..fb8b3fa2b0 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -84,7 +84,7 @@ namespace MWMechanics // reset creatureStats.setLevel(player->mNpdt.mLevel); creatureStats.getSpells().clear(true); - creatureStats.modifyMagicEffects(MagicEffects()); + creatureStats.getActiveSpells().clear(ptr); for (int i=0; i<27; ++i) npcStats.getSkill (i).setBase (player->mNpdt.mSkills[i]); @@ -213,6 +213,7 @@ namespace MWMechanics int attributes[ESM::Attribute::Length]; for (int i=0; i selectedSpells = autoCalcPlayerSpells(skills, attributes, race); @@ -221,6 +222,7 @@ namespace MWMechanics // forced update and current value adjustments mActors.updateActor (ptr, 0); + mActors.updateActor (ptr, 0); for (int i=0; i<3; ++i) { @@ -282,24 +284,6 @@ namespace MWMechanics mObjects.dropObjects(cellStore); } - void MechanicsManager::restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) - { - auto& stats = actor.getClass().getCreatureStats (actor); - auto& corprusSpells = stats.getCorprusSpells(); - - auto corprusIt = corprusSpells.find(sourceId); - - if (corprusIt != corprusSpells.end()) - { - for (int i = 0; i < ESM::Attribute::Length; ++i) - { - MWMechanics::AttributeValue attr = stats.getAttribute(i); - attr.restore(corprusIt->second.mWorsenings[i]); - actor.getClass().getCreatureStats(actor).setAttribute(i, attr); - } - } - } - void MechanicsManager::update(float duration, bool paused) { // Note: we should do it here since game mechanics and world updates use these values diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 0aaeb23435..206b5c5420 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -230,8 +230,6 @@ namespace MWMechanics GreetingState getGreetingState(const MWWorld::Ptr& ptr) const override; bool isTurningToPlayer(const MWWorld::Ptr& ptr) const override; - void restoreStatsAfterCorprus(const MWWorld::Ptr& actor, const std::string& sourceId) override; - private: bool canCommitCrimeAgainst(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); bool canReportCrime(const MWWorld::Ptr &actor, const MWWorld::Ptr &victim, std::set &playerFollowers); diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp index cb80921b33..82531132ca 100644 --- a/apps/openmw/mwmechanics/spellabsorption.cpp +++ b/apps/openmw/mwmechanics/spellabsorption.cpp @@ -16,33 +16,30 @@ namespace MWMechanics { - - class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor + float getProbability(const MWMechanics::ActiveSpells& activeSpells) { - public: - float mProbability{0.f}; - - GetAbsorptionProbability() = default; - - void visit (MWMechanics::EffectKey key, int /*effectIndex*/, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float magnitude, float /*remainingTime*/, float /*totalTime*/) override + float probability = 0.f; + for(const auto& params : activeSpells) { - if (key.mId == ESM::MagicEffect::SpellAbsorption) + for(const auto& effect : params.getEffects()) { - if (mProbability == 0.f) - mProbability = magnitude / 100; - else + if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption) { - // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. - // Real absorption probability will be the (1 - total fail chance) in this case. - float failProbability = 1.f - mProbability; - failProbability *= 1.f - magnitude / 100; - mProbability = 1.f - failProbability; + if(probability == 0.f) + probability = effect.mMagnitude / 100; + else + { + // If there are different sources of SpellAbsorption effect, multiply failing probability for all effects. + // Real absorption probability will be the (1 - total fail chance) in this case. + float failProbability = 1.f - probability; + failProbability *= 1.f - effect.mMagnitude / 100; + probability = 1.f - failProbability; + } } } } - }; + return static_cast(probability * 100); + } bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) { @@ -53,13 +50,7 @@ namespace MWMechanics if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f) return false; - GetAbsorptionProbability check; - stats.getActiveSpells().visitEffectSources(check); - stats.getSpells().visitEffectSources(check); - if (target.getClass().hasInventoryStore(target)) - target.getClass().getInventoryStore(target).visitEffectSources(check); - - int chance = check.mProbability * 100; + int chance = getProbability(stats.getActiveSpells()); if (Misc::Rng::roll0to99() >= chance) return false; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 502e56edf7..53fbe69ab3 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -22,14 +22,38 @@ #include "actorutil.hpp" #include "aifollow.hpp" #include "creaturestats.hpp" -#include "linkedeffects.hpp" #include "spellabsorption.hpp" -#include "spellresistance.hpp" +#include "spelleffects.hpp" #include "spellutil.hpp" #include "summoning.hpp" -#include "tickableeffects.hpp" #include "weapontype.hpp" +namespace +{ + bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, + const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects) + { + if (caster.isEmpty() || caster == target || !target.getClass().isActor()) + return false; + + bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; + bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable; + if (!isHarmful || isUnreflectable) + return false; + + float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); + if (Misc::Rng::roll0to99() >= reflect) + return false; + + const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + if (animation && !reflectStatic->mModel.empty()) + animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string()); + reflectedEffects.mList.emplace_back(effect); + return true; + } +} + namespace MWMechanics { CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) @@ -54,7 +78,7 @@ namespace MWMechanics (mTarget.getRefData().getPosition().asVec3() + offset) - (mCaster.getRefData().getPosition().asVec3()); - MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection); + MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot); } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, @@ -100,20 +124,13 @@ namespace MWMechanics } ESM::EffectList reflectedEffects; - std::vector appliedLastingEffects; - - // HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance. - // This is required for Weakness effects in a spell to apply to any subsequent effects in the spell. - // Otherwise, they'd only apply after the whole spell was added. - MagicEffects targetEffects; - if (targetIsActor) - targetEffects += target.getClass().getCreatureStats(target).getMagicEffects(); + ActiveSpells::ActiveSpellParams params(*this, caster); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); - ActiveSpells targetSpells; + const ActiveSpells* targetSpells = nullptr; if (targetIsActor) - targetSpells = target.getClass().getCreatureStats(target).getActiveSpells(); + targetSpells = &target.getClass().getCreatureStats(target).getActiveSpells(); bool canCastAnEffect = false; // For bound equipment.If this remains false // throughout the iteration of this spell's @@ -134,7 +151,7 @@ namespace MWMechanics effectIt->mEffectID); // Re-casting a bound equipment effect has no effect if the spell is still active - if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells.isSpellActive(mId)) + if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells && targetSpells->isSpellActive(mId)) { if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); @@ -163,307 +180,70 @@ namespace MWMechanics if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) continue; - // Try resisting. - float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); - if (magnitudeMult == 0) + ActiveSpells::ActiveEffect effect; + effect.mEffectId = effectIt->mEffectID; + effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; + effect.mMagnitude = 0.f; + effect.mMinMagnitude = effectIt->mMagnMin; + effect.mMaxMagnitude = effectIt->mMagnMax; + effect.mTimeLeft = 0.f; + effect.mEffectIndex = currentEffectIndex; + effect.mFlags = ESM::ActiveEffect::Flag_None; + + // Avoid applying harmful effects to the player in god mode + if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) { - // Fully resisted, show message - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + effect.mMinMagnitude = 0; + effect.mMaxMagnitude = 0; } - else - { - float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1); - magnitude *= magnitudeMult; - if (!target.getClass().isActor()) - { - // non-actor objects have no list of active magic effects, so have to apply instantly - if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude)) - continue; - } - else // target.getClass().isActor() == true - { - ActiveSpells::ActiveEffect effect; - effect.mEffectId = effectIt->mEffectID; - effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; - effect.mMagnitude = magnitude; - effect.mTimeLeft = 0.f; - effect.mEffectIndex = currentEffectIndex; - - // Avoid applying absorb effects if the caster is the target - // We still need the spell to be added - if (caster == target - && effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute - && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill) - { - effect.mMagnitude = 0; - } + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; - // Avoid applying harmful effects to the player in god mode - if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) - { - effect.mMagnitude = 0; - } + bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; + if (!appliedOnce) + effect.mDuration = std::max(1.f, effect.mDuration); - bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; - if (castByPlayer && target != caster && !target.getClass().getCreatureStats(target).isDead() && effectAffectsHealth) - { - // If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar. - MWBase::Environment::get().getWindowManager()->setEnemy(target); - } - - bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; + effect.mTimeLeft = effect.mDuration; - bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; - if (!appliedOnce) - effect.mDuration = std::max(1.f, effect.mDuration); + // add to list of active effects, to apply in next frame + params.getEffects().emplace_back(effect); - if (effect.mDuration == 0) - { - // We still should add effect to list to allow GetSpellEffects to detect this spell - appliedLastingEffects.push_back(effect); - - // duration 0 means apply full magnitude instantly - bool wasDead = target.getClass().getCreatureStats(target).isDead(); - effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude); - bool isDead = target.getClass().getCreatureStats(target).isDead(); - - if (!wasDead && isDead) - MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster); - } - else - { - effect.mTimeLeft = effect.mDuration; - - targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); - - // add to list of active effects, to apply in next frame - appliedLastingEffects.push_back(effect); - - // Unequip all items, if a spell with the ExtraSpell effect was casted - if (effectIt->mEffectID == ESM::MagicEffect::ExtraSpell && target.getClass().hasInventoryStore(target)) - { - MWWorld::InventoryStore& store = target.getClass().getInventoryStore(target); - store.unequipAll(target); - } - - // Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target - if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc()) - || (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name())) - && !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) - { - MWMechanics::AiFollow package(caster, true); - target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); - } - - // For absorb effects, also apply the effect to the caster - but with a negative - // magnitude, since we're transferring stats from the target to the caster - if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill) - absorbStat(*effectIt, effect, caster, target, reflected, mSourceName); - } - } - - // Re-casting a summon effect will remove the creature from previous castings of that effect. - if (isSummoningEffect(effectIt->mEffectID) && targetIsActor) - { - CreatureStats& targetStats = target.getClass().getCreatureStats(target); - ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex); - auto findCreature = targetStats.getSummonedCreatureMap().find(key); - if (findCreature != targetStats.getSummonedCreatureMap().end()) - { - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, findCreature->second); - targetStats.getSummonedCreatureMap().erase(findCreature); - } - } - - if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; + bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; + if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth) + { + // If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar. + MWBase::Environment::get().getWindowManager()->setEnemy(target); + } - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - - // Add VFX - const ESM::Static* castStatic; - if (!magicEffect->mHit.empty()) - castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - else - castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); - - bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; - // Note: in case of non actor, a free effect should be fine as well - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); - if (anim && !castStatic->mModel.empty()) - anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); - } + if (targetIsActor || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) + { + playEffects(target, *magicEffect); } } if (!exploded) - MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile); + MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile, mSlot); if (!target.isEmpty()) { if (!reflectedEffects.mList.empty()) inflict(caster, target, reflectedEffects, range, true, exploded); - if (!appliedLastingEffects.empty()) - { - int casterActorId = -1; - if (!caster.isEmpty() && caster.getClass().isActor()) - casterActorId = caster.getClass().getCreatureStats(caster).getActorId(); - target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, - mSourceName, casterActorId); - } - } - } - - bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude) - { - short effectId = effect.mId; - if (target.getClass().canLock(target)) - { - if (effectId == ESM::MagicEffect::Lock) - { - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::MagicEffect *magiceffect = store.get().find(effectId); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation) - animation->addSpellCastGlow(magiceffect); - if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude - { - if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); - target.getCellRef().lock(static_cast(magnitude)); - } - return true; - } - else if (effectId == ESM::MagicEffect::Open) + if (!params.getEffects().empty()) { - if (!caster.isEmpty()) - { - MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target); - // Use the player instead of the caster for vanilla crime compatibility - } - const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const ESM::MagicEffect *magiceffect = store.get().find(effectId); - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); - if (animation) - animation->addSpellCastGlow(magiceffect); - if (target.getCellRef().getLockLevel() <= magnitude) - { - if (target.getCellRef().getLockLevel() > 0) - { - MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); - - if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); - } - target.getCellRef().unlock(); - } + if(targetIsActor) + target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params); else { - MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + // Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway + for(auto& effect : params.getEffects()) + applyMagicEffect(target, caster, params, effect, 0.f); } - - return true; - } - } - else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel) - { - target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude, true); - return true; - } - else if(target.getClass().isActor() && effectId >= ESM::MagicEffect::CalmHumanoid && effectId <= ESM::MagicEffect::RallyCreature) - { - // Treat X Humanoid spells on creatures and X Creature spells on NPCs as instant effects and remove their VFX - bool affectsCreatures = (effectId - ESM::MagicEffect::CalmHumanoid) & 1; - if(affectsCreatures == target.getClass().isNpc()) - { - MWBase::Environment::get().getWorld()->getAnimation(target)->removeEffect(effectId); - return true; - } - } - else if(target.getClass().isActor() && effectId == ESM::MagicEffect::TurnUndead) - { - // Diverge from vanilla by giving scripts a chance to detect Turn Undead on non-undead, but still remove the effect and VFX - if(target.getClass().isNpc() || target.get()->mBase->mData.mType != ESM::Creature::Undead) - { - MWBase::Environment::get().getWorld()->getAnimation(target)->removeEffect(effectId); - return true; } } - else if (target.getClass().isActor() && target == getPlayer()) - { - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster); - bool teleportingEnabled = MWBase::Environment::get().getWorld()->isTeleportingEnabled(); - - if (effectId == ESM::MagicEffect::DivineIntervention || effectId == ESM::MagicEffect::AlmsiviIntervention) - { - if (!teleportingEnabled) - { - if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - return true; - } - std::string marker = (effectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; - MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, marker); - anim->removeEffect(effectId); - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_end"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1); - return true; - } - else if (effectId == ESM::MagicEffect::Mark) - { - if (teleportingEnabled) - { - MWBase::Environment::get().getWorld()->getPlayer().markPosition( - target.getCell(), target.getRefData().getPosition()); - } - else if (caster == getPlayer()) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - } - return true; - } - else if (effectId == ESM::MagicEffect::Recall) - { - if (!teleportingEnabled) - { - if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - return true; - } - - MWWorld::CellStore* markedCell = nullptr; - ESM::Position markedPosition; - - MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition); - if (markedCell) - { - MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, - markedPosition, false); - action.execute(target); - anim->removeEffect(effectId); - } - return true; - } - } - return false; } - bool CastSpell::cast(const std::string &id) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); @@ -479,7 +259,7 @@ namespace MWMechanics throw std::runtime_error("ID type cannot be casted"); } - bool CastSpell::cast(const MWWorld::Ptr &item, bool launchProjectile) + bool CastSpell::cast(const MWWorld::Ptr &item, int slot, bool launchProjectile) { std::string enchantmentName = item.getClass().getEnchantment(item); if (enchantmentName.empty()) @@ -490,7 +270,7 @@ namespace MWMechanics const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(enchantmentName); - mStack = false; + mSlot = slot; bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool isProjectile = false; @@ -570,7 +350,7 @@ namespace MWMechanics { mSourceName = potion->mName; mId = potion->mId; - mStack = true; + mType = ESM::ActiveSpells::Type_Consumable; inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); @@ -581,7 +361,6 @@ namespace MWMechanics { mSourceName = spell->mName; mId = spell->mId; - mStack = false; const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); @@ -656,7 +435,7 @@ namespace MWMechanics bool CastSpell::cast (const ESM::Ingredient* ingredient) { mId = ingredient->mId; - mStack = true; + mType = ESM::ActiveSpells::Type_Consumable; mSourceName = ingredient->mName; ESM::ENAMstruct effect; @@ -799,4 +578,36 @@ namespace MWMechanics sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); } } + + void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping) + { + if (playNonLooping) + { + static const std::string schools[] = { + "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" + }; + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(!magicEffect.mHitSound.empty()) + sndMgr->playSound3D(target, magicEffect.mHitSound, 1.0f, 1.0f); + else + sndMgr->playSound3D(target, schools[magicEffect.mData.mSchool]+" hit", 1.0f, 1.0f); + } + + // Add VFX + const ESM::Static* castStatic; + if (!magicEffect.mHit.empty()) + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect.mHit); + else + castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); + + bool loop = (magicEffect.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); + if(anim && !castStatic->mModel.empty()) + { + // Don't play particle VFX unless the effect is new or it should be looping. + if (playNonLooping || loop) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect.mIndex, loop, "", magicEffect.mParticle); + } + } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index cc525d2db6..a21ea33e0b 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -1,6 +1,7 @@ #ifndef MWMECHANICS_SPELLCASTING_H #define MWMECHANICS_SPELLCASTING_H +#include #include #include "../mwworld/ptr.hpp" @@ -11,6 +12,7 @@ namespace ESM struct Ingredient; struct Potion; struct EffectList; + struct MagicEffect; } namespace MWMechanics @@ -26,13 +28,14 @@ namespace MWMechanics void playSpellCastingEffects(const std::vector& effects); public: - bool mStack{false}; std::string mId; // ID of spell, potion, item etc std::string mSourceName; // Display name for spell, potion, etc osg::Vec3f mHitPosition{0,0,0}; // Used for spawning area orb bool mAlwaysSucceed{false}; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon) bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance, etc.) + int mSlot{0}; + ESM::ActiveSpells::EffectType mType{ESM::ActiveSpells::Type_Temporary}; public: CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false); @@ -41,7 +44,7 @@ namespace MWMechanics /// @note mCaster must be an actor /// @param launchProjectile If set to false, "on target" effects are directly applied instead of being launched as projectile originating from the caster. - bool cast (const MWWorld::Ptr& item, bool launchProjectile=true); + bool cast (const MWWorld::Ptr& item, int slot, bool launchProjectile=true); /// @note mCaster must be an NPC bool cast (const ESM::Ingredient* ingredient); @@ -60,11 +63,9 @@ namespace MWMechanics /// @note \a caster can be any type of object, or even an empty object. void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); - - /// @note \a caster can be any type of object, or even an empty object. - /// @return was the target suitable for the effect? - bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; + + void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true); } #endif diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp new file mode 100644 index 0000000000..895e908bd2 --- /dev/null +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -0,0 +1,1032 @@ +#include "spelleffects.hpp" + +#include + +#include +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/aifollow.hpp" +#include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellresistance.hpp" +#include "../mwmechanics/summoning.hpp" + +#include "../mwrender/animation.hpp" + +#include "../mwworld/actionequip.hpp" +#include "../mwworld/actionteleport.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" + +namespace +{ + float roll(const ESM::ActiveEffect& effect) + { + if(effect.mMinMagnitude == effect.mMaxMagnitude) + return effect.mMinMagnitude; + return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1); + } + + void modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, ESM::MagicEffect::Effects creatureEffect, MWMechanics::CreatureStats::AiSetting setting, float magnitude, bool& invalid) + { + if(target == MWMechanics::getPlayer() || (effect.mEffectId == creatureEffect) == target.getClass().isNpc()) + invalid = true; + else + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getAiSetting(setting); + stat.setModifier(static_cast(stat.getModifier() - magnitude)); + creatureStats.setAiSetting(setting, stat); + } + } + + void adjustDynamicStat(const MWWorld::Ptr& target, int index, float magnitude, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getDynamic(index); + stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero, allowIncreaseAboveModified); + creatureStats.setDynamic(index, stat); + } + + void modDynamicStat(const MWWorld::Ptr& target, int index, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto stat = creatureStats.getDynamic(index); + float current = stat.getCurrent(); + stat.setModified(stat.getModified() - magnitude, 0); + stat.setCurrentModified(stat.getCurrentModified() - magnitude); + stat.setCurrent(current - magnitude); + creatureStats.setDynamic(index, stat); + } + + void damageAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attr = creatureStats.getAttribute(effect.mArg); + attr.damage(magnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + + void restoreAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attr = creatureStats.getAttribute(effect.mArg); + attr.restore(magnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + + void fortifyAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attr = creatureStats.getAttribute(effect.mArg); + attr.setModifier(attr.getModifier() + magnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + + void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.damage(magnitude); + } + + void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.restore(magnitude); + } + + void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.setModifier(skill.getModifier() + magnitude); + } + + bool disintegrateSlot(const MWWorld::Ptr& ptr, int slot, float disintegrate) + { + MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); + MWWorld::ContainerStoreIterator item = inv.getSlot(slot); + + if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) + { + if (!item->getClass().hasItemHealth(*item)) + return false; + int charge = item->getClass().getItemHealth(*item); + if (charge == 0) + return false; + + // Store remainder of disintegrate amount (automatically subtracted if > 1) + item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); + + charge = item->getClass().getItemHealth(*item); + charge -= std::min(static_cast(disintegrate), charge); + item->getCellRef().setCharge(charge); + + if (charge == 0) + { + // Will unequip the broken item and try to find a replacement + if (ptr != MWMechanics::getPlayer()) + inv.autoEquip(ptr); + else + inv.unequipItem(*item, ptr); + } + + return true; + } + + return false; + } + + int getBoundItemSlot(const MWWorld::Ptr& boundPtr) + { + const auto [slots, _] = boundPtr.getClass().getEquipmentSlots(boundPtr); + if(!slots.empty()) + return slots[0]; + return -1; + } + + void addBoundItem(const std::string& itemId, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor); + + int slot = getBoundItemSlot(boundPtr); + auto prevItem = slot >= 0 ? store.getSlot(slot) : store.end(); + + MWWorld::ActionEquip action(boundPtr); + action.execute(actor); + + if (actor != MWMechanics::getPlayer()) + return; + + MWWorld::Ptr newItem; + auto it = slot >= 0 ? store.getSlot(slot) : store.end(); + // Equip can fail because beast races cannot equip boots/helmets + if(it != store.end()) + newItem = *it; + + if (newItem.isEmpty() || boundPtr != newItem) + return; + + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + + // change draw state only if the item is in player's right hand + if (slot == MWWorld::InventoryStore::Slot_CarriedRight) + player.setDrawState(MWMechanics::DrawState_Weapon); + + if (prevItem != store.end()) + player.setPreviousItem(itemId, prevItem->getCellRef().getRefId()); + } + + void removeBoundItem(const std::string& itemId, const MWWorld::Ptr& actor) + { + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + auto item = std::find_if(store.begin(), store.end(), [&] (const auto& it) + { + return Misc::StringUtils::ciEqual(it.getCellRef().getRefId(), itemId); + }); + if(item == store.end()) + return; + int slot = getBoundItemSlot(*item); + + auto currentItem = store.getSlot(slot); + + bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId); + + if (actor != MWMechanics::getPlayer()) + { + store.remove(itemId, 1, actor); + + // Equip a replacement + if (!wasEquipped) + return; + + std::string type = currentItem->getTypeName(); + if (type != typeid(ESM::Weapon).name() && type != typeid(ESM::Armor).name() && type != typeid(ESM::Clothing).name()) + return; + + if (actor.getClass().getCreatureStats(actor).isDead()) + return; + + if (!actor.getClass().hasInventoryStore(actor)) + return; + + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + return; + + actor.getClass().getInventoryStore(actor).autoEquip(actor); + + return; + } + + MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer(); + std::string prevItemId = player.getPreviousItem(itemId); + player.erasePreviousItem(itemId); + + if (!prevItemId.empty() && wasEquipped) + { + // Find previous item (or its replacement) by id. + // we should equip previous item only if expired bound item was equipped. + MWWorld::Ptr prevItem = store.findReplacement(prevItemId); + if (!prevItem.isEmpty()) + { + MWWorld::ActionEquip action(prevItem); + action.execute(actor); + } + } + + store.remove(itemId, 1, actor); + } + + bool isCorprusEffect(const MWMechanics::ActiveSpells::ActiveEffect& effect, bool harmfulOnly = false) + { + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && effect.mEffectId != ESM::MagicEffect::Corprus) + { + const auto* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectId); + if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce && (!harmfulOnly || magicEffect->mData.mFlags & ESM::MagicEffect::Flags::Harmful)) + return true; + } + return false; + } + + static const std::map sBoundItemsMap{ + {ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"}, + {ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"}, + {ESM::MagicEffect::BoundCuirass, "sMagicBoundCuirassID"}, + {ESM::MagicEffect::BoundDagger, "sMagicBoundDaggerID"}, + {ESM::MagicEffect::BoundGloves, "sMagicBoundLeftGauntletID"}, + {ESM::MagicEffect::BoundHelm, "sMagicBoundHelmID"}, + {ESM::MagicEffect::BoundLongbow, "sMagicBoundLongbowID"}, + {ESM::MagicEffect::BoundLongsword, "sMagicBoundLongswordID"}, + {ESM::MagicEffect::BoundMace, "sMagicBoundMaceID"}, + {ESM::MagicEffect::BoundShield, "sMagicBoundShieldID"}, + {ESM::MagicEffect::BoundSpear, "sMagicBoundSpearID"} + }; +} + +namespace MWMechanics +{ + +void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage) +{ + const auto world = MWBase::Environment::get().getWorld(); + bool godmode = target == getPlayer() && world->getGodModeState(); + switch(effect.mEffectId) + { + case ESM::MagicEffect::CureCommonDisease: + target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); + break; + case ESM::MagicEffect::CureBlightDisease: + target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); + break; + case ESM::MagicEffect::RemoveCurse: + target.getClass().getCreatureStats(target).getSpells().purgeCurses(); + break; + case ESM::MagicEffect::CureCorprusDisease: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Corprus); + break; + case ESM::MagicEffect::CurePoison: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Poison); + break; + case ESM::MagicEffect::CureParalyzation: + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Paralyze); + break; + case ESM::MagicEffect::Dispel: + // Dispel removes entire spells at once + target.getClass().getCreatureStats(target).getActiveSpells().purge([magnitude=effect.mMagnitude] (const ActiveSpells::ActiveSpellParams& params) + { + if(params.getType() == ESM::ActiveSpells::Type_Temporary) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(params.getId()); + if(spell && spell->mData.mType == ESM::Spell::ST_Spell) + return Misc::Rng::roll0to99() < magnitude; + } + return false; + }, target); + break; + case ESM::MagicEffect::AlmsiviIntervention: + case ESM::MagicEffect::DivineIntervention: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + { + auto marker = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; + world->teleportToClosestMarker(target, marker); + if(!caster.isEmpty()) + { + MWRender::Animation* anim = world->getAnimation(caster); + anim->removeEffect(effect.mEffectId); + const ESM::Static* fx = world->getStore().get().search("VFX_Summon_end"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1); + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::Mark: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition()); + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::Recall: + if (target != getPlayer()) + invalid = true; + else if (world->isTeleportingEnabled()) + { + MWWorld::CellStore* markedCell = nullptr; + ESM::Position markedPosition; + + world->getPlayer().getMarkedPosition(markedCell, markedPosition); + if (markedCell) + { + MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false); + action.execute(target); + if(!caster.isEmpty()) + { + MWRender::Animation* anim = world->getAnimation(caster); + anim->removeEffect(effect.mEffectId); + } + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + break; + case ESM::MagicEffect::CommandCreature: + case ESM::MagicEffect::CommandHumanoid: + if(caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc()) + invalid = true; + else if(effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) + { + MWMechanics::AiFollow package(caster, true); + target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); + } + break; + case ESM::MagicEffect::ExtraSpell: + if(target.getClass().hasInventoryStore(target)) + { + auto& store = target.getClass().getInventoryStore(target); + store.unequipAll(target); + } + else + invalid = true; + break; + case ESM::MagicEffect::TurnUndead: + if(target.getClass().isNpc() || target.get()->mBase->mData.mType != ESM::Creature::Undead) + invalid = true; + else + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat.setModifier(static_cast(stat.getModifier() + effect.mMagnitude)); + creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); + } + break; + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::FrenzyHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::CalmHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid); + if(!invalid && effect.mMagnitude > 0) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + creatureStats.getAiSequence().stopCombat(); + } + break; + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, CreatureStats::AI_Flee, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::RallyCreature: + case ESM::MagicEffect::RallyHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, CreatureStats::AI_Flee, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::SummonScamp: + case ESM::MagicEffect::SummonClannfear: + case ESM::MagicEffect::SummonDaedroth: + case ESM::MagicEffect::SummonDremora: + case ESM::MagicEffect::SummonAncestralGhost: + case ESM::MagicEffect::SummonSkeletalMinion: + case ESM::MagicEffect::SummonBonewalker: + case ESM::MagicEffect::SummonGreaterBonewalker: + case ESM::MagicEffect::SummonBonelord: + case ESM::MagicEffect::SummonWingedTwilight: + case ESM::MagicEffect::SummonHunger: + case ESM::MagicEffect::SummonGoldenSaint: + case ESM::MagicEffect::SummonFlameAtronach: + case ESM::MagicEffect::SummonFrostAtronach: + case ESM::MagicEffect::SummonStormAtronach: + case ESM::MagicEffect::SummonCenturionSphere: + case ESM::MagicEffect::SummonFabricant: + case ESM::MagicEffect::SummonWolf: + case ESM::MagicEffect::SummonBear: + case ESM::MagicEffect::SummonBonewolf: + case ESM::MagicEffect::SummonCreature04: + case ESM::MagicEffect::SummonCreature05: + if(!target.isInCell()) + invalid = true; + else + effect.mArg = summonCreature(effect.mEffectId, target); + break; + case ESM::MagicEffect::BoundGloves: + if(!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + addBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); + // left gauntlet added below + case ESM::MagicEffect::BoundDagger: + case ESM::MagicEffect::BoundLongsword: + case ESM::MagicEffect::BoundMace: + case ESM::MagicEffect::BoundBattleAxe: + case ESM::MagicEffect::BoundSpear: + case ESM::MagicEffect::BoundLongbow: + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundHelm: + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundShield: + if(!target.getClass().hasInventoryStore(target)) + invalid = true; + else + { + const std::string& item = sBoundItemsMap.at(effect.mEffectId); + addBoundItem(world->getStore().get().find(item)->mValue.getString(), target); + } + break; + case ESM::MagicEffect::FireDamage: + case ESM::MagicEffect::ShockDamage: + case ESM::MagicEffect::FrostDamage: + case ESM::MagicEffect::DamageHealth: + case ESM::MagicEffect::Poison: + case ESM::MagicEffect::DamageMagicka: + case ESM::MagicEffect::DamageFatigue: + if(!godmode) + { + int index = 0; + if(effect.mEffectId == ESM::MagicEffect::DamageMagicka) + index = 1; + else if(effect.mEffectId == ESM::MagicEffect::DamageFatigue) + index = 2; + // Damage "Dynamic" abilities reduce the base value + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, index, effect.mMagnitude); + else + { + static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); + adjustDynamicStat(target, index, -effect.mMagnitude, index == 2 && uncappedDamageFatigue); + if(index == 0) + receivedMagicDamage = true; + } + } + break; + case ESM::MagicEffect::DamageAttribute: + if(!godmode) + damageAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::DamageSkill: + if(!target.getClass().isNpc()) + invalid = true; + else if(!godmode) + { + // Damage Skill abilities reduce base skill :todd: + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& npcStats = target.getClass().getNpcStats(target); + SkillValue& skill = npcStats.getSkill(effect.mArg); + // Damage Skill abilities reduce base skill :todd: + skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); + } + else + damageSkill(target, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::RestoreAttribute: + restoreAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::RestoreSkill: + if(!target.getClass().isNpc()) + invalid = true; + else + restoreSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::RestoreHealth: + case ESM::MagicEffect::RestoreMagicka: + case ESM::MagicEffect::RestoreFatigue: + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::RestoreHealth, effect.mMagnitude); + break; + case ESM::MagicEffect::SunDamage: + { + // isInCell shouldn't be needed, but updateActor called during game start + if (!target.isInCell() || !target.getCell()->isExterior() || godmode) + break; + float time = world->getTimeStamp().getHour(); + float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); + float damageScale = 1.f - timeDiff / 7.f; + // When cloudy, the sun damage effect is halved + static float fMagicSunBlockedMult = world->getStore().get().find("fMagicSunBlockedMult")->mValue.getFloat(); + + int weather = world->getCurrentWeather(); + if (weather > 1) + damageScale *= fMagicSunBlockedMult; + float damage = effect.mMagnitude * damageScale; + adjustDynamicStat(target, 0, -damage); + if (damage > 0.f) + receivedMagicDamage = true; + } + break; + case ESM::MagicEffect::DrainHealth: + case ESM::MagicEffect::DrainMagicka: + case ESM::MagicEffect::DrainFatigue: + if(!godmode) + { + static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); + int index = effect.mEffectId - ESM::MagicEffect::DrainHealth; + adjustDynamicStat(target, index, -effect.mMagnitude, uncappedDamageFatigue && index == 2); + if(index == 0) + receivedMagicDamage = true; + } + break; + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude); + else + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude, false, true); + break; + case ESM::MagicEffect::DrainAttribute: + if(!godmode) + damageAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyAttribute: + // Abilities affect base stats, but not for drain + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + AttributeValue attr = creatureStats.getAttribute(effect.mArg); + attr.setBase(attr.getBase() + effect.mMagnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + else + fortifyAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::DrainSkill: + if(!target.getClass().isNpc()) + invalid = true; + else if(!godmode) + damageSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifySkill: + if(!target.getClass().isNpc()) + invalid = true; + else if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + // Abilities affect base stats, but not for drain + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.setBase(skill.getBase() + effect.mMagnitude); + } + else + fortifySkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyMaximumMagicka: + target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true); + break; + case ESM::MagicEffect::AbsorbHealth: + case ESM::MagicEffect::AbsorbMagicka: + case ESM::MagicEffect::AbsorbFatigue: + if(!godmode) + { + int index = effect.mEffectId - ESM::MagicEffect::AbsorbHealth; + adjustDynamicStat(target, index, -effect.mMagnitude); + if(!caster.isEmpty()) + adjustDynamicStat(caster, index, effect.mMagnitude); + if(index == 0) + receivedMagicDamage = true; + } + break; + case ESM::MagicEffect::AbsorbAttribute: + if(!godmode) + { + damageAttribute(target, effect, effect.mMagnitude); + if(!caster.isEmpty()) + fortifyAttribute(caster, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::AbsorbSkill: + if(!target.getClass().isNpc()) + invalid = true; + else if(!godmode) + { + damageSkill(target, effect, effect.mMagnitude); + if(!caster.isEmpty()) + fortifySkill(caster, effect, effect.mMagnitude); + } + break; + case ESM::MagicEffect::DisintegrateArmor: + { + if (!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + if (godmode) + break; + static const std::array priorities + { + MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, + MWWorld::InventoryStore::Slot_RightGauntlet, + MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots + }; + for (const int priority : priorities) + { + if (disintegrateSlot(target, priority, effect.mMagnitude)) + break; + } + break; + } + case ESM::MagicEffect::DisintegrateWeapon: + if (!target.getClass().hasInventoryStore(target)) + { + invalid = true; + break; + } + if (!godmode) + disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude); + break; + } +} + +bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) +{ + const auto world = MWBase::Environment::get().getWorld(); + bool invalid = false; + bool receivedMagicDamage = false; + if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen()) + { + spellParams.worsen(); + for(auto& otherEffect : spellParams.getEffects()) + { + if(isCorprusEffect(otherEffect)) + applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage); + } + if(target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); + return false; + } + else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled()) + { + if(target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); + return true; + } + const auto* magicEffect = world->getStore().get().find(effect.mEffectId); + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + { + effect.mTimeLeft -= dt; + return false; + } + if(effect.mEffectId == ESM::MagicEffect::Lock) + { + if(target.getClass().canLock(target)) + { + MWRender::Animation* animation = world->getAnimation(target); + if(animation) + animation->addSpellCastGlow(magicEffect); + int magnitude = static_cast(roll(effect)); + if(target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude + { + if(caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); + target.getCellRef().lock(magnitude); + } + } + else + invalid = true; + } + else if(effect.mEffectId == ESM::MagicEffect::Open) + { + if(target.getClass().canLock(target)) + { + // Use the player instead of the caster for vanilla crime compatibility + MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target); + + MWRender::Animation* animation = world->getAnimation(target); + if(animation) + animation->addSpellCastGlow(magicEffect); + int magnitude = static_cast(roll(effect)); + if(target.getCellRef().getLockLevel() <= magnitude) + { + if(target.getCellRef().getLockLevel() > 0) + { + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); + + if(caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}"); + } + target.getCellRef().unlock(); + } + else + { + MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); + } + } + else + invalid = true; + } + else if(!target.getClass().isActor()) + { + invalid = true; + } + else + { + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & (ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Ignore_Resistances))) + { + const ESM::Spell* spell = nullptr; + if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + spell = world->getStore().get().search(spellParams.getId()); + float magnitudeMult = getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); + if (magnitudeMult == 0) + { + // Fully resisted, show message + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); + return true; + } + effect.mMinMagnitude *= magnitudeMult; + effect.mMaxMagnitude *= magnitudeMult; + } + float oldMagnitude = 0.f; + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) + oldMagnitude = effect.mMagnitude; + //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here + effect.mMagnitude = roll(effect); + if(!(magicEffect->mData.mFlags & (ESM::MagicEffect::Flags::NoMagnitude | ESM::MagicEffect::Flags::AppliedOnce))) + { + if(effect.mDuration != 0) + { + float mult = dt; + if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary) + mult = std::min(effect.mTimeLeft, dt); + effect.mMagnitude *= mult; + } + if(effect.mMagnitude == 0) + { + effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; + effect.mTimeLeft -= dt; + return false; + } + } + if(effect.mEffectId == ESM::MagicEffect::Corprus) + spellParams.worsen(); + else + applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage); + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude)); + } + effect.mTimeLeft -= dt; + if(invalid) + { + effect.mTimeLeft = 0; + effect.mFlags |= ESM::ActiveEffect::Flag_Remove; + auto anim = world->getAnimation(target); + if(anim) + anim->removeEffect(effect.mEffectId); + } + else + effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; + if (receivedMagicDamage && target == getPlayer()) + MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); + return false; +} + +void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) +{ + const auto world = MWBase::Environment::get().getWorld(); + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + bool invalid; + switch(effect.mEffectId) + { + case ESM::MagicEffect::CommandCreature: + case ESM::MagicEffect::CommandHumanoid: + if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) + { + auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); + auto it = std::find_if(seq.begin(), seq.end(), [&](const auto& package) + { + return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast(package.get())->isCommanded(); + }); + if(it != seq.end()) + seq.erase(it); + } + break; + case ESM::MagicEffect::ExtraSpell: + if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f) + target.getClass().getInventoryStore(target).autoEquip(target); + break; + case ESM::MagicEffect::TurnUndead: + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(CreatureStats::AI_Flee); + stat.setModifier(static_cast(stat.getModifier() - effect.mMagnitude)); + creatureStats.setAiSetting(CreatureStats::AI_Flee, stat); + } + break; + case ESM::MagicEffect::FrenzyCreature: + case ESM::MagicEffect::FrenzyHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::CalmCreature: + case ESM::MagicEffect::CalmHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::DemoralizeCreature: + case ESM::MagicEffect::DemoralizeHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, CreatureStats::AI_Flee, -effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::RallyCreature: + case ESM::MagicEffect::RallyHumanoid: + modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, CreatureStats::AI_Flee, effect.mMagnitude, invalid); + break; + case ESM::MagicEffect::SummonScamp: + case ESM::MagicEffect::SummonClannfear: + case ESM::MagicEffect::SummonDaedroth: + case ESM::MagicEffect::SummonDremora: + case ESM::MagicEffect::SummonAncestralGhost: + case ESM::MagicEffect::SummonSkeletalMinion: + case ESM::MagicEffect::SummonBonewalker: + case ESM::MagicEffect::SummonGreaterBonewalker: + case ESM::MagicEffect::SummonBonelord: + case ESM::MagicEffect::SummonWingedTwilight: + case ESM::MagicEffect::SummonHunger: + case ESM::MagicEffect::SummonGoldenSaint: + case ESM::MagicEffect::SummonFlameAtronach: + case ESM::MagicEffect::SummonFrostAtronach: + case ESM::MagicEffect::SummonStormAtronach: + case ESM::MagicEffect::SummonCenturionSphere: + case ESM::MagicEffect::SummonFabricant: + case ESM::MagicEffect::SummonWolf: + case ESM::MagicEffect::SummonBear: + case ESM::MagicEffect::SummonBonewolf: + case ESM::MagicEffect::SummonCreature04: + case ESM::MagicEffect::SummonCreature05: + { + if(effect.mArg != -1) + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, effect.mArg); + auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap(); + auto [begin, end] = summons.equal_range(effect.mEffectId); + for(auto it = begin; it != end; ++it) + { + if(it->second == effect.mArg) + { + summons.erase(it); + break; + } + } + } + break; + case ESM::MagicEffect::BoundGloves: + removeBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); + case ESM::MagicEffect::BoundDagger: + case ESM::MagicEffect::BoundLongsword: + case ESM::MagicEffect::BoundMace: + case ESM::MagicEffect::BoundBattleAxe: + case ESM::MagicEffect::BoundSpear: + case ESM::MagicEffect::BoundLongbow: + case ESM::MagicEffect::BoundCuirass: + case ESM::MagicEffect::BoundHelm: + case ESM::MagicEffect::BoundBoots: + case ESM::MagicEffect::BoundShield: + { + const std::string& item = sBoundItemsMap.at(effect.mEffectId); + removeBoundItem(world->getStore().get().find(item)->mValue.getString(), target); + } + break; + case ESM::MagicEffect::DrainHealth: + case ESM::MagicEffect::DrainMagicka: + case ESM::MagicEffect::DrainFatigue: + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::DrainHealth, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyHealth: + case ESM::MagicEffect::FortifyMagicka: + case ESM::MagicEffect::FortifyFatigue: + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude); + else + adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude, true); + break; + case ESM::MagicEffect::DrainAttribute: + restoreAttribute(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyAttribute: + // Abilities affect base stats, but not for drain + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + AttributeValue attr = creatureStats.getAttribute(effect.mArg); + attr.setBase(attr.getBase() - effect.mMagnitude); + creatureStats.setAttribute(effect.mArg, attr); + } + else + fortifyAttribute(target, effect, -effect.mMagnitude); + break; + case ESM::MagicEffect::DrainSkill: + restoreSkill(target, effect, effect.mMagnitude); + break; + case ESM::MagicEffect::FortifySkill: + // Abilities affect base stats, but not for drain + if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.mArg); + skill.setBase(skill.getBase() - effect.mMagnitude); + } + else + fortifySkill(target, effect, -effect.mMagnitude); + break; + case ESM::MagicEffect::FortifyMaximumMagicka: + target.getClass().getCreatureStats(target).setNeedRecalcDynamicStats(true); + break; + case ESM::MagicEffect::AbsorbAttribute: + { + const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); + restoreAttribute(target, effect, effect.mMagnitude); + if(!caster.isEmpty()) + fortifyAttribute(caster, effect, -effect.mMagnitude); + } + break; + case ESM::MagicEffect::AbsorbSkill: + { + const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId()); + restoreSkill(target, effect, effect.mMagnitude); + if(!caster.isEmpty()) + fortifySkill(caster, effect, -effect.mMagnitude); + } + break; + case ESM::MagicEffect::Corprus: + { + int worsenings = spellParams.getWorsenings(); + spellParams.resetWorsenings(); + if(worsenings > 0) + { + for(const auto& otherEffect : spellParams.getEffects()) + { + if(isCorprusEffect(otherEffect, true)) + { + for(int i = 0; i < worsenings; i++) + removeMagicEffect(target, spellParams, otherEffect); + } + } + } + //Note that we remove the effects, but keep the params + target.getClass().getCreatureStats(target).getActiveSpells().purge([&spellParams] (const ActiveSpells::ActiveSpellParams& params, const auto&) + { + return &spellParams == ¶ms; + }, target); + } + break; + } +} + +void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) +{ + if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) + return; + const auto world = MWBase::Environment::get().getWorld(); + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + const auto* magicEffect = world->getStore().get().find(effect.mEffectId); + if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMinMagnitude)); + removeMagicEffect(target, spellParams, effect); + auto anim = world->getAnimation(target); + if(anim) + anim->removeEffect(effect.mEffectId); +} + +} \ No newline at end of file diff --git a/apps/openmw/mwmechanics/spelleffects.hpp b/apps/openmw/mwmechanics/spelleffects.hpp new file mode 100644 index 0000000000..a9e7b066f3 --- /dev/null +++ b/apps/openmw/mwmechanics/spelleffects.hpp @@ -0,0 +1,20 @@ +#ifndef GAME_MWMECHANICS_SPELLEFFECTS_H +#define GAME_MWMECHANICS_SPELLEFFECTS_H + +#include "activespells.hpp" + +#include "../mwworld/ptr.hpp" + +// These functions should probably be split up into separate Lua functions for each magic effect when magic is dehardcoded. +// That way ESM::MGEF could point to two Lua scripts for each effect. Needs discussion. + +namespace MWMechanics +{ + // Applies a tick of a single effect. Returns true if the effect should be removed immediately + bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt); + + // Undoes permanent effects created by ESM::MagicEffect::AppliedOnce + void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect); +} + +#endif diff --git a/apps/openmw/mwmechanics/spelllist.hpp b/apps/openmw/mwmechanics/spelllist.hpp index 5920949d65..f5759fd7ee 100644 --- a/apps/openmw/mwmechanics/spelllist.hpp +++ b/apps/openmw/mwmechanics/spelllist.hpp @@ -16,12 +16,6 @@ namespace ESM namespace MWMechanics { - struct SpellParams - { - std::map mEffectRands; // - std::set mPurgedEffects; // indices of purged effects - }; - class Spells; /// Multiple instances of the same actor share the same spell list in Morrowind. diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index bd9c5f7cb3..974d297f10 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -31,21 +31,20 @@ namespace // if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted items etc. if (effectFilter == -1) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->getId()); if (!spell || spell->mData.mType != ESM::Spell::ST_Spell) continue; } - const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; - for (std::vector::const_iterator effectIt = params.mEffects.begin(); - effectIt != params.mEffects.end(); ++effectIt) + const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; + for (const auto& effect : params.getEffects()) { - int effectId = effectIt->mEffectId; + int effectId = effect.mEffectId; if (effectFilter != -1 && effectId != effectFilter) continue; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); - if (effectIt->mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway + if (effect.mDuration <= 3) // Don't attempt to dispel if effect runs out shortly anyway continue; if (negative && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) @@ -64,15 +63,14 @@ namespace const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it) { - if (it->first != spellId) + if (it->getId() != spellId) continue; - const MWMechanics::ActiveSpells::ActiveSpellParams& params = it->second; - for (std::vector::const_iterator effectIt = params.mEffects.begin(); - effectIt != params.mEffects.end(); ++effectIt) + const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; + for (const auto& effect : params.getEffects()) { - if (effectIt->mDuration > duration) - duration = effectIt->mDuration; + if (effect.mDuration > duration) + duration = effect.mDuration; } } return duration; diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index b871376003..743eacfe2c 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -20,72 +20,33 @@ namespace MWMechanics { Spells::Spells() - : mSpellsChanged(false) { } Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells), - mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers), - mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects) + mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers) { if(mSpellList) mSpellList->addListener(this); } Spells::Spells(Spells&& spells) : mSpellList(std::move(spells.mSpellList)), mSpells(std::move(spells.mSpells)), - mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers)), - mSpellsChanged(std::move(spells.mSpellsChanged)), mEffects(std::move(spells.mEffects)), - mSourcedEffects(std::move(spells.mSourcedEffects)) + mSelectedSpell(std::move(spells.mSelectedSpell)), mUsedPowers(std::move(spells.mUsedPowers)) { if (mSpellList) mSpellList->updateListener(&spells, this); } - std::map::const_iterator Spells::begin() const + std::vector::const_iterator Spells::begin() const { return mSpells.begin(); } - std::map::const_iterator Spells::end() const + std::vector::const_iterator Spells::end() const { return mSpells.end(); } - void Spells::rebuildEffects() const - { - mEffects = MagicEffects(); - mSourcedEffects.clear(); - - for (const auto& iter : mSpells) - { - const ESM::Spell *spell = iter.first; - - if (spell->mData.mType==ESM::Spell::ST_Ability || spell->mData.mType==ESM::Spell::ST_Blight || - spell->mData.mType==ESM::Spell::ST_Disease || spell->mData.mType==ESM::Spell::ST_Curse) - { - int i=0; - for (const auto& effect : spell->mEffects.mList) - { - if (iter.second.mPurgedEffects.find(i) != iter.second.mPurgedEffects.end()) - { - ++i; - continue; // effect was purged - } - - float random = 1.f; - if (iter.second.mEffectRands.find(i) != iter.second.mEffectRands.end()) - random = iter.second.mEffectRands.at(i); - - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * random; - mEffects.add (effect, magnitude); - mSourcedEffects[spell].add(MWMechanics::EffectKey(effect), magnitude); - - ++i; - } - } - } - } - bool Spells::hasSpell(const std::string &spell) const { return hasSpell(SpellList::getSpell(spell)); @@ -93,7 +54,7 @@ namespace MWMechanics bool Spells::hasSpell(const ESM::Spell *spell) const { - return mSpells.find(spell) != mSpells.end(); + return std::find(mSpells.begin(), mSpells.end(), spell) != mSpells.end(); } void Spells::add (const ESM::Spell* spell) @@ -108,29 +69,8 @@ namespace MWMechanics void Spells::addSpell(const ESM::Spell* spell) { - if (mSpells.find (spell)==mSpells.end()) - { - std::map random; - - // Determine the random magnitudes (unless this is a castable spell, in which case - // they will be determined when the spell is cast) - if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) - { - for (unsigned int i=0; imEffects.mList.size();++i) - { - if (spell->mEffects.mList[i].mMagnMin != spell->mEffects.mList[i].mMagnMax) - { - int delta = spell->mEffects.mList[i].mMagnMax - spell->mEffects.mList[i].mMagnMin; - random[i] = Misc::Rng::rollDice(delta + 1) / static_cast(delta); - } - } - } - - SpellParams params; - params.mEffectRands = random; - mSpells.emplace(spell, params); - mSpellsChanged = true; - } + if (!hasSpell(spell)) + mSpells.emplace_back(spell); } void Spells::remove (const std::string& spellId) @@ -145,27 +85,14 @@ namespace MWMechanics void Spells::removeSpell(const ESM::Spell* spell) { - const auto it = mSpells.find(spell); + const auto it = std::find(mSpells.begin(), mSpells.end(), spell); if(it != mSpells.end()) - { mSpells.erase(it); - mSpellsChanged = true; - } - } - - MagicEffects Spells::getMagicEffects() const - { - if (mSpellsChanged) { - rebuildEffects(); - mSpellsChanged = false; - } - return mEffects; } void Spells::removeAllSpells() { mSpells.clear(); - mSpellsChanged = true; } void Spells::clear(bool modifyBase) @@ -185,26 +112,10 @@ namespace MWMechanics return mSelectedSpell; } - bool Spells::isSpellActive(const std::string &id) const - { - if (id.empty()) - return false; - - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); - if (spell && hasSpell(spell)) - { - auto type = spell->mData.mType; - return (type==ESM::Spell::ST_Ability || type==ESM::Spell::ST_Blight || type==ESM::Spell::ST_Disease || type==ESM::Spell::ST_Curse); - } - - return false; - } - bool Spells::hasDisease(const ESM::Spell::SpellType type) const { - for (const auto& iter : mSpells) + for (const auto spell : mSpells) { - const ESM::Spell *spell = iter.first; if (spell->mData.mType == type) return true; } @@ -227,12 +138,11 @@ namespace MWMechanics std::vector purged; for (auto iter = mSpells.begin(); iter!=mSpells.end();) { - const ESM::Spell *spell = iter->first; + const ESM::Spell *spell = *iter; if (filter(spell)) { mSpells.erase(iter++); purged.push_back(spell->mId); - mSpellsChanged = true; } else ++iter; @@ -261,43 +171,6 @@ namespace MWMechanics purge([](auto spell) { return spell->mData.mType == ESM::Spell::ST_Curse; }); } - void Spells::removeEffects(const std::string &id) - { - if (isSpellActive(id)) - { - for (auto& spell : mSpells) - { - if (spell.first == SpellList::getSpell(id)) - { - for (long unsigned int i = 0; i != spell.first->mEffects.mList.size(); i++) - { - spell.second.mPurgedEffects.insert(i); - } - } - } - - mSpellsChanged = true; - } - } - - void Spells::visitEffectSources(EffectSourceVisitor &visitor) const - { - if (mSpellsChanged) { - rebuildEffects(); - mSpellsChanged = false; - } - - for (const auto& it : mSourcedEffects) - { - const ESM::Spell * spell = it.first; - for (const auto& effectIt : it.second) - { - // FIXME: since Spells merges effects with the same ID, there is no sense to use multiple effects with same ID here - visitor.visit(effectIt.first, -1, spell->mName, spell->mId, -1, effectIt.second.getMagnitude()); - } - } - } - bool Spells::hasCorprusEffect(const ESM::Spell *spell) { for (const auto& effectIt : spell->mEffects.mList) @@ -310,46 +183,6 @@ namespace MWMechanics return false; } - void Spells::purgeEffect(int effectId) - { - for (auto& spellIt : mSpells) - { - int i = 0; - for (auto& effectIt : spellIt.first->mEffects.mList) - { - if (effectIt.mEffectID == effectId) - { - spellIt.second.mPurgedEffects.insert(i); - mSpellsChanged = true; - } - ++i; - } - } - } - - void Spells::purgeEffect(int effectId, const std::string & sourceId) - { - // Effect source may be not a spell - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(sourceId); - if (spell == nullptr) - return; - - auto spellIt = mSpells.find(spell); - if (spellIt == mSpells.end()) - return; - - int index = 0; - for (auto& effectIt : spellIt->first->mEffects.mList) - { - if (effectIt.mEffectID == effectId) - { - spellIt->second.mPurgedEffects.insert(index); - mSpellsChanged = true; - } - ++index; - } - } - bool Spells::canUsePower(const ESM::Spell* spell) const { const auto it = mUsedPowers.find(spell); @@ -365,17 +198,16 @@ namespace MWMechanics { const auto& baseSpells = mSpellList->getSpells(); - for (ESM::SpellState::TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) + for (const std::string& id : state.mSpells) { // Discard spells that are no longer available due to changed content files - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); if (spell) { - mSpells[spell].mEffectRands = it->second.mEffectRands; - mSpells[spell].mPurgedEffects = it->second.mPurgedEffects; + mSpells.emplace_back(spell); - if (it->first == state.mSelectedSpell) - mSelectedSpell = it->first; + if (id == state.mSelectedSpell) + mSelectedSpell = id; } } // Add spells from the base record @@ -394,31 +226,6 @@ namespace MWMechanics mUsedPowers[spell] = MWWorld::TimeStamp(it->second); } - for (std::map::const_iterator it = state.mCorprusSpells.begin(); it != state.mCorprusSpells.end(); ++it) - { - const ESM::Spell * spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); - if (!spell) - continue; - - CorprusStats stats; - - int worsening = state.mCorprusSpells.at(it->first).mWorsenings; - - for (int i=0; imEffects.mList) - { - if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) - stats.mWorsenings[effect.mAttribute] = worsening; - } - stats.mNextWorsening = MWWorld::TimeStamp(state.mCorprusSpells.at(it->first).mNextWorsening); - - creatureStats->addCorprusSpell(it->first, stats); - } - - mSpellsChanged = true; - // Permanent effects are used only to keep the custom magnitude of corprus spells effects (after cure too), and only in old saves. Convert data to the new approach. for (std::map >::const_iterator it = state.mPermanentSpellEffects.begin(); it != state.mPermanentSpellEffects.end(); ++it) @@ -457,16 +264,13 @@ namespace MWMechanics void Spells::writeState(ESM::SpellState &state) const { const auto& baseSpells = mSpellList->getSpells(); - for (const auto& it : mSpells) + for (const auto spell : mSpells) { // Don't save spells and powers stored in the base record - if((it.first->mData.mType != ESM::Spell::ST_Spell && it.first->mData.mType != ESM::Spell::ST_Power) || - std::find(baseSpells.begin(), baseSpells.end(), it.first->mId) == baseSpells.end()) + if((spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power) || + std::find(baseSpells.begin(), baseSpells.end(), spell->mId) == baseSpells.end()) { - ESM::SpellState::SpellParams params; - params.mEffectRands = it.second.mEffectRands; - params.mPurgedEffects = it.second.mPurgedEffects; - state.mSpells.emplace(it.first->mId, params); + state.mSpells.emplace_back(spell->mId); } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index 9737b72cd0..29f505d369 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -29,18 +29,13 @@ namespace MWMechanics class Spells { std::shared_ptr mSpellList; - std::map mSpells; + std::vector mSpells; // Note: this is the spell that's about to be cast, *not* the spell selected in the GUI (which may be different) std::string mSelectedSpell; std::map mUsedPowers; - mutable bool mSpellsChanged; - mutable MagicEffects mEffects; - mutable std::map mSourcedEffects; - void rebuildEffects() const; - bool hasDisease(const ESM::Spell::SpellType type) const; using SpellFilter = bool (*)(const ESM::Spell*); @@ -52,8 +47,6 @@ namespace MWMechanics friend class SpellList; public: - using TIterator = std::map::const_iterator; - Spells(); Spells(const Spells&); @@ -64,9 +57,6 @@ namespace MWMechanics static bool hasCorprusEffect(const ESM::Spell *spell); - void purgeEffect(int effectId); - void purgeEffect(int effectId, const std::string & sourceId); - bool canUsePower (const ESM::Spell* spell) const; void usePower (const ESM::Spell* spell); @@ -75,9 +65,9 @@ namespace MWMechanics void purgeCorprusDisease(); void purgeCurses(); - TIterator begin() const; + std::vector::const_iterator begin() const; - TIterator end() const; + std::vector::const_iterator end() const; bool hasSpell(const std::string& spell) const; bool hasSpell(const ESM::Spell* spell) const; @@ -92,9 +82,6 @@ namespace MWMechanics ///< If the spell to be removed is the selected spell, the selected spell will be changed to /// no spell (empty string). - MagicEffects getMagicEffects() const; - ///< Return sum of magic effects resulting from abilities, blights, deseases and curses. - void clear(bool modifyBase = false); ///< Remove all spells of al types. @@ -104,17 +91,10 @@ namespace MWMechanics const std::string getSelectedSpell() const; ///< May return an empty string. - bool isSpellActive(const std::string& id) const; - ///< Are we under the effects of the given spell ID? - bool hasCommonDisease() const; bool hasBlightDisease() const; - void removeEffects(const std::string& id); - - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - void readState (const ESM::SpellState& state, CreatureStats* creatureStats); void writeState (ESM::SpellState& state) const; diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index d90545fc60..953db077fb 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -60,90 +60,60 @@ namespace MWMechanics return std::string(); } - UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) - : mActor(actor) + int summonCreature(int effectId, const MWWorld::Ptr& summoner) { - } - - void UpdateSummonedCreatures::visit(EffectKey key, int effectIndex, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) - { - if (isSummoningEffect(key.mId) && magnitude > 0) + std::string creatureID = getSummonedCreature(effectId); + int creatureActorId = -1; + if (!creatureID.empty()) { - mActiveEffects.insert(ESM::SummonKey(key.mId, sourceId, effectIndex)); - } - } + try + { + auto world = MWBase::Environment::get().getWorld(); + MWWorld::ManualRef ref(world->getStore(), creatureID, 1); - void UpdateSummonedCreatures::process(bool cleanup) - { - MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - for (std::set::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it) - { - bool found = creatureMap.find(*it) != creatureMap.end(); - if (!found) - { - std::string creatureID = getSummonedCreature(it->mEffectId); - if (!creatureID.empty()) + // Make the summoned creature follow its master and help in fights + AiFollow package(summoner); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + creatureActorId = summonedCreatureStats.getActorId(); + + MWWorld::Ptr placed = world->safePlaceObject(ref.getPtr(), summoner, summoner.getCell(), 0, 120.f); + + MWRender::Animation* anim = world->getAnimation(placed); + if (anim) { - int creatureActorId = -1; - try - { - MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); - - MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - - // Make the summoned creature follow its master and help in fights - AiFollow package(mActor); - summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); - creatureActorId = summonedCreatureStats.getActorId(); - - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(), mActor, mActor.getCell(), 0, 120.f); - - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); - if (anim) - { - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_Start"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1, false); - } - } - catch (std::exception& e) - { - Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what(); - // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log - } - - creatureMap.emplace(*it, creatureActorId); + const ESM::Static* fx = world->getStore().get().search("VFX_Summon_Start"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1, false); } } - } - - // Update summon effects - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) - { - bool found = mActiveEffects.find(it->first) != mActiveEffects.end(); - if (!found) + catch (std::exception& e) { - // Effect has ended - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, it->second); - creatureMap.erase(it++); - continue; + Log(Debug::Error) << "Failed to spawn summoned creature: " << e.what(); + // still insert into creatureMap so we don't try to spawn again every frame, that would spam the warning log } - ++it; + + summoner.getClass().getCreatureStats(summoner).getSummonedCreatureMap().emplace(effectId, creatureActorId); } + return creatureActorId; + } + + void updateSummons(const MWWorld::Ptr& summoner, bool cleanup) + { + MWMechanics::CreatureStats& creatureStats = summoner.getClass().getCreatureStats(summoner); + auto& creatureMap = creatureStats.getSummonedCreatureMap(); std::vector graveyard = creatureStats.getSummonedCreatureGraveyard(); creatureStats.getSummonedCreatureGraveyard().clear(); for (const int creature : graveyard) - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(mActor, creature); + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, creature); if (!cleanup) return; - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + for (auto it = creatureMap.begin(); it != creatureMap.end(); ) { if(it->second == -1) { @@ -155,7 +125,7 @@ namespace MWMechanics if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) { // Purge the magic effect so a new creature can be summoned if desired - purgeSummonEffect(mActor, *it); + purgeSummonEffect(summoner, *it); creatureMap.erase(it++); } else @@ -163,13 +133,13 @@ namespace MWMechanics } } - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) { auto& creatureStats = summoner.getClass().getCreatureStats(summoner); - creatureStats.getActiveSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId, summon.first.mEffectIndex); - creatureStats.getSpells().purgeEffect(summon.first.mEffectId, summon.first.mSourceId); - if (summoner.getClass().hasInventoryStore(summoner)) - summoner.getClass().getInventoryStore(summoner).purgeEffect(summon.first.mEffectId, summon.first.mSourceId, false, summon.first.mEffectIndex); + creatureStats.getActiveSpells().purge([summon] (const auto& spell, const auto& effect) + { + return effect.mEffectId == summon.first && effect.mArg == summon.second; + }, summoner); MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(summoner, summon.second); } diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index 3c3e18a96b..3186eef986 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -11,32 +11,15 @@ namespace MWMechanics { - class CreatureStats; - bool isSummoningEffect(int effectId); std::string getSummonedCreature(int effectId); - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); - - struct UpdateSummonedCreatures : public EffectSourceVisitor - { - UpdateSummonedCreatures(const MWWorld::Ptr& actor); - virtual ~UpdateSummonedCreatures() = default; - - void visit (MWMechanics::EffectKey key, int effectIndex, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) override; - - /// To call after all effect sources have been visited - void process(bool cleanup); - - private: - MWWorld::Ptr mActor; + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); - std::set mActiveEffects; - }; + int summonCreature(int effectId, const MWWorld::Ptr& summoner); + void updateSummons(const MWWorld::Ptr& summoner, bool cleanup); } #endif diff --git a/apps/openmw/mwmechanics/tickableeffects.cpp b/apps/openmw/mwmechanics/tickableeffects.cpp deleted file mode 100644 index 5056179f8f..0000000000 --- a/apps/openmw/mwmechanics/tickableeffects.cpp +++ /dev/null @@ -1,216 +0,0 @@ -#include "tickableeffects.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwbase/world.hpp" - -#include "../mwworld/cellstore.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" -#include "../mwworld/inventorystore.hpp" - -#include "actorutil.hpp" -#include "npcstats.hpp" - -namespace MWMechanics -{ - void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false) - { - DynamicStat stat = creatureStats.getDynamic(index); - stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero); - creatureStats.setDynamic(index, stat); - } - - bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate) - { - if (!ptr.getClass().hasInventoryStore(ptr)) - return false; - - MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); - MWWorld::ContainerStoreIterator item = inv.getSlot(slot); - - if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) - { - if (!item->getClass().hasItemHealth(*item)) - return false; - int charge = item->getClass().getItemHealth(*item); - if (charge == 0) - return false; - - // Store remainder of disintegrate amount (automatically subtracted if > 1) - item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); - - charge = item->getClass().getItemHealth(*item); - charge -= std::min(static_cast(disintegrate), charge); - item->getCellRef().setCharge(charge); - - if (charge == 0) - { - // Will unequip the broken item and try to find a replacement - if (ptr != getPlayer()) - inv.autoEquip(ptr); - else - inv.unequipItem(*item, ptr); - } - - return true; - } - - return false; - } - - bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) - { - if (magnitude == 0.f) - return false; - - bool receivedMagicDamage = false; - bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - switch (effectKey.mId) - { - case ESM::MagicEffect::DamageAttribute: - { - if (godmode) - break; - AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); - attr.damage(magnitude); - creatureStats.setAttribute(effectKey.mArg, attr); - break; - } - case ESM::MagicEffect::RestoreAttribute: - { - AttributeValue attr = creatureStats.getAttribute(effectKey.mArg); - attr.restore(magnitude); - creatureStats.setAttribute(effectKey.mArg, attr); - break; - } - case ESM::MagicEffect::RestoreHealth: - case ESM::MagicEffect::RestoreMagicka: - case ESM::MagicEffect::RestoreFatigue: - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude); - break; - case ESM::MagicEffect::DamageHealth: - if (godmode) - break; - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); - break; - - case ESM::MagicEffect::DamageMagicka: - case ESM::MagicEffect::DamageFatigue: - { - if (godmode) - break; - int index = effectKey.mId-ESM::MagicEffect::DamageHealth; - static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); - adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue); - break; - } - case ESM::MagicEffect::AbsorbHealth: - if (!godmode || magnitude <= 0) - { - if (magnitude > 0.f) - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - } - break; - - case ESM::MagicEffect::AbsorbMagicka: - case ESM::MagicEffect::AbsorbFatigue: - if (!godmode || magnitude <= 0) - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - break; - - case ESM::MagicEffect::DisintegrateArmor: - { - if (godmode) - break; - static const std::array priorities - { - MWWorld::InventoryStore::Slot_CarriedLeft, - MWWorld::InventoryStore::Slot_Cuirass, - MWWorld::InventoryStore::Slot_LeftPauldron, - MWWorld::InventoryStore::Slot_RightPauldron, - MWWorld::InventoryStore::Slot_LeftGauntlet, - MWWorld::InventoryStore::Slot_RightGauntlet, - MWWorld::InventoryStore::Slot_Helmet, - MWWorld::InventoryStore::Slot_Greaves, - MWWorld::InventoryStore::Slot_Boots - }; - for (const int priority : priorities) - { - if (disintegrateSlot(actor, priority, magnitude)) - break; - } - - break; - } - case ESM::MagicEffect::DisintegrateWeapon: - if (!godmode) - disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude); - break; - - case ESM::MagicEffect::SunDamage: - { - // isInCell shouldn't be needed, but updateActor called during game start - if (!actor.isInCell() || !actor.getCell()->isExterior() || godmode) - break; - float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour(); - float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13))); - float damageScale = 1.f - timeDiff / 7.f; - // When cloudy, the sun damage effect is halved - static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fMagicSunBlockedMult")->mValue.getFloat(); - - int weather = MWBase::Environment::get().getWorld()->getCurrentWeather(); - if (weather > 1) - damageScale *= fMagicSunBlockedMult; - - adjustDynamicStat(creatureStats, 0, -magnitude * damageScale); - if (magnitude * damageScale > 0.f) - receivedMagicDamage = true; - - break; - } - - case ESM::MagicEffect::FireDamage: - case ESM::MagicEffect::ShockDamage: - case ESM::MagicEffect::FrostDamage: - case ESM::MagicEffect::Poison: - { - if (godmode) - break; - adjustDynamicStat(creatureStats, 0, -magnitude); - receivedMagicDamage = true; - break; - } - - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::RestoreSkill: - { - if (!actor.getClass().isNpc()) - break; - if (godmode && effectKey.mId == ESM::MagicEffect::DamageSkill) - break; - NpcStats &npcStats = actor.getClass().getNpcStats(actor); - SkillValue& skill = npcStats.getSkill(effectKey.mArg); - if (effectKey.mId == ESM::MagicEffect::RestoreSkill) - skill.restore(magnitude); - else - skill.damage(magnitude); - break; - } - - default: - return false; - } - - if (receivedMagicDamage && actor == getPlayer()) - MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); - return true; - } -} diff --git a/apps/openmw/mwmechanics/tickableeffects.hpp b/apps/openmw/mwmechanics/tickableeffects.hpp deleted file mode 100644 index ccd42ca19b..0000000000 --- a/apps/openmw/mwmechanics/tickableeffects.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef MWMECHANICS_TICKABLEEFFECTS_H -#define MWMECHANICS_TICKABLEEFFECTS_H - -namespace MWWorld -{ - class Ptr; -} - -namespace MWMechanics -{ - class CreatureStats; - struct EffectKey; - - /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed - /// Note: this function works in loop, so magic effects should not be removed here to avoid iterator invalidation. - /// @return Was the effect a tickable effect with a magnitude? - bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude); -} - -#endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 7fc488020c..bcb2d36f5d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1101,34 +1101,6 @@ Resource::ResourceSystem* NpcAnimation::getResourceSystem() return mResourceSystem; } -void NpcAnimation::permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) -{ - // During first auto equip, we don't play any sounds. - // Basically we don't want sounds when the actor is first loaded, - // the items should appear as if they'd always been equipped. - if (isNew) - { - static const std::string schools[] = { - "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" - }; - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(!magicEffect->mHitSound.empty()) - sndMgr->playSound3D(mPtr, magicEffect->mHitSound, 1.0f, 1.0f); - else - sndMgr->playSound3D(mPtr, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); - } - - if (!magicEffect->mHit.empty()) - { - const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; - // Don't play particle VFX unless the effect is new or it should be looping. - if (isNew || loop) - addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); - } -} - void NpcAnimation::enableHeadAnimation(bool enable) { mHeadAnimationTime->setEnabled(enable); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 768ac01622..9f7a186c5e 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -31,7 +31,6 @@ class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWor { public: void equipmentChanged() override; - void permanentEffectAdded(const ESM::MagicEffect *magicEffect, bool isNew) override; public: typedef std::map PartBoneMap; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 149f9f3f3f..3642f68dc2 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -563,13 +563,7 @@ namespace MWScript const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects(); - effects += stats.getActiveSpells().getMagicEffects(); - if (ptr.getClass().hasInventoryStore(ptr) && !stats.isDeathAnimationFinished()) - { - MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); - effects += store.getMagicEffects(); - } + const MWMechanics::MagicEffects& effects = stats.getMagicEffects(); for (const auto& activeEffect : effects) { @@ -821,7 +815,7 @@ namespace MWScript } const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - runtime.push(stats.getActiveSpells().isSpellActive(id) || stats.getSpells().isSpellActive(id)); + runtime.push(stats.getActiveSpells().isSpellActive(id)); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index fe2df68473..ccad186a0d 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -473,6 +473,8 @@ namespace MWScript ESM::Spell::SpellType type = static_cast(spell->mData.mType); if (type != ESM::Spell::ST_Spell && type != ESM::Spell::ST_Power) { + // Add spell effect to *this actor's* queue immediately + creatureStats.getActiveSpells().addSpell(spell, ptr); // Apply looping particles immediately for constant effects MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); } @@ -492,17 +494,6 @@ namespace MWScript runtime.pop(); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); - // The spell may have an instant effect which must be handled before the spell's removal. - for (const auto& effect : creatureStats.getSpells().getMagicEffects()) - { - if (effect.second.getMagnitude() <= 0) - continue; - MWMechanics::CastSpell cast(ptr, ptr); - if (cast.applyInstantEffect(ptr, ptr, effect.first, effect.second.getMagnitude())) - creatureStats.getSpells().purgeEffect(effect.first.mId); - } - - MWBase::Environment::get().getMechanicsManager()->restoreStatsAfterCorprus(ptr, id); creatureStats.getSpells().remove (id); MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager(); @@ -527,8 +518,7 @@ namespace MWScript std::string spellid = runtime.getStringLiteral (runtime[0].mInteger); runtime.pop(); - ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(spellid); - ptr.getClass().getCreatureStats (ptr).getSpells().removeEffects(spellid); + ptr.getClass().getCreatureStats (ptr).getActiveSpells().removeEffects(ptr, spellid); } }; @@ -544,7 +534,7 @@ namespace MWScript Interpreter::Type_Integer effectId = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(effectId); + ptr.getClass().getCreatureStats (ptr).getActiveSpells().purgeEffect(ptr, effectId); } }; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3d82a30c29..b2ac511509 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -26,6 +26,7 @@ #include "../mwmechanics/recharge.hpp" #include "ptr.hpp" +#include "esmloader.hpp" #include "esmstore.hpp" #include "class.hpp" #include "containerstore.hpp" @@ -176,6 +177,13 @@ namespace if (state.mVersion < 15) fixRestocking(record, state); + if (state.mVersion < 17) + { + if constexpr (std::is_same_v) + MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory); + else if constexpr (std::is_same_v) + MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); + } if (state.mRef.mRefNum.hasContentFile()) { diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index b12d646e70..a01128fe36 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -2,6 +2,26 @@ #include "esmstore.hpp" #include +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/magiceffects.hpp" + +namespace +{ + template + void getEnchantedItem(const std::string& id, std::string& enchantment, std::string& itemName) + { + const T* item = MWBase::Environment::get().getWorld()->getStore().get().search(id); + if(item) + { + enchantment = item->mEnchant; + itemName = item->mName; + } + } +} namespace MWWorld { @@ -28,4 +48,187 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index) mStore.load(mEsm[index], &mListener); } + void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats) + { + const auto& store = MWBase::Environment::get().getWorld()->getStore(); + // Convert corprus to format 10 + for (const auto& [id, oldStats] : creatureStats.mSpells.mCorprusSpells) + { + const ESM::Spell* spell = store.get().search(id); + if (!spell) + continue; + + ESM::CreatureStats::CorprusStats stats; + stats.mNextWorsening = oldStats.mNextWorsening; + for (int i=0; imEffects.mList) + { + if (effect.mEffectID == ESM::MagicEffect::DrainAttribute) + stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings; + } + creatureStats.mCorprusSpells[id] = stats; + } + // Convert to format 17 + for(const auto& [id, oldParams] : creatureStats.mSpells.mSpellParams) + { + const ESM::Spell* spell = store.get().search(id); + if (!spell || spell->mData.mType == ESM::Spell::ST_Spell || spell->mData.mType == ESM::Spell::ST_Power) + continue; + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = id; + params.mDisplayName = spell->mName; + params.mItem.unset(); + params.mCasterActorId = creatureStats.mActorId; + if(spell->mData.mType == ESM::Spell::ST_Ability) + params.mType = ESM::ActiveSpells::Type_Ability; + else + params.mType = ESM::ActiveSpells::Type_Permanent; + params.mWorsenings = -1; + int effectIndex = 0; + for(const auto& enam : spell->mEffects.mList) + { + if(oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end()) + { + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effect.mEffectIndex = effectIndex; + auto rand = oldParams.mEffectRands.find(effectIndex); + if(rand != oldParams.mEffectRands.end()) + { + float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin; + effect.mMagnitude = magnitude; + effect.mMinMagnitude = magnitude; + effect.mMaxMagnitude = magnitude; + // Prevent recalculation of resistances + effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances; + } + else + { + effect.mMagnitude = 0.f; + effect.mMinMagnitude = enam.mMagnMin; + effect.mMaxMagnitude = enam.mMagnMax; + effect.mFlags = ESM::ActiveEffect::Flag_None; + } + params.mEffects.emplace_back(effect); + } + effectIndex++; + } + creatureStats.mActiveSpells.mSpells.emplace_back(params); + } + std::multimap equippedItems; + for(std::size_t i = 0; i < inventory.mItems.size(); ++i) + { + const ESM::ObjectState& item = inventory.mItems[i]; + auto slot = inventory.mEquipmentSlots.find(i); + if(slot != inventory.mEquipmentSlots.end()) + equippedItems.emplace(item.mRef.mRefID, slot->second); + } + for(const auto& [id, oldMagnitudes] : inventory.mPermanentMagicEffectMagnitudes) + { + std::string eId; + std::string name; + switch(store.find(id)) + { + case ESM::REC_ARMO: + getEnchantedItem(id, eId, name); + break; + case ESM::REC_CLOT: + getEnchantedItem(id, eId, name); + break; + case ESM::REC_WEAP: + getEnchantedItem(id, eId, name); + break; + } + if(eId.empty()) + continue; + const ESM::Enchantment* enchantment = store.get().search(eId); + if(!enchantment) + continue; + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = id; + params.mDisplayName = name; + params.mCasterActorId = creatureStats.mActorId; + params.mType = ESM::ActiveSpells::Type_Enchantment; + params.mWorsenings = -1; + for(std::size_t effectIndex = 0; effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex) + { + const auto& enam = enchantment->mEffects.mList[effectIndex]; + auto [random, multiplier] = oldMagnitudes[effectIndex]; + float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin; + magnitude *= multiplier; + if(magnitude <= 0) + continue; + ESM::ActiveEffect effect; + effect.mEffectId = enam.mEffectID; + effect.mMagnitude = magnitude; + effect.mMinMagnitude = magnitude; + effect.mMaxMagnitude = magnitude; + effect.mArg = MWMechanics::EffectKey(enam).mArg; + effect.mDuration = -1; + effect.mTimeLeft = -1; + effect.mEffectIndex = static_cast(effectIndex); + // Prevent recalculation of resistances + effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances; + params.mEffects.emplace_back(effect); + } + auto [begin, end] = equippedItems.equal_range(id); + for(auto it = begin; it != end; ++it) + { + params.mItem = { static_cast(it->second), 0 }; + creatureStats.mActiveSpells.mSpells.emplace_back(params); + } + } + for(const auto& spell : creatureStats.mCorprusSpells) + { + auto it = std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(), [&] (const auto& params) { return params.mId == spell.first; }); + if(it != creatureStats.mActiveSpells.mSpells.end()) + { + it->mNextWorsening = spell.second.mNextWorsening; + int worsenings = 0; + for(int i = 0; i < ESM::Attribute::Length; ++i) + worsenings = std::max(spell.second.mWorsenings[i], worsenings); + it->mWorsenings = worsenings; + } + } + for(const auto& [key, actorId] : creatureStats.mSummonedCreatureMap) + { + if(actorId == -1) + continue; + for(auto& params : creatureStats.mActiveSpells.mSpells) + { + if(params.mId == key.mSourceId) + { + bool found = false; + for(auto& effect : params.mEffects) + { + if(effect.mEffectId == key.mEffectId && effect.mEffectIndex == key.mEffectIndex) + { + effect.mArg = actorId; + found = true; + break; + } + } + if(found) + break; + } + } + } + // Reset modifiers that were previously recalculated each frame + for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) + creatureStats.mAttributes[i].mMod = 0.f; + for(std::size_t i = 0; i < 3; ++i) + creatureStats.mDynamic[i].mMod = 0.f; + for(std::size_t i = 0; i < 4; ++i) + creatureStats.mAiSettings[i].mMod = 0.f; + if(npcStats) + { + for(std::size_t i = 0; i < ESM::Skill::Length; ++i) + npcStats->mSkills[i].mMod = 0.f; + } + } } /* namespace MWWorld */ diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index 506105bebb..50631603de 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -13,6 +13,9 @@ namespace ToUTF8 namespace ESM { class ESMReader; + struct CreatureStats; + struct InventoryState; + struct NpcStats; } namespace MWWorld @@ -33,6 +36,8 @@ struct EsmLoader : public ContentLoader ToUTF8::Utf8Encoder* mEncoder; }; +void convertMagicEffects(ESM::CreatureStats& creatureStats, ESM::InventoryState& inventory, ESM::NpcStats* npcStats = nullptr); + } /* namespace MWWorld */ #endif // ESMLOADER_HPP diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 0b8af4463c..49e60af1fd 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -108,11 +108,9 @@ MWWorld::InventoryStore::InventoryStore() MWWorld::InventoryStore::InventoryStore (const InventoryStore& store) : ContainerStore (store) - , mMagicEffects(store.mMagicEffects) , mInventoryListener(store.mInventoryListener) , mUpdatesEnabled(store.mUpdatesEnabled) , mFirstAutoEquip(store.mFirstAutoEquip) - , mPermanentMagicEffectMagnitudes(store.mPermanentMagicEffectMagnitudes) , mSelectedEnchantItem(end()) { copySlots (store); @@ -125,9 +123,7 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStor mListener = store.mListener; mInventoryListener = store.mInventoryListener; - mMagicEffects = store.mMagicEffects; mFirstAutoEquip = store.mFirstAutoEquip; - mPermanentMagicEffectMagnitudes = store.mPermanentMagicEffectMagnitudes; mRechargingItemsUpToDate = false; ContainerStore::operator= (store); mSlots.clear(); @@ -186,8 +182,6 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite flagAsModified(); fireEquipmentChangedEvent(actor); - - updateMagicEffects(actor); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -199,7 +193,6 @@ void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) mUpdatesEnabled = true; fireEquipmentChangedEvent(actor); - updateMagicEffects(actor); } MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) @@ -554,7 +547,6 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { mSlots.swap (slots_); fireEquipmentChangedEvent(actor); - updateMagicEffects(actor); flagAsModified(); } } @@ -567,129 +559,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getPreferredShield(cons return slots[Slot_CarriedLeft]; } -const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() const -{ - return mMagicEffects; -} - -void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) -{ - // To avoid excessive updates during auto-equip - if (!mUpdatesEnabled) - return; - - // Delay update until the listener is set up - if (!mInventoryListener) - return; - - mMagicEffects = MWMechanics::MagicEffects(); - - const auto& stats = actor.getClass().getCreatureStats(actor); - if (stats.isDead() && stats.isDeathAnimationFinished()) - return; - - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter==end()) - continue; - - std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); - - if (!enchantmentId.empty()) - { - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - std::vector params; - - bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) != mPermanentMagicEffectMagnitudes.end()); - if (!existed) - { - params.resize(enchantment.mEffects.mList.size()); - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - int delta = effect.mMagnMax - effect.mMagnMin; - // Roll some dice, one for each effect - if (delta) - params[i].mRandom = Misc::Rng::rollDice(delta + 1) / static_cast(delta); - // Try resisting each effect - params[i].mMultiplier = MWMechanics::getEffectMultiplier(effect.mEffectID, actor, actor); - ++i; - } - - // Note that using the RefID as a key here is not entirely correct. - // Consider equipping the same item twice (e.g. a ring) - // However, permanent enchantments with a random magnitude are kind of an exploit anyway, - // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. - mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()] = params; - } - else - params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()]; - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effect.mEffectID); - - // Fully resisted or can't be applied to target? - if (params[i].mMultiplier == 0 || !MWMechanics::checkEffectTarget(effect.mEffectID, actor, actor, actor == MWMechanics::getPlayer())) - { - i++; - continue; - } - - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params[i].mRandom; - magnitude *= params[i].mMultiplier; - - if (!existed) - { - // During first auto equip, we don't play any sounds. - // Basically we don't want sounds when the actor is first loaded, - // the items should appear as if they'd always been equipped. - mInventoryListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip); - } - - if (magnitude) - mMagicEffects.add (effect, magnitude); - - i++; - } - } - } - - // Now drop expired effects - for (TEffectMagnitudes::iterator it = mPermanentMagicEffectMagnitudes.begin(); - it != mPermanentMagicEffectMagnitudes.end();) - { - bool found = false; - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter == end()) - continue; - if ((**iter).getCellRef().getRefId() == it->first) - { - found = true; - } - } - if (!found) - mPermanentMagicEffectMagnitudes.erase(it++); - else - ++it; - } - - // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping - MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); - - mFirstAutoEquip = false; -} - bool MWWorld::InventoryStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) const { bool canStack = MWWorld::ContainerStore::stacks(ptr1, ptr2); @@ -800,7 +669,6 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c if (applyUpdates) { fireEquipmentChangedEvent(actor); - updateMagicEffects(actor); } return retval; @@ -848,7 +716,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con return unstack(item, actor, item.getRefData().getCount() - count); } -MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() +MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const { return mInventoryListener; } @@ -856,7 +724,6 @@ MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() void MWWorld::InventoryStore::setInvListener(InventoryStoreListener *listener, const Ptr& actor) { mInventoryListener = listener; - updateMagicEffects(actor); } void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) @@ -875,105 +742,6 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent(const Ptr& actor) */ } -void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) -{ - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter==end()) - continue; - - std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); - if (enchantmentId.empty()) - continue; - - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().getRefId()) == mPermanentMagicEffectMagnitudes.end()) - continue; - - int i=0; - for (const ESM::ENAMstruct& effect : enchantment.mEffects.mList) - { - i++; - // Don't get spell icon display information for enchantments that weren't actually applied - if (mMagicEffects.get(MWMechanics::EffectKey(effect)).getMagnitude() == 0) - continue; - const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i-1]; - float magnitude = effect.mMagnMin + (effect.mMagnMax - effect.mMagnMin) * params.mRandom; - magnitude *= params.mMultiplier; - if (magnitude > 0) - visitor.visit(MWMechanics::EffectKey(effect), i-1, (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude); - } - } -} - -void MWWorld::InventoryStore::purgeEffect(short effectId, bool wholeSpell) -{ - for (TSlots::const_iterator it = mSlots.begin(); it != mSlots.end(); ++it) - { - if (*it != end()) - purgeEffect(effectId, (*it)->getCellRef().getRefId(), wholeSpell); - } -} - -void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId, bool wholeSpell, int effectIndex) -{ - TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId); - if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end()) - return; - - for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) - { - if (*iter==end()) - continue; - - if ((*iter)->getCellRef().getRefId() != sourceId) - continue; - - std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); - - if (!enchantmentId.empty()) - { - const ESM::Enchantment& enchantment = - *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); - - if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) - continue; - - std::vector& params = effectMagnitudeIt->second; - - int i=0; - for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) - { - if (effectIt->mEffectID != effectId) - continue; - - if (effectIndex >= 0 && effectIndex != i) - continue; - - if (wholeSpell) - { - mPermanentMagicEffectMagnitudes.erase(sourceId); - return; - } - - float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; - magnitude *= params[i].mMultiplier; - - if (magnitude) - mMagicEffects.add (*effectIt, -magnitude); - - params[i].mMultiplier = 0; - } - } - } -} - void MWWorld::InventoryStore::clear() { mSlots.clear(); @@ -991,38 +759,9 @@ bool MWWorld::InventoryStore::isEquipped(const MWWorld::ConstPtr &item) return false; } -void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) const -{ - MWWorld::ContainerStore::writeState(state); - - for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) - { - std::vector > params; - for (std::vector::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) - { - params.emplace_back(pIt->mRandom, pIt->mMultiplier); - } - - state.mPermanentMagicEffectMagnitudes[it->first] = params; - } -} - -void MWWorld::InventoryStore::readState(const ESM::InventoryState &state) +bool MWWorld::InventoryStore::isFirstEquip() { - MWWorld::ContainerStore::readState(state); - - for (ESM::InventoryState::TEffectMagnitudes::const_iterator it = state.mPermanentMagicEffectMagnitudes.begin(); - it != state.mPermanentMagicEffectMagnitudes.end(); ++it) - { - std::vector params; - for (std::vector >::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) - { - EffectParams p; - p.mRandom = pIt->first; - p.mMultiplier = pIt->second; - params.push_back(p); - } - - mPermanentMagicEffectMagnitudes[it->first] = params; - } + bool first = mFirstAutoEquip; + mFirstAutoEquip = false; + return first; } diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 32dc0d2e91..01c53d7028 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -25,15 +25,6 @@ namespace MWWorld */ virtual void equipmentChanged () {} - /** - * @param effect - * @param isNew Is this effect new (e.g. the item for it was just now manually equipped) - * or was it loaded from a savegame / initial game state? \n - * If it isn't new, non-looping VFX should not be played. - * @param playSound Play effect sound? - */ - virtual void permanentEffectAdded (const ESM::MagicEffect *magicEffect, bool isNew) {} - virtual ~InventoryStoreListener() = default; }; @@ -68,8 +59,6 @@ namespace MWWorld private: - MWMechanics::MagicEffects mMagicEffects; - InventoryStoreListener* mInventoryListener; // Enables updates of magic effects and actor model whenever items are equipped or unequipped. @@ -78,19 +67,6 @@ namespace MWWorld bool mFirstAutoEquip; - // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. - // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. - struct EffectParams - { - // Modifier to scale between min and max magnitude - float mRandom; - // Multiplier for when an effect was fully or partially resisted - float mMultiplier; - }; - - typedef std::map > TEffectMagnitudes; - TEffectMagnitudes mPermanentMagicEffectMagnitudes; - typedef std::vector TSlots; TSlots mSlots; @@ -106,8 +82,6 @@ namespace MWWorld void initSlots (TSlots& slots_); - void updateMagicEffects(const Ptr& actor); - void fireEquipmentChangedEvent(const Ptr& actor); void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; @@ -161,9 +135,6 @@ namespace MWWorld void autoEquip (const MWWorld::Ptr& actor); ///< Auto equip items according to stats and item value. - const MWMechanics::MagicEffects& getMagicEffects() const; - ///< Return magic effects from worn items. - bool stacks (const ConstPtr& ptr1, const ConstPtr& ptr2) const override; ///< @return true if the two specified objects can stack with each other @@ -198,22 +169,12 @@ namespace MWWorld void setInvListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener - InventoryStoreListener* getInvListener(); - - void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); - - void purgeEffect (short effectId, bool wholeSpell = false); - ///< Remove a magic effect - - void purgeEffect (short effectId, const std::string& sourceId, bool wholeSpell = false, int effectIndex=-1); - ///< Remove a magic effect + InventoryStoreListener* getInvListener() const; void clear() override; ///< Empty container. - void writeState (ESM::InventoryState& state) const override; - - void readState (const ESM::InventoryState& state) override; + bool isFirstEquip(); }; } diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index d8e2fb2f0d..caa0600f7c 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -22,9 +22,10 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellutil.hpp" +#include "cellstore.hpp" #include "class.hpp" +#include "esmloader.hpp" #include "ptr.hpp" -#include "cellstore.hpp" namespace MWWorld { @@ -381,6 +382,14 @@ namespace MWWorld // this is the one object we can not silently drop. throw std::runtime_error ("invalid player state record (object state)"); } + if (reader.getFormat() < 17) + { + convertMagicEffects(player.mObject.mCreatureStats, player.mObject.mInventory, &player.mObject.mNpcStats); + for(std::size_t i = 0; i < ESM::Attribute::Length; ++i) + player.mSaveAttributes[i].mMod = 0.f; + for(std::size_t i = 0; i < ESM::Skill::Length; ++i) + player.mSaveSkills[i].mMod = 0.f; + } if (!player.mObject.mEnabled) { diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 25e7f0c7de..3b7be64ad1 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -256,7 +256,7 @@ namespace MWWorld state.mEffectAnimationTime->addTime(duration); } - void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection) + void ProjectileManager::launchMagicBolt(const std::string &spellId, const Ptr &caster, const osg::Vec3f& fallbackDirection, int slot) { osg::Vec3f pos = caster.getRefData().getPosition().asVec3(); if (caster.getClass().isActor()) @@ -278,6 +278,7 @@ namespace MWWorld MagicBoltState state; state.mSpellId = spellId; state.mCasterHandle = caster; + state.mSlot = slot; if (caster.getClass().isActor()) state.mActorId = caster.getClass().getCreatureStats(caster).getActorId(); else @@ -545,10 +546,10 @@ namespace MWWorld cast.mHitPosition = pos; cast.mId = magicBoltState.mSpellId; cast.mSourceName = magicBoltState.mSourceName; - cast.mStack = false; + cast.mSlot = magicBoltState.mSlot; cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); - MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName, false, magicBoltState.mSlot); magicBoltState.mToDelete = true; } @@ -628,7 +629,7 @@ namespace MWWorld state.mPosition = ESM::Vector3(osg::Vec3f(it->mNode->getPosition())); state.mOrientation = ESM::Quaternion(osg::Quat(it->mNode->getAttitude())); state.mActorId = it->mActorId; - + state.mSlot = it->mSlot; state.mSpellId = it->mSpellId; state.mSpeed = it->mSpeed; @@ -684,6 +685,7 @@ namespace MWWorld state.mSpellId = esm.mSpellId; state.mActorId = esm.mActorId; state.mToDelete = false; + state.mSlot = esm.mSlot; std::string texture; try diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 4dc250dc5f..f889250e1d 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -49,7 +49,7 @@ namespace MWWorld MWRender::RenderingManager* rendering, MWPhysics::PhysicsSystem* physics); /// If caster is an actor, the actor's facing orientation is used. Otherwise fallbackDirection is used. - void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection); + void launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot); void launchProjectile (const MWWorld::Ptr& actor, const MWWorld::ConstPtr& projectile, const osg::Vec3f& pos, const osg::Quat& orient, const MWWorld::Ptr& bow, float speed, float attackStrength); @@ -107,6 +107,7 @@ namespace MWWorld ESM::EffectList mEffects; float mSpeed; + int mSlot; std::vector mSounds; std::set mSoundIds; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index da5daed295..ea455a31cf 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1862,7 +1862,7 @@ namespace MWWorld mRendering->getCamera()->setSneakOffset(0.f); int blind = 0; - auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects(); + const auto& magicEffects = player.getClass().getCreatureStats(player).getMagicEffects(); if (!mGodMode) blind = static_cast(magicEffects.get(ESM::MagicEffect::Blind).getMagnitude()); MWBase::Environment::get().getWindowManager()->setBlindness(std::max(0, std::min(100, blind))); @@ -3109,7 +3109,20 @@ namespace MWWorld { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); if (inv.getSelectedEnchantItem() != inv.end()) - cast.cast(*inv.getSelectedEnchantItem()); + { + const auto& itemPtr = *inv.getSelectedEnchantItem(); + auto [slots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); + int slot = 0; + for(std::size_t i = 0; i < slots.size(); ++i) + { + if(inv.getSlot(slots[i]) == inv.getSelectedEnchantItem()) + { + slot = slots[i]; + break; + } + } + cast.cast(itemPtr, slot); + } } } @@ -3144,9 +3157,9 @@ namespace MWWorld mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed, attackStrength); } - void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) + void World::launchMagicBolt (const std::string &spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) { - mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); + mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection, slot); } void World::updateProjectilesCasters() @@ -3154,46 +3167,24 @@ namespace MWWorld mProjectileManager->updateCasters(); } - class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor - { - private: - MWWorld::Ptr mActor; - - public: - ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor) - : mActor(actor) - { - } - - void visit (MWMechanics::EffectKey key, int /*effectIndex*/, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1) override - { - const ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); - const auto magicEffect = store.get().find(key.mId); - if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0) - return; - const ESM::Static* castStatic; - if (!magicEffect->mHit.empty()) - castStatic = store.get().find (magicEffect->mHit); - else - castStatic = store.get().find ("VFX_DefaultHit"); - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor); - if (anim && !castStatic->mModel.empty()) - anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle); - } - }; - void World::applyLoopingParticles(const MWWorld::Ptr& ptr) { const MWWorld::Class &cls = ptr.getClass(); if (cls.isActor()) { - ApplyLoopingParticlesVisitor visitor(ptr); - cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor); - cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor); - if (cls.hasInventoryStore(ptr)) - cls.getInventoryStore(ptr).visitEffectSources(visitor); + std::set playing; + for(const auto& params : cls.getCreatureStats(ptr).getActiveSpells()) + { + for(const auto& effect : params.getEffects()) + { + if(playing.insert(effect.mEffectId).second) + { + const auto magicEffect = getStore().get().find(effect.mEffectId); + if(magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) + MWMechanics::playEffects(ptr, *magicEffect, false); + } + } + } } } @@ -3204,10 +3195,7 @@ namespace MWWorld void World::breakInvisibility(const Ptr &actor) { - actor.getClass().getCreatureStats(actor).getSpells().purgeEffect(ESM::MagicEffect::Invisibility); - actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Invisibility); - if (actor.getClass().hasInventoryStore(actor)) - actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Invisibility); + actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(actor, ESM::MagicEffect::Invisibility); // Normally updated once per frame, but here it is kinda important to do it right away. MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); @@ -3700,7 +3688,7 @@ namespace MWWorld } void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType, - const std::string& id, const std::string& sourceName, const bool fromProjectile) + const std::string& id, const std::string& sourceName, const bool fromProjectile, int slot) { std::map > toApply; for (const ESM::ENAMstruct& effectInfo : effects.mList) @@ -3778,7 +3766,7 @@ namespace MWWorld cast.mHitPosition = origin; cast.mId = id; cast.mSourceName = sourceName; - cast.mStack = false; + cast.mSlot = slot; ESM::EffectList effectsToApply; effectsToApply.mList = applyPair.second; cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 32fc9b101a..9ead6926c2 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -636,7 +636,7 @@ namespace MWWorld */ void castSpell (const MWWorld::Ptr& actor, bool manualSpell=false) override; - void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection) override; + void launchMagicBolt (const std::string& spellId, const MWWorld::Ptr& caster, const osg::Vec3f& fallbackDirection, int slot) override; void launchProjectile (MWWorld::Ptr& actor, MWWorld::Ptr& projectile, const osg::Vec3f& worldPos, const osg::Quat& orient, MWWorld::Ptr& bow, float speed, float attackStrength) override; void updateProjectilesCasters() override; @@ -681,7 +681,7 @@ namespace MWWorld void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id, const std::string& sourceName, - const bool fromProjectile=false) override; + const bool fromProjectile=false, int slot = 0) override; void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override; diff --git a/components/esm/activespells.cpp b/components/esm/activespells.cpp index 4017a4933e..22f862b6e4 100644 --- a/components/esm/activespells.cpp +++ b/components/esm/activespells.cpp @@ -3,44 +3,67 @@ #include "esmreader.hpp" #include "esmwriter.hpp" -namespace ESM +namespace { - - void ActiveSpells::save(ESMWriter &esm) const + void save(ESM::ESMWriter& esm, const std::vector& spells, const std::string& tag) { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) + for (const auto& params : spells) { - esm.writeHNString ("ID__", it->first); - - const ActiveSpellParams& params = it->second; + esm.writeHNString (tag, params.mId); esm.writeHNT ("CAST", params.mCasterActorId); esm.writeHNString ("DISP", params.mDisplayName); + esm.writeHNT ("TYPE", params.mType); + if(params.mItem.isSet()) + params.mItem.save(esm, true, "ITEM"); + if(params.mWorsenings >= 0) + { + esm.writeHNT ("WORS", params.mWorsenings); + esm.writeHNT ("TIME", params.mNextWorsening); + } - for (std::vector::const_iterator effectIt = params.mEffects.begin(); effectIt != params.mEffects.end(); ++effectIt) + for (auto effect : params.mEffects) { - esm.writeHNT ("MGEF", effectIt->mEffectId); - if (effectIt->mArg != -1) - esm.writeHNT ("ARG_", effectIt->mArg); - esm.writeHNT ("MAGN", effectIt->mMagnitude); - esm.writeHNT ("DURA", effectIt->mDuration); - esm.writeHNT ("EIND", effectIt->mEffectIndex); - esm.writeHNT ("LEFT", effectIt->mTimeLeft); + esm.writeHNT ("MGEF", effect.mEffectId); + if (effect.mArg != -1) + esm.writeHNT ("ARG_", effect.mArg); + esm.writeHNT ("MAGN", effect.mMagnitude); + esm.writeHNT ("MAGN", effect.mMinMagnitude); + esm.writeHNT ("MAGN", effect.mMaxMagnitude); + esm.writeHNT ("DURA", effect.mDuration); + esm.writeHNT ("EIND", effect.mEffectIndex); + esm.writeHNT ("LEFT", effect.mTimeLeft); + esm.writeHNT ("FLAG", effect.mFlags); } } } - void ActiveSpells::load(ESMReader &esm) + void load(ESM::ESMReader& esm, std::vector& spells, const char* tag) { int format = esm.getFormat(); - while (esm.isNextSub("ID__")) + while (esm.isNextSub(tag)) { - std::string spellId = esm.getHString(); - - ActiveSpellParams params; + ESM::ActiveSpells::ActiveSpellParams params; + params.mId = esm.getHString(); esm.getHNT (params.mCasterActorId, "CAST"); params.mDisplayName = esm.getHNString ("DISP"); + params.mItem.unset(); + if (format < 17) + params.mType = ESM::ActiveSpells::Type_Temporary; + else + { + esm.getHNT (params.mType, "TYPE"); + if(esm.peekNextSub("ITEM")) + params.mItem.load(esm, true, "ITEM"); + } + if(esm.isNextSub("WORS")) + { + esm.getHT(params.mWorsenings); + esm.getHNT(params.mNextWorsening, "TIME"); + } + else + params.mWorsenings = -1; // spell casting timestamp, no longer used if (esm.isNextSub("TIME")) @@ -48,11 +71,21 @@ namespace ESM while (esm.isNextSub("MGEF")) { - ActiveEffect effect; + ESM::ActiveEffect effect; esm.getHT(effect.mEffectId); effect.mArg = -1; esm.getHNOT(effect.mArg, "ARG_"); esm.getHNT (effect.mMagnitude, "MAGN"); + if (format < 17) + { + effect.mMinMagnitude = effect.mMagnitude; + effect.mMaxMagnitude = effect.mMagnitude; + } + else + { + esm.getHNT (effect.mMinMagnitude, "MAGN"); + esm.getHNT (effect.mMaxMagnitude, "MAGN"); + } esm.getHNT (effect.mDuration, "DURA"); effect.mEffectIndex = -1; esm.getHNOT (effect.mEffectIndex, "EIND"); @@ -60,10 +93,30 @@ namespace ESM effect.mTimeLeft = effect.mDuration; else esm.getHNT (effect.mTimeLeft, "LEFT"); + if (format < 17) + effect.mFlags = ESM::ActiveEffect::Flag_None; + else + esm.getHNT (effect.mFlags, "FLAG"); params.mEffects.push_back(effect); } - mSpells.insert(std::make_pair(spellId, params)); + spells.emplace_back(params); } } } + +namespace ESM +{ + + void ActiveSpells::save(ESMWriter &esm) const + { + ::save(esm, mSpells, "ID__"); + ::save(esm, mQueue, "QID_"); + } + + void ActiveSpells::load(ESMReader &esm) + { + ::load(esm, mSpells, "ID__"); + ::load(esm, mQueue, "QID_"); + } +} diff --git a/components/esm/activespells.hpp b/components/esm/activespells.hpp index 1b7f8b319c..8b5f1f1946 100644 --- a/components/esm/activespells.hpp +++ b/components/esm/activespells.hpp @@ -1,11 +1,12 @@ #ifndef OPENMW_ESM_ACTIVESPELLS_H #define OPENMW_ESM_ACTIVESPELLS_H -#include "effectlist.hpp" +#include "cellref.hpp" #include "defs.hpp" +#include "effectlist.hpp" #include -#include +#include namespace ESM { @@ -14,29 +15,53 @@ namespace ESM // Parameters of an effect concerning lasting effects. // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc. - // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster. struct ActiveEffect { + enum Flags + { + Flag_None = 0, + Flag_Applied = 1 << 0, + Flag_Remove = 1 << 1, + Flag_Ignore_Resistances = 1 << 2 + }; + int mEffectId; float mMagnitude; + float mMinMagnitude; + float mMaxMagnitude; int mArg; // skill or attribute float mDuration; float mTimeLeft; int mEffectIndex; + int mFlags; }; // format 0, saved games only struct ActiveSpells { + enum EffectType + { + Type_Temporary, + Type_Ability, + Type_Enchantment, + Type_Permanent, + Type_Consumable + }; + struct ActiveSpellParams { + std::string mId; std::vector mEffects; std::string mDisplayName; int mCasterActorId; + RefNum mItem; + EffectType mType; + int mWorsenings; + TimeStamp mNextWorsening; }; - typedef std::multimap TContainer; - TContainer mSpells; + std::vector mSpells; + std::vector mQueue; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index cb383992c6..d5030a6580 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -110,16 +110,31 @@ void ESM::CreatureStats::load (ESMReader &esm) mAiSequence.load(esm); mMagicEffects.load(esm); - while (esm.isNextSub("SUMM")) + if (esm.getFormat() < 17) { - int magicEffect; - esm.getHT(magicEffect); - std::string source = esm.getHNOString("SOUR"); - int effectIndex = -1; - esm.getHNOT (effectIndex, "EIND"); - int actorId; - esm.getHNT (actorId, "ACID"); - mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId; + while (esm.isNextSub("SUMM")) + { + int magicEffect; + esm.getHT(magicEffect); + std::string source = esm.getHNOString("SOUR"); + int effectIndex = -1; + esm.getHNOT (effectIndex, "EIND"); + int actorId; + esm.getHNT (actorId, "ACID"); + mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId; + mSummonedCreatures.emplace(magicEffect, actorId); + } + } + else + { + while (esm.isNextSub("SUMM")) + { + int magicEffect; + esm.getHT(magicEffect); + int actorId; + esm.getHNT (actorId, "ACID"); + mSummonedCreatures.emplace(magicEffect, actorId); + } } while (esm.isNextSub("GRAV")) @@ -214,14 +229,10 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mAiSequence.save(esm); mMagicEffects.save(esm); - for (const auto& summon : mSummonedCreatureMap) + for (const auto& [effectId, actorId] : mSummonedCreatures) { - esm.writeHNT ("SUMM", summon.first.mEffectId); - esm.writeHNString ("SOUR", summon.first.mSourceId); - int effectIndex = summon.first.mEffectIndex; - if (effectIndex != -1) - esm.writeHNT ("EIND", effectIndex); - esm.writeHNT ("ACID", summon.second); + esm.writeHNT ("SUMM", effectId); + esm.writeHNT ("ACID", actorId); } for (int key : mSummonGraveyard) @@ -235,15 +246,6 @@ void ESM::CreatureStats::save (ESMWriter &esm) const for (int i=0; i<4; ++i) mAiSettings[i].save(esm); } - - for (const auto& corprusSpell : mCorprusSpells) - { - esm.writeHNString("CORP", corprusSpell.first); - - const CorprusStats & stats = corprusSpell.second; - esm.writeHNT("WORS", stats.mWorsenings); - esm.writeHNT("TIME", stats.mNextWorsening); - } } void ESM::CreatureStats::blank() diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 13bc50008c..651b126d0e 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -40,6 +40,7 @@ namespace ESM StatState mAiSettings[4]; std::map mSummonedCreatureMap; + std::multimap mSummonedCreatures; std::vector mSummonGraveyard; ESM::TimeStamp mTradeTime; diff --git a/components/esm/magiceffects.cpp b/components/esm/magiceffects.cpp index 898e7e4b18..a1f943a93d 100644 --- a/components/esm/magiceffects.cpp +++ b/components/esm/magiceffects.cpp @@ -8,10 +8,11 @@ namespace ESM void MagicEffects::save(ESMWriter &esm) const { - for (std::map::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + for (const auto& [key, params] : mEffects) { - esm.writeHNT("EFID", it->first); - esm.writeHNT("BASE", it->second); + esm.writeHNT("EFID", key); + esm.writeHNT("BASE", params.first); + esm.writeHNT("MODI", params.second); } } @@ -19,10 +20,15 @@ void MagicEffects::load(ESMReader &esm) { while (esm.isNextSub("EFID")) { - int id, base; + int id; + std::pair params; esm.getHT(id); - esm.getHNT(base, "BASE"); - mEffects.insert(std::make_pair(id, base)); + esm.getHNT(params.first, "BASE"); + if(esm.getFormat() < 17) + params.second = 0.f; + else + esm.getHNT(params.second, "MODI"); + mEffects.emplace(id, params); } } diff --git a/components/esm/magiceffects.hpp b/components/esm/magiceffects.hpp index 5b8b0c924a..4b54692c5f 100644 --- a/components/esm/magiceffects.hpp +++ b/components/esm/magiceffects.hpp @@ -12,8 +12,8 @@ namespace ESM // format 0, saved games only struct MagicEffects { - // - std::map mEffects; + // + std::map> mEffects; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/projectilestate.cpp b/components/esm/projectilestate.cpp index 8ade9d5b2e..3421c19526 100644 --- a/components/esm/projectilestate.cpp +++ b/components/esm/projectilestate.cpp @@ -28,6 +28,7 @@ namespace ESM esm.writeHNString ("SPEL", mSpellId); esm.writeHNT ("SPED", mSpeed); + esm.writeHNT ("SLOT", mSlot); } void MagicBoltState::load(ESMReader &esm) @@ -39,6 +40,10 @@ namespace ESM esm.skipHSub(); ESM::EffectList().load(esm); // for backwards compatibility esm.getHNT (mSpeed, "SPED"); + if(esm.getFormat() < 17) + mSlot = 0; + else + esm.getHNT(mSlot, "SLOT"); if (esm.isNextSub("STCK")) // for backwards compatibility esm.skipHSub(); if (esm.isNextSub("SOUN")) // for backwards compatibility diff --git a/components/esm/projectilestate.hpp b/components/esm/projectilestate.hpp index 67ec89bb6d..84292813ce 100644 --- a/components/esm/projectilestate.hpp +++ b/components/esm/projectilestate.hpp @@ -32,6 +32,7 @@ namespace ESM { std::string mSpellId; float mSpeed; + int mSlot; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/savedgame.cpp b/components/esm/savedgame.cpp index 3f8bf10c56..8a98a63419 100644 --- a/components/esm/savedgame.cpp +++ b/components/esm/savedgame.cpp @@ -4,7 +4,7 @@ #include "esmwriter.hpp" unsigned int ESM::SavedGame::sRecordId = ESM::REC_SAVE; -int ESM::SavedGame::sCurrentFormat = 16; +int ESM::SavedGame::sCurrentFormat = 17; void ESM::SavedGame::load (ESMReader &esm) { diff --git a/components/esm/spellstate.cpp b/components/esm/spellstate.cpp index 2eb1e78679..b1ddb6523c 100644 --- a/components/esm/spellstate.cpp +++ b/components/esm/spellstate.cpp @@ -8,29 +8,38 @@ namespace ESM void SpellState::load(ESMReader &esm) { - while (esm.isNextSub("SPEL")) + if(esm.getFormat() < 17) { - std::string id = esm.getHString(); - - SpellParams state; - while (esm.isNextSub("INDX")) + while (esm.isNextSub("SPEL")) { - int index; - esm.getHT(index); + std::string id = esm.getHString(); - float magnitude; - esm.getHNT(magnitude, "RAND"); + SpellParams state; + while (esm.isNextSub("INDX")) + { + int index; + esm.getHT(index); - state.mEffectRands[index] = magnitude; - } + float magnitude; + esm.getHNT(magnitude, "RAND"); - while (esm.isNextSub("PURG")) { - int index; - esm.getHT(index); - state.mPurgedEffects.insert(index); - } + state.mEffectRands[index] = magnitude; + } - mSpells[id] = state; + while (esm.isNextSub("PURG")) { + int index; + esm.getHT(index); + state.mPurgedEffects.insert(index); + } + + mSpellParams[id] = state; + mSpells.emplace_back(id); + } + } + else + { + while (esm.isNextSub("SPEL")) + mSpells.emplace_back(esm.getHString()); } // Obsolete @@ -88,30 +97,8 @@ namespace ESM void SpellState::save(ESMWriter &esm) const { - for (TContainer::const_iterator it = mSpells.begin(); it != mSpells.end(); ++it) - { - esm.writeHNString("SPEL", it->first); - - const std::map& random = it->second.mEffectRands; - for (std::map::const_iterator rIt = random.begin(); rIt != random.end(); ++rIt) - { - esm.writeHNT("INDX", rIt->first); - esm.writeHNT("RAND", rIt->second); - } - - const std::set& purges = it->second.mPurgedEffects; - for (std::set::const_iterator pIt = purges.begin(); pIt != purges.end(); ++pIt) - esm.writeHNT("PURG", *pIt); - } - - for (std::map::const_iterator it = mCorprusSpells.begin(); it != mCorprusSpells.end(); ++it) - { - esm.writeHNString("CORP", it->first); - - const CorprusStats & stats = it->second; - esm.writeHNT("WORS", stats.mWorsenings); - esm.writeHNT("TIME", stats.mNextWorsening); - } + for (const std::string& spell : mSpells) + esm.writeHNString("SPEL", spell); for (std::map::const_iterator it = mUsedPowers.begin(); it != mUsedPowers.end(); ++it) { diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp index 55c57611a2..e7067dae8c 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm/spellstate.hpp @@ -31,13 +31,13 @@ namespace ESM struct SpellParams { - std::map mEffectRands; - std::set mPurgedEffects; + std::map mEffectRands; // + std::set mPurgedEffects; // indices of purged effects }; - typedef std::map TContainer; - TContainer mSpells; + std::vector mSpells; // FIXME: obsolete, used only for old saves + std::map mSpellParams; std::map > mPermanentSpellEffects; std::map mCorprusSpells; From 161e042e2ac72c84280bdb7005190fc3b53b2cad Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 28 Aug 2021 11:06:47 +0200 Subject: [PATCH 095/137] Prevent iterator invalidation when cleaning up summons --- apps/openmw/mwgui/container.cpp | 3 ++- apps/openmw/mwmechanics/summoning.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index df490943d2..de771051ef 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -277,8 +277,9 @@ namespace MWGui auto it = std::find_if(summons.begin(), summons.end(), [&] (const auto& entry) { return entry.second == creatureStats.getActorId(); }); if(it != summons.end()) { - MWMechanics::purgeSummonEffect(summoner, *it); + auto summon = *it; summons.erase(it); + MWMechanics::purgeSummonEffect(summoner, summon); break; } } diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 953db077fb..c1923d4337 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -125,8 +125,9 @@ namespace MWMechanics if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()) { // Purge the magic effect so a new creature can be summoned if desired - purgeSummonEffect(summoner, *it); + auto summon = *it; creatureMap.erase(it++); + purgeSummonEffect(summoner, summon); } else ++it; From b8e4f1875197621ce4587d8f42081b260ab90ddd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 28 Aug 2021 14:36:30 +0200 Subject: [PATCH 096/137] Clear temporary effects before unloading actors to prevent absorb effects becoming permanent --- CHANGELOG.md | 1 + apps/openmw/mwbase/mechanicsmanager.hpp | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 15 +++++++++++++-- apps/openmw/mwmechanics/actors.hpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 6 +++--- apps/openmw/mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwworld/actionteleport.cpp | 4 ++-- apps/openmw/mwworld/scene.cpp | 4 ++-- apps/openmw/mwworld/scene.hpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 8 ++++---- apps/openmw/mwworld/worldimp.hpp | 2 +- 12 files changed, 31 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffe687a04c..34e7829274 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,7 @@ Feature #4595: Unique object identifier Feature #4737: Handle instance move from one cell to another Feature #5198: Implement "Magic effect expired" event + Feature #5454: Clear active spells from actor when he disappears from scene Feature #5489: MCP: Telekinesis fix for activators Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index ccc60afc27..6bedbb5b4d 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -58,7 +58,7 @@ namespace MWBase virtual void add (const MWWorld::Ptr& ptr) = 0; ///< Register an object for management - virtual void remove (const MWWorld::Ptr& ptr) = 0; + virtual void remove (const MWWorld::Ptr& ptr, bool keepActive) = 0; ///< Deregister an object for management virtual void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index e9fc6e5c7a..22fe42c5ed 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -286,7 +286,7 @@ namespace MWBase virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) = 0; ///< @return an updated Ptr in case the Ptr's cell changes - virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) = 0; + virtual MWWorld::Ptr moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) = 0; ///< @return an updated Ptr virtual MWWorld::Ptr moveObjectBy(const MWWorld::Ptr &ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) = 0; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index b1aa246385..d71a8f53ee 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -210,6 +210,14 @@ void soulTrap(const MWWorld::Ptr& creature) } } } + +void removeTemporaryEffects(const MWWorld::Ptr& ptr) +{ + ptr.getClass().getCreatureStats(ptr).getActiveSpells().purge([] (const auto& spell) + { + return spell.getType() == ESM::ActiveSpells::Type_Consumable || spell.getType() == ESM::ActiveSpells::Type_Temporary; + }, ptr); +} } namespace MWMechanics @@ -1050,7 +1058,7 @@ namespace MWMechanics void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately) { - removeActor(ptr); + removeActor(ptr, true); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (!anim) @@ -1098,11 +1106,13 @@ namespace MWMechanics ctrl->setVisibility(visibilityRatio); } - void Actors::removeActor (const MWWorld::Ptr& ptr) + void Actors::removeActor (const MWWorld::Ptr& ptr, bool keepActive) { PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) { + if(!keepActive) + removeTemporaryEffects(iter->first); delete iter->second; mActors.erase(iter); } @@ -1167,6 +1177,7 @@ namespace MWMechanics { if((iter->first.isInCell() && iter->first.getCell()==cellStore) && iter->first != ignore) { + removeTemporaryEffects(iter->first); delete iter->second; mActors.erase(iter++); } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 3efe28204d..9950a591ab 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -90,7 +90,7 @@ namespace MWMechanics /// /// \note Dead actors are ignored. - void removeActor (const MWWorld::Ptr& ptr); + void removeActor (const MWWorld::Ptr& ptr, bool keepActive); ///< Deregister an actor for stats management /// /// \note Ignored, if \a ptr is not a registered actor. diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index fb8b3fa2b0..5e18e737df 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -259,11 +259,11 @@ namespace MWMechanics mActors.castSpell(ptr, spellId, manualSpell); } - void MechanicsManager::remove(const MWWorld::Ptr& ptr) + void MechanicsManager::remove(const MWWorld::Ptr& ptr, bool keepActive) { if(ptr == MWBase::Environment::get().getWindowManager()->getWatchedActor()) MWBase::Environment::get().getWindowManager()->watchActor(MWWorld::Ptr()); - mActors.removeActor(ptr); + mActors.removeActor(ptr, keepActive); mObjects.removeObject(ptr); } @@ -317,7 +317,7 @@ namespace MWMechanics // HACK? The player has been changed, so a new Animation object may // have been made for them. Make sure they're properly updated. - mActors.removeActor(ptr); + mActors.removeActor(ptr, true); mActors.addActor(ptr, true); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 206b5c5420..06da2fde51 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -45,7 +45,7 @@ namespace MWMechanics void add (const MWWorld::Ptr& ptr) override; ///< Register an object for management - void remove (const MWWorld::Ptr& ptr) override; + void remove (const MWWorld::Ptr& ptr, bool keepActive) override; ///< Deregister an object for management void updateCell(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) override; diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index fcfafc5f4f..56274eb9a0 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -55,10 +55,10 @@ namespace MWWorld int cellY; world->positionToIndex(mPosition.pos[0],mPosition.pos[1],cellX,cellY); world->moveObject(actor,world->getExterior(cellX,cellY), - mPosition.asVec3()); + mPosition.asVec3(), true, true); } else - world->moveObject(actor,world->getInterior(mCellName),mPosition.asVec3()); + world->moveObject(actor,world->getInterior(mCellName),mPosition.asVec3(), true, true); } } diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 559ab4aef4..57a5bd18f7 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1010,9 +1010,9 @@ namespace MWWorld } } - void Scene::removeObjectFromScene (const Ptr& ptr) + void Scene::removeObjectFromScene (const Ptr& ptr, bool keepActive) { - MWBase::Environment::get().getMechanicsManager()->remove (ptr); + MWBase::Environment::get().getMechanicsManager()->remove (ptr, keepActive); MWBase::Environment::get().getSoundManager()->stopSound3D (ptr); MWBase::Environment::get().getLuaManager()->objectRemovedFromScene(ptr); if (const auto object = mPhysics->getObject(ptr)) diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 3f5d7bf2f5..86cb1ee5b7 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -161,7 +161,7 @@ namespace MWWorld void addObjectToScene (const Ptr& ptr); ///< Add an object that already exists in the world model to the scene. - void removeObjectFromScene (const Ptr& ptr); + void removeObjectFromScene (const Ptr& ptr, bool keepActive = false); ///< Remove an object from the scene, but not from the world model. void removeFromPagedRefs(const Ptr &ptr); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ea455a31cf..b9b8af0987 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1114,7 +1114,7 @@ namespace MWWorld } } - MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics) + MWWorld::Ptr World::moveObject(const Ptr &ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics, bool keepActive) { ESM::Position pos = ptr.getRefData().getPosition(); std::memcpy(pos.pos, &position, sizeof(osg::Vec3f)); @@ -1171,7 +1171,7 @@ namespace MWWorld } else if (!newCellActive && currCellActive) { - mWorldScene->removeObjectFromScene(ptr); + mWorldScene->removeObjectFromScene(ptr, keepActive); mLocalScripts.remove(ptr); removeContainerScripts (ptr); haveToMove = false; @@ -2433,7 +2433,7 @@ namespace MWWorld else { // Remove the old CharacterController - MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); + MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr(), true); mNavigator->removeAgent(getPathfindingHalfExtents(getPlayerConstPtr())); mPhysics->remove(getPlayerPtr()); mRendering->removePlayer(getPlayerPtr()); @@ -2449,7 +2449,7 @@ namespace MWWorld void World::renderPlayer() { - MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr()); + MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr(), true); MWWorld::Ptr player = getPlayerPtr(); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 9ead6926c2..d6b7eb3fbf 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -373,7 +373,7 @@ namespace MWWorld MWWorld::Ptr moveObject (const Ptr& ptr, const osg::Vec3f& position, bool movePhysics=true, bool moveToActive=false) override; ///< @return an updated Ptr in case the Ptr's cell changes - MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true) override; + MWWorld::Ptr moveObject (const Ptr& ptr, CellStore* newCell, const osg::Vec3f& position, bool movePhysics=true, bool keepActive=false) override; ///< @return an updated Ptr MWWorld::Ptr moveObjectBy(const Ptr& ptr, const osg::Vec3f& vec, bool moveToActive, bool ignoreCollisions) override; From 43074347e82066c3601edf195e959a0e14d7f9f9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 31 Aug 2021 19:59:55 +0200 Subject: [PATCH 097/137] Prevent spell duplication --- apps/openmw/mwmechanics/spells.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index 743eacfe2c..6520ae3ab3 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -141,7 +141,7 @@ namespace MWMechanics const ESM::Spell *spell = *iter; if (filter(spell)) { - mSpells.erase(iter++); + iter = mSpells.erase(iter); purged.push_back(spell->mId); } else @@ -204,7 +204,7 @@ namespace MWMechanics const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(id); if (spell) { - mSpells.emplace_back(spell); + addSpell(spell); if (id == state.mSelectedSpell) mSelectedSpell = id; From 63a9203dde6cd4cccc64fa81fb33ae6b3af1a4e5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 31 Aug 2021 20:07:30 +0200 Subject: [PATCH 098/137] Fix icon magnitude --- apps/openmw/mwgui/spellicons.cpp | 2 +- apps/openmw/mwmechanics/spelleffects.cpp | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 4a86503f46..0673446fe7 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -34,7 +34,7 @@ namespace MWGui { for(const auto& effect : params.getEffects()) { - if(!effect.mMagnitude) + if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied)) continue; MagicEffectInfo newEffectSource; newEffectSource.mKey = MWMechanics::EffectKey(effect.mEffectId, effect.mArg); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 895e908bd2..96416f00c0 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -705,10 +705,15 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac return true; } const auto* magicEffect = world->getStore().get().find(effect.mEffectId); - if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) { - effect.mTimeLeft -= dt; - return false; + if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) + { + effect.mTimeLeft -= dt; + return false; + } + else if(!dt) + return false; } if(effect.mEffectId == ESM::MagicEffect::Lock) { @@ -786,8 +791,9 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac float oldMagnitude = 0.f; if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) oldMagnitude = effect.mMagnitude; + float magnitude = roll(effect); //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here - effect.mMagnitude = roll(effect); + effect.mMagnitude = magnitude; if(!(magicEffect->mData.mFlags & (ESM::MagicEffect::Flags::NoMagnitude | ESM::MagicEffect::Flags::AppliedOnce))) { if(effect.mDuration != 0) @@ -808,6 +814,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac spellParams.worsen(); else applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage); + effect.mMagnitude = magnitude; magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude)); } effect.mTimeLeft -= dt; From 46ce2ff10cab2ad3a41306b254ca69e68b25aed7 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 31 Aug 2021 20:12:11 +0200 Subject: [PATCH 099/137] Add #4414 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34e7829274..0550725bc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map Feature #4297: Implement APPLIED_ONCE flag for magic effects + Feature #4414: Handle duration of EXTRA SPELL magic effect Feature #4595: Unique object identifier Feature #4737: Handle instance move from one cell to another Feature #5198: Implement "Magic effect expired" event From 4abcb0d7b960c79e5990e5893654512a22f9f20e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 8 Sep 2021 19:25:22 +0200 Subject: [PATCH 100/137] Remove applied magnitude instead of min magnitude --- apps/openmw/mwmechanics/spelleffects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 96416f00c0..24816b9ed1 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -1029,7 +1029,7 @@ void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellP auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); const auto* magicEffect = world->getStore().get().find(effect.mEffectId); if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) - magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMinMagnitude)); + magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMagnitude)); removeMagicEffect(target, spellParams, effect); auto anim = world->getAnimation(target); if(anim) From e4994054ecabbd5eb5199e62ec84ff82ad57f258 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 21 Sep 2021 18:07:39 +0200 Subject: [PATCH 101/137] Add #5207 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0550725bc8..25df64c65c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Bug #4744: Invisible particles must still be processed Bug #5100: Persuasion doesn't always clamp the resulting disposition Bug #5120: Scripted object spawning updates physics system + Bug #5207: Loose summons can be present in scene Bug #5379: Wandering NPCs falling through cantons Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost From 2de7b8e2fba5daaedb76ef567b6e12d73fa0b349 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 29 Sep 2021 21:21:52 +0200 Subject: [PATCH 102/137] Allow non-biped creatures using weapons to open doors --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aipackage.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc9510f6e..9b78cdfa81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Bug #6133: Cannot reliably sneak or steal in the sight of the NPCs siding with player Bug #6143: Capturing a screenshot makes engine to be a temporary unresponsive Bug #6165: Paralyzed player character can pickup items when the inventory is open + Bug #6172: Some creatures can't open doors Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 46e4729a8a..ad0a3943ad 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -34,6 +34,11 @@ namespace const float actorTolerance = 2 * speed * duration + 1.2 * std::max(halfExtents.x(), halfExtents.y()); return std::max(MWMechanics::MIN_TOLERANCE, actorTolerance); } + + bool canOpenDoors(const MWWorld::Ptr& ptr) + { + return ptr.getClass().isBipedal(ptr) || ptr.getClass().hasInventoryStore(ptr); + } } MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) : @@ -118,7 +123,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed) { - if (actor.getClass().isBipedal(actor)) + if (canOpenDoors(actor)) openDoors(actor); const bool wasShortcutting = mIsShortcutting; @@ -232,7 +237,7 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor) static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance(); const MWWorld::Ptr door = getNearbyDoor(actor, distance); - if (!door.isEmpty() && actor.getClass().isBipedal(actor)) + if (!door.isEmpty() && canOpenDoors(actor)) { openDoors(actor); } @@ -445,7 +450,7 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0) result |= DetourNavigator::Flag_walk; - if (actorClass.isBipedal(actor) && getTypeId() != AiPackageTypeId::Wander) + if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander) result |= DetourNavigator::Flag_openDoor; return result; From 24ecdc37a78384c365b2bb643310a68b8e4d2eed Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 30 Sep 2021 02:59:38 +0200 Subject: [PATCH 103/137] Fix crash in LuaUtil::ScriptsContainer::~ScriptsContainer() --- components/lua/scriptscontainer.cpp | 6 ++++++ components/lua/scriptscontainer.hpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index b6882b0988..703381a453 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -328,6 +328,12 @@ namespace LuaUtil std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end()); } + ScriptsContainer::~ScriptsContainer() + { + for (auto& [_, script] : mScripts) + script.mHiddenData[ScriptId::KEY] = sol::nil; + } + void ScriptsContainer::removeAllScripts() { for (auto& [_, script] : mScripts) diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 0bf50b8793..69aa18e940 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -75,7 +75,7 @@ namespace LuaUtil ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix); ScriptsContainer(const ScriptsContainer&) = delete; ScriptsContainer(ScriptsContainer&&) = delete; - virtual ~ScriptsContainer() { removeAllScripts(); } + virtual ~ScriptsContainer(); // Adds package that will be available (via `require`) for all scripts in the container. // Automatically applies LuaUtil::makeReadOnly to the package. From f6c39b9f19ea0758427d4aaf10073daf528a928a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 30 Sep 2021 22:24:25 +0200 Subject: [PATCH 104/137] Fix issue numbers --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b78cdfa81..b21639d144 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ Bug #5453: Magic effect VFX are offset for creatures Bug #5483: AutoCalc flag is not used to calculate spells cost Bug #5508: Engine binary links to Qt without using it - Bug #5755: Active grid object paging - disappearing textures + Bug #5766: Active grid object paging - disappearing textures Bug #5788: Texture editing parses the selected indexes wrongly Bug #5842: GetDisposition adds temporary disposition change from different actors Bug #6037: Morrowind Content Language Cannot be Set to English in OpenMW Launcher @@ -47,8 +47,8 @@ Feature #2780: A way to see current OpenMW version in the console Feature #3616: Allow Zoom levels on the World Map Feature #4595: Unique object identifier - Feature #4737: Handle instance move from one cell to another Feature #5489: MCP: Telekinesis fix for activators + Feature #5737: Handle instance move from one cell to another Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving Feature #6032: Reverse-z depth buffer From 8309910d9de491f3a10c56708aa070e434f9b018 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Thu, 30 Sep 2021 22:58:16 +0200 Subject: [PATCH 105/137] Restore the cell grid to its former non-exorbitant size, reducing stutter and also threw in a simple alternative fix for the actor position adjustment issue. --- apps/openmw/mwclass/activator.cpp | 8 +- apps/openmw/mwclass/activator.hpp | 4 +- apps/openmw/mwclass/actor.cpp | 2 +- apps/openmw/mwclass/actor.hpp | 2 +- apps/openmw/mwclass/container.cpp | 8 +- apps/openmw/mwclass/container.hpp | 4 +- apps/openmw/mwclass/door.cpp | 8 +- apps/openmw/mwclass/door.hpp | 4 +- apps/openmw/mwclass/light.cpp | 8 +- apps/openmw/mwclass/light.hpp | 4 +- apps/openmw/mwclass/static.cpp | 8 +- apps/openmw/mwclass/static.hpp | 4 +- apps/openmw/mwmechanics/actor.cpp | 11 ++ apps/openmw/mwmechanics/actor.hpp | 4 + apps/openmw/mwmechanics/actors.cpp | 7 + apps/openmw/mwphysics/physicssystem.cpp | 5 +- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwworld/cellvisitors.hpp | 10 -- apps/openmw/mwworld/class.cpp | 4 +- apps/openmw/mwworld/class.hpp | 4 +- apps/openmw/mwworld/scene.cpp | 201 ++++++------------------ apps/openmw/mwworld/scene.hpp | 9 +- 22 files changed, 113 insertions(+), 208 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index e4ba7af4f0..6285bdbf7e 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -38,15 +38,15 @@ namespace MWClass } } - void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); } - void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Activator::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Activator::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 32bf1564c9..48a679e0b7 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -17,9 +17,9 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/actor.cpp b/apps/openmw/mwclass/actor.cpp index b142fd2643..ad43bd6e5f 100644 --- a/apps/openmw/mwclass/actor.cpp +++ b/apps/openmw/mwclass/actor.cpp @@ -22,7 +22,7 @@ namespace MWClass MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Actor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if (!model.empty()) { diff --git a/apps/openmw/mwclass/actor.hpp b/apps/openmw/mwclass/actor.hpp index 6a5d4c5150..886ffe4771 100644 --- a/apps/openmw/mwclass/actor.hpp +++ b/apps/openmw/mwclass/actor.hpp @@ -24,7 +24,7 @@ namespace MWClass ///< Adjust position to stand on ground. Must be called post model load /// @param force do this even if the ptr is flying - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 051fbafeb5..06980f55da 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -106,15 +106,15 @@ namespace MWClass } } - void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); } - void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Container::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Container::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index 9407dab243..0b290a73e1 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -42,8 +42,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 70c4a27582..b5fe705ca6 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -55,9 +55,9 @@ namespace MWClass } } - void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) @@ -70,10 +70,10 @@ namespace MWClass } } - void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Door::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_Door); } bool Door::isDoor() const diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 1fd59f8f52..f9288a88ce 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -18,8 +18,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool isDoor() const override; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 0281861980..69cc1a09bf 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -33,13 +33,13 @@ namespace MWClass renderingInterface.getObjects().insertModel(ptr, model, true, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); assert (ref->mBase != nullptr); - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, @@ -47,11 +47,11 @@ namespace MWClass MWSound::PlayMode::Loop); } - void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Light::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { // TODO: add option somewhere to enable collision for placeable objects if (!model.empty() && (ptr.get()->mBase->mData.mFlags & ESM::Light::Carry) == 0) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } bool Light::useAnim() const diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index 206abede43..e8aa4e5878 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -14,8 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; bool useAnim() const override; diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index 5cbe219580..0805ca3dd1 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -23,15 +23,15 @@ namespace MWClass } } - void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { - insertObjectPhysics(ptr, model, rotation, physics, skipAnimated); + insertObjectPhysics(ptr, model, rotation, physics); } - void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Static::insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { if(!model.empty()) - physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World, skipAnimated); + physics.addObject(ptr, model, rotation, MWPhysics::CollisionType_World); } std::string Static::getModel(const MWWorld::ConstPtr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 7c326987a8..c747eebf2f 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -14,8 +14,8 @@ namespace MWClass void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const override; ///< Add reference into a cell for rendering - void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; - void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const override; + void insertObject(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; + void insertObjectPhysics(const MWWorld::Ptr& ptr, const std::string& model, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const override; std::string getName (const MWWorld::ConstPtr& ptr) const override; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwmechanics/actor.cpp b/apps/openmw/mwmechanics/actor.cpp index a5c55633ac..5801ea751d 100644 --- a/apps/openmw/mwmechanics/actor.cpp +++ b/apps/openmw/mwmechanics/actor.cpp @@ -5,6 +5,7 @@ namespace MWMechanics { Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation) + : mPositionAdjusted(false) { mCharacterController.reset(new CharacterController(ptr, animation)); } @@ -58,4 +59,14 @@ namespace MWMechanics { mIsTurningToPlayer = turning; } + + void Actor::setPositionAdjusted(bool adjusted) + { + mPositionAdjusted = adjusted; + } + + bool Actor::getPositionAdjusted() const + { + return mPositionAdjusted; + } } diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp index be4f425378..c25fc1cb73 100644 --- a/apps/openmw/mwmechanics/actor.hpp +++ b/apps/openmw/mwmechanics/actor.hpp @@ -48,6 +48,9 @@ namespace MWMechanics return mEngageCombat.update(duration); } + void setPositionAdjusted(bool adjusted); + bool getPositionAdjusted() const; + private: std::unique_ptr mCharacterController; int mGreetingTimer{0}; @@ -55,6 +58,7 @@ namespace MWMechanics GreetingState mGreetingState{Greet_None}; bool mIsTurningToPlayer{false}; Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)}; + bool mPositionAdjusted; }; } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 542f185854..1cd66291f0 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2157,7 +2157,14 @@ namespace MWMechanics continue; } else if (!isPlayer) + { iter->first.getRefData().getBaseNode()->setNodeMask(MWRender::Mask_Actor); + if (!iter->second->getPositionAdjusted()) + { + iter->first.getClass().adjustPosition(iter->first, false); + iter->second->setPositionAdjusted(true); + } + } const bool isDead = iter->first.getClass().getCreatureStats(iter->first).isDead(); if (!isDead && (!godmode || !isPlayer) && iter->first.getClass().getCreatureStats(iter->first).isParalyzed()) diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 903e383370..23ece987ef 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -492,7 +492,7 @@ namespace MWPhysics return heightField->second.get(); } - void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType, bool skipAnimated) + void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType) { if (ptr.mRef->mData.mPhysicsPostponed) return; @@ -500,9 +500,6 @@ namespace MWPhysics if (!shapeInstance || !shapeInstance->getCollisionShape()) return; - if (skipAnimated && shapeInstance->isAnimated()) - return; - assert(!getObject(ptr)); auto obj = std::make_shared(ptr, shapeInstance, rotation, collisionType, mTaskScheduler.get()); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 52515b563f..bc358cb3c4 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -128,7 +128,7 @@ namespace MWPhysics void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World, bool skipAnimated = false); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, osg::Quat rotation, int collisionType = CollisionType_World); void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, const std::string& mesh, bool computeRadius); diff --git a/apps/openmw/mwworld/cellvisitors.hpp b/apps/openmw/mwworld/cellvisitors.hpp index 81e1248e6d..77f33fa84b 100644 --- a/apps/openmw/mwworld/cellvisitors.hpp +++ b/apps/openmw/mwworld/cellvisitors.hpp @@ -25,16 +25,6 @@ namespace MWWorld } }; - struct ListObjectsVisitor - { - std::vector mObjects; - - bool operator() (const MWWorld::Ptr& ptr) - { - mObjects.push_back (ptr); - return true; - } - }; } #endif diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 55e39de55e..da4dd9d99e 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -30,12 +30,12 @@ namespace MWWorld } - void Class::insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const { } - void Class::insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated) const + void Class::insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const {} bool Class::apply (const MWWorld::Ptr& ptr, const std::string& id, const MWWorld::Ptr& actor) const diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 4779a3065b..8e451ea580 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -78,9 +78,9 @@ namespace MWWorld } virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). - virtual void insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics, bool skipAnimated = false) const; + virtual void insertObjectPhysics(const Ptr& ptr, const std::string& mesh, const osg::Quat& rotation, MWPhysics::PhysicsSystem& physics) const; virtual std::string getName (const ConstPtr& ptr) const = 0; ///< \return name or ID; can return an empty string. diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 559ab4aef4..765464c8de 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -104,7 +104,7 @@ namespace } void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics, - MWRender::RenderingManager& rendering, std::set& pagedRefs, bool onlyPhysics) + MWRender::RenderingManager& rendering, std::set& pagedRefs) { if (ptr.getRefData().getBaseNode() || physics.getActor(ptr)) { @@ -114,12 +114,6 @@ namespace std::string model = getModel(ptr, rendering.getResourceSystem()->getVFS()); const auto rotation = makeNodeRotation(ptr, RotationOrder::direct); - if (onlyPhysics && !physics.getObject(ptr) && !ptr.getClass().isActor()) - { - // When we preload physics object we need to skip animated objects. They are dependant on the scene graph which doesn't yet exist. - ptr.getClass().insertObject (ptr, model, rotation, physics, true); - return; - } const ESM::RefNum& refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end()) @@ -137,8 +131,7 @@ namespace // Restore effect particles MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr); - if (!physics.getObject(ptr)) - ptr.getClass().insertObject (ptr, model, rotation, physics); + ptr.getClass().insertObject (ptr, model, rotation, physics); MWBase::Environment::get().getLuaManager()->objectAddedToScene(ptr); } @@ -201,11 +194,10 @@ namespace { MWWorld::CellStore& mCell; Loading::Listener* mLoadingListener; - bool mOnlyObjects; std::vector mToInsert; - InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects); + InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener); bool operator() (const MWWorld::Ptr& ptr); @@ -213,8 +205,8 @@ namespace void insert(AddObject&& addObject); }; - InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener, bool onlyObjects) - : mCell(cell), mLoadingListener(loadingListener), mOnlyObjects(onlyObjects) + InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, Loading::Listener* loadingListener) + : mCell(cell), mLoadingListener(loadingListener) {} bool InsertVisitor::operator() (const MWWorld::Ptr& ptr) @@ -230,7 +222,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled() && (!mOnlyObjects || !ptr.getClass().isActor())) + if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) { try { @@ -248,16 +240,6 @@ namespace } } - struct PositionVisitor - { - bool operator() (const MWWorld::Ptr& ptr) - { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) - ptr.getClass().adjustPosition (ptr, false); - return true; - } - }; - int getCellPositionDistanceToOrigin(const std::pair& cellPosition) { return std::abs(cellPosition.first) + std::abs(cellPosition.second); @@ -324,39 +306,12 @@ namespace MWWorld mRendering.update (duration, paused); } - void Scene::unloadInactiveCell (CellStore* cell) + void Scene::unloadCell(CellStore* cell) { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); - - Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); - - ListObjectsVisitor visitor; - - cell->forEach(visitor); - for (const auto& ptr : visitor.mObjects) - { - mPhysics->remove(ptr); - ptr.mRef->mData.mPhysicsPostponed = false; - } - - if (cell->getCell()->isExterior()) - { - const auto cellX = cell->getCell()->getGridX(); - const auto cellY = cell->getCell()->getGridY(); - mPhysics->removeHeightField(cellX, cellY); - } - - mInactiveCells.erase(cell); - } - - void Scene::deactivateCell(CellStore* cell) - { - assert(mInactiveCells.find(cell) != mInactiveCells.end()); if (mActiveCells.find(cell) == mActiveCells.end()) return; - Log(Debug::Info) << "Deactivate cell " << cell->getCell()->getDescription(); + Log(Debug::Info) << "Unloading cell " << cell->getCell()->getDescription(); ListAndResetObjectsVisitor visitor; @@ -367,8 +322,8 @@ namespace MWWorld if (const auto object = mPhysics->getObject(ptr)) { mNavigator.removeObject(DetourNavigator::ObjectId(object)); - if (object->isAnimated()) - mPhysics->remove(ptr); + mPhysics->remove(ptr); + ptr.mRef->mData.mPhysicsPostponed = false; } else if (mPhysics->getActor(ptr)) { @@ -386,6 +341,8 @@ namespace MWWorld { if (mPhysics->getHeightField(cellX, cellY) != nullptr) mNavigator.removeHeightfield(osg::Vec2i(cellX, cellY)); + + mPhysics->removeHeightField(cellX, cellY); } if (cell->getCell()->hasWater()) @@ -408,12 +365,11 @@ namespace MWWorld mActiveCells.erase(cell); } - void Scene::activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn) + void Scene::loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn) { using DetourNavigator::HeightfieldShape; assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) != mInactiveCells.end()); mActiveCells.insert(cell); Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription(); @@ -425,13 +381,25 @@ namespace MWWorld if (cell->getCell()->isExterior()) { + osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + const float verts = ESM::Land::LAND_SIZE; + const float worldsize = ESM::Land::REAL_SIZE; + if (data) + { + mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); + } + else + { + static std::vector defaultHeight; + defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); + mPhysics->addHeightField (&defaultHeight[0], cellX, cellY, worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); + } if (const auto heightField = mPhysics->getHeightField(cellX, cellY)) { const osg::Vec2i cellPosition(cellX, cellY); const btVector3& origin = heightField->getCollisionObject()->getWorldTransform().getOrigin(); const osg::Vec3f shift(origin.x(), origin.y(), origin.z()); - const osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* const data = land == nullptr ? nullptr : land->getData(ESM::Land::DATA_VHGT); const HeightfieldShape shape = [&] () -> HeightfieldShape { if (data == nullptr) @@ -462,7 +430,7 @@ namespace MWWorld if (respawn) cell->respawn(); - insertCell(*cell, loadingListener, false); + insertCell(*cell, loadingListener); mRendering.addCell(cell); @@ -511,49 +479,14 @@ namespace MWWorld mPreloader->notifyLoaded(cell); } - void Scene::loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener) - { - assert(mActiveCells.find(cell) == mActiveCells.end()); - assert(mInactiveCells.find(cell) == mInactiveCells.end()); - mInactiveCells.insert(cell); - - Log(Debug::Info) << "Loading inactive cell " << cell->getCell()->getDescription(); - - if (cell->getCell()->isExterior()) - { - float verts = ESM::Land::LAND_SIZE; - float worldsize = ESM::Land::REAL_SIZE; - - const int cellX = cell->getCell()->getGridX(); - const int cellY = cell->getCell()->getGridY(); - - osg::ref_ptr land = mRendering.getLandManager()->getLand(cellX, cellY); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; - if (data) - { - mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get()); - } - else - { - static std::vector defaultHeight; - defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT); - mPhysics->addHeightField (&defaultHeight[0], cellX, cellY, worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get()); - } - } - - insertCell(*cell, loadingListener, true); - } - void Scene::clear() { - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); ) { auto* cell = *iter++; - deactivateCell(cell); - unloadInactiveCell (cell); + unloadCell (cell); } assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); mCurrentCell = nullptr; mPreloader->clear(); @@ -595,7 +528,7 @@ namespace MWWorld void Scene::changeCellGrid (const osg::Vec3f &pos, int playerCellX, int playerCellY, bool changeEvent) { - for (auto iter = mInactiveCells.begin(); iter != mInactiveCells.end(); ) + for (auto iter = mActiveCells.begin(); iter != mActiveCells.end(); ) { auto* cell = *iter++; if (cell->getCell()->isExterior()) @@ -603,16 +536,10 @@ namespace MWWorld const auto dx = std::abs(playerCellX - cell->getCell()->getGridX()); const auto dy = std::abs(playerCellY - cell->getCell()->getGridY()); if (dx > mHalfGridSize || dy > mHalfGridSize) - deactivateCell(cell); - - if (dx > mHalfGridSize+1 || dy > mHalfGridSize+1) - unloadInactiveCell(cell); + unloadCell(cell); } else - { - deactivateCell(cell); - unloadInactiveCell(cell); - } + unloadCell (cell); } mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); @@ -662,7 +589,6 @@ namespace MWWorld } auto cellsPositionsToLoad = cellsToLoad(mActiveCells,mHalfGridSize); - auto cellsPositionsToLoadInactive = cellsToLoad(mInactiveCells,mHalfGridSize+1); Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen(); Loading::ScopedLoad load(loadingListener); @@ -685,26 +611,12 @@ namespace MWWorld return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); }); - std::sort(cellsPositionsToLoadInactive.begin(), cellsPositionsToLoadInactive.end(), - [&] (const std::pair& lhs, const std::pair& rhs) { - return getCellPositionPriority(lhs) < getCellPositionPriority(rhs); - }); - - // Load cells - for (const auto& [x,y] : cellsPositionsToLoadInactive) - { - if (!isCellInCollection(x, y, mInactiveCells)) - { - CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - loadInactiveCell (cell, loadingListener); - } - } for (const auto& [x,y] : cellsPositionsToLoad) { if (!isCellInCollection(x, y, mActiveCells)) { CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(x, y); - activateCell (cell, loadingListener, changeEvent); + loadCell (cell, loadingListener, changeEvent); } } @@ -737,17 +649,15 @@ namespace MWWorld loadingListener->setLabel("Testing exterior cells ("+std::to_string(i)+"/"+std::to_string(cells.getExtSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getExterior(it->mData.mX, it->mData.mY); - loadInactiveCell(cell, nullptr); - activateCell(cell, nullptr, false); + loadCell(cell, nullptr, false); - auto iter = mInactiveCells.begin(); - while (iter != mInactiveCells.end()) + auto iter = mActiveCells.begin(); + while (iter != mActiveCells.end()) { if (it->isExterior() && it->mData.mX == (*iter)->getCell()->getGridX() && it->mData.mY == (*iter)->getCell()->getGridY()) { - deactivateCell(*iter); - unloadInactiveCell(*iter); + unloadCell(*iter); break; } @@ -785,18 +695,16 @@ namespace MWWorld loadingListener->setLabel("Testing interior cells ("+std::to_string(i)+"/"+std::to_string(cells.getIntSize())+")..."); CellStore *cell = MWBase::Environment::get().getWorld()->getInterior(it->mName); - loadInactiveCell(cell, nullptr); - activateCell(cell, nullptr, false); + loadCell(cell, nullptr, false); - auto iter = mInactiveCells.begin(); - while (iter != mInactiveCells.end()) + auto iter = mActiveCells.begin(); + while (iter != mActiveCells.end()) { assert (!(*iter)->getCell()->isExterior()); if (it->mName == (*iter)->getCell()->mName) { - deactivateCell(*iter); - unloadInactiveCell(*iter); + unloadCell(*iter); break; } @@ -915,11 +823,10 @@ namespace MWWorld Log(Debug::Info) << "Changing to interior"; // unload - for (auto iter = mInactiveCells.begin(); iter!=mInactiveCells.end(); ) + for (auto iter = mActiveCells.begin(); iter!=mActiveCells.end(); ) { auto* cellToUnload = *iter++; - deactivateCell(cellToUnload); - unloadInactiveCell(cellToUnload); + unloadCell(cellToUnload); } assert(mActiveCells.empty()); assert(mInactiveCells.empty()); @@ -928,8 +835,7 @@ namespace MWWorld // Load cell. mPagedRefs.clear(); - loadInactiveCell (cell, loadingListener); - activateCell (cell, loadingListener, changeEvent); + loadCell(cell, loadingListener, changeEvent); changePlayerCell(cell, position, adjustPlayerPos); @@ -979,26 +885,19 @@ namespace MWWorld mCellChanged = false; } - void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects) + void Scene::insertCell (CellStore &cell, Loading::Listener* loadingListener) { - InsertVisitor insertVisitor(cell, loadingListener, onlyObjects); + InsertVisitor insertVisitor(cell, loadingListener); cell.forEach (insertVisitor); - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs, onlyObjects); }); - if (!onlyObjects) - { - insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); - - // do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order - PositionVisitor posVisitor; - cell.forEach (posVisitor); - } + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering, mPagedRefs); }); + insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); }); } void Scene::addObjectToScene (const Ptr& ptr) { try { - addObject(ptr, *mPhysics, mRendering, mPagedRefs, false); + addObject(ptr, *mPhysics, mRendering, mPagedRefs); addObject(ptr, *mPhysics, mNavigator); MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr(); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 3f5d7bf2f5..12d79d3d11 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -77,7 +77,6 @@ namespace MWWorld CellStore* mCurrentCell; // the cell the player is in CellStoreCollection mActiveCells; - CellStoreCollection mInactiveCells; bool mCellChanged; MWPhysics::PhysicsSystem *mPhysics; MWRender::RenderingManager& mRendering; @@ -100,7 +99,7 @@ namespace MWWorld std::vector> mWorkItems; - void insertCell(CellStore &cell, Loading::Listener* loadingListener, bool onlyObjects); + void insertCell(CellStore &cell, Loading::Listener* loadingListener); osg::Vec2i mCurrentGridCenter; // Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center @@ -116,10 +115,8 @@ namespace MWWorld osg::Vec4i gridCenterToBounds(const osg::Vec2i ¢erCell) const; osg::Vec2i getNewGridCenter(const osg::Vec3f &pos, const osg::Vec2i *currentGridCenter = nullptr) const; - void unloadInactiveCell(CellStore* cell); - void deactivateCell(CellStore* cell); - void activateCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn); - void loadInactiveCell(CellStore *cell, Loading::Listener* loadingListener); + void unloadCell(CellStore* cell); + void loadCell(CellStore *cell, Loading::Listener* loadingListener, bool respawn); public: From b5af192888c8f38a2f04f77e0cffce61cf683f5b Mon Sep 17 00:00:00 2001 From: andrewapp Date: Fri, 1 Oct 2021 09:19:26 +1000 Subject: [PATCH 106/137] gamepad cursor speed fix --- AUTHORS.md | 1 + apps/openmw/mwinput/controllermanager.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index a1f00bca10..2080f12a99 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -229,6 +229,7 @@ Programmers Yuri Krupenin zelurker Noah Gooder + Andrew Appuhamy (andrew-app) Documentation ------------- diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 7a91f8643b..fa10ce03cd 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -99,8 +99,8 @@ namespace MWInput // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - float xMove = xAxis * dt * 1500.0f / uiScale; - float yMove = yAxis * dt * 1500.0f / uiScale; + float xMove = xAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; + float yMove = yAxis * dt * 1500.0f / uiScale * mGamepadCursorSpeed; float mouseWheelMove = -zAxis * dt * 1500.0f; if (xMove != 0 || yMove != 0 || mouseWheelMove != 0) From 2568f119a4e31ed192e54e3fecb8bd62f33194f3 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Fri, 1 Oct 2021 08:11:00 +0000 Subject: [PATCH 107/137] reapplies PR without npe (#3137) * avoids creating empty statesets on drawables Currently, we attempt to skip creating state on drawable nodes when this state matches the default state. This attempt is incomplete because we still create an avoidable empty stateset in the default case. * renderingmanager.cpp * nifloader.cpp * nifloader.cpp * shadervisitor.cpp --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/nifosg/nifloader.cpp | 20 +++++++++----------- components/shader/shadervisitor.cpp | 6 ++++-- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 07b0418f2b..be143510b2 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -486,6 +486,7 @@ namespace MWRender defaultMat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1)); defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); mFog.reset(new FogManager()); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 0432aef5b4..a564844ce6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1134,8 +1134,6 @@ namespace NifOsg trans->addChild(toAttach); parentNode->addChild(trans); } - // create partsys stateset in order to pass in ShaderVisitor like all other Drawables - partsys->getOrCreateStateSet(); } void handleNiGeometryData(osg::Geometry *geometry, const Nif::NiGeometryData* data, const std::vector& boundTextures, const std::string& name) @@ -1923,8 +1921,6 @@ namespace NifOsg void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, bool hasVertexColors, int animflags) { - osg::StateSet* stateset = node->getOrCreateStateSet(); - // Specular lighting is enabled by default, but there's a quirk... bool specEnabled = true; osg::ref_ptr mat (new osg::Material); @@ -2006,15 +2002,15 @@ namespace NifOsg if (blendFunc->getDestination() == GL_DST_ALPHA) blendFunc->setDestination(GL_ONE); blendFunc = shareAttribute(blendFunc); - stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); + node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON); bool noSort = (alphaprop->flags>>13)&1; if (!noSort) - stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); + node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); else - stateset->setRenderBinToInherit(); + node->getOrCreateStateSet()->setRenderBinToInherit(); } - else + else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::BLENDFUNC); stateset->removeMode(GL_BLEND); @@ -2025,9 +2021,9 @@ namespace NifOsg { osg::ref_ptr alphaFunc (new osg::AlphaFunc(getTestMode((alphaprop->flags>>10)&0x7), alphaprop->data.threshold/255.f)); alphaFunc = shareAttribute(alphaFunc); - stateset->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); + node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON); } - else + else if (osg::StateSet* stateset = node->getStateSet()) { stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC); stateset->removeMode(GL_ALPHA_TEST); @@ -2083,8 +2079,10 @@ namespace NifOsg mat = shareAttribute(mat); + osg::StateSet* stateset = node->getOrCreateStateSet(); stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); + if (emissiveMult != 1.f) + stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult)); } }; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 0d0710853f..cc2b781cfd 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -116,7 +116,6 @@ namespace Shader , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) { - mRequirements.emplace_back(); } void ShaderVisitor::setForceShaders(bool force) @@ -421,7 +420,10 @@ namespace Shader void ShaderVisitor::pushRequirements(osg::Node& node) { - mRequirements.push_back(mRequirements.back()); + if (mRequirements.empty()) + mRequirements.emplace_back(); + else + mRequirements.push_back(mRequirements.back()); mRequirements.back().mNode = &node; } From 52a10a4bc076d0235a5f382ed3980d34e1059cf9 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 1 Oct 2021 12:24:29 +0200 Subject: [PATCH 108/137] remove one last assert --- apps/openmw/mwworld/scene.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 765464c8de..1e21dc2f57 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -829,7 +829,6 @@ namespace MWWorld unloadCell(cellToUnload); } assert(mActiveCells.empty()); - assert(mInactiveCells.empty()); loadingListener->setProgressRange(cell->count()); From 1c9f06f74208eeb28b12a917c84ff94a53babc7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matja=C5=BE=20Lamut?= Date: Sun, 3 Oct 2021 19:13:51 +0000 Subject: [PATCH 109/137] Minor UI tweaks all around OpenMW-CS --- apps/opencs/view/doc/view.cpp | 50 +++++++++++++++---------- apps/opencs/view/render/cellarrow.cpp | 8 ++-- apps/opencs/view/world/enumdelegate.cpp | 2 + 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 4514cfb585..c89437d70d 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -65,6 +65,8 @@ void CSVDoc::View::setupFileMenu() QAction* save = createMenuEntry("Save", ":./menu-save.png", file, "document-file-save"); connect (save, SIGNAL (triggered()), this, SLOT (save())); mSave = save; + + file->addSeparator(); QAction* verify = createMenuEntry("Verify", ":./menu-verify.png", file, "document-file-verify"); connect (verify, SIGNAL (triggered()), this, SLOT (verify())); @@ -80,6 +82,8 @@ void CSVDoc::View::setupFileMenu() QAction* meta = createMenuEntry(CSMWorld::UniversalId::Type_MetaDatas, file, "document-file-metadata"); connect (meta, SIGNAL (triggered()), this, SLOT (addMetaDataSubView())); + file->addSeparator(); + QAction* close = createMenuEntry("Close", ":./menu-close.png", file, "document-file-close"); connect (close, SIGNAL (triggered()), this, SLOT (close())); @@ -156,17 +160,16 @@ void CSVDoc::View::setupWorldMenu() { QMenu *world = menuBar()->addMenu (tr ("World")); - QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); - connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); - - QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); - connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); - QAction* referenceables = createMenuEntry(CSMWorld::UniversalId::Type_Referenceables, world, "document-world-referencables"); connect (referenceables, SIGNAL (triggered()), this, SLOT (addReferenceablesSubView())); QAction* references = createMenuEntry(CSMWorld::UniversalId::Type_References, world, "document-world-references"); connect (references, SIGNAL (triggered()), this, SLOT (addReferencesSubView())); + + world->addSeparator(); + + QAction* cells = createMenuEntry(CSMWorld::UniversalId::Type_Cells, world, "document-world-cells"); + connect (cells, SIGNAL (triggered()), this, SLOT (addCellsSubView())); QAction *lands = createMenuEntry(CSMWorld::UniversalId::Type_Lands, world, "document-world-lands"); connect (lands, SIGNAL (triggered()), this, SLOT (addLandsSubView())); @@ -177,7 +180,10 @@ void CSVDoc::View::setupWorldMenu() QAction *grid = createMenuEntry(CSMWorld::UniversalId::Type_Pathgrids, world, "document-world-pathgrid"); connect (grid, SIGNAL (triggered()), this, SLOT (addPathgridSubView())); - world->addSeparator(); // items that don't represent single record lists follow here + world->addSeparator(); + + QAction* regions = createMenuEntry(CSMWorld::UniversalId::Type_Regions, world, "document-world-regions"); + connect (regions, SIGNAL (triggered()), this, SLOT (addRegionsSubView())); QAction *regionMap = createMenuEntry(CSMWorld::UniversalId::Type_RegionMap, world, "document-world-regionmap"); connect (regionMap, SIGNAL (triggered()), this, SLOT (addRegionMapSubView())); @@ -187,14 +193,19 @@ void CSVDoc::View::setupMechanicsMenu() { QMenu *mechanics = menuBar()->addMenu (tr ("Mechanics")); + QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); + connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); + + QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); + connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); + QAction* globals = createMenuEntry(CSMWorld::UniversalId::Type_Globals, mechanics, "document-mechanics-globals"); connect (globals, SIGNAL (triggered()), this, SLOT (addGlobalsSubView())); QAction* gmsts = createMenuEntry(CSMWorld::UniversalId::Type_Gmsts, mechanics, "document-mechanics-gamesettings"); connect (gmsts, SIGNAL (triggered()), this, SLOT (addGmstsSubView())); - - QAction* scripts = createMenuEntry(CSMWorld::UniversalId::Type_Scripts, mechanics, "document-mechanics-scripts"); - connect (scripts, SIGNAL (triggered()), this, SLOT (addScriptsSubView())); + + mechanics->addSeparator(); QAction* spells = createMenuEntry(CSMWorld::UniversalId::Type_Spells, mechanics, "document-mechanics-spells"); connect (spells, SIGNAL (triggered()), this, SLOT (addSpellsSubView())); @@ -204,9 +215,6 @@ void CSVDoc::View::setupMechanicsMenu() QAction* magicEffects = createMenuEntry(CSMWorld::UniversalId::Type_MagicEffects, mechanics, "document-mechanics-magiceffects"); connect (magicEffects, SIGNAL (triggered()), this, SLOT (addMagicEffectsSubView())); - - QAction* startScripts = createMenuEntry(CSMWorld::UniversalId::Type_StartScripts, mechanics, "document-mechanics-startscripts"); - connect (startScripts, SIGNAL (triggered()), this, SLOT (addStartScriptsSubView())); } void CSVDoc::View::setupCharacterMenu() @@ -227,21 +235,25 @@ void CSVDoc::View::setupCharacterMenu() QAction* birthsigns = createMenuEntry(CSMWorld::UniversalId::Type_Birthsigns, characters, "document-character-birthsigns"); connect (birthsigns, SIGNAL (triggered()), this, SLOT (addBirthsignsSubView())); + + QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); + connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); + characters->addSeparator(); + QAction* topics = createMenuEntry(CSMWorld::UniversalId::Type_Topics, characters, "document-character-topics"); connect (topics, SIGNAL (triggered()), this, SLOT (addTopicsSubView())); - QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); - connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); - QAction* topicInfos = createMenuEntry(CSMWorld::UniversalId::Type_TopicInfos, characters, "document-character-topicinfos"); connect (topicInfos, SIGNAL (triggered()), this, SLOT (addTopicInfosSubView())); + + characters->addSeparator(); + + QAction* journals = createMenuEntry(CSMWorld::UniversalId::Type_Journals, characters, "document-character-journals"); + connect (journals, SIGNAL (triggered()), this, SLOT (addJournalsSubView())); QAction* journalInfos = createMenuEntry(CSMWorld::UniversalId::Type_JournalInfos, characters, "document-character-journalinfos"); connect (journalInfos, SIGNAL (triggered()), this, SLOT (addJournalInfosSubView())); - - QAction* bodyParts = createMenuEntry(CSMWorld::UniversalId::Type_BodyParts, characters, "document-character-bodyparts"); - connect (bodyParts, SIGNAL (triggered()), this, SLOT (addBodyPartsSubView())); } void CSVDoc::View::setupAssetsMenu() diff --git a/apps/opencs/view/render/cellarrow.cpp b/apps/opencs/view/render/cellarrow.cpp index a654171fce..776dbf7890 100644 --- a/apps/opencs/view/render/cellarrow.cpp +++ b/apps/opencs/view/render/cellarrow.cpp @@ -60,7 +60,7 @@ void CSVRender::CellArrow::adjustTransform() { // position const int cellSize = Constants::CellSizeInUnits; - const int offset = cellSize / 2 + 800; + const int offset = cellSize / 2 + 600; int x = mCoordinates.getX()*cellSize + cellSize/2; int y = mCoordinates.getY()*cellSize + cellSize/2; @@ -92,9 +92,9 @@ void CSVRender::CellArrow::buildShape() { osg::ref_ptr geometry (new osg::Geometry); - const int arrowWidth = 4000; - const int arrowLength = 1500; - const int arrowHeight = 500; + const int arrowWidth = 2700; + const int arrowLength = 1350; + const int arrowHeight = 300; osg::Vec3Array *vertices = new osg::Vec3Array; for (int i2=0; i2<2; ++i2) diff --git a/apps/opencs/view/world/enumdelegate.cpp b/apps/opencs/view/world/enumdelegate.cpp index 65ded46c7f..2681a398b2 100644 --- a/apps/opencs/view/world/enumdelegate.cpp +++ b/apps/opencs/view/world/enumdelegate.cpp @@ -78,6 +78,8 @@ QWidget *CSVWorld::EnumDelegate::createEditor(QWidget *parent, const QStyleOptio for (std::vector >::const_iterator iter (mValues.begin()); iter!=mValues.end(); ++iter) comboBox->addItem (iter->second); + + comboBox->setMaxVisibleItems(20); return comboBox; } From 686268b2f90de54a39000b5424fe02c125d6c034 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 3 Oct 2021 21:39:43 +0200 Subject: [PATCH 110/137] Remove incorrect changelog entries --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7ef5b62a..9a813d704f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,6 @@ Bug #5508: Engine binary links to Qt without using it Bug #5596: Effects in constant spells should not be merged Bug #5621: Drained stats cannot be restored - Bug #5755: Active grid object paging - disappearing textures Bug #5766: Active grid object paging - disappearing textures Bug #5788: Texture editing parses the selected indexes wrongly Bug #5801: A multi-effect spell with the intervention effects and recall always favors Almsivi intervention @@ -45,7 +44,6 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6197: Infinite Casting Loop - Bug #6223: Some Constant Effect Bound Items inconsistencies Bug #6273: Respawning NPCs rotation is inconsistent Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death @@ -59,7 +57,6 @@ Feature #4297: Implement APPLIED_ONCE flag for magic effects Feature #4414: Handle duration of EXTRA SPELL magic effect Feature #4595: Unique object identifier - Feature #4737: Handle instance move from one cell to another Feature #5198: Implement "Magic effect expired" event Feature #5454: Clear active spells from actor when he disappears from scene Feature #5489: MCP: Telekinesis fix for activators From d680aa26e9033615befede2e915dc0b67a69869c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 3 Oct 2021 21:58:10 +0200 Subject: [PATCH 111/137] Disallow switch fallthrough --- CMakeLists.txt | 4 ++++ apps/opencs/model/world/refidadapterimp.hpp | 1 + apps/openmw/mwmechanics/spelleffects.cpp | 2 ++ 3 files changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b8350a04b..ae4584a4cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -543,6 +543,10 @@ if (BUILD_OPENCS) add_subdirectory (extern/osgQt) endif() +if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=implicit-fallthrough") +endif() + # Components add_subdirectory (components) diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 84fec5bed0..c35d3c5a7c 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1870,6 +1870,7 @@ namespace CSMWorld content.mWander.mDuration = static_cast(value.toInt()); else return; // return without saving + break; case 3: if (content.mType == ESM::AI_Wander) content.mWander.mTimeOfDay = static_cast(value.toInt()); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 24816b9ed1..fa2733d837 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -453,6 +453,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co } addBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); // left gauntlet added below + [[fallthrough]]; case ESM::MagicEffect::BoundDagger: case ESM::MagicEffect::BoundLongsword: case ESM::MagicEffect::BoundMace: @@ -920,6 +921,7 @@ void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellPara break; case ESM::MagicEffect::BoundGloves: removeBoundItem(world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString(), target); + [[fallthrough]]; case ESM::MagicEffect::BoundDagger: case ESM::MagicEffect::BoundLongsword: case ESM::MagicEffect::BoundMace: From aaf7b423d6796169f382883a669ebfb9c248dc96 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 4 Oct 2021 08:56:55 +0000 Subject: [PATCH 112/137] adds a replacement for osg::NodeCallback (#3144) * nodecallback.hpp * lightmanager.hpp * lightmanager.cpp * lightmanager.hpp * nodecallback.hpp * nodecallback.hpp * [ci skip] * lightmanager.hpp * nodecallback.hpp * nodecallback.hpp * lightmanager.cpp * lightmanager.cpp * nodecallback.hpp * [ci skip] * [ci skip] * controller.cpp * [ci skip] * osgacontroller.cpp * keyframe.hpp * controller.hpp * keyframe.hpp * [ci skip] * keyframe.hpp * animation.hpp * [ci skip] * weaponanimation.cpp * nodecallback.hpp --- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwrender/weaponanimation.cpp | 2 +- apps/openmw/mwrender/weaponanimation.hpp | 2 +- components/nifosg/controller.cpp | 1 + components/nifosg/controller.hpp | 5 +-- components/sceneutil/keyframe.hpp | 8 ++--- components/sceneutil/lightmanager.cpp | 24 ++++---------- components/sceneutil/lightmanager.hpp | 7 ++-- components/sceneutil/nodecallback.hpp | 41 ++++++++++++++++++++++++ components/sceneutil/osgacontroller.cpp | 2 +- components/sceneutil/osgacontroller.hpp | 5 +-- 11 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 components/sceneutil/nodecallback.hpp diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index ffcd9b3260..c2f0f351f3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -245,7 +245,7 @@ protected: // Keep track of controllers that we added to our scene graph. // We may need to rebuild these controllers when the active animation groups / sources change. - std::vector, osg::ref_ptr>> mActiveControllers; + std::vector, osg::ref_ptr>> mActiveControllers; std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index fb92a4980a..b89cfd8df1 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -173,7 +173,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor, float attackStrength) } void WeaponAnimation::addControllers(const std::map >& nodes, - std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) + std::vector, osg::ref_ptr>> &map, osg::Node* objectRoot) { for (int i=0; i<2; ++i) { diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index 333dccc915..1f614463a6 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -43,7 +43,7 @@ namespace MWRender /// Add WeaponAnimation-related controllers to \a nodes and store the added controllers in \a map. void addControllers(const std::map >& nodes, - std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); + std::vector, osg::ref_ptr>>& map, osg::Node* objectRoot); void deleteControllers(); diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index d48c55ad7d..c8a6ef74ab 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -72,6 +72,7 @@ KeyframeController::KeyframeController() KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) + , SceneUtil::NodeCallback(copy, copyop) , mRotations(copy.mRotations) , mXRotations(copy.mXRotations) , mYRotations(copy.mYRotations) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index b459166931..078b7e14ce 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -223,7 +224,7 @@ namespace NifOsg std::vector mKeyFrames; }; - class KeyframeController : public SceneUtil::KeyframeController + class KeyframeController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: // This is used if there's no interpolator but there is data (Morrowind meshes). @@ -241,7 +242,7 @@ namespace NifOsg osg::Vec3f getTranslation(float time) const override; - void operator() (osg::Node*, osg::NodeVisitor*) override; + void operator() (osg::Node*, osg::NodeVisitor*); private: QuaternionInterpolator mRotations; diff --git a/components/sceneutil/keyframe.hpp b/components/sceneutil/keyframe.hpp index e09541cb9a..5be6924a09 100644 --- a/components/sceneutil/keyframe.hpp +++ b/components/sceneutil/keyframe.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include #include @@ -11,20 +11,18 @@ namespace SceneUtil { - class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller + class KeyframeController : public SceneUtil::Controller, public virtual osg::Callback { public: KeyframeController() {} KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) + : osg::Callback(copy, copyop) , SceneUtil::Controller(copy) {} META_Object(SceneUtil, KeyframeController) virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } - - virtual void operator() (osg::Node* node, osg::NodeVisitor* nodeVisitor) override { traverse(node, nodeVisitor); } }; /// Wrapper object containing an animation track as a ref-countable osg::Object. diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index ba60cb3185..09fa302457 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -605,19 +605,19 @@ namespace SceneUtil // Set on a LightSource. Adds the light source to its light manager for the current frame. // This allows us to keep track of the current lights in the scene graph without tying creation & destruction to the manager. - class CollectLightCallback : public osg::NodeCallback + class CollectLightCallback : public SceneUtil::NodeCallback { public: CollectLightCallback() : mLightManager(nullptr) { } CollectLightCallback(const CollectLightCallback& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , mLightManager(nullptr) { } META_Object(SceneUtil, SceneUtil::CollectLightCallback) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (!mLightManager) { @@ -637,19 +637,11 @@ namespace SceneUtil }; // Set on a LightManager. Clears the data from the previous frame. - class LightManagerUpdateCallback : public osg::NodeCallback + class LightManagerUpdateCallback : public SceneUtil::NodeCallback { public: - LightManagerUpdateCallback() - { } - - LightManagerUpdateCallback(const LightManagerUpdateCallback& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) - { } - - META_Object(SceneUtil, LightManagerUpdateCallback) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { LightManager* lightManager = static_cast(node); lightManager->update(nv->getTraversalNumber()); @@ -1285,12 +1277,10 @@ namespace SceneUtil mLight[i] = new osg::Light(*copy.mLight[i].get(), copyop); } - void LightListCallback::operator()(osg::Node *node, osg::NodeVisitor *nv) + void LightListCallback::operator()(osg::Node *node, osgUtil::CullVisitor *cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - bool pushedState = pushLightState(node, cv); - traverse(node, nv); + traverse(node, cv); if (pushedState) cv->popStateSet(); } diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 6dbe7a3f75..4048bf9b09 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -16,6 +16,7 @@ #include #include +#include namespace osgUtil { @@ -254,7 +255,7 @@ namespace SceneUtil /// starting point is to attach a LightListCallback to each game object's base node. /// @note Not thread safe for CullThreadPerCamera threading mode. /// @note Due to lack of OSG support, the callback does not work on Drawables. - class LightListCallback : public osg::NodeCallback + class LightListCallback : public SceneUtil::NodeCallback { public: LightListCallback() @@ -262,7 +263,7 @@ namespace SceneUtil , mLastFrameNumber(0) {} LightListCallback(const LightListCallback& copy, const osg::CopyOp& copyop) - : osg::Object(copy, copyop), osg::NodeCallback(copy, copyop) + : osg::Object(copy, copyop), SceneUtil::NodeCallback(copy, copyop) , mLightManager(copy.mLightManager) , mLastFrameNumber(0) , mIgnoredLightSources(copy.mIgnoredLightSources) @@ -270,7 +271,7 @@ namespace SceneUtil META_Object(SceneUtil, LightListCallback) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::Node* node, osgUtil::CullVisitor* nv); bool pushLightState(osg::Node* node, osgUtil::CullVisitor* nv); diff --git a/components/sceneutil/nodecallback.hpp b/components/sceneutil/nodecallback.hpp new file mode 100644 index 0000000000..6f0140d64c --- /dev/null +++ b/components/sceneutil/nodecallback.hpp @@ -0,0 +1,41 @@ +#ifndef SCENEUTIL_NODECALLBACK_H +#define SCENEUTIL_NODECALLBACK_H + +#include + +namespace osg +{ + class Node; + class NodeVisitor; +} + +namespace SceneUtil +{ + +template +class NodeCallback : public virtual osg::Callback +{ +public: + NodeCallback(){} + NodeCallback(const NodeCallback& nc,const osg::CopyOp& copyop): + osg::Callback(nc, copyop) {} + META_Object(SceneUtil, NodeCallback) + + bool run(osg::Object* object, osg::Object* data) override + { + static_cast(this)->operator()((NodeType)object, (VisitorType)data->asNodeVisitor()); + return true; + } + + template + void traverse(NodeType object, VT data) + { + if (_nestedCallback.valid()) + _nestedCallback->run(object, data); + else + data->traverse(*object); + } +}; + +} +#endif diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index 02c9558ab3..b986c92884 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -102,7 +102,7 @@ namespace SceneUtil apply(static_cast(node)); } - OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) + OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop), SceneUtil::NodeCallback(copy, copyop) , mEmulatedAnimations(copy.mEmulatedAnimations) { mLinker = nullptr; diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 46538fa813..427a79ca97 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -44,7 +45,7 @@ namespace SceneUtil Resource::Animation* mAnimation; }; - class OsgAnimationController : public SceneUtil::KeyframeController + class OsgAnimationController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: /// @brief Handles the animation for osgAnimation formats @@ -61,7 +62,7 @@ namespace SceneUtil void update(float time, const std::string& animationName); /// @brief Called every frame for osgAnimation - void operator() (osg::Node*, osg::NodeVisitor*) override; + void operator() (osg::Node*, osg::NodeVisitor*); /// @brief Sets details of the animations void setEmulatedAnimations(const std::vector& emulatedAnimations); From 61168c3583870cc0fc6c8d91a9839e3b9489c086 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 4 Oct 2021 13:12:54 +0400 Subject: [PATCH 113/137] Add missing changelog entries for 0.47 (#3145) --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7ef5b62a..cfa8c2e79f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -209,6 +209,7 @@ Bug #6043: Actor can have torch missing when torch animation is played Bug #6047: Mouse bindings can be triggered during save loading Bug #6136: Game freezes when NPCs try to open doors that are about to be closed + Bug #6294: Game crashes with empty pathgrid Feature #390: 3rd person look "over the shoulder" Feature #832: OpenMW-CS: Handle deleted references Feature #1536: Show more information about level on menu @@ -238,6 +239,7 @@ Feature #5519: Code Patch tab in launcher Feature #5524: Resume failed script execution after reload Feature #5545: Option to allow stealing from an unconscious NPC during combat + Feature #5551: Do not reboot PC after OpenMW installation on Windows Feature #5563: Run physics update in background thread Feature #5579: MCP SetAngle enhancement Feature #5580: Service refusal filtering @@ -252,6 +254,7 @@ Feature #5814: Bsatool should be able to create BSA archives, not only to extract it Feature #5828: Support more than 8 lights Feature #5910: Fall back to delta time when physics can't keep up + Feature #5980: Support Bullet with double precision instead of one with single precision Feature #6024: OpenMW-CS: Selecting terrain in "Terrain land editing" should support "Add to selection" and "Remove from selection" modes Feature #6033: Include pathgrid to navigation mesh Feature #6034: Find path based on area cost depending on NPC stats From e4a9eefc2aceb75d491f98e9b36985f70f93e7ee Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 4 Oct 2021 10:12:00 +0000 Subject: [PATCH 114/137] uses a bitfield in refdata.hpp (#3143) * uses a bitfield in refdata.hpp With this simple change we pack boolean values to reduce the memory requirements of game objects. * refdata.hpp [ci skip] * refdata.cpp --- apps/openmw/mwworld/refdata.cpp | 8 ++++---- apps/openmw/mwworld/refdata.hpp | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 7398aef77e..f4c4beb7e8 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -65,21 +65,21 @@ namespace MWWorld } RefData::RefData (const ESM::CellRef& cellRef) - : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), + : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mPhysicsPostponed(false), mCount (1), mPosition (cellRef.mPos), mCustomData (nullptr), - mChanged(false), mFlags(0), mPhysicsPostponed(false) // Loading from ESM/ESP files -> assume unchanged + mChanged(false), mFlags(0) // Loading from ESM/ESP files -> assume unchanged { } RefData::RefData (const ESM::ObjectState& objectState, bool deletedByContentFile) : mBaseNode(nullptr), mDeletedByContentFile(deletedByContentFile), - mEnabled (objectState.mEnabled != 0), + mEnabled (objectState.mEnabled != 0), mPhysicsPostponed(false), mCount (objectState.mCount), mPosition (objectState.mPosition), mAnimationState(objectState.mAnimationState), mCustomData (nullptr), - mChanged(true), mFlags(objectState.mFlags), mPhysicsPostponed(false) // Loading from a savegame -> assume changed + mChanged(true), mFlags(objectState.mFlags) // Loading from a savegame -> assume changed { // "Note that the ActivationFlag_UseEnabled is saved to the reference, // which will result in permanently suppressed activation if the reference script is removed. diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index d90224b9bb..c98eb0f2af 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -41,9 +41,12 @@ namespace MWWorld /// separate delete flag used for deletion by a content file /// @note not stored in the save game file. - bool mDeletedByContentFile; + bool mDeletedByContentFile:1; - bool mEnabled; + bool mEnabled:1; + public: + bool mPhysicsPostponed:1; + private: /// 0: deleted int mCount; @@ -63,9 +66,6 @@ namespace MWWorld unsigned int mFlags; public: - - bool mPhysicsPostponed; - RefData(); /// @param cellRef Used to copy constant data such as position into this class where it can From 14d15dcfac77647450e30561153cce3c2aac1f74 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 4 Oct 2021 10:20:33 +0000 Subject: [PATCH 115/137] cleans up osgacontroller.cpp (#3142) `handle_stateset` is not needed because `UpdateMatrixTransform` is a `NodeCallback` only allowed to be set on a `Node`. `Geode` and `Drawable` do not need explicit logic because they are both derived from `Node`. --- components/sceneutil/osgacontroller.cpp | 38 ++----------------------- components/sceneutil/osgacontroller.hpp | 10 ------- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/components/sceneutil/osgacontroller.cpp b/components/sceneutil/osgacontroller.cpp index b986c92884..520b2d177b 100644 --- a/components/sceneutil/osgacontroller.cpp +++ b/components/sceneutil/osgacontroller.cpp @@ -1,19 +1,12 @@ #include -#include #include #include #include -#include #include -#include #include -#include -#include #include -#include -#include #include #include @@ -55,19 +48,6 @@ namespace SceneUtil } } - void LinkVisitor::handle_stateset(osg::StateSet* stateset) - { - if (!stateset) - return; - const osg::StateSet::AttributeList& attributeList = stateset->getAttributeList(); - for (auto attribute : attributeList) - { - osg::StateAttribute* sattr = attribute.second.first.get(); - osgAnimation::UpdateMatrixTransform* umt = dynamic_cast(sattr->getUpdateCallback()); //Can this even be in sa? - if (umt) link(umt); - } - } - void LinkVisitor::setAnimation(Resource::Animation* animation) { mAnimation = animation; @@ -75,10 +55,6 @@ namespace SceneUtil void LinkVisitor::apply(osg::Node& node) { - osg::StateSet* st = node.getStateSet(); - if (st) - handle_stateset(st); - osg::Callback* cb = node.getUpdateCallback(); while (cb) { @@ -88,18 +64,8 @@ namespace SceneUtil cb = cb->getNestedCallback(); } - traverse( node ); - } - - void LinkVisitor::apply(osg::Geode& node) - { - for (unsigned int i = 0; i < node.getNumDrawables(); i++) - { - osg::Drawable* drawable = node.getDrawable(i); - if (drawable && drawable->getStateSet()) - handle_stateset(drawable->getStateSet()); - } - apply(static_cast(node)); + if (node.getNumChildrenRequiringUpdateTraversal()) + traverse( node ); } OsgAnimationController::OsgAnimationController(const OsgAnimationController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop), SceneUtil::NodeCallback(copy, copyop) diff --git a/components/sceneutil/osgacontroller.hpp b/components/sceneutil/osgacontroller.hpp index 427a79ca97..26212a3b99 100644 --- a/components/sceneutil/osgacontroller.hpp +++ b/components/sceneutil/osgacontroller.hpp @@ -3,13 +3,7 @@ #include #include -#include #include -#include -#include -#include -#include -#include #include #include @@ -33,14 +27,10 @@ namespace SceneUtil virtual void link(osgAnimation::UpdateMatrixTransform* umt); - virtual void handle_stateset(osg::StateSet* stateset); - virtual void setAnimation(Resource::Animation* animation); virtual void apply(osg::Node& node) override; - virtual void apply(osg::Geode& node) override; - protected: Resource::Animation* mAnimation; }; From 3f731cd102cefe246ad897bc9c5ec8c2d720c6e5 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Mon, 4 Oct 2021 20:00:31 +0000 Subject: [PATCH 116/137] attempts to fix spellcasting freezes (#3146) Firstly, this PR reintroduces commit "Recreate a special case for IntersectionVisitor on QuadTreeWorld" we forgot to reapply while reverting a revert commit. Secondly, in cases we still need to build a view for an intersection visitor, we now use the available `osgUtil::IntersectionVisitor::getReferenceEyePoint` instead of falling back to the origin position that was previously causing long rebuild times. --- components/terrain/quadtreeworld.cpp | 6 +++--- components/terrain/viewdata.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 4baf08149d..7e5cd001ba 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -456,13 +456,13 @@ void QuadTreeWorld::accept(osg::NodeVisitor &nv) osg::Object * viewer = isCullVisitor ? static_cast(&nv)->getCurrentCamera() : nullptr; bool needsUpdate = true; - ViewData *vd = mViewDataMap->getViewData(viewer, nv.getViewPoint(), mActiveGrid, needsUpdate); - + osg::Vec3f viewPoint = viewer ? nv.getViewPoint() : nv.getEyePoint(); + ViewData *vd = mViewDataMap->getViewData(viewer, viewPoint, mActiveGrid, needsUpdate); if (needsUpdate) { vd->reset(); DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); - mRootNode->traverseNodes(vd, nv.getViewPoint(), &lodCallback); + mRootNode->traverseNodes(vd, viewPoint, &lodCallback); } const float cellWorldSize = mStorage->getCellWorldSize(); diff --git a/components/terrain/viewdata.cpp b/components/terrain/viewdata.cpp index c5070fdf0d..3ebc99f1df 100644 --- a/components/terrain/viewdata.cpp +++ b/components/terrain/viewdata.cpp @@ -143,7 +143,7 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo if (!(vd->suitableToUse(activeGrid) && (vd->getViewPoint()-viewPoint).length2() < mReuseDistance*mReuseDistance && vd->getWorldUpdateRevision() >= mWorldUpdateRevision)) { - float shortestDist = mReuseDistance*mReuseDistance; + float shortestDist = viewer ? mReuseDistance*mReuseDistance : std::numeric_limits::max(); const ViewData* mostSuitableView = nullptr; for (const ViewData* other : mUsedViews) { @@ -157,12 +157,12 @@ ViewData *ViewDataMap::getViewData(osg::Object *viewer, const osg::Vec3f& viewPo } } } - if (mostSuitableView) + if (mostSuitableView && mostSuitableView != vd) { vd->copyFrom(*mostSuitableView); return vd; } - else + else if (!mostSuitableView) { vd->setViewPoint(viewPoint); vd->setActiveGrid(activeGrid); From 8e9851c97c43596b269fbb9d739ff28999b06729 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 11:52:19 +0000 Subject: [PATCH 117/137] npcanimation.cpp (#3148) --- apps/openmw/mwrender/npcanimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index bcb2d36f5d..f430432659 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -403,7 +403,7 @@ public: { osgUtil::CullVisitor* cv = static_cast(nv); float fov, aspect, zNear, zFar; - if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar)) + if (cv->getProjectionMatrix()->getPerspective(fov, aspect, zNear, zFar) && std::abs(fov-mFov) > 0.001) { fov = mFov; osg::ref_ptr newProjectionMatrix = new osg::RefMatrix(); From 9f58616aa032a716013d543788ba26b0727c7209 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 12:02:49 +0000 Subject: [PATCH 118/137] fixes -Wreorder warning --- apps/openmw/mwworld/refdata.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index f4c4beb7e8..e8c5ba35e4 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -55,7 +55,7 @@ namespace MWWorld } RefData::RefData() - : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mCount (1), mCustomData (nullptr), mChanged(false), mFlags(0), mPhysicsPostponed(false) + : mBaseNode(nullptr), mDeletedByContentFile(false), mEnabled (true), mPhysicsPostponed(false), mCount (1), mCustomData (nullptr), mChanged(false), mFlags(0) { for (int i=0; i<3; ++i) { From b2af81bc18bc66f00551731d59a2699ebacc3a4e Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 12:21:12 +0000 Subject: [PATCH 119/137] converts remaining osg::NodeCallback (#3147) With this PR we convert remaining instantiations of the deprecated osg::NodeCallback in Open MW to SceneUtil::NodeCallback. --- apps/openmw/mwrender/animation.cpp | 16 +++--- apps/openmw/mwrender/animation.hpp | 5 +- apps/openmw/mwrender/camera.cpp | 9 ++- apps/openmw/mwrender/camera.hpp | 4 +- apps/openmw/mwrender/characterpreview.cpp | 13 ++--- apps/openmw/mwrender/globalmap.cpp | 9 +-- apps/openmw/mwrender/localmap.cpp | 8 +-- apps/openmw/mwrender/npcanimation.cpp | 40 +------------- apps/openmw/mwrender/npcanimation.hpp | 4 +- apps/openmw/mwrender/postprocessor.cpp | 12 ++-- apps/openmw/mwrender/rotatecontroller.cpp | 16 ++++-- apps/openmw/mwrender/rotatecontroller.hpp | 15 +++-- apps/openmw/mwrender/sky.cpp | 31 +++++------ apps/openmw/mwrender/water.cpp | 37 +++++-------- apps/openmw/mwrender/water.hpp | 5 +- apps/openmw/mwworld/projectilemanager.cpp | 9 ++- .../myguiplatform/myguirendermanager.cpp | 16 ++---- components/nifosg/controller.cpp | 55 +++++++++---------- components/nifosg/controller.hpp | 43 +++++++++------ components/nifosg/nifloader.cpp | 11 ++-- components/nifosg/particle.cpp | 6 +- components/nifosg/particle.hpp | 10 ++-- components/resource/keyframemanager.cpp | 1 + components/resource/scenemanager.cpp | 10 ++-- components/sceneutil/lightcontroller.cpp | 7 +-- components/sceneutil/lightcontroller.hpp | 9 +-- components/sceneutil/lightmanager.cpp | 27 ++++----- components/sceneutil/rtt.cpp | 14 ++--- components/sceneutil/statesetupdater.cpp | 2 +- components/sceneutil/statesetupdater.hpp | 6 +- components/sceneutil/util.cpp | 10 ++-- components/sceneutil/util.hpp | 1 - components/terrain/world.hpp | 7 ++- 33 files changed, 206 insertions(+), 262 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 1dc42db47c..475c656cc9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -87,22 +87,22 @@ namespace std::vector > mToRemove; }; - class DayNightCallback : public osg::NodeCallback + class DayNightCallback : public SceneUtil::NodeCallback { public: DayNightCallback() : mCurrentState(0) { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Switch* node, osg::NodeVisitor* nv) { unsigned int state = MWBase::Environment::get().getWorld()->getNightDayMode(); - const unsigned int newState = node->asGroup()->getNumChildren() > state ? state : 0; + const unsigned int newState = node->getNumChildren() > state ? state : 0; if (newState != mCurrentState) { mCurrentState = newState; - node->asSwitch()->setSingleChildOn(mCurrentState); + node->setSingleChildOn(mCurrentState); } traverse(node, nv); @@ -472,20 +472,18 @@ namespace MWRender } } - class ResetAccumRootCallback : public osg::NodeCallback + class ResetAccumRootCallback : public SceneUtil::NodeCallback { public: - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix mat = transform->getMatrix(); osg::Vec3f position = mat.getTrans(); position = osg::componentMultiply(mResetAxes, position); mat.setTrans(position); transform->setMatrix(mat); - traverse(node, nv); + traverse(transform, nv); } void setAccumulate(const osg::Vec3f& accumulate) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index c2f0f351f3..84788c1e2d 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -506,7 +507,7 @@ public: bool canBeHarvested() const override; }; -class UpdateVfxCallback : public osg::NodeCallback +class UpdateVfxCallback : public SceneUtil::NodeCallback { public: UpdateVfxCallback(EffectParams& params) @@ -519,7 +520,7 @@ public: bool mFinished; EffectParams mParams; - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::Node* node, osg::NodeVisitor* nv); private: double mStartingTime; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index edb017c2a2..e750fcad46 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -25,7 +26,7 @@ namespace { -class UpdateRenderCameraCallback : public osg::NodeCallback +class UpdateRenderCameraCallback : public SceneUtil::NodeCallback { public: UpdateRenderCameraCallback(MWRender::Camera* cam) @@ -33,12 +34,10 @@ public: { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Camera* cam, osg::NodeVisitor* nv) { - osg::Camera* cam = static_cast(node); - // traverse first to update animations, in case the camera is attached to an animated node - traverse(node, nv); + traverse(cam, nv); mCamera->updateCamera(cam); } diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 9e2b608dfd..1a5477e89c 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -12,7 +12,7 @@ namespace osg { class Camera; - class NodeCallback; + class Callback; class Node; } @@ -82,7 +82,7 @@ namespace MWRender void updatePosition(); float getCameraDistanceCorrection() const; - osg::ref_ptr mUpdateCallback; + osg::ref_ptr mUpdateCallback; // Used to rotate player to the direction of view after exiting preview or vanity mode. osg::Vec3f mDeferredRotation; diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 44b7b2f457..51e5a536f7 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "../mwbase/world.hpp" #include "../mwworld/class.hpp" @@ -36,7 +37,7 @@ namespace MWRender { - class DrawOnceCallback : public osg::NodeCallback + class DrawOnceCallback : public SceneUtil::NodeCallback { public: DrawOnceCallback () @@ -45,7 +46,7 @@ namespace MWRender { } - void operator () (osg::Node* node, osg::NodeVisitor* nv) override + void operator () (osg::Node* node, osg::NodeVisitor* nv) { if (!mRendered) { @@ -523,7 +524,7 @@ namespace MWRender rebuild(); } - class UpdateCameraCallback : public osg::NodeCallback + class UpdateCameraCallback : public SceneUtil::NodeCallback { public: UpdateCameraCallback(osg::ref_ptr nodeToFollow, const osg::Vec3& posOffset, const osg::Vec3& lookAtOffset) @@ -533,12 +534,10 @@ namespace MWRender { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Camera* cam, osg::NodeVisitor* nv) { - osg::Camera* cam = static_cast(node); - // Update keyframe controllers in the scene graph first... - traverse(node, nv); + traverse(cam, nv); // Now update camera utilizing the updated head position osg::NodePathList nodepaths = mNodeToFollow->getParentalNodePaths(); diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 7d7c6e45df..b248efad4e 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -16,6 +16,7 @@ #include #include +#include #include @@ -62,7 +63,7 @@ namespace } - class CameraUpdateGlobalCallback : public osg::NodeCallback + class CameraUpdateGlobalCallback : public SceneUtil::NodeCallback { public: CameraUpdateGlobalCallback(MWRender::GlobalMap* parent) @@ -71,14 +72,14 @@ namespace { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Camera* node, osg::NodeVisitor* nv) { if (mRendered) { - if (mParent->copyResult(static_cast(node), nv->getTraversalNumber())) + if (mParent->copyResult(node, nv->getTraversalNumber())) { node->setNodeMask(0); - mParent->markForRemoval(static_cast(node)); + mParent->markForRemoval(node); } return; } diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 02f59e0ed0..0560d1485a 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -35,7 +36,7 @@ namespace { - class CameraLocalUpdateCallback : public osg::NodeCallback + class CameraLocalUpdateCallback : public SceneUtil::NodeCallback { public: CameraLocalUpdateCallback(MWRender::LocalMap* parent) @@ -44,7 +45,7 @@ namespace { } - void operator()(osg::Node* node, osg::NodeVisitor*) override + void operator()(osg::Camera* node, osg::NodeVisitor*) { if (mRendered) node->setNodeMask(0); @@ -52,12 +53,11 @@ namespace if (!mRendered) { mRendered = true; - mParent->markForRemoval(static_cast(node)); + mParent->markForRemoval(node); } // Note, we intentionally do not traverse children here. The map camera's scene data is the same as the master camera's, // so it has been updated already. - //traverse(node, nv); } private: diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f430432659..7c6d82d83d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -148,44 +148,6 @@ public: float getValue(osg::NodeVisitor* nv) override; }; -// -------------------------------------------------------------------------------- - -/// Subclass RotateController to add a Z-offset for sneaking in first person mode. -/// @note We use inheritance instead of adding another controller, so that we do not have to compute the worldOrient twice. -/// @note Must be set on a MatrixTransform. -class NeckController : public RotateController -{ -public: - NeckController(osg::Node* relativeTo) - : RotateController(relativeTo) - { - } - - void setOffset(const osg::Vec3f& offset) - { - mOffset = offset; - } - - void operator()(osg::Node* node, osg::NodeVisitor* nv) override - { - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix matrix = transform->getMatrix(); - - osg::Quat worldOrient = getWorldOrientation(node); - osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); - - matrix.setRotate(orient); - matrix.setTrans(matrix.getTrans() + worldOrient.inverse() * mOffset); - - transform->setMatrix(matrix); - - traverse(node,nv); - } - -private: - osg::Vec3f mOffset; -}; - // -------------------------------------------------------------------------------------------------------------- HeadAnimationTime::HeadAnimationTime(const MWWorld::Ptr& reference) @@ -954,7 +916,7 @@ void NpcAnimation::addControllers() if (found != mNodeMap.end()) { osg::MatrixTransform* node = found->second.get(); - mFirstPersonNeckController = new NeckController(mObjectRoot.get()); + mFirstPersonNeckController = new RotateController(mObjectRoot.get()); node->addUpdateCallback(mFirstPersonNeckController); mActiveControllers.emplace_back(node, mFirstPersonNeckController); } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 9f7a186c5e..b511a52f37 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -24,7 +24,7 @@ namespace MWSound namespace MWRender { -class NeckController; +class RotateController; class HeadAnimationTime; class NpcAnimation : public ActorAnimation, public WeaponAnimation, public MWWorld::InventoryStoreListener @@ -96,7 +96,7 @@ private: void setRenderBin(); - osg::ref_ptr mFirstPersonNeckController; + osg::ref_ptr mFirstPersonNeckController; static bool isFirstPersonPart(const ESM::BodyPart* bodypart); static bool isFemalePart(const ESM::BodyPart* bodypart); diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index f846a05872..bc00676d7b 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include "vismask.hpp" @@ -33,7 +34,7 @@ namespace return geom; } - class CullCallback : public osg::NodeCallback + class CullCallback : public SceneUtil::NodeCallback { public: CullCallback() @@ -41,12 +42,11 @@ namespace { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); - unsigned int frame = nv->getTraversalNumber(); + unsigned int frame = cv->getTraversalNumber(); if (frame != mLastFrameNumber) { mLastFrameNumber = frame; @@ -56,7 +56,7 @@ namespace if (!postProcessor) { Log(Debug::Error) << "Failed retrieving user data for master camera: FBO setup failed"; - traverse(node, nv); + traverse(node, cv); return; } @@ -71,7 +71,7 @@ namespace } } - traverse(node, nv); + traverse(node, cv); } private: diff --git a/apps/openmw/mwrender/rotatecontroller.cpp b/apps/openmw/mwrender/rotatecontroller.cpp index 534cc74906..d3df4364d0 100644 --- a/apps/openmw/mwrender/rotatecontroller.cpp +++ b/apps/openmw/mwrender/rotatecontroller.cpp @@ -22,21 +22,27 @@ void RotateController::setRotate(const osg::Quat &rotate) mRotate = rotate; } -void RotateController::operator()(osg::Node *node, osg::NodeVisitor *nv) +void RotateController::setOffset(const osg::Vec3f& offset) +{ + mOffset = offset; +} + +void RotateController::operator()(osg::MatrixTransform *node, osg::NodeVisitor *nv) { if (!mEnabled) { traverse(node, nv); return; } - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix matrix = transform->getMatrix(); + osg::Matrix matrix = node->getMatrix(); osg::Quat worldOrient = getWorldOrientation(node); + osg::Quat worldOrientInverse = worldOrient.inverse(); - osg::Quat orient = worldOrient * mRotate * worldOrient.inverse() * matrix.getRotate(); + osg::Quat orient = worldOrient * mRotate * worldOrientInverse * matrix.getRotate(); matrix.setRotate(orient); + matrix.setTrans(matrix.getTrans() + worldOrientInverse * mOffset); - transform->setMatrix(matrix); + node->setMatrix(matrix); traverse(node,nv); } diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp index 9d4080ac6e..1f3ee0f845 100644 --- a/apps/openmw/mwrender/rotatecontroller.hpp +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -1,31 +1,36 @@ #ifndef OPENMW_MWRENDER_ROTATECONTROLLER_H #define OPENMW_MWRENDER_ROTATECONTROLLER_H -#include +#include #include +namespace osg +{ + class MatrixTransform; +} + namespace MWRender { /// Applies a rotation in \a relativeTo's space. /// @note Assumes that the node being rotated has its "original" orientation set every frame by a different controller. /// The rotation is then applied on top of that orientation. -/// @note Must be set on a MatrixTransform. -class RotateController : public osg::NodeCallback +class RotateController : public SceneUtil::NodeCallback { public: RotateController(osg::Node* relativeTo); void setEnabled(bool enabled); - + void setOffset(const osg::Vec3f& offset); void setRotate(const osg::Quat& rotate); - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); protected: osg::Quat getWorldOrientation(osg::Node* node); bool mEnabled; + osg::Vec3f mOffset; osg::Quat mRotate; osg::Node* mRelativeTo; }; diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index c1b79843a7..c2ed62ef87 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include @@ -301,13 +302,11 @@ public: return osg::BoundingSphere(osg::Vec3f(0,0,0), 0); } - class CullCallback : public osg::NodeCallback + class CullCallback : public SceneUtil::NodeCallback { public: - void operator() (osg::Node* node, osg::NodeVisitor* nv) override + void operator() (osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - // XXX have to remove unwanted culling plane of the water reflection camera // Remove all planes that aren't from the standard frustum @@ -336,7 +335,7 @@ public: cv->getProjectionCullingStack().back().pushCurrentMask(); cv->getCurrentCullingSet().pushCurrentMask(); - traverse(node, nv); + traverse(node, cv); cv->getProjectionCullingStack().back().popCurrentMask(); cv->getCurrentCullingSet().popCurrentMask(); @@ -398,7 +397,7 @@ private: /// @note Must be added as cull callback. /// @note Meant to be used on a node that is child of a CameraRelativeTransform. /// The current view point must be retrieved by the CameraRelativeTransform since we can't get it anymore once we are in camera-relative space. -class UnderwaterSwitchCallback : public osg::NodeCallback +class UnderwaterSwitchCallback : public SceneUtil::NodeCallback { public: UnderwaterSwitchCallback(CameraRelativeTransform* cameraRelativeTransform) @@ -414,7 +413,7 @@ public: return mEnabled && viewPoint.z() < mWaterLevel; } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (isUnderwater()) return; @@ -717,7 +716,7 @@ private: } }; - class OcclusionCallback : public osg::NodeCallback + class OcclusionCallback { public: OcclusionCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) @@ -760,7 +759,7 @@ private: }; /// SunFlashCallback handles fading/scaling of a node depending on occlusion query result. Must be attached as a cull callback. - class SunFlashCallback : public OcclusionCallback + class SunFlashCallback : public OcclusionCallback, public SceneUtil::NodeCallback { public: SunFlashCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal) @@ -769,10 +768,8 @@ private: { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); osg::ref_ptr stateset; @@ -811,7 +808,7 @@ private: cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); - traverse(node, nv); + traverse(node, cv); cv->popModelViewMatrix(); @@ -832,7 +829,7 @@ private: /// SunGlareCallback controls a full-screen glare effect depending on occlusion query result and the angle between sun and camera. /// Must be attached as a cull callback to the node above the glare node. - class SunGlareCallback : public OcclusionCallback + class SunGlareCallback : public OcclusionCallback, public SceneUtil::NodeCallback { public: SunGlareCallback(osg::ref_ptr oqnVisible, osg::ref_ptr oqnTotal, @@ -854,10 +851,8 @@ private: mColor[i] = std::min(1.f, mColor[i]); } - void operator ()(osg::Node* node, osg::NodeVisitor* nv) override + void operator ()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - float angleRadians = getAngleToSunInRadians(*cv->getCurrentRenderStage()->getInitialViewMatrix()); float visibleRatio = getVisibleRatio(cv->getCurrentCamera()); @@ -885,7 +880,7 @@ private: stateset->setAttributeAndModes(mat, osg::StateAttribute::ON); cv->pushStateSet(stateset); - traverse(node, nv); + traverse(node, cv); cv->popStateSet(); } } diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 88e422fcf3..37368b8e7a 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -58,20 +58,17 @@ namespace MWRender /// To use, simply create the scene as subgraph of this node, then do setPlane(const osg::Plane& plane); class ClipCullNode : public osg::Group { - class PlaneCullCallback : public osg::NodeCallback + class PlaneCullCallback : public SceneUtil::NodeCallback { public: /// @param cullPlane The culling plane (in world space). PlaneCullCallback(const osg::Plane* cullPlane) - : osg::NodeCallback() - , mCullPlane(cullPlane) + : mCullPlane(cullPlane) { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - osg::Polytope::PlaneList origPlaneList = cv->getProjectionCullingStack().back().getFrustum().getPlaneList(); osg::Plane plane = *mCullPlane; @@ -83,7 +80,7 @@ class ClipCullNode : public osg::Group cv->getProjectionCullingStack().back().getFrustum().add(plane); - traverse(node, nv); + traverse(node, cv); // undo cv->getProjectionCullingStack().back().getFrustum().set(origPlaneList); @@ -93,7 +90,7 @@ class ClipCullNode : public osg::Group const osg::Plane* mCullPlane; }; - class FlipCallback : public osg::NodeCallback + class FlipCallback : public SceneUtil::NodeCallback { public: FlipCallback(const osg::Plane* cullPlane) @@ -101,9 +98,8 @@ class ClipCullNode : public osg::Group { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); osg::Vec3d eyePoint = cv->getEyePoint(); osg::RefMatrix* modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); @@ -123,7 +119,7 @@ class ClipCullNode : public osg::Group modelViewMatrix->preMultTranslate(mCullPlane->getNormal() * clipFudge); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); - traverse(node, nv); + traverse(node, cv); cv->popModelViewMatrix(); } @@ -166,31 +162,28 @@ private: /// This callback on the Camera has the effect of a RELATIVE_RF_INHERIT_VIEWPOINT transform mode (which does not exist in OSG). /// We want to keep the View Point of the parent camera so we will not have to recreate LODs. -class InheritViewPointCallback : public osg::NodeCallback +class InheritViewPointCallback : public SceneUtil::NodeCallback { public: - InheritViewPointCallback() {} + InheritViewPointCallback() {} - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); osg::ref_ptr modelViewMatrix = new osg::RefMatrix(*cv->getModelViewMatrix()); cv->popModelViewMatrix(); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::ABSOLUTE_RF_INHERIT_VIEWPOINT); - traverse(node, nv); + traverse(node, cv); } }; /// Moves water mesh away from the camera slightly if the camera gets too close on the Z axis. /// The offset works around graphics artifacts that occurred with the GL_DEPTH_CLAMP when the camera gets extremely close to the mesh (seen on NVIDIA at least). /// Must be added as a Cull callback. -class FudgeCallback : public osg::NodeCallback +class FudgeCallback : public SceneUtil::NodeCallback { public: - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - const float fudge = 0.2; if (std::abs(cv->getEyeLocal().z()) < fudge) { @@ -203,11 +196,11 @@ public: modelViewMatrix->preMultTranslate(osg::Vec3f(0,0,diff)); cv->pushModelViewMatrix(modelViewMatrix, osg::Transform::RELATIVE_RF); - traverse(node, nv); + traverse(node, cv); cv->popModelViewMatrix(); } else - traverse(node, nv); + traverse(node, cv); } }; diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 870b8949c7..719e4fdc2b 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include @@ -16,6 +16,7 @@ namespace osg class PositionAttitudeTransform; class Geometry; class Node; + class Callback; } namespace osgUtil @@ -72,7 +73,7 @@ namespace MWRender bool mInterior; osg::Callback* mCullCallback; - osg::ref_ptr mShaderWaterStateSetUpdater; + osg::ref_ptr mShaderWaterStateSetUpdater; osg::Vec3f getSceneNodeCoordinates(int gridX, int gridY); void updateVisible(); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3b7be64ad1..9ee137fab6 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" @@ -161,7 +162,7 @@ namespace MWWorld } /// Rotates an osg::PositionAttitudeTransform over time. - class RotateCallback : public osg::NodeCallback + class RotateCallback : public SceneUtil::NodeCallback { public: RotateCallback(const osg::Vec3f& axis = osg::Vec3f(0,-1,0), float rotateSpeed = osg::PI*2) @@ -170,14 +171,12 @@ namespace MWWorld { } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::PositionAttitudeTransform* node, osg::NodeVisitor* nv) { - osg::PositionAttitudeTransform* transform = static_cast(node); - double time = nv->getFrameStamp()->getSimulationTime(); osg::Quat orient = osg::Quat(time * mRotateSpeed, mAxis); - transform->setAttitude(orient); + node->setAttitude(orient); traverse(node, nv); } diff --git a/components/myguiplatform/myguirendermanager.cpp b/components/myguiplatform/myguirendermanager.cpp index abc170c02e..3061b329c2 100644 --- a/components/myguiplatform/myguirendermanager.cpp +++ b/components/myguiplatform/myguirendermanager.cpp @@ -15,6 +15,7 @@ #include #include +#include #include @@ -51,7 +52,7 @@ class Drawable : public osg::Drawable { public: // Stage 0: update widget animations and controllers. Run during the Update traversal. - class FrameUpdate : public osg::Drawable::UpdateCallback + class FrameUpdate : public SceneUtil::NodeCallback { public: FrameUpdate() @@ -64,10 +65,9 @@ public: mRenderManager = renderManager; } - void update(osg::NodeVisitor*, osg::Drawable*) override + void operator()(osg::Node*, osg::NodeVisitor*) { - if (mRenderManager) - mRenderManager->update(); + mRenderManager->update(); } private: @@ -75,7 +75,7 @@ public: }; // Stage 1: collect draw calls. Run during the Cull traversal. - class CollectDrawCalls : public osg::Drawable::CullCallback + class CollectDrawCalls : public SceneUtil::NodeCallback { public: CollectDrawCalls() @@ -88,13 +88,9 @@ public: mRenderManager = renderManager; } - bool cull(osg::NodeVisitor*, osg::Drawable*, osg::State*) const override + void operator()(osg::Node*, osg::NodeVisitor*) { - if (!mRenderManager) - return false; - mRenderManager->collectDrawCalls(); - return false; } private: diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index c8a6ef74ab..ddded82156 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -72,7 +72,7 @@ KeyframeController::KeyframeController() KeyframeController::KeyframeController(const KeyframeController ©, const osg::CopyOp ©op) : SceneUtil::KeyframeController(copy, copyop) - , SceneUtil::NodeCallback(copy, copyop) + , SceneUtil::NodeCallback(copy, copyop) , mRotations(copy.mRotations) , mXRotations(copy.mXRotations) , mYRotations(copy.mYRotations) @@ -134,16 +134,15 @@ osg::Vec3f KeyframeController::getTranslation(float time) const return osg::Vec3f(); } -void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) +void KeyframeController::operator() (NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) { if (hasInput()) { - NifOsg::MatrixTransform* trans = static_cast(node); - osg::Matrix mat = trans->getMatrix(); + osg::Matrix mat = node->getMatrix(); float time = getInputValue(nv); - Nif::Matrix3& rot = trans->mRotationScale; + Nif::Matrix3& rot = node->mRotationScale; bool setRot = false; if(!mRotations.empty()) @@ -169,7 +168,7 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) for (int j=0;j<3;++j) rot.mValues[i][j] = mat(j,i); // NB column/row major difference - float& scale = trans->mScale; + float& scale = node->mScale; if(!mScales.empty()) scale = mScales.interpKey(time); @@ -180,7 +179,7 @@ void KeyframeController::operator() (osg::Node* node, osg::NodeVisitor* nv) if(!mTranslations.empty()) mat.setTrans(mTranslations.interpKey(time)); - trans->setMatrix(mat); + node->setMatrix(mat); } traverse(node, nv); @@ -191,8 +190,8 @@ GeomMorpherController::GeomMorpherController() } GeomMorpherController::GeomMorpherController(const GeomMorpherController ©, const osg::CopyOp ©op) - : osg::Drawable::UpdateCallback(copy, copyop) - , Controller(copy) + : Controller(copy) + , SceneUtil::NodeCallback(copy, copyop) , mKeyFrames(copy.mKeyFrames) { } @@ -218,9 +217,8 @@ GeomMorpherController::GeomMorpherController(const Nif::NiGeomMorpherController* } } -void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable) +void GeomMorpherController::operator()(SceneUtil::MorphGeometry* node, osg::NodeVisitor *nv) { - SceneUtil::MorphGeometry* morphGeom = static_cast(drawable); if (hasInput()) { if (mKeyFrames.size() <= 1) @@ -233,11 +231,11 @@ void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable if (!(*it).empty()) val = it->interpKey(input); - SceneUtil::MorphGeometry::MorphTarget& target = morphGeom->getMorphTarget(i); + SceneUtil::MorphGeometry::MorphTarget& target = node->getMorphTarget(i); if (target.getWeight() != val) { target.setWeight(val); - morphGeom->dirty(); + node->dirty(); } } } @@ -312,7 +310,7 @@ VisController::VisController() } VisController::VisController(const VisController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mData(copy.mData) , mMask(copy.mMask) @@ -353,14 +351,14 @@ RollController::RollController(const Nif::NiFloatInterpolator* interpolator) } RollController::RollController(const RollController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mData(copy.mData) , mStartingTime(copy.mStartingTime) { } -void RollController::operator() (osg::Node* node, osg::NodeVisitor* nv) +void RollController::operator() (osg::MatrixTransform* node, osg::NodeVisitor* nv) { traverse(node, nv); @@ -371,15 +369,14 @@ void RollController::operator() (osg::Node* node, osg::NodeVisitor* nv) mStartingTime = newTime; float value = mData.interpKey(getInputValue(nv)); - osg::MatrixTransform* transform = static_cast(node); - osg::Matrix matrix = transform->getMatrix(); + osg::Matrix matrix = node->getMatrix(); // Rotate around "roll" axis. // Note: in original game rotation speed is the framerate-dependent in a very tricky way. // Do not replicate this behaviour until we will really need it. // For now consider controller's current value as an angular speed in radians per 1/60 seconds. matrix = osg::Matrix::rotate(value * duration * 60.f, 0, 0, 1) * matrix; - transform->setMatrix(matrix); + node->setMatrix(matrix); } } @@ -545,29 +542,28 @@ ParticleSystemController::ParticleSystemController() } ParticleSystemController::ParticleSystemController(const ParticleSystemController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mEmitStart(copy.mEmitStart) , mEmitStop(copy.mEmitStop) { } -void ParticleSystemController::operator() (osg::Node* node, osg::NodeVisitor* nv) +void ParticleSystemController::operator() (osgParticle::ParticleProcessor* node, osg::NodeVisitor* nv) { - osgParticle::ParticleProcessor* emitter = static_cast(node); if (hasInput()) { float time = getInputValue(nv); - emitter->getParticleSystem()->setFrozen(false); - emitter->setEnabled(time >= mEmitStart && time < mEmitStop); + node->getParticleSystem()->setFrozen(false); + node->setEnabled(time >= mEmitStart && time < mEmitStop); } else - emitter->getParticleSystem()->setFrozen(true); + node->getParticleSystem()->setFrozen(true); traverse(node, nv); } PathController::PathController(const PathController ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) , Controller(copy) , mPath(copy.mPath) , mPercent(copy.mPercent) @@ -592,7 +588,7 @@ float PathController::getPercent(float time) const return percent; } -void PathController::operator() (osg::Node* node, osg::NodeVisitor* nv) +void PathController::operator() (osg::MatrixTransform* node, osg::NodeVisitor* nv) { if (mPath.empty() || mPercent.empty() || !hasInput()) { @@ -600,14 +596,13 @@ void PathController::operator() (osg::Node* node, osg::NodeVisitor* nv) return; } - osg::MatrixTransform* trans = static_cast(node); - osg::Matrix mat = trans->getMatrix(); + osg::Matrix mat = node->getMatrix(); float time = getInputValue(nv); float percent = getPercent(time); osg::Vec3f pos(mPath.interpKey(percent)); mat.setTrans(pos); - trans->setMatrix(mat); + node->setMatrix(mat); traverse(node, nv); } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 078b7e14ce..c6311fd5fc 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -15,19 +15,27 @@ #include -#include -#include -#include - - namespace osg { class Material; + class MatrixTransform; +} + +namespace osgParticle +{ + class ParticleProcessor; +} + +namespace SceneUtil +{ + class MorphGeometry; } namespace NifOsg { + class MatrixTransform; + // interpolation of keyframes template class ValueInterpolator @@ -208,8 +216,7 @@ namespace NifOsg float getMaximum() const override; }; - /// Must be set on a SceneUtil::MorphGeometry. - class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller + class GeomMorpherController : public SceneUtil::Controller, public SceneUtil::NodeCallback { public: GeomMorpherController(const Nif::NiGeomMorpherController* ctrl); @@ -218,13 +225,13 @@ namespace NifOsg META_Object(NifOsg, GeomMorpherController) - void update(osg::NodeVisitor* nv, osg::Drawable* drawable) override; + void operator()(SceneUtil::MorphGeometry*, osg::NodeVisitor*); private: std::vector mKeyFrames; }; - class KeyframeController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback + class KeyframeController : public SceneUtil::KeyframeController, public SceneUtil::NodeCallback { public: // This is used if there's no interpolator but there is data (Morrowind meshes). @@ -242,7 +249,7 @@ namespace NifOsg osg::Vec3f getTranslation(float time) const override; - void operator() (osg::Node*, osg::NodeVisitor*); + void operator() (NifOsg::MatrixTransform*, osg::NodeVisitor*); private: QuaternionInterpolator mRotations; @@ -277,7 +284,7 @@ namespace NifOsg std::set mTextureUnits; }; - class VisController : public osg::NodeCallback, public SceneUtil::Controller + class VisController : public SceneUtil::NodeCallback, public SceneUtil::Controller { private: std::vector mData; @@ -292,10 +299,10 @@ namespace NifOsg META_Object(NifOsg, VisController) - void operator() (osg::Node* node, osg::NodeVisitor* nv) override; + void operator() (osg::Node* node, osg::NodeVisitor* nv); }; - class RollController : public osg::NodeCallback, public SceneUtil::Controller + class RollController : public SceneUtil::NodeCallback, public SceneUtil::Controller { private: FloatInterpolator mData; @@ -307,7 +314,7 @@ namespace NifOsg RollController() = default; RollController(const RollController& copy, const osg::CopyOp& copyop); - void operator() (osg::Node* node, osg::NodeVisitor* nv) override; + void operator() (osg::MatrixTransform* node, osg::NodeVisitor* nv); META_Object(NifOsg, RollController) }; @@ -378,7 +385,7 @@ namespace NifOsg void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) override; }; - class ParticleSystemController : public osg::NodeCallback, public SceneUtil::Controller + class ParticleSystemController : public SceneUtil::NodeCallback, public SceneUtil::Controller { public: ParticleSystemController(const Nif::NiParticleSystemController* ctrl); @@ -387,14 +394,14 @@ namespace NifOsg META_Object(NifOsg, ParticleSystemController) - void operator() (osg::Node* node, osg::NodeVisitor* nv) override; + void operator() (osgParticle::ParticleProcessor* node, osg::NodeVisitor* nv); private: float mEmitStart; float mEmitStop; }; - class PathController : public osg::NodeCallback, public SceneUtil::Controller + class PathController : public SceneUtil::NodeCallback, public SceneUtil::Controller { public: PathController(const Nif::NiPathController* ctrl); @@ -403,7 +410,7 @@ namespace NifOsg META_Object(NifOsg, PathController) - void operator() (osg::Node*, osg::NodeVisitor*) override; + void operator() (osg::MatrixTransform*, osg::NodeVisitor*); private: Vec3Interpolator mPath; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a564844ce6..838895eb47 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -109,24 +109,21 @@ namespace // NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale // set just like a regular MatrixTransform, but the rotation set will be overridden in order to face the camera. - // Must be set as a cull callback. - class BillboardCallback : public osg::NodeCallback + class BillboardCallback : public SceneUtil::NodeCallback { public: BillboardCallback() { } BillboardCallback(const BillboardCallback& copy, const osg::CopyOp& copyop) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) { } META_Object(NifOsg, BillboardCallback) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - osg::Matrix modelView = *cv->getModelViewMatrix(); // attempt to preserve scale @@ -143,7 +140,7 @@ namespace cv->pushModelViewMatrix(new osg::RefMatrix(modelView), osg::Transform::RELATIVE_RF); - traverse(node, nv); + traverse(node, cv); cv->popModelViewMatrix(); } diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index e2e1f92cf3..0f103588e8 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -68,19 +68,17 @@ void ParticleSystem::drawImplementation(osg::RenderInfo& renderInfo) const osgParticle::ParticleSystem::drawImplementation(renderInfo); } -void InverseWorldMatrix::operator()(osg::Node *node, osg::NodeVisitor *nv) +void InverseWorldMatrix::operator()(osg::MatrixTransform *node, osg::NodeVisitor *nv) { if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) { osg::NodePath path = nv->getNodePath(); path.pop_back(); - osg::MatrixTransform* trans = static_cast(node); - osg::Matrix mat = osg::computeLocalToWorld( path ); mat.orthoNormalize(mat); // don't undo the scale mat.invert(mat); - trans->setMatrix(mat); + node->setMatrix(mat); } traverse(node,nv); } diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index b0a46f0ca5..8b724545f4 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include "controller.hpp" // ValueInterpolator @@ -57,20 +57,20 @@ namespace NifOsg // Node callback used to set the inverse of the parent's world matrix on the MatrixTransform // that the callback is attached to. Used for certain particle systems, // so that the particles do not move with the node they are attached to. - class InverseWorldMatrix : public osg::NodeCallback + class InverseWorldMatrix : public SceneUtil::NodeCallback { public: InverseWorldMatrix() { } - InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& op) - : osg::Object(), osg::NodeCallback() + InverseWorldMatrix(const InverseWorldMatrix& copy, const osg::CopyOp& copyop) + : osg::Object(copy, copyop), SceneUtil::NodeCallback(copy, copyop) { } META_Object(NifOsg, InverseWorldMatrix) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); }; class ParticleShooter : public osgParticle::Shooter diff --git a/components/resource/keyframemanager.cpp b/components/resource/keyframemanager.cpp index 01a8639aa2..8aa32a28bc 100644 --- a/components/resource/keyframemanager.cpp +++ b/components/resource/keyframemanager.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index be32539d40..7c730b24a7 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -40,16 +40,14 @@ namespace { - class InitWorldSpaceParticlesCallback : public osg::NodeCallback + class InitWorldSpaceParticlesCallback : public SceneUtil::NodeCallback { public: - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osgParticle::ParticleSystem* node, osg::NodeVisitor* nv) { - osgParticle::ParticleSystem* partsys = static_cast(node); - // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to - if (partsys->getNumParents() && partsys->getParent(0)->getNumParents()) - transformInitialParticles(partsys, partsys->getParent(0)->getParent(0)); + if (node->getNumParents() && node->getParent(0)->getNumParents()) + transformInitialParticles(node, node->getParent(0)->getParent(0)); node->removeUpdateCallback(this); } diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index cc320aecf8..13e367baa4 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -26,7 +26,7 @@ namespace SceneUtil mType = type; } - void LightController::operator ()(osg::Node* node, osg::NodeVisitor* nv) + void LightController::operator ()(SceneUtil::LightSource* node, osg::NodeVisitor* nv) { double time = nv->getFrameStamp()->getSimulationTime(); if (mStartTime == 0) @@ -38,7 +38,7 @@ namespace SceneUtil if (mType == LT_Normal) { - static_cast(node)->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); + node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); traverse(node, nv); return; } @@ -62,8 +62,7 @@ namespace SceneUtil mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } - auto* lightSource = static_cast(node); - lightSource->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * lightSource->getActorFade()); + node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * node->getActorFade()); traverse(node, nv); } diff --git a/components/sceneutil/lightcontroller.hpp b/components/sceneutil/lightcontroller.hpp index 36b2e868e5..b67cd59472 100644 --- a/components/sceneutil/lightcontroller.hpp +++ b/components/sceneutil/lightcontroller.hpp @@ -1,15 +1,16 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H #define OPENMW_COMPONENTS_SCENEUTIL_LIGHTCONTROLLER_H -#include +#include #include namespace SceneUtil { + class LightSource; + /// @brief Controller class to handle a pulsing and/or flickering light - /// @note Must be set on a SceneUtil::LightSource. - class LightController : public osg::NodeCallback + class LightController : public SceneUtil::NodeCallback { public: enum LightType { @@ -26,7 +27,7 @@ namespace SceneUtil void setDiffuse(const osg::Vec4f& color); - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(SceneUtil::LightSource* node, osg::NodeVisitor* nv); private: LightType mType; diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 09fa302457..0abebd4884 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -650,23 +650,21 @@ namespace SceneUtil } }; - class LightManagerCullCallback : public osg::NodeCallback + class LightManagerCullCallback : public SceneUtil::NodeCallback { public: - LightManagerCullCallback(LightManager* lightManager) : mLightManager(lightManager), mLastFrameNumber(0) {} + LightManagerCullCallback() : mLastFrameNumber(0) {} - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(LightManager* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - if (mLastFrameNumber != cv->getTraversalNumber()) { mLastFrameNumber = cv->getTraversalNumber(); - if (mLightManager->getLightingMethod() == LightingMethod::SingleUBO) + if (node->getLightingMethod() == LightingMethod::SingleUBO) { - auto stateset = mLightManager->getStateSet(); - auto bo = mLightManager->getLightBuffer(mLastFrameNumber); + auto stateset = node->getStateSet(); + auto bo = node->getLightBuffer(mLastFrameNumber); #if OSG_VERSION_GREATER_OR_EQUAL(3,5,7) osg::ref_ptr ubb = new osg::UniformBufferBinding(static_cast(Resource::SceneManager::UBOBinding::LightBuffer), bo->getData(), 0, bo->getData()->getTotalDataSize()); @@ -676,23 +674,23 @@ namespace SceneUtil stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON); } - auto sun = mLightManager->getSunlight(); + auto sun = node->getSunlight(); if (sun) { // we must defer uploading the transformation to view-space position to deal with different cameras (e.g. reflection RTT). - if (mLightManager->getLightingMethod() == LightingMethod::PerObjectUniform) + if (node->getLightingMethod() == LightingMethod::PerObjectUniform) { osg::Matrixf lightMat; configurePosition(lightMat, sun->getPosition()); configureAmbient(lightMat, sun->getAmbient()); configureDiffuse(lightMat, sun->getDiffuse()); configureSpecular(lightMat, sun->getSpecular()); - mLightManager->setSunlightBuffer(lightMat, mLastFrameNumber); + node->setSunlightBuffer(lightMat, mLastFrameNumber); } else { - auto buf = mLightManager->getLightBuffer(mLastFrameNumber); + auto buf = node->getLightBuffer(mLastFrameNumber); buf->setCachedSunPos(sun->getPosition()); buf->setAmbient(0, sun->getAmbient()); @@ -702,11 +700,10 @@ namespace SceneUtil } } - traverse(node, nv); + traverse(node, cv); } private: - LightManager* mLightManager; size_t mLastFrameNumber; }; @@ -899,7 +896,7 @@ namespace SceneUtil getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0)); - addCullCallback(new LightManagerCullCallback(this)); + addCullCallback(new LightManagerCullCallback()); } LightManager::LightManager(const LightManager ©, const osg::CopyOp ©op) diff --git a/components/sceneutil/rtt.cpp b/components/sceneutil/rtt.cpp index a661a90279..3b60c80afd 100644 --- a/components/sceneutil/rtt.cpp +++ b/components/sceneutil/rtt.cpp @@ -6,23 +6,19 @@ #include #include +#include #include namespace SceneUtil { - // RTTNode's cull callback - class CullCallback : public osg::NodeCallback + class CullCallback : public SceneUtil::NodeCallback { public: - CullCallback(RTTNode* group) - : mGroup(group) {} - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(RTTNode* node, osgUtil::CullVisitor* cv) { - osgUtil::CullVisitor* cv = static_cast(nv); - mGroup->cull(cv); + node->cull(cv); } - RTTNode* mGroup; }; RTTNode::RTTNode(uint32_t textureWidth, uint32_t textureHeight, int renderOrderNum, bool doPerViewMapping) @@ -31,7 +27,7 @@ namespace SceneUtil , mRenderOrderNum(renderOrderNum) , mDoPerViewMapping(doPerViewMapping) { - addCullCallback(new CullCallback(this)); + addCullCallback(new CullCallback); setCullingActive(false); } diff --git a/components/sceneutil/statesetupdater.cpp b/components/sceneutil/statesetupdater.cpp index a8232f938e..9ef82b9271 100644 --- a/components/sceneutil/statesetupdater.cpp +++ b/components/sceneutil/statesetupdater.cpp @@ -68,7 +68,7 @@ namespace SceneUtil } StateSetUpdater::StateSetUpdater(const StateSetUpdater ©, const osg::CopyOp ©op) - : osg::NodeCallback(copy, copyop) + : SceneUtil::NodeCallback(copy, copyop) { } diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index 263f76ae54..ab091fd39f 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SCENEUTIL_STATESETCONTROLLER_H #define OPENMW_COMPONENTS_SCENEUTIL_STATESETCONTROLLER_H -#include +#include #include #include @@ -28,7 +28,7 @@ namespace SceneUtil /// @note When used as a CullCallback, StateSetUpdater will have no effect on leaf nodes such as osg::Geometry and must be used on branch nodes only. /// @note Do not add the same StateSetUpdater to multiple nodes. /// @note Do not add multiple StateSetUpdaters on the same Node as they will conflict - instead use the CompositeStateSetUpdater. - class StateSetUpdater : public osg::NodeCallback + class StateSetUpdater : public SceneUtil::NodeCallback { public: StateSetUpdater(); @@ -36,7 +36,7 @@ namespace SceneUtil META_Object(SceneUtil, StateSetUpdater) - void operator()(osg::Node* node, osg::NodeVisitor* nv) override; + void operator()(osg::Node* node, osg::NodeVisitor* nv); /// Apply state - to override in derived classes /// @note Due to the double buffering approach you *have* to apply all state diff --git a/components/sceneutil/util.cpp b/components/sceneutil/util.cpp index 442b164981..5c2f8c2934 100644 --- a/components/sceneutil/util.cpp +++ b/components/sceneutil/util.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #ifndef GL_DEPTH32F_STENCIL8_NV #define GL_DEPTH32F_STENCIL8_NV 0x8DAC @@ -174,8 +175,7 @@ void GlowUpdater::setDuration(float duration) } // Allows camera to render to a color and floating point depth texture with a multisampled framebuffer. -// Must be set on a camera's cull callback. -class AttachMultisampledDepthColorCallback : public osg::NodeCallback +class AttachMultisampledDepthColorCallback : public SceneUtil::NodeCallback { public: AttachMultisampledDepthColorCallback(osg::Texture2D* colorTex, osg::Texture2D* depthTex, int samples, int colorSamples) @@ -195,14 +195,14 @@ public: mFbo->setAttachment(osg::Camera::DEPTH_BUFFER, osg::FrameBufferAttachment(depthTex)); } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osgUtil::CullVisitor* cv) { - osgUtil::RenderStage* renderStage = static_cast(nv)->getCurrentRenderStage(); + osgUtil::RenderStage* renderStage = cv->getCurrentRenderStage(); renderStage->setMultisampleResolveFramebufferObject(mFbo); renderStage->setFrameBufferObject(mMsaaFbo); - traverse(node, nv); + traverse(node, cv); } private: diff --git a/components/sceneutil/util.hpp b/components/sceneutil/util.hpp index 0bca32c325..4d19714d66 100644 --- a/components/sceneutil/util.hpp +++ b/components/sceneutil/util.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index 8d60e4c5b6..def7a2eccf 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -4,13 +4,14 @@ #include #include #include -#include #include #include #include #include +#include + #include "defs.hpp" #include "cellborder.hpp" @@ -45,7 +46,7 @@ namespace Terrain class ChunkManager; class CompositeMapRenderer; - class HeightCullCallback : public osg::NodeCallback + class HeightCullCallback : public SceneUtil::NodeCallback { public: void setLowZ(float z) @@ -75,7 +76,7 @@ namespace Terrain return mMask; } - void operator()(osg::Node* node, osg::NodeVisitor* nv) override + void operator()(osg::Node* node, osg::NodeVisitor* nv) { if (mLowZ <= mHighZ) traverse(node, nv); From 4b1c009ffd5ed0d0698b967c29edf0d6c912ea92 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 12:37:08 +0000 Subject: [PATCH 120/137] use StateSet define for translucentFramebuffer (#3138) With this PR we test out osg's shader define system for a somewhat harmless feature. As we can see, our code becomes more concise and efficient in this case. Most importantly, we no longer create unneeded vertex shader objects. --- apps/openmw/mwrender/characterpreview.cpp | 9 +++------ apps/openmw/mwrender/groundcover.cpp | 2 +- components/resource/scenemanager.cpp | 7 +++---- components/resource/scenemanager.hpp | 4 ++-- components/shader/shadervisitor.cpp | 10 +--------- components/shader/shadervisitor.hpp | 4 ---- files/shaders/nv_default_fragment.glsl | 9 ++++----- files/shaders/nv_nolighting_fragment.glsl | 8 +++----- files/shaders/objects_fragment.glsl | 7 +++---- 9 files changed, 20 insertions(+), 40 deletions(-) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 51e5a536f7..a9733cddf8 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -91,7 +91,7 @@ namespace MWRender class SetUpBlendVisitor : public osg::NodeVisitor { public: - SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN), mNoAlphaUniform(new osg::Uniform("noAlpha", false)) + SetUpBlendVisitor(): osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { } @@ -130,7 +130,7 @@ namespace MWRender } // Disable noBlendAlphaEnv newStateSet->setTextureMode(7, GL_TEXTURE_2D, osg::StateAttribute::OFF); - newStateSet->addUniform(mNoAlphaUniform); + newStateSet->setDefine("FORCE_OPAQUE", "0", osg::StateAttribute::ON); } if (SceneUtil::getReverseZ() && stateset->getAttribute(osg::StateAttribute::DEPTH)) { @@ -172,8 +172,6 @@ namespace MWRender } traverse(node); } - private: - osg::ref_ptr mNoAlphaUniform; }; CharacterPreview::CharacterPreview(osg::Group* parent, Resource::ResourceSystem* resourceSystem, @@ -216,6 +214,7 @@ namespace MWRender osg::ref_ptr lightManager = new SceneUtil::LightManager(ffp); lightManager->setStartLight(1); osg::ref_ptr stateset = lightManager->getOrCreateStateSet(); + stateset->setDefine("FORCE_OPAQUE", "1", osg::StateAttribute::ON); stateset->setMode(GL_LIGHTING, osg::StateAttribute::ON); stateset->setMode(GL_NORMALIZE, osg::StateAttribute::ON); stateset->setMode(GL_CULL_FACE, osg::StateAttribute::ON); @@ -253,7 +252,6 @@ namespace MWRender dummyTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); stateset->setTextureAttributeAndModes(7, dummyTexture, osg::StateAttribute::ON); stateset->setTextureAttribute(7, noBlendAlphaEnv, osg::StateAttribute::ON); - stateset->addUniform(new osg::Uniform("noAlpha", true)); osg::ref_ptr lightmodel = new osg::LightModel; lightmodel->setAmbientIntensity(osg::Vec4(0.0, 0.0, 0.0, 1.0)); @@ -328,7 +326,6 @@ namespace MWRender void CharacterPreview::setBlendMode() { - mResourceSystem->getSceneManager()->recreateShaders(mNode, "objects", true); SetUpBlendVisitor visitor; mNode->accept(visitor); } diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 71bddb1f9d..77c4f0fab5 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -224,7 +224,7 @@ namespace MWRender group->setNodeMask(Mask_Groundcover); if (mSceneManager->getLightingMethod() != SceneUtil::LightingMethod::FFP) group->setCullCallback(new SceneUtil::LightListCallback); - mSceneManager->recreateShaders(group, "groundcover", false, true, mProgramTemplate); + mSceneManager->recreateShaders(group, "groundcover", true, mProgramTemplate); mSceneManager->shareState(group); group->getBound(); return group; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index 7c730b24a7..c5ef957c3e 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -331,9 +331,9 @@ namespace Resource return mForceShaders; } - void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool translucentFramebuffer, bool forceShadersForNode, const osg::Program* programTemplate) + void SceneManager::recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix, bool forceShadersForNode, const osg::Program* programTemplate) { - osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix, translucentFramebuffer)); + osg::ref_ptr shaderVisitor(createShaderVisitor(shaderPrefix)); shaderVisitor->setAllowedToModifyStateSets(false); shaderVisitor->setProgramTemplate(programTemplate); if (forceShadersForNode) @@ -871,7 +871,7 @@ namespace Resource stats->setAttribute(frameNumber, "Node", mCache->getCacheSize()); } - Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer) + Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix) { Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix); shaderVisitor->setForceShaders(mForceShaders); @@ -882,7 +882,6 @@ namespace Resource shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps); shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage); - shaderVisitor->setTranslucentFramebuffer(translucentFramebuffer); return shaderVisitor; } } diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 1443476fd5..b5d3e453a0 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -76,7 +76,7 @@ namespace Resource Shader::ShaderManager& getShaderManager(); /// Re-create shaders for this node, need to call this if alpha testing, texture stages or vertex color mode have changed. - void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false, bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); + void recreateShaders(osg::ref_ptr node, const std::string& shaderPrefix = "objects", bool forceShadersForNode = false, const osg::Program* programTemplate = nullptr); /// Applying shaders to a node may replace some fixed-function state. /// This restores it. @@ -188,7 +188,7 @@ namespace Resource private: - Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects", bool translucentFramebuffer = false); + Shader::ShaderVisitor* createShaderVisitor(const std::string& shaderPrefix = "objects"); std::unique_ptr mShaderManager; bool mForceShaders; diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index cc2b781cfd..6709ee842e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -111,7 +111,6 @@ namespace Shader , mAutoUseSpecularMaps(false) , mApplyLightingToEnvMaps(false) , mConvertAlphaTestToAlphaToCoverage(false) - , mTranslucentFramebuffer(false) , mShaderManager(shaderManager) , mImageManager(imageManager) , mDefaultShaderPrefix(defaultShaderPrefix) @@ -266,7 +265,7 @@ namespace Shader mRequirements.back().mShaderRequired = true; } } - else if (!mTranslucentFramebuffer) + else Log(Debug::Error) << "ShaderVisitor encountered unknown texture " << texture; } } @@ -547,8 +546,6 @@ namespace Shader updateAddedState(*writableUserData, addedState); } - defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0"; - std::string shaderPrefix; if (!node.getUserValue("shaderPrefix", shaderPrefix)) shaderPrefix = mDefaultShaderPrefix; @@ -764,11 +761,6 @@ namespace Shader mConvertAlphaTestToAlphaToCoverage = convert; } - void ShaderVisitor::setTranslucentFramebuffer(bool translucent) - { - mTranslucentFramebuffer = translucent; - } - ReinstateRemovedStateVisitor::ReinstateRemovedStateVisitor(bool allowedToModifyStateSets) : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) , mAllowedToModifyStateSets(allowedToModifyStateSets) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 3380a66cca..5f9739ea90 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -45,8 +45,6 @@ namespace Shader void setConvertAlphaTestToAlphaToCoverage(bool convert); - void setTranslucentFramebuffer(bool translucent); - void apply(osg::Node& node) override; void apply(osg::Drawable& drawable) override; @@ -72,8 +70,6 @@ namespace Shader bool mConvertAlphaTestToAlphaToCoverage; - bool mTranslucentFramebuffer; - ShaderManager& mShaderManager; Resource::ImageManager& mImageManager; diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 1bd0f4e310..38dca66c40 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -1,4 +1,5 @@ #version 120 +#pragma import_defines(FORCE_OPAQUE) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -24,8 +25,6 @@ varying vec2 normalMapUV; varying vec4 passTangent; #endif -uniform bool noAlpha; - varying float euclideanDepth; varying float linearDepth; @@ -95,9 +94,9 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if @translucentFramebuffer - if (noAlpha) - gl_FragData[0].a = 1.0; +#if FORCE_OPAQUE + // having testing & blending isn't enough - we need to write an opaque pixel to be opaque + gl_FragData[0].a = 1.0; #endif applyShadowDebugOverlay(); diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl index 27679a069f..76b01828d6 100644 --- a/files/shaders/nv_nolighting_fragment.glsl +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -1,4 +1,5 @@ #version 120 +#pragma import_defines(FORCE_OPAQUE) #if @useGPUShader4 #extension GL_EXT_gpu_shader4: require @@ -9,8 +10,6 @@ uniform sampler2D diffuseMap; varying vec2 diffuseMapUV; #endif -uniform bool noAlpha; - #if @radialFog varying float euclideanDepth; #else @@ -46,9 +45,8 @@ void main() float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #endif -#if @translucentFramebuffer - if (noAlpha) - gl_FragData[0].a = 1.0; +#if FORCE_OPAQUE + gl_FragData[0].a = 1.0; #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index d304b2abe8..c343b6a189 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -1,4 +1,5 @@ #version 120 +#pragma import_defines(FORCE_OPAQUE) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -58,7 +59,6 @@ uniform mat2 bumpMapMatrix; #endif uniform bool simpleWater; -uniform bool noAlpha; varying float euclideanDepth; varying float linearDepth; @@ -220,10 +220,9 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if @translucentFramebuffer +#if FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque - if (noAlpha) - gl_FragData[0].a = 1.0; + gl_FragData[0].a = 1.0; #endif applyShadowDebugOverlay(); From 499b161e7bab9f11857e7bd347546ba1963c3fb1 Mon Sep 17 00:00:00 2001 From: Frederic Chardon Date: Sat, 25 Sep 2021 16:06:30 +0200 Subject: [PATCH 121/137] Merge handleFall() and updateMechanics() into updateActor(). It remove gratuitious differences between sync and async cases. Also, it will make the after simulation update logic easier to follow when projectiles move into the simulation thread. --- apps/openmw/mwphysics/mtphysics.cpp | 48 +++++++------------------ apps/openmw/mwphysics/physicssystem.cpp | 2 -- apps/openmw/mwphysics/physicssystem.hpp | 2 -- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 7363b9964a..bb5447341b 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -54,29 +54,6 @@ namespace return actorData.mPosition.z() < actorData.mSwimLevel; } - void handleFall(MWPhysics::ActorFrameData& actorData, bool simulationPerformed) - { - const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; - - const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); - - if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1) - actorData.mNeedLand = true; - else if (heightDiff < 0) - actorData.mFallHeight += heightDiff; - } - - void updateMechanics(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData) - { - auto ptr = actor.getPtr(); - - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); - if (actorData.mNeedLand) - stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData))); - else if (actorData.mFallHeight < 0) - stats.addToFallHeight(-actorData.mFallHeight); - } - osg::Vec3f interpolateMovements(MWPhysics::Actor& actor, MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt) { const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f); @@ -221,10 +198,8 @@ namespace MWPhysics if (mNumThreads != 0) { for (size_t i = 0; i < mActors.size(); ++i) - { - updateMechanics(*mActors[i], mActorsFrameData[i]); updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); - } + if(mAdvanceSimulation) mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); updateStats(frameStart, frameNumber, stats); @@ -449,11 +424,6 @@ namespace MWPhysics if (!mRemainingSteps) { - while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) - { - handleFall(mActorsFrameData[job], mAdvanceSimulation); - } - refreshLOSCache(); mPostSimBarrier->wait([this] { afterPostSim(); }); } @@ -476,6 +446,17 @@ namespace MWPhysics void PhysicsTaskScheduler::updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const { + auto ptr = actor.getPtr(); + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + const float heightDiff = actorData.mPosition.z() - actorData.mOldHeight; + const bool isStillOnGround = (simulationPerformed && actorData.mWasOnGround && actorData.mIsOnGround); + + if (isStillOnGround || actorData.mFlying || isUnderWater(actorData) || actorData.mSlowFall < 1) + stats.land(ptr == MWMechanics::getPlayer() && (actorData.mFlying || isUnderWater(actorData))); + else if (heightDiff < 0) + stats.addToFallHeight(-heightDiff); + actor.setSimulationPosition(interpolateMovements(actor, actorData, timeAccum, dt)); actor.setLastStuckPosition(actorData.mLastStuckPosition); actor.setStuckFrames(actorData.mStuckFrames); @@ -524,11 +505,8 @@ namespace MWPhysics } for (size_t i = 0; i < mActors.size(); ++i) - { - handleFall(mActorsFrameData[i], mAdvanceSimulation); - updateMechanics(*mActors[i], mActorsFrameData[i]); updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); - } + refreshLOSCache(); } diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index a8c39efee2..6b2600069a 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -978,14 +978,12 @@ namespace MWPhysics , mWaterlevel(waterlevel) , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) - , mFallHeight(0) , mStuckFrames(0) , mFlying(MWBase::Environment::get().getWorld()->isFlying(actor.getPtr())) , mWasOnGround(actor.getOnGround()) , mIsAquatic(actor.getPtr().getClass().isPureWaterCreature(actor.getPtr())) , mWaterCollision(waterCollision) , mSkipCollisionDetection(actor.skipCollisions() || !actor.getCollisionMode()) - , mNeedLand(false) { } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 4fb7a3518f..cf4ca40a54 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -97,14 +97,12 @@ namespace MWPhysics const float mWaterlevel; const float mHalfExtentsZ; float mOldHeight; - float mFallHeight; unsigned int mStuckFrames; const bool mFlying; const bool mWasOnGround; const bool mIsAquatic; const bool mWaterCollision; const bool mSkipCollisionDetection; - bool mNeedLand; }; struct WorldFrameData From 21dbe314bf3e487975becd35e6ae8ae3af7916b3 Mon Sep 17 00:00:00 2001 From: fredzio Date: Wed, 29 Sep 2021 10:17:31 +0200 Subject: [PATCH 122/137] Use common function for sync and async case. Now both cases follow the same flow, synchronous simulation is just a special case. --- apps/openmw/mwphysics/mtphysics.cpp | 80 +++++++++++++---------------- apps/openmw/mwphysics/mtphysics.hpp | 4 +- components/misc/barrier.hpp | 15 +++--- 3 files changed, 46 insertions(+), 53 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index bb5447341b..ecf1e6dadd 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -22,31 +22,31 @@ namespace { - /// @brief A scoped lock that is either shared or exclusive depending on configuration + /// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration template class MaybeSharedLock { public: /// @param mutex a shared mutex - /// @param canBeSharedLock decide wether the lock will be shared or exclusive - MaybeSharedLock(Mutex& mutex, bool canBeSharedLock) : mMutex(mutex), mCanBeSharedLock(canBeSharedLock) + /// @param threadCount decide wether the lock will be shared, exclusive or inexistent + MaybeSharedLock(Mutex& mutex, int threadCount) : mMutex(mutex), mThreadCount(threadCount) { - if (mCanBeSharedLock) + if (mThreadCount > 1) mMutex.lock_shared(); - else + else if(mThreadCount == 1) mMutex.lock(); } ~MaybeSharedLock() { - if (mCanBeSharedLock) + if (mThreadCount > 1) mMutex.unlock_shared(); - else + else if(mThreadCount == 1) mMutex.unlock(); } private: Mutex& mMutex; - bool mCanBeSharedLock; + int mThreadCount; }; bool isUnderWater(const MWPhysics::ActorFrameData& actorData) @@ -62,14 +62,14 @@ namespace namespace Config { - /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading - int computeNumThreads(bool& threadSafeBullet) + /// @return either the number of thread as configured by the user, or 1 if Bullet doesn't support multithreading and user requested more than 1 background threads + int computeNumThreads() { int wantedThread = Settings::Manager::getInt("async num threads", "Physics"); auto broad = std::make_unique(); auto maxSupportedThreads = broad->m_rayTestStacks.size(); - threadSafeBullet = (maxSupportedThreads > 1); + auto threadSafeBullet = (maxSupportedThreads > 1); if (!threadSafeBullet && wantedThread > 1) { Log(Debug::Warning) << "Bullet was not compiled with multithreading support, 1 async thread will be used"; @@ -88,6 +88,7 @@ namespace MWPhysics , mTimeAccum(0.f) , mCollisionWorld(collisionWorld) , mDebugDrawer(debugDrawer) + , mNumThreads(Config::computeNumThreads()) , mNumJobs(0) , mRemainingSteps(0) , mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics")) @@ -107,8 +108,6 @@ namespace MWPhysics , mTimeEnd(0) , mFrameStart(0) { - mNumThreads = Config::computeNumThreads(mThreadSafeBullet); - if (mNumThreads >= 1) { for (int i = 0; i < mNumThreads; ++i) @@ -197,8 +196,7 @@ namespace MWPhysics // start by finishing previous background computation if (mNumThreads != 0) { - for (size_t i = 0; i < mActors.size(); ++i) - updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); + syncWithMainThread(); if(mAdvanceSimulation) mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor); @@ -233,7 +231,8 @@ namespace MWPhysics if (mNumThreads == 0) { - syncComputation(); + doSimulation(); + syncWithMainThread(); if(mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor); return; @@ -262,13 +261,13 @@ namespace MWPhysics void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback); } void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback); } @@ -280,7 +279,7 @@ namespace MWPhysics std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) { - MaybeSharedLock lock(mCollisionWorldMutex, mThreadSafeBullet); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); // target the collision object's world origin, this should be the center of the collision object btTransform rayTo; rayTo.setIdentity(); @@ -411,22 +410,7 @@ namespace MWPhysics if (!mNewFrame) mHasJob.wait(lock, [&]() { return mQuit || mNewFrame; }); - mPreStepBarrier->wait([this] { afterPreStep(); }); - - int job = 0; - while (mRemainingSteps && (job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) - { - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); - MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); - } - - mPostStepBarrier->wait([this] { afterPostStep(); }); - - if (!mRemainingSteps) - { - refreshLOSCache(); - mPostSimBarrier->wait([this] { afterPostSim(); }); - } + doSimulation(); } } @@ -485,29 +469,29 @@ namespace MWPhysics resultCallback.m_collisionFilterGroup = 0xFF; resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet); + MaybeSharedLock lockColWorld(mCollisionWorldMutex, mNumThreads); mCollisionWorld->rayTest(pos1, pos2, resultCallback); return !resultCallback.hasHit(); } - void PhysicsTaskScheduler::syncComputation() + void PhysicsTaskScheduler::doSimulation() { - while (mRemainingSteps--) + while (mRemainingSteps) { - for (auto& actorData : mActorsFrameData) + mPreStepBarrier->wait([this] { afterPreStep(); }); + int job = 0; + while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - MovementSolver::unstuck(actorData, mCollisionWorld); - MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData); + MaybeSharedLock lockColWorld(mCollisionWorldMutex, mNumThreads); + MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); } - updateActorsPositions(); + mPostStepBarrier->wait([this] { afterPostStep(); }); } - for (size_t i = 0; i < mActors.size(); ++i) - updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); - refreshLOSCache(); + mPostSimBarrier->wait([this] { afterPostSim(); }); } void PhysicsTaskScheduler::updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) @@ -580,4 +564,10 @@ namespace MWPhysics } mTimeEnd = mTimer->tick(); } + + void PhysicsTaskScheduler::syncWithMainThread() + { + for (size_t i = 0; i < mActors.size(); ++i) + updateActor(*mActors[i], mActorsFrameData[i], mAdvanceSimulation, mTimeAccum, mPhysicsDt); + } } diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index e2cfccffb0..08997947e4 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -61,7 +61,7 @@ namespace MWPhysics void releaseSharedStates(); // destroy all objects whose destructor can't be safely called from ~PhysicsTaskScheduler() private: - void syncComputation(); + void doSimulation(); void worker(); void updateActorsPositions(); void updateActor(Actor& actor, ActorFrameData& actorData, bool simulationPerformed, float timeAccum, float dt) const; @@ -74,6 +74,7 @@ namespace MWPhysics void afterPreStep(); void afterPostStep(); void afterPostSim(); + void syncWithMainThread(); std::unique_ptr mWorldFrameData; std::vector> mActors; @@ -98,7 +99,6 @@ namespace MWPhysics int mLOSCacheExpiry; bool mNewFrame; bool mAdvanceSimulation; - bool mThreadSafeBullet; bool mQuit; std::atomic mNextJob; std::atomic mNextLOS; diff --git a/components/misc/barrier.hpp b/components/misc/barrier.hpp index b3fe944b04..277e40814a 100644 --- a/components/misc/barrier.hpp +++ b/components/misc/barrier.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_BARRIER_H #define OPENMW_BARRIER_H +#include #include #include @@ -12,7 +13,9 @@ namespace Misc public: /// @param count number of threads to wait on explicit Barrier(int count) : mThreadCount(count), mRendezvousCount(0), mGeneration(0) - {} + { + assert(count >= 0); + } /// @brief stop execution of threads until count distinct threads reach this point /// @param func callable to be executed once after all threads have met @@ -22,8 +25,8 @@ namespace Misc std::unique_lock lock(mMutex); ++mRendezvousCount; - const int currentGeneration = mGeneration; - if (mRendezvousCount == mThreadCount) + const unsigned int currentGeneration = mGeneration; + if (mRendezvousCount == mThreadCount || mThreadCount == 0) { ++mGeneration; mRendezvousCount = 0; @@ -37,9 +40,9 @@ namespace Misc } private: - int mThreadCount; - int mRendezvousCount; - int mGeneration; + unsigned int mThreadCount; + unsigned int mRendezvousCount; + unsigned int mGeneration; mutable std::mutex mMutex; std::condition_variable mRendezvous; }; From 3b174d72d85688fa7355ca5ae2cadc5af09378b3 Mon Sep 17 00:00:00 2001 From: fredzio Date: Tue, 5 Oct 2021 15:43:21 +0200 Subject: [PATCH 123/137] Introduce 3 scoped mutex wrappers to allow to conditionally skip taking mutexes in synchronous case. --- apps/openmw/mwphysics/mtphysics.cpp | 115 ++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index ecf1e6dadd..a0dde67c2e 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -22,22 +22,73 @@ namespace { - /// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration + /// @brief A scoped lock that is either exclusive or inexistent depending on configuration + template + class MaybeExclusiveLock + { + public: + /// @param mutex a mutex + /// @param threadCount decide wether the excluse lock will be taken + MaybeExclusiveLock(Mutex& mutex, int threadCount) : mMutex(mutex), mThreadCount(threadCount) + { + assert(threadCount >= 0); + if (mThreadCount > 0) + mMutex.lock(); + } + + ~MaybeExclusiveLock() + { + if (mThreadCount > 0) + mMutex.unlock(); + } + + private: + Mutex& mMutex; + unsigned int mThreadCount; + }; + + /// @brief A scoped lock that is either shared or inexistent depending on configuration template class MaybeSharedLock { public: /// @param mutex a shared mutex - /// @param threadCount decide wether the lock will be shared, exclusive or inexistent + /// @param threadCount decide wether the shared lock will be taken MaybeSharedLock(Mutex& mutex, int threadCount) : mMutex(mutex), mThreadCount(threadCount) { + assert(threadCount >= 0); + if (mThreadCount > 0) + mMutex.lock_shared(); + } + + ~MaybeSharedLock() + { + if (mThreadCount > 0) + mMutex.unlock_shared(); + } + + private: + Mutex& mMutex; + unsigned int mThreadCount; + }; + + /// @brief A scoped lock that is either shared, exclusive or inexistent depending on configuration + template + class MaybeLock + { + public: + /// @param mutex a shared mutex + /// @param threadCount decide wether the lock will be shared, exclusive or inexistent + MaybeLock(Mutex& mutex, int threadCount) : mMutex(mutex), mThreadCount(threadCount) + { + assert(threadCount >= 0); if (mThreadCount > 1) mMutex.lock_shared(); else if(mThreadCount == 1) mMutex.lock(); } - ~MaybeSharedLock() + ~MaybeLock() { if (mThreadCount > 1) mMutex.unlock_shared(); @@ -46,7 +97,7 @@ namespace } private: Mutex& mMutex; - int mThreadCount; + unsigned int mThreadCount; }; bool isUnderWater(const MWPhysics::ActorFrameData& actorData) @@ -127,11 +178,12 @@ namespace MWPhysics PhysicsTaskScheduler::~PhysicsTaskScheduler() { - std::unique_lock lock(mSimulationMutex); - mQuit = true; - mNumJobs = 0; - mRemainingSteps = 0; - lock.unlock(); + { + MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); + mQuit = true; + mNumJobs = 0; + mRemainingSteps = 0; + } mHasJob.notify_all(); for (auto& thread : mThreads) thread.join(); @@ -188,7 +240,7 @@ namespace MWPhysics // This function run in the main thread. // While the mSimulationMutex is held, background physics threads can't run. - std::unique_lock lock(mSimulationMutex); + MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); assert(actors.size() == actorsData.size()); double timeStart = mTimer->tick(); @@ -239,7 +291,6 @@ namespace MWPhysics } mAsyncStartTime = mTimer->tick(); - lock.unlock(); mHasJob.notify_all(); if (mAdvanceSimulation) mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor); @@ -247,7 +298,7 @@ namespace MWPhysics void PhysicsTaskScheduler::resetSimulation(const ActorMap& actors) { - std::unique_lock lock(mSimulationMutex); + MaybeExclusiveLock lock(mSimulationMutex, mNumThreads); mBudget.reset(mDefaultPhysicsDt); mAsyncBudget.reset(0.0f); mActors.clear(); @@ -261,25 +312,25 @@ namespace MWPhysics void PhysicsTaskScheduler::rayTest(const btVector3& rayFromWorld, const btVector3& rayToWorld, btCollisionWorld::RayResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); + MaybeLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->rayTest(rayFromWorld, rayToWorld, resultCallback); } void PhysicsTaskScheduler::convexSweepTest(const btConvexShape* castShape, const btTransform& from, const btTransform& to, btCollisionWorld::ConvexResultCallback& resultCallback) const { - MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); + MaybeLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->convexSweepTest(castShape, from, to, resultCallback); } void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback); } std::optional PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target) { - MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); + MaybeLock lock(mCollisionWorldMutex, mNumThreads); // target the collision object's world origin, this should be the center of the collision object btTransform rayTo; rayTo.setIdentity(); @@ -296,33 +347,33 @@ namespace MWPhysics void PhysicsTaskScheduler::aabbTest(const btVector3& aabbMin, const btVector3& aabbMax, btBroadphaseAabbCallback& callback) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, callback); } void PhysicsTaskScheduler::getAabb(const btCollisionObject* obj, btVector3& min, btVector3& max) { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); obj->getCollisionShape()->getAabb(obj->getWorldTransform(), min, max); } void PhysicsTaskScheduler::setCollisionFilterMask(btCollisionObject* collisionObject, int collisionFilterMask) { - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); collisionObject->getBroadphaseHandle()->m_collisionFilterMask = collisionFilterMask; } void PhysicsTaskScheduler::addCollisionObject(btCollisionObject* collisionObject, int collisionFilterGroup, int collisionFilterMask) { mCollisionObjects.insert(collisionObject); - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->addCollisionObject(collisionObject, collisionFilterGroup, collisionFilterMask); } void PhysicsTaskScheduler::removeCollisionObject(btCollisionObject* collisionObject) { mCollisionObjects.erase(collisionObject); - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); mCollisionWorld->removeCollisionObject(collisionObject); } @@ -334,14 +385,14 @@ namespace MWPhysics } else { - std::unique_lock lock(mUpdateAabbMutex); + MaybeExclusiveLock lock(mUpdateAabbMutex, mNumThreads); mUpdateAabb.insert(std::move(ptr)); } } bool PhysicsTaskScheduler::getLineOfSight(const std::shared_ptr& actor1, const std::shared_ptr& actor2) { - std::unique_lock lock(mLOSCacheMutex); + MaybeExclusiveLock lock(mLOSCacheMutex, mNumThreads); auto req = LOSRequest(actor1, actor2); auto result = std::find(mLOSCache.begin(), mLOSCache.end(), req); @@ -357,7 +408,7 @@ namespace MWPhysics void PhysicsTaskScheduler::refreshLOSCache() { - std::shared_lock lock(mLOSCacheMutex); + MaybeSharedLock lock(mLOSCacheMutex, mNumThreads); int job = 0; int numLOS = mLOSCache.size(); while ((job = mNextLOS.fetch_add(1, std::memory_order_relaxed)) < numLOS) @@ -376,7 +427,7 @@ namespace MWPhysics void PhysicsTaskScheduler::updateAabbs() { - std::scoped_lock lock(mUpdateAabbMutex); + MaybeExclusiveLock lock(mUpdateAabbMutex, mNumThreads); std::for_each(mUpdateAabb.begin(), mUpdateAabb.end(), [this](const std::shared_ptr& ptr) { updatePtrAabb(ptr); }); mUpdateAabb.clear(); @@ -384,7 +435,7 @@ namespace MWPhysics void PhysicsTaskScheduler::updatePtrAabb(const std::shared_ptr& ptr) { - std::scoped_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); if (const auto actor = std::dynamic_pointer_cast(ptr)) { actor->updateCollisionObjectPosition(); @@ -420,7 +471,7 @@ namespace MWPhysics { if (mActors[i]->setPosition(mActorsFrameData[i].mPosition)) { - std::scoped_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); mActorsFrameData[i].mPosition = mActors[i]->getPosition(); // account for potential position change made by script mActors[i]->updateCollisionObjectPosition(); mCollisionWorld->updateSingleAabb(mActors[i]->getCollisionObject()); @@ -469,7 +520,7 @@ namespace MWPhysics resultCallback.m_collisionFilterGroup = 0xFF; resultCallback.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap|CollisionType_Door; - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mNumThreads); + MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads); mCollisionWorld->rayTest(pos1, pos2, resultCallback); return !resultCallback.hasHit(); @@ -483,7 +534,7 @@ namespace MWPhysics int job = 0; while ((job = mNextJob.fetch_add(1, std::memory_order_relaxed)) < mNumJobs) { - MaybeSharedLock lockColWorld(mCollisionWorldMutex, mNumThreads); + MaybeLock lockColWorld(mCollisionWorldMutex, mNumThreads); MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData); } @@ -511,7 +562,7 @@ namespace MWPhysics void PhysicsTaskScheduler::debugDraw() { - std::shared_lock lock(mCollisionWorldMutex); + MaybeSharedLock lock(mCollisionWorldMutex, mNumThreads); mDebugDrawer->step(); } @@ -537,7 +588,7 @@ namespace MWPhysics return; for (size_t i = 0; i < mActors.size(); ++i) { - std::unique_lock lock(mCollisionWorldMutex); + MaybeExclusiveLock lock(mCollisionWorldMutex, mNumThreads); MovementSolver::unstuck(mActorsFrameData[i], mCollisionWorld); } } @@ -556,7 +607,7 @@ namespace MWPhysics { mNewFrame = false; { - std::unique_lock lock(mLOSCacheMutex); + MaybeExclusiveLock lock(mLOSCacheMutex, mNumThreads); mLOSCache.erase( std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), From 442a0e84345bd30a332ba257d778064d213c4cc7 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:00:30 +0000 Subject: [PATCH 124/137] avoids two transforms in sky.cpp (#3150) As we discovered in #3148, `Transform` nodes and their low level equivalence `pushModelViewMatrix` are somewhat costly involving a `Matrix::invert` operation per frame. With this PR we avoid one `Transform` node for sun flashes and avoid another `pushModelViewMatrix` call in case the sun is fully visible. --- apps/openmw/mwrender/sky.cpp | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index c2ed62ef87..dd62d6678b 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -81,15 +81,15 @@ namespace return mat; } - osg::ref_ptr createTexturedQuad(int numUvSets=1) + osg::ref_ptr createTexturedQuad(int numUvSets=1, float scale=1.f) { osg::ref_ptr geom = new osg::Geometry; osg::ref_ptr verts = new osg::Vec3Array; - verts->push_back(osg::Vec3f(-0.5, -0.5, 0)); - verts->push_back(osg::Vec3f(-0.5, 0.5, 0)); - verts->push_back(osg::Vec3f(0.5, 0.5, 0)); - verts->push_back(osg::Vec3f(0.5, -0.5, 0)); + verts->push_back(osg::Vec3f(-0.5*scale, -0.5*scale, 0)); + verts->push_back(osg::Vec3f(-0.5*scale, 0.5*scale, 0)); + verts->push_back(osg::Vec3f(0.5*scale, 0.5*scale, 0)); + verts->push_back(osg::Vec3f(0.5*scale, -0.5*scale, 0)); geom->setVertexArray(verts); @@ -621,14 +621,13 @@ private: tex->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); tex->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - osg::ref_ptr transform (new osg::PositionAttitudeTransform); - const float scale = 2.6f; - transform->setScale(osg::Vec3f(scale,scale,scale)); + osg::ref_ptr group (new osg::Group); - mTransform->addChild(transform); + mTransform->addChild(group); - osg::ref_ptr geom = createTexturedQuad(); - transform->addChild(geom); + const float scale = 2.6f; + osg::ref_ptr geom = createTexturedQuad(1, scale); + group->addChild(geom); osg::StateSet* stateset = geom->getOrCreateStateSet(); @@ -637,7 +636,7 @@ private: stateset->setRenderBinDetails(RenderBin_SunGlare, "RenderBin"); stateset->setNestRenderBins(false); - mSunFlashNode = transform; + mSunFlashNode = group; mSunFlashCallback = new SunFlashCallback(mOcclusionQueryVisiblePixels, mOcclusionQueryTotalPixels); mSunFlashNode->addCullCallback(mSunFlashCallback); @@ -785,9 +784,11 @@ private: stateset = new osg::StateSet; stateset->setAttributeAndModes(mat, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); } - - const float threshold = 0.6; - visibleRatio = visibleRatio * (1.f - threshold) + threshold; + else if (visibleRatio < 1.f) + { + const float threshold = 0.6; + visibleRatio = visibleRatio * (1.f - threshold) + threshold; + } } float scale = visibleRatio; @@ -797,11 +798,13 @@ private: // no traverse return; } + else if (scale == 1.f) + traverse(node, cv); else { osg::Matrix modelView = *cv->getModelViewMatrix(); - modelView.preMultScale(osg::Vec3f(visibleRatio, visibleRatio, visibleRatio)); + modelView.preMultScale(osg::Vec3f(scale, scale, scale)); if (stateset) cv->pushStateSet(stateset); From 40f18d6a8bdbf913be35d9ce1479a9712254ff77 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 5 Oct 2021 16:39:50 +0200 Subject: [PATCH 125/137] Update cmake.yml to modify cxxflags (#3151) Use the same CXXFLAGS in Github Pipelines as we do in Gitlab Pipelines --- .github/workflows/cmake.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index f9375a3ba5..54966fdfb0 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -27,7 +27,7 @@ jobs: max-size: 1000M - name: Configure - run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + run: cmake -S . -B . -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=./install -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="-Werror -Wno-error=deprecated-declarations -Wno-error=nonnull -Wno-error=deprecated-copy" - name: Build run: cmake --build . --config ${{env.BUILD_TYPE}} --parallel 3 From 035307b012f151260b98aa9de62f5d26da26bd4d Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 30 Sep 2021 03:48:47 +0200 Subject: [PATCH 126/137] Add tests for openmw options In attempt to document current behaviour. Add commented out checks as desired behaviour. --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/main.cpp | 106 +----- apps/openmw/options.cpp | 115 +++++++ apps/openmw/options.hpp | 11 + apps/openmw_test_suite/CMakeLists.txt | 3 + apps/openmw_test_suite/openmw/options.cpp | 379 ++++++++++++++++++++++ components/files/configurationmanager.cpp | 35 +- components/files/configurationmanager.hpp | 17 +- 8 files changed, 555 insertions(+), 112 deletions(-) create mode 100644 apps/openmw/options.cpp create mode 100644 apps/openmw/options.hpp create mode 100644 apps/openmw_test_suite/openmw/options.cpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 82a91699a9..ff89b2af7c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -2,6 +2,7 @@ set(GAME main.cpp engine.cpp + options.cpp ${CMAKE_SOURCE_DIR}/files/windows/openmw.rc ${CMAKE_SOURCE_DIR}/files/windows/openmw.exe.manifest diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index de0fb0df03..d1f9fd1787 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -7,6 +7,7 @@ #include #include "engine.hpp" +#include "options.hpp" #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN @@ -39,108 +40,11 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat namespace bpo = boost::program_options; typedef std::vector StringsVector; - bpo::options_description desc("Syntax: openmw \nAllowed options"); - - desc.add_options() - ("help", "print help message") - ("version", "print version information and quit") - - ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") - - ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") - ->multitoken()->composing(), "set data directories (later directories have higher priority)") - - ("data-local", bpo::value()->default_value(Files::EscapePath(), ""), - "set local data directory (highest priority)") - - ("fallback-archive", bpo::value()->default_value(Files::EscapeStringVector(), "fallback-archive") - ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") - - ("resources", bpo::value()->default_value(Files::EscapePath(), "resources"), - "set resources directory") - - ("start", bpo::value()->default_value(""), - "set initial cell") - - ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") - - ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") - - ("lua-scripts", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "file(s) with a list of global Lua scripts: omwscripts") - - ("no-sound", bpo::value()->implicit_value(true) - ->default_value(false), "disable all sounds") - - ("script-all", bpo::value()->implicit_value(true) - ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") - - ("script-all-dialogue", bpo::value()->implicit_value(true) - ->default_value(false), "compile all dialogue scripts at startup") - - ("script-console", bpo::value()->implicit_value(true) - ->default_value(false), "enable console-only script functionality") - - ("script-run", bpo::value()->default_value(""), - "select a file containing a list of console commands that is executed on startup") - - ("script-warn", bpo::value()->implicit_value (1) - ->default_value (1), - "handling of warnings when compiling scripts\n" - "\t0 - ignore warning\n" - "\t1 - show warning but consider script as correctly compiled anyway\n" - "\t2 - treat warnings as errors") - - ("script-blacklist", bpo::value()->default_value(Files::EscapeStringVector(), "") - ->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)") - - ("script-blacklist-use", bpo::value()->implicit_value(true) - ->default_value(true), "enable script blacklisting") - - ("load-savegame", bpo::value()->default_value(Files::EscapePath(), ""), - "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") - - ("skip-menu", bpo::value()->implicit_value(true) - ->default_value(false), "skip main menu on game startup") - - ("new-game", bpo::value()->implicit_value(true) - ->default_value(false), "run new game sequence (ignored if skip-menu=0)") - - ("fs-strict", bpo::value()->implicit_value(true) - ->default_value(false), "strict file system handling (no case folding)") - - ("encoding", bpo::value()-> - default_value("win1252"), - "Character encoding used in OpenMW game messages:\n" - "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" - "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" - "\n\twin1252 - Western European (Latin) alphabet, used by default") - - ("fallback", bpo::value()->default_value(FallbackMap(), "") - ->multitoken()->composing(), "fallback values") - - ("no-grab", bpo::value()->implicit_value(true)->default_value(false), "Don't grab mouse cursor") - - ("export-fonts", bpo::value()->implicit_value(true) - ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") - - ("activate-dist", bpo::value ()->default_value (-1), "activation distance override") - - ("random-seed", bpo::value () - ->default_value(Misc::Rng::generateDefaultSeed()), - "seed value for random number generator") - ; - - bpo::parsed_options valid_opts = bpo::command_line_parser(argc, argv) - .options(desc).allow_unregistered().run(); + bpo::options_description desc = OpenMW::makeOptionsDescription(); bpo::variables_map variables; - // Runtime options override settings from all configs - bpo::store(valid_opts, variables); + Files::parseArgs(argc, argv, variables, desc); bpo::notify(variables); if (variables.count ("help")) @@ -158,9 +62,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat return false; } - bpo::variables_map composingVariables = cfgMgr.separateComposingVariables(variables, desc); + bpo::variables_map composingVariables = Files::separateComposingVariables(variables, desc); cfgMgr.readConfiguration(variables, desc); - cfgMgr.mergeComposingVariables(variables, composingVariables, desc); + Files::mergeComposingVariables(variables, composingVariables, desc); Version::Version v = Version::getOpenmwVersion(variables["resources"].as().mPath.string()); Log(Debug::Info) << v.describe(); diff --git a/apps/openmw/options.cpp b/apps/openmw/options.cpp new file mode 100644 index 0000000000..62ea0910dd --- /dev/null +++ b/apps/openmw/options.cpp @@ -0,0 +1,115 @@ +#include "options.hpp" + +#include +#include +#include + +#include + +namespace +{ + namespace bpo = boost::program_options; +} + +namespace OpenMW +{ + bpo::options_description makeOptionsDescription() + { + bpo::options_description desc("Syntax: openmw \nAllowed options"); + + desc.add_options() + ("help", "print help message") + ("version", "print version information and quit") + + ("replace", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "settings where the values from the current source should replace those from lower-priority sources instead of being appended") + + ("data", bpo::value()->default_value(Files::EscapePathContainer(), "data") + ->multitoken()->composing(), "set data directories (later directories have higher priority)") + + ("data-local", bpo::value()->default_value(Files::EscapePath(), ""), + "set local data directory (highest priority)") + + ("fallback-archive", bpo::value()->default_value(Files::EscapeStringVector(), "fallback-archive") + ->multitoken()->composing(), "set fallback BSA archives (later archives have higher priority)") + + ("resources", bpo::value()->default_value(Files::EscapePath(), "resources"), + "set resources directory") + + ("start", bpo::value()->default_value(""), + "set initial cell") + + ("content", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "content file(s): esm/esp, or omwgame/omwaddon") + + ("groundcover", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "groundcover content file(s): esm/esp, or omwgame/omwaddon") + + ("lua-scripts", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "file(s) with a list of global Lua scripts: omwscripts") + + ("no-sound", bpo::value()->implicit_value(true) + ->default_value(false), "disable all sounds") + + ("script-all", bpo::value()->implicit_value(true) + ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") + + ("script-all-dialogue", bpo::value()->implicit_value(true) + ->default_value(false), "compile all dialogue scripts at startup") + + ("script-console", bpo::value()->implicit_value(true) + ->default_value(false), "enable console-only script functionality") + + ("script-run", bpo::value()->default_value(""), + "select a file containing a list of console commands that is executed on startup") + + ("script-warn", bpo::value()->implicit_value (1) + ->default_value (1), + "handling of warnings when compiling scripts\n" + "\t0 - ignore warning\n" + "\t1 - show warning but consider script as correctly compiled anyway\n" + "\t2 - treat warnings as errors") + + ("script-blacklist", bpo::value()->default_value(Files::EscapeStringVector(), "") + ->multitoken()->composing(), "ignore the specified script (if the use of the blacklist is enabled)") + + ("script-blacklist-use", bpo::value()->implicit_value(true) + ->default_value(true), "enable script blacklisting") + + ("load-savegame", bpo::value()->default_value(Files::EscapePath(), ""), + "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") + + ("skip-menu", bpo::value()->implicit_value(true) + ->default_value(false), "skip main menu on game startup") + + ("new-game", bpo::value()->implicit_value(true) + ->default_value(false), "run new game sequence (ignored if skip-menu=0)") + + ("fs-strict", bpo::value()->implicit_value(true) + ->default_value(false), "strict file system handling (no case folding)") + + ("encoding", bpo::value()-> + default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default") + + ("fallback", bpo::value()->default_value(Fallback::FallbackMap(), "") + ->multitoken()->composing(), "fallback values") + + ("no-grab", bpo::value()->implicit_value(true)->default_value(false), "Don't grab mouse cursor") + + ("export-fonts", bpo::value()->implicit_value(true) + ->default_value(false), "Export Morrowind .fnt fonts to PNG image and XML file in current directory") + + ("activate-dist", bpo::value ()->default_value (-1), "activation distance override") + + ("random-seed", bpo::value () + ->default_value(Misc::Rng::generateDefaultSeed()), + "seed value for random number generator") + ; + + return desc; + } +} diff --git a/apps/openmw/options.hpp b/apps/openmw/options.hpp new file mode 100644 index 0000000000..246999eb19 --- /dev/null +++ b/apps/openmw/options.hpp @@ -0,0 +1,11 @@ +#ifndef APPS_OPENMW_OPTIONS_H +#define APPS_OPENMW_OPTIONS_H + +#include + +namespace OpenMW +{ + boost::program_options::options_description makeOptionsDescription(); +} + +#endif diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 7800efac3d..5665f59508 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -40,6 +40,9 @@ if (GTEST_FOUND AND GMOCK_FOUND) shader/parsedefines.cpp shader/parsefors.cpp shader/shadermanager.cpp + + ../openmw/options.cpp + openmw/options.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp new file mode 100644 index 0000000000..ebb956b10c --- /dev/null +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -0,0 +1,379 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ + using namespace testing; + using namespace OpenMW; + + namespace bpo = boost::program_options; + + std::vector generateSupportedCharacters(std::vector&& base = {}) + { + std::vector result = std::move(base); + for (int i = 1; i <= std::numeric_limits::max(); ++i) + if (i != '&' && i != '"' && i != ' ' && i != '@' && i != '\n') + result.push_back(std::string(1, i)); + return result; + } + + constexpr std::array supportedAtSignEscapings { + std::pair {'a', '@'}, + std::pair {'h', '#'}, + }; + + MATCHER_P(IsEscapePath, v, "") { return arg.mPath.string() == v; } + + template + void parseArgs(const T& arguments, bpo::variables_map& variables, bpo::options_description& description) + { + Files::parseArgs(static_cast(arguments.size()), arguments.data(), variables, description); + } + + TEST(OpenMWOptionsFromArguments, should_support_equality_to_separate_flag_and_value) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame=save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_single_word_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_multi_component_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "/home/user/openmw/save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "/home/user/openmw/save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_windows_multi_component_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"(C:\OpenMW\save.omwsave)"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(C:\OpenMW\save.omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_spaces) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "my save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my"); +// EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_number_sign) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "my#save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my#save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_at_sign) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "my@save.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my?ave.omwsave"); +// EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my@save.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_quote) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"(my"save.omwsave)"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(my"save.omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_support_quted_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"("save".omwsave)"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(save)"); +// EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"("save".omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"("save&".omwsave")"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(save".omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_support_quoted_load_savegame_path_with_escaped_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"("save.omwsave&&")"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave&"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "save&.omwsave"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save&.omwsave"); + } + + TEST(OpenMWOptionsFromArguments, should_support_load_savegame_path_with_multiple_quotes) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", R"(my"save".omwsave)"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(my"save".omwsave)"); + } + + TEST(OpenMWOptionsFromArguments, should_compose_data) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--data", "1", "--data", "2"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsEscapePath("1"), IsEscapePath("2"))); + } + + TEST(OpenMWOptionsFromArguments, should_compose_data_from_single_flag) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--data", "1", "2"}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsEscapePath("1"), IsEscapePath("2"))); + } + + TEST(OpenMWOptionsFromArguments, should_throw_on_multiple_load_savegame) + { + bpo::options_description description = makeOptionsDescription(); + const std::array arguments {"openmw", "--load-savegame", "1.omwsave", "--load-savegame", "2.omwsave"}; + bpo::variables_map variables; + EXPECT_THROW(parseArgs(arguments, variables, description), std::exception); + } + + struct OpenMWOptionsFromArgumentsStrings : TestWithParam {}; + + TEST_P(OpenMWOptionsFromArgumentsStrings, should_support_paths_with_certain_characters_in_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::string path = "save_" + std::string(GetParam()) + ".omwsave"; + const std::string pathArgument = "\"" + path + "\""; + const std::array arguments {"openmw", "--load-savegame", pathArgument.c_str()}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), path); + } + + INSTANTIATE_TEST_SUITE_P( + SupportedCharacters, + OpenMWOptionsFromArgumentsStrings, + ValuesIn(generateSupportedCharacters({u8"👍", u8"Ъ", u8"Ǽ", "\n"})) + ); + + struct OpenMWOptionsFromArgumentsEscapings : TestWithParam> {}; + + TEST_P(OpenMWOptionsFromArgumentsEscapings, should_support_escaping_with_at_sign_in_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::string path = "save_@" + std::string(1, GetParam().first) + ".omwsave"; + const std::array arguments {"openmw", "--load-savegame", path.c_str()}; + bpo::variables_map variables; + parseArgs(arguments, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), + "save_" + std::string(1, GetParam().second) + ".omwsave"); + } + + INSTANTIATE_TEST_SUITE_P( + SupportedEscapingsWithAtSign, + OpenMWOptionsFromArgumentsEscapings, + ValuesIn(supportedAtSignEscapings) + ); + + TEST(OpenMWOptionsFromConfig, should_support_single_word_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=save.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="save.omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_strip_outer_quotes_from_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame=""save".omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), ""); +// EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(""save".omwsave")"); + } + + TEST(OpenMWOptionsFromConfig, should_strip_quotes_from_load_savegame_path_with_space) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="my save.omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "my save.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_number_sign) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=save#.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save#.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_at_sign) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=save@.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save@.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_quote) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame=save".omwsave)"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(save".omwsave)"); + } + + TEST(OpenMWOptionsFromConfig, should_ignore_commented_option) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("#load-savegame=save.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), ""); + } + + TEST(OpenMWOptionsFromConfig, should_throw_on_multiple_load_savegame) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=1.omwsave\nload-savegame=2.omwsave"); + bpo::variables_map variables; + EXPECT_THROW(Files::parseConfig(stream, variables, description), std::exception); + } + + TEST(OpenMWOptionsFromConfig, should_support_multi_component_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=/home/user/openmw/save.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "/home/user/openmw/save.omwsave"); + } + + TEST(OpenMWOptionsFromConfig, should_support_windows_multi_component_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame=C:\OpenMW\save.omwsave)"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(C:\OpenMW\save.omwsave)"); + } + + TEST(OpenMWOptionsFromConfig, should_compose_data) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("data=1\ndata=2"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_THAT(variables["data"].as(), ElementsAre(IsEscapePath("1"), IsEscapePath("2"))); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_quote_by_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="save&".omwsave")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), R"(save".omwsave)"); + } + + TEST(OpenMWOptionsFromConfig, should_support_quoted_load_savegame_path_with_escaped_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream(R"(load-savegame="save.omwsave&&")"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save.omwsave&"); + } + + TEST(OpenMWOptionsFromConfig, should_support_load_savegame_path_with_ampersand) + { + bpo::options_description description = makeOptionsDescription(); + std::istringstream stream("load-savegame=save&.omwsave"); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), "save&.omwsave"); + } + + struct OpenMWOptionsFromConfigStrings : TestWithParam {}; + + TEST_P(OpenMWOptionsFromConfigStrings, should_support_paths_with_certain_characters_in_load_savegame_path) + { + bpo::options_description description = makeOptionsDescription(); + const std::string path = "save_" + std::string(GetParam()) + ".omwsave"; + std::istringstream stream("load-savegame=\"" + path + "\""); + bpo::variables_map variables; + Files::parseConfig(stream, variables, description); + EXPECT_EQ(variables["load-savegame"].as().mPath.string(), path); + } + + INSTANTIATE_TEST_SUITE_P( + SupportedCharacters, + OpenMWOptionsFromConfigStrings, + ValuesIn(generateSupportedCharacters({u8"👍", u8"Ъ", u8"Ǽ"})) + ); +} diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 35679ef293..b5d8b415f5 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -82,7 +82,8 @@ void ConfigurationManager::readConfiguration(boost::program_options::variables_m mSilent = silent; } -boost::program_options::variables_map ConfigurationManager::separateComposingVariables(boost::program_options::variables_map & variables, boost::program_options::options_description& description) +boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map & variables, + boost::program_options::options_description& description) { boost::program_options::variables_map composingVariables; for (auto itr = variables.begin(); itr != variables.end();) @@ -98,7 +99,8 @@ boost::program_options::variables_map ConfigurationManager::separateComposingVar return composingVariables; } -void ConfigurationManager::mergeComposingVariables(boost::program_options::variables_map & first, boost::program_options::variables_map & second, boost::program_options::options_description& description) +void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, + boost::program_options::options_description& description) { // There are a few places this assumes all variables are present in second, but it's never crashed in the wild, so it looks like that's guaranteed. std::set replacedVariables; @@ -234,13 +236,10 @@ bool ConfigurationManager::loadConfig(const boost::filesystem::path& path, Log(Debug::Info) << "Loading config file: " << cfgFile.string(); boost::filesystem::ifstream configFileStreamUnfiltered(cfgFile); - boost::iostreams::filtering_istream configFileStream; - configFileStream.push(escape_hash_filter()); - configFileStream.push(configFileStreamUnfiltered); + if (configFileStreamUnfiltered.is_open()) { - boost::program_options::store(boost::program_options::parse_config_file( - configFileStream, description, true), variables); + parseConfig(configFileStreamUnfiltered, variables, description); return true; } @@ -300,4 +299,26 @@ const boost::filesystem::path& ConfigurationManager::getScreenshotPath() const return mScreenshotPath; } +void parseArgs(int argc, const char* const argv[], boost::program_options::variables_map& variables, + boost::program_options::options_description& description) +{ + boost::program_options::store( + boost::program_options::command_line_parser(argc, argv).options(description).allow_unregistered().run(), + variables + ); +} + +void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, + boost::program_options::options_description& description) +{ + boost::iostreams::filtering_istream configFileStream; + configFileStream.push(escape_hash_filter()); + configFileStream.push(stream); + + boost::program_options::store( + boost::program_options::parse_config_file(configFileStream, description, true), + variables + ); +} + } /* namespace Cfg */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 0ec0a1f67d..99e65a7658 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -25,10 +25,6 @@ struct ConfigurationManager void readConfiguration(boost::program_options::variables_map& variables, boost::program_options::options_description& description, bool quiet=false); - boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map& variables, boost::program_options::options_description& description); - - void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, boost::program_options::options_description& description); - void processPaths(Files::PathContainer& dataDirs, bool create = false); ///< \param create Try creating the directory, if it does not exist. @@ -68,6 +64,19 @@ struct ConfigurationManager bool mSilent; }; + +boost::program_options::variables_map separateComposingVariables(boost::program_options::variables_map& variables, + boost::program_options::options_description& description); + +void mergeComposingVariables(boost::program_options::variables_map& first, boost::program_options::variables_map& second, + boost::program_options::options_description& description); + +void parseArgs(int argc, const char* const argv[], boost::program_options::variables_map& variables, + boost::program_options::options_description& description); + +void parseConfig(std::istream& stream, boost::program_options::variables_map& variables, + boost::program_options::options_description& description); + } /* namespace Cfg */ #endif /* COMPONENTS_FILES_CONFIGURATIONMANAGER_HPP */ From e581b61ecb758e7d2c6141c18699710817a964ff Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Wed, 6 Oct 2021 08:05:10 +0200 Subject: [PATCH 127/137] check if FORCE_OPAQUE is available before using it. --- files/shaders/nv_default_fragment.glsl | 2 +- files/shaders/nv_nolighting_fragment.glsl | 2 +- files/shaders/objects_fragment.glsl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/shaders/nv_default_fragment.glsl b/files/shaders/nv_default_fragment.glsl index 38dca66c40..245f83b620 100644 --- a/files/shaders/nv_default_fragment.glsl +++ b/files/shaders/nv_default_fragment.glsl @@ -94,7 +94,7 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if FORCE_OPAQUE +#if defined(FORCE_OPAQUE) && FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque gl_FragData[0].a = 1.0; #endif diff --git a/files/shaders/nv_nolighting_fragment.glsl b/files/shaders/nv_nolighting_fragment.glsl index 76b01828d6..7c4f4737e0 100644 --- a/files/shaders/nv_nolighting_fragment.glsl +++ b/files/shaders/nv_nolighting_fragment.glsl @@ -45,7 +45,7 @@ void main() float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #endif -#if FORCE_OPAQUE +#if defined(FORCE_OPAQUE) && FORCE_OPAQUE gl_FragData[0].a = 1.0; #endif diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index c343b6a189..6f6cede4e4 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -220,7 +220,7 @@ void main() #endif gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue); -#if FORCE_OPAQUE +#if defined(FORCE_OPAQUE) && FORCE_OPAQUE // having testing & blending isn't enough - we need to write an opaque pixel to be opaque gl_FragData[0].a = 1.0; #endif From 787f91211d100d2ecbab8eb16becb6a1e22fb625 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:15:47 +0000 Subject: [PATCH 128/137] resets state updater to apply light settings (#3141) resets state updater to apply light settings With this PR we achieve the same effect with fewer lines of code. --- apps/openmw/mwrender/renderingmanager.cpp | 6 +----- components/sceneutil/statesetupdater.hpp | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index be143510b2..331ddeca7b 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1281,11 +1281,7 @@ namespace MWRender defines[name] = key; mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines); - mSceneRoot->removeUpdateCallback(mStateUpdater); - mStateUpdater = new StateUpdater; - mSceneRoot->addUpdateCallback(mStateUpdater); - mStateUpdater->setFogEnd(mViewDistance); - updateAmbient(); + mStateUpdater->reset(); mViewer->startThreading(); } diff --git a/components/sceneutil/statesetupdater.hpp b/components/sceneutil/statesetupdater.hpp index ab091fd39f..35be9cb434 100644 --- a/components/sceneutil/statesetupdater.hpp +++ b/components/sceneutil/statesetupdater.hpp @@ -46,8 +46,7 @@ namespace SceneUtil /// Set default state - optionally override in derived classes /// @par May be used e.g. to allocate StateAttributes. virtual void setDefaults(osg::StateSet* stateset) {} - - protected: + /// Reset mStateSets, forcing a setDefaults() on the next frame. Can be used to change the defaults if needed. void reset(); From cd4d76f8c5121575b711cc7e19a46f1acf49014b Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 6 Oct 2021 09:53:24 +0000 Subject: [PATCH 129/137] discard off-screen lights (#3120) Currently, we run culling tests against all lights in the scene during LightListCallback::pushLightState. We can avoid most of these tests by removing off-screen lights at an earlier stage. We should benchmark the cumulative time spent within LightListCallback::pushLightState before and after this PR. --- components/sceneutil/lightmanager.cpp | 104 +++++++++++++------------- components/sceneutil/lightmanager.hpp | 2 +- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 0abebd4884..f38fd80d26 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -1192,8 +1192,10 @@ namespace SceneUtil return stateset; } - const std::vector& LightManager::getLightsInViewSpace(osg::Camera *camera, const osg::RefMatrix* viewMatrix, size_t frameNum) + const std::vector& LightManager::getLightsInViewSpace(osgUtil::CullVisitor *cv, const osg::RefMatrix* viewMatrix, size_t frameNum) { + osg::Camera* camera = cv->getCurrentCamera(); + osg::observer_ptr camPtr (camera); auto it = mLightsInViewSpace.find(camPtr); @@ -1209,7 +1211,7 @@ namespace SceneUtil float radius = transform.mLightSource->getRadius(); - osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius * mPointLightRadiusMultiplier); + osg::BoundingSphere viewBound = osg::BoundingSphere(osg::Vec3f(0,0,0), radius); transformBoundingSphere(worldViewMat, viewBound); if (!isReflection && mPointLightFadeEnd != 0.f) @@ -1223,6 +1225,15 @@ namespace SceneUtil light->setDiffuse(light->getDiffuse() * fade); } + // remove lights culled by this camera + if (!usingFFP()) + { + viewBound._radius *= 2.f; + if (cv->getModelViewCullingStack().front().isCulled(viewBound)) + continue; + viewBound._radius /= 2.f; + } + viewBound._radius *= mPointLightRadiusMultiplier; LightSourceViewBound l; l.mLightSource = transform.mLightSource; l.mViewBound = viewBound; @@ -1295,46 +1306,41 @@ namespace SceneUtil return false; // Possible optimizations: - // - cull list of lights by the camera frustum // - organize lights in a quad tree - // update light list if necessary - // makes sure we don't update it more than once per frame when rendering with multiple cameras - if (mLastFrameNumber != cv->getTraversalNumber()) - { - mLastFrameNumber = cv->getTraversalNumber(); + mLastFrameNumber = cv->getTraversalNumber(); - // Don't use Camera::getViewMatrix, that one might be relative to another camera! - const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); - const std::vector& lights = mLightManager->getLightsInViewSpace(cv->getCurrentCamera(), viewMatrix, mLastFrameNumber); + // Don't use Camera::getViewMatrix, that one might be relative to another camera! + const osg::RefMatrix* viewMatrix = cv->getCurrentRenderStage()->getInitialViewMatrix(); + const std::vector& lights = mLightManager->getLightsInViewSpace(cv, viewMatrix, mLastFrameNumber); - // get the node bounds in view space - // NB do not node->getBound() * modelView, that would apply the node's transformation twice - osg::BoundingSphere nodeBound; - osg::Transform* transform = node->asTransform(); - if (transform) - { - for (size_t i = 0; i < transform->getNumChildren(); ++i) - nodeBound.expandBy(transform->getChild(i)->getBound()); - } - else - nodeBound = node->getBound(); - osg::Matrixf mat = *cv->getModelViewMatrix(); - transformBoundingSphere(mat, nodeBound); + // get the node bounds in view space + // NB do not node->getBound() * modelView, that would apply the node's transformation twice + osg::BoundingSphere nodeBound; + osg::Transform* transform = node->asTransform(); + if (transform) + { + for (size_t i = 0; i < transform->getNumChildren(); ++i) + nodeBound.expandBy(transform->getChild(i)->getBound()); + } + else + nodeBound = node->getBound(); + osg::Matrixf mat = *cv->getModelViewMatrix(); + transformBoundingSphere(mat, nodeBound); - mLightList.clear(); - for (size_t i = 0; i < lights.size(); ++i) - { - const LightManager::LightSourceViewBound& l = lights[i]; + mLightList.clear(); + for (size_t i = 0; i < lights.size(); ++i) + { + const LightManager::LightSourceViewBound& l = lights[i]; - if (mIgnoredLightSources.count(l.mLightSource)) - continue; + if (mIgnoredLightSources.count(l.mLightSource)) + continue; - if (l.mViewBound.intersects(nodeBound)) - mLightList.push_back(&l); - } + if (l.mViewBound.intersects(nodeBound)) + mLightList.push_back(&l); } + if (!mLightList.empty()) { size_t maxLights = mLightManager->getMaxLights() - mLightManager->getStartLight(); @@ -1343,31 +1349,25 @@ namespace SceneUtil if (mLightList.size() > maxLights) { - // remove lights culled by this camera LightManager::LightList lightList = mLightList; - for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) - { - osg::CullStack::CullingStack& stack = cv->getModelViewCullingStack(); - osg::BoundingSphere bs = (*it)->mViewBound; - bs._radius = bs._radius * 2.0; - osg::CullingSet& cullingSet = stack.front(); - if (cullingSet.isCulled(bs)) + if (mLightManager->usingFFP()) + { + for (auto it = lightList.begin(); it != lightList.end() && lightList.size() > maxLights;) { - it = lightList.erase(it); - continue; + osg::BoundingSphere bs = (*it)->mViewBound; + bs._radius = bs._radius * 2.0; + if (cv->getModelViewCullingStack().front().isCulled(bs)) + it = lightList.erase(it); + else + ++it; } - else - ++it; } - if (lightList.size() > maxLights) - { - // sort by proximity to camera, then get rid of furthest away lights - std::sort(lightList.begin(), lightList.end(), sortLights); - while (lightList.size() > maxLights) - lightList.pop_back(); - } + // sort by proximity to camera, then get rid of furthest away lights + std::sort(lightList.begin(), lightList.end(), sortLights); + while (lightList.size() > maxLights) + lightList.pop_back(); stateset = mLightManager->getLightListStateSet(lightList, cv->getTraversalNumber(), cv->getCurrentRenderStage()->getInitialViewMatrix()); } else diff --git a/components/sceneutil/lightmanager.hpp b/components/sceneutil/lightmanager.hpp index 4048bf9b09..b518a4723c 100644 --- a/components/sceneutil/lightmanager.hpp +++ b/components/sceneutil/lightmanager.hpp @@ -157,7 +157,7 @@ namespace SceneUtil /// Internal use only, called automatically by the LightSource's UpdateCallback void addLight(LightSource* lightSource, const osg::Matrixf& worldMat, size_t frameNum); - const std::vector& getLightsInViewSpace(osg::Camera* camera, const osg::RefMatrix* viewMatrix, size_t frameNum); + const std::vector& getLightsInViewSpace(osgUtil::CullVisitor* cv, const osg::RefMatrix* viewMatrix, size_t frameNum); osg::ref_ptr getLightListStateSet(const LightList& lightList, size_t frameNum, const osg::RefMatrix* viewMatrix); From 87d52dc1fd106b659424cca5fbbc3610d30888fc Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Wed, 6 Oct 2021 10:04:03 +0000 Subject: [PATCH 130/137] fixes coverity-ci warning --- components/nifosg/particle.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 0f103588e8..7821b9c2b8 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -70,16 +70,14 @@ void ParticleSystem::drawImplementation(osg::RenderInfo& renderInfo) const void InverseWorldMatrix::operator()(osg::MatrixTransform *node, osg::NodeVisitor *nv) { - if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR) - { - osg::NodePath path = nv->getNodePath(); - path.pop_back(); + osg::NodePath path = nv->getNodePath(); + path.pop_back(); + + osg::Matrix mat = osg::computeLocalToWorld( path ); + mat.orthoNormalize(mat); // don't undo the scale + mat.invert(mat); + node->setMatrix(mat); - osg::Matrix mat = osg::computeLocalToWorld( path ); - mat.orthoNormalize(mat); // don't undo the scale - mat.invert(mat); - node->setMatrix(mat); - } traverse(node,nv); } From 75402654320dfd3dc810e41fc432a68b66ec2259 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Oct 2021 17:14:00 +0200 Subject: [PATCH 131/137] Fix regression #6328 --- apps/openmw/mwmechanics/spelleffects.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 24816b9ed1..e8795b8bc4 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -63,9 +63,9 @@ namespace auto& creatureStats = target.getClass().getCreatureStats(target); auto stat = creatureStats.getDynamic(index); float current = stat.getCurrent(); - stat.setModified(stat.getModified() - magnitude, 0); - stat.setCurrentModified(stat.getCurrentModified() - magnitude); - stat.setCurrent(current - magnitude); + stat.setModified(stat.getModified() + magnitude, 0); + stat.setCurrentModified(stat.getCurrentModified() + magnitude); + stat.setCurrent(current + magnitude); creatureStats.setDynamic(index, stat); } @@ -487,7 +487,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co index = 2; // Damage "Dynamic" abilities reduce the base value if(spellParams.getType() == ESM::ActiveSpells::Type_Ability) - modDynamicStat(target, index, effect.mMagnitude); + modDynamicStat(target, index, -effect.mMagnitude); else { static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game"); From a1825980c496eb100e719a918ab2cc9f96389f52 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Oct 2021 17:28:48 +0200 Subject: [PATCH 132/137] Define OpenMW specific C++ flags --- CI/before_script.linux.sh | 1 + CMakeLists.txt | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index bc0eb0013d..5d20fa75ce 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -32,6 +32,7 @@ declare -a CMAKE_CONF_OPTS=( -DCMAKE_INSTALL_PREFIX=install -DCMAKE_C_FLAGS='-Werror' -DCMAKE_CXX_FLAGS="${CXX_FLAGS}" + -DOPENMW_CXX_FLAGS="-Werror=implicit-fallthrough" ) if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then diff --git a/CMakeLists.txt b/CMakeLists.txt index ae4584a4cd..2564379847 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -543,8 +543,8 @@ if (BUILD_OPENCS) add_subdirectory (extern/osgQt) endif() -if (CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID STREQUAL Clang) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror=implicit-fallthrough") +if (OPENMW_CXX_FLAGS) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OPENMW_CXX_FLAGS}") endif() # Components From dfc72e9b7e7a78176d2ab38348a2dc608131617f Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Oct 2021 18:25:28 +0200 Subject: [PATCH 133/137] Make StayOutside only block teleportation from exteriors to interiors --- CHANGELOG.md | 1 + apps/openmw/mwgui/travelwindow.cpp | 2 +- apps/openmw/mwworld/actionteleport.cpp | 7 ++++--- apps/openmw/mwworld/actionteleport.hpp | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa8c2e79f..7fd364bfe2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6302: Teleporting disabled actor breaks its disabled state Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken + Bug #6323: Wyrmhaven: Alboin doesn't follower the player character out of his house Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 22e3a55566..3fc0673735 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -74,7 +74,7 @@ namespace MWGui // Add price for the travelling followers std::set followers; - MWWorld::ActionTeleport::getFollowers(player, followers); + MWWorld::ActionTeleport::getFollowers(player, followers, !interior); // Apply followers cost, unlike vanilla the first follower doesn't travel for free price *= 1 + static_cast(followers.size()); diff --git a/apps/openmw/mwworld/actionteleport.cpp b/apps/openmw/mwworld/actionteleport.cpp index 56274eb9a0..d74dcd82f7 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -6,6 +6,7 @@ #include "../mwmechanics/creaturestats.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "player.hpp" @@ -24,7 +25,7 @@ namespace MWWorld { // Find any NPCs that are following the actor and teleport them with him std::set followers; - getFollowers(actor, followers, true); + getFollowers(actor, followers, mCellName.empty(), true); for (std::set::iterator it = followers.begin(); it != followers.end(); ++it) teleport(*it); @@ -62,7 +63,7 @@ namespace MWWorld } } - void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set& out, bool includeHostiles) { + void ActionTeleport::getFollowers(const MWWorld::Ptr& actor, std::set& out, bool toExterior, bool includeHostiles) { std::set followers; MWBase::Environment::get().getMechanicsManager()->getActorsFollowing(actor, followers); @@ -75,7 +76,7 @@ namespace MWWorld if (!includeHostiles && follower.getClass().getCreatureStats(follower).getAiSequence().isInCombat(actor)) continue; - if (!script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1) + if (!toExterior && !script.empty() && follower.getRefData().getLocals().getIntVar(script, "stayoutside") == 1 && follower.getCell()->getCell()->isExterior()) continue; if ((follower.getRefData().getPosition().asVec3() - actor.getRefData().getPosition().asVec3()).length2() > 800 * 800) diff --git a/apps/openmw/mwworld/actionteleport.hpp b/apps/openmw/mwworld/actionteleport.hpp index 0a981a418e..fcbf59a203 100644 --- a/apps/openmw/mwworld/actionteleport.hpp +++ b/apps/openmw/mwworld/actionteleport.hpp @@ -30,7 +30,7 @@ namespace MWWorld /// @param includeHostiles If true, include hostile followers (which won't actually be teleported) in the output, /// e.g. so that the teleport action can calm them. - static void getFollowers(const MWWorld::Ptr& actor, std::set& out, bool includeHostiles = false); + static void getFollowers(const MWWorld::Ptr& actor, std::set& out, bool toExterior, bool includeHostiles = false); }; } From 5037def3b32654b2bdad94ab353d44e851aeab81 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 6 Oct 2021 21:27:08 +0200 Subject: [PATCH 134/137] Parse local variables sharing a name with a function as variables --- CHANGELOG.md | 1 + components/compiler/exprparser.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa8c2e79f..65736756bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,6 +50,7 @@ Bug #6282: Laura craft doesn't follow the player character Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters + Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod Bug #6302: Teleporting disabled actor breaks its disabled state Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken Feature #890: OpenMW-CS: Column filtering diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 23f15c8bf5..2d525e6f8c 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -353,15 +353,21 @@ namespace Compiler { if (const Extensions *extensions = getContext().getExtensions()) { + char returnType; // ignored std::string argumentType; // ignored bool hasExplicit = false; // ignored - if (extensions->isInstruction (keyword, argumentType, hasExplicit)) + bool isInstruction = extensions->isInstruction (keyword, argumentType, hasExplicit); + + if(isInstruction || (mExplicit.empty() && extensions->isFunction(keyword, returnType, argumentType, hasExplicit))) { - // pretend this is not a keyword std::string name = loc.mLiteral; if (name.size()>=2 && name[0]=='"' && name[name.size()-1]=='"') name = name.substr (1, name.size()-2); - return parseName (name, loc, scanner); + if(isInstruction || mLocals.getType(Misc::StringUtils::lowerCase(name)) != ' ') + { + // pretend this is not a keyword + return parseName (name, loc, scanner); + } } } From 08608da62cd7b941dff11110b95e49ce39cda999 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Thu, 7 Oct 2021 08:29:38 +0000 Subject: [PATCH 135/137] optimizer.cpp --- components/sceneutil/optimizer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index dffbe62ec7..2cfabbfe19 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -913,11 +913,11 @@ void Optimizer::RemoveRedundantNodesVisitor::removeRedundantNodes() unsigned int childIndex = (*pitr)->getChildIndex(group); for (unsigned int i=0; igetNumChildren(); ++i) { - osg::Node* child = group->getChild(i); - (*pitr)->insertChild(childIndex++, child); + if (i==0) + (*pitr)->setChild(childIndex, group->getChild(i)); + else + (*pitr)->insertChild(childIndex+i, group->getChild(i)); } - - (*pitr)->removeChild(group); } group->removeChildren(0, group->getNumChildren()); From e5abadc234517beb421a84f410cbc8607e1848dd Mon Sep 17 00:00:00 2001 From: florent teppe Date: Thu, 7 Oct 2021 13:26:40 +0000 Subject: [PATCH 136/137] Fix keyword search when the keyword is preceded by a non whitespace non alpha character --- apps/openmw/mwdialogue/keywordsearch.hpp | 23 --------- .../mwdialogue/test_keywordsearch.cpp | 50 ++++++++++++++++++- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 8ba3349bc8..0c14ea8f8d 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include // std::reverse @@ -69,18 +68,6 @@ public: return false; } - static bool isWhitespaceUTF8(const int utf8Char) - { - if (utf8Char >= 0 && utf8Char <= static_cast( std::numeric_limits::max())) - { - //That function has undefined behavior if the character doesn't fit in unsigned char - return std::isspace(utf8Char); - } - else - { - return false; - } - } static bool sortMatches(const Match& left, const Match& right) { @@ -92,16 +79,6 @@ public: std::vector matches; for (Point i = beg; i != end; ++i) { - // check if previous character marked start of new word - if (i != beg) - { - Point prev = i; - --prev; - if(!isWhitespaceUTF8(*prev)) - continue; - } - - // check first character typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (Misc::StringUtils::toLower (*i)); diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index 62b6f67aae..6128c7e5dd 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -74,7 +74,7 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) search.seed("états", 0); search.seed("ïrradiés", 0); search.seed("ça nous déçois", 0); - + search.seed("ois", 0); std::string text = "les nations unis ont réunis le monde entier, états units inclus pour parler du problème des gens ïrradiés et ça nous déçois"; @@ -86,3 +86,51 @@ TEST_F(KeywordSearchTest, keyword_test_utf8_word_begin) EXPECT_EQ (std::string( matches[1].mBeg, matches[1].mEnd) , "ïrradiés"); EXPECT_EQ (std::string( matches[2].mBeg, matches[2].mEnd) , "ça nous déçois"); } + +TEST_F(KeywordSearchTest, keyword_test_non_alpha_non_whitespace_word_begin) +{ + // We make sure that the search works well even if the separator is not a whitespace + MWDialogue::KeywordSearch search; + search.seed("Report to caius cosades", 0); + + + + std::string text = "I was told to \"Report to caius cosades\""; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 1); + EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Report to caius cosades"); +} + +TEST_F(KeywordSearchTest, keyword_test_russian_non_ascii_before) +{ + // We make sure that the search works well even if the separator is not a whitespace with russian chars + MWDialogue::KeywordSearch search; + search.seed("Доложить Каю Косадесу", 0); + + std::string text = "Что? Да. Я Кай Косадес. То есть как это, вам велели «Доложить Каю Косадесу»? О чем вы говорите?"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 1); + EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Доложить Каю Косадесу"); +} + +TEST_F(KeywordSearchTest, keyword_test_russian_ascii_before) +{ + // We make sure that the search works well even if the separator is not a whitespace with russian chars + MWDialogue::KeywordSearch search; + search.seed("Доложить Каю Косадесу", 0); + + std::string text = "Что? Да. Я Кай Косадес. То есть как это, вам велели 'Доложить Каю Косадесу'? О чем вы говорите?"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + EXPECT_EQ(matches.size(), 1); + EXPECT_EQ(std::string(matches[0].mBeg, matches[0].mEnd), "Доложить Каю Косадесу"); +} + From 5242e2695c3cf908820607bd28b50c42355421f2 Mon Sep 17 00:00:00 2001 From: Bo Svensson <90132211+bosvensson1@users.noreply.github.com> Date: Fri, 8 Oct 2021 07:56:55 +0000 Subject: [PATCH 137/137] avoids memory allocations within ComputeLightSpaceBounds (#3156) Currently, we create a new ComputeLightSpaceBounds visitor per frame. Within this visitor, we require excessive memory allocations, mainly a new osg::RefMatrix per encountered Transform node. With this PR we reuse a single ComputeLightSpaceBounds visitor across frames and enable the createOrReuseMatrix functionality to avoid allocating new matrices every frame. osgUtil::CullVisitor internally uses the same approach. --- components/sceneutil/mwshadowtechnique.cpp | 29 ++++++++++++++++------ components/sceneutil/mwshadowtechnique.hpp | 6 ++++- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index 95ff8f2b01..db7aef7cb1 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -353,18 +353,20 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) } // namespace -MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds(osg::Viewport* viewport, const osg::Matrixd& projectionMatrix, osg::Matrixd& viewMatrix) : +MWShadowTechnique::ComputeLightSpaceBounds::ComputeLightSpaceBounds() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN) { setCullingMode(osg::CullSettings::VIEW_FRUSTUM_CULLING); - pushViewport(viewport); - pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); - pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); - setName("SceneUtil::MWShadowTechnique::ComputeLightSpaceBounds,AcceptedByComponentsTerrainQuadTreeWorld"); } +void MWShadowTechnique::ComputeLightSpaceBounds::reset() +{ + osg::CullStack::reset(); + _bb = osg::BoundingBox(); +} + void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Node& node) { if (isCulled(node)) return; @@ -421,9 +423,9 @@ void MWShadowTechnique::ComputeLightSpaceBounds::apply(osg::Transform& transform // absolute transforms won't affect a shadow map so their subgraphs should be ignored. if (transform.getReferenceFrame() == osg::Transform::RELATIVE_RF) { - osg::ref_ptr matrix = new osg::RefMatrix(*getModelViewMatrix()); + osg::RefMatrix* matrix = createOrReuseMatrix(*getModelViewMatrix()); transform.computeLocalToWorldMatrix(*matrix, this); - pushModelViewMatrix(matrix.get(), transform.getReferenceFrame()); + pushModelViewMatrix(matrix, transform.getReferenceFrame()); traverse(transform); @@ -1125,7 +1127,12 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) // osg::ElapsedTime timer; osg::ref_ptr viewport = new osg::Viewport(0,0,2048,2048); - ComputeLightSpaceBounds clsb(viewport.get(), projectionMatrix, viewMatrix); + if (!_clsb) _clsb = new ComputeLightSpaceBounds; + ComputeLightSpaceBounds& clsb = *_clsb; + clsb.reset(); + clsb.pushViewport(viewport); + clsb.pushProjectionMatrix(new osg::RefMatrix(projectionMatrix)); + clsb.pushModelViewMatrix(new osg::RefMatrix(viewMatrix), osg::Transform::ABSOLUTE_RF); clsb.setTraversalMask(_shadowedScene->getCastsShadowTraversalMask()); osg::Matrixd invertModelView; @@ -1139,6 +1146,12 @@ void MWShadowTechnique::cull(osgUtil::CullVisitor& cv) _shadowedScene->accept(clsb); + clsb.popCullingSet(); + + clsb.popModelViewMatrix(); + clsb.popProjectionMatrix(); + clsb.popViewport(); + // OSG_NOTICE<<"Extents of LightSpace "<