From 12044a607bcad2f9d394dbb43b899635b8e29aed Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Fri, 10 Apr 2020 15:45:37 +0100 Subject: [PATCH 01/58] Only alpha-test shadows when necessary Previously we always discarded shadow map fragments if the alpha channel of the output would have been low, but there were some (modded) assets that have non-one alpha but have testing or blending disabled so end up opaque anyway. This lets the shadows of those objects match. --- components/sceneutil/mwshadowtechnique.cpp | 10 +++++ components/sceneutil/mwshadowtechnique.hpp | 1 + components/shader/shadermanager.cpp | 10 +++++ components/shader/shadermanager.hpp | 6 +++ components/shader/shadervisitor.cpp | 47 +++++++++++++++++++++- components/shader/shadervisitor.hpp | 3 ++ files/shaders/shadowcasting_fragment.glsl | 7 ++++ files/shaders/shadowcasting_vertex.glsl | 8 ++-- 8 files changed, 87 insertions(+), 5 deletions(-) diff --git a/components/sceneutil/mwshadowtechnique.cpp b/components/sceneutil/mwshadowtechnique.cpp index f31d2faef..cb3a1b278 100644 --- a/components/sceneutil/mwshadowtechnique.cpp +++ b/components/sceneutil/mwshadowtechnique.cpp @@ -872,6 +872,15 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh _castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); _castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); + + _shadowMapAlphaTestDisableUniform = shaderManager.getShadowMapAlphaTestDisableUniform(); + _shadowMapAlphaTestDisableUniform->setName("alphaTestShadows"); + _shadowMapAlphaTestDisableUniform->setType(osg::Uniform::BOOL); + _shadowMapAlphaTestDisableUniform->set(false); + + shaderManager.getShadowMapAlphaTestEnableUniform()->setName("alphaTestShadows"); + shaderManager.getShadowMapAlphaTestEnableUniform()->setType(osg::Uniform::BOOL); + shaderManager.getShadowMapAlphaTestEnableUniform()->set(true); } MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) @@ -1570,6 +1579,7 @@ void MWShadowTechnique::createShaders() // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); + _shadowCastingStateSet->addUniform(_shadowMapAlphaTestDisableUniform); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); diff --git a/components/sceneutil/mwshadowtechnique.hpp b/components/sceneutil/mwshadowtechnique.hpp index 165613f3c..85e548b4b 100644 --- a/components/sceneutil/mwshadowtechnique.hpp +++ b/components/sceneutil/mwshadowtechnique.hpp @@ -286,6 +286,7 @@ namespace SceneUtil { osg::ref_ptr _debugHud; osg::ref_ptr _castingProgram; + osg::ref_ptr _shadowMapAlphaTestDisableUniform; }; } diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index 0a7345b97..92848de86 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -370,4 +370,14 @@ namespace Shader program.second->releaseGLObjects(state); } + const osg::ref_ptr ShaderManager::getShadowMapAlphaTestEnableUniform() + { + return mShadowMapAlphaTestEnableUniform; + } + + const osg::ref_ptr ShaderManager::getShadowMapAlphaTestDisableUniform() + { + return mShadowMapAlphaTestDisableUniform; + } + } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 05775edb6..4ea979c60 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -44,6 +44,9 @@ namespace Shader void releaseGLObjects(osg::State* state); + const osg::ref_ptr getShadowMapAlphaTestEnableUniform(); + const osg::ref_ptr getShadowMapAlphaTestDisableUniform(); + private: std::string mPath; @@ -61,6 +64,9 @@ namespace Shader ProgramMap mPrograms; OpenThreads::Mutex mMutex; + + const osg::ref_ptr mShadowMapAlphaTestEnableUniform = new osg::Uniform(); + const osg::ref_ptr mShadowMapAlphaTestDisableUniform = new osg::Uniform(); }; } diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 7fb5d53f5..b2a6d6f63 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -1,8 +1,10 @@ #include "shadervisitor.hpp" -#include -#include +#include +#include #include +#include +#include #include @@ -23,6 +25,8 @@ namespace Shader : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) + , mAlphaFuncOverridden(false) + , mBlendFuncOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mNode(nullptr) @@ -229,15 +233,21 @@ namespace Shader { if (!writableStateSet) writableStateSet = getWritableStateSet(node); + // We probably shouldn't construct a new version of this each time as StateSets only use pointer comparison by default. + // Also it should probably belong to the shader manager writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); } } + bool alphaSettingsChanged = false; + bool alphaTestShadows = false; + const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) { if (it->first.first == osg::StateAttribute::MATERIAL) { + // This should probably be moved out of ShaderRequirements and be applied directly now it's a uniform instead of a define if (!mRequirements.back().mMaterialOverridden || it->second.second & osg::StateAttribute::PROTECTED) { if (it->second.second & osg::StateAttribute::OVERRIDE) @@ -269,6 +279,39 @@ namespace Shader mRequirements.back().mColorMode = colorMode; } } + else if (it->first.first == osg::StateAttribute::ALPHAFUNC) + { + if (!mRequirements.back().mAlphaFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mAlphaFuncOverridden = true; + + const osg::AlphaFunc* test = static_cast(it->second.first.get()); + if (test->getFunction() == osg::AlphaFunc::GREATER || test->getFunction() == osg::AlphaFunc::GEQUAL) + alphaTestShadows = true; + alphaSettingsChanged = true; + } + } + else if (it->first.first == osg::StateAttribute::BLENDFUNC) + { + if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) + { + if (it->second.second & osg::StateAttribute::OVERRIDE) + mRequirements.back().mBlendFuncOverridden = true; + + const osg::BlendFunc* blend = static_cast(it->second.first.get()); + if (blend->getSource() == osg::BlendFunc::SRC_ALPHA || blend->getSource() == osg::BlendFunc::SRC_COLOR) + alphaTestShadows = true; + alphaSettingsChanged = true; + } + } + } + // we don't need to check for glEnable/glDisable of blending and testing as we always set it at the same time + if (alphaSettingsChanged) + { + if (!writableStateSet) + writableStateSet = getWritableStateSet(node); + writableStateSet->addUniform(alphaTestShadows ? mShaderManager.getShadowMapAlphaTestEnableUniform() : mShaderManager.getShadowMapAlphaTestDisableUniform()); } } diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index ac0ecc699..311f6213f 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -75,6 +75,9 @@ namespace Shader int mColorMode; bool mMaterialOverridden; + bool mAlphaFuncOverridden; + bool mBlendFuncOverridden; + bool mNormalHeight; // true if normal map has height info in alpha channel // -1 == no tangents required diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 336bfe4a4..47323cc6a 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -6,10 +6,17 @@ varying vec2 diffuseMapUV; varying float alphaPassthrough; uniform bool useDiffuseMapForShadowAlpha; +uniform bool alphaTestShadows; void main() { gl_FragData[0].rgb = vec3(1.0); + if (!alphaTestShadows) + { + gl_FragData[0].a = 1.0; + return; + } + if (useDiffuseMapForShadowAlpha) gl_FragData[0].a = texture2D(diffuseMap, diffuseMapUV).a * alphaPassthrough; else diff --git a/files/shaders/shadowcasting_vertex.glsl b/files/shaders/shadowcasting_vertex.glsl index d578e97b7..e19b587e5 100644 --- a/files/shaders/shadowcasting_vertex.glsl +++ b/files/shaders/shadowcasting_vertex.glsl @@ -6,6 +6,7 @@ varying float alphaPassthrough; uniform int colorMode; uniform bool useDiffuseMapForShadowAlpha; +uniform bool alphaTestShadows; void main(void) { @@ -14,12 +15,13 @@ void main(void) vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; - if (useDiffuseMapForShadowAlpha) + if (alphaTestShadows && useDiffuseMapForShadowAlpha) diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy; else diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions - - if (colorMode == 2) + if (!alphaTestShadows) + alphaPassthrough = 1.0; + else if (colorMode == 2) alphaPassthrough = gl_Color.a; else // This is uniform, so if it's too low, we might be able to put the position/clip vertex outside the view frustum and skip the fragment shader and rasteriser From 9698c21b367d059cfdab07ada7660fd5f6ce663b Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 17 Apr 2020 13:50:54 +0200 Subject: [PATCH 02/58] build bsa and esm tools --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 847435ac5..77ac1cc33 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -759,8 +759,8 @@ echo cd $DEPS_INSTALL/.. echo echo "Setting up OpenMW build..." -add_cmake_opts -DBUILD_BSATOOL=no \ - -DBUILD_ESMTOOL=no \ +add_cmake_opts -DBUILD_BSATOOL=yes \ + -DBUILD_ESMTOOL=yes \ -DBUILD_MYGUI_PLUGIN=no \ -DOPENMW_MP_BUILD=on if [ ! -z $CI ]; then From 0d10293f24a6142fe6868d95bf6cc7b6ec3d8cb0 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Fri, 17 Apr 2020 15:10:05 +0200 Subject: [PATCH 03/58] everything is implied yes --- CI/before_script.msvc.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 77ac1cc33..2427f2be1 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -759,9 +759,7 @@ echo cd $DEPS_INSTALL/.. echo echo "Setting up OpenMW build..." -add_cmake_opts -DBUILD_BSATOOL=yes \ - -DBUILD_ESMTOOL=yes \ - -DBUILD_MYGUI_PLUGIN=no \ +add_cmake_opts -DBUILD_MYGUI_PLUGIN=no \ -DOPENMW_MP_BUILD=on if [ ! -z $CI ]; then case $STEP in From 2b54e6216b4b8773d3b7481684540849ac5a5e82 Mon Sep 17 00:00:00 2001 From: p4r4digm Date: Sun, 19 Apr 2020 16:34:00 -0700 Subject: [PATCH 04/58] Added setting to change the directory screenshots are stored in --- apps/openmw/engine.cpp | 4 ++-- components/files/configurationmanager.cpp | 13 +++++++++++++ components/files/configurationmanager.hpp | 1 + files/settings-default.cfg | 3 +++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index ec8c1e305..5b0bd491f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -657,7 +657,6 @@ private: }; // Initialise and enter main loop. - void OMW::Engine::go() { assert (!mContentFiles.empty()); @@ -686,7 +685,8 @@ void OMW::Engine::go() mViewer->setUseConfigureAffinity(false); #endif - mScreenCaptureOperation = new WriteScreenshotToFileOperation(mCfgMgr.getUserDataPath().string(), + mScreenCaptureOperation = new WriteScreenshotToFileOperation( + mCfgMgr.getScreenshotPath(Settings::Manager::getString("screenshot path", "General")).string(), Settings::Manager::getString("screenshot format", "General")); mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 3bc6e1772..231401c08 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -196,4 +196,17 @@ const boost::filesystem::path& ConfigurationManager::getLogPath() const return mLogPath; } +const boost::filesystem::path ConfigurationManager::getScreenshotPath(std::string const& screenshotSettings) const +{ + boost::filesystem::path ssPath = screenshotSettings; + if (ssPath.is_relative()) { + ssPath = mFixedPath.getUserDataPath() / ssPath; + } + boost::system::error_code dirErr; + if (!boost::filesystem::create_directories(ssPath, dirErr) && !boost::filesystem::is_directory(ssPath)) { + ssPath = mFixedPath.getUserDataPath(); + } + return ssPath; +} + } /* namespace Cfg */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index df131e671..1c6123b84 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -41,6 +41,7 @@ struct ConfigurationManager const boost::filesystem::path& getCachePath() const; const boost::filesystem::path& getLogPath() const; + const boost::filesystem::path getScreenshotPath(std::string const& screenshotSetting) const; private: typedef Files::FixedPath<> FixedPathType; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 06950e50d..08a520ca0 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -284,6 +284,9 @@ anisotropy = 4 # File format for screenshots. (jpg, png, tga, and possibly more). screenshot format = png +# Directory to store screenshots in. Supports relative and absolute paths. Relative paths will be to the user data folder. +screenshot path =./ + # Texture magnification filter type. (nearest or linear). texture mag filter = linear From 0741fe5b800278b11e2a34bb878116a38c21eee1 Mon Sep 17 00:00:00 2001 From: p4r4digm Date: Mon, 20 Apr 2020 09:22:50 -0700 Subject: [PATCH 05/58] removed path configuration and made screenshots just save in a folder --- apps/openmw/engine.cpp | 2 +- components/files/configurationmanager.cpp | 20 ++++++++++---------- components/files/configurationmanager.hpp | 3 ++- files/settings-default.cfg | 3 --- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 5b0bd491f..3d609259f 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -686,7 +686,7 @@ void OMW::Engine::go() #endif mScreenCaptureOperation = new WriteScreenshotToFileOperation( - mCfgMgr.getScreenshotPath(Settings::Manager::getString("screenshot path", "General")).string(), + mCfgMgr.getScreenshotPath().string(), Settings::Manager::getString("screenshot format", "General")); mScreenCaptureHandler = new osgViewer::ScreenCaptureHandler(mScreenCaptureOperation); diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 231401c08..0ba2d1519 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -32,6 +32,14 @@ ConfigurationManager::ConfigurationManager(bool silent) boost::filesystem::create_directories(mFixedPath.getUserDataPath()); mLogPath = mFixedPath.getUserConfigPath(); + + mScreenshotPath = mFixedPath.getUserDataPath() / "screenshots"; + + // probably not necessary but validate the creation of the screenshots directory and fallback to the original behavior if it fails + boost::system::error_code dirErr; + if (!boost::filesystem::create_directories(mScreenshotPath, dirErr) && !boost::filesystem::is_directory(mScreenshotPath)) { + mScreenshotPath = mFixedPath.getUserDataPath(); + } } ConfigurationManager::~ConfigurationManager() @@ -196,17 +204,9 @@ const boost::filesystem::path& ConfigurationManager::getLogPath() const return mLogPath; } -const boost::filesystem::path ConfigurationManager::getScreenshotPath(std::string const& screenshotSettings) const +const boost::filesystem::path& ConfigurationManager::getScreenshotPath() const { - boost::filesystem::path ssPath = screenshotSettings; - if (ssPath.is_relative()) { - ssPath = mFixedPath.getUserDataPath() / ssPath; - } - boost::system::error_code dirErr; - if (!boost::filesystem::create_directories(ssPath, dirErr) && !boost::filesystem::is_directory(ssPath)) { - ssPath = mFixedPath.getUserDataPath(); - } - return ssPath; + return mScreenshotPath; } } /* namespace Cfg */ diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 1c6123b84..446abd4dc 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -41,7 +41,7 @@ struct ConfigurationManager const boost::filesystem::path& getCachePath() const; const boost::filesystem::path& getLogPath() const; - const boost::filesystem::path getScreenshotPath(std::string const& screenshotSetting) const; + const boost::filesystem::path& getScreenshotPath() const; private: typedef Files::FixedPath<> FixedPathType; @@ -58,6 +58,7 @@ struct ConfigurationManager FixedPathType mFixedPath; boost::filesystem::path mLogPath; + boost::filesystem::path mScreenshotPath; TokensMappingContainer mTokensMapping; diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 08a520ca0..06950e50d 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -284,9 +284,6 @@ anisotropy = 4 # File format for screenshots. (jpg, png, tga, and possibly more). screenshot format = png -# Directory to store screenshots in. Supports relative and absolute paths. Relative paths will be to the user data folder. -screenshot path =./ - # Texture magnification filter type. (nearest or linear). texture mag filter = linear From 53b9b411591b2bc22e78ba2cc15fa422449d89a1 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Tue, 21 Apr 2020 18:18:55 +0100 Subject: [PATCH 06/58] Rely on existing alpha test for non-blended shadow casting --- components/shader/shadervisitor.cpp | 17 ++--------------- components/shader/shadervisitor.hpp | 1 - files/shaders/shadowcasting_fragment.glsl | 10 ++-------- files/shaders/shadowcasting_vertex.glsl | 10 ++++------ 4 files changed, 8 insertions(+), 30 deletions(-) diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index b2a6d6f63..639a7ecca 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -25,7 +25,6 @@ namespace Shader : mShaderRequired(false) , mColorMode(0) , mMaterialOverridden(false) - , mAlphaFuncOverridden(false) , mBlendFuncOverridden(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) @@ -279,19 +278,6 @@ namespace Shader mRequirements.back().mColorMode = colorMode; } } - else if (it->first.first == osg::StateAttribute::ALPHAFUNC) - { - if (!mRequirements.back().mAlphaFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) - { - if (it->second.second & osg::StateAttribute::OVERRIDE) - mRequirements.back().mAlphaFuncOverridden = true; - - const osg::AlphaFunc* test = static_cast(it->second.first.get()); - if (test->getFunction() == osg::AlphaFunc::GREATER || test->getFunction() == osg::AlphaFunc::GEQUAL) - alphaTestShadows = true; - alphaSettingsChanged = true; - } - } else if (it->first.first == osg::StateAttribute::BLENDFUNC) { if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED) @@ -305,8 +291,9 @@ namespace Shader alphaSettingsChanged = true; } } + // Eventually, move alpha testing to discard in shader adn remove deprecated state here } - // we don't need to check for glEnable/glDisable of blending and testing as we always set it at the same time + // we don't need to check for glEnable/glDisable of blending as we always set it at the same time if (alphaSettingsChanged) { if (!writableStateSet) diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 311f6213f..8e35f1d9c 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -75,7 +75,6 @@ namespace Shader int mColorMode; bool mMaterialOverridden; - bool mAlphaFuncOverridden; bool mBlendFuncOverridden; bool mNormalHeight; // true if normal map has height info in alpha channel diff --git a/files/shaders/shadowcasting_fragment.glsl b/files/shaders/shadowcasting_fragment.glsl index 47323cc6a..a5410d008 100644 --- a/files/shaders/shadowcasting_fragment.glsl +++ b/files/shaders/shadowcasting_fragment.glsl @@ -11,18 +11,12 @@ uniform bool alphaTestShadows; void main() { gl_FragData[0].rgb = vec3(1.0); - if (!alphaTestShadows) - { - gl_FragData[0].a = 1.0; - return; - } - if (useDiffuseMapForShadowAlpha) gl_FragData[0].a = texture2D(diffuseMap, diffuseMapUV).a * alphaPassthrough; else gl_FragData[0].a = alphaPassthrough; - // Prevent translucent things casting shadow (including the player using an invisibility effect) - if (gl_FragData[0].a <= 0.5) + // Prevent translucent things casting shadow (including the player using an invisibility effect). For now, rely on the deprecated FF test for non-blended stuff. + if (alphaTestShadows && gl_FragData[0].a <= 0.5) discard; } diff --git a/files/shaders/shadowcasting_vertex.glsl b/files/shaders/shadowcasting_vertex.glsl index e19b587e5..e36f21a4d 100644 --- a/files/shaders/shadowcasting_vertex.glsl +++ b/files/shaders/shadowcasting_vertex.glsl @@ -5,8 +5,8 @@ varying vec2 diffuseMapUV; varying float alphaPassthrough; uniform int colorMode; -uniform bool useDiffuseMapForShadowAlpha; -uniform bool alphaTestShadows; +uniform bool useDiffuseMapForShadowAlpha = true; +uniform bool alphaTestShadows = true; void main(void) { @@ -15,13 +15,11 @@ void main(void) vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex); gl_ClipVertex = viewPos; - if (alphaTestShadows && useDiffuseMapForShadowAlpha) + if (useDiffuseMapForShadowAlpha) diffuseMapUV = (gl_TextureMatrix[0] * gl_MultiTexCoord0).xy; else diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions - if (!alphaTestShadows) - alphaPassthrough = 1.0; - else if (colorMode == 2) + if (colorMode == 2) alphaPassthrough = gl_Color.a; else // This is uniform, so if it's too low, we might be able to put the position/clip vertex outside the view frustum and skip the fragment shader and rasteriser From ac256f05ff0f9aae575b175ba4e78ccbf6f8f292 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Wed, 22 Apr 2020 19:20:48 +0100 Subject: [PATCH 07/58] Close graphics context while it still exists --- extern/osgQt/GraphicsWindowQt.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extern/osgQt/GraphicsWindowQt.cpp b/extern/osgQt/GraphicsWindowQt.cpp index af963c04b..aa9b4bbdb 100644 --- a/extern/osgQt/GraphicsWindowQt.cpp +++ b/extern/osgQt/GraphicsWindowQt.cpp @@ -119,6 +119,13 @@ bool GLWidget::event( QEvent* event ) enqueueDeferredEvent(QEvent::ParentChange); return true; } +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + else if (event->type() == QEvent::PlatformSurface && static_cast(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) + { + if (_gw) + _gw->close(); + } +#endif // perform regular event handling return QGLWidget::event( event ); From 688e804548861d3b12009ab414da05e58438aae7 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 24 Apr 2020 13:21:49 +0300 Subject: [PATCH 08/58] Fix simple water with radial fog enabled --- components/sceneutil/waterutil.cpp | 3 +++ files/shaders/objects_fragment.glsl | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/components/sceneutil/waterutil.cpp b/components/sceneutil/waterutil.cpp index 562b0ee73..b98a19ae8 100644 --- a/components/sceneutil/waterutil.cpp +++ b/components/sceneutil/waterutil.cpp @@ -74,6 +74,9 @@ namespace SceneUtil stateset->setRenderBinDetails(renderBin, "RenderBin"); + // Let the shader know we're dealing with simple water here. + stateset->addUniform(new osg::Uniform("simpleWater", true)); + return stateset; } } diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 31e929a90..81884feac 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -49,6 +49,8 @@ uniform vec2 envMapLumaBias; uniform mat2 bumpMapMatrix; #endif +uniform bool simpleWater = false; + varying float euclideanDepth; varying float linearDepth; @@ -180,7 +182,11 @@ void main() gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing; #if @radialFog - float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); + float depth = euclideanDepth; + // For the less detailed mesh of simple water we need to recalculate depth on per-pixel basis + if (simpleWater) + depth = length(passViewPos); + float fogValue = clamp((depth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #else float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0); #endif From ee60d4bcea96ab5c8abcfc807bb242e29c68d5f4 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 7 Nov 2019 22:08:15 +0100 Subject: [PATCH 09/58] Remove unused ItemStack::stacks --- apps/openmw/mwgui/itemmodel.cpp | 20 -------------------- apps/openmw/mwgui/itemmodel.hpp | 1 - 2 files changed, 21 deletions(-) diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 5bbf74e26..cf88efaae 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -32,26 +32,6 @@ namespace MWGui { } - bool ItemStack::stacks(const ItemStack &other) - { - if(mBase == other.mBase) - return true; - - // If one of the items is in an inventory and currently equipped, we need to check stacking both ways to be sure - if (mBase.getContainerStore() && other.mBase.getContainerStore()) - return mBase.getContainerStore()->stacks(mBase, other.mBase) - && other.mBase.getContainerStore()->stacks(mBase, other.mBase); - - if (mBase.getContainerStore()) - return mBase.getContainerStore()->stacks(mBase, other.mBase); - if (other.mBase.getContainerStore()) - return other.mBase.getContainerStore()->stacks(mBase, other.mBase); - - MWWorld::ContainerStore store; - return store.stacks(mBase, other.mBase); - - } - bool operator == (const ItemStack& left, const ItemStack& right) { if (left.mType != right.mType) diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 36432d479..4d923bae3 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -13,7 +13,6 @@ namespace MWGui { ItemStack (const MWWorld::Ptr& base, ItemModel* creator, size_t count); ItemStack(); - bool stacks (const ItemStack& other); ///< like operator==, only without checking mType enum Type From c59c8ae1f4282365fa07f2e5cbcdd8734f8d0d80 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Apr 2020 18:36:51 +0200 Subject: [PATCH 10/58] Remove unused macro --- components/sceneutil/detourdebugdraw.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/components/sceneutil/detourdebugdraw.cpp b/components/sceneutil/detourdebugdraw.cpp index b9c2fecef..7ef329fc1 100644 --- a/components/sceneutil/detourdebugdraw.cpp +++ b/components/sceneutil/detourdebugdraw.cpp @@ -7,9 +7,6 @@ #include #include -#define OPENMW_TO_STRING(X) #X -#define OPENMW_LINE_STRING OPENMW_TO_STRING(__LINE__) - namespace { using DetourNavigator::operator<<; @@ -121,6 +118,3 @@ namespace SceneUtil mColors->push_back(value); } } - -#undef OPENMW_TO_STRING -#undef OPENMW_LINE_STRING From 066f0a744fa181c3d0d80335a4d118a0e408f59c Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 25 Apr 2020 00:26:42 +0200 Subject: [PATCH 11/58] Add env variable to enable/disable crash catcher --- components/crashcatcher/crashcatcher.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/crashcatcher/crashcatcher.cpp b/components/crashcatcher/crashcatcher.cpp index 006ab2d88..99df2cfd8 100644 --- a/components/crashcatcher/crashcatcher.cpp +++ b/components/crashcatcher/crashcatcher.cpp @@ -555,6 +555,9 @@ static bool is_debugger_present() void crashCatcherInstall(int argc, char **argv, const std::string &crashLogPath) { + if (const auto env = std::getenv("OPENMW_DISABLE_CRASH_CATCHER")) + if (std::atol(env) != 0) + return; if ((argc == 2 && strcmp(argv[1], crash_switch) == 0) || !is_debugger_present()) { int s[5] = { SIGSEGV, SIGILL, SIGFPE, SIGBUS, SIGABRT }; From 7502db1570c6773487dcc13ef35363cb926a532f Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 22 Feb 2020 12:13:08 -0800 Subject: [PATCH 12/58] Add number of thread jobs to update jobs stats --- .../detournavigator/asyncnavmeshupdater.cpp | 17 +++++++++++++---- .../detournavigator/asyncnavmeshupdater.hpp | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index e085aba16..da1ac19a7 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -7,6 +7,8 @@ #include +#include + namespace { using DetourNavigator::ChangeType; @@ -101,7 +103,7 @@ namespace DetourNavigator void AsyncNavMeshUpdater::wait() { std::unique_lock lock(mMutex); - mDone.wait(lock, [&] { return mJobs.empty(); }); + mDone.wait(lock, [&] { return mJobs.empty() && getTotalThreadJobsUnsafe() == 0; }); } void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const @@ -110,7 +112,7 @@ namespace DetourNavigator { const std::lock_guard lock(mMutex); - jobs = mJobs.size(); + jobs = mJobs.size() + getTotalThreadJobsUnsafe(); } stats.setAttribute(frameNumber, "NavMesh UpdateJobs", jobs); @@ -188,12 +190,13 @@ namespace DetourNavigator while (true) { - const auto hasJob = [&] { return !mJobs.empty() || !threadQueue.mPushed.empty(); }; + const auto hasJob = [&] { return !mJobs.empty() || !threadQueue.mJobs.empty(); }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) { mFirstStart.lock()->reset(); - mDone.notify_all(); + if (getTotalThreadJobsUnsafe() == 0) + mDone.notify_all(); return boost::none; } @@ -327,4 +330,10 @@ namespace DetourNavigator if (agent->second.empty()) locked->erase(agent); } + + std::size_t AsyncNavMeshUpdater::getTotalThreadJobsUnsafe() const + { + return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0), + [] (auto r, const auto& v) { return r + v.second.mJobs.size(); }); + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 3f17d5ca0..c833d617c 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -114,6 +114,8 @@ namespace DetourNavigator std::thread::id lockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); + + inline std::size_t getTotalThreadJobsUnsafe() const; }; } From df6e85b619390eb1753766e808aa01942889739f Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 24 Feb 2020 18:38:39 -0800 Subject: [PATCH 13/58] Use callback to handle changed tiles Instead of collecting changed tiles into a temporary vector. --- .../tilecachedrecastmeshmanager.cpp | 27 ++++++---- components/detournavigator/navmeshmanager.cpp | 8 +-- .../tilecachedrecastmeshmanager.cpp | 37 -------------- .../tilecachedrecastmeshmanager.hpp | 50 ++++++++++++++++++- 4 files changed, 67 insertions(+), 55 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp index e44ae4878..eac3c024f 100644 --- a/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/apps/openmw_test_suite/detournavigator/tilecachedrecastmeshmanager.cpp @@ -20,6 +20,7 @@ namespace struct DetourNavigatorTileCachedRecastMeshManagerTest : Test { Settings mSettings; + std::vector mChangedTiles; DetourNavigatorTileCachedRecastMeshManagerTest() { @@ -29,6 +30,11 @@ namespace mSettings.mTileSize = 64; mSettings.mTrianglesPerChunk = 256; } + + void onChangedTile(const TilePosition& tilePosition) + { + mChangedTiles.push_back(tilePosition); + } }; TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_for_empty_should_return_nullptr) @@ -78,8 +84,10 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground); + EXPECT_TRUE(manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, + [&] (const auto& v) { onChangedTile(v); })); EXPECT_THAT( - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground), + mChangedTiles, ElementsAre(TilePosition(-1, -1), TilePosition(-1, 0), TilePosition(0, -1), TilePosition(0, 0), TilePosition(1, -1), TilePosition(1, 0)) ); @@ -90,10 +98,9 @@ namespace TileCachedRecastMeshManager manager(mSettings); const btBoxShape boxShape(btVector3(20, 20, 100)); manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); - EXPECT_EQ( - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground), - std::vector() - ); + EXPECT_FALSE(manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, + [&] (const auto& v) { onChangedTile(v); })); + EXPECT_EQ(mChangedTiles, std::vector()); } TEST_F(DetourNavigatorTileCachedRecastMeshManagerTest, get_mesh_after_add_object_should_return_recast_mesh_for_each_used_tile) @@ -127,7 +134,7 @@ namespace EXPECT_NE(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(1, -1)), nullptr); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); @@ -144,7 +151,7 @@ namespace EXPECT_EQ(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(-1, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getMesh(TilePosition(1, 0)), nullptr); EXPECT_EQ(manager.getMesh(TilePosition(1, -1)), nullptr); } @@ -172,7 +179,7 @@ namespace EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, 0)), nullptr); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_NE(manager.getMesh(TilePosition(-1, -1)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(-1, 0)), nullptr); EXPECT_NE(manager.getMesh(TilePosition(0, -1)), nullptr); @@ -205,7 +212,7 @@ namespace const btTransform transform(btMatrix3x3::getIdentity(), btVector3(getTileSize(mSettings) / mSettings.mRecastScaleFactor, 0, 0)); manager.addObject(ObjectId(&boxShape), boxShape, transform, AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision + 1); } @@ -215,7 +222,7 @@ namespace const btBoxShape boxShape(btVector3(20, 20, 100)); manager.addObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); const auto beforeUpdateRevision = manager.getRevision(); - manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground); + manager.updateObject(ObjectId(&boxShape), boxShape, btTransform::getIdentity(), AreaType::AreaType_ground, [] (auto) {}); EXPECT_EQ(manager.getRevision(), beforeUpdateRevision); } diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index a769981d3..b6c25bd93 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -56,12 +56,8 @@ namespace DetourNavigator bool NavMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType) { - const auto changedTiles = mRecastMeshManager.updateObject(id, shape, transform, areaType); - if (changedTiles.empty()) - return false; - for (const auto& tile : changedTiles) - addChangedTile(tile, ChangeType::update); - return true; + return mRecastMeshManager.updateObject(id, shape, transform, areaType, + [&] (const auto& tile) { addChangedTile(tile, ChangeType::update); }); } bool NavMeshManager::removeObject(const ObjectId id) diff --git a/components/detournavigator/tilecachedrecastmeshmanager.cpp b/components/detournavigator/tilecachedrecastmeshmanager.cpp index bbdbd410a..9debe5dea 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.cpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.cpp @@ -31,43 +31,6 @@ namespace DetourNavigator return result; } - std::vector TileCachedRecastMeshManager::updateObject(const ObjectId id, const btCollisionShape& shape, - const btTransform& transform, const AreaType areaType) - { - const auto object = mObjectsTilesPositions.find(id); - if (object == mObjectsTilesPositions.end()) - return std::vector(); - auto& currentTiles = object->second; - const auto border = getBorderSize(mSettings); - std::vector changedTiles; - std::set newTiles; - { - auto tiles = mTiles.lock(); - const auto onTilePosition = [&] (const TilePosition& tilePosition) - { - if (currentTiles.count(tilePosition)) - { - newTiles.insert(tilePosition); - if (updateTile(id, transform, areaType, tilePosition, tiles.get())) - changedTiles.push_back(tilePosition); - } - else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) - { - newTiles.insert(tilePosition); - changedTiles.push_back(tilePosition); - } - }; - getTilesPositions(shape, transform, mSettings, onTilePosition); - for (const auto& tile : currentTiles) - if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) - changedTiles.push_back(tile); - } - std::swap(currentTiles, newTiles); - if (!changedTiles.empty()) - ++mRevision; - return changedTiles; - } - boost::optional TileCachedRecastMeshManager::removeObject(const ObjectId id) { const auto object = mObjectsTilesPositions.find(id); diff --git a/components/detournavigator/tilecachedrecastmeshmanager.hpp b/components/detournavigator/tilecachedrecastmeshmanager.hpp index 4351c86bb..557cde1be 100644 --- a/components/detournavigator/tilecachedrecastmeshmanager.hpp +++ b/components/detournavigator/tilecachedrecastmeshmanager.hpp @@ -3,6 +3,8 @@ #include "cachedrecastmeshmanager.hpp" #include "tileposition.hpp" +#include "settingsutils.hpp" +#include "gettilespositions.hpp" #include @@ -20,8 +22,52 @@ namespace DetourNavigator bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, const AreaType areaType); - std::vector updateObject(const ObjectId id, const btCollisionShape& shape, - const btTransform& transform, const AreaType areaType); + template + bool updateObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform, + const AreaType areaType, OnChangedTile&& onChangedTile) + { + const auto object = mObjectsTilesPositions.find(id); + if (object == mObjectsTilesPositions.end()) + return false; + auto& currentTiles = object->second; + const auto border = getBorderSize(mSettings); + bool changed = false; + std::set newTiles; + { + auto tiles = mTiles.lock(); + const auto onTilePosition = [&] (const TilePosition& tilePosition) + { + if (currentTiles.count(tilePosition)) + { + newTiles.insert(tilePosition); + if (updateTile(id, transform, areaType, tilePosition, tiles.get())) + { + onChangedTile(tilePosition); + changed = true; + } + } + else if (addTile(id, shape, transform, areaType, tilePosition, border, tiles.get())) + { + newTiles.insert(tilePosition); + onChangedTile(tilePosition); + changed = true; + } + }; + getTilesPositions(shape, transform, mSettings, onTilePosition); + for (const auto& tile : currentTiles) + { + if (!newTiles.count(tile) && removeTile(id, tile, tiles.get())) + { + onChangedTile(tile); + changed = true; + } + } + } + std::swap(currentTiles, newTiles); + if (changed) + ++mRevision; + return changed; + } boost::optional removeObject(const ObjectId id); From 4838cf7362ddf01c3302c1a30291d48a86ef1014 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 25 Apr 2020 23:56:05 +0300 Subject: [PATCH 14/58] Clean up spellcasting --- apps/openmw/mwmechanics/spellcasting.cpp | 216 +++++++++-------------- apps/openmw/mwmechanics/spellcasting.hpp | 9 +- 2 files changed, 83 insertions(+), 142 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 3f32485de..d7645a698 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -32,41 +32,23 @@ namespace MWMechanics { ESM::Skill::SkillEnum spellSchoolToSkill(int school) { - static std::map schoolSkillMap; // maps spell school to skill id - if (schoolSkillMap.empty()) + static const std::map schoolSkillMap { - schoolSkillMap[0] = ESM::Skill::Alteration; - schoolSkillMap[1] = ESM::Skill::Conjuration; - schoolSkillMap[3] = ESM::Skill::Illusion; - schoolSkillMap[2] = ESM::Skill::Destruction; - schoolSkillMap[4] = ESM::Skill::Mysticism; - schoolSkillMap[5] = ESM::Skill::Restoration; - } - - assert(schoolSkillMap.find(school) != schoolSkillMap.end()); - return schoolSkillMap[school]; - } - - float calcEffectCost(const ESM::ENAMstruct& effect) - { - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); - return calcEffectCost(effect, magicEffect); + {0, ESM::Skill::Alteration}, {1, ESM::Skill::Conjuration}, {2, ESM::Skill::Destruction}, + {3, ESM::Skill::Illusion}, {4, ESM::Skill::Mysticism}, {5, ESM::Skill::Restoration} + }; + return schoolSkillMap.at(school); } float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect) { - int minMagn = 1; - int maxMagn = 1; - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) - { - minMagn = effect.mMagnMin; - maxMagn = effect.mMagnMax; - } - - int duration = 1; - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) - duration = effect.mDuration; - + if (!magicEffect) + magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude); + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + int minMagn = hasMagnitude ? effect.mMagnMin : 1; + int maxMagn = hasMagnitude ? effect.mMagnMax : 1; + int duration = hasDuration ? effect.mDuration : 1; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() .get().find("fEffectCostMult")->mValue.getFloat(); @@ -84,19 +66,18 @@ namespace MWMechanics float y = std::numeric_limits::max(); float lowestSkill = 0; - for (std::vector::const_iterator it = spell->mEffects.mList.begin(); it != spell->mEffects.mList.end(); ++it) + for (const ESM::ENAMstruct& effect : spell->mEffects.mList) { - float x = static_cast(it->mDuration); - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find( - it->mEffectID); + float x = static_cast(effect.mDuration); + const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) x = std::max(1.f, x); x *= 0.1f * magicEffect->mData.mBaseCost; - x *= 0.5f * (it->mMagnMin + it->mMagnMax); - x *= it->mArea * 0.05f * magicEffect->mData.mBaseCost; - if (it->mRange == ESM::RT_Target) + x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); + x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mRange == ESM::RT_Target) x *= 1.5f; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fEffectCostMult")->mValue.getFloat(); @@ -149,24 +130,18 @@ namespace MWMechanics return 100; if (godmode) - { return 100; - } - if (!cap) - return std::max(0.f, castChance); - else - return std::max(0.f, std::min(100.f, castChance)); + return std::max(0.f, cap ? std::min(100.f, castChance) : castChance); } float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); + if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId)) + return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); + return 0.f; } - int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) { int school = 0; @@ -183,16 +158,13 @@ namespace MWMechanics bool spellIncreasesSkill(const ESM::Spell *spell) { - if (spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always)) - return true; - return false; + return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always); } bool spellIncreasesSkill(const std::string &spellId) { - const ESM::Spell* spell = - MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - return spellIncreasesSkill(spell); + const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); + return spell && spellIncreasesSkill(spell); } float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects) @@ -314,10 +286,9 @@ namespace MWMechanics class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor { public: - float mProbability; + float mProbability{0.f}; - GetAbsorptionProbability(const MWWorld::Ptr& actor) - : mProbability(0.f){} + GetAbsorptionProbability() = default; virtual void visit (MWMechanics::EffectKey key, const std::string& sourceName, const std::string& sourceId, int casterActorId, @@ -342,9 +313,6 @@ namespace MWMechanics CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) : mCaster(caster) , mTarget(target) - , mStack(false) - , mHitPosition(0,0,0) - , mAlwaysSucceed(false) , mFromProjectile(fromProjectile) , mManualSpell(manualSpell) { @@ -375,10 +343,9 @@ namespace MWMechanics // If none of the effects need to apply, we can early-out bool found = false; - for (std::vector::const_iterator iter (effects.mList.begin()); - iter!=effects.mList.end(); ++iter) + for (const ESM::ENAMstruct& effect : effects.mList) { - if (iter->mRange == range) + if (effect.mRange == range) { found = true; break; @@ -441,8 +408,7 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}"); continue; } - else - canCastAnEffect = true; + canCastAnEffect = true; if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer)) continue; @@ -466,7 +432,7 @@ namespace MWMechanics CreatureStats& stats = target.getClass().getCreatureStats(target); if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f) { - GetAbsorptionProbability check(target); + GetAbsorptionProbability check; stats.getActiveSpells().visitEffectSources(check); stats.getSpells().visitEffectSources(check); if (target.getClass().hasInventoryStore(target)) @@ -578,11 +544,7 @@ namespace MWMechanics } else { - - if (!hasDuration) - effect.mDuration = 1.0f; - else - effect.mDuration = static_cast(effectIt->mDuration); + effect.mDuration = hasDuration ? static_cast(effectIt->mDuration) : 1.f; targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude)); @@ -658,13 +620,11 @@ namespace MWMechanics else castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_DefaultHit"); - std::string texture = magicEffect->mParticle; - 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) - anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture); + if (anim && !castStatic->mModel.empty()) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle); } } } @@ -985,7 +945,7 @@ namespace MWMechanics // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) - playSpellCastingEffects(mId, false); + playSpellCastingEffects(spell->mEffects.mList); inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); @@ -1065,14 +1025,13 @@ namespace MWMechanics const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); if (enchantment) { - const ESM::Enchantment *spell = store.get().find(spellid); - playSpellCastingEffects(spell->mEffects.mList); - + if (const auto spell = store.get().search(spellid)) + playSpellCastingEffects(spell->mEffects.mList); } else { - const ESM::Spell *spell = store.get().find(spellid); - playSpellCastingEffects(spell->mEffects.mList); + if (const auto spell = store.get().search(spellid)) + playSpellCastingEffects(spell->mEffects.mList); } } @@ -1080,12 +1039,9 @@ namespace MWMechanics { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); std::vector addedEffects; - for (std::vector::const_iterator iter = effects.begin(); iter != effects.end(); ++iter) + for (const ESM::ENAMstruct& effectData : effects) { - const ESM::MagicEffect *effect; - effect = store.get().find(iter->mEffectID); - - MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); + const auto effect = store.get().find(effectData.mEffectID); const ESM::Static* castStatic; @@ -1098,13 +1054,10 @@ namespace MWMechanics if (std::find(addedEffects.begin(), addedEffects.end(), "meshes\\" + castStatic->mModel) != addedEffects.end()) continue; - std::string texture = effect->mParticle; - - osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); - + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation) { - animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", texture); + animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", effect->mParticle); } else { @@ -1113,6 +1066,7 @@ namespace MWMechanics osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f / Constants::UnitsPerFoot); float scale = std::max({ bounds.x()/3.f, bounds.y()/3.f, bounds.z()/6.f }); float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f; + osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3()); MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale); } @@ -1135,10 +1089,7 @@ namespace MWMechanics bool CastSpell::spellIncreasesSkill() { - if (mManualSpell) - return false; - - return MWMechanics::spellIncreasesSkill(mId); + return !mManualSpell && MWMechanics::spellIncreasesSkill(mId); } int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) @@ -1167,8 +1118,7 @@ namespace MWMechanics if (ptr.getClass().hasInventoryStore(ptr)) { MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr); - MWWorld::ContainerStoreIterator item = - inv.getSlot(slot); + MWWorld::ContainerStoreIterator item = inv.getSlot(slot); if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon)) { @@ -1183,9 +1133,7 @@ namespace MWMechanics item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate)); charge = item->getClass().getItemHealth(*item); - charge -= - std::min(static_cast(disintegrate), - charge); + charge -= std::min(static_cast(disintegrate), charge); item->getCellRef().setCharge(charge); if (charge == 0) @@ -1366,58 +1314,52 @@ namespace MWMechanics std::string getSummonedCreature(int effectId) { - static std::map summonMap; - if (summonMap.empty()) + static const std::map summonMap { - summonMap[ESM::MagicEffect::SummonAncestralGhost] = "sMagicAncestralGhostID"; - summonMap[ESM::MagicEffect::SummonBonelord] = "sMagicBonelordID"; - summonMap[ESM::MagicEffect::SummonBonewalker] = "sMagicLeastBonewalkerID"; - summonMap[ESM::MagicEffect::SummonCenturionSphere] = "sMagicCenturionSphereID"; - summonMap[ESM::MagicEffect::SummonClannfear] = "sMagicClannfearID"; - summonMap[ESM::MagicEffect::SummonDaedroth] = "sMagicDaedrothID"; - summonMap[ESM::MagicEffect::SummonDremora] = "sMagicDremoraID"; - summonMap[ESM::MagicEffect::SummonFabricant] = "sMagicFabricantID"; - summonMap[ESM::MagicEffect::SummonFlameAtronach] = "sMagicFlameAtronachID"; - summonMap[ESM::MagicEffect::SummonFrostAtronach] = "sMagicFrostAtronachID"; - summonMap[ESM::MagicEffect::SummonGoldenSaint] = "sMagicGoldenSaintID"; - summonMap[ESM::MagicEffect::SummonGreaterBonewalker] = "sMagicGreaterBonewalkerID"; - summonMap[ESM::MagicEffect::SummonHunger] = "sMagicHungerID"; - summonMap[ESM::MagicEffect::SummonScamp] = "sMagicScampID"; - summonMap[ESM::MagicEffect::SummonSkeletalMinion] = "sMagicSkeletalMinionID"; - summonMap[ESM::MagicEffect::SummonStormAtronach] = "sMagicStormAtronachID"; - summonMap[ESM::MagicEffect::SummonWingedTwilight] = "sMagicWingedTwilightID"; - summonMap[ESM::MagicEffect::SummonWolf] = "sMagicCreature01ID"; - summonMap[ESM::MagicEffect::SummonBear] = "sMagicCreature02ID"; - summonMap[ESM::MagicEffect::SummonBonewolf] = "sMagicCreature03ID"; - summonMap[ESM::MagicEffect::SummonCreature04] = "sMagicCreature04ID"; - summonMap[ESM::MagicEffect::SummonCreature05] = "sMagicCreature05ID"; - } + {ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"}, + {ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"}, + {ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"}, + {ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"}, + {ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"}, + {ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"}, + {ESM::MagicEffect::SummonDremora, "sMagicDremoraID"}, + {ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"}, + {ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"}, + {ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"}, + {ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"}, + {ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"}, + {ESM::MagicEffect::SummonHunger, "sMagicHungerID"}, + {ESM::MagicEffect::SummonScamp, "sMagicScampID"}, + {ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"}, + {ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"}, + {ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"}, + {ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"}, + {ESM::MagicEffect::SummonBear, "sMagicCreature02ID"}, + {ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"}, + {ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"}, + {ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"} + }; - std::map::const_iterator it = summonMap.find(effectId); - if (it == summonMap.end()) - return std::string(); - else + auto it = summonMap.find(effectId); + if (it != summonMap.end()) return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->mValue.getString(); + return std::string(); } void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key, const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/) { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find(key.mId); - + const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(key.mId); + if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0) + return; 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"); - - std::string texture = magicEffect->mParticle; - - bool loop = (magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0; MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor); - if (anim && loop) - anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", texture); + if (anim && !castStatic->mModel.empty()) + anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle); } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 5f0e4a1cf..85e732e03 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -25,8 +25,7 @@ namespace MWMechanics ESM::Skill::SkillEnum spellSchoolToSkill(int school); - float calcEffectCost(const ESM::ENAMstruct& effect); - float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect); + float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr); bool isSummoningEffect(int effectId); @@ -87,11 +86,11 @@ namespace MWMechanics void playSpellCastingEffects(const std::vector& effects); public: - bool mStack; + bool mStack{false}; std::string mId; // ID of spell, potion, item etc std::string mSourceName; // Display name for spell, potion, etc - osg::Vec3f mHitPosition; // Used for spawning area orb - bool mAlwaysSucceed; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false) + 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.) From 916a9641fc16504189f82fde0fac7abfcf935403 Mon Sep 17 00:00:00 2001 From: fredzio Date: Sun, 29 Sep 2019 19:10:54 +0200 Subject: [PATCH 15/58] Delete support for 2-arguments opcodes - it was never used --- components/interpreter/interpreter.cpp | 52 -------------------------- components/interpreter/interpreter.hpp | 9 ----- components/interpreter/opcodes.hpp | 10 ----- 3 files changed, 71 deletions(-) diff --git a/components/interpreter/interpreter.cpp b/components/interpreter/interpreter.cpp index b64369e70..0b636092c 100644 --- a/components/interpreter/interpreter.cpp +++ b/components/interpreter/interpreter.cpp @@ -28,22 +28,6 @@ namespace Interpreter return; } - case 1: - { - int opcode = (code>>24) & 0x3f; - unsigned int arg0 = (code>>16) & 0xfff; - unsigned int arg1 = code & 0xfff; - - std::map::iterator iter = mSegment1.find (opcode); - - if (iter==mSegment1.end()) - abortUnknownCode (1, opcode); - - iter->second->execute (mRuntime, arg0, arg1); - - return; - } - case 2: { int opcode = (code>>20) & 0x3ff; @@ -79,22 +63,6 @@ namespace Interpreter return; } - case 0x31: - { - int opcode = (code>>16) & 0x3ff; - unsigned int arg0 = (code>>8) & 0xff; - unsigned int arg1 = code & 0xff; - - std::map::iterator iter = mSegment4.find (opcode); - - if (iter==mSegment4.end()) - abortUnknownCode (4, opcode); - - iter->second->execute (mRuntime, arg0, arg1); - - return; - } - case 0x32: { int opcode = code & 0x3ffffff; @@ -161,10 +129,6 @@ namespace Interpreter iter!=mSegment0.end(); ++iter) delete iter->second; - for (std::map::iterator iter (mSegment1.begin()); - iter!=mSegment1.end(); ++iter) - delete iter->second; - for (std::map::iterator iter (mSegment2.begin()); iter!=mSegment2.end(); ++iter) delete iter->second; @@ -173,10 +137,6 @@ namespace Interpreter iter!=mSegment3.end(); ++iter) delete iter->second; - for (std::map::iterator iter (mSegment4.begin()); - iter!=mSegment4.end(); ++iter) - delete iter->second; - for (std::map::iterator iter (mSegment5.begin()); iter!=mSegment5.end(); ++iter) delete iter->second; @@ -188,12 +148,6 @@ namespace Interpreter mSegment0.insert (std::make_pair (code, opcode)); } - void Interpreter::installSegment1 (int code, Opcode2 *opcode) - { - assert(mSegment1.find(code) == mSegment1.end()); - mSegment1.insert (std::make_pair (code, opcode)); - } - void Interpreter::installSegment2 (int code, Opcode1 *opcode) { assert(mSegment2.find(code) == mSegment2.end()); @@ -206,12 +160,6 @@ namespace Interpreter mSegment3.insert (std::make_pair (code, opcode)); } - void Interpreter::installSegment4 (int code, Opcode2 *opcode) - { - assert(mSegment4.find(code) == mSegment4.end()); - mSegment4.insert (std::make_pair (code, opcode)); - } - void Interpreter::installSegment5 (int code, Opcode0 *opcode) { assert(mSegment5.find(code) == mSegment5.end()); diff --git a/components/interpreter/interpreter.hpp b/components/interpreter/interpreter.hpp index dd341e709..ff3bcf7b7 100644 --- a/components/interpreter/interpreter.hpp +++ b/components/interpreter/interpreter.hpp @@ -11,7 +11,6 @@ namespace Interpreter { class Opcode0; class Opcode1; - class Opcode2; class Interpreter { @@ -19,10 +18,8 @@ namespace Interpreter bool mRunning; Runtime mRuntime; std::map mSegment0; - std::map mSegment1; std::map mSegment2; std::map mSegment3; - std::map mSegment4; std::map mSegment5; // not implemented @@ -48,18 +45,12 @@ namespace Interpreter void installSegment0 (int code, Opcode1 *opcode); ///< ownership of \a opcode is transferred to *this. - void installSegment1 (int code, Opcode2 *opcode); - ///< ownership of \a opcode is transferred to *this. - void installSegment2 (int code, Opcode1 *opcode); ///< ownership of \a opcode is transferred to *this. void installSegment3 (int code, Opcode1 *opcode); ///< ownership of \a opcode is transferred to *this. - void installSegment4 (int code, Opcode2 *opcode); - ///< ownership of \a opcode is transferred to *this. - void installSegment5 (int code, Opcode0 *opcode); ///< ownership of \a opcode is transferred to *this. diff --git a/components/interpreter/opcodes.hpp b/components/interpreter/opcodes.hpp index c447e1f10..da266e8ff 100644 --- a/components/interpreter/opcodes.hpp +++ b/components/interpreter/opcodes.hpp @@ -25,16 +25,6 @@ namespace Interpreter virtual ~Opcode1() {} }; - /// opcode for 2 arguments - class Opcode2 - { - public: - - virtual void execute (Runtime& runtime, unsigned int arg1, unsigned int arg2) = 0; - - virtual ~Opcode2() {} - }; - } #endif From a3cd3281fb76214b629f95fc4eb75c3a21329042 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 26 Apr 2020 16:17:06 +0300 Subject: [PATCH 16/58] Use an array instead of a map in spellSchoolToSkill() --- apps/openmw/mwmechanics/spellcasting.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d7645a698..ed8972f05 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -32,12 +32,12 @@ namespace MWMechanics { ESM::Skill::SkillEnum spellSchoolToSkill(int school) { - static const std::map schoolSkillMap + static const std::array schoolSkillArray { - {0, ESM::Skill::Alteration}, {1, ESM::Skill::Conjuration}, {2, ESM::Skill::Destruction}, - {3, ESM::Skill::Illusion}, {4, ESM::Skill::Mysticism}, {5, ESM::Skill::Restoration} + ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction, + ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration }; - return schoolSkillMap.at(school); + return schoolSkillArray.at(school); } float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect) From 5535a7fb44956a7644d45b3ced4bf4ac186b04a5 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 4 Apr 2020 16:27:00 +0300 Subject: [PATCH 17/58] Move getSummonedCreature() to summoning --- apps/openmw/mwmechanics/spellcasting.cpp | 34 ----------------------- apps/openmw/mwmechanics/spellcasting.hpp | 2 -- apps/openmw/mwmechanics/summoning.cpp | 35 ++++++++++++++++++++++-- apps/openmw/mwmechanics/summoning.hpp | 5 ++-- apps/openmw/mwworld/worldimp.cpp | 1 + 5 files changed, 36 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index ed8972f05..3ded41e91 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1312,40 +1312,6 @@ namespace MWMechanics return true; } - std::string getSummonedCreature(int effectId) - { - static const std::map summonMap - { - {ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"}, - {ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"}, - {ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"}, - {ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"}, - {ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"}, - {ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"}, - {ESM::MagicEffect::SummonDremora, "sMagicDremoraID"}, - {ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"}, - {ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"}, - {ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"}, - {ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"}, - {ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"}, - {ESM::MagicEffect::SummonHunger, "sMagicHungerID"}, - {ESM::MagicEffect::SummonScamp, "sMagicScampID"}, - {ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"}, - {ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"}, - {ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"}, - {ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"}, - {ESM::MagicEffect::SummonBear, "sMagicCreature02ID"}, - {ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"}, - {ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"}, - {ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"} - }; - - auto it = summonMap.find(effectId); - if (it != summonMap.end()) - return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->mValue.getString(); - return std::string(); - } - void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key, const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/) diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 85e732e03..bca93c734 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -75,8 +75,6 @@ namespace MWMechanics /// @return Was the effect a tickable effect with a magnitude? bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude); - std::string getSummonedCreature(int effectId); - class CastSpell { private: diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 86d0faa9d..d4973925b 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -20,13 +20,42 @@ namespace MWMechanics { - UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) - : mActor(actor) + std::string getSummonedCreature(int effectId) { + static const std::map summonMap + { + {ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"}, + {ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"}, + {ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"}, + {ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"}, + {ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"}, + {ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"}, + {ESM::MagicEffect::SummonDremora, "sMagicDremoraID"}, + {ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"}, + {ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"}, + {ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"}, + {ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"}, + {ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"}, + {ESM::MagicEffect::SummonHunger, "sMagicHungerID"}, + {ESM::MagicEffect::SummonScamp, "sMagicScampID"}, + {ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"}, + {ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"}, + {ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"}, + {ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"}, + {ESM::MagicEffect::SummonBear, "sMagicCreature02ID"}, + {ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"}, + {ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"}, + {ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"} + }; + auto it = summonMap.find(effectId); + if (it != summonMap.end()) + return MWBase::Environment::get().getWorld()->getStore().get().find(it->second)->mValue.getString(); + return std::string(); } - UpdateSummonedCreatures::~UpdateSummonedCreatures() + UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) + : mActor(actor) { } diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index 9329dcb83..e638155a9 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -9,13 +9,14 @@ namespace MWMechanics { - class CreatureStats; + std::string getSummonedCreature(int effectId); + struct UpdateSummonedCreatures : public EffectSourceVisitor { UpdateSummonedCreatures(const MWWorld::Ptr& actor); - virtual ~UpdateSummonedCreatures(); + virtual ~UpdateSummonedCreatures() = default; virtual void visit (MWMechanics::EffectKey key, const std::string& sourceName, const std::string& sourceId, int casterActorId, diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ce06d887c..37123099a 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -41,6 +41,7 @@ #include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors +#include "../mwmechanics/summoning.hpp" #include "../mwrender/animation.hpp" #include "../mwrender/npcanimation.hpp" From 5973285446017312e331b943ca5ddcc61469780e Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 4 Apr 2020 17:45:03 +0300 Subject: [PATCH 18/58] Move isSummoningEffect to summoning --- apps/openmw/mwmechanics/spellcasting.cpp | 10 +--------- apps/openmw/mwmechanics/spellcasting.hpp | 2 -- apps/openmw/mwmechanics/spellpriority.cpp | 1 + apps/openmw/mwmechanics/summoning.cpp | 8 +++++++- apps/openmw/mwmechanics/summoning.hpp | 2 ++ 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 3ded41e91..d7c3bcdb1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -27,6 +27,7 @@ #include "actorutil.hpp" #include "aifollow.hpp" #include "weapontype.hpp" +#include "summoning.hpp" namespace MWMechanics { @@ -1104,15 +1105,6 @@ namespace MWMechanics return static_cast((result < 1) ? 1 : result); } - bool isSummoningEffect(int effectId) - { - return ((effectId >= ESM::MagicEffect::SummonScamp - && effectId <= ESM::MagicEffect::SummonStormAtronach) - || effectId == ESM::MagicEffect::SummonCenturionSphere - || (effectId >= ESM::MagicEffect::SummonFabricant - && effectId <= ESM::MagicEffect::SummonCreature05)); - } - bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) { if (ptr.getClass().hasInventoryStore(ptr)) diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index bca93c734..804b4bca9 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -27,8 +27,6 @@ namespace MWMechanics float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr); - bool isSummoningEffect(int effectId); - /** * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 7b5c38592..f90e59971 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -18,6 +18,7 @@ #include "spellcasting.hpp" #include "weapontype.hpp" #include "combat.hpp" +#include "summoning.hpp" namespace { diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index d4973925b..03fd0d681 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -13,13 +13,19 @@ #include "../mwrender/animation.hpp" -#include "spellcasting.hpp" #include "creaturestats.hpp" #include "aifollow.hpp" namespace MWMechanics { + bool isSummoningEffect(int effectId) + { + return ((effectId >= ESM::MagicEffect::SummonScamp && effectId <= ESM::MagicEffect::SummonStormAtronach) + || (effectId == ESM::MagicEffect::SummonCenturionSphere) + || (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05)); + } + std::string getSummonedCreature(int effectId) { static const std::map summonMap diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index e638155a9..f24413120 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -11,6 +11,8 @@ namespace MWMechanics { class CreatureStats; + bool isSummoningEffect(int effectId); + std::string getSummonedCreature(int effectId); struct UpdateSummonedCreatures : public EffectSourceVisitor From db13984db00e9096fdc48817757856ee2100c3a4 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 4 Apr 2020 18:28:53 +0300 Subject: [PATCH 19/58] Separate spell resistance --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwmechanics/combat.cpp | 1 + apps/openmw/mwmechanics/spellcasting.cpp | 78 +----------------- apps/openmw/mwmechanics/spellcasting.hpp | 22 +---- apps/openmw/mwmechanics/spellpriority.cpp | 1 + apps/openmw/mwmechanics/spellresistance.cpp | 90 +++++++++++++++++++++ apps/openmw/mwmechanics/spellresistance.hpp | 37 +++++++++ apps/openmw/mwworld/inventorystore.cpp | 1 + 8 files changed, 134 insertions(+), 98 deletions(-) create mode 100644 apps/openmw/mwmechanics/spellresistance.cpp create mode 100644 apps/openmw/mwmechanics/spellresistance.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 57262f964..3395b5fc8 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -83,7 +83,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat creaturestats magiceffects movement actorutil drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe - aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellsuccess spellcasting + 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 coordinateconverter trading weaponpriority spellpriority weapontype ) diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 9f5446c11..9698892e4 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -18,6 +18,7 @@ #include "npcstats.hpp" #include "movement.hpp" #include "spellcasting.hpp" +#include "spellresistance.hpp" #include "difficultyscaling.hpp" #include "actorutil.hpp" #include "pathfinding.hpp" diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d7c3bcdb1..d5ddd9a55 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -28,6 +28,7 @@ #include "aifollow.hpp" #include "weapontype.hpp" #include "summoning.hpp" +#include "spellresistance.hpp" namespace MWMechanics { @@ -168,83 +169,6 @@ namespace MWMechanics return spell && spellIncreasesSkill(spell); } - float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects) - { - short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); - short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); - - float resistance = 0; - if (resistanceEffect != -1) - resistance += actorEffects->get(resistanceEffect).getMagnitude(); - if (weaknessEffect != -1) - resistance -= actorEffects->get(weaknessEffect).getMagnitude(); - - if (effectId == ESM::MagicEffect::FireDamage) - resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude(); - if (effectId == ESM::MagicEffect::ShockDamage) - resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude(); - if (effectId == ESM::MagicEffect::FrostDamage) - resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude(); - - return resistance; - } - - float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell, const MagicEffects* effects) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectId); - - const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects(); - if (effects) - magicEffects = effects; - - // Effects with no resistance attribute belonging to them can not be resisted - if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) - return 0.f; - - float resistance = getEffectResistanceAttribute(effectId, magicEffects); - - int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); - float luck = static_cast(stats.getAttribute(ESM::Attribute::Luck).getModified()); - float x = (willpower + 0.1f * luck) * stats.getFatigueTerm(); - - // This makes spells that are easy to cast harder to resist and vice versa - float castChance = 100.f; - if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor()) - { - castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance - } - if (castChance > 0) - x *= 50 / castChance; - - float roll = Misc::Rng::rollClosedProbability() * 100; - if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) - roll -= resistance; - - if (x <= roll) - x = 0; - else - { - if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) - x = 100; - else - x = roll / std::min(x, 100.f); - } - - x = std::min(x + resistance, 100.f); - return x; - } - - float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell, const MagicEffects* effects) - { - float resistance = getEffectResistance(effectId, actor, caster, spell, effects); - return 1 - resistance / 100.f; - } - /// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure. bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer) { diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 804b4bca9..91cf37272 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -1,5 +1,5 @@ -#ifndef MWMECHANICS_SPELLSUCCESS_H -#define MWMECHANICS_SPELLSUCCESS_H +#ifndef MWMECHANICS_SPELLCASTING_H +#define MWMECHANICS_SPELLCASTING_H #include #include @@ -46,24 +46,6 @@ namespace MWMechanics bool spellIncreasesSkill(const ESM::Spell* spell); bool spellIncreasesSkill(const std::string& spellId); - /// Get the resistance attribute against an effect for a given actor. This will add together - /// ResistX and Weakness to X effects relevant against the given effect. - float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects); - - /// Get the effective resistance against an effect casted by the given actor in the given spell (optional). - /// @return >=100 for fully resisted. can also return negative value for damage amplification. - /// @param effects Override the actor's current magicEffects. Useful if there are effects currently - /// being applied (but not applied yet) that should also be considered. - float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); - - /// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional). - /// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness) - /// @param effects Override the actor's current magicEffects. Useful if there are effects currently - /// being applied (but not applied yet) that should also be considered. - float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, - const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); - bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index f90e59971..a529781cb 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -16,6 +16,7 @@ #include "creaturestats.hpp" #include "spellcasting.hpp" +#include "spellresistance.hpp" #include "weapontype.hpp" #include "combat.hpp" #include "summoning.hpp" diff --git a/apps/openmw/mwmechanics/spellresistance.cpp b/apps/openmw/mwmechanics/spellresistance.cpp new file mode 100644 index 000000000..bbb4b56a5 --- /dev/null +++ b/apps/openmw/mwmechanics/spellresistance.cpp @@ -0,0 +1,90 @@ +#include "spellresistance.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "creaturestats.hpp" +#include "spellcasting.hpp" + +namespace MWMechanics +{ + + float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell, const MagicEffects* effects) + { + float resistance = getEffectResistance(effectId, actor, caster, spell, effects); + return 1 - resistance / 100.f; + } + + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell, const MagicEffects* effects) + { + // Effects with no resistance attribute belonging to them can not be resisted + if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) + return 0.f; + + const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); + + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects(); + if (effects) + magicEffects = effects; + + float resistance = getEffectResistanceAttribute(effectId, magicEffects); + + int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + float luck = static_cast(stats.getAttribute(ESM::Attribute::Luck).getModified()); + float x = (willpower + 0.1f * luck) * stats.getFatigueTerm(); + + // This makes spells that are easy to cast harder to resist and vice versa + float castChance = 100.f; + if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor()) + castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance + if (castChance > 0) + x *= 50 / castChance; + + float roll = Misc::Rng::rollClosedProbability() * 100; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + roll -= resistance; + + if (x <= roll) + x = 0; + else + { + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + x = 100; + else + x = roll / std::min(x, 100.f); + } + + x = std::min(x + resistance, 100.f); + return x; + } + + float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects) + { + short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); + short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); + + float resistance = 0; + if (resistanceEffect != -1) + resistance += actorEffects->get(resistanceEffect).getMagnitude(); + if (weaknessEffect != -1) + resistance -= actorEffects->get(weaknessEffect).getMagnitude(); + + if (effectId == ESM::MagicEffect::FireDamage) + resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude(); + if (effectId == ESM::MagicEffect::ShockDamage) + resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude(); + if (effectId == ESM::MagicEffect::FrostDamage) + resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude(); + + return resistance; + } + +} diff --git a/apps/openmw/mwmechanics/spellresistance.hpp b/apps/openmw/mwmechanics/spellresistance.hpp new file mode 100644 index 000000000..8e74c2260 --- /dev/null +++ b/apps/openmw/mwmechanics/spellresistance.hpp @@ -0,0 +1,37 @@ +#ifndef MWMECHANICS_SPELLRESISTANCE_H +#define MWMECHANICS_SPELLRESISTANCE_H + +namespace ESM +{ + struct Spell; +} + +namespace MWWorld +{ + class Ptr; +} + +namespace MWMechanics +{ + class MagicEffects; + + /// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional). + /// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness) + /// @param effects Override the actor's current magicEffects. Useful if there are effects currently + /// being applied (but not applied yet) that should also be considered. + float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); + + /// Get the effective resistance against an effect casted by the given actor in the given spell (optional). + /// @return >=100 for fully resisted. can also return negative value for damage amplification. + /// @param effects Override the actor's current magicEffects. Useful if there are effects currently + /// being applied (but not applied yet) that should also be considered. + float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); + + /// Get the resistance attribute against an effect for a given actor. This will add together + /// ResistX and Weakness to X effects relevant against the given effect. + float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects); +} + +#endif diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 1c8062011..1de97d068 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -14,6 +14,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spellresistance.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" From 8d22e075e67cc5304260f19e7072997e48b4a05c Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 26 Apr 2020 20:46:51 +0300 Subject: [PATCH 20/58] Separate functions that don't belong to CastSpell class --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 2 +- apps/openmw/mwgui/spellcreationdialog.cpp | 2 +- apps/openmw/mwgui/spellmodel.cpp | 2 +- apps/openmw/mwgui/spellwindow.cpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 1 + apps/openmw/mwmechanics/aicombataction.cpp | 1 - apps/openmw/mwmechanics/autocalcspell.cpp | 2 +- apps/openmw/mwmechanics/enchanting.cpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 4 +- apps/openmw/mwmechanics/spellcasting.cpp | 409 +----------------- apps/openmw/mwmechanics/spellcasting.hpp | 51 --- apps/openmw/mwmechanics/spellpriority.cpp | 2 +- apps/openmw/mwmechanics/spellresistance.cpp | 2 +- apps/openmw/mwmechanics/spellutil.cpp | 208 +++++++++ apps/openmw/mwmechanics/spellutil.hpp | 50 +++ apps/openmw/mwmechanics/tickableeffects.cpp | 218 ++++++++++ apps/openmw/mwmechanics/tickableeffects.hpp | 19 + apps/openmw/mwmechanics/weaponpriority.cpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 2 +- apps/openmw/mwworld/player.cpp | 2 +- apps/openmw/mwworld/worldimp.cpp | 32 +- 23 files changed, 544 insertions(+), 475 deletions(-) create mode 100644 apps/openmw/mwmechanics/spellutil.cpp create mode 100644 apps/openmw/mwmechanics/spellutil.hpp create mode 100644 apps/openmw/mwmechanics/tickableeffects.cpp create mode 100644 apps/openmw/mwmechanics/tickableeffects.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3395b5fc8..bee3641f8 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -85,7 +85,7 @@ 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 coordinateconverter trading weaponpriority spellpriority weapontype + character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype spellutil tickableeffects ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 39278f0fa..8449e6a5b 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -19,7 +19,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 23f24e321..a567d114b 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -15,10 +15,10 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/spellutil.hpp" #include "tooltips.hpp" #include "class.hpp" diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index cbe664ab1..1dedfa10b 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -7,7 +7,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/creaturestats.hpp" -#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spellutil.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 41be4f3a8..7776b376a 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -18,7 +18,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/player.hpp" -#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/spells.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/actorutil.hpp" diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index e3250e5fe..c0db57b1b 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -17,7 +17,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" -#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/actorutil.hpp" #include "mapwindow.hpp" diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 726b2a31f..ba3dc1725 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -40,6 +40,7 @@ #include "summoning.hpp" #include "combat.hpp" #include "actorutil.hpp" +#include "tickableeffects.hpp" namespace { diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 9f698b630..c26454aab 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -14,7 +14,6 @@ #include "../mwworld/cellstore.hpp" #include "npcstats.hpp" -#include "spellcasting.hpp" #include "combat.hpp" #include "weaponpriority.hpp" #include "spellpriority.hpp" diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index f55bebfc9..6d3090918 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -1,5 +1,4 @@ #include "autocalcspell.hpp" -#include "spellcasting.hpp" #include @@ -8,6 +7,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "spellutil.hpp" namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index fdf25b7c6..c71516090 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -13,7 +13,7 @@ #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" -#include "spellcasting.hpp" +#include "spellutil.hpp" #include "actorutil.hpp" #include "weapontype.hpp" diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 695abe105..25b33c486 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -21,7 +21,7 @@ #include "aicombat.hpp" #include "aipursue.hpp" -#include "spellcasting.hpp" +#include "spellutil.hpp" #include "autocalcspell.hpp" #include "npcstats.hpp" #include "actorutil.hpp" @@ -376,7 +376,7 @@ namespace MWMechanics { const std::string& spell = winMgr->getSelectedSpell(); if (!spell.empty()) - winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, mWatched))); + winMgr->setSelectedSpell(spell, int(getSpellSuccessChance(spell, mWatched))); else winMgr->unsetSelectedSpell(); } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d5ddd9a55..bd61e7798 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -1,8 +1,5 @@ #include "spellcasting.hpp" -#include -#include - #include #include #include @@ -29,185 +26,11 @@ #include "weapontype.hpp" #include "summoning.hpp" #include "spellresistance.hpp" +#include "spellutil.hpp" +#include "tickableeffects.hpp" namespace MWMechanics { - ESM::Skill::SkillEnum spellSchoolToSkill(int school) - { - static const std::array schoolSkillArray - { - ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction, - ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration - }; - return schoolSkillArray.at(school); - } - - float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect) - { - if (!magicEffect) - magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); - bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude); - bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); - int minMagn = hasMagnitude ? effect.mMagnMin : 1; - int maxMagn = hasMagnitude ? effect.mMagnMax : 1; - int duration = hasDuration ? effect.mDuration : 1; - static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore() - .get().find("fEffectCostMult")->mValue.getFloat(); - - float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); - x *= 0.1 * magicEffect->mData.mBaseCost; - x *= 1 + duration; - x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; - - return x * fEffectCostMult; - } - - float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) - { - // Morrowind for some reason uses a formula slightly different from magicka cost calculation - float y = std::numeric_limits::max(); - float lowestSkill = 0; - - for (const ESM::ENAMstruct& effect : spell->mEffects.mList) - { - float x = static_cast(effect.mDuration); - const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); - - if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) - x = std::max(1.f, x); - - x *= 0.1f * magicEffect->mData.mBaseCost; - x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); - x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; - if (effect.mRange == ESM::RT_Target) - x *= 1.5f; - static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fEffectCostMult")->mValue.getFloat(); - x *= fEffectCostMult; - - float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool)); - if (s - x < y) - { - y = s - x; - if (effectiveSchool) - *effectiveSchool = magicEffect->mData.mSchool; - lowestSkill = s; - } - } - - CreatureStats& stats = actor.getClass().getCreatureStats(actor); - - int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); - int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); - - float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck); - - return castChance; - } - - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) - { - bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); - - CreatureStats& stats = actor.getClass().getCreatureStats(actor); - - float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); - - float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus; - castChance *= stats.getFatigueTerm(); - - if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude()&& !godmode) - return 0; - - if (spell->mData.mType == ESM::Spell::ST_Power) - return stats.getSpells().canUsePower(spell) ? 100 : 0; - - if (spell->mData.mType != ESM::Spell::ST_Spell) - return 100; - - if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost && !godmode) - return 0; - - if (spell->mData.mFlags & ESM::Spell::F_Always) - return 100; - - if (godmode) - return 100; - - return std::max(0.f, cap ? std::min(100.f, castChance) : castChance); - } - - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) - { - if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId)) - return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); - return 0.f; - } - - int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) - { - int school = 0; - getSpellSuccessChance(spellId, actor, &school); - return school; - } - - int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) - { - int school = 0; - getSpellSuccessChance(spell, actor, &school); - return school; - } - - bool spellIncreasesSkill(const ESM::Spell *spell) - { - return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always); - } - - bool spellIncreasesSkill(const std::string &spellId) - { - const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); - return spell && spellIncreasesSkill(spell); - } - - /// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure. - bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer) - { - switch (effectId) - { - case ESM::MagicEffect::Levitate: - if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); - return false; - } - break; - case ESM::MagicEffect::Soultrap: - if (!target.getClass().isNpc() // no messagebox for NPCs - && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) - { - if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); - return true; // must still apply to get visual effect and have target regard it as attack - } - break; - case ESM::MagicEffect::WaterWalking: - if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target)) - return false; - - MWBase::World *world = MWBase::Environment::get().getWorld(); - - if (!world->isWaterWalkingCastableOnTarget(target)) - { - if (castByPlayer && caster == target) - MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}"); - return false; - } - break; - } - return true; - } - class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor { public: @@ -1016,232 +839,4 @@ namespace MWMechanics { return !mManualSpell && MWMechanics::spellIncreasesSkill(mId); } - - int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) - { - /* - * Each point of enchant skill above/under 10 subtracts/adds - * one percent of enchantment cost while minimum is 1. - */ - int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant); - const float result = castCost - (castCost / 100) * (eSkill - 10); - - return static_cast((result < 1) ? 1 : result); - } - - bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) - { - if (ptr.getClass().hasInventoryStore(ptr)) - { - 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; - } - - 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 effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude) - { - if (magnitude == 0.f) - return false; - - bool receivedMagicDamage = false; - - switch (effectKey.mId) - { - case ESM::MagicEffect::DamageAttribute: - { - 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: - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); - break; - - case ESM::MagicEffect::DamageMagicka: - case ESM::MagicEffect::DamageFatigue: - { - 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 (magnitude > 0.f) - receivedMagicDamage = true; - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - - break; - - case ESM::MagicEffect::AbsorbMagicka: - case ESM::MagicEffect::AbsorbFatigue: - adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); - break; - - case ESM::MagicEffect::DisintegrateArmor: - { - // According to UESP - int 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 (unsigned int i=0; iisExterior()) - 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: - { - adjustDynamicStat(creatureStats, 0, -magnitude); - receivedMagicDamage = true; - break; - } - - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::RestoreSkill: - { - if (!actor.getClass().isNpc()) - 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; - } - - case ESM::MagicEffect::CurePoison: - actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); - break; - case ESM::MagicEffect::CureParalyzation: - actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); - break; - case ESM::MagicEffect::CureCommonDisease: - actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease(); - break; - case ESM::MagicEffect::CureBlightDisease: - actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease(); - break; - case ESM::MagicEffect::CureCorprusDisease: - actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease(); - break; - case ESM::MagicEffect::RemoveCurse: - actor.getClass().getCreatureStats(actor).getSpells().purgeCurses(); - break; - default: - return false; - } - - if (receivedMagicDamage && actor == getPlayer()) - MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); - return true; - } - - void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/) - { - const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(key.mId); - if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0) - return; - 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"); - 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); - } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 91cf37272..3fcec8f4a 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -2,13 +2,10 @@ #define MWMECHANICS_SPELLCASTING_H #include -#include #include #include "../mwworld/ptr.hpp" -#include "magiceffects.hpp" - namespace ESM { struct Spell; @@ -23,38 +20,6 @@ namespace MWMechanics class MagicEffects; class CreatureStats; - ESM::Skill::SkillEnum spellSchoolToSkill(int school); - - float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr); - - /** - * @param spell spell to cast - * @param actor calculate spell success chance for this actor (depends on actor's skills) - * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here - * @param cap cap the result to 100%? - * @param checkMagicka check magicka? - * @note actor can be an NPC or a creature - * @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned. - */ - float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); - float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); - - int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); - int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); - - /// Get whether or not the given spell contributes to skill progress. - bool spellIncreasesSkill(const ESM::Spell* spell); - bool spellIncreasesSkill(const std::string& spellId); - - bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); - - int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); - float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool); - - /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed - /// @return Was the effect a tickable effect with a magnitude? - bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude); - class CastSpell { private: @@ -105,22 +70,6 @@ namespace MWMechanics /// @return was the target suitable for the effect? bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; - - class ApplyLoopingParticlesVisitor : public EffectSourceVisitor - { - private: - MWWorld::Ptr mActor; - - public: - ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor) - : mActor(actor) - { - } - - virtual void visit (MWMechanics::EffectKey key, - const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, - float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1); - }; } #endif diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index a529781cb..9428beafc 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -15,11 +15,11 @@ #include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" -#include "spellcasting.hpp" #include "spellresistance.hpp" #include "weapontype.hpp" #include "combat.hpp" #include "summoning.hpp" +#include "spellutil.hpp" namespace { diff --git a/apps/openmw/mwmechanics/spellresistance.cpp b/apps/openmw/mwmechanics/spellresistance.cpp index bbb4b56a5..4868a7a25 100644 --- a/apps/openmw/mwmechanics/spellresistance.cpp +++ b/apps/openmw/mwmechanics/spellresistance.cpp @@ -9,7 +9,7 @@ #include "../mwworld/esmstore.hpp" #include "creaturestats.hpp" -#include "spellcasting.hpp" +#include "spellutil.hpp" namespace MWMechanics { diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp new file mode 100644 index 000000000..cce07f9e3 --- /dev/null +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -0,0 +1,208 @@ +#include "spellutil.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include "actorutil.hpp" +#include "creaturestats.hpp" + +namespace MWMechanics +{ + ESM::Skill::SkillEnum spellSchoolToSkill(int school) + { + static const std::array schoolSkillArray + { + ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction, + ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration + }; + return schoolSkillArray.at(school); + } + + float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect) + { + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + if (!magicEffect) + magicEffect = store.get().find(effect.mEffectID); + bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude); + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + int minMagn = hasMagnitude ? effect.mMagnMin : 1; + int maxMagn = hasMagnitude ? effect.mMagnMax : 1; + int duration = hasDuration ? effect.mDuration : 1; + static const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); + + float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); + x *= 0.1 * magicEffect->mData.mBaseCost; + x *= 1 + duration; + x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; + + return x * fEffectCostMult; + } + + int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) + { + /* + * Each point of enchant skill above/under 10 subtracts/adds + * one percent of enchantment cost while minimum is 1. + */ + int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant); + const float result = castCost - (castCost / 100) * (eSkill - 10); + + return static_cast((result < 1) ? 1 : result); + } + + float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) + { + // Morrowind for some reason uses a formula slightly different from magicka cost calculation + float y = std::numeric_limits::max(); + float lowestSkill = 0; + + for (const ESM::ENAMstruct& effect : spell->mEffects.mList) + { + float x = static_cast(effect.mDuration); + const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effect.mEffectID); + + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce)) + x = std::max(1.f, x); + + x *= 0.1f * magicEffect->mData.mBaseCost; + x *= 0.5f * (effect.mMagnMin + effect.mMagnMax); + x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost; + if (effect.mRange == ESM::RT_Target) + x *= 1.5f; + static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fEffectCostMult")->mValue.getFloat(); + x *= fEffectCostMult; + + float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool)); + if (s - x < y) + { + y = s - x; + if (effectiveSchool) + *effectiveSchool = magicEffect->mData.mSchool; + lowestSkill = s; + } + } + + CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + + float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck); + + return castChance; + } + + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) + { + bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); + + CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() && !godmode) + return 0; + + if (spell->mData.mType == ESM::Spell::ST_Power) + return stats.getSpells().canUsePower(spell) ? 100 : 0; + + if (godmode) + return 100; + + if (spell->mData.mType != ESM::Spell::ST_Spell) + return 100; + + if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost) + return 0; + + if (spell->mData.mFlags & ESM::Spell::F_Always) + return 100; + + float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude(); + float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus; + castChance *= stats.getFatigueTerm(); + + return std::max(0.f, cap ? std::min(100.f, castChance) : castChance); + } + + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka) + { + if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId)) + return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka); + return 0.f; + } + + int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) + { + int school = 0; + getSpellSuccessChance(spellId, actor, &school); + return school; + } + + int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) + { + int school = 0; + getSpellSuccessChance(spell, actor, &school); + return school; + } + + bool spellIncreasesSkill(const ESM::Spell *spell) + { + return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always); + } + + bool spellIncreasesSkill(const std::string &spellId) + { + const auto spell = MWBase::Environment::get().getWorld()->getStore().get().search(spellId); + return spell && spellIncreasesSkill(spell); + } + + bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer) + { + switch (effectId) + { + case ESM::MagicEffect::Levitate: + { + if (!MWBase::Environment::get().getWorld()->isLevitationEnabled()) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + return false; + } + break; + } + case ESM::MagicEffect::Soultrap: + { + if (!target.getClass().isNpc() // no messagebox for NPCs + && (target.getTypeName() == typeid(ESM::Creature).name() && target.get()->mBase->mData.mSoul == 0)) + { + if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}"); + return true; // must still apply to get visual effect and have target regard it as attack + } + break; + } + case ESM::MagicEffect::WaterWalking: + { + if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target)) + return false; + + MWBase::World *world = MWBase::Environment::get().getWorld(); + + if (!world->isWaterWalkingCastableOnTarget(target)) + { + if (castByPlayer && caster == target) + MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}"); + return false; + } + break; + } + } + return true; + } +} diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp new file mode 100644 index 000000000..865a9126e --- /dev/null +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -0,0 +1,50 @@ +#ifndef MWMECHANICS_SPELLUTIL_H +#define MWMECHANICS_SPELLUTIL_H + +#include + +namespace ESM +{ + struct ENAMstruct; + struct MagicEffect; + struct Spell; +} + +namespace MWWorld +{ + class Ptr; +} + +namespace MWMechanics +{ + ESM::Skill::SkillEnum spellSchoolToSkill(int school); + + float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr); + + int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); + + /** + * @param spell spell to cast + * @param actor calculate spell success chance for this actor (depends on actor's skills) + * @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here + * @param cap cap the result to 100%? + * @param checkMagicka check magicka? + * @note actor can be an NPC or a creature + * @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned. + */ + float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool); + float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); + float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true); + + int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor); + int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor); + + /// Get whether or not the given spell contributes to skill progress. + bool spellIncreasesSkill(const ESM::Spell* spell); + bool spellIncreasesSkill(const std::string& spellId); + + /// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure. + bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer); +} + +#endif diff --git a/apps/openmw/mwmechanics/tickableeffects.cpp b/apps/openmw/mwmechanics/tickableeffects.cpp new file mode 100644 index 000000000..2e48375c9 --- /dev/null +++ b/apps/openmw/mwmechanics/tickableeffects.cpp @@ -0,0 +1,218 @@ +#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; + + switch (effectKey.mId) + { + case ESM::MagicEffect::DamageAttribute: + { + 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: + receivedMagicDamage = true; + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude); + break; + + case ESM::MagicEffect::DamageMagicka: + case ESM::MagicEffect::DamageFatigue: + { + 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 (magnitude > 0.f) + receivedMagicDamage = true; + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); + + break; + + case ESM::MagicEffect::AbsorbMagicka: + case ESM::MagicEffect::AbsorbFatigue: + adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude); + break; + + case ESM::MagicEffect::DisintegrateArmor: + { + // According to UESP + int 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 (unsigned int i=0; iisExterior()) + 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: + { + adjustDynamicStat(creatureStats, 0, -magnitude); + receivedMagicDamage = true; + break; + } + + case ESM::MagicEffect::DamageSkill: + case ESM::MagicEffect::RestoreSkill: + { + if (!actor.getClass().isNpc()) + 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; + } + + case ESM::MagicEffect::CurePoison: + actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); + break; + case ESM::MagicEffect::CureParalyzation: + actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); + break; + case ESM::MagicEffect::CureCommonDisease: + actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease(); + break; + case ESM::MagicEffect::CureBlightDisease: + actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease(); + break; + case ESM::MagicEffect::CureCorprusDisease: + actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease(); + break; + case ESM::MagicEffect::RemoveCurse: + actor.getClass().getCreatureStats(actor).getSpells().purgeCurses(); + 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 new file mode 100644 index 000000000..c4abed6a3 --- /dev/null +++ b/apps/openmw/mwmechanics/tickableeffects.hpp @@ -0,0 +1,19 @@ +#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 + /// @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/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index 2e6501225..13ce30927 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -13,7 +13,7 @@ #include "combat.hpp" #include "aicombataction.hpp" #include "spellpriority.hpp" -#include "spellcasting.hpp" +#include "spellutil.hpp" #include "weapontype.hpp" namespace MWMechanics diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 1de97d068..d4358532c 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -13,8 +13,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spellresistance.hpp" +#include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/weapontype.hpp" diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 8c5f52655..11444c8eb 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -20,7 +20,7 @@ #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spellutil.hpp" #include "class.hpp" #include "ptr.hpp" diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 37123099a..a623a5d52 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3161,12 +3161,42 @@ namespace MWWorld mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection); } + class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor + { + private: + MWWorld::Ptr mActor; + + public: + ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor) + : mActor(actor) + { + } + + virtual void visit (MWMechanics::EffectKey key, + const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, + float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1) + { + 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()) { - MWMechanics::ApplyLoopingParticlesVisitor visitor(ptr); + ApplyLoopingParticlesVisitor visitor(ptr); cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor); cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor); if (cls.hasInventoryStore(ptr)) From b1d857818d12283b63b2f74bd3025659708b21a6 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 26 Apr 2020 21:37:19 +0300 Subject: [PATCH 21/58] Clean up CastSpell --- apps/openmw/mwmechanics/spellcasting.cpp | 32 ++++++++---------------- apps/openmw/mwmechanics/spellcasting.hpp | 4 --- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index bd61e7798..d78cab84a 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -20,7 +20,7 @@ #include "../mwrender/animation.hpp" -#include "npcstats.hpp" +#include "creaturestats.hpp" #include "actorutil.hpp" #include "aifollow.hpp" #include "weapontype.hpp" @@ -515,16 +515,14 @@ namespace MWMechanics bool CastSpell::cast(const std::string &id) { - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + if (const auto spell = store.get().search(id)) return cast(spell); - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) + if (const auto potion = store.get().search(id)) return cast(potion); - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) + if (const auto ingredient = store.get().search(id)) return cast(ingredient); throw std::runtime_error("ID type cannot be casted"); @@ -687,10 +685,9 @@ namespace MWMechanics stats.getSpells().usePower(spell); } - if (mCaster == getPlayer() && spellIncreasesSkill()) - mCaster.getClass().skillUsageSucceeded(mCaster, - spellSchoolToSkill(school), 0); - + if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell)) + mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0); + // A non-actor doesn't play its spell cast effects from a character controller, so play them here if (!mCaster.getClass().isActor()) playSpellCastingEffects(spell->mEffects.mList); @@ -718,10 +715,8 @@ namespace MWMechanics effect.mRange = ESM::RT_Self; effect.mArea = 0; - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effect.mEffectID); - + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + const auto magicEffect = store.get().find(effect.mEffectID); const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) + @@ -733,7 +728,7 @@ namespace MWMechanics if (roll > x) { // "X has no effect on you" - std::string message = MWBase::Environment::get().getWorld()->getStore().get().find("sNotifyMessage50")->mValue.getString(); + std::string message = store.get().find("sNotifyMessage50")->mValue.getString(); message = Misc::StringUtils::format(message, ingredient->mName); MWBase::Environment::get().getWindowManager()->messageBox(message); return false; @@ -834,9 +829,4 @@ namespace MWMechanics sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f); } } - - bool CastSpell::spellIncreasesSkill() - { - return !mManualSpell && MWMechanics::spellIncreasesSkill(mId); - } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 3fcec8f4a..45431bbc6 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -17,8 +17,6 @@ namespace ESM namespace MWMechanics { struct EffectKey; - class MagicEffects; - class CreatureStats; class CastSpell { @@ -56,8 +54,6 @@ namespace MWMechanics void playSpellCastingEffects(const std::string &spellid, bool enchantment); - bool spellIncreasesSkill(); - /// Launch a bolt with the given effects. void launchMagicBolt (); From bd1ef4dd6d1f314dab61b86b6a18c5c5f2052079 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 22 Feb 2020 13:03:44 -0800 Subject: [PATCH 22/58] Add detournavigator test for multiple worker threads --- .../detournavigator/navigator.cpp | 67 +++++++++++++++++++ .../detournavigator/asyncnavmeshupdater.cpp | 22 ++++-- .../detournavigator/asyncnavmeshupdater.hpp | 1 + components/detournavigator/navmeshmanager.cpp | 2 +- components/misc/guarded.hpp | 8 +++ 5 files changed, 92 insertions(+), 8 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 51370c8e0..71b531513 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -699,4 +699,71 @@ namespace EXPECT_FLOAT_EQ(distance, 85.260780334472656); } + + TEST_F(DetourNavigatorNavigatorTest, multiple_threads_should_lock_tiles) + { + mSettings.mAsyncNavMeshUpdaterThreads = 2; + mNavigator.reset(new NavigatorImpl(mSettings)); + + const std::array heightfieldData {{ + 0, 0, 0, 0, 0, + 0, -25, -25, -25, -25, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + 0, -25, -100, -100, -100, + }}; + btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false); + heightfieldShape.setLocalScaling(btVector3(128, 128, 1)); + + const std::vector boxShapes(100, btVector3(20, 20, 100)); + + mNavigator->addAgent(mAgentHalfExtents); + + mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity()); + + for (std::size_t i = 0; i < boxShapes.size(); ++i) + { + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10, i * 10, i * 10)); + mNavigator->addObject(ObjectId(&boxShapes[i]), boxShapes[i], transform); + } + + std::this_thread::sleep_for(std::chrono::microseconds(1)); + + for (std::size_t i = 0; i < boxShapes.size(); ++i) + { + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 10 + 1, i * 10 + 1, i * 10 + 1)); + mNavigator->updateObject(ObjectId(&boxShapes[i]), boxShapes[i], transform); + } + + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mOut), Status::Success); + + EXPECT_THAT(mPath, ElementsAre( + Vec3fEq(-215, 215, 1.8782780170440673828125), + Vec3fEq(-199.7968292236328125, 191.09100341796875, -3.54875946044921875), + Vec3fEq(-184.5936431884765625, 167.1819915771484375, -8.97846889495849609375), + Vec3fEq(-169.3904571533203125, 143.2729949951171875, -14.40818119049072265625), + Vec3fEq(-154.1872711181640625, 119.363983154296875, -19.837886810302734375), + Vec3fEq(-138.9840850830078125, 95.4549713134765625, -25.2675952911376953125), + Vec3fEq(-123.78090667724609375, 71.54595947265625, -30.6973056793212890625), + Vec3fEq(-108.57772064208984375, 47.63695526123046875, -36.12701416015625), + Vec3fEq(-93.3745269775390625, 23.72794342041015625, -40.754695892333984375), + Vec3fEq(-78.17134857177734375, -0.18106450140476226806640625, -37.128795623779296875), + Vec3fEq(-62.968158721923828125, -24.0900726318359375, -33.50289154052734375), + Vec3fEq(-47.764972686767578125, -47.99908447265625, -30.797946929931640625), + Vec3fEq(-23.8524494171142578125, -63.196746826171875, -33.97112274169921875), + Vec3fEq(0.0600722394883632659912109375, -78.3944091796875, -37.14543914794921875), + Vec3fEq(23.97259521484375, -93.592071533203125, -40.774089813232421875), + Vec3fEq(47.885120391845703125, -108.78974151611328125, -36.051296234130859375), + Vec3fEq(71.797637939453125, -123.98740386962890625, -30.62355804443359375), + Vec3fEq(95.71016693115234375, -139.18505859375, -25.195819854736328125), + Vec3fEq(119.6226806640625, -154.382720947265625, -19.768085479736328125), + Vec3fEq(143.5352020263671875, -169.5803680419921875, -14.34035015106201171875), + Vec3fEq(167.447723388671875, -184.7780303955078125, -8.912616729736328125), + Vec3fEq(191.3602294921875, -199.9756927490234375, -3.48488140106201171875), + Vec3fEq(215, -215, 1.8782813549041748046875) + )) << mPath; + } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index da1ac19a7..1c07384b8 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -102,8 +102,11 @@ namespace DetourNavigator void AsyncNavMeshUpdater::wait() { - std::unique_lock lock(mMutex); - mDone.wait(lock, [&] { return mJobs.empty() && getTotalThreadJobsUnsafe() == 0; }); + { + std::unique_lock lock(mMutex); + mDone.wait(lock, [&] { return mJobs.empty() && getTotalThreadJobsUnsafe() == 0; }); + } + mProcessingTiles.wait(mProcessed, [] (const auto& v) { return v.empty(); }); } void AsyncNavMeshUpdater::reportStats(unsigned int frameNumber, osg::Stats& stats) const @@ -122,7 +125,7 @@ namespace DetourNavigator void AsyncNavMeshUpdater::process() throw() { - Log(Debug::Debug) << "Start process navigator jobs"; + Log(Debug::Debug) << "Start process navigator jobs by thread=" << std::this_thread::get_id(); while (!mShouldStop) { try @@ -140,12 +143,13 @@ namespace DetourNavigator Log(Debug::Error) << "AsyncNavMeshUpdater::process exception: " << e.what(); } } - Log(Debug::Debug) << "Stop navigator jobs processing"; + Log(Debug::Debug) << "Stop navigator jobs processing by thread=" << std::this_thread::get_id(); } bool AsyncNavMeshUpdater::processJob(const Job& job) { - Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")"; + Log(Debug::Debug) << "Process job for agent=(" << std::fixed << std::setprecision(2) << job.mAgentHalfExtents << ")" + " by thread=" << std::this_thread::get_id(); const auto start = std::chrono::steady_clock::now(); @@ -176,7 +180,8 @@ namespace DetourNavigator " generation=" << locked->getGeneration() << " revision=" << locked->getNavMeshRevision() << " time=" << std::chrono::duration_cast(finish - start).count() << "ms" << - " total_time=" << std::chrono::duration_cast(finish - firstStart).count() << "ms"; + " total_time=" << std::chrono::duration_cast(finish - firstStart).count() << "ms" + " thread=" << std::this_thread::get_id(); return isSuccess(status); } @@ -201,7 +206,7 @@ namespace DetourNavigator } Log(Debug::Debug) << "Got " << mJobs.size() << " navigator jobs and " - << threadQueue.mJobs.size() << " thread jobs"; + << threadQueue.mJobs.size() << " thread jobs by thread=" << std::this_thread::get_id(); auto job = threadQueue.mJobs.empty() ? getJob(mJobs, mPushed) @@ -329,6 +334,9 @@ namespace DetourNavigator if (agent->second.empty()) locked->erase(agent); + + if (locked->empty()) + mProcessed.notify_all(); } std::size_t AsyncNavMeshUpdater::getTotalThreadJobsUnsafe() const diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index c833d617c..6a3799969 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -86,6 +86,7 @@ namespace DetourNavigator mutable std::mutex mMutex; std::condition_variable mHasJob; std::condition_variable mDone; + std::condition_variable mProcessed; Jobs mJobs; std::map> mPushed; Misc::ScopeGuarded mPlayerTile; diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index b6c25bd93..2424a51e3 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -191,7 +191,7 @@ namespace DetourNavigator mAsyncNavMeshUpdater.post(agentHalfExtents, cached, playerTile, tilesToPost); if (changedTiles != mChangedTiles.end()) changedTiles->second.clear(); - Log(Debug::Debug) << "cache update posted for agent=" << agentHalfExtents << + Log(Debug::Debug) << "Cache update posted for agent=" << agentHalfExtents << " playerTile=" << lastPlayerTile->second << " recastMeshManagerRevision=" << lastRevision; } diff --git a/components/misc/guarded.hpp b/components/misc/guarded.hpp index 559476867..55a2c670c 100644 --- a/components/misc/guarded.hpp +++ b/components/misc/guarded.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Misc { @@ -79,6 +80,13 @@ namespace Misc return Locked(mMutex, mValue); } + template + void wait(std::condition_variable& cv, Predicate&& predicate) + { + std::unique_lock lock(mMutex); + cv.wait(lock, [&] { return predicate(mValue); }); + } + private: std::mutex mMutex; T mValue; From 6d3f9ce3072cf789165a5f28b073129937f47815 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Sun, 26 Apr 2020 22:20:57 +0200 Subject: [PATCH 23/58] puts an end of error spam when OSG is copmiled without Freetype support --- components/resource/stats.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 51497cd27..1d78d7a4c 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -8,6 +8,8 @@ #include +#include + #include #include @@ -32,12 +34,14 @@ StatsHandler::StatsHandler(): _resourceStatsChildNum = 0; - _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); + if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) + _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); } Profiler::Profiler() { - _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); + if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) + _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); } From bbd15cccd5c76c9245134c3a71553b400ce569ca Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 27 Apr 2020 00:21:34 +0300 Subject: [PATCH 24/58] Fix hidden node with NiVisController optimization --- components/nifosg/nifloader.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7d62d1ef1..8b6d69f2d 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -591,7 +591,13 @@ namespace NifOsg { bool hasVisController = false; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) - hasVisController = (ctrl->recType == Nif::RC_NiVisController); + { + if (ctrl->recType == Nif::RC_NiVisController) + { + hasVisController = true; + break; + } + } if (!hasVisController) skipMeshes = true; // skip child meshes, but still create the child node hierarchy for animating collision shapes From 1870b4b34586a052ee70b41a771d89c174558c31 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 27 Apr 2020 08:02:42 +0200 Subject: [PATCH 25/58] catch and set with no _found; leave empty as empty string --- components/resource/stats.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 1d78d7a4c..bc63ddf58 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -42,6 +42,8 @@ Profiler::Profiler() { if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); + else + _font = ""; setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); } From dc33eeadf1b3e58ef76f85d339bbfcb13d1862dd Mon Sep 17 00:00:00 2001 From: bzzt Date: Fri, 21 Jun 2019 13:37:00 +0000 Subject: [PATCH 26/58] tightscenebound is uncessary after water bbfix --- apps/openmw/mwgui/loadingscreen.cpp | 10 ----- apps/openmw/mwgui/windowmanagerimp.cpp | 38 ++++++++++++++----- apps/openmw/mwgui/windowmanagerimp.hpp | 4 ++ apps/openmw/mwrender/renderingmanager.cpp | 8 ++-- components/sceneutil/shadow.cpp | 3 ++ components/sceneutil/waterutil.cpp | 8 ++++ .../reference/modding/settings/shadows.rst | 3 +- files/settings-default.cfg | 4 +- 8 files changed, 49 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index 093a2f34c..dcfe723f7 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -334,12 +334,6 @@ namespace MWGui setupCopyFramebufferToTextureCallback(); } - // Turn off rendering except the GUI - int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); - int oldCullMask = mViewer->getCamera()->getCullMask(); - mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile); - mViewer->getCamera()->setCullMask(MWRender::Mask_GUI|MWRender::Mask_PreCompile); - MWBase::Environment::get().getInputManager()->update(0, true, true); //osg::Timer timer; @@ -355,10 +349,6 @@ namespace MWGui //if (mViewer->getIncrementalCompileOperation()) //std::cout << "num to compile " << mViewer->getIncrementalCompileOperation()->getToCompile().size() << std::endl; - // resume 3d rendering - mViewer->getUpdateVisitor()->setTraversalMask(oldUpdateMask); - mViewer->getCamera()->setCullMask(oldCullMask); - mLastRenderTime = mTimer.time_m(); } diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index d10270d77..28521ac6f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -130,7 +130,9 @@ namespace MWGui osgViewer::Viewer* viewer, osg::Group* guiRoot, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue, const std::string& logpath, const std::string& resourcePath, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::string& versionDescription, const std::string& userDataPath) - : mStore(nullptr) + : mOldUpdateMask(0) + , mOldCullMask(0) + , mStore(nullptr) , mResourceSystem(resourceSystem) , mWorkQueue(workQueue) , mViewer(viewer) @@ -676,13 +678,34 @@ namespace MWGui } } + void WindowManager::enableScene(bool enable) + { + unsigned int disablemask = MWRender::Mask_GUI|MWRender::Mask_PreCompile; + if (!enable && mViewer->getCamera()->getCullMask() != disablemask) + { + mOldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); + mOldCullMask = mViewer->getCamera()->getCullMask(); + mViewer->getUpdateVisitor()->setTraversalMask(disablemask); + mViewer->getCamera()->setCullMask(disablemask); + } + else if (enable && mViewer->getCamera()->getCullMask() == disablemask) + { + mViewer->getUpdateVisitor()->setTraversalMask(mOldUpdateMask); + mViewer->getCamera()->setCullMask(mOldCullMask); + } + } + void WindowManager::updateVisible() { + bool loading = (getMode() == GM_Loading || getMode() == GM_LoadingWallpaper); + + bool mainmenucover = containsMode(GM_MainMenu) && MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame; + + enableScene(!loading && !mainmenucover); + if (!mMap) return; // UI not created yet - bool loading = (getMode() == GM_Loading || getMode() == GM_LoadingWallpaper); - mHud->setVisible(mHudEnabled && !loading); mToolTips->setVisible(mHudEnabled && !loading); @@ -1876,11 +1899,7 @@ namespace MWGui mVideoBackground->eventKeyButtonPressed += MyGUI::newDelegate(this, &WindowManager::onVideoKeyPressed); } - // Turn off all rendering except for the GUI - int oldUpdateMask = mViewer->getUpdateVisitor()->getTraversalMask(); - int oldCullMask = mViewer->getCamera()->getCullMask(); - mViewer->getUpdateVisitor()->setTraversalMask(MWRender::Mask_GUI); - mViewer->getCamera()->setCullMask(MWRender::Mask_GUI); + enableScene(false); MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); sizeVideo(screenSize.width, screenSize.height); @@ -1936,8 +1955,7 @@ namespace MWGui setCursorVisible(cursorWasVisible); // Restore normal rendering - mViewer->getUpdateVisitor()->setTraversalMask(oldUpdateMask); - mViewer->getCamera()->setCullMask(oldCullMask); + updateVisible(); mVideoBackground->setVisible(false); } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 0b4307ec4..4e90f2e93 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -390,6 +390,8 @@ namespace MWGui virtual bool injectKeyRelease(MyGUI::KeyCode key); private: + unsigned int mOldUpdateMask; unsigned int mOldCullMask; + const MWWorld::ESMStore* mStore; Resource::ResourceSystem* mResourceSystem; osg::ref_ptr mWorkQueue; @@ -560,6 +562,8 @@ namespace MWGui void setMenuTransparency(float value); void updatePinnedWindows(); + + void enableScene(bool enable); }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c966a5d67..e631a8fd8 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -235,6 +235,8 @@ namespace MWRender sceneRoot->setLightingMask(Mask_Lighting); mSceneRoot = sceneRoot; sceneRoot->setStartLight(1); + sceneRoot->setNodeMask(Mask_Scene); + sceneRoot->setName("Scene Root"); int shadowCastingTraversalMask = Mask_Scene; if (Settings::Manager::getBool("actor shadows", "Shadows")) @@ -347,9 +349,6 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); - sceneRoot->setNodeMask(Mask_Scene); - sceneRoot->setName("Scene Root"); - mSky.reset(new SkyManager(sceneRoot, resourceSystem->getSceneManager())); mSky->setCamera(mViewer->getCamera()); @@ -768,11 +767,10 @@ namespace MWRender void waitTillDone() { - mMutex.lock(); + OpenThreads::ScopedLock lock(mMutex); if (mDone) return; mCondition.wait(&mMutex); - mMutex.unlock(); } mutable OpenThreads::Condition mCondition; diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index 6b88adaab..a1cd1d660 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -40,6 +40,8 @@ namespace SceneUtil mShadowSettings->setMinimumShadowMapNearFarRatio(Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); if (Settings::Manager::getBool("compute tight scene bounds", "Shadows")) mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); + else + mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres)); @@ -95,6 +97,7 @@ namespace SceneUtil mShadowedScene->addChild(sceneRoot); rootNode->addChild(mShadowedScene); + mShadowedScene->setNodeMask(sceneRoot->getNodeMask()); mShadowSettings = mShadowedScene->getShadowSettings(); setupShadowSettings(); diff --git a/components/sceneutil/waterutil.cpp b/components/sceneutil/waterutil.cpp index 562b0ee73..20a50a930 100644 --- a/components/sceneutil/waterutil.cpp +++ b/components/sceneutil/waterutil.cpp @@ -7,6 +7,12 @@ namespace SceneUtil { + // disable nonsense test against a worldsize bb what will always pass + class WaterBoundCallback : public osg::Drawable::ComputeBoundingBoxCallback + { + virtual osg::BoundingBox computeBound(const osg::Drawable&) const { return osg::BoundingBox(); } + }; + osg::ref_ptr createWaterGeometry(float size, int segments, float textureRepeats) { osg::ref_ptr verts (new osg::Vec3Array); @@ -51,6 +57,8 @@ namespace SceneUtil waterGeom->setNormalArray(normal, osg::Array::BIND_OVERALL); waterGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,verts->size())); + waterGeom->setComputeBoundingBoxCallback(new WaterBoundCallback); + waterGeom->setCullingActive(false); return waterGeom; } diff --git a/docs/source/reference/modding/settings/shadows.rst b/docs/source/reference/modding/settings/shadows.rst index 5854b6a91..638349ee6 100644 --- a/docs/source/reference/modding/settings/shadows.rst +++ b/docs/source/reference/modding/settings/shadows.rst @@ -85,10 +85,9 @@ compute tight scene bounds :Type: boolean :Range: True/False -:Default: True +:Default: False With this setting enabled, attempt to better use the shadow map(s) by making them cover a smaller area. -This can be especially helpful when looking downwards with a high viewing distance but will be less useful with the default value. May have a minor to major performance impact. shadow map resolution diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 06950e50d..056ee4a1e 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -805,8 +805,8 @@ enable debug hud = false # Enable the debug overlay to see where each shadow map affects. enable debug overlay = false -# Attempt to better use the shadow map by making them cover a smaller area. Especially helpful when looking downwards. May have a minor to major performance impact. -compute tight scene bounds = true +# Attempt to better use the shadow map by making them cover a smaller area. May have a minor to major performance impact. +compute tight scene bounds = false # How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations. shadow map resolution = 1024 From 3ba77b933a8aef090457e6f1bca1b1ac0efb7f43 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 27 Apr 2020 08:45:52 +0200 Subject: [PATCH 27/58] "compute tight scene bounds" reset back to true until it is decided how best to handle this in settings --- docs/source/reference/modding/settings/shadows.rst | 2 +- files/settings-default.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/reference/modding/settings/shadows.rst b/docs/source/reference/modding/settings/shadows.rst index 638349ee6..d0d92a6e2 100644 --- a/docs/source/reference/modding/settings/shadows.rst +++ b/docs/source/reference/modding/settings/shadows.rst @@ -85,7 +85,7 @@ compute tight scene bounds :Type: boolean :Range: True/False -:Default: False +:Default: True With this setting enabled, attempt to better use the shadow map(s) by making them cover a smaller area. May have a minor to major performance impact. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 056ee4a1e..6703e7732 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -806,7 +806,7 @@ enable debug hud = false enable debug overlay = false # Attempt to better use the shadow map by making them cover a smaller area. May have a minor to major performance impact. -compute tight scene bounds = false +compute tight scene bounds = true # How large to make the shadow map(s). Higher values increase GPU load, but can produce better-looking results. Power-of-two values may turn out to be faster on some GPU/driver combinations. shadow map resolution = 1024 From 4c1c30db3350472d024e4391a9f85421985f82de Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 27 Apr 2020 12:03:20 +0300 Subject: [PATCH 28/58] Address akortunov's concerns regarding spell refactoring Separate linked effect handling into linked effects header Separate spell absorption handling into spell absorption header Make armor disintegration loop a range-based for loop --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/mwmechanics/linkedeffects.cpp | 71 +++++++++++++ apps/openmw/mwmechanics/linkedeffects.hpp | 32 ++++++ apps/openmw/mwmechanics/spellabsorption.cpp | 76 ++++++++++++++ apps/openmw/mwmechanics/spellabsorption.hpp | 20 ++++ apps/openmw/mwmechanics/spellcasting.cpp | 107 ++++---------------- apps/openmw/mwmechanics/tickableeffects.cpp | 9 +- 7 files changed, 222 insertions(+), 94 deletions(-) create mode 100644 apps/openmw/mwmechanics/linkedeffects.cpp create mode 100644 apps/openmw/mwmechanics/linkedeffects.hpp create mode 100644 apps/openmw/mwmechanics/spellabsorption.cpp create mode 100644 apps/openmw/mwmechanics/spellabsorption.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index bee3641f8..2b4739ba9 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -86,6 +86,7 @@ add_openmw_dir (mwmechanics 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 coordinateconverter trading weaponpriority spellpriority weapontype spellutil tickableeffects + spellabsorption linkedeffects ) add_openmw_dir (mwstate diff --git a/apps/openmw/mwmechanics/linkedeffects.cpp b/apps/openmw/mwmechanics/linkedeffects.cpp new file mode 100644 index 000000000..58a497a59 --- /dev/null +++ b/apps/openmw/mwmechanics/linkedeffects.cpp @@ -0,0 +1,71 @@ +#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"); + + std::vector absorbEffects; + ActiveSpells::ActiveEffect absorbEffect = appliedEffect; + absorbEffect.mMagnitude *= -1; + 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 new file mode 100644 index 000000000..a6dea2a3a --- /dev/null +++ b/apps/openmw/mwmechanics/linkedeffects.hpp @@ -0,0 +1,32 @@ +#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/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp new file mode 100644 index 000000000..f38fd78e2 --- /dev/null +++ b/apps/openmw/mwmechanics/spellabsorption.cpp @@ -0,0 +1,76 @@ +#include "spellabsorption.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwrender/animation.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" + +#include "creaturestats.hpp" + +namespace MWMechanics +{ + + class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor + { + public: + float mProbability{0.f}; + + GetAbsorptionProbability() = default; + + virtual void visit (MWMechanics::EffectKey key, + const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/, + float magnitude, float /*remainingTime*/, float /*totalTime*/) + { + if (key.mId == ESM::MagicEffect::SpellAbsorption) + { + if (mProbability == 0.f) + mProbability = magnitude / 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 - mProbability; + failProbability *= 1.f - magnitude / 100; + mProbability = 1.f - failProbability; + } + } + } + }; + + bool absorbSpell (const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target) + { + if (!spell || caster == target || !target.getClass().isActor()) + return false; + + CreatureStats& stats = target.getClass().getCreatureStats(target); + 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; + if (Misc::Rng::roll0to99() >= chance) + return false; + + const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find("VFX_Absorb"); + MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); + if (animation && !absorbStatic->mModel.empty()) + animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string()); + // Magicka is increased by the cost of the spell + DynamicStat magicka = stats.getMagicka(); + magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); + stats.setMagicka(magicka); + return true; + } + +} diff --git a/apps/openmw/mwmechanics/spellabsorption.hpp b/apps/openmw/mwmechanics/spellabsorption.hpp new file mode 100644 index 000000000..147090d96 --- /dev/null +++ b/apps/openmw/mwmechanics/spellabsorption.hpp @@ -0,0 +1,20 @@ +#ifndef MWMECHANICS_SPELLABSORPTION_H +#define MWMECHANICS_SPELLABSORPTION_H + +namespace ESM +{ + struct Spell; +} + +namespace MWWorld +{ + class Ptr; +} + +namespace MWMechanics +{ + // Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target. + bool absorbSpell(const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target); +} + +#endif diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d78cab84a..4326a43f9 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -2,7 +2,6 @@ #include #include -#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -20,44 +19,19 @@ #include "../mwrender/animation.hpp" -#include "creaturestats.hpp" #include "actorutil.hpp" #include "aifollow.hpp" -#include "weapontype.hpp" -#include "summoning.hpp" +#include "creaturestats.hpp" +#include "linkedeffects.hpp" +#include "spellabsorption.hpp" #include "spellresistance.hpp" #include "spellutil.hpp" +#include "summoning.hpp" #include "tickableeffects.hpp" +#include "weapontype.hpp" namespace MWMechanics { - class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor - { - public: - float mProbability{0.f}; - - GetAbsorptionProbability() = default; - - virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, const std::string& sourceId, int casterActorId, - float magnitude, float remainingTime = -1, float totalTime = -1) - { - if (key.mId == ESM::MagicEffect::SpellAbsorption) - { - if (mProbability == 0.f) - mProbability = magnitude / 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 - mProbability; - failProbability *= 1.f - magnitude / 100; - mProbability = 1.f - failProbability; - } - } - } - }; - CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) : mCaster(caster) , mTarget(target) @@ -172,57 +146,27 @@ namespace MWMechanics && target.getClass().isActor()) MWBase::Environment::get().getWindowManager()->setEnemy(target); - // Try absorbing if it's a spell - // Unlike Reflect, this is done once per spell absorption effect source + // Try absorbing the spell + // FIXME: this should be done only once for the spell bool absorbed = false; - if (spell && caster != target && target.getClass().isActor()) + if (absorbSpell(spell, caster, target)) { - CreatureStats& stats = target.getClass().getCreatureStats(target); - if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f) - { - GetAbsorptionProbability check; - stats.getActiveSpells().visitEffectSources(check); - stats.getSpells().visitEffectSources(check); - if (target.getClass().hasInventoryStore(target)) - target.getClass().getInventoryStore(target).visitEffectSources(check); + absorbed = true; + continue; + } - int absorb = check.mProbability * 100; - absorbed = (Misc::Rng::roll0to99() < absorb); - if (absorbed) - { - const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Absorb"); - MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( - "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, ""); - // Magicka is increased by cost of spell - DynamicStat magicka = stats.getMagicka(); - magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); - stats.setMagicka(magicka); - } - } + // Reflect harmful effects + if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) + { + reflected = true; + continue; } float magnitudeMult = 1; if (target.getClass().isActor()) { - if (absorbed) - continue; - bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; - // Reflect harmful effects - if (isHarmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)) - { - float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude(); - bool isReflected = (Misc::Rng::roll0to99() < reflect); - if (isReflected) - { - const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Reflect"); - MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( - "meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, ""); - reflectedEffects.mList.push_back(*effectIt); - continue; - } - } // Try resisting magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); @@ -317,23 +261,8 @@ namespace MWMechanics // 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 (!caster.isEmpty() && caster != target && caster.getClass().isActor()) - { - if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && - effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill) - { - std::vector absorbEffects; - ActiveSpells::ActiveEffect effect_ = effect; - effect_.mMagnitude *= -1; - absorbEffects.push_back(effect_); - if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game")) - target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true, - absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId()); - else - caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true, - absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId()); - } - } + if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill) + absorbStat(*effectIt, effect, caster, target, reflected, mSourceName); } } diff --git a/apps/openmw/mwmechanics/tickableeffects.cpp b/apps/openmw/mwmechanics/tickableeffects.cpp index 2e48375c9..31e8c150c 100644 --- a/apps/openmw/mwmechanics/tickableeffects.cpp +++ b/apps/openmw/mwmechanics/tickableeffects.cpp @@ -117,8 +117,8 @@ namespace MWMechanics case ESM::MagicEffect::DisintegrateArmor: { - // According to UESP - int priorities[] = { + static const std::array priorities + { MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_Cuirass, MWWorld::InventoryStore::Slot_LeftPauldron, @@ -129,10 +129,9 @@ namespace MWMechanics MWWorld::InventoryStore::Slot_Greaves, MWWorld::InventoryStore::Slot_Boots }; - - for (unsigned int i=0; i Date: Mon, 27 Apr 2020 11:40:49 +0200 Subject: [PATCH 29/58] Something changed in OSG 3.6 that makes the command "showscenegraph" less useful; it writes out raw data of images to the debug output file openmw.ogst. This commit adds the hint and restores default behaviour found in OSG 3.4 --- components/sceneutil/writescene.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/sceneutil/writescene.cpp b/components/sceneutil/writescene.cpp index 19e8dd73b..6be963ef2 100644 --- a/components/sceneutil/writescene.cpp +++ b/components/sceneutil/writescene.cpp @@ -21,6 +21,7 @@ void SceneUtil::writeScene(osg::Node *node, const std::string& filename, const s osg::ref_ptr options = new osgDB::Options; options->setPluginStringData("fileType", format); + options->setPluginStringData("WriteImageHint", "UseExternal"); rw->writeNode(*node, stream, options); } From e7f91ff341358e4a8bb21992601f92b4d4b75cd5 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 27 Apr 2020 12:43:34 +0300 Subject: [PATCH 30/58] Simplify some inflict() logic --- apps/openmw/mwmechanics/linkedeffects.cpp | 3 + apps/openmw/mwmechanics/spellcasting.cpp | 76 +++++++++------------ apps/openmw/mwmechanics/spellresistance.cpp | 3 + 3 files changed, 39 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwmechanics/linkedeffects.cpp b/apps/openmw/mwmechanics/linkedeffects.cpp index 58a497a59..364358433 100644 --- a/apps/openmw/mwmechanics/linkedeffects.cpp +++ b/apps/openmw/mwmechanics/linkedeffects.cpp @@ -52,6 +52,9 @@ namespace MWMechanics 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; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 4326a43f9..cfea2e7ab 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -113,6 +113,9 @@ namespace MWMechanics // throughout the iteration of this spell's // effects, we display a "can't re-cast" message + // Try absorbing the spell. Some handling must still happen for absorbed effects. + bool absorbed = absorbSpell(spell, caster, target); + for (std::vector::const_iterator effectIt (effects.mList.begin()); !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt) { @@ -140,20 +143,14 @@ namespace MWMechanics && (caster.isEmpty() || !caster.getClass().isActor())) continue; - // If player is healing someone, show the target's HP bar - if (castByPlayer && target != caster - && effectIt->mEffectID == ESM::MagicEffect::RestoreHealth - && target.getClass().isActor()) - MWBase::Environment::get().getWindowManager()->setEnemy(target); + // Notify the target actor they've been hit + bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; + if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) + target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); - // Try absorbing the spell - // FIXME: this should be done only once for the spell - bool absorbed = false; - if (absorbSpell(spell, caster, target)) - { - absorbed = true; + // Avoid proceeding further for absorbed spells. + if (absorbed) continue; - } // Reflect harmful effects if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) @@ -162,37 +159,17 @@ namespace MWMechanics continue; } - float magnitudeMult = 1; - - if (target.getClass().isActor()) + // Try resisting. + float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); + if (magnitudeMult == 0) { - bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; - - // Try resisting - magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); - if (magnitudeMult == 0) - { - // Fully resisted, show message - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); - else if (castByPlayer) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); - } - else if (isHarmful && castByPlayer && target != caster) - { - // If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar - MWBase::Environment::get().getWindowManager()->setEnemy(target); - } - - if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) - magnitudeMult = 0; - - // Notify the target actor they've been hit - if (target != caster && !caster.isEmpty() && isHarmful) - target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); + // Fully resisted, show message + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); + else if (castByPlayer) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); } - - if (magnitudeMult > 0 && !absorbed) + else { float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1); magnitude *= magnitudeMult; @@ -219,6 +196,19 @@ namespace MWMechanics effect.mMagnitude = 0; } + // Avoid applying harmful effects to the player in god mode + if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful) + { + effect.mMagnitude = 0; + } + + bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; + if (castByPlayer && target != caster && effectAffectsHealth) + { + // If player is attempting to cast a harmful spell or is healing someone, show the target's HP bar. + MWBase::Environment::get().getWindowManager()->setEnemy(target); + } + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); if (hasDuration && effectIt->mDuration == 0) { @@ -228,7 +218,7 @@ namespace MWMechanics // duration 0 means apply full magnitude instantly bool wasDead = target.getClass().getCreatureStats(target).isDead(); - effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude); + effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude); bool isDead = target.getClass().getCreatureStats(target).isDead(); if (!wasDead && isDead) @@ -253,7 +243,7 @@ namespace MWMechanics // 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() && magnitude >= target.getClass().getCreatureStats(target).getLevel()) + && !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); diff --git a/apps/openmw/mwmechanics/spellresistance.cpp b/apps/openmw/mwmechanics/spellresistance.cpp index 4868a7a25..a187600fb 100644 --- a/apps/openmw/mwmechanics/spellresistance.cpp +++ b/apps/openmw/mwmechanics/spellresistance.cpp @@ -17,6 +17,9 @@ namespace MWMechanics float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { + if (!actor.getClass().isActor()) + return 1; + float resistance = getEffectResistance(effectId, actor, caster, spell, effects); return 1 - resistance / 100.f; } From ca0adc25bb63bcdf100c18bc960cab0d0daf4ce3 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 27 Apr 2020 11:51:18 +0200 Subject: [PATCH 31/58] add two additional classes we do not need to serialize; less pam during `showscenegraph` debug dump. --- components/sceneutil/serialize.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index ab0321532..8a2f9e816 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -115,6 +115,8 @@ void registerSerializers() "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", "SceneUtil::StateSetUpdater", + "SceneUtil::DisableLight", + "SceneUtil::MWShadowTechnique", "NifOsg::NodeUserData", "NifOsg::FlipController", "NifOsg::KeyframeController", From 31a75a962aa51793a409c8764615c8814386a405 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 27 Apr 2020 11:56:10 +0200 Subject: [PATCH 32/58] Add 3 additional classes to be ignored who dumping "showscenegraph" debug output; less spammy --- components/sceneutil/serialize.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 8a2f9e816..f84a19876 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -109,6 +109,7 @@ void registerSerializers() const char* ignore[] = { "MWRender::PtrHolder", "Resource::TemplateRef", + "SceneUtil::CompositeStateSetUpdater", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", "SceneUtil::UpdateRigBounds", @@ -122,7 +123,9 @@ void registerSerializers() "NifOsg::KeyframeController", "NifOsg::TextKeyMapHolder", "NifOsg::Emitter", + "NifOsg::ParticleColorAffector", "NifOsg::ParticleSystem", + "NifOsg::GravityAffector", "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", From 69cd53ef8abd370cf244d8deb305dc7775e100e5 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 27 Apr 2020 14:06:50 +0300 Subject: [PATCH 33/58] Fix reflect --- apps/openmw/mwmechanics/spellcasting.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index cfea2e7ab..044a4338e 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -154,10 +154,7 @@ namespace MWMechanics // Reflect harmful effects if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects)) - { - reflected = true; continue; - } // Try resisting. float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects); From a3b032bf2bc3f4331f0b35220117978a2609eed3 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Mon, 27 Apr 2020 23:49:48 +0100 Subject: [PATCH 34/58] Fix chameleon shadows --- apps/openmw/mwrender/animation.cpp | 12 ++++++++++-- components/shader/shadervisitor.cpp | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0f7548f05..627d36ac4 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -37,6 +38,8 @@ #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" @@ -502,8 +505,9 @@ namespace MWRender class TransparencyUpdater : public SceneUtil::StateSetUpdater { public: - TransparencyUpdater(const float alpha) + TransparencyUpdater(const float alpha, osg::ref_ptr shadowUniform) : mAlpha(alpha) + , mShadowUniform(shadowUniform) { } @@ -517,6 +521,9 @@ namespace MWRender { osg::BlendFunc* blendfunc (new osg::BlendFunc); stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); + // TODO: don't do this anymore once custom shadow renderbin is handling it + if (mShadowUniform) + stateset->addUniform(mShadowUniform); // FIXME: overriding diffuse/ambient/emissive colors osg::Material* material = new osg::Material; @@ -535,6 +542,7 @@ namespace MWRender private: float mAlpha; + osg::ref_ptr mShadowUniform; }; struct Animation::AnimSource @@ -1744,7 +1752,7 @@ namespace MWRender { if (mTransparencyUpdater == nullptr) { - mTransparencyUpdater = new TransparencyUpdater(alpha); + mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform()); mObjectRoot->addUpdateCallback(mTransparencyUpdater); } else diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 639a7ecca..8165effb1 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -232,7 +232,7 @@ namespace Shader { if (!writableStateSet) writableStateSet = getWritableStateSet(node); - // We probably shouldn't construct a new version of this each time as StateSets only use pointer comparison by default. + // 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 writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); } From 3b7fb9ec094c9aefbfa3baa540e1b9bf76d4aa99 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Tue, 28 Apr 2020 10:00:46 +0300 Subject: [PATCH 35/58] Use pipe-equal operator --- components/nifosg/nifloader.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8b6d69f2d..e77e6b34f 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -592,11 +592,8 @@ namespace NifOsg bool hasVisController = false; for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { - if (ctrl->recType == Nif::RC_NiVisController) - { - hasVisController = true; + if (hasVisController |= (ctrl->recType == Nif::RC_NiVisController)) break; - } } if (!hasVisController) From d77047e1cfe00092d2efb54893f766288a950f99 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Tue, 28 Apr 2020 13:16:37 +0200 Subject: [PATCH 36/58] tab2space --- components/resource/stats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index bc63ddf58..59d65e889 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -43,7 +43,7 @@ Profiler::Profiler() if (osgDB::Registry::instance()->getReaderWriterForExtension("ttf")) _font = osgMyGUI::DataManager::getInstance().getDataPath("DejaVuLGCSansMono.ttf"); else - _font = ""; + _font = ""; setKeyEventTogglesOnScreenStats(osgGA::GUIEventAdapter::KEY_F3); } From c08f9e13af2a9b5319eaab074465e7733bcad50d Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Tue, 28 Apr 2020 23:53:00 +0300 Subject: [PATCH 37/58] Allow emitters to be attached to nodes after particle systems --- components/nifosg/nifloader.cpp | 45 +++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7d62d1ef1..15c461bf2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -219,6 +219,9 @@ namespace NifOsg size_t mFirstRootTextureIndex = -1; bool mFoundFirstRootTexturingProperty = false; + // This is used to queue emitters that weren't attached to their node yet. + std::vector>> mEmitterQueue; + static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target) { if(nif->numRoots() < 1) @@ -290,6 +293,9 @@ namespace NifOsg osg::ref_ptr created = handleNode(nifNode, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + // Attach particle emitters to their nodes which should all be loaded by now. + handleQueuedParticleEmitters(created, nif); + if (nif->getUseSkinning()) { osg::ref_ptr skel = new SceneUtil::Skeleton; @@ -979,6 +985,27 @@ namespace NifOsg return emitter; } + void handleQueuedParticleEmitters(osg::Node* rootNode, Nif::NIFFilePtr nif) + { + for (const auto& emitterPair : mEmitterQueue) + { + size_t recIndex = emitterPair.first; + FindGroupByRecIndex findEmitterNode(recIndex); + rootNode->accept(findEmitterNode); + osg::Group* emitterNode = findEmitterNode.mFound; + if (!emitterNode) + { + nif->warn("Failed to find particle emitter emitter node (node record index " + std::to_string(recIndex) + ")"); + continue; + } + + // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node + // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! + emitterNode->addChild(emitterPair.second); + } + mEmitterQueue.clear(); + } + void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags, osg::Node* rootNode) { osg::ref_ptr partsys (new ParticleSystem); @@ -1028,22 +1055,8 @@ namespace NifOsg emitter->setParticleSystem(partsys); emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); - // Note: we assume that the Emitter node is placed *before* the Particle node in the scene graph. - // This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order. - // If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster. - - FindGroupByRecIndex find (partctrl->emitter->recIndex); - rootNode->accept(find); - if (!find.mFound) - { - Log(Debug::Info) << "can't find emitter node, wrong node order? in " << mFilename; - return; - } - osg::Group* emitterNode = find.mFound; - - // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node - // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! - emitterNode->addChild(emitter); + // The emitter node may not actually be handled yet, so let's delay attaching the emitter to a later moment. + mEmitterQueue.emplace_back(partctrl->emitter->recIndex, emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); setupController(partctrl, callback, animflags); From f516178ec9b42840fc2d626844f402893a0f8cb1 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Apr 2020 11:06:14 +0300 Subject: [PATCH 38/58] Fix particle processor cloning Extend emitter handling comment in NIF loader --- components/nifosg/nifloader.cpp | 4 +++- components/sceneutil/clone.cpp | 12 ++++++++++++ components/sceneutil/clone.hpp | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 15c461bf2..f89720dd4 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1056,6 +1056,8 @@ namespace NifOsg emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); // The emitter node may not actually be handled yet, so let's delay attaching the emitter to a later moment. + // If the emitter node is placed later than the particle node, it'll have a single frame delay in particle processing. + // But that shouldn't be a game-breaking issue. mEmitterQueue.emplace_back(partctrl->emitter->recIndex, emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); @@ -1073,7 +1075,7 @@ namespace NifOsg partsys->update(0.0, nv); } - // affectors must be attached *after* the emitter in the scene graph for correct update order + // affectors should be attached *after* the emitter in the scene graph for correct update order // attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct // localToWorldMatrix for transforming to particle space handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 04fd5d78d..0423d6117 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -69,6 +69,15 @@ namespace SceneUtil osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const { osgParticle::ParticleProcessor* cloned = osg::clone(processor, osg::CopyOp::DEEP_COPY_CALLBACKS); + for (std::map::const_iterator it = mMap3.begin(); it != mMap3.end(); ++it) + { + if (processor->getParticleSystem() == it->first) + { + cloned->setParticleSystem(it->second); + return cloned; + } + } + mMap[cloned] = processor->getParticleSystem(); return cloned; } @@ -93,6 +102,9 @@ namespace SceneUtil updater->addParticleSystem(cloned); } } + // In rare situations a particle processor may be placed after the particle system in the scene graph. + mMap3[partsys] = cloned; + return cloned; } diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp index 8a18eeb20..b0a9d56c3 100644 --- a/components/sceneutil/clone.hpp +++ b/components/sceneutil/clone.hpp @@ -35,10 +35,11 @@ namespace SceneUtil virtual osg::Object* operator ()(const osg::Object* node) const; private: - // maps new ParticleProcessor to their old ParticleSystem pointer + // maps new pointers to their old pointers // a little messy, but I think this should be the most efficient way mutable std::map mMap; mutable std::map mMap2; + mutable std::map mMap3; }; } From 6b874e397b030ef042ae0426551fdceca290dc89 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Apr 2020 12:25:52 +0300 Subject: [PATCH 39/58] Make particle system cloning map names more sensible --- components/sceneutil/clone.cpp | 24 ++++++++++++------------ components/sceneutil/clone.hpp | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 0423d6117..0df0f4a5b 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -47,7 +47,7 @@ namespace SceneUtil if (const osgParticle::ParticleSystemUpdater* updater = dynamic_cast(node)) { osgParticle::ParticleSystemUpdater* cloned = new osgParticle::ParticleSystemUpdater(*updater, osg::CopyOp::SHALLOW_COPY); - mMap2[cloned] = updater->getParticleSystem(0); + mUpdaterToOldPs[cloned] = updater->getParticleSystem(0); return cloned; } return osg::CopyOp::operator()(node); @@ -69,16 +69,16 @@ namespace SceneUtil osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const { osgParticle::ParticleProcessor* cloned = osg::clone(processor, osg::CopyOp::DEEP_COPY_CALLBACKS); - for (std::map::const_iterator it = mMap3.begin(); it != mMap3.end(); ++it) + for (const auto& oldPsNewPsPair : mOldPsToNewPs) { - if (processor->getParticleSystem() == it->first) + if (processor->getParticleSystem() == oldPsNewPsPair.first) { - cloned->setParticleSystem(it->second); + cloned->setParticleSystem(oldPsNewPsPair.second); return cloned; } } - mMap[cloned] = processor->getParticleSystem(); + mProcessorToOldPs[cloned] = processor->getParticleSystem(); return cloned; } @@ -86,24 +86,24 @@ namespace SceneUtil { osgParticle::ParticleSystem* cloned = osg::clone(partsys, *this); - for (std::map::const_iterator it = mMap.begin(); it != mMap.end(); ++it) + for (const auto& processorPsPair : mProcessorToOldPs) { - if (it->second == partsys) + if (processorPsPair.second == partsys) { - it->first->setParticleSystem(cloned); + processorPsPair.first->setParticleSystem(cloned); } } - for (std::map::const_iterator it = mMap2.begin(); it != mMap2.end(); ++it) + for (const auto& updaterPsPair : mUpdaterToOldPs) { - if (it->second == partsys) + if (updaterPsPair.second == partsys) { - osgParticle::ParticleSystemUpdater* updater = it->first; + osgParticle::ParticleSystemUpdater* updater = updaterPsPair.first; updater->removeParticleSystem(updater->getParticleSystem(0)); updater->addParticleSystem(cloned); } } // In rare situations a particle processor may be placed after the particle system in the scene graph. - mMap3[partsys] = cloned; + mOldPsToNewPs[partsys] = cloned; return cloned; } diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp index b0a9d56c3..20788799f 100644 --- a/components/sceneutil/clone.hpp +++ b/components/sceneutil/clone.hpp @@ -37,9 +37,9 @@ namespace SceneUtil private: // maps new pointers to their old pointers // a little messy, but I think this should be the most efficient way - mutable std::map mMap; - mutable std::map mMap2; - mutable std::map mMap3; + mutable std::map mProcessorToOldPs; + mutable std::map mUpdaterToOldPs; + mutable std::map mOldPsToNewPs; }; } From 899a6b5aa3d9e21169276cfca6e50bdb0fc29d73 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 29 Apr 2020 13:54:48 +0200 Subject: [PATCH 40/58] Workaround for GCC 5 bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61636 --- components/detournavigator/navmeshmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/detournavigator/navmeshmanager.cpp b/components/detournavigator/navmeshmanager.cpp index b6c25bd93..29a297018 100644 --- a/components/detournavigator/navmeshmanager.cpp +++ b/components/detournavigator/navmeshmanager.cpp @@ -57,7 +57,7 @@ namespace DetourNavigator const AreaType areaType) { return mRecastMeshManager.updateObject(id, shape, transform, areaType, - [&] (const auto& tile) { addChangedTile(tile, ChangeType::update); }); + [&] (const TilePosition& tile) { addChangedTile(tile, ChangeType::update); }); } bool NavMeshManager::removeObject(const ObjectId id) From 89282d14aa8063f98a089c011d253900594ad774 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Apr 2020 16:20:03 +0300 Subject: [PATCH 41/58] Fix collision switch node mask (again) --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/nifosg/nifloader.cpp | 14 +++++++++++++- components/nifosg/nifloader.hpp | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index e631a8fd8..6b41854e0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -376,6 +376,7 @@ namespace MWRender mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); + NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index e77e6b34f..a3972b0e2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -177,7 +177,7 @@ namespace NifOsg void setEnabled(bool enabled) { - setNodeMask(enabled ? ~0 : 0); + setNodeMask(enabled ? ~0 : Loader::getIntersectionDisabledNodeMask()); } }; @@ -204,6 +204,18 @@ namespace NifOsg return sHiddenNodeMask; } + unsigned int Loader::sIntersectionDisabledNodeMask = ~0; + + void Loader::setIntersectionDisabledNodeMask(unsigned int mask) + { + sIntersectionDisabledNodeMask = mask; + } + + unsigned int Loader::getIntersectionDisabledNodeMask() + { + return sIntersectionDisabledNodeMask; + } + class LoaderImpl { public: diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 6168bb474..4de9027b8 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -79,8 +79,14 @@ namespace NifOsg static void setHiddenNodeMask(unsigned int mask); static unsigned int getHiddenNodeMask(); + // Set the mask to use for nodes that ignore the crosshair intersection. The default is the default node mask. + // This is used for NiCollisionSwitch nodes with NiCollisionSwitch state set to disabled. + static void setIntersectionDisabledNodeMask(unsigned int mask); + static unsigned int getIntersectionDisabledNodeMask(); + private: static unsigned int sHiddenNodeMask; + static unsigned int sIntersectionDisabledNodeMask; static bool sShowMarkers; }; From 844838c46ac27872133e9492a46786121017a55f Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 30 Apr 2020 00:12:39 +0300 Subject: [PATCH 42/58] Revert an invalid attempt to autoequip shields instead of torches --- CHANGELOG.md | 1 - apps/openmw/mwmechanics/actors.cpp | 5 ----- 2 files changed, 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97aa37f23..77b126d85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -217,7 +217,6 @@ Bug #5264: "Damage Fatigue" Magic Effect Can Bring Fatigue below 0 Bug #5269: Editor: Cell lighting in resaved cleaned content files is corrupted Bug #5278: Console command Show doesn't fall back to global variable after local var not found - Bug #5300: NPCs don't switch from torch to shield when starting combat Bug #5308: World map copying makes save loading much slower Bug #5313: Node properties of identical type are not applied in the correct order Bug #5326: Formatting issues in the settings.cfg diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 726b2a31f..b089d543a 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1235,11 +1235,6 @@ namespace MWMechanics if (heldIter != inventoryStore.end() && heldIter->getTypeName() != typeid(ESM::Light).name()) inventoryStore.unequipItem(*heldIter, ptr); } - else if (heldIter == inventoryStore.end() || heldIter->getTypeName() == typeid(ESM::Light).name()) - { - // For hostile NPCs, see if they have anything better to equip first - inventoryStore.autoEquip(ptr); - } heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); From 16f6c7b27fb878782b9bc449dce25a396f07a4ac Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Apr 2020 14:14:53 +0300 Subject: [PATCH 43/58] Use the new option to treat TGA files as TGA 1.0 --- components/resource/imagemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/resource/imagemanager.cpp b/components/resource/imagemanager.cpp index c1d71ee00..41ce999e0 100644 --- a/components/resource/imagemanager.cpp +++ b/components/resource/imagemanager.cpp @@ -47,7 +47,7 @@ namespace Resource ImageManager::ImageManager(const VFS::Manager *vfs) : ResourceManager(vfs) , mWarningImage(createWarningImage()) - , mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba")) + , mOptions(new osgDB::Options("dds_flip dds_dxt1_detect_rgba ignoreTga2Fields")) { } From 63fe02b1ba5a5618afa5ef01b01e87f0c87ae601 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Apr 2020 15:50:50 +0300 Subject: [PATCH 44/58] CollisionSwitch and Switch/LOD node fixes Properly apply transformations to both switch and LOD nodes Allow both NiSwitchNode and NiLODNode to be the root node Properly add CollisionSwitch into the scene graph --- components/nifosg/nifloader.cpp | 38 ++++++++++++++++++++---------- components/sceneutil/serialize.cpp | 1 + 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a3972b0e2..e49e672d2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -170,6 +170,17 @@ namespace NifOsg class CollisionSwitch : public osg::MatrixTransform { public: + CollisionSwitch() : osg::MatrixTransform() + { + } + + CollisionSwitch(const CollisionSwitch& copy, const osg::CopyOp& copyop) + : osg::MatrixTransform(copy, copyop) + { + } + + META_Node(NifOsg, CollisionSwitch) + CollisionSwitch(const osg::Matrixf& transformations, bool enabled) : osg::MatrixTransform(transformations) { setEnabled(enabled); @@ -472,17 +483,8 @@ namespace NifOsg osg::ref_ptr node; osg::Object::DataVariance dataVariance = osg::Object::UNSPECIFIED; - // TODO: it is unclear how to handle transformations of LOD nodes and controllers for them. switch (nifNode->recType) { - case Nif::RC_NiLODNode: - { - const Nif::NiLODNode* niLodNode = static_cast(nifNode); - node = handleLodNode(niLodNode); - dataVariance = osg::Object::DYNAMIC; - break; - } - case Nif::RC_NiSwitchNode: case Nif::RC_NiAutoNormalParticles: case Nif::RC_NiRotatingParticles: // Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children. @@ -663,6 +665,11 @@ namespace NifOsg && !nifNode->controller.empty() && node->getDataVariance() == osg::Object::DYNAMIC) handleNodeControllers(nifNode, static_cast(node.get()), animflags); + // LOD and Switch nodes must be wrapped by a transform (the current node) to support transformations properly + // and we need to attach their children to the osg::LOD/osg::Switch nodes + // but we must return that transform to the caller of handleNode instead of the actual LOD/Switch nodes. + osg::ref_ptr currentNode = node; + if (nifNode->recType == Nif::RC_NiSwitchNode) { const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); @@ -673,7 +680,14 @@ namespace NifOsg else if (niSwitchNode->name == Constants::HerbalismLabel && !SceneUtil::hasUserDescription(rootNode, Constants::HerbalismLabel)) rootNode->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); - node = switchNode; + currentNode = switchNode; + } + else if (nifNode->recType == Nif::RC_NiLODNode) + { + const Nif::NiLODNode* niLodNode = static_cast(nifNode); + osg::ref_ptr lodNode = handleLodNode(niLodNode); + node->addChild(lodNode); + currentNode = lodNode; } const Nif::NiNode *ninode = dynamic_cast(nifNode); @@ -683,14 +697,14 @@ namespace NifOsg for (size_t i = 0; i < effects.length(); ++i) { if (!effects[i].empty()) - handleEffect(effects[i].getPtr(), node, imageManager); + handleEffect(effects[i].getPtr(), currentNode, imageManager); } const Nif::NodeList &children = ninode->children; for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - handleNode(children[i].getPtr(), node, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode); + handleNode(children[i].getPtr(), currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, isAnimated, textKeys, rootNode); } } diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index f84a19876..60f096a72 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -131,6 +131,7 @@ void registerSerializers() "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", + "NifOsg::CollisionSwitch", "osgMyGUI::Drawable", "osg::DrawCallback", "osgOQ::ClearQueriesCallback", From 957d2a890f769feee80815c2ce0a310033d5645d Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Apr 2020 17:14:50 +0300 Subject: [PATCH 45/58] Ignore empty children of osg::LOD and osg::Switch like in OSG --- components/sceneutil/optimizer.cpp | 21 +++++---------------- components/sceneutil/optimizer.hpp | 2 -- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/components/sceneutil/optimizer.cpp b/components/sceneutil/optimizer.cpp index e8ebed868..487126627 100644 --- a/components/sceneutil/optimizer.cpp +++ b/components/sceneutil/optimizer.cpp @@ -735,20 +735,6 @@ bool Optimizer::CombineStaticTransformsVisitor::removeTransforms(osg::Node* node // RemoveEmptyNodes. //////////////////////////////////////////////////////////////////////////// -void Optimizer::RemoveEmptyNodesVisitor::apply(osg::Switch& switchNode) -{ - // We should keep all switch child nodes since they reflect different switch states. - for (unsigned int i=0; i0) @@ -787,8 +773,11 @@ void Optimizer::RemoveEmptyNodesVisitor::removeEmptyNodes() ++pitr) { osg::Group* parent = *pitr; - parent->removeChild(nodeToRemove.get()); - if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent); + if (!parent->asSwitch() && !dynamic_cast(parent)) + { + parent->removeChild(nodeToRemove.get()); + if (parent->getNumChildren()==0 && isOperationPermissibleForObject(parent)) newEmptyGroups.insert(parent); + } } } diff --git a/components/sceneutil/optimizer.hpp b/components/sceneutil/optimizer.hpp index 9974e7097..6dd4394d1 100644 --- a/components/sceneutil/optimizer.hpp +++ b/components/sceneutil/optimizer.hpp @@ -321,8 +321,6 @@ class Optimizer BaseOptimizerVisitor(optimizer, REMOVE_REDUNDANT_NODES) {} virtual void apply(osg::Group& group); - virtual void apply(osg::LOD& lod); - virtual void apply(osg::Switch& switchNode); void removeEmptyNodes(); From 10daadefbea1f62a818c339dc7f3d69a858bc8a6 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 30 Apr 2020 21:57:22 +0200 Subject: [PATCH 46/58] Add missing include --- components/detournavigator/offmeshconnectionsmanager.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/detournavigator/offmeshconnectionsmanager.hpp b/components/detournavigator/offmeshconnectionsmanager.hpp index 30d7976ae..155ce3296 100644 --- a/components/detournavigator/offmeshconnectionsmanager.hpp +++ b/components/detournavigator/offmeshconnectionsmanager.hpp @@ -13,6 +13,7 @@ #include +#include #include #include #include From 87ba0bb0e044e5d8a17eee40d20a7ffb71e53df6 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Fri, 1 May 2020 16:22:07 +0300 Subject: [PATCH 47/58] Fix usage of uninitialized weapon type in equipmentChanged() --- 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 a797a9876..9c27b3bb0 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -1127,7 +1127,7 @@ void NpcAnimation::equipmentChanged() static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); if (shieldSheathing) { - int weaptype; + int weaptype = ESM::Weapon::None; MWMechanics::getActiveWeapon(mPtr, &weaptype); showCarriedLeft(updateCarriedLeftVisible(weaptype)); } From 6d8debe009ea0a9cc9ae4253b4a45857eddb4721 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 May 2020 17:10:06 +0200 Subject: [PATCH 48/58] Initialize variable without reading itself --- apps/openmw/mwrender/npcanimation.cpp | 14 ++++++++++---- apps/openmw/mwrender/npcanimation.hpp | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index a797a9876..629b10ba4 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -266,16 +266,22 @@ void HeadAnimationTime::setBlinkStop(float value) // ---------------------------------------------------- -NpcAnimation::NpcType NpcAnimation::getNpcType() +NpcAnimation::NpcType NpcAnimation::getNpcType() const { const MWWorld::Class &cls = mPtr.getClass(); // Dead vampires should typically stay vampires. if (mNpcType == Type_Vampire && cls.getNpcStats(mPtr).isDead() && !cls.getNpcStats(mPtr).isWerewolf()) return mNpcType; + return getNpcType(mPtr); +} + +NpcAnimation::NpcType NpcAnimation::getNpcType(const MWWorld::Ptr& ptr) +{ + const MWWorld::Class &cls = ptr.getClass(); NpcAnimation::NpcType curType = Type_Normal; - if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) + if (cls.getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0) curType = Type_Vampire; - if (cls.getNpcStats(mPtr).isWerewolf()) + if (cls.getNpcStats(ptr).isWerewolf()) curType = Type_Werewolf; return curType; @@ -326,7 +332,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, osg::ref_ptr par mViewMode(viewMode), mShowWeapons(false), mShowCarriedLeft(true), - mNpcType(getNpcType()), + mNpcType(getNpcType(ptr)), mFirstPersonFieldOfView(firstPersonFieldOfView), mSoundsDisabled(disableSounds), mAccurateAiming(false), diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 9e7969976..e102f5097 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -74,7 +74,7 @@ private: void updateNpcBase(); - NpcType getNpcType(); + NpcType getNpcType() const; PartHolderPtr insertBoundedPart(const std::string &model, const std::string &bonename, const std::string &bonefilter, bool enchantedGlow, osg::Vec4f* glowColor=nullptr); @@ -94,6 +94,7 @@ private: static bool isFirstPersonPart(const ESM::BodyPart* bodypart); static bool isFemalePart(const ESM::BodyPart* bodypart); + static NpcType getNpcType(const MWWorld::Ptr& ptr); protected: virtual void addControllers(); From b150d681a98344e850324888471df9c9973566fe Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 20 Feb 2020 15:05:50 -0800 Subject: [PATCH 49/58] Update same navmesh tile with limited frequency --- .../detournavigator/navigator.cpp | 38 ++++++++++++ .../detournavigator/asyncnavmeshupdater.cpp | 61 ++++++++++++++++--- .../detournavigator/asyncnavmeshupdater.hpp | 25 +++++++- components/detournavigator/settings.cpp | 1 + components/detournavigator/settings.hpp | 2 + .../reference/modding/settings/navigator.rst | 14 +++++ files/settings-default.cfg | 3 + 7 files changed, 132 insertions(+), 12 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 71b531513..276877508 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -73,6 +73,7 @@ namespace mSettings.mTrianglesPerChunk = 256; mSettings.mMaxPolys = 4096; mSettings.mMaxTilesNumber = 512; + mSettings.mMinUpdateInterval = std::chrono::milliseconds(50); mNavigator.reset(new NavigatorImpl(mSettings)); } }; @@ -766,4 +767,41 @@ namespace Vec3fEq(215, -215, 1.8782813549041748046875) )) << mPath; } + + TEST_F(DetourNavigatorNavigatorTest, update_changed_multiple_times_object_should_delay_navmesh_change) + { + const std::vector shapes(100, btVector3(64, 64, 64)); + + mNavigator->addAgent(mAgentHalfExtents); + + for (std::size_t i = 0; i < shapes.size(); ++i) + { + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32, i * 32, i * 32)); + mNavigator->addObject(ObjectId(&shapes[i]), shapes[i], transform); + } + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + const auto start = std::chrono::steady_clock::now(); + for (std::size_t i = 0; i < shapes.size(); ++i) + { + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 1, i * 32 + 1, i * 32 + 1)); + mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform); + } + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + for (std::size_t i = 0; i < shapes.size(); ++i) + { + const btTransform transform(btMatrix3x3::getIdentity(), btVector3(i * 32 + 2, i * 32 + 2, i * 32 + 2)); + mNavigator->updateObject(ObjectId(&shapes[i]), shapes[i], transform); + } + mNavigator->update(mPlayerPosition); + mNavigator->wait(); + + const auto duration = std::chrono::steady_clock::now() - start; + + EXPECT_GT(duration, mSettings.mMinUpdateInterval) + << std::chrono::duration_cast>(duration).count() << " ms"; + } } diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 1c07384b8..0683a43bc 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -89,6 +89,9 @@ namespace DetourNavigator job.mChangeType = changedTile.second; job.mDistanceToPlayer = getManhattanDistance(changedTile.first, playerTile); job.mDistanceToOrigin = getManhattanDistance(changedTile.first, TilePosition {0, 0}); + job.mProcessTime = job.mChangeType == ChangeType::update + ? mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] + mSettings.get().mMinUpdateInterval + : std::chrono::steady_clock::time_point(); mJobs.push(std::move(job)); } @@ -137,6 +140,8 @@ namespace DetourNavigator if (!processed) repost(std::move(*job)); } + else + cleanupLastUpdates(); } catch (const std::exception& e) { @@ -176,6 +181,7 @@ namespace DetourNavigator const auto locked = navMeshCacheItem->lockConst(); Log(Debug::Debug) << std::fixed << std::setprecision(2) << "Cache updated for agent=(" << job.mAgentHalfExtents << ")" << + " tile=" << job.mChangedTile << " status=" << status << " generation=" << locked->getGeneration() << " revision=" << locked->getNavMeshRevision() << @@ -195,12 +201,15 @@ namespace DetourNavigator while (true) { - const auto hasJob = [&] { return !mJobs.empty() || !threadQueue.mJobs.empty(); }; + const auto hasJob = [&] { + return (!mJobs.empty() && mJobs.top().mProcessTime <= std::chrono::steady_clock::now()) + || !threadQueue.mJobs.empty(); + }; if (!mHasJob.wait_for(lock, std::chrono::milliseconds(10), hasJob)) { mFirstStart.lock()->reset(); - if (getTotalThreadJobsUnsafe() == 0) + if (mJobs.empty() && getTotalThreadJobsUnsafe() == 0) mDone.notify_all(); return boost::none; } @@ -209,29 +218,40 @@ namespace DetourNavigator << threadQueue.mJobs.size() << " thread jobs by thread=" << std::this_thread::get_id(); auto job = threadQueue.mJobs.empty() - ? getJob(mJobs, mPushed) - : getJob(threadQueue.mJobs, threadQueue.mPushed); + ? getJob(mJobs, mPushed, true) + : getJob(threadQueue.mJobs, threadQueue.mPushed, false); - const auto owner = lockTile(job.mAgentHalfExtents, job.mChangedTile); + if (!job) + continue; + + const auto owner = lockTile(job->mAgentHalfExtents, job->mChangedTile); if (owner == threadId) return job; - postThreadJob(std::move(job), mThreadsQueues[owner]); + postThreadJob(std::move(*job), mThreadsQueues[owner]); } } - AsyncNavMeshUpdater::Job AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed) + boost::optional AsyncNavMeshUpdater::getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate) { - auto job = jobs.top(); + const auto now = std::chrono::steady_clock::now(); + + if (jobs.top().mProcessTime > now) + return {}; + + Job job = std::move(jobs.top()); jobs.pop(); + if (changeLastUpdate && job.mChangeType == ChangeType::update) + mLastUpdates[job.mAgentHalfExtents][job.mChangedTile] = now; + const auto it = pushed.find(job.mAgentHalfExtents); it->second.erase(job.mChangedTile); if (it->second.empty()) pushed.erase(it); - return job; + return {std::move(job)}; } void AsyncNavMeshUpdater::writeDebugFiles(const Job& job, const RecastMesh* recastMesh) const @@ -344,4 +364,27 @@ namespace DetourNavigator return std::accumulate(mThreadsQueues.begin(), mThreadsQueues.end(), std::size_t(0), [] (auto r, const auto& v) { return r + v.second.mJobs.size(); }); } + + void AsyncNavMeshUpdater::cleanupLastUpdates() + { + const auto now = std::chrono::steady_clock::now(); + + const std::lock_guard lock(mMutex); + + for (auto agent = mLastUpdates.begin(); agent != mLastUpdates.end();) + { + for (auto tile = agent->second.begin(); tile != agent->second.end();) + { + if (now - tile->second > mSettings.get().mMinUpdateInterval) + tile = agent->second.erase(tile); + else + ++tile; + } + + if (agent->second.empty()) + agent = mLastUpdates.erase(agent); + else + ++agent; + } + } } diff --git a/components/detournavigator/asyncnavmeshupdater.hpp b/components/detournavigator/asyncnavmeshupdater.hpp index 6a3799969..4debcd6cd 100644 --- a/components/detournavigator/asyncnavmeshupdater.hpp +++ b/components/detournavigator/asyncnavmeshupdater.hpp @@ -32,6 +32,21 @@ namespace DetourNavigator update = 3, }; + inline std::ostream& operator <<(std::ostream& stream, ChangeType value) + { + switch (value) { + case ChangeType::remove: + return stream << "ChangeType::remove"; + case ChangeType::mixed: + return stream << "ChangeType::mixed"; + case ChangeType::add: + return stream << "ChangeType::add"; + case ChangeType::update: + return stream << "ChangeType::update"; + } + return stream << "ChangeType::" << static_cast(value); + } + class AsyncNavMeshUpdater { public: @@ -56,10 +71,11 @@ namespace DetourNavigator ChangeType mChangeType; int mDistanceToPlayer; int mDistanceToOrigin; + std::chrono::steady_clock::time_point mProcessTime; - std::tuple getPriority() const + std::tuple getPriority() const { - return std::make_tuple(mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin); + return std::make_tuple(mProcessTime, mTryNumber, mChangeType, mDistanceToPlayer, mDistanceToOrigin); } friend inline bool operator <(const Job& lhs, const Job& rhs) @@ -93,6 +109,7 @@ namespace DetourNavigator Misc::ScopeGuarded> mFirstStart; NavMeshTilesCache mNavMeshTilesCache; Misc::ScopeGuarded>> mProcessingTiles; + std::map> mLastUpdates; std::map mThreadsQueues; std::vector mThreads; @@ -102,7 +119,7 @@ namespace DetourNavigator boost::optional getNextJob(); - static Job getJob(Jobs& jobs, Pushed& pushed); + boost::optional getJob(Jobs& jobs, Pushed& pushed, bool changeLastUpdate); void postThreadJob(Job&& job, Queue& queue); @@ -117,6 +134,8 @@ namespace DetourNavigator void unlockTile(const osg::Vec3f& agentHalfExtents, const TilePosition& changedTile); inline std::size_t getTotalThreadJobsUnsafe() const; + + void cleanupLastUpdates(); }; } diff --git a/components/detournavigator/settings.cpp b/components/detournavigator/settings.cpp index 735194dba..49aec41ff 100644 --- a/components/detournavigator/settings.cpp +++ b/components/detournavigator/settings.cpp @@ -40,6 +40,7 @@ namespace DetourNavigator navigatorSettings.mNavMeshPathPrefix = ::Settings::Manager::getString("nav mesh path prefix", "Navigator"); navigatorSettings.mEnableRecastMeshFileNameRevision = ::Settings::Manager::getBool("enable recast mesh file name revision", "Navigator"); navigatorSettings.mEnableNavMeshFileNameRevision = ::Settings::Manager::getBool("enable nav mesh file name revision", "Navigator"); + navigatorSettings.mMinUpdateInterval = std::chrono::milliseconds(::Settings::Manager::getInt("min update interval ms", "Navigator")); return navigatorSettings; } diff --git a/components/detournavigator/settings.hpp b/components/detournavigator/settings.hpp index dc0e5dc5a..939d825a5 100644 --- a/components/detournavigator/settings.hpp +++ b/components/detournavigator/settings.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace DetourNavigator { @@ -38,6 +39,7 @@ namespace DetourNavigator std::size_t mTrianglesPerChunk = 0; std::string mRecastMeshPathPrefix; std::string mNavMeshPathPrefix; + std::chrono::milliseconds mMinUpdateInterval; }; boost::optional makeSettingsFromSettingsManager(); diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index c7817b6e8..af40ac750 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -74,6 +74,20 @@ Game will not eat all memory at once. Memory will be consumed in approximately linear dependency from number of nav mesh updates. But only for new locations or already dropped from cache. +min update interval ms +---------------- + +:Type: integer +:Range: >= 0 +:Default: 250 + +Minimum time duration required to pass before next navmesh update for the same tile in milliseconds. +Only tiles affected where objects are transformed. +Next update for tile with added or removed object will not be delayed. +Visible ingame effect is navmesh update around opening or closing door. +Primary usage is for rotating signs like in Seyda Neen at Arrille's Tradehouse entrance. +Decreasing this value may increase CPU usage by background threads. + Developer's settings ******************** diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 6703e7732..5b587776c 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -776,6 +776,9 @@ enable recast mesh render = false # Max number of navmesh tiles (value >= 0) max tiles number = 512 +# Min time duration for the same tile update in milliseconds (value >= 0) +min update interval ms = 250 + [Shadows] # Enable or disable shadows. Bear in mind that this will force OpenMW to use shaders as if "[Shaders]/force shaders" was set to true. From edf002aa97854b6f909829d2be986dec638a506a Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 May 2020 19:11:04 +0200 Subject: [PATCH 50/58] Rename argument shaderTemplate to templateName --- components/shader/shadermanager.cpp | 50 ++++++++++++++--------------- components/shader/shadermanager.hpp | 2 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/components/shader/shadermanager.cpp b/components/shader/shadermanager.cpp index c2126275f..10f2de819 100644 --- a/components/shader/shadermanager.cpp +++ b/components/shader/shadermanager.cpp @@ -58,7 +58,7 @@ namespace Shader return true; } - bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& shaderTemplate) + bool parseIncludes(boost::filesystem::path shaderPath, std::string& source, const std::string& templateName) { Misc::StringUtils::replaceAll(source, "\r\n", "\n"); @@ -70,13 +70,13 @@ namespace Shader size_t start = source.find('"', foundPos); if (start == std::string::npos || start == source.size()-1) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Invalid #include"; + Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include"; return false; } size_t end = source.find('"', start+1); if (end == std::string::npos) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Invalid #include"; + Log(Debug::Error) << "Shader " << templateName << " error: Invalid #include"; return false; } std::string includeFilename = source.substr(start+1, end-(start+1)); @@ -85,7 +85,7 @@ namespace Shader includeFstream.open(includePath); if (includeFstream.fail()) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Failed to open include " << includePath.string(); + Log(Debug::Error) << "Shader " << templateName << " error: Failed to open include " << includePath.string(); return false; } @@ -120,14 +120,14 @@ namespace Shader if (includedFiles.insert(includePath).second == false) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Detected cyclic #includes"; + Log(Debug::Error) << "Shader " << templateName << " error: Detected cyclic #includes"; return false; } } return true; } - bool parseFors(std::string& source, const std::string& shaderTemplate) + bool parseFors(std::string& source, const std::string& templateName) { const char escapeCharacter = '$'; size_t foundPos = 0; @@ -136,13 +136,13 @@ namespace Shader size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); if (endPos == std::string::npos) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF"; + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string command = source.substr(foundPos + 1, endPos - (foundPos + 1)); if (command != "foreach") { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unknown shader directive: $" << command; + Log(Debug::Error) << "Shader " << templateName << " error: Unknown shader directive: $" << command; return false; } @@ -150,7 +150,7 @@ namespace Shader size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); if (iterNameEnd == std::string::npos) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF"; + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string iteratorName = "$" + source.substr(iterNameStart, iterNameEnd - iterNameStart); @@ -159,7 +159,7 @@ namespace Shader size_t listEnd = source.find_first_of("\n\r", listStart); if (listEnd == std::string::npos) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF"; + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string list = source.substr(listStart, listEnd - listStart); @@ -171,7 +171,7 @@ namespace Shader size_t contentEnd = source.find("$endforeach", contentStart); if (contentEnd == std::string::npos) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF"; + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string content = source.substr(contentStart, contentEnd - contentStart); @@ -211,7 +211,7 @@ namespace Shader } bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, - const ShaderManager::DefineMap& globalDefines, const std::string& shaderTemplate) + const ShaderManager::DefineMap& globalDefines, const std::string& templateName) { const char escapeCharacter = '@'; size_t foundPos = 0; @@ -221,7 +221,7 @@ namespace Shader size_t endPos = source.find_first_of(" \n\r()[].;,", foundPos); if (endPos == std::string::npos) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF"; + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } std::string define = source.substr(foundPos+1, endPos - (foundPos+1)); @@ -234,7 +234,7 @@ namespace Shader size_t iterNameEnd = source.find_first_of(" \n\r()[].;,", iterNameStart); if (iterNameEnd == std::string::npos) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Unexpected EOF"; + Log(Debug::Error) << "Shader " << templateName << " error: Unexpected EOF"; return false; } forIterators.push_back(source.substr(iterNameStart, iterNameEnd - iterNameStart)); @@ -244,7 +244,7 @@ namespace Shader source.replace(foundPos, 1, "$"); if (forIterators.empty()) { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: endforeach without foreach"; + Log(Debug::Error) << "Shader " << templateName << " error: endforeach without foreach"; return false; } else @@ -264,22 +264,22 @@ namespace Shader } else { - Log(Debug::Error) << "Shader " << shaderTemplate << " error: Undefined " << define; + Log(Debug::Error) << "Shader " << templateName << " error: Undefined " << define; return false; } } return true; } - osg::ref_ptr ShaderManager::getShader(const std::string &shaderTemplate, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) + osg::ref_ptr ShaderManager::getShader(const std::string &templateName, const ShaderManager::DefineMap &defines, osg::Shader::Type shaderType) { OpenThreads::ScopedLock lock(mMutex); // read the template if we haven't already - TemplateMap::iterator templateIt = mShaderTemplates.find(shaderTemplate); + TemplateMap::iterator templateIt = mShaderTemplates.find(templateName); if (templateIt == mShaderTemplates.end()) { - boost::filesystem::path p = (boost::filesystem::path(mPath) / shaderTemplate); + boost::filesystem::path p = (boost::filesystem::path(mPath) / templateName); boost::filesystem::ifstream stream; stream.open(p); if (stream.fail()) @@ -293,20 +293,20 @@ namespace Shader // parse includes std::string source = buffer.str(); if (!addLineDirectivesAfterConditionalBlocks(source) - || !parseIncludes(boost::filesystem::path(mPath), source, shaderTemplate)) + || !parseIncludes(boost::filesystem::path(mPath), source, templateName)) return nullptr; - templateIt = mShaderTemplates.insert(std::make_pair(shaderTemplate, source)).first; + templateIt = mShaderTemplates.insert(std::make_pair(templateName, source)).first; } - ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(shaderTemplate, defines)); + ShaderMap::iterator shaderIt = mShaders.find(std::make_pair(templateName, defines)); if (shaderIt == mShaders.end()) { std::string shaderSource = templateIt->second; - if (!parseDefines(shaderSource, defines, mGlobalDefines, shaderTemplate) || !parseFors(shaderSource, shaderTemplate)) + if (!parseDefines(shaderSource, defines, mGlobalDefines, templateName) || !parseFors(shaderSource, templateName)) { // Add to the cache anyway to avoid logging the same error over and over. - mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), nullptr)); + mShaders.insert(std::make_pair(std::make_pair(templateName, defines), nullptr)); return nullptr; } @@ -316,7 +316,7 @@ namespace Shader static unsigned int counter = 0; shader->setName(std::to_string(counter++)); - shaderIt = mShaders.insert(std::make_pair(std::make_pair(shaderTemplate, defines), shader)).first; + shaderIt = mShaders.insert(std::make_pair(std::make_pair(templateName, defines), shader)).first; } return shaderIt->second; } diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index 05775edb6..dbe989476 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -30,7 +30,7 @@ namespace Shader /// @param shaderType The type of shader (usually vertex or fragment shader). /// @note May return nullptr on failure. /// @note Thread safe. - osg::ref_ptr getShader(const std::string& shaderTemplate, const DefineMap& defines, osg::Shader::Type shaderType); + 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); From ca649003ed74c81100ac8b41aed8dad9f19094cb Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 May 2020 23:01:47 +0200 Subject: [PATCH 51/58] Use googletest 1.10.0 To get support for INSTANTIATE_TEST_SUITE_P macro --- CI/build_googletest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/build_googletest.sh b/CI/build_googletest.sh index ee89ebda6..0ffda7f9b 100755 --- a/CI/build_googletest.sh +++ b/CI/build_googletest.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -git clone -b release-1.8.1 https://github.com/google/googletest.git +git clone -b release-1.10.0 https://github.com/google/googletest.git cd googletest mkdir build cd build From 1f3dfaedcc12c524c1e5d4a59517a0170f8d3592 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 1 May 2020 23:03:13 +0200 Subject: [PATCH 52/58] Add tests for ShaderManager --- apps/openmw_test_suite/CMakeLists.txt | 4 + .../openmw_test_suite/shader/parsedefines.cpp | 191 ++++++++++++++ apps/openmw_test_suite/shader/parsefors.cpp | 94 +++++++ .../shader/shadermanager.cpp | 240 ++++++++++++++++++ components/shader/shadermanager.hpp | 4 + 5 files changed, 533 insertions(+) create mode 100644 apps/openmw_test_suite/shader/parsedefines.cpp create mode 100644 apps/openmw_test_suite/shader/parsefors.cpp create mode 100644 apps/openmw_test_suite/shader/shadermanager.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index e216ec759..cd2d2e80a 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -27,6 +27,10 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/tilecachedrecastmeshmanager.cpp settings/parser.cpp + + shader/parsedefines.cpp + shader/parsefors.cpp + shader/shadermanager.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/shader/parsedefines.cpp b/apps/openmw_test_suite/shader/parsedefines.cpp new file mode 100644 index 000000000..65b4380a7 --- /dev/null +++ b/apps/openmw_test_suite/shader/parsedefines.cpp @@ -0,0 +1,191 @@ +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace Shader; + + using DefineMap = ShaderManager::DefineMap; + + struct ShaderParseDefinesTest : Test + { + std::string mSource; + const std::string mName = "shader"; + DefineMap mDefines; + DefineMap mGlobalDefines; + }; + + TEST_F(ShaderParseDefinesTest, empty_should_succeed) + { + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, ""); + } + + TEST_F(ShaderParseDefinesTest, should_fail_for_absent_define) + { + mSource = "@foo\n"; + ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "@foo\n"); + } + + TEST_F(ShaderParseDefinesTest, should_replace_by_existing_define) + { + mDefines["foo"] = "42"; + mSource = "@foo\n"; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "42\n"); + } + + TEST_F(ShaderParseDefinesTest, should_replace_by_existing_global_define) + { + mGlobalDefines["foo"] = "42"; + mSource = "@foo\n"; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "42\n"); + } + + TEST_F(ShaderParseDefinesTest, should_prefer_define_over_global_define) + { + mDefines["foo"] = "13"; + mGlobalDefines["foo"] = "42"; + mSource = "@foo\n"; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "13\n"); + } + + namespace SupportedTerminals + { + struct ShaderParseDefinesTest : ::ShaderParseDefinesTest, WithParamInterface {}; + + TEST_P(ShaderParseDefinesTest, support_defines_terminated_by) + { + mDefines["foo"] = "13"; + mSource = "@foo" + std::string(1, GetParam()); + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "13" + std::string(1, GetParam())); + } + + INSTANTIATE_TEST_SUITE_P( + SupportedTerminals, + ShaderParseDefinesTest, + Values(' ', '\n', '\r', '(', ')', '[', ']', '.', ';', ',') + ); + } + + TEST_F(ShaderParseDefinesTest, should_not_support_define_ending_with_source) + { + mDefines["foo"] = "42"; + mSource = "@foo"; + ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "@foo"); + } + + TEST_F(ShaderParseDefinesTest, should_replace_all_matched_values) + { + mDefines["foo"] = "42"; + mSource = "@foo @foo "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "42 42 "); + } + + TEST_F(ShaderParseDefinesTest, should_support_define_with_empty_name) + { + mDefines[""] = "42"; + mSource = "@ "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "42 "); + } + + TEST_F(ShaderParseDefinesTest, should_replace_all_found_defines) + { + mDefines["foo"] = "42"; + mDefines["bar"] = "13"; + mDefines["baz"] = "55"; + mSource = "@foo @bar "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "42 13 "); + } + + TEST_F(ShaderParseDefinesTest, should_fail_on_foreach_without_endforeach) + { + mSource = "@foreach "; + ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$foreach "); + } + + TEST_F(ShaderParseDefinesTest, should_fail_on_endforeach_without_foreach) + { + mSource = "@endforeach "; + ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$endforeach "); + } + + TEST_F(ShaderParseDefinesTest, should_replace_at_sign_by_dollar_for_foreach_endforeach) + { + mSource = "@foreach @endforeach "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$foreach $endforeach "); + } + + TEST_F(ShaderParseDefinesTest, should_succeed_on_unmatched_nested_foreach) + { + mSource = "@foreach @foreach @endforeach "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$foreach $foreach $endforeach "); + } + + TEST_F(ShaderParseDefinesTest, should_fail_on_unmatched_nested_endforeach) + { + mSource = "@foreach @endforeach @endforeach "; + ASSERT_FALSE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$foreach $endforeach $endforeach "); + } + + TEST_F(ShaderParseDefinesTest, should_support_nested_foreach) + { + mSource = "@foreach @foreach @endforeach @endforeach "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$foreach $foreach $endforeach $endforeach "); + } + + TEST_F(ShaderParseDefinesTest, should_support_foreach_variable) + { + mSource = "@foreach foo @foo @endforeach "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$foreach foo $foo $endforeach "); + } + + TEST_F(ShaderParseDefinesTest, should_not_replace_foreach_variable_by_define) + { + mDefines["foo"] = "42"; + mSource = "@foreach foo @foo @endforeach "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$foreach foo $foo $endforeach "); + } + + TEST_F(ShaderParseDefinesTest, should_support_nested_foreach_with_variable) + { + mSource = "@foreach foo @foo @foreach bar @bar @endforeach @endforeach "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "$foreach foo $foo $foreach bar $bar $endforeach $endforeach "); + } + + TEST_F(ShaderParseDefinesTest, should_not_support_single_line_comments_for_defines) + { + mDefines["foo"] = "42"; + mSource = "@foo // @foo\n"; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "42 // 42\n"); + } + + TEST_F(ShaderParseDefinesTest, should_not_support_multiline_comments_for_defines) + { + mDefines["foo"] = "42"; + mSource = "/* @foo */ @foo "; + ASSERT_TRUE(parseDefines(mSource, mDefines, mGlobalDefines, mName)); + EXPECT_EQ(mSource, "/* 42 */ 42 "); + } +} diff --git a/apps/openmw_test_suite/shader/parsefors.cpp b/apps/openmw_test_suite/shader/parsefors.cpp new file mode 100644 index 000000000..330feb172 --- /dev/null +++ b/apps/openmw_test_suite/shader/parsefors.cpp @@ -0,0 +1,94 @@ +#include + +#include +#include + +namespace +{ + using namespace testing; + using namespace Shader; + + using DefineMap = ShaderManager::DefineMap; + + struct ShaderParseForsTest : Test + { + std::string mSource; + const std::string mName = "shader"; + }; + + TEST_F(ShaderParseForsTest, empty_should_succeed) + { + ASSERT_TRUE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, ""); + } + + TEST_F(ShaderParseForsTest, should_fail_for_single_escape_symbol) + { + mSource = "$"; + ASSERT_FALSE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "$"); + } + + TEST_F(ShaderParseForsTest, should_fail_on_first_found_escaped_not_foreach) + { + mSource = "$foo "; + ASSERT_FALSE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "$foo "); + } + + TEST_F(ShaderParseForsTest, should_fail_on_absent_foreach_variable) + { + mSource = "$foreach "; + ASSERT_FALSE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "$foreach "); + } + + TEST_F(ShaderParseForsTest, should_fail_on_unmatched_after_variable) + { + mSource = "$foreach foo "; + ASSERT_FALSE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "$foreach foo "); + } + + TEST_F(ShaderParseForsTest, should_fail_on_absent_newline_after_foreach_list) + { + mSource = "$foreach foo 1,2,3 "; + ASSERT_FALSE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "$foreach foo 1,2,3 "); + } + + TEST_F(ShaderParseForsTest, should_fail_on_absent_endforeach_after_newline) + { + mSource = "$foreach foo 1,2,3\n"; + ASSERT_FALSE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "$foreach foo 1,2,3\n"); + } + + TEST_F(ShaderParseForsTest, should_replace_complete_foreach_by_line_number) + { + mSource = "$foreach foo 1,2,3\n$endforeach"; + ASSERT_TRUE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "\n#line 3"); + } + + TEST_F(ShaderParseForsTest, should_replace_loop_variable) + { + mSource = "$foreach foo 1,2,3\n$foo\n$endforeach"; + ASSERT_TRUE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "1\n2\n3\n\n#line 4"); + } + + TEST_F(ShaderParseForsTest, should_count_line_number_from_existing) + { + mSource = "$foreach foo 1,2,3\n#line 10\n$foo\n$endforeach"; + ASSERT_TRUE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "#line 10\n1\n#line 10\n2\n#line 10\n3\n\n#line 12"); + } + + TEST_F(ShaderParseForsTest, should_not_support_nested_loops) + { + mSource = "$foreach foo 1,2\n$foo\n$foreach bar 1,2\n$bar\n$endforeach\n$endforeach"; + ASSERT_FALSE(parseFors(mSource, mName)); + EXPECT_EQ(mSource, "1\n1\n2\n$foreach bar 1,2\n1\n\n#line 6\n2\n2\n$foreach bar 1,2\n2\n\n#line 6\n\n#line 7"); + } +} diff --git a/apps/openmw_test_suite/shader/shadermanager.cpp b/apps/openmw_test_suite/shader/shadermanager.cpp new file mode 100644 index 000000000..e823d5fe2 --- /dev/null +++ b/apps/openmw_test_suite/shader/shadermanager.cpp @@ -0,0 +1,240 @@ +#include + +#include + +#include + +namespace +{ + using namespace testing; + using namespace Shader; + + struct ShaderManagerTest : Test + { + ShaderManager mManager; + ShaderManager::DefineMap mDefines; + + ShaderManagerTest() + { + mManager.setShaderPath("."); + } + + template + void withShaderFile(const std::string& content, F&& f) + { + withShaderFile("", content, std::forward(f)); + } + + template + void withShaderFile(const std::string& suffix, const std::string& content, F&& f) + { + const auto path = UnitTest::GetInstance()->current_test_info()->name() + suffix + ".glsl"; + + { + boost::filesystem::ofstream stream; + stream.open(path); + stream << content; + stream.close(); + } + + f(path); + } + }; + + TEST_F(ShaderManagerTest, get_shader_with_empty_content_should_succeed) + { + const std::string content; + + withShaderFile(content, [this] (const std::string& templateName) { + EXPECT_TRUE(mManager.getShader(templateName, {}, osg::Shader::VERTEX)); + }); + } + + TEST_F(ShaderManagerTest, get_shader_should_not_change_source_without_template_parameters) + { + const std::string content = + "#version 120\n" + "void main() {}\n"; + + withShaderFile(content, [&] (const std::string& templateName) { + const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX); + ASSERT_TRUE(shader); + EXPECT_EQ(shader->getShaderSource(), content); + }); + } + + TEST_F(ShaderManagerTest, get_shader_should_replace_includes_with_content) + { + const std::string content0 = + "void foo() {}\n"; + + withShaderFile("_0", content0, [&] (const std::string& templateName0) { + const std::string content1 = + "#include \"" + templateName0 + "\"\n" + "void bar() { foo() }\n"; + + withShaderFile("_1", content1, [&] (const std::string& templateName1) { + const std::string content2 = + "#version 120\n" + "#include \"" + templateName1 + "\"\n" + "void main() { bar() }\n"; + + withShaderFile(content2, [&] (const std::string& templateName2) { + const auto shader = mManager.getShader(templateName2, mDefines, osg::Shader::VERTEX); + ASSERT_TRUE(shader); + const std::string expected = + "#version 120\n" + "#line 0 1\n" + "#line 0 2\n" + "void foo() {}\n" + "\n" + "#line 0 0\n" + "\n" + "void bar() { foo() }\n" + "\n" + "#line 2 0\n" + "\n" + "void main() { bar() }\n"; + EXPECT_EQ(shader->getShaderSource(), expected); + }); + }); + }); + } + + TEST_F(ShaderManagerTest, get_shader_should_replace_defines) + { + const std::string content = + "#version 120\n" + "#define FLAG @flag\n" + "void main() {}\n" + ; + + withShaderFile(content, [&] (const std::string& templateName) { + mDefines["flag"] = "1"; + const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX); + ASSERT_TRUE(shader); + const std::string expected = + "#version 120\n" + "#define FLAG 1\n" + "void main() {}\n"; + EXPECT_EQ(shader->getShaderSource(), expected); + }); + } + + TEST_F(ShaderManagerTest, get_shader_should_expand_loop) + { + const std::string content = + "#version 120\n" + "@foreach index @list\n" + " varying vec4 foo@index;\n" + "@endforeach\n" + "void main() {}\n" + ; + + withShaderFile(content, [&] (const std::string& templateName) { + mDefines["list"] = "1,2,3"; + const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX); + ASSERT_TRUE(shader); + const std::string expected = + "#version 120\n" + " varying vec4 foo1;\n" + " varying vec4 foo2;\n" + " varying vec4 foo3;\n" + "\n" + "#line 5\n" + "void main() {}\n"; + EXPECT_EQ(shader->getShaderSource(), expected); + }); + } + + TEST_F(ShaderManagerTest, get_shader_should_replace_loops_with_conditions) + { + const std::string content = + "#version 120\n" + "@foreach index @list\n" + " varying vec4 foo@index;\n" + "@endforeach\n" + "void main()\n" + "{\n" + "#ifdef BAR\n" + "@foreach index @list\n" + " foo@index = vec4(1.0);\n" + "@endforeach\n" + "#elif BAZ\n" + "@foreach index @list\n" + " foo@index = vec4(2.0);\n" + "@endforeach\n" + "#else\n" + "@foreach index @list\n" + " foo@index = vec4(3.0);\n" + "@endforeach\n" + "#endif\n" + "}\n" + ; + + withShaderFile(content, [&] (const std::string& templateName) { + mDefines["list"] = "1,2,3"; + const auto shader = mManager.getShader(templateName, mDefines, osg::Shader::VERTEX); + ASSERT_TRUE(shader); + const std::string expected = + "#version 120\n" + " varying vec4 foo1;\n" + " varying vec4 foo2;\n" + " varying vec4 foo3;\n" + "\n" + "#line 5\n" + "void main()\n" + "{\n" + "#ifdef BAR\n" + " foo1 = vec4(1.0);\n" + " foo2 = vec4(1.0);\n" + " foo3 = vec4(1.0);\n" + "\n" + "#line 11\n" + "#elif BAZ\n" + "#line 12\n" + " foo1 = vec4(2.0);\n" + " foo2 = vec4(2.0);\n" + " foo3 = vec4(2.0);\n" + "\n" + "#line 15\n" + "#else\n" + "#line 16\n" + " foo1 = vec4(3.0);\n" + " foo2 = vec4(3.0);\n" + " foo3 = vec4(3.0);\n" + "\n" + "#line 19\n" + "#endif\n" + "#line 20\n" + "}\n"; + EXPECT_EQ(shader->getShaderSource(), expected); + }); + } + + TEST_F(ShaderManagerTest, get_shader_should_fail_on_absent_template_parameters_in_single_line_comments) + { + const std::string content = + "#version 120\n" + "// #define FLAG @flag\n" + "void main() {}\n" + ; + + withShaderFile(content, [&] (const std::string& templateName) { + EXPECT_FALSE(mManager.getShader(templateName, mDefines, osg::Shader::VERTEX)); + }); + } + + TEST_F(ShaderManagerTest, get_shader_should_fail_on_absent_template_parameter_in_multi_line_comments) + { + const std::string content = + "#version 120\n" + "/* #define FLAG @flag */\n" + "void main() {}\n" + ; + + withShaderFile(content, [&] (const std::string& templateName) { + EXPECT_FALSE(mManager.getShader(templateName, mDefines, osg::Shader::VERTEX)); + }); + } +} diff --git a/components/shader/shadermanager.hpp b/components/shader/shadermanager.hpp index dbe989476..c602ac62b 100644 --- a/components/shader/shadermanager.hpp +++ b/components/shader/shadermanager.hpp @@ -63,6 +63,10 @@ namespace Shader OpenThreads::Mutex mMutex; }; + bool parseFors(std::string& source, const std::string& templateName); + + bool parseDefines(std::string& source, const ShaderManager::DefineMap& defines, + const ShaderManager::DefineMap& globalDefines, const std::string& templateName); } #endif From f9881b699c4f7b9026d83cd93c4214443df2d79a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 5 May 2020 19:18:23 +0200 Subject: [PATCH 53/58] remove redundant templating --- apps/openmw/mwscript/transformationextensions.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 5ad51f887..d28d9c373 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -396,7 +396,6 @@ namespace MWScript } }; - template class OpPlaceItemCell : public Interpreter::Opcode0 { public: @@ -450,7 +449,6 @@ namespace MWScript } }; - template class OpPlaceItem : public Interpreter::Opcode0 { public: @@ -750,8 +748,8 @@ namespace MWScript interpreter.installSegment5(Compiler::Transformation::opcodePositionExplicit,new OpPosition); interpreter.installSegment5(Compiler::Transformation::opcodePositionCell,new OpPositionCell); interpreter.installSegment5(Compiler::Transformation::opcodePositionCellExplicit,new OpPositionCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); - interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItemCell,new OpPlaceItemCell); + interpreter.installSegment5(Compiler::Transformation::opcodePlaceItem,new OpPlaceItem); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtPc,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMe,new OpPlaceAt); interpreter.installSegment5(Compiler::Transformation::opcodePlaceAtMeExplicit,new OpPlaceAt); From 19f12cb3fe7a9163d708212bb642efcf21aaf403 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 5 May 2020 19:37:15 +0200 Subject: [PATCH 54/58] remove magic numbers and casts --- apps/openmw/mwscript/aiextensions.cpp | 76 ++++++++++++--------------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index 66ce65aa9..79639197d 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -233,24 +233,23 @@ namespace MWScript template class OpGetAiSetting : public Interpreter::Opcode0 { - int mIndex; + MWMechanics::CreatureStats::AiSetting mIndex; public: - OpGetAiSetting(int index) : mIndex(index) {} + OpGetAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} virtual void execute (Interpreter::Runtime& runtime) { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting ( - (MWMechanics::CreatureStats::AiSetting)mIndex).getModified()); + runtime.push(ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getModified()); } }; template class OpModAiSetting : public Interpreter::Opcode0 { - int mIndex; + MWMechanics::CreatureStats::AiSetting mIndex; public: - OpModAiSetting(int index) : mIndex(index) {} + OpModAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} virtual void execute (Interpreter::Runtime& runtime) { @@ -258,19 +257,16 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWMechanics::CreatureStats::AiSetting setting - = MWMechanics::CreatureStats::AiSetting(mIndex); - - ptr.getClass().getCreatureStats (ptr).setAiSetting (setting, - ptr.getClass().getCreatureStats (ptr).getAiSetting (setting).getBase() + value); + ptr.getClass().getCreatureStats (ptr).setAiSetting (mIndex, + ptr.getClass().getCreatureStats (ptr).getAiSetting (mIndex).getBase() + value); } }; template class OpSetAiSetting : public Interpreter::Opcode0 { - int mIndex; + MWMechanics::CreatureStats::AiSetting mIndex; public: - OpSetAiSetting(int index) : mIndex(index) {} + OpSetAiSetting(MWMechanics::CreatureStats::AiSetting index) : mIndex(index) {} virtual void execute (Interpreter::Runtime& runtime) { @@ -278,11 +274,9 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - MWMechanics::CreatureStats::AiSetting setting = (MWMechanics::CreatureStats::AiSetting)mIndex; - - MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(setting); + MWMechanics::Stat stat = ptr.getClass().getCreatureStats(ptr).getAiSetting(mIndex); stat.setModified(value, 0); - ptr.getClass().getCreatureStats(ptr).setAiSetting(setting, stat); + ptr.getClass().getCreatureStats(ptr).setAiSetting(mIndex, stat); } }; @@ -541,32 +535,32 @@ namespace MWScript interpreter.installSegment5 (Compiler::Ai::opcodeStopCombatExplicit, new OpStopCombat); interpreter.installSegment5 (Compiler::Ai::opcodeToggleAI, new OpToggleAI); - interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(0)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(0)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting(1)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFightExplicit, new OpSetAiSetting(1)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFlee, new OpSetAiSetting(2)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetFleeExplicit, new OpSetAiSetting(2)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarm, new OpSetAiSetting(3)); - interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarmExplicit, new OpSetAiSetting(3)); + interpreter.installSegment5 (Compiler::Ai::opcodeSetHello, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); + interpreter.installSegment5 (Compiler::Ai::opcodeSetHelloExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); + interpreter.installSegment5 (Compiler::Ai::opcodeSetFight, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); + interpreter.installSegment5 (Compiler::Ai::opcodeSetFightExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); + interpreter.installSegment5 (Compiler::Ai::opcodeSetFlee, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); + interpreter.installSegment5 (Compiler::Ai::opcodeSetFleeExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); + interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarm, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); + interpreter.installSegment5 (Compiler::Ai::opcodeSetAlarmExplicit, new OpSetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - interpreter.installSegment5 (Compiler::Ai::opcodeModHello, new OpModAiSetting(0)); - interpreter.installSegment5 (Compiler::Ai::opcodeModHelloExplicit, new OpModAiSetting(0)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFight, new OpModAiSetting(1)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFightExplicit, new OpModAiSetting(1)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFlee, new OpModAiSetting(2)); - interpreter.installSegment5 (Compiler::Ai::opcodeModFleeExplicit, new OpModAiSetting(2)); - interpreter.installSegment5 (Compiler::Ai::opcodeModAlarm, new OpModAiSetting(3)); - interpreter.installSegment5 (Compiler::Ai::opcodeModAlarmExplicit, new OpModAiSetting(3)); + interpreter.installSegment5 (Compiler::Ai::opcodeModHello, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); + interpreter.installSegment5 (Compiler::Ai::opcodeModHelloExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); + interpreter.installSegment5 (Compiler::Ai::opcodeModFight, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); + interpreter.installSegment5 (Compiler::Ai::opcodeModFightExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); + interpreter.installSegment5 (Compiler::Ai::opcodeModFlee, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); + interpreter.installSegment5 (Compiler::Ai::opcodeModFleeExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); + interpreter.installSegment5 (Compiler::Ai::opcodeModAlarm, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); + interpreter.installSegment5 (Compiler::Ai::opcodeModAlarmExplicit, new OpModAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetHello, new OpGetAiSetting(0)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetHelloExplicit, new OpGetAiSetting(0)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFight, new OpGetAiSetting(1)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFightExplicit, new OpGetAiSetting(1)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFlee, new OpGetAiSetting(2)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting(2)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting(3)); - interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting(3)); + interpreter.installSegment5 (Compiler::Ai::opcodeGetHello, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); + interpreter.installSegment5 (Compiler::Ai::opcodeGetHelloExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Hello)); + interpreter.installSegment5 (Compiler::Ai::opcodeGetFight, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); + interpreter.installSegment5 (Compiler::Ai::opcodeGetFightExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Fight)); + interpreter.installSegment5 (Compiler::Ai::opcodeGetFlee, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); + interpreter.installSegment5 (Compiler::Ai::opcodeGetFleeExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Flee)); + interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarm, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); + interpreter.installSegment5 (Compiler::Ai::opcodeGetAlarmExplicit, new OpGetAiSetting(MWMechanics::CreatureStats::AiSetting::AI_Alarm)); interpreter.installSegment5 (Compiler::Ai::opcodeFace, new OpFace); interpreter.installSegment5 (Compiler::Ai::opcodeFaceExplicit, new OpFace); From 1bf2ddac4db6b266f92559d4bfc157375c4fb7b2 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 7 May 2020 15:35:34 +0300 Subject: [PATCH 55/58] Cleanup Move static variable declaration out of the loop Remove redundant boolean argument from applyDrawableProperties() Improve HeightCullCallback class formatting --- components/nifosg/nifloader.cpp | 6 +-- components/terrain/quadtreeworld.cpp | 2 +- components/terrain/world.hpp | 62 ++++++++++++++++------------ 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 72654da6d..00576943a 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1111,7 +1111,7 @@ namespace NifOsg std::vector drawableProps; collectDrawableProperties(nifNode, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, true, animflags, true); + applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); // particle system updater (after the emitters and affectors in the scene graph) // I think for correct culling needs to be *before* the ParticleSystem, though osg examples do it the other way @@ -1203,7 +1203,7 @@ namespace NifOsg // above the actual renderable would be tedious. std::vector drawableProps; collectDrawableProperties(nifNode, drawableProps); - applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags, false); + applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags); } void handleTriShape(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) @@ -1755,7 +1755,7 @@ namespace NifOsg } void applyDrawableProperties(osg::Node* node, const std::vector& properties, SceneUtil::CompositeStateSetUpdater* composite, - bool hasVertexColors, int animflags, bool particleMaterial) + bool hasVertexColors, int animflags) { osg::StateSet* stateset = node->getOrCreateStateSet(); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 0140ade49..c43a9a21b 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -323,6 +323,7 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: return; } cv->pushCurrentMask(); + static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr; for (unsigned int i=0; igetNumEntries(); ++i) { ViewData::Entry& entry = vd->getEntry(i); @@ -337,7 +338,6 @@ void updateWaterCullingView(HeightCullCallback* callback, ViewData* vd, osgUtil: continue; lowZ = bb._min.z(); - static bool debug = getenv("OPENMW_WATER_CULLING_DEBUG") != nullptr; if (!debug) break; osg::Box* b = new osg::Box; diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index fb6c45967..a69d03ca9 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -40,36 +40,46 @@ namespace Terrain class ChunkManager; class CompositeMapRenderer; -class HeightCullCallback : public osg::NodeCallback -{ -public: - HeightCullCallback() : mLowZ(-std::numeric_limits::max()), mHighZ(std::numeric_limits::max()), mMask(~0) {} - - void setLowZ(float z) + class HeightCullCallback : public osg::NodeCallback { - mLowZ = z; - } - float getLowZ() const { return mLowZ; } + public: + void setLowZ(float z) + { + mLowZ = z; + } + float getLowZ() const + { + return mLowZ; + } - void setHighZ(float highZ) - { - mHighZ = highZ; - } - float getHighZ() const { return mHighZ; } + void setHighZ(float highZ) + { + mHighZ = highZ; + } + float getHighZ() const + { + return mHighZ; + } - void setCullMask(unsigned int mask) { mMask = mask; } - unsigned int getCullMask() const { return mMask; } + void setCullMask(unsigned int mask) + { + mMask = mask; + } + unsigned int getCullMask() const + { + return mMask; + } - virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) - { - if (mLowZ <= mHighZ) - traverse(node, nv); - } -private: - float mLowZ; - float mHighZ; - unsigned int mMask; -}; + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + if (mLowZ <= mHighZ) + traverse(node, nv); + } + private: + float mLowZ{-std::numeric_limits::max()}; + float mHighZ{std::numeric_limits::max()}; + unsigned int mMask{~0u}; + }; /** * @brief A View is a collection of rendering objects that are visible from a given camera/intersection. From b19f53aab610429fe12e30abf4bc07a40965dc42 Mon Sep 17 00:00:00 2001 From: Sisah Date: Fri, 8 May 2020 15:55:22 +0200 Subject: [PATCH 56/58] Fix parallax and specular for android --- files/shaders/objects_fragment.glsl | 4 ++-- files/shaders/terrain_fragment.glsl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/files/shaders/objects_fragment.glsl b/files/shaders/objects_fragment.glsl index 31e929a90..bc6d03639 100644 --- a/files/shaders/objects_fragment.glsl +++ b/files/shaders/objects_fragment.glsl @@ -90,7 +90,7 @@ void main() vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; vec3 eyeDir = normalize(cameraPos - objectPos); - vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, (passTangent.w > 0) ? -1.f : 1.f); + vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, (passTangent.w > 0.0) ? -1.f : 1.f); adjustedDiffuseUV += offset; // only offset diffuse for now, other textures are more likely to be using a completely different UV set // TODO: check not working as the same UV buffer is being bound to different targets @@ -171,7 +171,7 @@ void main() #if @specularMap vec4 specTex = texture2D(specularMap, specularMapUV); - float shininess = specTex.a * 255; + float shininess = specTex.a * 255.0; vec3 matSpec = specTex.xyz; #else float shininess = gl_FrontMaterial.shininess; diff --git a/files/shaders/terrain_fragment.glsl b/files/shaders/terrain_fragment.glsl index b1917d33b..505061681 100644 --- a/files/shaders/terrain_fragment.glsl +++ b/files/shaders/terrain_fragment.glsl @@ -82,7 +82,7 @@ void main() #endif #if @specularMap - float shininess = 128; // TODO: make configurable + float shininess = 128.0; // TODO: make configurable vec3 matSpec = vec3(diffuseTex.a, diffuseTex.a, diffuseTex.a); #else float shininess = gl_FrontMaterial.shininess; From 039c9a37ebfbae90a07509f7fba4c5046747682c Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 9 May 2020 00:31:10 +0300 Subject: [PATCH 57/58] Verifier: Don't check race of non-skin body parts (bug #5400) Remove unnecessary flag field check Remove magic numbers --- CHANGELOG.md | 1 + apps/opencs/model/tools/bodypartcheck.cpp | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b126d85..56e778c0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Bug #5367: Selecting a spell on an enchanted item per hotkey always plays the equip sound Bug #5369: Spawnpoint in the Grazelands doesn't produce oversized creatures Bug #5370: Opening an unlocked but trapped door uses the key + Bug #5400: Editor: Verifier checks race of non-skin bodyparts Feature #5362: Show the soul gems' trapped soul in count dialog 0.46.0 diff --git a/apps/opencs/model/tools/bodypartcheck.cpp b/apps/opencs/model/tools/bodypartcheck.cpp index 26b807360..1490a8103 100644 --- a/apps/opencs/model/tools/bodypartcheck.cpp +++ b/apps/opencs/model/tools/bodypartcheck.cpp @@ -33,13 +33,10 @@ void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &message CSMWorld::UniversalId id( CSMWorld::UniversalId::Type_BodyPart, bodyPart.mId ); // Check BYDT - if (bodyPart.mData.mPart > 14 ) + if (bodyPart.mData.mPart >= ESM::BodyPart::MP_Count ) messages.add(id, "Invalid part", "", CSMDoc::Message::Severity_Error); - if (bodyPart.mData.mFlags > 3 ) - messages.add(id, "Invalid flags", "", CSMDoc::Message::Severity_Error); - - if (bodyPart.mData.mType > 2 ) + if (bodyPart.mData.mType > ESM::BodyPart::MT_Armor ) messages.add(id, "Invalid type", "", CSMDoc::Message::Severity_Error); // Check MODL @@ -48,9 +45,12 @@ void CSMTools::BodyPartCheckStage::perform (int stage, CSMDoc::Messages &message else if ( mMeshes.searchId( bodyPart.mModel ) == -1 ) messages.add(id, "Model '" + bodyPart.mModel + "' does not exist", "", CSMDoc::Message::Severity_Error); - // Check FNAM - if ( bodyPart.mRace.empty() ) - messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error); - else if ( mRaces.searchId( bodyPart.mRace ) == -1 ) - messages.add(id, "Race '" + bodyPart.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error); + // Check FNAM for skin body parts (for non-skin body parts it's meaningless) + if ( bodyPart.mData.mType == ESM::BodyPart::MT_Skin ) + { + if ( bodyPart.mRace.empty() ) + messages.add(id, "Race is missing", "", CSMDoc::Message::Severity_Error); + else if ( mRaces.searchId( bodyPart.mRace ) == -1 ) + messages.add(id, "Race '" + bodyPart.mRace + "' does not exist", "", CSMDoc::Message::Severity_Error); + } } From 45e6a03937010ce074a02669a1003e8c44c63a67 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 9 May 2020 02:01:55 +0300 Subject: [PATCH 58/58] Only reset dialogue history of dialogue GUI mode is gone --- apps/openmw/mwgui/dialogue.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index bb40bea33..76b219009 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -477,6 +477,8 @@ namespace MWGui void DialogueWindow::onClose() { + if (MWBase::Environment::get().getWindowManager()->containsMode(GM_Dialogue)) + return; // Reset history for (DialogueText* text : mHistoryContents) delete text;